rork-xcode 0.8.0 → 0.10.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 +59 -11
- package/dist/index.d.ts +379 -51
- package/dist/index.js +662 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -52,8 +52,8 @@ function repoNameFromUrl(repoUrl) {
|
|
|
52
52
|
* Builds the uuid-to-comment map for a parsed project document.
|
|
53
53
|
*
|
|
54
54
|
* Objects that should render without a comment (unnamed groups) map to the
|
|
55
|
-
* empty string
|
|
56
|
-
* Derivation is linear over the object graph
|
|
55
|
+
* empty string, and uuids absent from the map are not references at all.
|
|
56
|
+
* Derivation is linear over the object graph. Reverse indexes are built in
|
|
57
57
|
* one pass, and every object's comment is computed once and cached.
|
|
58
58
|
*/
|
|
59
59
|
function createReferenceComments(root) {
|
|
@@ -100,9 +100,9 @@ function createReferenceComments(root) {
|
|
|
100
100
|
};
|
|
101
101
|
/**
|
|
102
102
|
* Comment for a `PBXFileSystemSynchronizedBuildFileExceptionSet`,
|
|
103
|
-
* matching current Xcode
|
|
104
|
-
* target`. Falls back to the isa when the folder or target cannot
|
|
105
|
-
* resolved (older documents and hand-edited graphs).
|
|
103
|
+
* matching current Xcode, for example `Exceptions for "clip" folder in
|
|
104
|
+
* "clip" target`. Falls back to the isa when the folder or target cannot
|
|
105
|
+
* be resolved (older documents and hand-edited graphs).
|
|
106
106
|
*/
|
|
107
107
|
const buildFileExceptionSetComment = (id, set) => {
|
|
108
108
|
const folder = exceptionSetFolderName(id);
|
|
@@ -114,9 +114,9 @@ function createReferenceComments(root) {
|
|
|
114
114
|
};
|
|
115
115
|
/**
|
|
116
116
|
* Comment for a `PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet`,
|
|
117
|
-
* matching current Xcode
|
|
118
|
-
* phase from "Tophat" target`. Falls back to the isa when
|
|
119
|
-
* three names cannot be resolved.
|
|
117
|
+
* matching current Xcode, for example `Exceptions for "Tophat" folder in
|
|
118
|
+
* "CopyFiles" phase from "Tophat" target`. Falls back to the isa when
|
|
119
|
+
* any of the three names cannot be resolved.
|
|
120
120
|
*/
|
|
121
121
|
const membershipExceptionSetComment = (id, set) => {
|
|
122
122
|
const folder = exceptionSetFolderName(id);
|
|
@@ -246,7 +246,7 @@ var PbxprojParseError = class extends Error {
|
|
|
246
246
|
/** Where in the source text parsing failed. */
|
|
247
247
|
position;
|
|
248
248
|
/**
|
|
249
|
-
* @param message Failure description without location
|
|
249
|
+
* @param message Failure description without location. The location is
|
|
250
250
|
* appended automatically.
|
|
251
251
|
* @param source Full source text, used to compute the position.
|
|
252
252
|
* @param offset Character offset of the failure inside `source`.
|
|
@@ -270,7 +270,7 @@ var XcschemeParseError = class extends Error {
|
|
|
270
270
|
/** Where in the source text parsing failed. */
|
|
271
271
|
position;
|
|
272
272
|
/**
|
|
273
|
-
* @param message Failure description without location
|
|
273
|
+
* @param message Failure description without location. The location is
|
|
274
274
|
* appended automatically.
|
|
275
275
|
* @param source Full source text, used to compute the position.
|
|
276
276
|
* @param offset Character offset of the failure inside `source`.
|
|
@@ -283,6 +283,30 @@ var XcschemeParseError = class extends Error {
|
|
|
283
283
|
}
|
|
284
284
|
};
|
|
285
285
|
/**
|
|
286
|
+
* Thrown when the source text is not a well-formed build configuration
|
|
287
|
+
* file (the line-based format of `.xcconfig` files).
|
|
288
|
+
*
|
|
289
|
+
* The message always embeds the line and column of the failure, and the
|
|
290
|
+
* same information is available in structured form on {@link position}
|
|
291
|
+
* for programmatic use.
|
|
292
|
+
*/
|
|
293
|
+
var XcconfigParseError = class extends Error {
|
|
294
|
+
/** Where in the source text parsing failed. */
|
|
295
|
+
position;
|
|
296
|
+
/**
|
|
297
|
+
* @param message Failure description without location. The location is
|
|
298
|
+
* appended automatically.
|
|
299
|
+
* @param source Full source text, used to compute the position.
|
|
300
|
+
* @param offset Character offset of the failure inside `source`.
|
|
301
|
+
*/
|
|
302
|
+
constructor(message, source, offset) {
|
|
303
|
+
const position = positionAt(source, offset);
|
|
304
|
+
super(`${message} (line ${position.line}, column ${position.column})`);
|
|
305
|
+
this.name = "XcconfigParseError";
|
|
306
|
+
this.position = position;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
286
310
|
* Thrown when a scheme element cannot be written as XML.
|
|
287
311
|
*
|
|
288
312
|
* Raised for element and attribute names that are not valid XML names and
|
|
@@ -293,7 +317,7 @@ var XcschemeBuildError = class extends Error {
|
|
|
293
317
|
/** Path to the offending element from the root, e.g. `Scheme.BuildAction[0]`. */
|
|
294
318
|
path;
|
|
295
319
|
/**
|
|
296
|
-
* @param message Failure description without location
|
|
320
|
+
* @param message Failure description without location. The element path
|
|
297
321
|
* is appended automatically.
|
|
298
322
|
* @param path Path to the offending element from the root.
|
|
299
323
|
*/
|
|
@@ -304,7 +328,7 @@ var XcschemeBuildError = class extends Error {
|
|
|
304
328
|
}
|
|
305
329
|
};
|
|
306
330
|
/**
|
|
307
|
-
* Thrown by the object model when an operation cannot proceed
|
|
331
|
+
* Thrown by the object model when an operation cannot proceed, because the
|
|
308
332
|
* document lacks the structure the operation needs (no objects dictionary,
|
|
309
333
|
* no root project object), a view's object was removed from the document,
|
|
310
334
|
* or a creation request names a product type the model cannot scaffold.
|
|
@@ -336,7 +360,7 @@ var PbxprojBuildError = class extends Error {
|
|
|
336
360
|
/** Path to the offending value from the root, e.g. `$.objects.13B07F86.name`. */
|
|
337
361
|
path;
|
|
338
362
|
/**
|
|
339
|
-
* @param message Failure description without location
|
|
363
|
+
* @param message Failure description without location. The value path is
|
|
340
364
|
* appended automatically.
|
|
341
365
|
* @param path Path to the offending value from the root, `$`.
|
|
342
366
|
*/
|
|
@@ -363,8 +387,8 @@ var PbxprojBuildError = class extends Error {
|
|
|
363
387
|
*/
|
|
364
388
|
const UNQUOTED_SAFE_PATTERN = /^[A-Za-z0-9_$/:.]+$/u;
|
|
365
389
|
/**
|
|
366
|
-
* Whether the value can render without quotes
|
|
367
|
-
* unquoted-safe alphabet and the string is not empty.
|
|
390
|
+
* Whether the value can render without quotes, meaning every character is
|
|
391
|
+
* in the unquoted-safe alphabet and the string is not empty.
|
|
368
392
|
*
|
|
369
393
|
* Quoting decisions scan every string a document carries, and many of those
|
|
370
394
|
* strings are substring slices of the source text. The regex engine flattens
|
|
@@ -376,7 +400,7 @@ function isSafeUnquoted(value) {
|
|
|
376
400
|
}
|
|
377
401
|
/**
|
|
378
402
|
* Whether the string contains characters that require escape sequences
|
|
379
|
-
* inside a quoted string
|
|
403
|
+
* inside a quoted string, meaning control characters, `"`, `\`, or DEL.
|
|
380
404
|
*/
|
|
381
405
|
function needsEscaping(value) {
|
|
382
406
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -386,8 +410,8 @@ function needsEscaping(value) {
|
|
|
386
410
|
return false;
|
|
387
411
|
}
|
|
388
412
|
/**
|
|
389
|
-
* Escapes special characters for a quoted string
|
|
390
|
-
* plus `\Uxxxx` for remaining control characters.
|
|
413
|
+
* Escapes special characters for a quoted string, using the named C-style
|
|
414
|
+
* escapes plus `\Uxxxx` for remaining control characters.
|
|
391
415
|
*/
|
|
392
416
|
function escapeString(value) {
|
|
393
417
|
let result = "";
|
|
@@ -439,7 +463,7 @@ function ensureQuotes(value) {
|
|
|
439
463
|
return `"${escapeString(value)}"`;
|
|
440
464
|
}
|
|
441
465
|
/**
|
|
442
|
-
* Renders binary data as an uppercase hex run
|
|
466
|
+
* Renders binary data as an uppercase hex run, for example `<DEADBEEF>`.
|
|
443
467
|
*/
|
|
444
468
|
function formatData(data) {
|
|
445
469
|
let hex = "";
|
|
@@ -461,7 +485,7 @@ function formatData(data) {
|
|
|
461
485
|
* - reference comments (`13B07F86… /* AppDelegate.swift in Sources */`)
|
|
462
486
|
* derived from the object graph.
|
|
463
487
|
*
|
|
464
|
-
* Numbers render exactly as JavaScript formats them
|
|
488
|
+
* Numbers render exactly as JavaScript formats them. The version-like
|
|
465
489
|
* settings Xcode writes with a trailing zero (`SWIFT_VERSION = 5.0`) arrive
|
|
466
490
|
* from the parser as strings and round-trip verbatim, so no reformatting
|
|
467
491
|
* heuristic is needed or applied.
|
|
@@ -517,25 +541,25 @@ function indentString(depth) {
|
|
|
517
541
|
/**
|
|
518
542
|
* Serialization state for one {@link buildPbxproj} call.
|
|
519
543
|
*
|
|
520
|
-
* The `write*` methods append directly to the output string
|
|
521
|
-
* methods return fragments for the caller to place. Output
|
|
522
|
-
* appending, because engines represent growing strings as
|
|
523
|
-
* stay cheap where template interpolation would allocate
|
|
524
|
-
* string per line.
|
|
544
|
+
* The `write*` methods append directly to the output string, and the
|
|
545
|
+
* `render*` methods return fragments for the caller to place. Output
|
|
546
|
+
* accumulates by appending, because engines represent growing strings as
|
|
547
|
+
* ropes. Appends stay cheap where template interpolation would allocate
|
|
548
|
+
* an intermediate string per line.
|
|
525
549
|
*/
|
|
526
550
|
var Writer = class {
|
|
527
551
|
/** The document text accumulated so far. */
|
|
528
552
|
out = "";
|
|
529
|
-
/** Current nesting depth
|
|
553
|
+
/** Current nesting depth, one tab per level. */
|
|
530
554
|
indent = 0;
|
|
531
555
|
/** Display comment per referenced uuid, derived once from the object graph. */
|
|
532
556
|
comments;
|
|
533
557
|
/**
|
|
534
|
-
* Rendered string values by input string
|
|
535
|
-
*
|
|
536
|
-
* render at least twice (their section entry plus each
|
|
537
|
-
* and build settings repeat across configurations, so
|
|
538
|
-
* cache hits. The map lives for one build call only.
|
|
558
|
+
* Rendered string values keyed by input string. Referenced uuids render
|
|
559
|
+
* as `id /* comment */` and everything else as quoted text. Referenced
|
|
560
|
+
* objects render at least twice (their section entry plus each
|
|
561
|
+
* referencing site) and build settings repeat across configurations, so
|
|
562
|
+
* most renders are cache hits. The map lives for one build call only.
|
|
539
563
|
*/
|
|
540
564
|
renderedReferences = /* @__PURE__ */ new Map();
|
|
541
565
|
/**
|
|
@@ -544,7 +568,7 @@ var Writer = class {
|
|
|
544
568
|
*/
|
|
545
569
|
quotedKeys = /* @__PURE__ */ new Map();
|
|
546
570
|
/**
|
|
547
|
-
* Serializes the whole document eagerly
|
|
571
|
+
* Serializes the whole document eagerly. Read it back with
|
|
548
572
|
* {@link toString}.
|
|
549
573
|
*
|
|
550
574
|
* @param root The document root dictionary.
|
|
@@ -582,7 +606,7 @@ var Writer = class {
|
|
|
582
606
|
*
|
|
583
607
|
* Most calls hit the cache, and the writers call this for every key and
|
|
584
608
|
* reference, so the method body stays small enough for the engine to
|
|
585
|
-
* inline
|
|
609
|
+
* inline. The miss path lives in {@link renderReferenceUncached}.
|
|
586
610
|
*/
|
|
587
611
|
renderReference(id) {
|
|
588
612
|
const cached = this.renderedReferences.get(id);
|
|
@@ -623,8 +647,8 @@ var Writer = class {
|
|
|
623
647
|
* Renders a string value in its key's context.
|
|
624
648
|
*
|
|
625
649
|
* `remoteGlobalIDString` and `TestTargetID` hold uuids of objects in
|
|
626
|
-
* another container
|
|
627
|
-
* be wrong, so they render bare.
|
|
650
|
+
* another container. Annotating them with this container's comments
|
|
651
|
+
* would be wrong, so they render bare.
|
|
628
652
|
*/
|
|
629
653
|
renderStringValue(key, value) {
|
|
630
654
|
if (key === "remoteGlobalIDString" || key === "TestTargetID") return ensureQuotes(value);
|
|
@@ -634,7 +658,7 @@ var Writer = class {
|
|
|
634
658
|
* Appends the entries of a dictionary, one `key = value;` line each.
|
|
635
659
|
*
|
|
636
660
|
* Value paths (`$.objects.AA….name`) exist for error messages and are
|
|
637
|
-
* only constructed in the branches that recurse or throw
|
|
661
|
+
* only constructed in the branches that recurse or throw. The flat string
|
|
638
662
|
* and number lines that dominate documents skip the concatenation.
|
|
639
663
|
*
|
|
640
664
|
* @param object The dictionary whose entries to write.
|
|
@@ -773,18 +797,18 @@ var Writer = class {
|
|
|
773
797
|
/**
|
|
774
798
|
* Serializes a project document to `project.pbxproj` text.
|
|
775
799
|
*
|
|
776
|
-
* The input is the same shape {@link parsePbxproj} produces
|
|
777
|
-
* documentation of `types.ts` for the value model. Output is stable
|
|
800
|
+
* The input is the same shape {@link parsePbxproj} produces. See the module
|
|
801
|
+
* documentation of `types.ts` for the value model. Output is stable, so two
|
|
778
802
|
* calls with semantically equal documents produce identical text, and the
|
|
779
803
|
* layout matches what Xcode itself writes so diffs stay minimal.
|
|
780
804
|
*
|
|
781
805
|
* @param root The document root. Real project documents carry `objects`,
|
|
782
806
|
* `rootObject`, and the version fields, but any dictionary serializes.
|
|
783
807
|
* @returns The document text, terminated by a newline.
|
|
784
|
-
* @throws PbxprojBuildError when a value has no pbxproj representation
|
|
785
|
-
* `null`, `undefined`, booleans, bigints, functions, symbols,
|
|
786
|
-
* instances, or non-finite numbers. The error names the path of
|
|
787
|
-
* offending value.
|
|
808
|
+
* @throws PbxprojBuildError when a value has no pbxproj representation,
|
|
809
|
+
* meaning `null`, `undefined`, booleans, bigints, functions, symbols,
|
|
810
|
+
* class instances, or non-finite numbers. The error names the path of
|
|
811
|
+
* the offending value.
|
|
788
812
|
*/
|
|
789
813
|
function buildPbxproj(root) {
|
|
790
814
|
if (!isDictionary(root)) throw new PbxprojBuildError("The document root must be a dictionary", "$");
|
|
@@ -795,15 +819,15 @@ function buildPbxproj(root) {
|
|
|
795
819
|
/**
|
|
796
820
|
* Names and well-known values of the pbxproj object vocabulary.
|
|
797
821
|
*
|
|
798
|
-
* Everything here mirrors strings Xcode itself writes
|
|
799
|
-
* Centralizing them keeps the object model free of string
|
|
800
|
-
* gives call sites one place to import from.
|
|
822
|
+
* Everything here mirrors strings Xcode itself writes, and nothing is
|
|
823
|
+
* invented. Centralizing them keeps the object model free of string
|
|
824
|
+
* literals and gives call sites one place to import from.
|
|
801
825
|
*
|
|
802
826
|
* @module
|
|
803
827
|
*/
|
|
804
828
|
/**
|
|
805
829
|
* The `isa` names the object model works with. The parser and serializer
|
|
806
|
-
* accept any isa
|
|
830
|
+
* accept any isa, and this list only covers the kinds the model creates or
|
|
807
831
|
* gives typed access to.
|
|
808
832
|
*/
|
|
809
833
|
const Isa = {
|
|
@@ -935,7 +959,7 @@ const CopyFilesDestination = {
|
|
|
935
959
|
};
|
|
936
960
|
/**
|
|
937
961
|
* Resolves the embed phase destination for an extension-like product type.
|
|
938
|
-
* Watch applications are embedded by product type here
|
|
962
|
+
* Watch applications are embedded by product type here. Callers that only
|
|
939
963
|
* know build settings should check the deployment-target key instead.
|
|
940
964
|
*/
|
|
941
965
|
function embedDestinationFor(productType) {
|
|
@@ -1023,7 +1047,7 @@ function ensureArray(object, key) {
|
|
|
1023
1047
|
* Collects the string items of a possibly absent, possibly mixed array.
|
|
1024
1048
|
*
|
|
1025
1049
|
* Reference lists in well-formed documents contain only id strings, but a
|
|
1026
|
-
* malformed document can mix in anything
|
|
1050
|
+
* malformed document can mix in anything. Non-strings are skipped rather
|
|
1027
1051
|
* than thrown on, matching the library's soft-failure stance on reads.
|
|
1028
1052
|
*/
|
|
1029
1053
|
function stringItems(value) {
|
|
@@ -1155,8 +1179,8 @@ var XcodeObject = class {
|
|
|
1155
1179
|
* nested dictionaries keyed by object id (such as the root project's
|
|
1156
1180
|
* `TargetAttributes`) drop its entry.
|
|
1157
1181
|
*
|
|
1158
|
-
* This is the low-level removal
|
|
1159
|
-
* made sense alongside this one. Higher-level operations like
|
|
1182
|
+
* This is the low-level removal, so it does not cascade to objects that
|
|
1183
|
+
* only made sense alongside this one. Higher-level operations like
|
|
1160
1184
|
* {@link XcodeProject.removeTarget} compose it into full teardowns.
|
|
1161
1185
|
*/
|
|
1162
1186
|
removeFromProject() {
|
|
@@ -1169,9 +1193,9 @@ var XcodeObject = class {
|
|
|
1169
1193
|
* Typed views over the document object kinds that are not targets, from
|
|
1170
1194
|
* groups and build phases to version groups and reference proxies.
|
|
1171
1195
|
*
|
|
1172
|
-
* Each class adds the accessors and mutations its kind supports
|
|
1173
|
-
* ultimately reads and writes the raw dictionaries through the
|
|
1174
|
-
* so mixing model calls with direct property access stays safe.
|
|
1196
|
+
* Each class adds the accessors and mutations its kind supports.
|
|
1197
|
+
* Everything ultimately reads and writes the raw dictionaries through the
|
|
1198
|
+
* base class, so mixing model calls with direct property access stays safe.
|
|
1175
1199
|
*
|
|
1176
1200
|
* @module
|
|
1177
1201
|
*/
|
|
@@ -1233,7 +1257,7 @@ var Group = class Group extends XcodeObject {
|
|
|
1233
1257
|
* it to the group's children.
|
|
1234
1258
|
*
|
|
1235
1259
|
* The reference's `lastKnownFileType` derives from the file extension
|
|
1236
|
-
* when it is a known kind
|
|
1260
|
+
* when it is a known kind. Otherwise the reference carries no type and
|
|
1237
1261
|
* Xcode re-derives one on open.
|
|
1238
1262
|
*
|
|
1239
1263
|
* @param path File path relative to the group, for example
|
|
@@ -1269,8 +1293,8 @@ var VariantGroup = class extends Group {
|
|
|
1269
1293
|
var BuildPhase = class extends XcodeObject {
|
|
1270
1294
|
/**
|
|
1271
1295
|
* The phase's display name, when it carries an explicit one. Xcode names
|
|
1272
|
-
* copy-files and shell-script phases
|
|
1273
|
-
* names from their isa.
|
|
1296
|
+
* copy-files and shell-script phases, while the standard phases derive
|
|
1297
|
+
* their names from their isa.
|
|
1274
1298
|
*/
|
|
1275
1299
|
get name() {
|
|
1276
1300
|
return this.getString("name");
|
|
@@ -1310,7 +1334,7 @@ var BuildPhase = class extends XcodeObject {
|
|
|
1310
1334
|
* @param reference The file reference or package product the build file
|
|
1311
1335
|
* should point at.
|
|
1312
1336
|
* @param options.referenceKey Which build-file field carries the
|
|
1313
|
-
* reference
|
|
1337
|
+
* reference, either `fileRef` for file references (the default) or
|
|
1314
1338
|
* `productRef` for Swift package products.
|
|
1315
1339
|
* @param options.settings Optional per-file settings, for example
|
|
1316
1340
|
* `{ ATTRIBUTES: ["RemoveHeadersOnCopy"] }`.
|
|
@@ -1423,7 +1447,7 @@ var BuildFile = class extends XcodeObject {
|
|
|
1423
1447
|
/**
|
|
1424
1448
|
* The view of the file reference the build file points at, when it
|
|
1425
1449
|
* points at one. Build files for Swift package products carry a
|
|
1426
|
-
* `productRef` instead
|
|
1450
|
+
* `productRef` instead, resolved through {@link productDependency}.
|
|
1427
1451
|
*/
|
|
1428
1452
|
fileReference() {
|
|
1429
1453
|
return this.project.get(this.getString("fileRef"));
|
|
@@ -1456,12 +1480,12 @@ var SyncRootGroup = class extends XcodeObject {
|
|
|
1456
1480
|
*
|
|
1457
1481
|
* Xcode keeps one exception set per target and folder, so when this
|
|
1458
1482
|
* group already carries a set for the target, the file names merge into
|
|
1459
|
-
* it instead of creating a second set
|
|
1460
|
-
* duplicated.
|
|
1483
|
+
* it instead of creating a second set, and names already excluded are
|
|
1484
|
+
* not duplicated.
|
|
1461
1485
|
*
|
|
1462
1486
|
* The standard use is keeping a scaffolded `Info.plist` from being
|
|
1463
|
-
* double-copied
|
|
1464
|
-
* `INFOPLIST_FILE` setting.
|
|
1487
|
+
* double-copied, since the build already processes it through the
|
|
1488
|
+
* target's `INFOPLIST_FILE` setting.
|
|
1465
1489
|
*
|
|
1466
1490
|
* @param target The target whose membership the exceptions restrict.
|
|
1467
1491
|
* @param membershipExceptions File names inside the folder to exclude.
|
|
@@ -1587,6 +1611,16 @@ var BuildConfiguration = class extends XcodeObject {
|
|
|
1587
1611
|
get buildSettings() {
|
|
1588
1612
|
return asDictionary(this.properties["buildSettings"]);
|
|
1589
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* The view of the `.xcconfig` file reference this configuration is
|
|
1616
|
+
* based on, when the configuration has one and it resolves. The file's
|
|
1617
|
+
* settings sit below the configuration's own `buildSettings` in
|
|
1618
|
+
* Xcode's resolution order.
|
|
1619
|
+
*/
|
|
1620
|
+
baseConfigurationReference() {
|
|
1621
|
+
const view = this.project.get(this.getString("baseConfigurationReference"));
|
|
1622
|
+
return FileReference.is(view) ? view : void 0;
|
|
1623
|
+
}
|
|
1590
1624
|
};
|
|
1591
1625
|
/**
|
|
1592
1626
|
* A `PBXBuildStyle` is the pre-Xcode-2 predecessor of
|
|
@@ -1782,9 +1816,10 @@ var ReferenceProxy = class extends XcodeObject {
|
|
|
1782
1816
|
* NeXTSTEP character set for byte values 0x80-0xFF, indexed by `byte - 0x80`.
|
|
1783
1817
|
*
|
|
1784
1818
|
* Octal escapes are how pre-Unicode NeXTSTEP text encoded non-ASCII
|
|
1785
|
-
* characters
|
|
1786
|
-
* `Æ` instead of the Latin-1 `á`. Values are Unicode code
|
|
1787
|
-
* published NEXTSTEP.TXT vendor mapping in the Unicode
|
|
1819
|
+
* characters, and mapping them through this table is what makes `\341`
|
|
1820
|
+
* decode to `Æ` instead of the Latin-1 `á`. Values are Unicode code
|
|
1821
|
+
* points, per the published NEXTSTEP.TXT vendor mapping in the Unicode
|
|
1822
|
+
* Character Database.
|
|
1788
1823
|
*/
|
|
1789
1824
|
const NEXT_STEP_MAPPINGS = [
|
|
1790
1825
|
160,
|
|
@@ -1919,8 +1954,8 @@ const NEXT_STEP_MAPPINGS = [
|
|
|
1919
1954
|
/**
|
|
1920
1955
|
* Maps an octal escape value to its Unicode code point.
|
|
1921
1956
|
*
|
|
1922
|
-
* Values below 0x80 are ASCII and pass through
|
|
1923
|
-
* from the NeXTSTEP character set.
|
|
1957
|
+
* Values below 0x80 are ASCII and pass through, while values in 0x80-0xFF
|
|
1958
|
+
* select from the NeXTSTEP character set.
|
|
1924
1959
|
*/
|
|
1925
1960
|
function nextStepToUnicode(code) {
|
|
1926
1961
|
if (code < 128 || code > 255) return code;
|
|
@@ -2029,7 +2064,8 @@ function unescapeString(input) {
|
|
|
2029
2064
|
* @module
|
|
2030
2065
|
*/
|
|
2031
2066
|
/**
|
|
2032
|
-
*
|
|
2067
|
+
* The characters allowed in unquoted string literals are
|
|
2068
|
+
* `[A-Za-z0-9_$/:.-]`.
|
|
2033
2069
|
*
|
|
2034
2070
|
* A 256-entry lookup table keyed by code unit keeps classification to a
|
|
2035
2071
|
* single array read in the hot loop. Non-ASCII units index past the table
|
|
@@ -2090,7 +2126,7 @@ function isDigit(code) {
|
|
|
2090
2126
|
* Scanner state and grammar productions for one parse call.
|
|
2091
2127
|
*
|
|
2092
2128
|
* The parser holds a single cursor into the source string and advances it
|
|
2093
|
-
* through the `read*` and `parse*` methods
|
|
2129
|
+
* through the `read*` and `parse*` methods. There is no separate tokenizer
|
|
2094
2130
|
* stage and no token objects.
|
|
2095
2131
|
*/
|
|
2096
2132
|
var Parser$1 = class {
|
|
@@ -2101,7 +2137,7 @@ var Parser$1 = class {
|
|
|
2101
2137
|
/**
|
|
2102
2138
|
* Offset of an unterminated block comment the trivia scanner consumed, or
|
|
2103
2139
|
* -1. Recording it instead of throwing keeps the trivia scanner free of
|
|
2104
|
-
* failure branches
|
|
2140
|
+
* failure branches, and {@link fail} reports it.
|
|
2105
2141
|
*/
|
|
2106
2142
|
unterminatedCommentAt = -1;
|
|
2107
2143
|
/**
|
|
@@ -2115,13 +2151,13 @@ var Parser$1 = class {
|
|
|
2115
2151
|
* failure.
|
|
2116
2152
|
*
|
|
2117
2153
|
* An unterminated block comment swallows the rest of the input, so any
|
|
2118
|
-
* failure raised after one (always some end-of-input error) is a symptom
|
|
2119
|
-
* the comment itself is reported instead. Content after the root
|
|
2120
|
-
* never scanned, so a trailing unterminated comment still
|
|
2121
|
-
* Apple's parser accepts it too.
|
|
2154
|
+
* failure raised after one (always some end-of-input error) is a symptom
|
|
2155
|
+
* and the comment itself is reported instead. Content after the root
|
|
2156
|
+
* value is never scanned, so a trailing unterminated comment still
|
|
2157
|
+
* parses, as Apple's parser accepts it too.
|
|
2122
2158
|
*
|
|
2123
2159
|
* @param message Failure description without location.
|
|
2124
|
-
* @param offset Offset of the failure
|
|
2160
|
+
* @param offset Offset of the failure, defaulting to the current cursor.
|
|
2125
2161
|
*/
|
|
2126
2162
|
fail(message, offset = this.pos) {
|
|
2127
2163
|
if (this.unterminatedCommentAt !== -1) throw new PbxprojParseError("Unterminated block comment", this.input, this.unterminatedCommentAt);
|
|
@@ -2217,11 +2253,11 @@ var Parser$1 = class {
|
|
|
2217
2253
|
return input.slice(start, pos);
|
|
2218
2254
|
}
|
|
2219
2255
|
/**
|
|
2220
|
-
* Reads a quoted string
|
|
2256
|
+
* Reads a quoted string whose opening quote is at the current position.
|
|
2221
2257
|
*
|
|
2222
|
-
* The scan tracks whether any escape sequence occurred
|
|
2223
|
-
* (the overwhelming majority) return as a direct slice, and only
|
|
2224
|
-
* ones pay for {@link unescapeString}.
|
|
2258
|
+
* The scan tracks whether any escape sequence occurred, so unescaped
|
|
2259
|
+
* strings (the overwhelming majority) return as a direct slice, and only
|
|
2260
|
+
* escaped ones pay for {@link unescapeString}.
|
|
2225
2261
|
*/
|
|
2226
2262
|
readQuotedString() {
|
|
2227
2263
|
const input = this.input;
|
|
@@ -2270,7 +2306,7 @@ var Parser$1 = class {
|
|
|
2270
2306
|
return bytes;
|
|
2271
2307
|
}
|
|
2272
2308
|
/**
|
|
2273
|
-
* Parses the document root
|
|
2309
|
+
* Parses the document root, which is a dictionary or an array.
|
|
2274
2310
|
*/
|
|
2275
2311
|
parseDocument() {
|
|
2276
2312
|
const code = this.peek();
|
|
@@ -2280,7 +2316,7 @@ var Parser$1 = class {
|
|
|
2280
2316
|
this.fail(`Expected '{' or '(' at the start of the document but found '${this.input[this.pos]}'`);
|
|
2281
2317
|
}
|
|
2282
2318
|
/**
|
|
2283
|
-
* Parses a `{ key = value; ... }` dictionary
|
|
2319
|
+
* Parses a `{ key = value; ... }` dictionary whose `{` is at the current
|
|
2284
2320
|
* position. Keys may be quoted or unquoted, and every entry requires the
|
|
2285
2321
|
* `=` and terminating `;`.
|
|
2286
2322
|
*/
|
|
@@ -2314,9 +2350,9 @@ var Parser$1 = class {
|
|
|
2314
2350
|
}
|
|
2315
2351
|
}
|
|
2316
2352
|
/**
|
|
2317
|
-
* Parses a `( item, item, ... )` array
|
|
2318
|
-
* position. A trailing comma before `)` is allowed
|
|
2319
|
-
* after every item.
|
|
2353
|
+
* Parses a `( item, item, ... )` array whose `(` is at the current
|
|
2354
|
+
* position. A trailing comma before `)` is allowed, because Xcode writes
|
|
2355
|
+
* one after every item.
|
|
2320
2356
|
*/
|
|
2321
2357
|
parseArray() {
|
|
2322
2358
|
const input = this.input;
|
|
@@ -2363,12 +2399,12 @@ var Parser$1 = class {
|
|
|
2363
2399
|
/**
|
|
2364
2400
|
* Decides whether an unquoted literal is a number or a string.
|
|
2365
2401
|
*
|
|
2366
|
-
*
|
|
2367
|
-
* an optional leading `-`) settles the token as a string,
|
|
2368
|
-
* 24-character identifiers that dominate project documents are
|
|
2369
|
-
* within their first few characters.
|
|
2402
|
+
* The decision is one loop with an early exit. The first character outside
|
|
2403
|
+
* `[0-9.]` (after an optional leading `-`) settles the token as a string,
|
|
2404
|
+
* so the 24-character identifiers that dominate project documents are
|
|
2405
|
+
* classified within their first few characters.
|
|
2370
2406
|
*
|
|
2371
|
-
* Numeric-looking candidates convert under a single print-back rule
|
|
2407
|
+
* Numeric-looking candidates convert under a single print-back rule. The
|
|
2372
2408
|
* literal becomes a number exactly when the number formats back to the
|
|
2373
2409
|
* identical text. Any literal the conversion would reshape stays a string,
|
|
2374
2410
|
* so a parse and build cycle cannot change a single byte of any scalar.
|
|
@@ -2406,13 +2442,34 @@ function interpretLiteral(literal) {
|
|
|
2406
2442
|
* @param text Source text of the document.
|
|
2407
2443
|
* @returns The document's root value. For real project files this is the
|
|
2408
2444
|
* root dictionary with `objects`, `rootObject`, and version fields.
|
|
2409
|
-
* @throws PbxprojParseError when the document is malformed
|
|
2445
|
+
* @throws PbxprojParseError when the document is malformed. The error
|
|
2410
2446
|
* carries the line and column of the failure.
|
|
2411
2447
|
*/
|
|
2412
2448
|
function parsePbxproj(text) {
|
|
2413
2449
|
return new Parser$1(text).parseDocument();
|
|
2414
2450
|
}
|
|
2415
2451
|
//#endregion
|
|
2452
|
+
//#region src/rename.ts
|
|
2453
|
+
/**
|
|
2454
|
+
* The stem-matching rule shared by the rename flows. The project model
|
|
2455
|
+
* renames product file references and host paths with it, and the scheme
|
|
2456
|
+
* model renames buildable names with it, so both sides agree on what
|
|
2457
|
+
* counts as the renamed target's file.
|
|
2458
|
+
*
|
|
2459
|
+
* @module
|
|
2460
|
+
*/
|
|
2461
|
+
/**
|
|
2462
|
+
* Renames a file name whose stem is the target name, keeping the
|
|
2463
|
+
* extension. `SampleApp` and `SampleApp.app` rename, and so does a
|
|
2464
|
+
* multi-part extension like `SampleApp.app.dSYM`. A name whose stem
|
|
2465
|
+
* merely starts with the old name, like `SampleAppTests.xctest`, is a
|
|
2466
|
+
* different target's product and returns `undefined`.
|
|
2467
|
+
*/
|
|
2468
|
+
function renameFileNameStem(fileName, oldName, newName) {
|
|
2469
|
+
if (fileName === oldName) return newName;
|
|
2470
|
+
if (fileName.startsWith(`${oldName}.`)) return newName + fileName.slice(oldName.length);
|
|
2471
|
+
}
|
|
2472
|
+
//#endregion
|
|
2416
2473
|
//#region src/md5.ts
|
|
2417
2474
|
/**
|
|
2418
2475
|
* Embedded MD5, implemented from RFC 1321.
|
|
@@ -2420,8 +2477,8 @@ function parsePbxproj(text) {
|
|
|
2420
2477
|
* Deterministic object ids hash their seed text (see `uuid.ts`), and the
|
|
2421
2478
|
* library runs in every JavaScript runtime without depending on a crypto
|
|
2422
2479
|
* module, so the digest is implemented here. MD5 is used strictly as a
|
|
2423
|
-
* stable text-to-bits mapping for identifier generation
|
|
2424
|
-
* relevant derives from it.
|
|
2480
|
+
* stable text-to-bits mapping for identifier generation, and nothing
|
|
2481
|
+
* security relevant derives from it.
|
|
2425
2482
|
*
|
|
2426
2483
|
* @module
|
|
2427
2484
|
*/
|
|
@@ -2496,7 +2553,7 @@ const SHIFTS = [
|
|
|
2496
2553
|
21
|
|
2497
2554
|
];
|
|
2498
2555
|
/**
|
|
2499
|
-
* Step constants for the 64 steps
|
|
2556
|
+
* Step constants for the 64 steps, which are the integer parts of
|
|
2500
2557
|
* `abs(sin(i + 1)) * 2^32`, as RFC 1321 section 3.4 tabulates them. The
|
|
2501
2558
|
* values are fixed by the specification rather than derived through
|
|
2502
2559
|
* `Math.sin` at load, because the digest feeds deterministic identifiers
|
|
@@ -2570,9 +2627,9 @@ const SINES = new Uint32Array([
|
|
|
2570
2627
|
3951481745
|
|
2571
2628
|
]);
|
|
2572
2629
|
/**
|
|
2573
|
-
* Encodes text as UTF-8 bytes with RFC 1321 padding applied
|
|
2574
|
-
* terminator, zero fill to 56 bytes mod 64, then the bit length as
|
|
2575
|
-
* little-endian 64-bit integer.
|
|
2630
|
+
* Encodes text as UTF-8 bytes with RFC 1321 padding applied, meaning a
|
|
2631
|
+
* `0x80` terminator, zero fill to 56 bytes mod 64, then the bit length as
|
|
2632
|
+
* a little-endian 64-bit integer.
|
|
2576
2633
|
*/
|
|
2577
2634
|
function paddedUtf8(text) {
|
|
2578
2635
|
const bytes = new TextEncoder().encode(text);
|
|
@@ -2590,8 +2647,8 @@ function paddedUtf8(text) {
|
|
|
2590
2647
|
/**
|
|
2591
2648
|
* Adds two 32-bit values with wraparound, keeping intermediates inside the
|
|
2592
2649
|
* 32-bit range JavaScript bitwise operators preserve. `Math.trunc` is not
|
|
2593
|
-
* a substitute here
|
|
2594
|
-
* wrap the algorithm requires.
|
|
2650
|
+
* a substitute here, because the bitwise coercion is what performs the
|
|
2651
|
+
* modular wrap the algorithm requires.
|
|
2595
2652
|
*/
|
|
2596
2653
|
function add32(a, b) {
|
|
2597
2654
|
return a + b | 0;
|
|
@@ -2663,7 +2720,7 @@ function md5Hex(text) {
|
|
|
2663
2720
|
* Deterministic object identifiers for generated pbxproj objects.
|
|
2664
2721
|
*
|
|
2665
2722
|
* Xcode identifies every object with 24 hexadecimal characters. Generated
|
|
2666
|
-
* ids here are deterministic
|
|
2723
|
+
* ids here are deterministic. The same seed always produces the same id, so
|
|
2667
2724
|
* programmatic edits are reproducible and diffs stay minimal across runs.
|
|
2668
2725
|
* The format is `XX` + the first 20 characters of `md5(seed)` + `XX`, which
|
|
2669
2726
|
* is a valid identifier that remains recognizable as generated.
|
|
@@ -2880,16 +2937,23 @@ function configurationsOf(project, configurationListId) {
|
|
|
2880
2937
|
return configurations;
|
|
2881
2938
|
}
|
|
2882
2939
|
/**
|
|
2883
|
-
* The
|
|
2884
|
-
*
|
|
2885
|
-
* configuration. Returns `undefined` when the list has no configurations
|
|
2886
|
-
* or the default carries no settings dictionary.
|
|
2940
|
+
* The view of a configuration list's default configuration, which is the
|
|
2941
|
+
* one named by `defaultConfigurationName`, falling back to the first
|
|
2942
|
+
* configuration. Returns `undefined` when the list has no configurations.
|
|
2887
2943
|
*/
|
|
2888
|
-
function
|
|
2944
|
+
function defaultConfigurationOf(project, configurationListId) {
|
|
2889
2945
|
const list = asDictionary(project.propertiesOfOptional(configurationListId));
|
|
2890
2946
|
const configurations = configurationsOf(project, configurationListId);
|
|
2891
2947
|
const defaultName = asString(list?.["defaultConfigurationName"]);
|
|
2892
|
-
return
|
|
2948
|
+
return configurations.find((configuration) => configuration.getString("name") === defaultName) ?? configurations[0];
|
|
2949
|
+
}
|
|
2950
|
+
/**
|
|
2951
|
+
* The settings dictionary of a configuration list's default configuration
|
|
2952
|
+
* (see {@link defaultConfigurationOf}). Returns `undefined` when the list
|
|
2953
|
+
* has no configurations or the default carries no settings dictionary.
|
|
2954
|
+
*/
|
|
2955
|
+
function defaultConfigurationSettingsOf(project, configurationListId) {
|
|
2956
|
+
return asDictionary(defaultConfigurationOf(project, configurationListId)?.properties["buildSettings"]);
|
|
2893
2957
|
}
|
|
2894
2958
|
//#endregion
|
|
2895
2959
|
//#region src/model/target.ts
|
|
@@ -2899,7 +2963,7 @@ function defaultConfigurationSettingsOf(project, configurationListId) {
|
|
|
2899
2963
|
* extends it with products, embedding, synchronized folders, Swift
|
|
2900
2964
|
* packages, and system frameworks.
|
|
2901
2965
|
*
|
|
2902
|
-
* Reads are deliberately soft
|
|
2966
|
+
* Reads are deliberately soft. User-generated projects can be malformed,
|
|
2903
2967
|
* so lookups return `undefined` instead of throwing wherever a document
|
|
2904
2968
|
* could legally or illegally omit something. Mutations create any missing
|
|
2905
2969
|
* structure they need.
|
|
@@ -2950,13 +3014,24 @@ var Target = class extends XcodeObject {
|
|
|
2950
3014
|
* generated app templates set values like `SDKROOT` only at the project
|
|
2951
3015
|
* level.
|
|
2952
3016
|
*
|
|
2953
|
-
*
|
|
2954
|
-
*
|
|
3017
|
+
* Configurations based on `.xcconfig` files take part once the files
|
|
3018
|
+
* are registered through {@link XcodeProject.registerXcconfig}, in
|
|
3019
|
+
* Xcode's order of target settings, then the target's xcconfig, then
|
|
3020
|
+
* project settings, then the project's xcconfig.
|
|
3021
|
+
*
|
|
3022
|
+
* Only string values are returned, so a list- or number-valued setting
|
|
3023
|
+
* reads as `undefined`.
|
|
2955
3024
|
*/
|
|
2956
3025
|
getBuildSetting(key) {
|
|
2957
|
-
const
|
|
3026
|
+
const targetConfiguration = defaultConfigurationOf(this.project, this.getString("buildConfigurationList"));
|
|
3027
|
+
const targetSettings = asDictionary(targetConfiguration?.properties["buildSettings"]);
|
|
2958
3028
|
if (targetSettings != null && key in targetSettings) return asString(targetSettings[key]);
|
|
2959
|
-
|
|
3029
|
+
const targetXcconfig = targetConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(targetConfiguration);
|
|
3030
|
+
if (targetXcconfig != null && key in targetXcconfig) return targetXcconfig[key];
|
|
3031
|
+
const projectConfiguration = defaultConfigurationOf(this.project, this.project.rootProject.getString("buildConfigurationList"));
|
|
3032
|
+
const projectSettings = asDictionary(projectConfiguration?.properties["buildSettings"]);
|
|
3033
|
+
if (projectSettings != null && key in projectSettings) return asString(projectSettings[key]);
|
|
3034
|
+
return (projectConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(projectConfiguration))?.[key];
|
|
2960
3035
|
}
|
|
2961
3036
|
/**
|
|
2962
3037
|
* Writes a build setting on every configuration of the target, so Debug
|
|
@@ -3320,7 +3395,7 @@ var LegacyTarget = class extends Target {
|
|
|
3320
3395
|
* `project.pbxproj`.
|
|
3321
3396
|
*
|
|
3322
3397
|
* The model is a set of lightweight views over the plain parsed document.
|
|
3323
|
-
* All state lives in the document itself
|
|
3398
|
+
* All state lives in the document itself. Views hold only an id and a
|
|
3324
3399
|
* project reference, so model mutations and direct dictionary writes
|
|
3325
3400
|
* compose freely and {@link XcodeProject.build} always serializes the
|
|
3326
3401
|
* current state. New objects receive deterministic identifiers (see
|
|
@@ -3350,7 +3425,7 @@ var RootProject = class extends XcodeObject {
|
|
|
3350
3425
|
}
|
|
3351
3426
|
/**
|
|
3352
3427
|
* The settings dictionary of the project-level default configuration.
|
|
3353
|
-
* Targets inherit from these settings
|
|
3428
|
+
* Targets inherit from these settings, as described on
|
|
3354
3429
|
* {@link NativeTarget.getBuildSetting}.
|
|
3355
3430
|
*/
|
|
3356
3431
|
defaultConfigurationSettings() {
|
|
@@ -3400,6 +3475,12 @@ var XcodeProject = class XcodeProject {
|
|
|
3400
3475
|
* access, so views of the same object compare with `===`.
|
|
3401
3476
|
*/
|
|
3402
3477
|
views = /* @__PURE__ */ new Map();
|
|
3478
|
+
/**
|
|
3479
|
+
* Flattened settings of registered `.xcconfig` files, keyed by the id
|
|
3480
|
+
* of the file reference configurations name in
|
|
3481
|
+
* `baseConfigurationReference`.
|
|
3482
|
+
*/
|
|
3483
|
+
xcconfigSettings = /* @__PURE__ */ new Map();
|
|
3403
3484
|
constructor(document) {
|
|
3404
3485
|
const objects = asDictionary(document["objects"]);
|
|
3405
3486
|
if (objects == null) throw new XcodeModelError("The document has no objects dictionary");
|
|
@@ -3419,7 +3500,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3419
3500
|
}
|
|
3420
3501
|
/**
|
|
3421
3502
|
* Wraps an already parsed document in a model. The document is used in
|
|
3422
|
-
* place
|
|
3503
|
+
* place rather than copied, so model mutations write into it.
|
|
3423
3504
|
*/
|
|
3424
3505
|
static fromDocument(document) {
|
|
3425
3506
|
return new XcodeProject(document);
|
|
@@ -3434,7 +3515,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3434
3515
|
/**
|
|
3435
3516
|
* The raw properties dictionary of an object.
|
|
3436
3517
|
*
|
|
3437
|
-
* @throws XcodeModelError when no object with the id exists
|
|
3518
|
+
* @throws XcodeModelError when no object with the id exists. Views use
|
|
3438
3519
|
* this accessor, so a view of a deleted object fails loudly instead of
|
|
3439
3520
|
* resurrecting the entry.
|
|
3440
3521
|
*/
|
|
@@ -3477,8 +3558,8 @@ var XcodeProject = class XcodeProject {
|
|
|
3477
3558
|
}
|
|
3478
3559
|
/**
|
|
3479
3560
|
* Generates a deterministic 24-character id from a seed, avoiding every
|
|
3480
|
-
* id the document currently contains. The id is not reserved
|
|
3481
|
-
* object with it (see {@link add})
|
|
3561
|
+
* id the document currently contains. The id is not reserved, and only
|
|
3562
|
+
* adding an object with it (see {@link add}) claims it.
|
|
3482
3563
|
*/
|
|
3483
3564
|
generateId(seed) {
|
|
3484
3565
|
return generateObjectId(seed, new Set(Object.keys(this.objectsDictionary)));
|
|
@@ -3508,6 +3589,27 @@ var XcodeProject = class XcodeProject {
|
|
|
3508
3589
|
return view;
|
|
3509
3590
|
}
|
|
3510
3591
|
/**
|
|
3592
|
+
* Registers the contents of a `.xcconfig` file so build-setting reads
|
|
3593
|
+
* can layer it below the configurations that are based on it. The
|
|
3594
|
+
* library never touches the filesystem, so the caller loads the file
|
|
3595
|
+
* and hands it over together with the file reference that
|
|
3596
|
+
* configurations name in `baseConfigurationReference`. Included files
|
|
3597
|
+
* take part through {@link XcconfigSettingsOptions.resolveInclude};
|
|
3598
|
+
* the settings are flattened once, at registration.
|
|
3599
|
+
*/
|
|
3600
|
+
registerXcconfig(reference, config, options = {}) {
|
|
3601
|
+
this.xcconfigSettings.set(reference.id, config.settings(options));
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* The flattened settings registered for the `.xcconfig` file a
|
|
3605
|
+
* configuration is based on, or `undefined` when the configuration
|
|
3606
|
+
* names none or the file was not registered.
|
|
3607
|
+
*/
|
|
3608
|
+
xcconfigSettingsOf(configuration) {
|
|
3609
|
+
const referenceId = configuration.getString("baseConfigurationReference");
|
|
3610
|
+
return referenceId == null ? void 0 : this.xcconfigSettings.get(referenceId);
|
|
3611
|
+
}
|
|
3612
|
+
/**
|
|
3511
3613
|
* The view of the document's root `PBXProject` object.
|
|
3512
3614
|
*
|
|
3513
3615
|
* @throws XcodeModelError when `rootObject` is missing or dangling,
|
|
@@ -3564,7 +3666,8 @@ var XcodeProject = class XcodeProject {
|
|
|
3564
3666
|
* and Resources build phases, and registers it on the project.
|
|
3565
3667
|
*
|
|
3566
3668
|
* @throws XcodeModelError for product types the model cannot create a
|
|
3567
|
-
* product reference for
|
|
3669
|
+
* product reference for, listed on
|
|
3670
|
+
* {@link AddNativeTargetOptions.productType}.
|
|
3568
3671
|
*/
|
|
3569
3672
|
addNativeTarget(options) {
|
|
3570
3673
|
const productInfo = PRODUCT_FILE_INFO[options.productType];
|
|
@@ -3610,7 +3713,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3610
3713
|
/**
|
|
3611
3714
|
* Adds a remote Swift package reference and registers it on the project.
|
|
3612
3715
|
* When the project already references the repository, the existing
|
|
3613
|
-
* reference is returned unchanged, requirement included
|
|
3716
|
+
* reference is returned unchanged, requirement included. Adjust an
|
|
3614
3717
|
* existing requirement through the reference's properties. Link products
|
|
3615
3718
|
* to targets with {@link NativeTarget.addSwiftPackageProduct}.
|
|
3616
3719
|
*
|
|
@@ -3685,7 +3788,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3685
3788
|
* list containing it, or a nested dictionary carrying it as a key or
|
|
3686
3789
|
* string value.
|
|
3687
3790
|
*
|
|
3688
|
-
* The scan is linear over the document
|
|
3791
|
+
* The scan is linear over the document. Removal flows call it once per
|
|
3689
3792
|
* removed object, which keeps teardown proportional to what is actually
|
|
3690
3793
|
* removed.
|
|
3691
3794
|
*/
|
|
@@ -3701,7 +3804,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3701
3804
|
* (such as `TargetAttributes`) drop its entry.
|
|
3702
3805
|
*
|
|
3703
3806
|
* Removing an id the document does not contain is a no-op. This is the
|
|
3704
|
-
* low-level removal
|
|
3807
|
+
* low-level removal, and {@link removeTarget} composes it into a full
|
|
3705
3808
|
* teardown.
|
|
3706
3809
|
*/
|
|
3707
3810
|
removeObject(id) {
|
|
@@ -3719,7 +3822,7 @@ var XcodeProject = class XcodeProject {
|
|
|
3719
3822
|
* dependencies on others), its membership exception sets, and
|
|
3720
3823
|
* synchronized folders no remaining target links.
|
|
3721
3824
|
*
|
|
3722
|
-
* On-disk sources are untouched
|
|
3825
|
+
* On-disk sources are untouched. The removal is document-only, like
|
|
3723
3826
|
* deleting a target in Xcode and keeping its folder.
|
|
3724
3827
|
*
|
|
3725
3828
|
* @throws XcodeModelError when the target belongs to another project,
|
|
@@ -3762,12 +3865,58 @@ var XcodeProject = class XcodeProject {
|
|
|
3762
3865
|
for (const groupId of syncGroupIds) if (!this.nativeTargets().some((remaining) => stringItems(remaining.properties["fileSystemSynchronizedGroups"]).includes(groupId))) this.removeObject(groupId);
|
|
3763
3866
|
}
|
|
3764
3867
|
/**
|
|
3868
|
+
* Renames a target and every place the document knows it by name. That
|
|
3869
|
+
* covers the target's `name` and `productName`, its product file
|
|
3870
|
+
* reference (`OldName.app` becomes `NewName.app`, whatever the
|
|
3871
|
+
* extension), the `remoteInfo` of container item proxies pointing at
|
|
3872
|
+
* the target, `TEST_TARGET_NAME` settings naming it, and the path
|
|
3873
|
+
* segments of `TEST_HOST` and `BUNDLE_LOADER` settings that name the
|
|
3874
|
+
* target or its product. A `PRODUCT_NAME` of the target's own
|
|
3875
|
+
* configurations is rewritten only when it spells the old name
|
|
3876
|
+
* literally. The usual `$(TARGET_NAME)` follows by itself.
|
|
3877
|
+
*
|
|
3878
|
+
* Scheme files live outside the pbxproj, so buildable references are
|
|
3879
|
+
* renamed separately through `Xcscheme.renameTarget`. On-disk renames
|
|
3880
|
+
* (source folders, entitlements files) and the group paths pointing at
|
|
3881
|
+
* those folders stay with the caller. Sibling targets such as
|
|
3882
|
+
* `OldNameTests` are renamed with their own calls.
|
|
3883
|
+
*
|
|
3884
|
+
* @throws XcodeModelError when the target belongs to another project.
|
|
3885
|
+
*/
|
|
3886
|
+
renameTarget(target, newName) {
|
|
3887
|
+
if (target.project !== this) throw new XcodeModelError("Cannot rename a target that belongs to another project");
|
|
3888
|
+
const oldName = target.name;
|
|
3889
|
+
if (oldName === newName) return;
|
|
3890
|
+
target.set("name", newName);
|
|
3891
|
+
if (oldName == null) return;
|
|
3892
|
+
if (target.getString("productName") === oldName) target.set("productName", newName);
|
|
3893
|
+
const product = this.get(target.getString("productReference"));
|
|
3894
|
+
if (product != null) for (const key of ["path", "name"]) {
|
|
3895
|
+
const fileName = product.getString(key);
|
|
3896
|
+
const renamed = fileName == null ? void 0 : renameFileNameStem(fileName, oldName, newName);
|
|
3897
|
+
if (renamed != null) product.set(key, renamed);
|
|
3898
|
+
}
|
|
3899
|
+
for (const configuration of target.buildConfigurations()) {
|
|
3900
|
+
const settings = configuration.buildSettings;
|
|
3901
|
+
if (settings?.PRODUCT_NAME === oldName) settings.PRODUCT_NAME = newName;
|
|
3902
|
+
}
|
|
3903
|
+
for (const [, view] of this.objects()) {
|
|
3904
|
+
if (ContainerItemProxy.is(view) && view.getString("remoteGlobalIDString") === target.id && view.remoteInfo === oldName) view.set("remoteInfo", newName);
|
|
3905
|
+
if (BuildConfiguration.is(view)) {
|
|
3906
|
+
const settings = view.buildSettings;
|
|
3907
|
+
if (settings == null) continue;
|
|
3908
|
+
if (settings.TEST_TARGET_NAME === oldName) settings.TEST_TARGET_NAME = newName;
|
|
3909
|
+
renameHostPathSettings(settings, oldName, newName);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
/**
|
|
3765
3914
|
* Finds a file reference by its project-relative path, resolving each
|
|
3766
3915
|
* reference's location through the group tree from the main group
|
|
3767
3916
|
* (nested group `path` components join with `/`).
|
|
3768
3917
|
*
|
|
3769
3918
|
* Members of file-system-synchronized folders are not listed in the
|
|
3770
|
-
* document and therefore cannot be found
|
|
3919
|
+
* document and therefore cannot be found. Check
|
|
3771
3920
|
* {@link NativeTarget.syncGroupPaths} for folder-level containment
|
|
3772
3921
|
* instead.
|
|
3773
3922
|
*/
|
|
@@ -3854,6 +4003,31 @@ function joinPath(prefix, segment) {
|
|
|
3854
4003
|
return prefix === "" ? segment : `${prefix}/${segment}`;
|
|
3855
4004
|
}
|
|
3856
4005
|
/**
|
|
4006
|
+
* Renames the segments of a path-valued build setting that name the
|
|
4007
|
+
* target or one of its products. Settings like `TEST_HOST` embed the
|
|
4008
|
+
* product path as
|
|
4009
|
+
* `$(BUILT_PRODUCTS_DIR)/SampleApp.app/.../SampleApp`, so each segment
|
|
4010
|
+
* is matched whole against the target name. Substring occurrences inside
|
|
4011
|
+
* unrelated segments stay untouched.
|
|
4012
|
+
*/
|
|
4013
|
+
function renamePathSegments(value, oldName, newName) {
|
|
4014
|
+
return value.split("/").map((segment) => renameFileNameStem(segment, oldName, newName) ?? segment).join("/");
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* Renames the target name inside the host-path settings of one
|
|
4018
|
+
* configuration. `TEST_HOST` and `BUNDLE_LOADER` carry the hosting
|
|
4019
|
+
* product's path, so their segments rename the way the product file
|
|
4020
|
+
* reference does.
|
|
4021
|
+
*/
|
|
4022
|
+
function renameHostPathSettings(settings, oldName, newName) {
|
|
4023
|
+
for (const key of ["TEST_HOST", "BUNDLE_LOADER"]) {
|
|
4024
|
+
const value = asString(settings[key]);
|
|
4025
|
+
if (value == null) continue;
|
|
4026
|
+
const rewritten = renamePathSegments(value, oldName, newName);
|
|
4027
|
+
if (rewritten !== value) settings[key] = rewritten;
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
/**
|
|
3857
4031
|
* Whether a value references the id anywhere in its structure. A
|
|
3858
4032
|
* reference is a string equal to it, an array containing it at any depth,
|
|
3859
4033
|
* or a dictionary carrying it as a key or somewhere in its values.
|
|
@@ -3899,7 +4073,7 @@ function stripValue(value, id) {
|
|
|
3899
4073
|
return value;
|
|
3900
4074
|
}
|
|
3901
4075
|
/**
|
|
3902
|
-
* Strips every reference to the id from an object's properties
|
|
4076
|
+
* Strips every reference to the id from an object's properties. See
|
|
3903
4077
|
* {@link stripValue} for the shapes handled. String properties naming the
|
|
3904
4078
|
* id are deleted rather than left empty.
|
|
3905
4079
|
*/
|
|
@@ -4451,6 +4625,46 @@ var Xcscheme = class Xcscheme {
|
|
|
4451
4625
|
buildableReferences() {
|
|
4452
4626
|
return this.elements("BuildableReference").map((element) => new BuildableReference(element));
|
|
4453
4627
|
}
|
|
4628
|
+
/**
|
|
4629
|
+
* Renames every buildable reference pointing at a target. The blueprint
|
|
4630
|
+
* name is matched whole, and the buildable name is matched by its stem,
|
|
4631
|
+
* so `OldApp.app` becomes `NewApp.app` while `OldAppTests.xctest`, a
|
|
4632
|
+
* different target's product, stays untouched. This is the scheme-file
|
|
4633
|
+
* side of `XcodeProject.renameTarget`. Returns whether anything
|
|
4634
|
+
* changed, so callers can skip rewriting untouched files.
|
|
4635
|
+
*/
|
|
4636
|
+
renameTarget(oldName, newName) {
|
|
4637
|
+
if (oldName === newName) return false;
|
|
4638
|
+
let changed = false;
|
|
4639
|
+
for (const reference of this.buildableReferences()) {
|
|
4640
|
+
if (reference.blueprintName === oldName) {
|
|
4641
|
+
reference.blueprintName = newName;
|
|
4642
|
+
changed = true;
|
|
4643
|
+
}
|
|
4644
|
+
const buildableName = reference.buildableName;
|
|
4645
|
+
const renamed = buildableName == null ? void 0 : renameFileNameStem(buildableName, oldName, newName);
|
|
4646
|
+
if (renamed != null) {
|
|
4647
|
+
reference.buildableName = renamed;
|
|
4648
|
+
changed = true;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
return changed;
|
|
4652
|
+
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Rewrites every buildable reference's container after the
|
|
4655
|
+
* `.xcodeproj` directory itself is renamed, so `container:Old.xcodeproj`
|
|
4656
|
+
* becomes `container:New.xcodeproj`. The project names are matched
|
|
4657
|
+
* exactly. Returns whether anything changed.
|
|
4658
|
+
*/
|
|
4659
|
+
renameContainer(oldProjectName, newProjectName) {
|
|
4660
|
+
if (oldProjectName === newProjectName) return false;
|
|
4661
|
+
let changed = false;
|
|
4662
|
+
for (const reference of this.buildableReferences()) if (reference.referencedContainer === `container:${oldProjectName}.xcodeproj`) {
|
|
4663
|
+
reference.referencedContainer = `container:${newProjectName}.xcodeproj`;
|
|
4664
|
+
changed = true;
|
|
4665
|
+
}
|
|
4666
|
+
return changed;
|
|
4667
|
+
}
|
|
4454
4668
|
};
|
|
4455
4669
|
/**
|
|
4456
4670
|
* Creates the scheme Xcode writes for an application target. The
|
|
@@ -4557,4 +4771,328 @@ function createXcscheme(options) {
|
|
|
4557
4771
|
};
|
|
4558
4772
|
}
|
|
4559
4773
|
//#endregion
|
|
4560
|
-
|
|
4774
|
+
//#region src/xcconfig/build.ts
|
|
4775
|
+
/**
|
|
4776
|
+
* Serializes a document back to `.xcconfig` text.
|
|
4777
|
+
*/
|
|
4778
|
+
function buildXcconfig(document) {
|
|
4779
|
+
let text = "";
|
|
4780
|
+
for (const statement of document.statements) text += statement.raw + statement.eol;
|
|
4781
|
+
return text;
|
|
4782
|
+
}
|
|
4783
|
+
//#endregion
|
|
4784
|
+
//#region src/xcconfig/parse.ts
|
|
4785
|
+
/**
|
|
4786
|
+
* Parser for Xcode build configuration files (`.xcconfig`).
|
|
4787
|
+
*
|
|
4788
|
+
* The grammar is line based. A line is blank, a `//` comment, an
|
|
4789
|
+
* `#include "path"` directive, or a `KEY[conditions] = value` assignment.
|
|
4790
|
+
* `//` starts a comment anywhere on a line, including inside values, which
|
|
4791
|
+
* matches Xcode's reading of the format. Every parsed statement keeps its
|
|
4792
|
+
* exact source text, so an untouched document rebuilds byte for byte.
|
|
4793
|
+
*
|
|
4794
|
+
* @module
|
|
4795
|
+
*/
|
|
4796
|
+
/**
|
|
4797
|
+
* Matches `#include "path"` and `#include? "path"`, with an optional
|
|
4798
|
+
* trailing comment.
|
|
4799
|
+
*/
|
|
4800
|
+
const INCLUDE_PATTERN = /^#include(\?)?\s*"([^"]*)"\s*(?:\/\/.*)?$/u;
|
|
4801
|
+
/**
|
|
4802
|
+
* Matches the head of an assignment, which is the leading whitespace, the
|
|
4803
|
+
* setting name, and the raw conditions block up to the equals sign.
|
|
4804
|
+
*/
|
|
4805
|
+
const ASSIGNMENT_HEAD_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*((?:\[[^\]]*\])*)\s*=/u;
|
|
4806
|
+
/**
|
|
4807
|
+
* Matches one `[name=value]` condition group inside the conditions block.
|
|
4808
|
+
*/
|
|
4809
|
+
const CONDITION_PATTERN = /\[([^=\]]+)=([^\]]*)\]/gu;
|
|
4810
|
+
/**
|
|
4811
|
+
* Strips the trailing comment, surrounding whitespace, and one trailing
|
|
4812
|
+
* semicolon from an assignment's right-hand side. Xcode tolerates the
|
|
4813
|
+
* semicolon as a leftover from property-list habits and ignores it.
|
|
4814
|
+
*/
|
|
4815
|
+
function cleanValue(rightHandSide) {
|
|
4816
|
+
const commentStart = rightHandSide.indexOf("//");
|
|
4817
|
+
const trimmed = (commentStart === -1 ? rightHandSide : rightHandSide.slice(0, commentStart)).trim();
|
|
4818
|
+
return trimmed.endsWith(";") ? trimmed.slice(0, -1).trimEnd() : trimmed;
|
|
4819
|
+
}
|
|
4820
|
+
/**
|
|
4821
|
+
* Parses the conditions block of an assignment, for example
|
|
4822
|
+
* `[sdk=iphoneos*][arch=arm64]`.
|
|
4823
|
+
*
|
|
4824
|
+
* @throws XcconfigParseError when the block has content that is not a
|
|
4825
|
+
* well-formed `[name=value]` sequence.
|
|
4826
|
+
*/
|
|
4827
|
+
function parseConditions(block, source, blockOffset) {
|
|
4828
|
+
const conditions = [];
|
|
4829
|
+
let consumed = 0;
|
|
4830
|
+
for (const match of block.matchAll(CONDITION_PATTERN)) {
|
|
4831
|
+
if (match.index !== consumed) throw new XcconfigParseError("Malformed setting condition", source, blockOffset + consumed);
|
|
4832
|
+
conditions.push({
|
|
4833
|
+
name: match[1].trim(),
|
|
4834
|
+
value: match[2].trim()
|
|
4835
|
+
});
|
|
4836
|
+
consumed = match.index + match[0].length;
|
|
4837
|
+
}
|
|
4838
|
+
if (consumed !== block.length) throw new XcconfigParseError("Malformed setting condition", source, blockOffset + consumed);
|
|
4839
|
+
return conditions;
|
|
4840
|
+
}
|
|
4841
|
+
/**
|
|
4842
|
+
* Parses one line into a statement.
|
|
4843
|
+
*
|
|
4844
|
+
* @param content The line without its terminator.
|
|
4845
|
+
* @param source The full source text, for error positions.
|
|
4846
|
+
* @param lineOffset Offset of the line inside `source`.
|
|
4847
|
+
*/
|
|
4848
|
+
function parseLine(content, eol, source, lineOffset) {
|
|
4849
|
+
const trimmed = content.trim();
|
|
4850
|
+
if (trimmed === "") return {
|
|
4851
|
+
kind: "blank",
|
|
4852
|
+
raw: content,
|
|
4853
|
+
eol
|
|
4854
|
+
};
|
|
4855
|
+
if (trimmed.startsWith("//")) return {
|
|
4856
|
+
kind: "comment",
|
|
4857
|
+
text: trimmed.slice(2),
|
|
4858
|
+
raw: content,
|
|
4859
|
+
eol
|
|
4860
|
+
};
|
|
4861
|
+
if (trimmed.startsWith("#")) {
|
|
4862
|
+
const include = INCLUDE_PATTERN.exec(trimmed);
|
|
4863
|
+
if (include == null) throw new XcconfigParseError("Malformed #include directive", source, lineOffset + content.indexOf("#"));
|
|
4864
|
+
return {
|
|
4865
|
+
kind: "include",
|
|
4866
|
+
path: include[2],
|
|
4867
|
+
optional: include[1] === "?",
|
|
4868
|
+
raw: content,
|
|
4869
|
+
eol
|
|
4870
|
+
};
|
|
4871
|
+
}
|
|
4872
|
+
const head = ASSIGNMENT_HEAD_PATTERN.exec(content);
|
|
4873
|
+
if (head == null) throw new XcconfigParseError("Expected a setting assignment, an #include directive, or a // comment", source, lineOffset + (content.length - content.trimStart().length));
|
|
4874
|
+
const conditionsBlock = head[2];
|
|
4875
|
+
const conditionsOffset = lineOffset + head[0].indexOf(conditionsBlock, head[1].length);
|
|
4876
|
+
const conditions = conditionsBlock === "" ? [] : parseConditions(conditionsBlock, source, conditionsOffset);
|
|
4877
|
+
return {
|
|
4878
|
+
kind: "assignment",
|
|
4879
|
+
key: head[1],
|
|
4880
|
+
conditions,
|
|
4881
|
+
value: cleanValue(content.slice(head[0].length)),
|
|
4882
|
+
raw: content,
|
|
4883
|
+
eol
|
|
4884
|
+
};
|
|
4885
|
+
}
|
|
4886
|
+
/**
|
|
4887
|
+
* Parses `.xcconfig` text into its document form.
|
|
4888
|
+
*
|
|
4889
|
+
* @throws XcconfigParseError when a line is not a blank, a comment, an
|
|
4890
|
+
* include, or a well-formed assignment. The error carries the line and
|
|
4891
|
+
* column of the failure.
|
|
4892
|
+
*/
|
|
4893
|
+
function parseXcconfig(source) {
|
|
4894
|
+
if (source === "") return { statements: [] };
|
|
4895
|
+
const statements = [];
|
|
4896
|
+
let offset = 0;
|
|
4897
|
+
while (offset < source.length) {
|
|
4898
|
+
const lineFeed = source.indexOf("\n", offset);
|
|
4899
|
+
const end = lineFeed === -1 ? source.length : lineFeed;
|
|
4900
|
+
const hasCarriageReturn = end > offset && source.charCodeAt(end - 1) === 13;
|
|
4901
|
+
const content = source.slice(offset, hasCarriageReturn ? end - 1 : end);
|
|
4902
|
+
const eol = lineFeed === -1 ? "" : hasCarriageReturn ? "\r\n" : "\n";
|
|
4903
|
+
statements.push(parseLine(content, eol, source, offset));
|
|
4904
|
+
if (lineFeed === -1) break;
|
|
4905
|
+
offset = lineFeed + 1;
|
|
4906
|
+
}
|
|
4907
|
+
return { statements };
|
|
4908
|
+
}
|
|
4909
|
+
//#endregion
|
|
4910
|
+
//#region src/xcconfig/model.ts
|
|
4911
|
+
/**
|
|
4912
|
+
* Object model for Xcode build configuration files (`.xcconfig`).
|
|
4913
|
+
*
|
|
4914
|
+
* {@link Xcconfig} wraps a parsed document the way `XcodeProject` wraps a
|
|
4915
|
+
* pbxproj. The document stays the single source of truth, reads and
|
|
4916
|
+
* writes go through it, and {@link Xcconfig.build} emits it back with
|
|
4917
|
+
* untouched lines preserved byte for byte.
|
|
4918
|
+
*
|
|
4919
|
+
* @module
|
|
4920
|
+
*/
|
|
4921
|
+
/**
|
|
4922
|
+
* Whether a condition value matches a context value. A bare `*` matches
|
|
4923
|
+
* anything, a trailing `*` matches by prefix, and anything else matches
|
|
4924
|
+
* exactly.
|
|
4925
|
+
*/
|
|
4926
|
+
function matchesCondition(conditionValue, contextValue) {
|
|
4927
|
+
if (conditionValue === "*") return true;
|
|
4928
|
+
if (conditionValue.endsWith("*")) return contextValue.startsWith(conditionValue.slice(0, -1));
|
|
4929
|
+
return conditionValue === contextValue;
|
|
4930
|
+
}
|
|
4931
|
+
/**
|
|
4932
|
+
* Splices a prior value into `$(inherited)` and `${inherited}`
|
|
4933
|
+
* references. With no prior value the references stay literal, because
|
|
4934
|
+
* they then refer to layers below the file chain, which resolve later.
|
|
4935
|
+
*/
|
|
4936
|
+
function spliceInherited(value, prior) {
|
|
4937
|
+
if (prior == null) return value;
|
|
4938
|
+
return value.replaceAll("$(inherited)", prior).replaceAll("${inherited}", prior);
|
|
4939
|
+
}
|
|
4940
|
+
/**
|
|
4941
|
+
* A build configuration file with typed, mutable access to its settings.
|
|
4942
|
+
*
|
|
4943
|
+
* ```ts
|
|
4944
|
+
* const config = Xcconfig.parse(text);
|
|
4945
|
+
* config.get("PRODUCT_BUNDLE_IDENTIFIER");
|
|
4946
|
+
* config.set("MARKETING_VERSION", "1.2.0");
|
|
4947
|
+
* const updated = config.build();
|
|
4948
|
+
* ```
|
|
4949
|
+
*/
|
|
4950
|
+
var Xcconfig = class Xcconfig {
|
|
4951
|
+
/** The parsed document this model wraps. */
|
|
4952
|
+
document;
|
|
4953
|
+
constructor(document) {
|
|
4954
|
+
this.document = document;
|
|
4955
|
+
}
|
|
4956
|
+
/**
|
|
4957
|
+
* Parses `.xcconfig` text and wraps it in a model.
|
|
4958
|
+
*
|
|
4959
|
+
* @throws XcconfigParseError when the text is malformed.
|
|
4960
|
+
*/
|
|
4961
|
+
static parse(text) {
|
|
4962
|
+
return new Xcconfig(parseXcconfig(text));
|
|
4963
|
+
}
|
|
4964
|
+
/**
|
|
4965
|
+
* Creates an empty configuration file.
|
|
4966
|
+
*/
|
|
4967
|
+
static create() {
|
|
4968
|
+
return new Xcconfig({ statements: [] });
|
|
4969
|
+
}
|
|
4970
|
+
/**
|
|
4971
|
+
* Serializes the current document state to `.xcconfig` text.
|
|
4972
|
+
*/
|
|
4973
|
+
build() {
|
|
4974
|
+
return buildXcconfig(this.document);
|
|
4975
|
+
}
|
|
4976
|
+
/**
|
|
4977
|
+
* The names of the settings this file assigns unconditionally, in
|
|
4978
|
+
* first-assignment order. Conditional assignments such as
|
|
4979
|
+
* `KEY[sdk=iphoneos*]` are reachable through {@link assignments}.
|
|
4980
|
+
*/
|
|
4981
|
+
keys() {
|
|
4982
|
+
const keys = [];
|
|
4983
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.conditions.length === 0 && !keys.includes(statement.key)) keys.push(statement.key);
|
|
4984
|
+
return keys;
|
|
4985
|
+
}
|
|
4986
|
+
/**
|
|
4987
|
+
* Every assignment of this file, conditional ones included, in
|
|
4988
|
+
* document order. The items are the document's own nodes, not copies,
|
|
4989
|
+
* so treat them as read-only views. Writes belong on {@link set},
|
|
4990
|
+
* which keeps a statement's value and its source text in step.
|
|
4991
|
+
*/
|
|
4992
|
+
assignments() {
|
|
4993
|
+
return this.document.statements.filter((statement) => statement.kind === "assignment");
|
|
4994
|
+
}
|
|
4995
|
+
/**
|
|
4996
|
+
* The `#include` directives of this file, in document order. The items
|
|
4997
|
+
* are the document's own nodes, not copies, so treat them as read-only
|
|
4998
|
+
* views.
|
|
4999
|
+
*/
|
|
5000
|
+
includes() {
|
|
5001
|
+
return this.document.statements.filter((statement) => statement.kind === "include");
|
|
5002
|
+
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Reads the value of a setting from this file alone. When the key is
|
|
5005
|
+
* assigned more than once the last assignment wins, matching how Xcode
|
|
5006
|
+
* reads the file top to bottom. Conditional assignments are ignored.
|
|
5007
|
+
*/
|
|
5008
|
+
get(key) {
|
|
5009
|
+
let value;
|
|
5010
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.key === key && statement.conditions.length === 0) value = statement.value;
|
|
5011
|
+
return value;
|
|
5012
|
+
}
|
|
5013
|
+
/**
|
|
5014
|
+
* Writes a setting. The last unconditional assignment of the key is
|
|
5015
|
+
* rewritten in place as a canonical `KEY = value` line, replacing any
|
|
5016
|
+
* trailing comment the line carried. A key the file does not assign
|
|
5017
|
+
* yet is appended at the end, following the document's line-ending
|
|
5018
|
+
* convention.
|
|
5019
|
+
*/
|
|
5020
|
+
set(key, value) {
|
|
5021
|
+
let target;
|
|
5022
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.key === key && statement.conditions.length === 0) target = statement;
|
|
5023
|
+
if (target != null) {
|
|
5024
|
+
target.value = value;
|
|
5025
|
+
target.raw = `${key} = ${value}`;
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
5028
|
+
const eol = this.document.statements.find((statement) => statement.eol !== "")?.eol ?? "\n";
|
|
5029
|
+
const last = this.document.statements.at(-1);
|
|
5030
|
+
if (last != null && last.eol === "") last.eol = eol;
|
|
5031
|
+
this.document.statements.push({
|
|
5032
|
+
kind: "assignment",
|
|
5033
|
+
key,
|
|
5034
|
+
conditions: [],
|
|
5035
|
+
value,
|
|
5036
|
+
raw: `${key} = ${value}`,
|
|
5037
|
+
eol
|
|
5038
|
+
});
|
|
5039
|
+
}
|
|
5040
|
+
/**
|
|
5041
|
+
* Removes every unconditional assignment of a setting.
|
|
5042
|
+
*
|
|
5043
|
+
* @returns True when at least one assignment was removed.
|
|
5044
|
+
*/
|
|
5045
|
+
remove(key) {
|
|
5046
|
+
const kept = this.document.statements.filter((statement) => statement.kind !== "assignment" || statement.key !== key || statement.conditions.length > 0);
|
|
5047
|
+
const removed = kept.length !== this.document.statements.length;
|
|
5048
|
+
this.document.statements = kept;
|
|
5049
|
+
return removed;
|
|
5050
|
+
}
|
|
5051
|
+
/**
|
|
5052
|
+
* Flattens the file into a settings dictionary the way Xcode reads it:
|
|
5053
|
+
* top to bottom with later assignments winning, and every `#include`
|
|
5054
|
+
* contributing its settings at the point of the directive, so lines
|
|
5055
|
+
* after an include override it.
|
|
5056
|
+
*
|
|
5057
|
+
* Conditional assignments apply when every condition matches
|
|
5058
|
+
* {@link XcconfigSettingsOptions.context}; without a context they are
|
|
5059
|
+
* skipped. `$(inherited)` references splice in the value accumulated
|
|
5060
|
+
* earlier in the chain, and stay literal when there is none, since
|
|
5061
|
+
* they then refer to layers below the file, which resolve later.
|
|
5062
|
+
*
|
|
5063
|
+
* Includes only take part when {@link XcconfigSettingsOptions.resolveInclude}
|
|
5064
|
+
* is provided, since the library never touches the filesystem itself.
|
|
5065
|
+
* A file included again later re-applies, exactly like pasting its text
|
|
5066
|
+
* a second time. Only re-entry while a file is still being expanded is
|
|
5067
|
+
* skipped, tracked by include path and by instance, so cyclic includes
|
|
5068
|
+
* terminate even when the resolver parses a fresh instance per call.
|
|
5069
|
+
*/
|
|
5070
|
+
settings(options = {}) {
|
|
5071
|
+
const merged = {};
|
|
5072
|
+
const pathStack = /* @__PURE__ */ new Set();
|
|
5073
|
+
const instanceStack = /* @__PURE__ */ new Set();
|
|
5074
|
+
const context = options.context;
|
|
5075
|
+
const applies = (statement) => statement.conditions.every((condition) => {
|
|
5076
|
+
const contextValue = condition.name === "sdk" || condition.name === "arch" || condition.name === "config" ? context?.[condition.name] : void 0;
|
|
5077
|
+
return contextValue != null && matchesCondition(condition.value, contextValue);
|
|
5078
|
+
});
|
|
5079
|
+
const visit = (config) => {
|
|
5080
|
+
if (instanceStack.has(config)) return;
|
|
5081
|
+
instanceStack.add(config);
|
|
5082
|
+
for (const statement of config.document.statements) if (statement.kind === "include") {
|
|
5083
|
+
if (pathStack.has(statement.path)) continue;
|
|
5084
|
+
const included = options.resolveInclude?.(statement.path, statement.optional);
|
|
5085
|
+
if (included != null) {
|
|
5086
|
+
pathStack.add(statement.path);
|
|
5087
|
+
visit(included);
|
|
5088
|
+
pathStack.delete(statement.path);
|
|
5089
|
+
}
|
|
5090
|
+
} else if (statement.kind === "assignment" && applies(statement)) merged[statement.key] = spliceInherited(statement.value, merged[statement.key]);
|
|
5091
|
+
instanceStack.delete(config);
|
|
5092
|
+
};
|
|
5093
|
+
visit(this);
|
|
5094
|
+
return merged;
|
|
5095
|
+
}
|
|
5096
|
+
};
|
|
5097
|
+
//#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 };
|