-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBuild.js
More file actions
315 lines (247 loc) · 8.37 KB
/
Build.js
File metadata and controls
315 lines (247 loc) · 8.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
import * as FileSystem from "fs/promises"
import * as Path from "path"
import { Exec, PrintStdOut, PrintStdErr } from './Execute.js'
function SanitiseXcodeDestination(Destination)
{
// destination needs to be key=value
// generic/platform=iOS
// platform=macOS,arch=x86_64
// platform=iOS Simulator,name=iPhone 6,OS=9.1
// platform=macOS
if ( !Destination.includes('=') )
throw `Destination (${Destination}) expected to have at least one key=value`;
// todo: any sanitiasation (add/remove quotes etc)
return Destination;
}
export class AppleBuildParams
{
constructor(ProjectPath,Scheme,Destination,Sdk,Configuration,AdditionalParams)
{
Destination = SanitiseXcodeDestination(Destination);
// required (thus turn to string)
this.Scheme = `${Scheme}`;
this.Configuration = `${Configuration}`;
this.Destination = `${Destination}`;
// optional
this.ProjectPath = ProjectPath;
this.Sdk = Sdk;
this.AdditionalParams = [];
if ( AdditionalParams )
{
this.AdditionalParams = AdditionalParams.split(' ');
}
}
get description()
{
return `${this.Scheme}(${this.Destination})`;
}
GetXcodeArguments(IncludeDestination=true,IncludeConfiguration=true)
{
const Args = [];
// all optional
if ( this.ProjectPath )
Args.push(`-project`,this.ProjectPath);
// require these
Args.push(`-scheme`, this.Scheme );
if ( this.Sdk )
Args.push(`-sdk`,this.Sdk);
// gr: removed
// `-workspace`, `${ProjectPath}/project.xcworkspace`,
// from these as it was erroring with an unknown error on xcode11/mojave (but okay on xcode10/high sierra)
// required for building but not other things
if ( IncludeConfiguration )
Args.push(`-configuration`, this.Configuration );
if ( IncludeDestination )
Args.push(`-destination`,this.Destination);
Args.push( ...this.AdditionalParams );
return Args;
}
}
async function PrintProjectSchemesAndConfigurations(ProjectPath)
{
const ListOptions = [];
if ( ProjectPath )
{
ListOptions.push(`-project`,ProjectPath);
}
ListOptions.push(`-list`);
console.log(`Listing schemes & configurations...`);
await Exec("xcodebuild", ListOptions, null, null, true );
}
// returns array of matches
export function MatchRegex(Lines,Pattern)
{
if ( !Array.isArray(Lines) )
throw `Lines for regex is not array (${typeof Lines})`;
const regex = new RegExp(Pattern, 'g');
let Matches = Lines.map( Line => regex.exec(Line.trim()) );
Matches = Matches.filter( Match => Match!=null );
Matches = Matches.map( Match => Match[1] );
return Matches;
}
// these are assuming line is trimmed
const BuildDirectorysPattern = `^BUILT_PRODUCTS_DIR ?= ?(.*)`;
const FullProductNamesPattern = `^FULL_PRODUCT_NAME ?= ?(.*)`;
const ScriptOutputsPattern = `^SCRIPT_OUTPUT_FILE_[0-9]+[ /\\/]?= ?(.*)`;
function GatherProjectMeta(StdOutLines,ProjectMeta={})
{
const BuildDirectorys = MatchRegex(StdOutLines,BuildDirectorysPattern);
const FullProductNames = MatchRegex(StdOutLines,FullProductNamesPattern);
const ScriptOutputs = MatchRegex(StdOutLines,ScriptOutputsPattern);
// merge into existing data
ProjectMeta.BuildDirectorys = (ProjectMeta.BuildDirectorys ?? []).concat( BuildDirectorys );
ProjectMeta.FullProductNames = (ProjectMeta.FullProductNames ?? []).concat( FullProductNames );
ProjectMeta.ScriptOutputs = (ProjectMeta.ScriptOutputs ?? []).concat( ScriptOutputs );
return ProjectMeta;
}
// returns
// .BuildDirectorys
// .FullProjectNmae
// .ScriptOutput
async function GetProjectMeta(BuildParams)
{
let ProjectMeta = {};
function OnStdOut(Lines)
{
PrintStdOut(Lines);
ProjectMeta = GatherProjectMeta(Lines,ProjectMeta);
}
const PreBuildOptions = BuildParams.GetXcodeArguments();
PreBuildOptions.push(`-showBuildSettings`);
console.log(`Listing build settings for ${BuildParams.description}...`);
const ExecResult = await Exec(
"xcodebuild",
PreBuildOptions,
OnStdOut,
null,
true
);
// resolve meta
console.log(JSON.stringify(ProjectMeta));
if ( ProjectMeta.BuildDirectorys.length != 1 )
throw `Build detected wrong amount of build-directories (expecting 1); ${JSON.stringify(ProjectMeta.BuildDirectorys.length)}`;
if ( ProjectMeta.FullProductNames.length != 1 )
throw `Build detected wrong amount of product-names (expecting 1); ${JSON.stringify(ProjectMeta.FullProductNames.length)}`;
// resolve path in case it incldues .. or ., or process-relative paths etc back to pure dir for github
const BuildDirectory = Path.normalize(ProjectMeta.BuildDirectorys[0]);
const OutputMeta = {};
OutputMeta.ProductDirectory = BuildDirectory;
OutputMeta.ProductFilename = ProjectMeta.FullProductNames[0];
return OutputMeta;
}
async function BuildProject(BuildParams)
{
const BuildOptions = BuildParams.GetXcodeArguments();
console.log(`---------Building product...`);
const UseSpawn = true;
await Exec(
"xcodebuild",
BuildOptions,
null,
null,
UseSpawn
);
console.log(`---------XCodeBuild product build successfull.`);
// we don't output meta here, as the build results don't include what we need
// need to have gotten that from GetProjectMeta.
}
async function CleanProject(BuildParams)
{
const BuildOptions = BuildParams.GetXcodeArguments();
BuildOptions.push('clean');
console.log(`---------Cleaning product...`);
const UseSpawn = true;
await Exec(
"xcodebuild",
BuildOptions,
null,
null,
UseSpawn
);
console.log(`---------XCodeBuild product clean successfull.`);
}
async function ResolveProjectPath(ProjectPath)
{
// gotta find project filename
if ( !ProjectPath )
{
throw `todo: if no ProjectPath provided, need to manually resolve the filename`;
}
// already ends with .xcodeproj, lets assume it's good
if ( ProjectPath.endsWith('.xcodeproj') )
return ProjectPath;
ProjectPath += '.xcodeproj';
return ProjectPath;
}
async function RewritePackageUrlInPbxProj(ProjectPath,RewriteMap)
{
ProjectPath = await ResolveProjectPath(ProjectPath);
const PbxPath = `${ProjectPath}/project.pbxproj`;
let Pbx = await FileSystem.readFile(PbxPath);
Pbx = Pbx.toString();
let Matches = 0;
for ( let [MatchUrl,NewUrl] of Object.entries(RewriteMap) )
{
function GetNewUrl(MatchString)
{
Matches++;
//console.warn(`GetNewUrl(${Match})`);
let Replacement = MatchString.replace(MatchUrl,NewUrl);
return Replacement;
}
// gr: simpler without regex
const Pattern = `repositoryURL = "${MatchUrl}";`;
//const regex = new RegExp(Pattern);
const NewPbx = Pbx.replaceAll( Pattern, GetNewUrl );
if ( NewPbx == Pbx )
throw `Failed to match replacing-url ${MatchUrl}`;
Pbx = NewPbx;
}
console.log(`---------Replacing ${PbxPath} after ${Object.entries(RewriteMap).length} changes...`);
await FileSystem.writeFile( PbxPath, Pbx );
}
// return dictionary of [MatchUrl] = NewUrl
function ParseRewritePackageUrls(RewritePackageUrls)
{
// mutliple entries with ;
RewritePackageUrls = RewritePackageUrls.split(';');
const RewriteMap = {};
for ( let KeyValue of RewritePackageUrls )
{
const Parts = KeyValue.split('=');
if ( Parts.length != 2 )
{
throw `Rewrite-url pattern expected \"old=new;\" got \"${KeyValue}\"`;
}
RewriteMap[Parts[0]] = Parts[1];
}
return RewriteMap;
}
// assume params are present from caller
// only param testing here is for specific validation
export async function Build(ProjectPath,Scheme,Destination,Sdk,Configuration,AdditionalParams,Clean,RewritePackageUrls)
{
// append xcodeproj if missing
// if omitted from build, it looks in the current path for any .xcodeproj
// but using -project requires .xcodeproj in the argument
if ( ProjectPath )
ProjectPath = await ResolveProjectPath(ProjectPath);
let BuildParams = new AppleBuildParams(ProjectPath, Scheme, Destination, Sdk, Configuration, AdditionalParams );
if ( Clean )
{
console.log(`---------Cleaning ${BuildParams.description}...`);
await CleanProject(BuildParams);
}
if ( RewritePackageUrls )
{
const RewriteMap = ParseRewritePackageUrls(RewritePackageUrls);
await RewritePackageUrlInPbxProj(ProjectPath,RewriteMap);
}
// print out debug
await PrintProjectSchemesAndConfigurations(BuildParams.ProjectPath);
const ProjectMeta = await GetProjectMeta(BuildParams);
console.log(`---------Building ${BuildParams.description}...`);
// this doesn't output new meta, we have to assume it matches the results of GetProjectMeta()
await BuildProject(BuildParams);
return ProjectMeta;
}