rork-xcode 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/dist/index.d.ts +346 -29
- package/dist/index.js +821 -102
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -213,20 +213,25 @@ function createReferenceComments(root) {
|
|
|
213
213
|
*
|
|
214
214
|
* @module
|
|
215
215
|
*/
|
|
216
|
-
/** UTF-16 code unit of `\n`, used to count lines when reporting a failure. */
|
|
217
216
|
const LINE_FEED = 10;
|
|
217
|
+
const CARRIAGE_RETURN = 13;
|
|
218
218
|
/**
|
|
219
219
|
* Converts a source offset into a position.
|
|
220
220
|
*
|
|
221
221
|
* Runs only when an error is actually thrown, so parsing never pays for line
|
|
222
|
-
* tracking on the happy path.
|
|
222
|
+
* tracking on the happy path. All three line-ending conventions count as
|
|
223
|
+
* one break each, with the carriage return of a `\r\n` pair deferring to
|
|
224
|
+
* its line feed.
|
|
223
225
|
*/
|
|
224
226
|
function positionAt(source, offset) {
|
|
225
227
|
let line = 1;
|
|
226
228
|
let lineStart = 0;
|
|
227
|
-
for (let i = 0; i < offset && i < source.length; i++)
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
for (let i = 0; i < offset && i < source.length; i++) {
|
|
230
|
+
const code = source.charCodeAt(i);
|
|
231
|
+
if (code === LINE_FEED || code === CARRIAGE_RETURN && source.charCodeAt(i + 1) !== LINE_FEED) {
|
|
232
|
+
line++;
|
|
233
|
+
lineStart = i + 1;
|
|
234
|
+
}
|
|
230
235
|
}
|
|
231
236
|
return {
|
|
232
237
|
offset,
|
|
@@ -283,6 +288,30 @@ var XcschemeParseError = class extends Error {
|
|
|
283
288
|
}
|
|
284
289
|
};
|
|
285
290
|
/**
|
|
291
|
+
* Thrown when the source text is not a well-formed workspace data file
|
|
292
|
+
* (the XML dialect of `contents.xcworkspacedata` files).
|
|
293
|
+
*
|
|
294
|
+
* The message always embeds the line and column of the failure, and the
|
|
295
|
+
* same information is available in structured form on {@link position}
|
|
296
|
+
* for programmatic use.
|
|
297
|
+
*/
|
|
298
|
+
var XcworkspaceParseError = class extends Error {
|
|
299
|
+
/** Where in the source text parsing failed. */
|
|
300
|
+
position;
|
|
301
|
+
/**
|
|
302
|
+
* @param message Failure description without location. The location is
|
|
303
|
+
* appended automatically.
|
|
304
|
+
* @param source Full source text, used to compute the position.
|
|
305
|
+
* @param offset Character offset of the failure inside `source`.
|
|
306
|
+
*/
|
|
307
|
+
constructor(message, source, offset) {
|
|
308
|
+
const position = positionAt(source, offset);
|
|
309
|
+
super(`${message} (line ${position.line}, column ${position.column})`);
|
|
310
|
+
this.name = "XcworkspaceParseError";
|
|
311
|
+
this.position = position;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
286
315
|
* Thrown when the source text is not a well-formed build configuration
|
|
287
316
|
* file (the line-based format of `.xcconfig` files).
|
|
288
317
|
*
|
|
@@ -328,6 +357,27 @@ var XcschemeBuildError = class extends Error {
|
|
|
328
357
|
}
|
|
329
358
|
};
|
|
330
359
|
/**
|
|
360
|
+
* Thrown when a workspace element cannot be written as XML.
|
|
361
|
+
*
|
|
362
|
+
* Raised for element and attribute names that are not valid XML names and
|
|
363
|
+
* for attribute values carrying control characters XML 1.0 cannot encode.
|
|
364
|
+
* The {@link path} pinpoints the offending element inside the tree.
|
|
365
|
+
*/
|
|
366
|
+
var XcworkspaceBuildError = class extends Error {
|
|
367
|
+
/** Path to the offending element from the root, e.g. `Workspace.FileRef[0]`. */
|
|
368
|
+
path;
|
|
369
|
+
/**
|
|
370
|
+
* @param message Failure description without location. The element path
|
|
371
|
+
* is appended automatically.
|
|
372
|
+
* @param path Path to the offending element from the root.
|
|
373
|
+
*/
|
|
374
|
+
constructor(message, path) {
|
|
375
|
+
super(`${message} (at ${path})`);
|
|
376
|
+
this.name = "XcworkspaceBuildError";
|
|
377
|
+
this.path = path;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
331
381
|
* Thrown by the object model when an operation cannot proceed, because the
|
|
332
382
|
* document lacks the structure the operation needs (no objects dictionary,
|
|
333
383
|
* no root project object), a view's object was removed from the document,
|
|
@@ -524,17 +574,17 @@ function sanitizeComment(comment) {
|
|
|
524
574
|
* Sharing the strings avoids a `"\t".repeat(depth)` allocation on every
|
|
525
575
|
* line of output.
|
|
526
576
|
*/
|
|
527
|
-
const INDENTS = [""];
|
|
577
|
+
const INDENTS$1 = [""];
|
|
528
578
|
/**
|
|
529
579
|
* Returns the shared indentation string for a nesting depth.
|
|
530
580
|
*/
|
|
531
581
|
function indentString(depth) {
|
|
532
|
-
const cached = INDENTS[depth];
|
|
582
|
+
const cached = INDENTS$1[depth];
|
|
533
583
|
if (cached != null) return cached;
|
|
534
|
-
let known = INDENTS.at(-1);
|
|
535
|
-
while (INDENTS.length <= depth) {
|
|
584
|
+
let known = INDENTS$1.at(-1);
|
|
585
|
+
while (INDENTS$1.length <= depth) {
|
|
536
586
|
known += " ";
|
|
537
|
-
INDENTS.push(known);
|
|
587
|
+
INDENTS$1.push(known);
|
|
538
588
|
}
|
|
539
589
|
return known;
|
|
540
590
|
}
|
|
@@ -815,6 +865,153 @@ function buildPbxproj(root) {
|
|
|
815
865
|
return new Writer(root).toString();
|
|
816
866
|
}
|
|
817
867
|
//#endregion
|
|
868
|
+
//#region src/expansion.ts
|
|
869
|
+
/**
|
|
870
|
+
* Expands every `$(NAME)` and `${NAME}` reference in a value through the
|
|
871
|
+
* lookup. Substituted text is expanded again, so references are followed
|
|
872
|
+
* through chains of values, and reference names may themselves contain
|
|
873
|
+
* references (`$(SETTING_$(VARIANT))`), which expand innermost first.
|
|
874
|
+
*
|
|
875
|
+
* A lookup answer of `undefined` leaves the reference in the output
|
|
876
|
+
* verbatim, so partial resolution loses no information. A reference
|
|
877
|
+
* whose expansion would recurse into itself also stays verbatim, which
|
|
878
|
+
* keeps cyclic definitions finite. Xcode's `:` operators are honored for
|
|
879
|
+
* the forms below, and a reference carrying any other operator stays
|
|
880
|
+
* verbatim rather than expanding to a wrongly transformed value.
|
|
881
|
+
*
|
|
882
|
+
* The supported operators are `lower` and `upper` for case mapping,
|
|
883
|
+
* `rfc1034identifier` and `c99extidentifier` for the identifier
|
|
884
|
+
* mappings bundle identifiers and product module names use, and
|
|
885
|
+
* `default=value` for substituting a fallback when the setting resolves
|
|
886
|
+
* empty or is not known to the lookup.
|
|
887
|
+
*/
|
|
888
|
+
function expandBuildSettingReferences(value, lookup, options) {
|
|
889
|
+
return expand(value, lookup, options?.expandLookupValues !== false, /* @__PURE__ */ new Set());
|
|
890
|
+
}
|
|
891
|
+
/** The UTF-16 code unit of `$`, which starts every reference. */
|
|
892
|
+
const CODE_DOLLAR = 36;
|
|
893
|
+
/**
|
|
894
|
+
* One expansion pass over a value. Active names carry the references
|
|
895
|
+
* currently being expanded up the call stack, so a cycle is detected the
|
|
896
|
+
* moment a name would expand inside its own expansion.
|
|
897
|
+
*/
|
|
898
|
+
function expand(value, lookup, expandLookupValues, active) {
|
|
899
|
+
if (!value.includes("$")) return value;
|
|
900
|
+
let out = "";
|
|
901
|
+
let index = 0;
|
|
902
|
+
while (index < value.length) {
|
|
903
|
+
const open = value.charCodeAt(index) === CODE_DOLLAR ? value[index + 1] : void 0;
|
|
904
|
+
if (open !== "(" && open !== "{") {
|
|
905
|
+
out += value[index];
|
|
906
|
+
index++;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const end = findClosingDelimiter(value, index + 2, open === "(" ? ")" : "}");
|
|
910
|
+
if (end === -1) {
|
|
911
|
+
out += value.slice(index);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
const reference = value.slice(index, end + 1);
|
|
915
|
+
const inner = expand(value.slice(index + 2, end), lookup, expandLookupValues, active);
|
|
916
|
+
out += expandReference(reference, inner, lookup, expandLookupValues, active);
|
|
917
|
+
index = end + 1;
|
|
918
|
+
}
|
|
919
|
+
return out;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Finds the index of the closing delimiter matching an opener at
|
|
923
|
+
* `start - 1`, honoring nested delimiters of the same kind, or -1 when
|
|
924
|
+
* the reference is unterminated.
|
|
925
|
+
*/
|
|
926
|
+
function findClosingDelimiter(value, start, close) {
|
|
927
|
+
const open = close === ")" ? "(" : "{";
|
|
928
|
+
let depth = 0;
|
|
929
|
+
for (let i = start; i < value.length; i++) {
|
|
930
|
+
const ch = value[i];
|
|
931
|
+
if (ch === open) depth++;
|
|
932
|
+
else if (ch === close) {
|
|
933
|
+
if (depth === 0) return i;
|
|
934
|
+
depth--;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return -1;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Expands one reference whose name and operators have already had their
|
|
941
|
+
* own references resolved. Returns the original reference text whenever
|
|
942
|
+
* the expansion cannot honestly produce a value.
|
|
943
|
+
*/
|
|
944
|
+
function expandReference(reference, inner, lookup, expandLookupValues, active) {
|
|
945
|
+
const colon = inner.indexOf(":");
|
|
946
|
+
const name = colon === -1 ? inner : inner.slice(0, colon);
|
|
947
|
+
if (active.has(name)) return reference;
|
|
948
|
+
const raw = lookup(name);
|
|
949
|
+
const nested = /* @__PURE__ */ new Set([...active, name]);
|
|
950
|
+
let value = raw;
|
|
951
|
+
if (raw != null && expandLookupValues) value = expand(raw, lookup, expandLookupValues, nested);
|
|
952
|
+
if (colon !== -1) {
|
|
953
|
+
for (const operator of splitOperators(inner.slice(colon + 1))) if (operator.startsWith("default=")) {
|
|
954
|
+
if (value == null || value === "") value = operator.slice(8);
|
|
955
|
+
} else if (value != null) {
|
|
956
|
+
const applied = applyOperator(operator, value);
|
|
957
|
+
if (applied == null) return reference;
|
|
958
|
+
value = applied;
|
|
959
|
+
} else if (applyOperator(operator, "") == null) return reference;
|
|
960
|
+
}
|
|
961
|
+
return value ?? reference;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Splits an operator chain at colons, keeping a `default=` value intact
|
|
965
|
+
* even when it contains colons of its own, the way Xcode consumes the
|
|
966
|
+
* rest of the reference as the default.
|
|
967
|
+
*/
|
|
968
|
+
function splitOperators(operators) {
|
|
969
|
+
const parts = [];
|
|
970
|
+
let rest = operators;
|
|
971
|
+
while (rest.length > 0) {
|
|
972
|
+
if (rest.startsWith("default=")) {
|
|
973
|
+
parts.push(rest);
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
const colon = rest.indexOf(":");
|
|
977
|
+
if (colon === -1) {
|
|
978
|
+
parts.push(rest);
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
parts.push(rest.slice(0, colon));
|
|
982
|
+
rest = rest.slice(colon + 1);
|
|
983
|
+
}
|
|
984
|
+
return parts;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* The transforming operators the expander supports, keyed by the name
|
|
988
|
+
* they are written with. `default=` is not listed because it carries an
|
|
989
|
+
* argument and substitutes rather than transforms, so the reference
|
|
990
|
+
* expansion handles it structurally.
|
|
991
|
+
*/
|
|
992
|
+
const OPERATOR_TRANSFORMS = {
|
|
993
|
+
c99extidentifier: (value) => value.replaceAll(/[^A-Za-z0-9_]/gu, "_"),
|
|
994
|
+
lower: (value) => value.toLowerCase(),
|
|
995
|
+
rfc1034identifier: (value) => value.replaceAll(/[^A-Za-z0-9-]/gu, "-"),
|
|
996
|
+
upper: (value) => value.toUpperCase()
|
|
997
|
+
};
|
|
998
|
+
/**
|
|
999
|
+
* Whether a parsed operator names one of the supported transforms. The
|
|
1000
|
+
* check is an own-property test, so document text like `toString` cannot
|
|
1001
|
+
* reach the table's prototype.
|
|
1002
|
+
*/
|
|
1003
|
+
function isBuildSettingOperator(operator) {
|
|
1004
|
+
return Object.hasOwn(OPERATOR_TRANSFORMS, operator);
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Applies one operator to a resolved value, or returns `undefined` for
|
|
1008
|
+
* operators outside {@link BuildSettingOperator}, which the caller turns
|
|
1009
|
+
* into a verbatim reference.
|
|
1010
|
+
*/
|
|
1011
|
+
function applyOperator(operator, value) {
|
|
1012
|
+
return isBuildSettingOperator(operator) ? OPERATOR_TRANSFORMS[operator](value) : void 0;
|
|
1013
|
+
}
|
|
1014
|
+
//#endregion
|
|
818
1015
|
//#region src/model/isa.ts
|
|
819
1016
|
/**
|
|
820
1017
|
* Names and well-known values of the pbxproj object vocabulary.
|
|
@@ -2080,11 +2277,11 @@ const IS_LITERAL_CHAR = (() => {
|
|
|
2080
2277
|
for (const ch of "_$/:.-") table[ch.charCodeAt(0)] = 1;
|
|
2081
2278
|
return table;
|
|
2082
2279
|
})();
|
|
2083
|
-
const CODE_TAB$
|
|
2084
|
-
const CODE_LINE_FEED$
|
|
2085
|
-
const CODE_CARRIAGE_RETURN$
|
|
2280
|
+
const CODE_TAB$2 = 9;
|
|
2281
|
+
const CODE_LINE_FEED$2 = 10;
|
|
2282
|
+
const CODE_CARRIAGE_RETURN$2 = 13;
|
|
2086
2283
|
const CODE_SPACE$1 = 32;
|
|
2087
|
-
const CODE_QUOTE$
|
|
2284
|
+
const CODE_QUOTE$2 = 34;
|
|
2088
2285
|
const CODE_SINGLE_QUOTE = 39;
|
|
2089
2286
|
const CODE_OPEN_PAREN = 40;
|
|
2090
2287
|
const CODE_CLOSE_PAREN = 41;
|
|
@@ -2096,9 +2293,9 @@ const CODE_SLASH$1 = 47;
|
|
|
2096
2293
|
const CODE_ZERO = 48;
|
|
2097
2294
|
const CODE_NINE = 57;
|
|
2098
2295
|
const CODE_SEMICOLON = 59;
|
|
2099
|
-
const CODE_LESS_THAN$
|
|
2296
|
+
const CODE_LESS_THAN$2 = 60;
|
|
2100
2297
|
const CODE_EQUALS$1 = 61;
|
|
2101
|
-
const CODE_GREATER_THAN$
|
|
2298
|
+
const CODE_GREATER_THAN$2 = 62;
|
|
2102
2299
|
const CODE_BACKSLASH = 92;
|
|
2103
2300
|
const CODE_OPEN_BRACE = 123;
|
|
2104
2301
|
const CODE_CLOSE_BRACE = 125;
|
|
@@ -2111,9 +2308,9 @@ const CODE_CLOSE_BRACE = 125;
|
|
|
2111
2308
|
const IS_WHITESPACE = (() => {
|
|
2112
2309
|
const table = /* @__PURE__ */ new Uint8Array(256);
|
|
2113
2310
|
table[CODE_SPACE$1] = 1;
|
|
2114
|
-
table[CODE_TAB$
|
|
2115
|
-
table[CODE_CARRIAGE_RETURN$
|
|
2116
|
-
table[CODE_LINE_FEED$
|
|
2311
|
+
table[CODE_TAB$2] = 1;
|
|
2312
|
+
table[CODE_CARRIAGE_RETURN$2] = 1;
|
|
2313
|
+
table[CODE_LINE_FEED$2] = 1;
|
|
2117
2314
|
return table;
|
|
2118
2315
|
})();
|
|
2119
2316
|
/**
|
|
@@ -2291,7 +2488,7 @@ var Parser$1 = class {
|
|
|
2291
2488
|
const input = this.input;
|
|
2292
2489
|
const length = input.length;
|
|
2293
2490
|
const start = ++this.pos;
|
|
2294
|
-
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN$
|
|
2491
|
+
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN$2) this.pos++;
|
|
2295
2492
|
if (this.pos >= length) this.fail("Unterminated data run", start - 1);
|
|
2296
2493
|
let hex = "";
|
|
2297
2494
|
for (let i = start; i < this.pos; i++) {
|
|
@@ -2334,7 +2531,7 @@ var Parser$1 = class {
|
|
|
2334
2531
|
return result;
|
|
2335
2532
|
}
|
|
2336
2533
|
let key;
|
|
2337
|
-
if (code === CODE_QUOTE$
|
|
2534
|
+
if (code === CODE_QUOTE$2 || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
|
|
2338
2535
|
else if (IS_LITERAL_CHAR[code] === 1) key = this.readLiteral();
|
|
2339
2536
|
else this.fail(`Expected a key but found '${input[this.pos]}'`);
|
|
2340
2537
|
this.expect(CODE_EQUALS$1, "=");
|
|
@@ -2390,9 +2587,9 @@ var Parser$1 = class {
|
|
|
2390
2587
|
const code = this.input.charCodeAt(this.pos);
|
|
2391
2588
|
if (code === CODE_OPEN_BRACE) return this.parseObject();
|
|
2392
2589
|
if (code === CODE_OPEN_PAREN) return this.parseArray();
|
|
2393
|
-
if (code === CODE_QUOTE$
|
|
2590
|
+
if (code === CODE_QUOTE$2 || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
|
|
2394
2591
|
if (IS_LITERAL_CHAR[code] === 1) return interpretLiteral(this.readLiteral());
|
|
2395
|
-
if (code === CODE_LESS_THAN$
|
|
2592
|
+
if (code === CODE_LESS_THAN$2) return this.readData();
|
|
2396
2593
|
this.fail(`Expected a value but found '${this.input[this.pos]}'`);
|
|
2397
2594
|
}
|
|
2398
2595
|
};
|
|
@@ -3034,6 +3231,47 @@ var Target = class extends XcodeObject {
|
|
|
3034
3231
|
return (projectConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(projectConfiguration))?.[key];
|
|
3035
3232
|
}
|
|
3036
3233
|
/**
|
|
3234
|
+
* Reads a build setting like {@link getBuildSetting} and expands the
|
|
3235
|
+
* `$(NAME)` and `${NAME}` references in it. Referenced settings resolve
|
|
3236
|
+
* through the same layering, `$(inherited)` and a setting referencing
|
|
3237
|
+
* itself continue from the next layer down the way Xcode composes
|
|
3238
|
+
* values, and `$(TARGET_NAME)` falls back to the target's name, so the
|
|
3239
|
+
* common `PRODUCT_NAME = "$(TARGET_NAME)"` resolves without any caller
|
|
3240
|
+
* setup.
|
|
3241
|
+
*
|
|
3242
|
+
* References the model cannot resolve (build-system paths like
|
|
3243
|
+
* `$(BUILT_PRODUCTS_DIR)`, environment values) stay in the result
|
|
3244
|
+
* verbatim unless the caller's lookup answers them, so no information
|
|
3245
|
+
* is invented. See {@link expandBuildSettingReferences} for the
|
|
3246
|
+
* operator forms honored during expansion.
|
|
3247
|
+
*/
|
|
3248
|
+
resolveBuildSetting(key, options) {
|
|
3249
|
+
const targetConfiguration = defaultConfigurationOf(this.project, this.getString("buildConfigurationList"));
|
|
3250
|
+
const projectConfiguration = defaultConfigurationOf(this.project, this.project.rootProject.getString("buildConfigurationList"));
|
|
3251
|
+
const layers = [
|
|
3252
|
+
asDictionary(targetConfiguration?.properties["buildSettings"]),
|
|
3253
|
+
targetConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(targetConfiguration),
|
|
3254
|
+
asDictionary(projectConfiguration?.properties["buildSettings"]),
|
|
3255
|
+
projectConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(projectConfiguration)
|
|
3256
|
+
];
|
|
3257
|
+
const resolve = (name, fromLayer, active) => {
|
|
3258
|
+
const guard = `${fromLayer}:${name}`;
|
|
3259
|
+
if (active.has(guard)) return;
|
|
3260
|
+
const nested = /* @__PURE__ */ new Set([...active, guard]);
|
|
3261
|
+
for (let layer = fromLayer; layer < layers.length; layer++) {
|
|
3262
|
+
const settings = layers[layer];
|
|
3263
|
+
if (settings == null || !(name in settings)) continue;
|
|
3264
|
+
const value = asString(settings[name]);
|
|
3265
|
+
if (value == null) return;
|
|
3266
|
+
return expandBuildSettingReferences(value, (reference) => {
|
|
3267
|
+
if (reference === "inherited" || reference === name) return resolve(name, layer + 1, nested) ?? "";
|
|
3268
|
+
return resolve(reference, 0, nested) ?? options?.lookup?.(reference) ?? builtinSetting(this, reference);
|
|
3269
|
+
}, { expandLookupValues: false });
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
return resolve(key, 0, /* @__PURE__ */ new Set());
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3037
3275
|
* Writes a build setting on every configuration of the target, so Debug
|
|
3038
3276
|
* and Release stay consistent.
|
|
3039
3277
|
*/
|
|
@@ -3147,6 +3385,15 @@ var Target = class extends XcodeObject {
|
|
|
3147
3385
|
}
|
|
3148
3386
|
};
|
|
3149
3387
|
/**
|
|
3388
|
+
* The settings the build system defines from the target itself rather
|
|
3389
|
+
* than from the document. `TARGET_NAME` is the one programmatic reads
|
|
3390
|
+
* meet constantly, through the template's `PRODUCT_NAME` of
|
|
3391
|
+
* `$(TARGET_NAME)`.
|
|
3392
|
+
*/
|
|
3393
|
+
function builtinSetting(target, name) {
|
|
3394
|
+
return name === "TARGET_NAME" ? target.name : void 0;
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3150
3397
|
* A `PBXNativeTarget` is a product the project compiles and packages
|
|
3151
3398
|
* itself, such as an application or an app extension.
|
|
3152
3399
|
*/
|
|
@@ -4087,57 +4334,159 @@ function stripReferences(properties, id) {
|
|
|
4087
4334
|
}
|
|
4088
4335
|
}
|
|
4089
4336
|
//#endregion
|
|
4090
|
-
//#region src/
|
|
4337
|
+
//#region src/xml/types.ts
|
|
4091
4338
|
/**
|
|
4092
4339
|
* Whether a node is an element rather than a comment.
|
|
4093
4340
|
*/
|
|
4094
|
-
function
|
|
4341
|
+
function isXmlElement(node) {
|
|
4095
4342
|
return "name" in node;
|
|
4096
4343
|
}
|
|
4344
|
+
/**
|
|
4345
|
+
* Collects elements of the given name anywhere under a root, in document
|
|
4346
|
+
* order, root included. Passing no name collects every element. Scheme
|
|
4347
|
+
* and workspace models both build their typed views over this query.
|
|
4348
|
+
*/
|
|
4349
|
+
function xmlElements(root, name) {
|
|
4350
|
+
const found = [];
|
|
4351
|
+
const visit = (element) => {
|
|
4352
|
+
if (name == null || element.name === name) found.push(element);
|
|
4353
|
+
for (const child of element.children) if (isXmlElement(child)) visit(child);
|
|
4354
|
+
};
|
|
4355
|
+
visit(root);
|
|
4356
|
+
return found;
|
|
4357
|
+
}
|
|
4097
4358
|
//#endregion
|
|
4098
|
-
//#region src/
|
|
4359
|
+
//#region src/xml/build.ts
|
|
4099
4360
|
/**
|
|
4100
|
-
* Serializer for
|
|
4361
|
+
* Serializer for Xcode's XML dialect, shared by scheme and workspace
|
|
4362
|
+
* files.
|
|
4101
4363
|
*
|
|
4102
4364
|
* The output reproduces Xcode's own layout byte for byte. Xcode writes
|
|
4103
4365
|
* the UTF-8 declaration, indents with three spaces, puts each attribute
|
|
4104
4366
|
* on its own line with spaces around the equals sign, glues the closing
|
|
4105
4367
|
* angle bracket to the last attribute, and closes every element with an
|
|
4106
|
-
* explicit close tag. Parsing an Xcode-written
|
|
4368
|
+
* explicit close tag. Parsing an Xcode-written file and building it
|
|
4107
4369
|
* back therefore yields the identical file, and any other input reaches
|
|
4108
4370
|
* that canonical form in one build.
|
|
4109
4371
|
*
|
|
4372
|
+
* Each file format wraps this core with its own error type, so a caller
|
|
4373
|
+
* always sees the error class of the format it asked for.
|
|
4374
|
+
*
|
|
4110
4375
|
* @module
|
|
4111
4376
|
*/
|
|
4112
|
-
/**
|
|
4377
|
+
/**
|
|
4378
|
+
* The internal failure the renderers throw. It carries the offending
|
|
4379
|
+
* node instead of a path, and {@link buildXmlDocument} resolves the path
|
|
4380
|
+
* by searching the tree only when a failure actually happens, so the
|
|
4381
|
+
* happy path never pays for path bookkeeping.
|
|
4382
|
+
*/
|
|
4383
|
+
var XmlBuildFailure = class extends Error {
|
|
4384
|
+
/** The element or comment node the failure points at. */
|
|
4385
|
+
node;
|
|
4386
|
+
constructor(message, node) {
|
|
4387
|
+
super(message);
|
|
4388
|
+
this.name = "XmlBuildFailure";
|
|
4389
|
+
this.node = node;
|
|
4390
|
+
}
|
|
4391
|
+
};
|
|
4392
|
+
/** The declaration line Xcode writes at the top of every file. */
|
|
4113
4393
|
const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
4114
|
-
/** One indentation step of Xcode's
|
|
4394
|
+
/** One indentation step of Xcode's writer. */
|
|
4115
4395
|
const INDENT = " ";
|
|
4116
4396
|
/**
|
|
4397
|
+
* Indentation strings by depth, extended on demand. Documents nest a
|
|
4398
|
+
* handful of levels, and reusing the strings keeps the writer from
|
|
4399
|
+
* re-allocating the same prefixes for every node.
|
|
4400
|
+
*/
|
|
4401
|
+
const INDENTS = [""];
|
|
4402
|
+
/**
|
|
4403
|
+
* The indentation prefix for a nesting depth.
|
|
4404
|
+
*/
|
|
4405
|
+
function indentAt(depth) {
|
|
4406
|
+
for (let known = INDENTS.length; known <= depth; known++) INDENTS.push(INDENTS[known - 1] + INDENT);
|
|
4407
|
+
return INDENTS[depth];
|
|
4408
|
+
}
|
|
4409
|
+
/**
|
|
4117
4410
|
* Matches valid XML names. A stray non-name, such as an empty string or
|
|
4118
4411
|
* one carrying spaces or XML syntax, must fail rather than produce a
|
|
4119
4412
|
* document no parser accepts.
|
|
4120
4413
|
*/
|
|
4121
4414
|
const NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/u;
|
|
4122
4415
|
/**
|
|
4123
|
-
*
|
|
4124
|
-
*
|
|
4125
|
-
*
|
|
4416
|
+
* Names that already passed {@link NAME_PATTERN}, capped so hostile
|
|
4417
|
+
* documents cannot grow the set without bound. Element and attribute
|
|
4418
|
+
* vocabularies repeat heavily, so nearly every check after the first is
|
|
4419
|
+
* a set lookup instead of a regex test.
|
|
4126
4420
|
*/
|
|
4127
|
-
const
|
|
4421
|
+
const VALID_NAMES = /* @__PURE__ */ new Set();
|
|
4128
4422
|
/**
|
|
4129
|
-
*
|
|
4130
|
-
*
|
|
4131
|
-
* @throws XcschemeBuildError for element or attribute names that are not
|
|
4132
|
-
* XML names and for attribute values carrying unencodable control
|
|
4133
|
-
* characters, with the path of the offending node.
|
|
4423
|
+
* Whether a name is a valid XML name, memoized across documents.
|
|
4134
4424
|
*/
|
|
4135
|
-
function
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4425
|
+
function isValidName(name) {
|
|
4426
|
+
if (VALID_NAMES.has(name)) return true;
|
|
4427
|
+
if (!NAME_PATTERN.test(name)) return false;
|
|
4428
|
+
if (VALID_NAMES.size < 512) VALID_NAMES.add(name);
|
|
4429
|
+
return true;
|
|
4430
|
+
}
|
|
4431
|
+
/**
|
|
4432
|
+
* Serializes a document to text in Xcode's canonical layout, reporting
|
|
4433
|
+
* failures through the given error factory.
|
|
4434
|
+
*/
|
|
4435
|
+
function buildXmlDocument(document, makeError) {
|
|
4436
|
+
try {
|
|
4437
|
+
let output = XML_DECLARATION;
|
|
4438
|
+
for (const comment of document.leading) output += renderComment(comment, 0);
|
|
4439
|
+
output += renderElement(document.root, 0);
|
|
4440
|
+
for (const comment of document.trailing) output += renderComment(comment, 0);
|
|
4441
|
+
return output;
|
|
4442
|
+
} catch (error) {
|
|
4443
|
+
if (error instanceof XmlBuildFailure) throw makeError(error.message, pathOf(document, error.node));
|
|
4444
|
+
throw error;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
/**
|
|
4448
|
+
* Resolves the path of a node for an error message, in the
|
|
4449
|
+
* `Scheme.BuildAction[0]` form, by walking the tree. This runs only when
|
|
4450
|
+
* a build actually fails.
|
|
4451
|
+
*/
|
|
4452
|
+
function pathOf(document, node) {
|
|
4453
|
+
if (node == null || document.leading.includes(node) || document.trailing.includes(node)) return "document";
|
|
4454
|
+
const search = (element, path) => {
|
|
4455
|
+
if (element === node) return path;
|
|
4456
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4457
|
+
for (const child of element.children) {
|
|
4458
|
+
if (!isXmlElement(child)) {
|
|
4459
|
+
if (child === node) return path;
|
|
4460
|
+
continue;
|
|
4461
|
+
}
|
|
4462
|
+
const index = seen.get(child.name) ?? 0;
|
|
4463
|
+
seen.set(child.name, index + 1);
|
|
4464
|
+
const found = search(child, `${path}.${child.name}[${index}]`);
|
|
4465
|
+
if (found != null) return found;
|
|
4466
|
+
}
|
|
4467
|
+
};
|
|
4468
|
+
return search(document.root, document.root.name) ?? document.root.name;
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* Matches the characters no XML document can carry, in any context. The
|
|
4472
|
+
* alternatives are the control characters XML cannot represent,
|
|
4473
|
+
* unpaired surrogate halves, and the two noncharacters at the end of
|
|
4474
|
+
* the basic plane. Comments validate against this directly, since
|
|
4475
|
+
* comment text has no escapes to fall back on.
|
|
4476
|
+
*/
|
|
4477
|
+
const UNCARRIABLE_PATTERN$1 = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uFFFE\uFFFF]/u;
|
|
4478
|
+
/**
|
|
4479
|
+
* Renders one comment, failing on text the comment grammar cannot hold.
|
|
4480
|
+
* A `--` inside the text or a `-` against the closing marker would
|
|
4481
|
+
* reparse at a different terminator, and characters outside XML's
|
|
4482
|
+
* character set have no escaped form inside a comment, so emitting
|
|
4483
|
+
* either would corrupt the document.
|
|
4484
|
+
*/
|
|
4485
|
+
function renderComment(node, depth) {
|
|
4486
|
+
const comment = node.comment;
|
|
4487
|
+
if (comment.includes("--") || comment.endsWith("-")) throw new XmlBuildFailure("Comments cannot contain -- or end with -", node);
|
|
4488
|
+
if (UNCARRIABLE_PATTERN$1.test(comment)) throw new XmlBuildFailure("Comments cannot contain characters XML cannot carry", node);
|
|
4489
|
+
return `${indentAt(depth)}<!--${comment}-->\n`;
|
|
4141
4490
|
}
|
|
4142
4491
|
/**
|
|
4143
4492
|
* Renders one element with Xcode's layout. Attributes go each on their
|
|
@@ -4145,67 +4494,109 @@ function buildXcscheme(document) {
|
|
|
4145
4494
|
* attribute, children indent one step, and the close tag is always
|
|
4146
4495
|
* explicit.
|
|
4147
4496
|
*/
|
|
4148
|
-
function renderElement(element, depth
|
|
4149
|
-
if (!
|
|
4150
|
-
const indent =
|
|
4497
|
+
function renderElement(element, depth) {
|
|
4498
|
+
if (!isValidName(element.name)) throw new XmlBuildFailure(`Element name ${JSON.stringify(element.name)} is not a valid XML name`, element);
|
|
4499
|
+
const indent = indentAt(depth);
|
|
4151
4500
|
const names = Object.keys(element.attributes);
|
|
4152
4501
|
let output;
|
|
4153
4502
|
if (names.length === 0) output = `${indent}<${element.name}>\n`;
|
|
4154
4503
|
else {
|
|
4155
4504
|
output = `${indent}<${element.name}\n`;
|
|
4156
|
-
const attributeIndent =
|
|
4505
|
+
const attributeIndent = indentAt(depth + 1);
|
|
4157
4506
|
for (let i = 0; i < names.length; i++) {
|
|
4158
4507
|
const name = names[i];
|
|
4159
|
-
if (!
|
|
4160
|
-
const value = escapeAttribute(element.attributes[name],
|
|
4508
|
+
if (!isValidName(name)) throw new XmlBuildFailure(`Attribute name ${JSON.stringify(name)} is not a valid XML name`, element);
|
|
4509
|
+
const value = escapeAttribute(element.attributes[name], element, name);
|
|
4161
4510
|
const terminator = i === names.length - 1 ? ">" : "";
|
|
4162
4511
|
output += `${attributeIndent}${name} = "${value}"${terminator}\n`;
|
|
4163
4512
|
}
|
|
4164
4513
|
}
|
|
4165
|
-
|
|
4514
|
+
const children = element.children;
|
|
4515
|
+
const childDepth = depth + 1;
|
|
4516
|
+
for (const child of children) output += isXmlElement(child) ? renderElement(child, childDepth) : renderComment(child, childDepth);
|
|
4166
4517
|
output += `${indent}</${element.name}>\n`;
|
|
4167
4518
|
return output;
|
|
4168
4519
|
}
|
|
4520
|
+
const CODE_TAB$1 = 9;
|
|
4521
|
+
const CODE_LINE_FEED$1 = 10;
|
|
4522
|
+
const CODE_CARRIAGE_RETURN$1 = 13;
|
|
4523
|
+
const CODE_QUOTE$1 = 34;
|
|
4524
|
+
const CODE_AMPERSAND$1 = 38;
|
|
4525
|
+
const CODE_APOSTROPHE$1 = 39;
|
|
4526
|
+
const CODE_LESS_THAN$1 = 60;
|
|
4527
|
+
const CODE_GREATER_THAN$1 = 62;
|
|
4169
4528
|
/**
|
|
4170
|
-
*
|
|
4171
|
-
*
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
for (const child of children) if (isXcschemeElement(child)) {
|
|
4177
|
-
const index = seen.get(child.name) ?? 0;
|
|
4178
|
-
seen.set(child.name, index + 1);
|
|
4179
|
-
output += renderElement(child, depth, `${path}.${child.name}[${index}]`);
|
|
4180
|
-
} else output += `${INDENT.repeat(depth)}<!--${child.comment}-->\n`;
|
|
4181
|
-
return output;
|
|
4182
|
-
}
|
|
4529
|
+
* Matches every code unit the writer must escape, validate, or reject.
|
|
4530
|
+
* Nearly all attribute values contain none of them, and the single
|
|
4531
|
+
* regex test is the cheapest full-string scan the engine offers, so a
|
|
4532
|
+
* miss returns the value with no further work.
|
|
4533
|
+
*/
|
|
4534
|
+
const NEEDS_WORK_PATTERN = /[\u0000-\u001F&<>"'\uD800-\uDFFF\uFFFE\uFFFF]/u;
|
|
4183
4535
|
/**
|
|
4184
4536
|
* Escapes an attribute value the way Xcode's writer does. XML syntax
|
|
4185
4537
|
* characters become the five named entities, and tab, line feed, and
|
|
4186
4538
|
* carriage return become character references so they survive
|
|
4187
4539
|
* attribute-value normalization on the next parse.
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4540
|
+
*
|
|
4541
|
+
* The common value with nothing to escape returns as-is after one regex
|
|
4542
|
+
* scan. Only values carrying an interesting code unit take the
|
|
4543
|
+
* classifying pass, which escapes what has an escape and rejects what
|
|
4544
|
+
* no XML document can carry, meaning the control characters XML cannot
|
|
4545
|
+
* represent, unpaired surrogate halves, and the two noncharacters at
|
|
4546
|
+
* the end of the basic plane.
|
|
4547
|
+
*/
|
|
4548
|
+
function escapeAttribute(value, element, attributeName) {
|
|
4549
|
+
if (!NEEDS_WORK_PATTERN.test(value)) return value;
|
|
4550
|
+
let needsEscaping = false;
|
|
4551
|
+
for (let i = 0; i < value.length; i++) {
|
|
4552
|
+
const code = value.charCodeAt(i);
|
|
4553
|
+
if (code < 32) {
|
|
4554
|
+
if (code === CODE_TAB$1 || code === CODE_LINE_FEED$1 || code === CODE_CARRIAGE_RETURN$1) {
|
|
4555
|
+
needsEscaping = true;
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
throw new XmlBuildFailure(`Attribute ${attributeName} contains the control character U+${code.toString(16).padStart(4, "0").toUpperCase()}, which XML cannot encode`, element);
|
|
4559
|
+
}
|
|
4560
|
+
if (code === CODE_AMPERSAND$1 || code === CODE_LESS_THAN$1 || code === CODE_GREATER_THAN$1 || code === CODE_QUOTE$1 || code === CODE_APOSTROPHE$1) {
|
|
4561
|
+
needsEscaping = true;
|
|
4562
|
+
continue;
|
|
4563
|
+
}
|
|
4564
|
+
if (code >= 55296) {
|
|
4565
|
+
if (code <= 56319) {
|
|
4566
|
+
const next = value.charCodeAt(i + 1);
|
|
4567
|
+
if (next >= 56320 && next <= 57343) {
|
|
4568
|
+
i++;
|
|
4569
|
+
continue;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
if (code <= 57343 || code === 65534 || code === 65535) throw new XmlBuildFailure(`Attribute ${attributeName} contains a code point XML cannot carry (U+${code.toString(16).padStart(4, "0").toUpperCase()})`, element);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
if (!needsEscaping) return value;
|
|
4192
4576
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'").replaceAll(" ", "	").replaceAll("\n", " ").replaceAll("\r", " ");
|
|
4193
4577
|
}
|
|
4194
4578
|
//#endregion
|
|
4195
|
-
//#region src/scheme/
|
|
4579
|
+
//#region src/scheme/build.ts
|
|
4196
4580
|
/**
|
|
4197
|
-
*
|
|
4198
|
-
*
|
|
4199
|
-
*
|
|
4200
|
-
* child elements, and there is no text content, no namespace, and no
|
|
4201
|
-
* DOCTYPE. The parser accepts that shape from any writer, since attribute
|
|
4202
|
-
* order, quoting style, and whitespace vary across tools. Character and
|
|
4203
|
-
* entity references in attribute values resolve to their characters, and
|
|
4204
|
-
* comments are preserved. Anything outside the shape fails loudly with a
|
|
4205
|
-
* position, in line with the pbxproj parser.
|
|
4581
|
+
* Serializer entry point for the `.xcscheme` dialect. The layout rules
|
|
4582
|
+
* live in the shared XML core, and this wrapper binds them to the scheme
|
|
4583
|
+
* error type.
|
|
4206
4584
|
*
|
|
4207
4585
|
* @module
|
|
4208
4586
|
*/
|
|
4587
|
+
/**
|
|
4588
|
+
* Serializes a scheme document to the text of a `.xcscheme` file in
|
|
4589
|
+
* Xcode's canonical layout.
|
|
4590
|
+
*
|
|
4591
|
+
* @throws XcschemeBuildError for element or attribute names that are not
|
|
4592
|
+
* XML names and for attribute values carrying unencodable control
|
|
4593
|
+
* characters, with the path of the offending node.
|
|
4594
|
+
*/
|
|
4595
|
+
function buildXcscheme(document) {
|
|
4596
|
+
return buildXmlDocument(document, (message, path) => new XcschemeBuildError(message, path));
|
|
4597
|
+
}
|
|
4598
|
+
//#endregion
|
|
4599
|
+
//#region src/xml/parse.ts
|
|
4209
4600
|
const CODE_TAB = 9;
|
|
4210
4601
|
const CODE_LINE_FEED = 10;
|
|
4211
4602
|
const CODE_CARRIAGE_RETURN = 13;
|
|
@@ -4230,7 +4621,7 @@ function isWhitespace(code) {
|
|
|
4230
4621
|
return code === CODE_SPACE || code === CODE_TAB || code === CODE_LINE_FEED || code === CODE_CARRIAGE_RETURN;
|
|
4231
4622
|
}
|
|
4232
4623
|
/**
|
|
4233
|
-
* Whether a code unit can start an XML name. The
|
|
4624
|
+
* Whether a code unit can start an XML name. The dialect's vocabulary is
|
|
4234
4625
|
* ASCII, so the accepted alphabet is letters, underscore, and colon.
|
|
4235
4626
|
*/
|
|
4236
4627
|
function isNameStart(code) {
|
|
@@ -4251,25 +4642,41 @@ const NAMED_ENTITIES = {
|
|
|
4251
4642
|
quot: "\""
|
|
4252
4643
|
};
|
|
4253
4644
|
/**
|
|
4254
|
-
*
|
|
4255
|
-
*
|
|
4256
|
-
*
|
|
4257
|
-
* document, with the line and column of the failure.
|
|
4645
|
+
* Matches the characters no XML document can carry, mirroring the
|
|
4646
|
+
* writer's validation so a comment the parser accepts is one the
|
|
4647
|
+
* serializer reproduces.
|
|
4258
4648
|
*/
|
|
4259
|
-
|
|
4260
|
-
|
|
4649
|
+
const UNCARRIABLE_PATTERN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uFFFE\uFFFF]/u;
|
|
4650
|
+
/**
|
|
4651
|
+
* Whether a code point is a character XML 1.0 allows in a document.
|
|
4652
|
+
* The excluded ranges are the control characters other than tab, line
|
|
4653
|
+
* feed, and carriage return, the surrogate halves, and the two
|
|
4654
|
+
* noncharacters at the end of the basic plane.
|
|
4655
|
+
*/
|
|
4656
|
+
function isXmlChar(codePoint) {
|
|
4657
|
+
return codePoint === CODE_TAB || codePoint === CODE_LINE_FEED || codePoint === CODE_CARRIAGE_RETURN || codePoint >= 32 && codePoint <= 55295 || codePoint >= 57344 && codePoint <= 65533 || codePoint >= 65536 && codePoint <= 1114111;
|
|
4658
|
+
}
|
|
4659
|
+
/**
|
|
4660
|
+
* Parses the text of a document in the dialect into its node tree,
|
|
4661
|
+
* reporting failures through the given error factory.
|
|
4662
|
+
*/
|
|
4663
|
+
function parseXmlDocument(text, makeError) {
|
|
4664
|
+
return new Parser(text, makeError).parseDocument();
|
|
4261
4665
|
}
|
|
4262
4666
|
/**
|
|
4263
4667
|
* Single-pass recursive-descent parser over the source text. One instance
|
|
4264
4668
|
* parses one document and is discarded.
|
|
4265
4669
|
*/
|
|
4266
4670
|
var Parser = class {
|
|
4267
|
-
/** The full source text of the
|
|
4671
|
+
/** The full source text of the file. */
|
|
4268
4672
|
input;
|
|
4673
|
+
/** Constructs the format's parse error for a failure. */
|
|
4674
|
+
makeError;
|
|
4269
4675
|
/** Cursor into {@link input}, in UTF-16 code units. */
|
|
4270
4676
|
pos = 0;
|
|
4271
|
-
constructor(input) {
|
|
4677
|
+
constructor(input, makeError) {
|
|
4272
4678
|
this.input = input;
|
|
4679
|
+
this.makeError = makeError;
|
|
4273
4680
|
if (input.charCodeAt(0) === CODE_BOM) this.pos = 1;
|
|
4274
4681
|
}
|
|
4275
4682
|
/**
|
|
@@ -4303,10 +4710,14 @@ var Parser = class {
|
|
|
4303
4710
|
/**
|
|
4304
4711
|
* Skips the `<?xml ... ?>` declaration when present. Its attributes are
|
|
4305
4712
|
* not retained, since the writer always emits the canonical UTF-8
|
|
4306
|
-
* declaration.
|
|
4713
|
+
* declaration. Other processing instructions fail, because the dialect
|
|
4714
|
+
* has no place to keep them and dropping one silently would make the
|
|
4715
|
+
* round-trip lossy.
|
|
4307
4716
|
*/
|
|
4308
4717
|
skipDeclaration() {
|
|
4309
4718
|
if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN || this.input.charCodeAt(this.pos + 1) !== CODE_QUESTION) return;
|
|
4719
|
+
const after = this.input.charCodeAt(this.pos + 5);
|
|
4720
|
+
if (this.input.slice(this.pos + 2, this.pos + 5) !== "xml" || !isWhitespace(after) && after !== CODE_QUESTION) this.fail("Unsupported processing instruction");
|
|
4310
4721
|
const end = this.input.indexOf("?>", this.pos + 2);
|
|
4311
4722
|
if (end === -1) this.fail("Unterminated XML declaration");
|
|
4312
4723
|
this.pos = end + 2;
|
|
@@ -4378,12 +4789,17 @@ var Parser = class {
|
|
|
4378
4789
|
}
|
|
4379
4790
|
/**
|
|
4380
4791
|
* Parses one comment with the cursor on its `<!--` opener. The text
|
|
4381
|
-
* between the markers is kept verbatim.
|
|
4792
|
+
* between the markers is kept verbatim. A `--` inside the text or a
|
|
4793
|
+
* `-` against the closing marker fails the way XML requires, which
|
|
4794
|
+
* also keeps every accepted comment rebuildable, since such text would
|
|
4795
|
+
* reparse at a different terminator.
|
|
4382
4796
|
*/
|
|
4383
4797
|
parseComment() {
|
|
4384
4798
|
const end = this.input.indexOf("-->", this.pos + 4);
|
|
4385
4799
|
if (end === -1) this.fail("Unterminated comment");
|
|
4386
4800
|
const comment = this.input.slice(this.pos + 4, end);
|
|
4801
|
+
if (comment.includes("--") || comment.endsWith("-")) this.fail("Comments cannot contain -- or end with -");
|
|
4802
|
+
if (UNCARRIABLE_PATTERN.test(comment)) this.fail("Comments cannot contain characters XML cannot carry");
|
|
4387
4803
|
this.pos = end + 3;
|
|
4388
4804
|
return { comment };
|
|
4389
4805
|
}
|
|
@@ -4423,14 +4839,37 @@ var Parser = class {
|
|
|
4423
4839
|
value += this.input.slice(runStart, this.pos);
|
|
4424
4840
|
value += this.parseReference();
|
|
4425
4841
|
runStart = this.pos;
|
|
4426
|
-
|
|
4842
|
+
continue;
|
|
4843
|
+
}
|
|
4844
|
+
if (code < 32 && !isWhitespace(code)) this.fail("Attribute values cannot contain a raw control character");
|
|
4845
|
+
if (code >= 55296) {
|
|
4846
|
+
this.validateUpperCharacter(code);
|
|
4847
|
+
this.pos += code <= 56319 ? 2 : 1;
|
|
4848
|
+
continue;
|
|
4849
|
+
}
|
|
4850
|
+
this.pos++;
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
/**
|
|
4854
|
+
* Validates a code unit in the surrogate or upper basic-plane range at
|
|
4855
|
+
* the cursor. A high surrogate must pair with a low one, a bare low
|
|
4856
|
+
* surrogate is not a character, and the two noncharacters at the end
|
|
4857
|
+
* of the plane have no XML representation.
|
|
4858
|
+
*/
|
|
4859
|
+
validateUpperCharacter(code) {
|
|
4860
|
+
if (code <= 56319) {
|
|
4861
|
+
const next = this.input.charCodeAt(this.pos + 1);
|
|
4862
|
+
if (Number.isNaN(next) || next < 56320 || next > 57343) this.fail("Attribute values cannot contain an unpaired surrogate");
|
|
4863
|
+
return;
|
|
4427
4864
|
}
|
|
4865
|
+
if (code <= 57343) this.fail("Attribute values cannot contain an unpaired surrogate");
|
|
4866
|
+
if (code === 65534 || code === 65535) this.fail("Attribute values cannot contain a noncharacter");
|
|
4428
4867
|
}
|
|
4429
4868
|
/**
|
|
4430
4869
|
* Resolves one `&...;` reference with the cursor on the ampersand. The
|
|
4431
4870
|
* accepted forms are the five XML named entities and decimal or hex
|
|
4432
4871
|
* character references, which covers everything observed in
|
|
4433
|
-
* Xcode-written
|
|
4872
|
+
* Xcode-written files (`"`, `&`, `'`, `<`, `>`,
|
|
4434
4873
|
* and ` `-style whitespace).
|
|
4435
4874
|
*/
|
|
4436
4875
|
parseReference() {
|
|
@@ -4442,9 +4881,10 @@ var Parser = class {
|
|
|
4442
4881
|
if (body.charCodeAt(0) === CODE_HASH) {
|
|
4443
4882
|
const isHex = body.charCodeAt(1) === 120 || body.charCodeAt(1) === 88;
|
|
4444
4883
|
const digits = body.slice(isHex ? 2 : 1);
|
|
4884
|
+
if (!(isHex ? /^[0-9A-Fa-f]+$/u : /^[0-9]+$/u).test(digits)) this.failAt(`Invalid character reference &${body};`, start);
|
|
4445
4885
|
const radix = isHex ? 16 : 10;
|
|
4446
4886
|
const codePoint = Number.parseInt(digits, radix);
|
|
4447
|
-
if (
|
|
4887
|
+
if (!isXmlChar(codePoint)) this.failAt(`Character reference &${body}; is not an XML character`, start);
|
|
4448
4888
|
return String.fromCodePoint(codePoint);
|
|
4449
4889
|
}
|
|
4450
4890
|
const named = Object.hasOwn(NAMED_ENTITIES, body) ? NAMED_ENTITIES[body] : void 0;
|
|
@@ -4471,10 +4911,28 @@ var Parser = class {
|
|
|
4471
4911
|
* uses to point at the start of a bad reference rather than its end.
|
|
4472
4912
|
*/
|
|
4473
4913
|
failAt(message, offset) {
|
|
4474
|
-
throw
|
|
4914
|
+
throw this.makeError(message, this.input, offset);
|
|
4475
4915
|
}
|
|
4476
4916
|
};
|
|
4477
4917
|
//#endregion
|
|
4918
|
+
//#region src/scheme/parse.ts
|
|
4919
|
+
/**
|
|
4920
|
+
* Parser entry point for the `.xcscheme` dialect. The grammar lives in
|
|
4921
|
+
* the shared XML core, and this wrapper binds it to the scheme error
|
|
4922
|
+
* type.
|
|
4923
|
+
*
|
|
4924
|
+
* @module
|
|
4925
|
+
*/
|
|
4926
|
+
/**
|
|
4927
|
+
* Parses the text of a `.xcscheme` file into its node tree.
|
|
4928
|
+
*
|
|
4929
|
+
* @throws XcschemeParseError when the text is not a well-formed scheme
|
|
4930
|
+
* document, with the line and column of the failure.
|
|
4931
|
+
*/
|
|
4932
|
+
function parseXcscheme(text) {
|
|
4933
|
+
return parseXmlDocument(text, (message, source, offset) => new XcschemeParseError(message, source, offset));
|
|
4934
|
+
}
|
|
4935
|
+
//#endregion
|
|
4478
4936
|
//#region src/scheme/model.ts
|
|
4479
4937
|
/**
|
|
4480
4938
|
* The scheme object model and its helpers.
|
|
@@ -4495,13 +4953,7 @@ var Parser = class {
|
|
|
4495
4953
|
* element.
|
|
4496
4954
|
*/
|
|
4497
4955
|
function xcschemeElements(root, name) {
|
|
4498
|
-
|
|
4499
|
-
const visit = (element) => {
|
|
4500
|
-
if (name == null || element.name === name) found.push(element);
|
|
4501
|
-
for (const child of element.children) if (isXcschemeElement(child)) visit(child);
|
|
4502
|
-
};
|
|
4503
|
-
visit(root);
|
|
4504
|
-
return found;
|
|
4956
|
+
return xmlElements(root, name);
|
|
4505
4957
|
}
|
|
4506
4958
|
/**
|
|
4507
4959
|
* A `BuildableReference` element with property-style attribute access.
|
|
@@ -4771,6 +5223,273 @@ function createXcscheme(options) {
|
|
|
4771
5223
|
};
|
|
4772
5224
|
}
|
|
4773
5225
|
//#endregion
|
|
5226
|
+
//#region src/scheme/types.ts
|
|
5227
|
+
/**
|
|
5228
|
+
* The node model of a scheme document. Scheme files share Xcode's XML
|
|
5229
|
+
* dialect with workspace data files, so the shapes here are the shared
|
|
5230
|
+
* XML node types under their scheme names.
|
|
5231
|
+
*
|
|
5232
|
+
* A parsed `.xcscheme` is a tree of plain objects. Elements carry ordered
|
|
5233
|
+
* attributes and child nodes, and comments survive as their own nodes.
|
|
5234
|
+
* All state lives in this tree. There is no wrapper to keep in sync, so
|
|
5235
|
+
* callers mutate nodes directly and serialize whatever the tree currently
|
|
5236
|
+
* says.
|
|
5237
|
+
*
|
|
5238
|
+
* @module
|
|
5239
|
+
*/
|
|
5240
|
+
/**
|
|
5241
|
+
* Whether a node is an element rather than a comment.
|
|
5242
|
+
*/
|
|
5243
|
+
function isXcschemeElement(node) {
|
|
5244
|
+
return isXmlElement(node);
|
|
5245
|
+
}
|
|
5246
|
+
//#endregion
|
|
5247
|
+
//#region src/workspace/build.ts
|
|
5248
|
+
/**
|
|
5249
|
+
* Serializer entry point for the `contents.xcworkspacedata` dialect. The
|
|
5250
|
+
* layout rules live in the shared XML core, and this wrapper binds them
|
|
5251
|
+
* to the workspace error type.
|
|
5252
|
+
*
|
|
5253
|
+
* @module
|
|
5254
|
+
*/
|
|
5255
|
+
/**
|
|
5256
|
+
* Serializes a workspace document to the text of a
|
|
5257
|
+
* `contents.xcworkspacedata` file in Xcode's canonical layout.
|
|
5258
|
+
*
|
|
5259
|
+
* @throws XcworkspaceBuildError for element or attribute names that are
|
|
5260
|
+
* not XML names and for attribute values carrying unencodable control
|
|
5261
|
+
* characters, with the path of the offending node.
|
|
5262
|
+
*/
|
|
5263
|
+
function buildXcworkspace(document) {
|
|
5264
|
+
return buildXmlDocument(document, (message, path) => new XcworkspaceBuildError(message, path));
|
|
5265
|
+
}
|
|
5266
|
+
//#endregion
|
|
5267
|
+
//#region src/workspace/parse.ts
|
|
5268
|
+
/**
|
|
5269
|
+
* Parser entry point for the `contents.xcworkspacedata` dialect. The
|
|
5270
|
+
* grammar lives in the shared XML core, and this wrapper binds it to the
|
|
5271
|
+
* workspace error type.
|
|
5272
|
+
*
|
|
5273
|
+
* @module
|
|
5274
|
+
*/
|
|
5275
|
+
/**
|
|
5276
|
+
* Parses the text of a `contents.xcworkspacedata` file into its node
|
|
5277
|
+
* tree.
|
|
5278
|
+
*
|
|
5279
|
+
* @throws XcworkspaceParseError when the text is not a well-formed
|
|
5280
|
+
* workspace document, with the line and column of the failure.
|
|
5281
|
+
*/
|
|
5282
|
+
function parseXcworkspace(text) {
|
|
5283
|
+
return parseXmlDocument(text, (message, source, offset) => new XcworkspaceParseError(message, source, offset));
|
|
5284
|
+
}
|
|
5285
|
+
//#endregion
|
|
5286
|
+
//#region src/workspace/model.ts
|
|
5287
|
+
/**
|
|
5288
|
+
* The workspace object model.
|
|
5289
|
+
*
|
|
5290
|
+
* A `.xcworkspace` directory carries a `contents.xcworkspacedata` file
|
|
5291
|
+
* listing the projects and folders the workspace shows, in the same XML
|
|
5292
|
+
* dialect scheme files use. {@link Xcworkspace} wraps the parsed
|
|
5293
|
+
* document with typed access to the file references, so tooling resolves
|
|
5294
|
+
* which projects a workspace opens instead of globbing the directory
|
|
5295
|
+
* tree. The node tree stays the single source of truth. Views hold only
|
|
5296
|
+
* a reference into it, so model calls and direct tree edits compose
|
|
5297
|
+
* freely.
|
|
5298
|
+
*
|
|
5299
|
+
* @module
|
|
5300
|
+
*/
|
|
5301
|
+
/**
|
|
5302
|
+
* Splits a location attribute into its kind and path. Xcode writes the
|
|
5303
|
+
* kinds `group` (relative to the enclosing group), `container` (relative
|
|
5304
|
+
* to the directory containing the `.xcworkspace`), `absolute`, `self`
|
|
5305
|
+
* (the containing `.xcodeproj` itself), and `developer` (inside the
|
|
5306
|
+
* developer directory).
|
|
5307
|
+
*/
|
|
5308
|
+
function parseWorkspaceLocation(location) {
|
|
5309
|
+
const colon = location.indexOf(":");
|
|
5310
|
+
if (colon === -1) return {
|
|
5311
|
+
kind: "group",
|
|
5312
|
+
path: location
|
|
5313
|
+
};
|
|
5314
|
+
return {
|
|
5315
|
+
kind: location.slice(0, colon),
|
|
5316
|
+
path: location.slice(colon + 1)
|
|
5317
|
+
};
|
|
5318
|
+
}
|
|
5319
|
+
/**
|
|
5320
|
+
* A `FileRef` element with property-style attribute access. File
|
|
5321
|
+
* references are the elements workspace edits touch, since each one
|
|
5322
|
+
* names a project or folder the workspace shows. The view reads and
|
|
5323
|
+
* writes the element's attributes directly, so it never goes stale and
|
|
5324
|
+
* needs no separate save step.
|
|
5325
|
+
*/
|
|
5326
|
+
var WorkspaceFileRef = class {
|
|
5327
|
+
/** The underlying element inside the document tree. */
|
|
5328
|
+
element;
|
|
5329
|
+
constructor(element) {
|
|
5330
|
+
this.element = element;
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5333
|
+
* The reference's location attribute, when present, for example
|
|
5334
|
+
* `group:Pods/Pods.xcodeproj`.
|
|
5335
|
+
*/
|
|
5336
|
+
get location() {
|
|
5337
|
+
return this.element.attributes["location"];
|
|
5338
|
+
}
|
|
5339
|
+
set location(value) {
|
|
5340
|
+
this.element.attributes["location"] = value;
|
|
5341
|
+
}
|
|
5342
|
+
};
|
|
5343
|
+
/**
|
|
5344
|
+
* A workspace document with typed access to the elements editing flows
|
|
5345
|
+
* touch.
|
|
5346
|
+
*
|
|
5347
|
+
* The model is a thin layer over the node tree. All state lives in the
|
|
5348
|
+
* document itself, and {@link build} serializes whatever the tree
|
|
5349
|
+
* currently says, so typed edits and direct tree edits compose freely.
|
|
5350
|
+
*
|
|
5351
|
+
* ```ts
|
|
5352
|
+
* const workspace = Xcworkspace.parse(xcworkspacedataText);
|
|
5353
|
+
* workspace.projectFilePaths(); // ["DemoApp.xcodeproj", "Pods/Pods.xcodeproj"]
|
|
5354
|
+
* ```
|
|
5355
|
+
*/
|
|
5356
|
+
var Xcworkspace = class Xcworkspace {
|
|
5357
|
+
/** The underlying parsed document. */
|
|
5358
|
+
document;
|
|
5359
|
+
constructor(document) {
|
|
5360
|
+
this.document = document;
|
|
5361
|
+
}
|
|
5362
|
+
/**
|
|
5363
|
+
* Parses the text of a `contents.xcworkspacedata` file into a
|
|
5364
|
+
* workspace model.
|
|
5365
|
+
*
|
|
5366
|
+
* @throws XcworkspaceParseError when the text is not a well-formed
|
|
5367
|
+
* workspace document, with the line and column of the failure.
|
|
5368
|
+
*/
|
|
5369
|
+
static parse(text) {
|
|
5370
|
+
return new Xcworkspace(parseXcworkspace(text));
|
|
5371
|
+
}
|
|
5372
|
+
/**
|
|
5373
|
+
* Creates the workspace document Xcode writes, listing the given
|
|
5374
|
+
* locations as file references.
|
|
5375
|
+
*/
|
|
5376
|
+
static create(options) {
|
|
5377
|
+
const children = (options?.locations ?? []).map((location) => ({
|
|
5378
|
+
name: "FileRef",
|
|
5379
|
+
attributes: { location },
|
|
5380
|
+
children: []
|
|
5381
|
+
}));
|
|
5382
|
+
return new Xcworkspace({
|
|
5383
|
+
leading: [],
|
|
5384
|
+
root: {
|
|
5385
|
+
name: "Workspace",
|
|
5386
|
+
attributes: { version: "1.0" },
|
|
5387
|
+
children
|
|
5388
|
+
},
|
|
5389
|
+
trailing: []
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
/**
|
|
5393
|
+
* The document's `Workspace` element.
|
|
5394
|
+
*/
|
|
5395
|
+
get root() {
|
|
5396
|
+
return this.document.root;
|
|
5397
|
+
}
|
|
5398
|
+
/**
|
|
5399
|
+
* Serializes the workspace to the text of a `contents.xcworkspacedata`
|
|
5400
|
+
* file in Xcode's canonical layout.
|
|
5401
|
+
*/
|
|
5402
|
+
build() {
|
|
5403
|
+
return buildXcworkspace(this.document);
|
|
5404
|
+
}
|
|
5405
|
+
/**
|
|
5406
|
+
* Collects elements of the given name anywhere in the document, in
|
|
5407
|
+
* document order. Passing no name collects every element.
|
|
5408
|
+
*/
|
|
5409
|
+
elements(name) {
|
|
5410
|
+
return xmlElements(this.document.root, name);
|
|
5411
|
+
}
|
|
5412
|
+
/**
|
|
5413
|
+
* The views of every file reference in the document, in document
|
|
5414
|
+
* order, references nested inside groups included.
|
|
5415
|
+
*/
|
|
5416
|
+
fileRefs() {
|
|
5417
|
+
return this.elements("FileRef").map((element) => new WorkspaceFileRef(element));
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Appends a file reference to the workspace's top level and returns
|
|
5421
|
+
* its view.
|
|
5422
|
+
*/
|
|
5423
|
+
addFileRef(location) {
|
|
5424
|
+
const element = {
|
|
5425
|
+
name: "FileRef",
|
|
5426
|
+
attributes: { location },
|
|
5427
|
+
children: []
|
|
5428
|
+
};
|
|
5429
|
+
this.root.children.push(element);
|
|
5430
|
+
return new WorkspaceFileRef(element);
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* Removes a file reference from whichever element holds it. Returns
|
|
5434
|
+
* whether the reference was found and removed.
|
|
5435
|
+
*/
|
|
5436
|
+
removeFileRef(reference) {
|
|
5437
|
+
for (const parent of this.elements()) {
|
|
5438
|
+
const index = parent.children.indexOf(reference.element);
|
|
5439
|
+
if (index !== -1) {
|
|
5440
|
+
parent.children.splice(index, 1);
|
|
5441
|
+
return true;
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
return false;
|
|
5445
|
+
}
|
|
5446
|
+
/**
|
|
5447
|
+
* The paths of every `.xcodeproj` the workspace references, in
|
|
5448
|
+
* document order, resolved relative to the directory containing the
|
|
5449
|
+
* `.xcworkspace`. Group locations compose through their enclosing
|
|
5450
|
+
* groups, container locations anchor at the workspace's directory, and
|
|
5451
|
+
* absolute locations pass through unchanged.
|
|
5452
|
+
*
|
|
5453
|
+
* The resolution is textual. The library never touches the
|
|
5454
|
+
* filesystem, so locations that only the running Xcode can resolve
|
|
5455
|
+
* (`self` and `developer`) are not listed.
|
|
5456
|
+
*/
|
|
5457
|
+
projectFilePaths() {
|
|
5458
|
+
const paths = [];
|
|
5459
|
+
const visit = (element, base) => {
|
|
5460
|
+
for (const child of element.children) {
|
|
5461
|
+
if (!isXmlElement(child)) continue;
|
|
5462
|
+
const resolved = resolveLocationPath(parseWorkspaceLocation(child.attributes["location"] ?? ""), base);
|
|
5463
|
+
if (child.name === "Group") visit(child, resolved ?? base);
|
|
5464
|
+
else if (child.name === "FileRef" && resolved != null && resolved.endsWith(".xcodeproj")) paths.push(resolved);
|
|
5465
|
+
}
|
|
5466
|
+
};
|
|
5467
|
+
visit(this.document.root, "");
|
|
5468
|
+
return paths;
|
|
5469
|
+
}
|
|
5470
|
+
};
|
|
5471
|
+
/**
|
|
5472
|
+
* Resolves a location against the enclosing group's base path, or
|
|
5473
|
+
* `undefined` for the kinds only a running Xcode can resolve. The empty
|
|
5474
|
+
* string names the workspace's own directory.
|
|
5475
|
+
*/
|
|
5476
|
+
function resolveLocationPath(location, base) {
|
|
5477
|
+
switch (location.kind) {
|
|
5478
|
+
case "group": return joinPaths(base, location.path);
|
|
5479
|
+
case "container": return location.path;
|
|
5480
|
+
case "absolute": return location.path;
|
|
5481
|
+
default: return;
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
/**
|
|
5485
|
+
* Joins two textual path segments, keeping the result free of empty
|
|
5486
|
+
* segments when either side is empty.
|
|
5487
|
+
*/
|
|
5488
|
+
function joinPaths(base, path) {
|
|
5489
|
+
if (base === "" || path === "") return base === "" ? path : base;
|
|
5490
|
+
return `${base}/${path}`;
|
|
5491
|
+
}
|
|
5492
|
+
//#endregion
|
|
4774
5493
|
//#region src/xcconfig/build.ts
|
|
4775
5494
|
/**
|
|
4776
5495
|
* Serializes a document back to `.xcconfig` text.
|
|
@@ -5095,4 +5814,4 @@ var Xcconfig = class Xcconfig {
|
|
|
5095
5814
|
}
|
|
5096
5815
|
};
|
|
5097
5816
|
//#endregion
|
|
5098
|
-
export { AggregateTarget, AppleScriptBuildPhase, BuildConfiguration, BuildFile, BuildFileExceptionSet, BuildPhase, BuildPhaseMembershipExceptionSet, BuildRule, BuildStyle, BuildableReference, ConfigurationList, ContainerItemProxy, CopyFilesBuildPhase, CopyFilesDestination, ExceptionSet, FileReference, FrameworksBuildPhase, Group, HeadersBuildPhase, Isa, LegacyTarget, LocalSwiftPackageReference, NativeTarget, PbxprojBuildError, PbxprojParseError, ProductType, ReferenceProxy, RemoteSwiftPackageReference, ResourcesBuildPhase, RezBuildPhase, RootProject, ShellScriptBuildPhase, SourcesBuildPhase, SwiftPackageProductDependency, SwiftPackageReference, SyncRootGroup, Target, TargetDependency, VariantGroup, VersionGroup, Xcconfig, XcconfigParseError, XcodeModelError, XcodeObject, XcodeProject, Xcscheme, XcschemeBuildError, XcschemeParseError, buildPbxproj, buildXcconfig, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parseApplePlatform, parsePbxproj, parseXcconfig, parseXcscheme, xcschemeElements };
|
|
5817
|
+
export { AggregateTarget, AppleScriptBuildPhase, BuildConfiguration, BuildFile, BuildFileExceptionSet, BuildPhase, BuildPhaseMembershipExceptionSet, BuildRule, BuildStyle, BuildableReference, ConfigurationList, ContainerItemProxy, CopyFilesBuildPhase, CopyFilesDestination, ExceptionSet, FileReference, FrameworksBuildPhase, Group, HeadersBuildPhase, Isa, LegacyTarget, LocalSwiftPackageReference, NativeTarget, PbxprojBuildError, PbxprojParseError, ProductType, ReferenceProxy, RemoteSwiftPackageReference, ResourcesBuildPhase, RezBuildPhase, RootProject, ShellScriptBuildPhase, SourcesBuildPhase, SwiftPackageProductDependency, SwiftPackageReference, SyncRootGroup, Target, TargetDependency, VariantGroup, VersionGroup, WorkspaceFileRef, Xcconfig, XcconfigParseError, XcodeModelError, XcodeObject, XcodeProject, Xcscheme, XcschemeBuildError, XcschemeParseError, Xcworkspace, XcworkspaceBuildError, XcworkspaceParseError, buildPbxproj, buildXcconfig, buildXcscheme, buildXcworkspace, createXcscheme, expandBuildSettingReferences, generateObjectId, isXcschemeElement, isXmlElement, parseApplePlatform, parsePbxproj, parseWorkspaceLocation, parseXcconfig, parseXcscheme, parseXcworkspace, xcschemeElements, xmlElements };
|