rork-xcode 0.2.0 → 0.4.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 +44 -8
- package/dist/index.d.ts +307 -5
- package/dist/index.js +795 -31
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -259,6 +259,51 @@ var PbxprojParseError = class extends Error {
|
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
261
|
/**
|
|
262
|
+
* Thrown when the source text is not a well-formed scheme document (the
|
|
263
|
+
* XML dialect of `.xcscheme` files).
|
|
264
|
+
*
|
|
265
|
+
* The message always embeds the line and column of the failure, and the
|
|
266
|
+
* same information is available in structured form on {@link position}
|
|
267
|
+
* for programmatic use.
|
|
268
|
+
*/
|
|
269
|
+
var XcschemeParseError = class extends Error {
|
|
270
|
+
/** Where in the source text parsing failed. */
|
|
271
|
+
position;
|
|
272
|
+
/**
|
|
273
|
+
* @param message Failure description without location; the location is
|
|
274
|
+
* appended automatically.
|
|
275
|
+
* @param source Full source text, used to compute the position.
|
|
276
|
+
* @param offset Character offset of the failure inside `source`.
|
|
277
|
+
*/
|
|
278
|
+
constructor(message, source, offset) {
|
|
279
|
+
const position = positionAt(source, offset);
|
|
280
|
+
super(`${message} (line ${position.line}, column ${position.column})`);
|
|
281
|
+
this.name = "XcschemeParseError";
|
|
282
|
+
this.position = position;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Thrown when a scheme element cannot be written as XML.
|
|
287
|
+
*
|
|
288
|
+
* Raised for element and attribute names that are not valid XML names and
|
|
289
|
+
* for attribute values carrying control characters XML 1.0 cannot encode.
|
|
290
|
+
* The {@link path} pinpoints the offending element inside the tree.
|
|
291
|
+
*/
|
|
292
|
+
var XcschemeBuildError = class extends Error {
|
|
293
|
+
/** Path to the offending element from the root, e.g. `Scheme.BuildAction[0]`. */
|
|
294
|
+
path;
|
|
295
|
+
/**
|
|
296
|
+
* @param message Failure description without location; the element path
|
|
297
|
+
* is appended automatically.
|
|
298
|
+
* @param path Path to the offending element from the root.
|
|
299
|
+
*/
|
|
300
|
+
constructor(message, path) {
|
|
301
|
+
super(`${message} (at ${path})`);
|
|
302
|
+
this.name = "XcschemeBuildError";
|
|
303
|
+
this.path = path;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
262
307
|
* Thrown by the object model when an operation cannot proceed: the
|
|
263
308
|
* document lacks the structure the operation needs (no objects dictionary,
|
|
264
309
|
* no root project object), a view's object was removed from the document,
|
|
@@ -961,9 +1006,9 @@ function stringItems(value) {
|
|
|
961
1006
|
*
|
|
962
1007
|
* Views are lightweight, identity-mapped facades over entries of the
|
|
963
1008
|
* document's `objects` dictionary. All state lives in the parsed document
|
|
964
|
-
* itself
|
|
965
|
-
* document text produced from the model reflects every
|
|
966
|
-
* separate serialization step.
|
|
1009
|
+
* itself, and a view holds only its id and a reference back to the
|
|
1010
|
+
* project, so document text produced from the model reflects every
|
|
1011
|
+
* mutation with no separate serialization step.
|
|
967
1012
|
*
|
|
968
1013
|
* @module
|
|
969
1014
|
*/
|
|
@@ -985,7 +1030,7 @@ var XcodeObject = class {
|
|
|
985
1030
|
/** The object's 24-character identifier (its key in `objects`). */
|
|
986
1031
|
id;
|
|
987
1032
|
/**
|
|
988
|
-
* Views are created by the project's identity map
|
|
1033
|
+
* Views are created by the project's identity map. Use
|
|
989
1034
|
* {@link XcodeProject.get} or a typed query instead of constructing
|
|
990
1035
|
* views directly.
|
|
991
1036
|
*/
|
|
@@ -994,9 +1039,28 @@ var XcodeObject = class {
|
|
|
994
1039
|
this.id = id;
|
|
995
1040
|
}
|
|
996
1041
|
/**
|
|
997
|
-
*
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1042
|
+
* Whether a value is a view of this class, narrowing it when so. Views
|
|
1043
|
+
* come typed out of the factory, and this is the readable way to
|
|
1044
|
+
* discriminate them when iterating mixed objects.
|
|
1045
|
+
*
|
|
1046
|
+
* ```ts
|
|
1047
|
+
* for (const [, object] of project.objects()) {
|
|
1048
|
+
* if (NativeTarget.is(object)) {
|
|
1049
|
+
* console.log(object.name);
|
|
1050
|
+
* }
|
|
1051
|
+
* }
|
|
1052
|
+
* ```
|
|
1053
|
+
*
|
|
1054
|
+
* Subclass views match their parent class too, the way `instanceof`
|
|
1055
|
+
* does, so a `VersionGroup` is a `Group`.
|
|
1056
|
+
*/
|
|
1057
|
+
static is(value) {
|
|
1058
|
+
return value instanceof this;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* The object's raw dictionary inside the document. Mutations through
|
|
1062
|
+
* the model write here, direct writes are equally valid, and the model
|
|
1063
|
+
* adds no caching over these properties.
|
|
1000
1064
|
*
|
|
1001
1065
|
* The typed shape is asserted, not checked. It describes what a
|
|
1002
1066
|
* well-formed object of this kind carries (see `properties.ts`).
|
|
@@ -1297,6 +1361,59 @@ var VersionGroup = class extends Group {
|
|
|
1297
1361
|
}
|
|
1298
1362
|
};
|
|
1299
1363
|
/**
|
|
1364
|
+
* An `XCBuildConfiguration` holds one named settings dictionary of a
|
|
1365
|
+
* target or of the project, for example Debug or Release.
|
|
1366
|
+
*/
|
|
1367
|
+
var BuildConfiguration = class extends XcodeObject {
|
|
1368
|
+
/**
|
|
1369
|
+
* The configuration's name, when present.
|
|
1370
|
+
*/
|
|
1371
|
+
get name() {
|
|
1372
|
+
return this.getString("name");
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* The configuration's settings dictionary, typed with the keys
|
|
1376
|
+
* programmatic edits touch most, or `undefined` when the configuration
|
|
1377
|
+
* carries none. The dictionary is live. Writes through it land in the
|
|
1378
|
+
* document.
|
|
1379
|
+
*/
|
|
1380
|
+
get buildSettings() {
|
|
1381
|
+
return asDictionary(this.properties["buildSettings"]);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
/**
|
|
1385
|
+
* A `PBXFileReference` names one file on disk, from source files to the
|
|
1386
|
+
* built products themselves.
|
|
1387
|
+
*/
|
|
1388
|
+
var FileReference = class extends XcodeObject {
|
|
1389
|
+
/**
|
|
1390
|
+
* The reference's path, relative to its `sourceTree`, when present.
|
|
1391
|
+
*/
|
|
1392
|
+
get path() {
|
|
1393
|
+
return this.getString("path");
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* The reference's display name, when it carries one distinct from the
|
|
1397
|
+
* path.
|
|
1398
|
+
*/
|
|
1399
|
+
get name() {
|
|
1400
|
+
return this.getString("name");
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
/**
|
|
1404
|
+
* A `PBXContainerItemProxy` is the indirection Xcode places between a
|
|
1405
|
+
* target dependency and the target it points at.
|
|
1406
|
+
*/
|
|
1407
|
+
var ContainerItemProxy = class extends XcodeObject {
|
|
1408
|
+
/**
|
|
1409
|
+
* The display name of the object the proxy points at, when present.
|
|
1410
|
+
* For target dependencies this is the target's name.
|
|
1411
|
+
*/
|
|
1412
|
+
get remoteInfo() {
|
|
1413
|
+
return this.getString("remoteInfo");
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
/**
|
|
1300
1417
|
* A `PBXReferenceProxy` stands in for a product built by a target of
|
|
1301
1418
|
* another project referenced from this one.
|
|
1302
1419
|
*/
|
|
@@ -1595,11 +1712,11 @@ const IS_LITERAL_CHAR = (() => {
|
|
|
1595
1712
|
for (const ch of "_$/:.-") table[ch.charCodeAt(0)] = 1;
|
|
1596
1713
|
return table;
|
|
1597
1714
|
})();
|
|
1598
|
-
const CODE_TAB = 9;
|
|
1599
|
-
const CODE_LINE_FEED = 10;
|
|
1600
|
-
const CODE_CARRIAGE_RETURN = 13;
|
|
1601
|
-
const CODE_SPACE = 32;
|
|
1602
|
-
const CODE_QUOTE = 34;
|
|
1715
|
+
const CODE_TAB$1 = 9;
|
|
1716
|
+
const CODE_LINE_FEED$1 = 10;
|
|
1717
|
+
const CODE_CARRIAGE_RETURN$1 = 13;
|
|
1718
|
+
const CODE_SPACE$1 = 32;
|
|
1719
|
+
const CODE_QUOTE$1 = 34;
|
|
1603
1720
|
const CODE_SINGLE_QUOTE = 39;
|
|
1604
1721
|
const CODE_OPEN_PAREN = 40;
|
|
1605
1722
|
const CODE_CLOSE_PAREN = 41;
|
|
@@ -1607,13 +1724,13 @@ const CODE_ASTERISK = 42;
|
|
|
1607
1724
|
const CODE_COMMA = 44;
|
|
1608
1725
|
const CODE_MINUS = 45;
|
|
1609
1726
|
const CODE_DOT = 46;
|
|
1610
|
-
const CODE_SLASH = 47;
|
|
1727
|
+
const CODE_SLASH$1 = 47;
|
|
1611
1728
|
const CODE_ZERO = 48;
|
|
1612
1729
|
const CODE_NINE = 57;
|
|
1613
1730
|
const CODE_SEMICOLON = 59;
|
|
1614
|
-
const CODE_LESS_THAN = 60;
|
|
1615
|
-
const CODE_EQUALS = 61;
|
|
1616
|
-
const CODE_GREATER_THAN = 62;
|
|
1731
|
+
const CODE_LESS_THAN$1 = 60;
|
|
1732
|
+
const CODE_EQUALS$1 = 61;
|
|
1733
|
+
const CODE_GREATER_THAN$1 = 62;
|
|
1617
1734
|
const CODE_BACKSLASH = 92;
|
|
1618
1735
|
const CODE_OPEN_BRACE = 123;
|
|
1619
1736
|
const CODE_CLOSE_BRACE = 125;
|
|
@@ -1625,10 +1742,10 @@ const CODE_CLOSE_BRACE = 125;
|
|
|
1625
1742
|
*/
|
|
1626
1743
|
const IS_WHITESPACE = (() => {
|
|
1627
1744
|
const table = /* @__PURE__ */ new Uint8Array(256);
|
|
1628
|
-
table[CODE_SPACE] = 1;
|
|
1629
|
-
table[CODE_TAB] = 1;
|
|
1630
|
-
table[CODE_CARRIAGE_RETURN] = 1;
|
|
1631
|
-
table[CODE_LINE_FEED] = 1;
|
|
1745
|
+
table[CODE_SPACE$1] = 1;
|
|
1746
|
+
table[CODE_TAB$1] = 1;
|
|
1747
|
+
table[CODE_CARRIAGE_RETURN$1] = 1;
|
|
1748
|
+
table[CODE_LINE_FEED$1] = 1;
|
|
1632
1749
|
return table;
|
|
1633
1750
|
})();
|
|
1634
1751
|
/**
|
|
@@ -1644,7 +1761,7 @@ function isDigit(code) {
|
|
|
1644
1761
|
* through the `read*` and `parse*` methods; there is no separate tokenizer
|
|
1645
1762
|
* stage and no token objects.
|
|
1646
1763
|
*/
|
|
1647
|
-
var Parser = class {
|
|
1764
|
+
var Parser$1 = class {
|
|
1648
1765
|
/** Source text of the document being parsed. */
|
|
1649
1766
|
input;
|
|
1650
1767
|
/** Cursor position as a UTF-16 code unit offset into {@link input}. */
|
|
@@ -1691,7 +1808,7 @@ var Parser = class {
|
|
|
1691
1808
|
const length = input.length;
|
|
1692
1809
|
let pos = this.pos;
|
|
1693
1810
|
while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
|
|
1694
|
-
if (pos < length && input.charCodeAt(pos) === CODE_SLASH) pos = this.skipCommentedTrivia(pos);
|
|
1811
|
+
if (pos < length && input.charCodeAt(pos) === CODE_SLASH$1) pos = this.skipCommentedTrivia(pos);
|
|
1695
1812
|
this.pos = pos;
|
|
1696
1813
|
}
|
|
1697
1814
|
/**
|
|
@@ -1711,7 +1828,7 @@ var Parser = class {
|
|
|
1711
1828
|
const length = input.length;
|
|
1712
1829
|
for (;;) {
|
|
1713
1830
|
const next = input.charCodeAt(pos + 1);
|
|
1714
|
-
if (next === CODE_SLASH) {
|
|
1831
|
+
if (next === CODE_SLASH$1) {
|
|
1715
1832
|
const lineEnd = input.indexOf("\n", pos + 2);
|
|
1716
1833
|
pos = lineEnd === -1 ? length : lineEnd;
|
|
1717
1834
|
} else if (next === CODE_ASTERISK) {
|
|
@@ -1723,7 +1840,7 @@ var Parser = class {
|
|
|
1723
1840
|
pos = commentEnd + 2;
|
|
1724
1841
|
} else return pos;
|
|
1725
1842
|
while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
|
|
1726
|
-
if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH) return pos;
|
|
1843
|
+
if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH$1) return pos;
|
|
1727
1844
|
}
|
|
1728
1845
|
}
|
|
1729
1846
|
/**
|
|
@@ -1806,7 +1923,7 @@ var Parser = class {
|
|
|
1806
1923
|
const input = this.input;
|
|
1807
1924
|
const length = input.length;
|
|
1808
1925
|
const start = ++this.pos;
|
|
1809
|
-
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.pos++;
|
|
1926
|
+
while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN$1) this.pos++;
|
|
1810
1927
|
if (this.pos >= length) this.fail("Unterminated data run", start - 1);
|
|
1811
1928
|
let hex = "";
|
|
1812
1929
|
for (let i = start; i < this.pos; i++) {
|
|
@@ -1849,10 +1966,10 @@ var Parser = class {
|
|
|
1849
1966
|
return result;
|
|
1850
1967
|
}
|
|
1851
1968
|
let key;
|
|
1852
|
-
if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
|
|
1969
|
+
if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
|
|
1853
1970
|
else if (IS_LITERAL_CHAR[code] === 1) key = this.readLiteral();
|
|
1854
1971
|
else this.fail(`Expected a key but found '${input[this.pos]}'`);
|
|
1855
|
-
this.expect(CODE_EQUALS, "=");
|
|
1972
|
+
this.expect(CODE_EQUALS$1, "=");
|
|
1856
1973
|
const value = this.parseValue();
|
|
1857
1974
|
this.expect(CODE_SEMICOLON, ";");
|
|
1858
1975
|
if (key === "__proto__") Object.defineProperty(result, key, {
|
|
@@ -1905,9 +2022,9 @@ var Parser = class {
|
|
|
1905
2022
|
const code = this.input.charCodeAt(this.pos);
|
|
1906
2023
|
if (code === CODE_OPEN_BRACE) return this.parseObject();
|
|
1907
2024
|
if (code === CODE_OPEN_PAREN) return this.parseArray();
|
|
1908
|
-
if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
|
|
2025
|
+
if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
|
|
1909
2026
|
if (IS_LITERAL_CHAR[code] === 1) return interpretLiteral(this.readLiteral());
|
|
1910
|
-
if (code === CODE_LESS_THAN) return this.readData();
|
|
2027
|
+
if (code === CODE_LESS_THAN$1) return this.readData();
|
|
1911
2028
|
this.fail(`Expected a value but found '${this.input[this.pos]}'`);
|
|
1912
2029
|
}
|
|
1913
2030
|
};
|
|
@@ -1961,7 +2078,7 @@ function interpretLiteral(literal) {
|
|
|
1961
2078
|
* carries the line and column of the failure.
|
|
1962
2079
|
*/
|
|
1963
2080
|
function parsePbxproj(text) {
|
|
1964
|
-
return new Parser(text).parseDocument();
|
|
2081
|
+
return new Parser$1(text).parseDocument();
|
|
1965
2082
|
}
|
|
1966
2083
|
//#endregion
|
|
1967
2084
|
//#region src/md5.ts
|
|
@@ -3325,6 +3442,9 @@ var XcodeProject = class XcodeProject {
|
|
|
3325
3442
|
case Isa.versionGroup: return new VersionGroup(this, id);
|
|
3326
3443
|
case Isa.fileSystemSynchronizedRootGroup: return new SyncRootGroup(this, id);
|
|
3327
3444
|
case Isa.buildRule: return new BuildRule(this, id);
|
|
3445
|
+
case Isa.buildConfiguration: return new BuildConfiguration(this, id);
|
|
3446
|
+
case Isa.fileReference: return new FileReference(this, id);
|
|
3447
|
+
case Isa.containerItemProxy: return new ContainerItemProxy(this, id);
|
|
3328
3448
|
case Isa.referenceProxy: return new ReferenceProxy(this, id);
|
|
3329
3449
|
default: return isa.endsWith("BuildPhase") ? new BuildPhase(this, id) : new XcodeObject(this, id);
|
|
3330
3450
|
}
|
|
@@ -3397,4 +3517,648 @@ function stripReferences(properties, id) {
|
|
|
3397
3517
|
}
|
|
3398
3518
|
}
|
|
3399
3519
|
//#endregion
|
|
3400
|
-
|
|
3520
|
+
//#region src/scheme/types.ts
|
|
3521
|
+
/**
|
|
3522
|
+
* Whether a node is an element rather than a comment.
|
|
3523
|
+
*/
|
|
3524
|
+
function isXcschemeElement(node) {
|
|
3525
|
+
return "name" in node;
|
|
3526
|
+
}
|
|
3527
|
+
//#endregion
|
|
3528
|
+
//#region src/scheme/build.ts
|
|
3529
|
+
/**
|
|
3530
|
+
* Serializer for the `.xcscheme` XML dialect.
|
|
3531
|
+
*
|
|
3532
|
+
* The output reproduces Xcode's own layout byte for byte. Xcode writes
|
|
3533
|
+
* the UTF-8 declaration, indents with three spaces, puts each attribute
|
|
3534
|
+
* on its own line with spaces around the equals sign, glues the closing
|
|
3535
|
+
* angle bracket to the last attribute, and closes every element with an
|
|
3536
|
+
* explicit close tag. Parsing an Xcode-written scheme and building it
|
|
3537
|
+
* back therefore yields the identical file, and any other input reaches
|
|
3538
|
+
* that canonical form in one build.
|
|
3539
|
+
*
|
|
3540
|
+
* @module
|
|
3541
|
+
*/
|
|
3542
|
+
/** The declaration line Xcode writes at the top of every scheme file. */
|
|
3543
|
+
const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
3544
|
+
/** One indentation step of Xcode's scheme writer. */
|
|
3545
|
+
const INDENT = " ";
|
|
3546
|
+
/**
|
|
3547
|
+
* Matches valid XML names. A stray non-name, such as an empty string or
|
|
3548
|
+
* one carrying spaces or XML syntax, must fail rather than produce a
|
|
3549
|
+
* document no parser accepts.
|
|
3550
|
+
*/
|
|
3551
|
+
const NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/u;
|
|
3552
|
+
/**
|
|
3553
|
+
* Matches characters that cannot appear in an attribute value. XML 1.0
|
|
3554
|
+
* has no representation for control characters other than tab, line
|
|
3555
|
+
* feed, and carriage return.
|
|
3556
|
+
*/
|
|
3557
|
+
const UNENCODABLE_PATTERN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]/u;
|
|
3558
|
+
/**
|
|
3559
|
+
* Serializes a scheme document to the text of a `.xcscheme` file.
|
|
3560
|
+
*
|
|
3561
|
+
* @throws XcschemeBuildError for element or attribute names that are not
|
|
3562
|
+
* XML names and for attribute values carrying unencodable control
|
|
3563
|
+
* characters, with the path of the offending node.
|
|
3564
|
+
*/
|
|
3565
|
+
function buildXcscheme(document) {
|
|
3566
|
+
let output = XML_DECLARATION;
|
|
3567
|
+
for (const comment of document.leading) output += `<!--${comment.comment}-->\n`;
|
|
3568
|
+
output += renderElement(document.root, 0, document.root.name);
|
|
3569
|
+
for (const comment of document.trailing) output += `<!--${comment.comment}-->\n`;
|
|
3570
|
+
return output;
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Renders one element with Xcode's layout. Attributes go each on their
|
|
3574
|
+
* own line indented one step past the tag, the `>` glues to the last
|
|
3575
|
+
* attribute, children indent one step, and the close tag is always
|
|
3576
|
+
* explicit.
|
|
3577
|
+
*/
|
|
3578
|
+
function renderElement(element, depth, path) {
|
|
3579
|
+
if (!NAME_PATTERN.test(element.name)) throw new XcschemeBuildError(`Element name ${JSON.stringify(element.name)} is not a valid XML name`, path);
|
|
3580
|
+
const indent = INDENT.repeat(depth);
|
|
3581
|
+
const names = Object.keys(element.attributes);
|
|
3582
|
+
let output;
|
|
3583
|
+
if (names.length === 0) output = `${indent}<${element.name}>\n`;
|
|
3584
|
+
else {
|
|
3585
|
+
output = `${indent}<${element.name}\n`;
|
|
3586
|
+
const attributeIndent = indent + INDENT;
|
|
3587
|
+
for (let i = 0; i < names.length; i++) {
|
|
3588
|
+
const name = names[i];
|
|
3589
|
+
if (!NAME_PATTERN.test(name)) throw new XcschemeBuildError(`Attribute name ${JSON.stringify(name)} is not a valid XML name`, path);
|
|
3590
|
+
const value = escapeAttribute(element.attributes[name], path, name);
|
|
3591
|
+
const terminator = i === names.length - 1 ? ">" : "";
|
|
3592
|
+
output += `${attributeIndent}${name} = "${value}"${terminator}\n`;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
output += renderChildren(element.children, depth + 1, path);
|
|
3596
|
+
output += `${indent}</${element.name}>\n`;
|
|
3597
|
+
return output;
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Renders an element's children in document order, numbering repeated
|
|
3601
|
+
* element names in the error path so a failure points at one node.
|
|
3602
|
+
*/
|
|
3603
|
+
function renderChildren(children, depth, path) {
|
|
3604
|
+
let output = "";
|
|
3605
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3606
|
+
for (const child of children) if (isXcschemeElement(child)) {
|
|
3607
|
+
const index = seen.get(child.name) ?? 0;
|
|
3608
|
+
seen.set(child.name, index + 1);
|
|
3609
|
+
output += renderElement(child, depth, `${path}.${child.name}[${index}]`);
|
|
3610
|
+
} else output += `${INDENT.repeat(depth)}<!--${child.comment}-->\n`;
|
|
3611
|
+
return output;
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Escapes an attribute value the way Xcode's writer does. XML syntax
|
|
3615
|
+
* characters become the five named entities, and tab, line feed, and
|
|
3616
|
+
* carriage return become character references so they survive
|
|
3617
|
+
* attribute-value normalization on the next parse.
|
|
3618
|
+
*/
|
|
3619
|
+
function escapeAttribute(value, path, attributeName) {
|
|
3620
|
+
const unencodable = UNENCODABLE_PATTERN.exec(value);
|
|
3621
|
+
if (unencodable != null) throw new XcschemeBuildError(`Attribute ${attributeName} contains the control character U+${unencodable[0].charCodeAt(0).toString(16).padStart(4, "0").toUpperCase()}, which XML cannot encode`, path);
|
|
3622
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'").replaceAll(" ", "	").replaceAll("\n", " ").replaceAll("\r", " ");
|
|
3623
|
+
}
|
|
3624
|
+
//#endregion
|
|
3625
|
+
//#region src/scheme/parse.ts
|
|
3626
|
+
/**
|
|
3627
|
+
* Parser for the `.xcscheme` XML dialect.
|
|
3628
|
+
*
|
|
3629
|
+
* Scheme files are XML with a narrow shape. Elements carry attributes and
|
|
3630
|
+
* child elements, and there is no text content, no namespace, and no
|
|
3631
|
+
* DOCTYPE. The parser accepts that shape from any writer, since attribute
|
|
3632
|
+
* order, quoting style, and whitespace vary across tools. Character and
|
|
3633
|
+
* entity references in attribute values resolve to their characters, and
|
|
3634
|
+
* comments are preserved. Anything outside the shape fails loudly with a
|
|
3635
|
+
* position, in line with the pbxproj parser.
|
|
3636
|
+
*
|
|
3637
|
+
* @module
|
|
3638
|
+
*/
|
|
3639
|
+
const CODE_TAB = 9;
|
|
3640
|
+
const CODE_LINE_FEED = 10;
|
|
3641
|
+
const CODE_CARRIAGE_RETURN = 13;
|
|
3642
|
+
const CODE_SPACE = 32;
|
|
3643
|
+
const CODE_BANG = 33;
|
|
3644
|
+
const CODE_QUOTE = 34;
|
|
3645
|
+
const CODE_AMPERSAND = 38;
|
|
3646
|
+
const CODE_APOSTROPHE = 39;
|
|
3647
|
+
const CODE_HYPHEN = 45;
|
|
3648
|
+
const CODE_SLASH = 47;
|
|
3649
|
+
const CODE_LESS_THAN = 60;
|
|
3650
|
+
const CODE_EQUALS = 61;
|
|
3651
|
+
const CODE_GREATER_THAN = 62;
|
|
3652
|
+
const CODE_QUESTION = 63;
|
|
3653
|
+
const CODE_HASH = 35;
|
|
3654
|
+
const CODE_BOM = 65279;
|
|
3655
|
+
/**
|
|
3656
|
+
* Whether a code unit is XML whitespace (space, tab, line feed, or
|
|
3657
|
+
* carriage return).
|
|
3658
|
+
*/
|
|
3659
|
+
function isWhitespace(code) {
|
|
3660
|
+
return code === CODE_SPACE || code === CODE_TAB || code === CODE_LINE_FEED || code === CODE_CARRIAGE_RETURN;
|
|
3661
|
+
}
|
|
3662
|
+
/**
|
|
3663
|
+
* Whether a code unit can start an XML name. The scheme vocabulary is
|
|
3664
|
+
* ASCII, so the accepted alphabet is letters, underscore, and colon.
|
|
3665
|
+
*/
|
|
3666
|
+
function isNameStart(code) {
|
|
3667
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 58;
|
|
3668
|
+
}
|
|
3669
|
+
/**
|
|
3670
|
+
* Whether a code unit can continue an XML name.
|
|
3671
|
+
*/
|
|
3672
|
+
function isNameChar(code) {
|
|
3673
|
+
return isNameStart(code) || code >= 48 && code <= 57 || code === CODE_HYPHEN || code === 46;
|
|
3674
|
+
}
|
|
3675
|
+
/** The named character entities XML 1.0 predefines. */
|
|
3676
|
+
const NAMED_ENTITIES = {
|
|
3677
|
+
amp: "&",
|
|
3678
|
+
apos: "'",
|
|
3679
|
+
gt: ">",
|
|
3680
|
+
lt: "<",
|
|
3681
|
+
quot: "\""
|
|
3682
|
+
};
|
|
3683
|
+
/**
|
|
3684
|
+
* Parses the text of a `.xcscheme` file into its node tree.
|
|
3685
|
+
*
|
|
3686
|
+
* @throws XcschemeParseError when the text is not a well-formed scheme
|
|
3687
|
+
* document, with the line and column of the failure.
|
|
3688
|
+
*/
|
|
3689
|
+
function parseXcscheme(text) {
|
|
3690
|
+
return new Parser(text).parseDocument();
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Single-pass recursive-descent parser over the source text. One instance
|
|
3694
|
+
* parses one document and is discarded.
|
|
3695
|
+
*/
|
|
3696
|
+
var Parser = class {
|
|
3697
|
+
/** The full source text of the scheme file. */
|
|
3698
|
+
input;
|
|
3699
|
+
/** Cursor into {@link input}, in UTF-16 code units. */
|
|
3700
|
+
pos = 0;
|
|
3701
|
+
constructor(input) {
|
|
3702
|
+
this.input = input;
|
|
3703
|
+
if (input.charCodeAt(0) === CODE_BOM) this.pos = 1;
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Parses the whole document. The XML declaration is skipped, comments
|
|
3707
|
+
* around the root element are collected, and anything left after the
|
|
3708
|
+
* root fails.
|
|
3709
|
+
*/
|
|
3710
|
+
parseDocument() {
|
|
3711
|
+
this.skipWhitespace();
|
|
3712
|
+
this.skipDeclaration();
|
|
3713
|
+
const leading = [];
|
|
3714
|
+
const trailing = [];
|
|
3715
|
+
this.skipWhitespace();
|
|
3716
|
+
while (this.peekIsCommentStart()) {
|
|
3717
|
+
leading.push(this.parseComment());
|
|
3718
|
+
this.skipWhitespace();
|
|
3719
|
+
}
|
|
3720
|
+
const root = this.parseElement();
|
|
3721
|
+
this.skipWhitespace();
|
|
3722
|
+
while (this.peekIsCommentStart()) {
|
|
3723
|
+
trailing.push(this.parseComment());
|
|
3724
|
+
this.skipWhitespace();
|
|
3725
|
+
}
|
|
3726
|
+
if (this.pos < this.input.length) this.fail("Expected end of document after the root element");
|
|
3727
|
+
return {
|
|
3728
|
+
leading,
|
|
3729
|
+
root,
|
|
3730
|
+
trailing
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Skips the `<?xml ... ?>` declaration when present. Its attributes are
|
|
3735
|
+
* not retained, since the writer always emits the canonical UTF-8
|
|
3736
|
+
* declaration.
|
|
3737
|
+
*/
|
|
3738
|
+
skipDeclaration() {
|
|
3739
|
+
if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN || this.input.charCodeAt(this.pos + 1) !== CODE_QUESTION) return;
|
|
3740
|
+
const end = this.input.indexOf("?>", this.pos + 2);
|
|
3741
|
+
if (end === -1) this.fail("Unterminated XML declaration");
|
|
3742
|
+
this.pos = end + 2;
|
|
3743
|
+
}
|
|
3744
|
+
/**
|
|
3745
|
+
* Parses one element with the cursor on its opening `<`, including its
|
|
3746
|
+
* attributes and children, through to the matching close tag. Xcode
|
|
3747
|
+
* never writes self-closing tags, but other generators do, so both
|
|
3748
|
+
* forms are accepted.
|
|
3749
|
+
*/
|
|
3750
|
+
parseElement() {
|
|
3751
|
+
if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN) this.fail("Expected an element");
|
|
3752
|
+
this.pos++;
|
|
3753
|
+
const name = this.parseName("element name");
|
|
3754
|
+
const attributes = Object.create(null);
|
|
3755
|
+
for (;;) {
|
|
3756
|
+
const hadWhitespace = this.skipWhitespace();
|
|
3757
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3758
|
+
if (code === CODE_GREATER_THAN) {
|
|
3759
|
+
this.pos++;
|
|
3760
|
+
break;
|
|
3761
|
+
}
|
|
3762
|
+
if (code === CODE_SLASH && this.input.charCodeAt(this.pos + 1) === CODE_GREATER_THAN) {
|
|
3763
|
+
this.pos += 2;
|
|
3764
|
+
return {
|
|
3765
|
+
name,
|
|
3766
|
+
attributes,
|
|
3767
|
+
children: []
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
if (Number.isNaN(code)) this.fail(`Unterminated <${name}> tag`);
|
|
3771
|
+
if (!hadWhitespace) this.fail("Expected whitespace before an attribute");
|
|
3772
|
+
const attributeName = this.parseName("attribute name");
|
|
3773
|
+
this.skipWhitespace();
|
|
3774
|
+
if (this.input.charCodeAt(this.pos) !== CODE_EQUALS) this.fail(`Expected = after attribute ${attributeName}`);
|
|
3775
|
+
this.pos++;
|
|
3776
|
+
this.skipWhitespace();
|
|
3777
|
+
if (attributeName in attributes) this.fail(`Duplicate attribute ${attributeName} on <${name}>`);
|
|
3778
|
+
attributes[attributeName] = this.parseAttributeValue();
|
|
3779
|
+
}
|
|
3780
|
+
const children = [];
|
|
3781
|
+
for (;;) {
|
|
3782
|
+
this.skipWhitespace();
|
|
3783
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3784
|
+
if (Number.isNaN(code)) this.fail(`Missing </${name}> close tag`);
|
|
3785
|
+
if (code !== CODE_LESS_THAN) this.fail("Unexpected text content inside an element");
|
|
3786
|
+
if (this.input.charCodeAt(this.pos + 1) === CODE_SLASH) {
|
|
3787
|
+
this.pos += 2;
|
|
3788
|
+
const closeName = this.parseName("close tag name");
|
|
3789
|
+
if (closeName !== name) this.fail(`Expected </${name}> but found </${closeName}>`);
|
|
3790
|
+
this.skipWhitespace();
|
|
3791
|
+
if (this.input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.fail(`Malformed </${closeName}> close tag`);
|
|
3792
|
+
this.pos++;
|
|
3793
|
+
return {
|
|
3794
|
+
name,
|
|
3795
|
+
attributes,
|
|
3796
|
+
children
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
if (this.peekIsCommentStart()) children.push(this.parseComment());
|
|
3800
|
+
else children.push(this.parseElement());
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
/**
|
|
3804
|
+
* Whether the cursor sits on a `<!--` comment opener.
|
|
3805
|
+
*/
|
|
3806
|
+
peekIsCommentStart() {
|
|
3807
|
+
return this.input.charCodeAt(this.pos) === CODE_LESS_THAN && this.input.charCodeAt(this.pos + 1) === CODE_BANG && this.input.charCodeAt(this.pos + 2) === CODE_HYPHEN && this.input.charCodeAt(this.pos + 3) === CODE_HYPHEN;
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Parses one comment with the cursor on its `<!--` opener. The text
|
|
3811
|
+
* between the markers is kept verbatim.
|
|
3812
|
+
*/
|
|
3813
|
+
parseComment() {
|
|
3814
|
+
const end = this.input.indexOf("-->", this.pos + 4);
|
|
3815
|
+
if (end === -1) this.fail("Unterminated comment");
|
|
3816
|
+
const comment = this.input.slice(this.pos + 4, end);
|
|
3817
|
+
this.pos = end + 3;
|
|
3818
|
+
return { comment };
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Parses an XML name at the cursor. The `what` label names the
|
|
3822
|
+
* expectation in the error when no name starts here.
|
|
3823
|
+
*/
|
|
3824
|
+
parseName(what) {
|
|
3825
|
+
const start = this.pos;
|
|
3826
|
+
if (!isNameStart(this.input.charCodeAt(this.pos))) this.fail(`Expected ${what}`);
|
|
3827
|
+
this.pos++;
|
|
3828
|
+
while (isNameChar(this.input.charCodeAt(this.pos))) this.pos++;
|
|
3829
|
+
return this.input.slice(start, this.pos);
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Parses a quoted attribute value with the cursor on the opening quote,
|
|
3833
|
+
* resolving character and entity references along the way. Both quote
|
|
3834
|
+
* styles are accepted, and a raw `<` inside the value fails as XML
|
|
3835
|
+
* requires.
|
|
3836
|
+
*/
|
|
3837
|
+
parseAttributeValue() {
|
|
3838
|
+
const quote = this.input.charCodeAt(this.pos);
|
|
3839
|
+
if (quote !== CODE_QUOTE && quote !== CODE_APOSTROPHE) this.fail("Expected a quoted attribute value");
|
|
3840
|
+
this.pos++;
|
|
3841
|
+
let value = "";
|
|
3842
|
+
let runStart = this.pos;
|
|
3843
|
+
for (;;) {
|
|
3844
|
+
const code = this.input.charCodeAt(this.pos);
|
|
3845
|
+
if (Number.isNaN(code)) this.fail("Unterminated attribute value");
|
|
3846
|
+
if (code === quote) {
|
|
3847
|
+
value += this.input.slice(runStart, this.pos);
|
|
3848
|
+
this.pos++;
|
|
3849
|
+
return value;
|
|
3850
|
+
}
|
|
3851
|
+
if (code === CODE_LESS_THAN) this.fail("Attribute values cannot contain a raw <");
|
|
3852
|
+
if (code === CODE_AMPERSAND) {
|
|
3853
|
+
value += this.input.slice(runStart, this.pos);
|
|
3854
|
+
value += this.parseReference();
|
|
3855
|
+
runStart = this.pos;
|
|
3856
|
+
} else this.pos++;
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Resolves one `&...;` reference with the cursor on the ampersand. The
|
|
3861
|
+
* accepted forms are the five XML named entities and decimal or hex
|
|
3862
|
+
* character references, which covers everything observed in
|
|
3863
|
+
* Xcode-written schemes (`"`, `&`, `'`, `<`, `>`,
|
|
3864
|
+
* and ` `-style whitespace).
|
|
3865
|
+
*/
|
|
3866
|
+
parseReference() {
|
|
3867
|
+
const start = this.pos;
|
|
3868
|
+
const end = this.input.indexOf(";", start + 1);
|
|
3869
|
+
if (end === -1 || end - start > 12) this.fail("Unterminated character reference");
|
|
3870
|
+
const body = this.input.slice(start + 1, end);
|
|
3871
|
+
this.pos = end + 1;
|
|
3872
|
+
if (body.charCodeAt(0) === CODE_HASH) {
|
|
3873
|
+
const isHex = body.charCodeAt(1) === 120 || body.charCodeAt(1) === 88;
|
|
3874
|
+
const digits = body.slice(isHex ? 2 : 1);
|
|
3875
|
+
const radix = isHex ? 16 : 10;
|
|
3876
|
+
const codePoint = Number.parseInt(digits, radix);
|
|
3877
|
+
if (digits.length === 0 || Number.isNaN(codePoint) || codePoint > 1114111) this.failAt(`Invalid character reference &${body};`, start);
|
|
3878
|
+
return String.fromCodePoint(codePoint);
|
|
3879
|
+
}
|
|
3880
|
+
const named = Object.hasOwn(NAMED_ENTITIES, body) ? NAMED_ENTITIES[body] : void 0;
|
|
3881
|
+
if (named == null) this.failAt(`Unknown entity &${body};`, start);
|
|
3882
|
+
return named;
|
|
3883
|
+
}
|
|
3884
|
+
/**
|
|
3885
|
+
* Skips whitespace and reports whether any was consumed, which attribute
|
|
3886
|
+
* parsing uses to require separation between attributes.
|
|
3887
|
+
*/
|
|
3888
|
+
skipWhitespace() {
|
|
3889
|
+
const start = this.pos;
|
|
3890
|
+
while (isWhitespace(this.input.charCodeAt(this.pos))) this.pos++;
|
|
3891
|
+
return this.pos > start;
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* Throws a parse error at the cursor.
|
|
3895
|
+
*/
|
|
3896
|
+
fail(message) {
|
|
3897
|
+
this.failAt(message, this.pos);
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Throws a parse error at an explicit offset, which reference parsing
|
|
3901
|
+
* uses to point at the start of a bad reference rather than its end.
|
|
3902
|
+
*/
|
|
3903
|
+
failAt(message, offset) {
|
|
3904
|
+
throw new XcschemeParseError(message, this.input, offset);
|
|
3905
|
+
}
|
|
3906
|
+
};
|
|
3907
|
+
//#endregion
|
|
3908
|
+
//#region src/scheme/model.ts
|
|
3909
|
+
/**
|
|
3910
|
+
* The scheme object model and its helpers.
|
|
3911
|
+
*
|
|
3912
|
+
* {@link Xcscheme} wraps a parsed document with the typed access editing
|
|
3913
|
+
* flows want, {@link BuildableReference} gives buildable references
|
|
3914
|
+
* property-style attribute access, {@link xcschemeElements} is the
|
|
3915
|
+
* recursive query underneath it all, and {@link createXcscheme} produces
|
|
3916
|
+
* the scheme Xcode's own "New Scheme" action writes. The node tree stays
|
|
3917
|
+
* the single source of truth. Views hold only a reference into it, so
|
|
3918
|
+
* model calls and direct tree edits compose freely.
|
|
3919
|
+
*
|
|
3920
|
+
* @module
|
|
3921
|
+
*/
|
|
3922
|
+
/**
|
|
3923
|
+
* Collects elements of the given name anywhere in the tree, in document
|
|
3924
|
+
* order, the subtree root included. Passing no name collects every
|
|
3925
|
+
* element.
|
|
3926
|
+
*/
|
|
3927
|
+
function xcschemeElements(root, name) {
|
|
3928
|
+
const found = [];
|
|
3929
|
+
const visit = (element) => {
|
|
3930
|
+
if (name == null || element.name === name) found.push(element);
|
|
3931
|
+
for (const child of element.children) if (isXcschemeElement(child)) visit(child);
|
|
3932
|
+
};
|
|
3933
|
+
visit(root);
|
|
3934
|
+
return found;
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* A `BuildableReference` element with property-style attribute access.
|
|
3938
|
+
*
|
|
3939
|
+
* Buildable references are the elements editing flows touch most, since
|
|
3940
|
+
* every action points at its target through one. The view reads and
|
|
3941
|
+
* writes the element's attributes directly, so it never goes stale and
|
|
3942
|
+
* needs no separate save step.
|
|
3943
|
+
*/
|
|
3944
|
+
var BuildableReference = class {
|
|
3945
|
+
/** The underlying element inside the document tree. */
|
|
3946
|
+
element;
|
|
3947
|
+
constructor(element) {
|
|
3948
|
+
this.element = element;
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* The referenced target's object id in the project document, when
|
|
3952
|
+
* present. Xcode repairs a missing or stale identifier on first open.
|
|
3953
|
+
*/
|
|
3954
|
+
get blueprintIdentifier() {
|
|
3955
|
+
return this.element.attributes["BlueprintIdentifier"];
|
|
3956
|
+
}
|
|
3957
|
+
set blueprintIdentifier(value) {
|
|
3958
|
+
this.element.attributes["BlueprintIdentifier"] = value;
|
|
3959
|
+
}
|
|
3960
|
+
/**
|
|
3961
|
+
* The referenced target's name, when present.
|
|
3962
|
+
*/
|
|
3963
|
+
get blueprintName() {
|
|
3964
|
+
return this.element.attributes["BlueprintName"];
|
|
3965
|
+
}
|
|
3966
|
+
set blueprintName(value) {
|
|
3967
|
+
this.element.attributes["BlueprintName"] = value;
|
|
3968
|
+
}
|
|
3969
|
+
/**
|
|
3970
|
+
* The built product's file name, for example `DemoApp.app`, when
|
|
3971
|
+
* present.
|
|
3972
|
+
*/
|
|
3973
|
+
get buildableName() {
|
|
3974
|
+
return this.element.attributes["BuildableName"];
|
|
3975
|
+
}
|
|
3976
|
+
set buildableName(value) {
|
|
3977
|
+
this.element.attributes["BuildableName"] = value;
|
|
3978
|
+
}
|
|
3979
|
+
/**
|
|
3980
|
+
* The container the target lives in, for example
|
|
3981
|
+
* `container:DemoApp.xcodeproj`, when present.
|
|
3982
|
+
*/
|
|
3983
|
+
get referencedContainer() {
|
|
3984
|
+
return this.element.attributes["ReferencedContainer"];
|
|
3985
|
+
}
|
|
3986
|
+
set referencedContainer(value) {
|
|
3987
|
+
this.element.attributes["ReferencedContainer"] = value;
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
/**
|
|
3991
|
+
* A scheme document with typed access to the elements editing flows
|
|
3992
|
+
* touch.
|
|
3993
|
+
*
|
|
3994
|
+
* The model is a thin layer over the node tree. All state lives in the
|
|
3995
|
+
* document itself, and {@link build} serializes whatever the tree
|
|
3996
|
+
* currently says, so typed edits and direct tree edits compose freely.
|
|
3997
|
+
*
|
|
3998
|
+
* ```ts
|
|
3999
|
+
* const scheme = Xcscheme.parse(xcschemeText);
|
|
4000
|
+
* for (const reference of scheme.buildableReferences()) {
|
|
4001
|
+
* reference.blueprintName = "RenamedApp";
|
|
4002
|
+
* reference.buildableName = "RenamedApp.app";
|
|
4003
|
+
* }
|
|
4004
|
+
* const text = scheme.build();
|
|
4005
|
+
* ```
|
|
4006
|
+
*/
|
|
4007
|
+
var Xcscheme = class Xcscheme {
|
|
4008
|
+
/** The underlying parsed document. */
|
|
4009
|
+
document;
|
|
4010
|
+
constructor(document) {
|
|
4011
|
+
this.document = document;
|
|
4012
|
+
}
|
|
4013
|
+
/**
|
|
4014
|
+
* Parses the text of a `.xcscheme` file into a scheme model.
|
|
4015
|
+
*
|
|
4016
|
+
* @throws XcschemeParseError when the text is not a well-formed scheme
|
|
4017
|
+
* document, with the line and column of the failure.
|
|
4018
|
+
*/
|
|
4019
|
+
static parse(text) {
|
|
4020
|
+
return new Xcscheme(parseXcscheme(text));
|
|
4021
|
+
}
|
|
4022
|
+
/**
|
|
4023
|
+
* Creates the scheme Xcode writes for an application target. See
|
|
4024
|
+
* {@link createXcscheme} for the shape it produces.
|
|
4025
|
+
*/
|
|
4026
|
+
static create(options) {
|
|
4027
|
+
return new Xcscheme(createXcscheme(options));
|
|
4028
|
+
}
|
|
4029
|
+
/**
|
|
4030
|
+
* The document's `Scheme` element.
|
|
4031
|
+
*/
|
|
4032
|
+
get root() {
|
|
4033
|
+
return this.document.root;
|
|
4034
|
+
}
|
|
4035
|
+
/**
|
|
4036
|
+
* Serializes the scheme to the text of a `.xcscheme` file in Xcode's
|
|
4037
|
+
* canonical layout.
|
|
4038
|
+
*/
|
|
4039
|
+
build() {
|
|
4040
|
+
return buildXcscheme(this.document);
|
|
4041
|
+
}
|
|
4042
|
+
/**
|
|
4043
|
+
* Collects elements of the given name anywhere in the document, in
|
|
4044
|
+
* document order. Passing no name collects every element.
|
|
4045
|
+
*/
|
|
4046
|
+
elements(name) {
|
|
4047
|
+
return xcschemeElements(this.document.root, name);
|
|
4048
|
+
}
|
|
4049
|
+
/**
|
|
4050
|
+
* The views of every buildable reference in the document, in document
|
|
4051
|
+
* order. Build entries, testables, macro expansions, runnables, and
|
|
4052
|
+
* action environment buildables all point at their target through one
|
|
4053
|
+
* of these, so rename flows iterate this list.
|
|
4054
|
+
*/
|
|
4055
|
+
buildableReferences() {
|
|
4056
|
+
return this.elements("BuildableReference").map((element) => new BuildableReference(element));
|
|
4057
|
+
}
|
|
4058
|
+
};
|
|
4059
|
+
/**
|
|
4060
|
+
* Creates the scheme Xcode writes for an application target. The
|
|
4061
|
+
* document carries build, launch, profile, analyze, and archive actions
|
|
4062
|
+
* wired to the app product, with Xcode's default configuration choices
|
|
4063
|
+
* of Debug for development actions and Release for profiling and
|
|
4064
|
+
* archiving.
|
|
4065
|
+
*/
|
|
4066
|
+
function createXcscheme(options) {
|
|
4067
|
+
const xcodeprojName = options.xcodeprojName ?? `${options.appName}.xcodeproj`;
|
|
4068
|
+
/**
|
|
4069
|
+
* Buildable references appear once per action. Each site gets its own
|
|
4070
|
+
* element so a mutation through one action cannot alias into another.
|
|
4071
|
+
*/
|
|
4072
|
+
const buildableReference = () => ({
|
|
4073
|
+
name: "BuildableReference",
|
|
4074
|
+
attributes: {
|
|
4075
|
+
BuildableIdentifier: "primary",
|
|
4076
|
+
BlueprintIdentifier: options.blueprintIdentifier ?? "",
|
|
4077
|
+
BuildableName: `${options.appName}.app`,
|
|
4078
|
+
BlueprintName: options.appName,
|
|
4079
|
+
ReferencedContainer: `container:${xcodeprojName}`
|
|
4080
|
+
},
|
|
4081
|
+
children: []
|
|
4082
|
+
});
|
|
4083
|
+
return {
|
|
4084
|
+
leading: [],
|
|
4085
|
+
root: {
|
|
4086
|
+
name: "Scheme",
|
|
4087
|
+
attributes: { version: "1.7" },
|
|
4088
|
+
children: [
|
|
4089
|
+
{
|
|
4090
|
+
name: "BuildAction",
|
|
4091
|
+
attributes: {
|
|
4092
|
+
parallelizeBuildables: "YES",
|
|
4093
|
+
buildImplicitDependencies: "YES"
|
|
4094
|
+
},
|
|
4095
|
+
children: [{
|
|
4096
|
+
name: "BuildActionEntries",
|
|
4097
|
+
attributes: {},
|
|
4098
|
+
children: [{
|
|
4099
|
+
name: "BuildActionEntry",
|
|
4100
|
+
attributes: {
|
|
4101
|
+
buildForTesting: "YES",
|
|
4102
|
+
buildForRunning: "YES",
|
|
4103
|
+
buildForProfiling: "YES",
|
|
4104
|
+
buildForArchiving: "YES",
|
|
4105
|
+
buildForAnalyzing: "YES"
|
|
4106
|
+
},
|
|
4107
|
+
children: [buildableReference()]
|
|
4108
|
+
}]
|
|
4109
|
+
}]
|
|
4110
|
+
},
|
|
4111
|
+
{
|
|
4112
|
+
name: "LaunchAction",
|
|
4113
|
+
attributes: {
|
|
4114
|
+
buildConfiguration: "Debug",
|
|
4115
|
+
selectedDebuggerIdentifier: "Xcode.DebuggerFoundation.Debugger.LLDB",
|
|
4116
|
+
selectedLauncherIdentifier: "Xcode.DebuggerFoundation.Launcher.LLDB",
|
|
4117
|
+
launchStyle: "0",
|
|
4118
|
+
useCustomWorkingDirectory: "NO",
|
|
4119
|
+
ignoresPersistentStateOnLaunch: "NO",
|
|
4120
|
+
debugDocumentVersioning: "YES",
|
|
4121
|
+
debugServiceExtension: "internal",
|
|
4122
|
+
allowLocationSimulation: "YES"
|
|
4123
|
+
},
|
|
4124
|
+
children: [{
|
|
4125
|
+
name: "BuildableProductRunnable",
|
|
4126
|
+
attributes: { runnableDebuggingMode: "0" },
|
|
4127
|
+
children: [buildableReference()]
|
|
4128
|
+
}]
|
|
4129
|
+
},
|
|
4130
|
+
{
|
|
4131
|
+
name: "ProfileAction",
|
|
4132
|
+
attributes: {
|
|
4133
|
+
buildConfiguration: "Release",
|
|
4134
|
+
shouldUseLaunchSchemeArgsEnv: "YES",
|
|
4135
|
+
savedToolIdentifier: "",
|
|
4136
|
+
useCustomWorkingDirectory: "NO",
|
|
4137
|
+
debugDocumentVersioning: "YES"
|
|
4138
|
+
},
|
|
4139
|
+
children: [{
|
|
4140
|
+
name: "BuildableProductRunnable",
|
|
4141
|
+
attributes: { runnableDebuggingMode: "0" },
|
|
4142
|
+
children: [buildableReference()]
|
|
4143
|
+
}]
|
|
4144
|
+
},
|
|
4145
|
+
{
|
|
4146
|
+
name: "AnalyzeAction",
|
|
4147
|
+
attributes: { buildConfiguration: "Debug" },
|
|
4148
|
+
children: []
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
name: "ArchiveAction",
|
|
4152
|
+
attributes: {
|
|
4153
|
+
buildConfiguration: "Release",
|
|
4154
|
+
revealArchiveInOrganizer: "YES"
|
|
4155
|
+
},
|
|
4156
|
+
children: []
|
|
4157
|
+
}
|
|
4158
|
+
]
|
|
4159
|
+
},
|
|
4160
|
+
trailing: []
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
//#endregion
|
|
4164
|
+
export { AggregateTarget, BuildConfiguration, BuildPhase, BuildRule, BuildableReference, ContainerItemProxy, CopyFilesDestination, FileReference, Group, Isa, LegacyTarget, NativeTarget, PbxprojBuildError, PbxprojParseError, ProductType, ReferenceProxy, RootProject, SyncRootGroup, Target, VersionGroup, XcodeModelError, XcodeObject, XcodeProject, Xcscheme, XcschemeBuildError, XcschemeParseError, buildPbxproj, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parsePbxproj, parseXcscheme, xcschemeElements };
|