rork-xcode 0.7.0 → 0.9.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 +42 -1
- package/dist/index.d.ts +300 -1
- package/dist/index.js +439 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,6 +266,47 @@ const text = scheme.build();
|
|
|
266
266
|
|
|
267
267
|
Underneath, the document is a plain tree of elements with ordered attributes and children, reachable through `scheme.root` and `scheme.elements(name)`, so anything the typed surface does not cover stays one property away. `parseXcscheme` and `buildXcscheme` remain available for working with the tree directly. Attribute order is preserved and meaningful, which is how byte-identical round-trips fall out. Comments are kept, attribute values resolve the character references Xcode writes (`"`, `&`, ` ` and friends), and the writer re-escapes them identically.
|
|
268
268
|
|
|
269
|
+
## Xcconfig files
|
|
270
|
+
|
|
271
|
+
Build settings do not only live in the pbxproj. Projects push them into `.xcconfig` files referenced through `baseConfigurationReference`, and the xcconfig module reads and writes that format with the fidelity the rest of the library promises. The format is hand-authored with no canonical layout, so the contract here is losslessness: parsing and building an untouched file reproduces it byte for byte — comments, blank lines, column alignment, and line endings included. Malformed lines fail loudly with a typed error carrying line and column rather than being dropped, so a file the parser accepts is a file it fully understood.
|
|
272
|
+
|
|
273
|
+
`Xcconfig` is the model. Reads follow the file top to bottom the way Xcode does, and writes edit single lines while leaving every other byte alone:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { Xcconfig } from "rork-xcode";
|
|
277
|
+
|
|
278
|
+
const config = Xcconfig.parse(xcconfigText);
|
|
279
|
+
|
|
280
|
+
config.get("PRODUCT_BUNDLE_IDENTIFIER"); // last unconditional assignment wins
|
|
281
|
+
config.set("MARKETING_VERSION", "1.2.0"); // rewrites in place, appends when new
|
|
282
|
+
const settings = config.settings(); // flattened, the way Xcode reads the file
|
|
283
|
+
|
|
284
|
+
const text = config.build();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`#include` directives are exposed as data because the library never touches the filesystem. Flattening resolves them through a caller-supplied lookup and applies each file at its directive's position, cycle-safe, so lines after an include override it exactly like textual inclusion. Position matters: an include hoisted or reordered changes what the file means, so the model never moves one.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const settings = config.settings({
|
|
291
|
+
resolveInclude: (path, optional) => loadedConfigs.get(path),
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Conditional assignments like `OTHER_LDFLAGS[sdk=iphoneos*][arch=arm64]` are parsed structurally with their conditions preserved verbatim on round-trip, unknown condition names included. Passing a build context applies them during flattening, with every condition required to match and trailing `*` wildcards honored; without a context they stay out, which mirrors reading the file with no build in mind. `$(inherited)` references splice in the value accumulated earlier in the chain and stay literal when there is none, so lower layers can still resolve them:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const settings = config.settings({
|
|
299
|
+
context: { sdk: "iphoneos", arch: "arm64", config: "Release" },
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Registering a file on the project makes `getBuildSetting` resolve through it in Xcode's order — target settings, the target's xcconfig, project settings, the project's xcconfig:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
project.registerXcconfig(reference, Xcconfig.parse(text));
|
|
307
|
+
app.getBuildSetting("SDKROOT"); // now sees values the xcconfig defines
|
|
308
|
+
```
|
|
309
|
+
|
|
269
310
|
## Performance
|
|
270
311
|
|
|
271
312
|
`rork-xcode` is measured against the pbxproj parsers on npm, [`@bacons/xcode`](https://www.npmjs.com/package/@bacons/xcode) (its `/json` parse/build entry point) and [`xcode`](https://www.npmjs.com/package/xcode) (the long-standing package used by native build tooling), on three documents: two real Xcode-written projects from the test suite and a deterministically generated five-target app with 800 source files. It is the fastest at both operations on every document, with zero dependencies.
|
|
@@ -297,7 +338,7 @@ Measured on an Apple M5 Max, Node.js 24, single thread, with `@bacons/xcode` 1.0
|
|
|
297
338
|
- The committed fixture corpus spans project generations from Xcode 3 to Xcode 16, captured from real projects with identifiers neutralized: synchronized folders with both exception-set kinds, classic groups, variant groups, aggregate and legacy targets, reference proxies, build rules, Swift packages, and a ~100 KiB multiplatform framework project.
|
|
298
339
|
- Documents already in current Xcode's layout must round-trip byte for byte; documents from other tool generations must normalize to a byte-stable fixed point with unchanged values.
|
|
299
340
|
- On macOS, the suite cross-validates every fixture and its rebuilt form with `plutil`, Apple's own property list parser and the empirical ground truth for what Apple tooling accepts.
|
|
300
|
-
- A corpus sweep (`pnpm corpus`) walks every Xcode project and
|
|
341
|
+
- A corpus sweep (`pnpm corpus`) walks every Xcode project, scheme, and xcconfig on the machine, verifies each one parses and reaches a byte-stable fixed point (byte-exact losslessness for xcconfig, which has no canonical layout), exercises the object model against every project, and cross-validates a sample against plutil's own reading.
|
|
301
342
|
- CI runs the full gate on Linux and macOS, and executes the built artifact on the oldest supported Node to enforce the `engines` floor.
|
|
302
343
|
|
|
303
344
|
## Releasing
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,25 @@ declare class XcschemeParseError extends Error {
|
|
|
133
133
|
*/
|
|
134
134
|
constructor(message: string, source: string, offset: number);
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Thrown when the source text is not a well-formed build configuration
|
|
138
|
+
* file (the line-based format of `.xcconfig` files).
|
|
139
|
+
*
|
|
140
|
+
* The message always embeds the line and column of the failure, and the
|
|
141
|
+
* same information is available in structured form on {@link position}
|
|
142
|
+
* for programmatic use.
|
|
143
|
+
*/
|
|
144
|
+
declare class XcconfigParseError extends Error {
|
|
145
|
+
/** Where in the source text parsing failed. */
|
|
146
|
+
readonly position: PbxprojErrorPosition;
|
|
147
|
+
/**
|
|
148
|
+
* @param message Failure description without location; the location is
|
|
149
|
+
* appended automatically.
|
|
150
|
+
* @param source Full source text, used to compute the position.
|
|
151
|
+
* @param offset Character offset of the failure inside `source`.
|
|
152
|
+
*/
|
|
153
|
+
constructor(message: string, source: string, offset: number);
|
|
154
|
+
}
|
|
136
155
|
/**
|
|
137
156
|
* Thrown when a scheme element cannot be written as XML.
|
|
138
157
|
*
|
|
@@ -261,6 +280,15 @@ declare const ProductType: {
|
|
|
261
280
|
* The Apple platforms the model can resolve a main application target for.
|
|
262
281
|
*/
|
|
263
282
|
type ApplePlatform = "ios" | "macos" | "tvos" | "watchos" | "visionos";
|
|
283
|
+
/**
|
|
284
|
+
* Normalizes a platform name to an {@link ApplePlatform}. Punctuation and
|
|
285
|
+
* casing are ignored, so spellings like `tvOS`, `TV_OS`, and `visionos`
|
|
286
|
+
* all resolve, and the SDK names Xcode writes into `SDKROOT` (for
|
|
287
|
+
* example `iphoneos`, `appletvos`, and `xros`, with their simulator
|
|
288
|
+
* variants) resolve to the platform they build for. Anything else
|
|
289
|
+
* returns `undefined`.
|
|
290
|
+
*/
|
|
291
|
+
declare function parseApplePlatform(value: string): ApplePlatform | undefined;
|
|
264
292
|
/**
|
|
265
293
|
* `dstSubfolderSpec` values for copy-files build phases, named after the
|
|
266
294
|
* destination each selects.
|
|
@@ -599,6 +627,11 @@ declare class Target<Properties extends TargetProperties = TargetProperties> ext
|
|
|
599
627
|
* generated app templates set values like `SDKROOT` only at the project
|
|
600
628
|
* level.
|
|
601
629
|
*
|
|
630
|
+
* Configurations based on `.xcconfig` files take part once the files
|
|
631
|
+
* are registered through {@link XcodeProject.registerXcconfig}, in
|
|
632
|
+
* Xcode's order: target settings, the target's xcconfig, project
|
|
633
|
+
* settings, the project's xcconfig.
|
|
634
|
+
*
|
|
602
635
|
* Only string values are returned; a list- or number-valued setting reads
|
|
603
636
|
* as `undefined`.
|
|
604
637
|
*/
|
|
@@ -1119,6 +1152,13 @@ declare class BuildConfiguration extends XcodeObject<BuildConfigurationPropertie
|
|
|
1119
1152
|
* document.
|
|
1120
1153
|
*/
|
|
1121
1154
|
get buildSettings(): BuildSettings | undefined;
|
|
1155
|
+
/**
|
|
1156
|
+
* The view of the `.xcconfig` file reference this configuration is
|
|
1157
|
+
* based on, when the configuration has one and it resolves. The file's
|
|
1158
|
+
* settings sit below the configuration's own `buildSettings` in
|
|
1159
|
+
* Xcode's resolution order.
|
|
1160
|
+
*/
|
|
1161
|
+
baseConfigurationReference(): FileReference | undefined;
|
|
1122
1162
|
}
|
|
1123
1163
|
/**
|
|
1124
1164
|
* A `PBXBuildStyle` is the pre-Xcode-2 predecessor of
|
|
@@ -1261,6 +1301,227 @@ declare class ReferenceProxy extends XcodeObject<ReferenceProxyProperties> {
|
|
|
1261
1301
|
remoteReference(): ContainerItemProxy | undefined;
|
|
1262
1302
|
}
|
|
1263
1303
|
//#endregion
|
|
1304
|
+
//#region src/xcconfig/types.d.ts
|
|
1305
|
+
/**
|
|
1306
|
+
* Document types for Xcode build configuration files (`.xcconfig`).
|
|
1307
|
+
*
|
|
1308
|
+
* The format is line based, so the document is the ordered list of parsed
|
|
1309
|
+
* lines. Every statement keeps the exact text it was parsed from (`raw`)
|
|
1310
|
+
* and its line terminator (`eol`), which is what lets an untouched
|
|
1311
|
+
* document rebuild byte for byte even though the format is hand-authored
|
|
1312
|
+
* and has no canonical layout.
|
|
1313
|
+
*
|
|
1314
|
+
* @module
|
|
1315
|
+
*/
|
|
1316
|
+
/**
|
|
1317
|
+
* One `[name=value]` condition attached to a setting assignment, as in
|
|
1318
|
+
* `LDFLAGS[sdk=iphoneos*] = -lfoo`.
|
|
1319
|
+
*/
|
|
1320
|
+
interface XcconfigCondition {
|
|
1321
|
+
/** The condition name, for example `sdk`, `arch`, or `config`. */
|
|
1322
|
+
name: string;
|
|
1323
|
+
/** The condition value, which may carry a trailing `*` wildcard. */
|
|
1324
|
+
value: string;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* A `KEY = value` line, optionally with conditions between the key and
|
|
1328
|
+
* the equals sign. The value has surrounding whitespace, an optional
|
|
1329
|
+
* trailing semicolon, and any trailing `//` comment removed.
|
|
1330
|
+
*/
|
|
1331
|
+
interface XcconfigAssignment {
|
|
1332
|
+
kind: "assignment";
|
|
1333
|
+
/** The setting name. */
|
|
1334
|
+
key: string;
|
|
1335
|
+
/** The `[name=value]` conditions, in written order. Usually empty. */
|
|
1336
|
+
conditions: readonly XcconfigCondition[];
|
|
1337
|
+
/** The assigned value with comment, semicolon, and padding stripped. */
|
|
1338
|
+
value: string;
|
|
1339
|
+
/** The exact source line, without its terminator. */
|
|
1340
|
+
raw: string;
|
|
1341
|
+
/** The line terminator, `"\n"`, `"\r\n"`, or `""` on a final line. */
|
|
1342
|
+
eol: string;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* An `#include "path"` line. The optional form `#include? "path"` tells
|
|
1346
|
+
* Xcode to ignore a missing file, and callers should mirror that when
|
|
1347
|
+
* resolving includes.
|
|
1348
|
+
*/
|
|
1349
|
+
interface XcconfigInclude {
|
|
1350
|
+
kind: "include";
|
|
1351
|
+
/** The include path exactly as written between the quotes. */
|
|
1352
|
+
path: string;
|
|
1353
|
+
/** True for the `#include?` form. */
|
|
1354
|
+
optional: boolean;
|
|
1355
|
+
/** The exact source line, without its terminator. */
|
|
1356
|
+
raw: string;
|
|
1357
|
+
/** The line terminator, `"\n"`, `"\r\n"`, or `""` on a final line. */
|
|
1358
|
+
eol: string;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* A line holding only a `//` comment.
|
|
1362
|
+
*/
|
|
1363
|
+
interface XcconfigComment {
|
|
1364
|
+
kind: "comment";
|
|
1365
|
+
/** The comment text after `//`, untrimmed. */
|
|
1366
|
+
text: string;
|
|
1367
|
+
/** The exact source line, without its terminator. */
|
|
1368
|
+
raw: string;
|
|
1369
|
+
/** The line terminator, `"\n"`, `"\r\n"`, or `""` on a final line. */
|
|
1370
|
+
eol: string;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* A line that is empty or holds only whitespace.
|
|
1374
|
+
*/
|
|
1375
|
+
interface XcconfigBlank {
|
|
1376
|
+
kind: "blank";
|
|
1377
|
+
/** The exact source line, without its terminator. */
|
|
1378
|
+
raw: string;
|
|
1379
|
+
/** The line terminator, `"\n"`, `"\r\n"`, or `""` on a final line. */
|
|
1380
|
+
eol: string;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Any parsed `.xcconfig` line.
|
|
1384
|
+
*/
|
|
1385
|
+
type XcconfigStatement = XcconfigAssignment | XcconfigBlank | XcconfigComment | XcconfigInclude;
|
|
1386
|
+
/**
|
|
1387
|
+
* A parsed `.xcconfig` file: its lines in order.
|
|
1388
|
+
*/
|
|
1389
|
+
interface XcconfigDocument {
|
|
1390
|
+
statements: XcconfigStatement[];
|
|
1391
|
+
}
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region src/xcconfig/model.d.ts
|
|
1394
|
+
/**
|
|
1395
|
+
* Resolves an `#include` path to the included file's model. Returning
|
|
1396
|
+
* `undefined` skips the include, which is always legal for the
|
|
1397
|
+
* `#include?` form and mirrors a missing file for the strict form.
|
|
1398
|
+
*
|
|
1399
|
+
* Cycles are detected while a file is being expanded, by the include
|
|
1400
|
+
* path exactly as written and by model instance. A cycle reached through
|
|
1401
|
+
* two different spellings of the same path escapes the path check, so
|
|
1402
|
+
* resolvers that memoize per path make the instance check catch it.
|
|
1403
|
+
*/
|
|
1404
|
+
type XcconfigIncludeResolver = (path: string, optional: boolean) => Xcconfig | undefined;
|
|
1405
|
+
/**
|
|
1406
|
+
* The build context conditional assignments are matched against. Keys
|
|
1407
|
+
* mirror the condition names of the format, so `KEY[sdk=iphoneos*]`
|
|
1408
|
+
* matches against {@link sdk}.
|
|
1409
|
+
*/
|
|
1410
|
+
interface XcconfigBuildContext {
|
|
1411
|
+
/** The SDK identifier, for example `iphoneos` or `appletvsimulator`. */
|
|
1412
|
+
sdk?: string;
|
|
1413
|
+
/** The architecture, for example `arm64`. */
|
|
1414
|
+
arch?: string;
|
|
1415
|
+
/** The configuration name, for example `Debug`. */
|
|
1416
|
+
config?: string;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Options for {@link Xcconfig.settings}.
|
|
1420
|
+
*/
|
|
1421
|
+
interface XcconfigSettingsOptions {
|
|
1422
|
+
/**
|
|
1423
|
+
* Called for every `#include` directive, in document order. Without a
|
|
1424
|
+
* resolver, includes contribute nothing.
|
|
1425
|
+
*/
|
|
1426
|
+
resolveInclude?: XcconfigIncludeResolver;
|
|
1427
|
+
/**
|
|
1428
|
+
* The build context conditional assignments apply under. An assignment
|
|
1429
|
+
* takes part when every one of its conditions matches, with trailing
|
|
1430
|
+
* `*` wildcards honored the way Xcode matches SDK names. Without a
|
|
1431
|
+
* context, and for dimensions the context leaves out, conditional
|
|
1432
|
+
* assignments are skipped.
|
|
1433
|
+
*/
|
|
1434
|
+
context?: XcconfigBuildContext;
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* A build configuration file with typed, mutable access to its settings.
|
|
1438
|
+
*
|
|
1439
|
+
* ```ts
|
|
1440
|
+
* const config = Xcconfig.parse(text);
|
|
1441
|
+
* config.get("PRODUCT_BUNDLE_IDENTIFIER");
|
|
1442
|
+
* config.set("MARKETING_VERSION", "1.2.0");
|
|
1443
|
+
* const updated = config.build();
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
declare class Xcconfig {
|
|
1447
|
+
/** The parsed document this model wraps. */
|
|
1448
|
+
readonly document: XcconfigDocument;
|
|
1449
|
+
private constructor();
|
|
1450
|
+
/**
|
|
1451
|
+
* Parses `.xcconfig` text and wraps it in a model.
|
|
1452
|
+
*
|
|
1453
|
+
* @throws XcconfigParseError when the text is malformed.
|
|
1454
|
+
*/
|
|
1455
|
+
static parse(text: string): Xcconfig;
|
|
1456
|
+
/**
|
|
1457
|
+
* Creates an empty configuration file.
|
|
1458
|
+
*/
|
|
1459
|
+
static create(): Xcconfig;
|
|
1460
|
+
/**
|
|
1461
|
+
* Serializes the current document state to `.xcconfig` text.
|
|
1462
|
+
*/
|
|
1463
|
+
build(): string;
|
|
1464
|
+
/**
|
|
1465
|
+
* The names of the settings this file assigns unconditionally, in
|
|
1466
|
+
* first-assignment order. Conditional assignments such as
|
|
1467
|
+
* `KEY[sdk=iphoneos*]` are reachable through {@link assignments}.
|
|
1468
|
+
*/
|
|
1469
|
+
keys(): string[];
|
|
1470
|
+
/**
|
|
1471
|
+
* Every assignment of this file, conditional ones included, in
|
|
1472
|
+
* document order. The items are the document's own nodes, not copies,
|
|
1473
|
+
* so treat them as read-only views. Writes belong on {@link set},
|
|
1474
|
+
* which keeps a statement's value and its source text in step.
|
|
1475
|
+
*/
|
|
1476
|
+
assignments(): readonly XcconfigAssignment[];
|
|
1477
|
+
/**
|
|
1478
|
+
* The `#include` directives of this file, in document order. The items
|
|
1479
|
+
* are the document's own nodes, not copies, so treat them as read-only
|
|
1480
|
+
* views.
|
|
1481
|
+
*/
|
|
1482
|
+
includes(): readonly XcconfigInclude[];
|
|
1483
|
+
/**
|
|
1484
|
+
* Reads the value of a setting from this file alone. When the key is
|
|
1485
|
+
* assigned more than once the last assignment wins, matching how Xcode
|
|
1486
|
+
* reads the file top to bottom. Conditional assignments are ignored.
|
|
1487
|
+
*/
|
|
1488
|
+
get(key: string): string | undefined;
|
|
1489
|
+
/**
|
|
1490
|
+
* Writes a setting. The last unconditional assignment of the key is
|
|
1491
|
+
* rewritten in place as a canonical `KEY = value` line, replacing any
|
|
1492
|
+
* trailing comment the line carried. A key the file does not assign
|
|
1493
|
+
* yet is appended at the end, following the document's line-ending
|
|
1494
|
+
* convention.
|
|
1495
|
+
*/
|
|
1496
|
+
set(key: string, value: string): void;
|
|
1497
|
+
/**
|
|
1498
|
+
* Removes every unconditional assignment of a setting.
|
|
1499
|
+
*
|
|
1500
|
+
* @returns True when at least one assignment was removed.
|
|
1501
|
+
*/
|
|
1502
|
+
remove(key: string): boolean;
|
|
1503
|
+
/**
|
|
1504
|
+
* Flattens the file into a settings dictionary the way Xcode reads it:
|
|
1505
|
+
* top to bottom with later assignments winning, and every `#include`
|
|
1506
|
+
* contributing its settings at the point of the directive, so lines
|
|
1507
|
+
* after an include override it.
|
|
1508
|
+
*
|
|
1509
|
+
* Conditional assignments apply when every condition matches
|
|
1510
|
+
* {@link XcconfigSettingsOptions.context}; without a context they are
|
|
1511
|
+
* skipped. `$(inherited)` references splice in the value accumulated
|
|
1512
|
+
* earlier in the chain, and stay literal when there is none, since
|
|
1513
|
+
* they then refer to layers below the file, which resolve later.
|
|
1514
|
+
*
|
|
1515
|
+
* Includes only take part when {@link XcconfigSettingsOptions.resolveInclude}
|
|
1516
|
+
* is provided, since the library never touches the filesystem itself.
|
|
1517
|
+
* A file included again later re-applies, exactly like pasting its text
|
|
1518
|
+
* a second time. Only re-entry while a file is still being expanded is
|
|
1519
|
+
* skipped, tracked by include path and by instance, so cyclic includes
|
|
1520
|
+
* terminate even when the resolver parses a fresh instance per call.
|
|
1521
|
+
*/
|
|
1522
|
+
settings(options?: XcconfigSettingsOptions): Record<string, string>;
|
|
1523
|
+
}
|
|
1524
|
+
//#endregion
|
|
1264
1525
|
//#region src/model/project.d.ts
|
|
1265
1526
|
/**
|
|
1266
1527
|
* The `PBXProject` object at the document root. It owns the target list,
|
|
@@ -1342,6 +1603,12 @@ declare class XcodeProject {
|
|
|
1342
1603
|
* access, so views of the same object compare with `===`.
|
|
1343
1604
|
*/
|
|
1344
1605
|
private readonly views;
|
|
1606
|
+
/**
|
|
1607
|
+
* Flattened settings of registered `.xcconfig` files, keyed by the id
|
|
1608
|
+
* of the file reference configurations name in
|
|
1609
|
+
* `baseConfigurationReference`.
|
|
1610
|
+
*/
|
|
1611
|
+
private readonly xcconfigSettings;
|
|
1345
1612
|
private constructor();
|
|
1346
1613
|
/**
|
|
1347
1614
|
* Parses pbxproj text and wraps it in a model.
|
|
@@ -1405,6 +1672,22 @@ declare class XcodeProject {
|
|
|
1405
1672
|
* @returns The view of the created object.
|
|
1406
1673
|
*/
|
|
1407
1674
|
add<I extends string>(isa: I, properties: PbxprojObject, seed?: string): ViewOf<I>;
|
|
1675
|
+
/**
|
|
1676
|
+
* Registers the contents of a `.xcconfig` file so build-setting reads
|
|
1677
|
+
* can layer it below the configurations that are based on it. The
|
|
1678
|
+
* library never touches the filesystem, so the caller loads the file
|
|
1679
|
+
* and hands it over together with the file reference that
|
|
1680
|
+
* configurations name in `baseConfigurationReference`. Included files
|
|
1681
|
+
* take part through {@link XcconfigSettingsOptions.resolveInclude};
|
|
1682
|
+
* the settings are flattened once, at registration.
|
|
1683
|
+
*/
|
|
1684
|
+
registerXcconfig(reference: FileReference, config: Xcconfig, options?: XcconfigSettingsOptions): void;
|
|
1685
|
+
/**
|
|
1686
|
+
* The flattened settings registered for the `.xcconfig` file a
|
|
1687
|
+
* configuration is based on, or `undefined` when the configuration
|
|
1688
|
+
* names none or the file was not registered.
|
|
1689
|
+
*/
|
|
1690
|
+
xcconfigSettingsOf(configuration: BuildConfiguration): Record<string, string> | undefined;
|
|
1408
1691
|
/**
|
|
1409
1692
|
* The view of the document's root `PBXProject` object.
|
|
1410
1693
|
*
|
|
@@ -1959,4 +2242,20 @@ declare function parseXcscheme(text: string): XcschemeDocument;
|
|
|
1959
2242
|
*/
|
|
1960
2243
|
declare function generateObjectId(seed: string, existing: ReadonlySet<string>): string;
|
|
1961
2244
|
//#endregion
|
|
1962
|
-
|
|
2245
|
+
//#region src/xcconfig/build.d.ts
|
|
2246
|
+
/**
|
|
2247
|
+
* Serializes a document back to `.xcconfig` text.
|
|
2248
|
+
*/
|
|
2249
|
+
declare function buildXcconfig(document: XcconfigDocument): string;
|
|
2250
|
+
//#endregion
|
|
2251
|
+
//#region src/xcconfig/parse.d.ts
|
|
2252
|
+
/**
|
|
2253
|
+
* Parses `.xcconfig` text into its document form.
|
|
2254
|
+
*
|
|
2255
|
+
* @throws XcconfigParseError when a line is not a blank, a comment, an
|
|
2256
|
+
* include, or a well-formed assignment. The error carries the line and
|
|
2257
|
+
* column of the failure.
|
|
2258
|
+
*/
|
|
2259
|
+
declare function parseXcconfig(source: string): XcconfigDocument;
|
|
2260
|
+
//#endregion
|
|
2261
|
+
export { type AddNativeTargetOptions, AggregateTarget, type ApplePlatform, AppleScriptBuildPhase, BuildConfiguration, type BuildConfigurationProperties, BuildFile, BuildFileExceptionSet, type BuildFileProperties, BuildPhase, type BuildPhaseIsa, BuildPhaseMembershipExceptionSet, type BuildPhaseMembershipExceptionSetProperties, type BuildPhaseOf, type BuildPhaseProperties, BuildRule, type BuildRuleProperties, type BuildSettings, BuildStyle, type BuildStyleProperties, BuildableReference, ConfigurationList, type ConfigurationListProperties, ContainerItemProxy, type ContainerItemProxyProperties, CopyFilesBuildPhase, type CopyFilesBuildPhaseProperties, CopyFilesDestination, type CreateXcschemeOptions, ExceptionSet, type ExceptionSetProperties, FileReference, type FileReferenceProperties, FrameworksBuildPhase, Group, type GroupProperties, HeadersBuildPhase, Isa, type IsaValue, LegacyTarget, type LegacyTargetProperties, LocalSwiftPackageReference, type LocalSwiftPackageReferenceProperties, NativeTarget, type NativeTargetProperties, type PbxprojArray, PbxprojBuildError, type PbxprojErrorPosition, type PbxprojObject, PbxprojParseError, type PbxprojValue, ProductType, type ProjectIssue, type ProjectIssueKind, ReferenceProxy, type ReferenceProxyProperties, RemoteSwiftPackageReference, type RemoteSwiftPackageReferenceProperties, ResourcesBuildPhase, RezBuildPhase, RootProject, type RootProjectProperties, ShellScriptBuildPhase, type ShellScriptBuildPhaseProperties, SourcesBuildPhase, SwiftPackageProductDependency, type SwiftPackageProductDependencyProperties, SwiftPackageReference, type SwiftPackageReferenceProperties, SyncRootGroup, type SyncRootGroupProperties, Target, TargetDependency, type TargetDependencyProperties, type TargetProperties, VariantGroup, VersionGroup, type VersionGroupProperties, type ViewByIsa, type ViewOf, Xcconfig, type XcconfigAssignment, type XcconfigBlank, type XcconfigComment, type XcconfigCondition, type XcconfigDocument, type XcconfigInclude, type XcconfigIncludeResolver, XcconfigParseError, type XcconfigSettingsOptions, type XcconfigStatement, XcodeModelError, XcodeObject, XcodeProject, Xcscheme, XcschemeBuildError, type XcschemeComment, type XcschemeDocument, type XcschemeElement, type XcschemeNode, XcschemeParseError, buildPbxproj, buildXcconfig, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parseApplePlatform, parsePbxproj, parseXcconfig, parseXcscheme, xcschemeElements };
|
package/dist/index.js
CHANGED
|
@@ -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
|
|
@@ -887,6 +911,33 @@ const PRODUCT_FILE_INFO = {
|
|
|
887
911
|
}
|
|
888
912
|
};
|
|
889
913
|
/**
|
|
914
|
+
* Normalizes a platform name to an {@link ApplePlatform}. Punctuation and
|
|
915
|
+
* casing are ignored, so spellings like `tvOS`, `TV_OS`, and `visionos`
|
|
916
|
+
* all resolve, and the SDK names Xcode writes into `SDKROOT` (for
|
|
917
|
+
* example `iphoneos`, `appletvos`, and `xros`, with their simulator
|
|
918
|
+
* variants) resolve to the platform they build for. Anything else
|
|
919
|
+
* returns `undefined`.
|
|
920
|
+
*/
|
|
921
|
+
function parseApplePlatform(value) {
|
|
922
|
+
switch (value.toLowerCase().replaceAll(/[^a-z]/gu, "")) {
|
|
923
|
+
case "ios":
|
|
924
|
+
case "iphoneos":
|
|
925
|
+
case "iphonesimulator": return "ios";
|
|
926
|
+
case "macos":
|
|
927
|
+
case "macosx":
|
|
928
|
+
case "osx": return "macos";
|
|
929
|
+
case "tvos":
|
|
930
|
+
case "appletvos":
|
|
931
|
+
case "appletvsimulator": return "tvos";
|
|
932
|
+
case "watchos":
|
|
933
|
+
case "watchsimulator": return "watchos";
|
|
934
|
+
case "visionos":
|
|
935
|
+
case "xros":
|
|
936
|
+
case "xrsimulator": return "visionos";
|
|
937
|
+
default: return;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
890
941
|
* The build-setting key that carries each platform's deployment target.
|
|
891
942
|
* A target (or its project) sets exactly one of these per platform it
|
|
892
943
|
* builds for, which is what makes them usable as a platform signal.
|
|
@@ -1560,6 +1611,16 @@ var BuildConfiguration = class extends XcodeObject {
|
|
|
1560
1611
|
get buildSettings() {
|
|
1561
1612
|
return asDictionary(this.properties["buildSettings"]);
|
|
1562
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
|
+
}
|
|
1563
1624
|
};
|
|
1564
1625
|
/**
|
|
1565
1626
|
* A `PBXBuildStyle` is the pre-Xcode-2 predecessor of
|
|
@@ -2853,16 +2914,23 @@ function configurationsOf(project, configurationListId) {
|
|
|
2853
2914
|
return configurations;
|
|
2854
2915
|
}
|
|
2855
2916
|
/**
|
|
2856
|
-
* The
|
|
2857
|
-
*
|
|
2858
|
-
* configuration. Returns `undefined` when the list has no configurations
|
|
2859
|
-
* or the default carries no settings dictionary.
|
|
2917
|
+
* The view of a configuration list's default configuration, which is the
|
|
2918
|
+
* one named by `defaultConfigurationName`, falling back to the first
|
|
2919
|
+
* configuration. Returns `undefined` when the list has no configurations.
|
|
2860
2920
|
*/
|
|
2861
|
-
function
|
|
2921
|
+
function defaultConfigurationOf(project, configurationListId) {
|
|
2862
2922
|
const list = asDictionary(project.propertiesOfOptional(configurationListId));
|
|
2863
2923
|
const configurations = configurationsOf(project, configurationListId);
|
|
2864
2924
|
const defaultName = asString(list?.["defaultConfigurationName"]);
|
|
2865
|
-
return
|
|
2925
|
+
return configurations.find((configuration) => configuration.getString("name") === defaultName) ?? configurations[0];
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* The settings dictionary of a configuration list's default configuration
|
|
2929
|
+
* (see {@link defaultConfigurationOf}). Returns `undefined` when the list
|
|
2930
|
+
* has no configurations or the default carries no settings dictionary.
|
|
2931
|
+
*/
|
|
2932
|
+
function defaultConfigurationSettingsOf(project, configurationListId) {
|
|
2933
|
+
return asDictionary(defaultConfigurationOf(project, configurationListId)?.properties["buildSettings"]);
|
|
2866
2934
|
}
|
|
2867
2935
|
//#endregion
|
|
2868
2936
|
//#region src/model/target.ts
|
|
@@ -2923,13 +2991,24 @@ var Target = class extends XcodeObject {
|
|
|
2923
2991
|
* generated app templates set values like `SDKROOT` only at the project
|
|
2924
2992
|
* level.
|
|
2925
2993
|
*
|
|
2994
|
+
* Configurations based on `.xcconfig` files take part once the files
|
|
2995
|
+
* are registered through {@link XcodeProject.registerXcconfig}, in
|
|
2996
|
+
* Xcode's order: target settings, the target's xcconfig, project
|
|
2997
|
+
* settings, the project's xcconfig.
|
|
2998
|
+
*
|
|
2926
2999
|
* Only string values are returned; a list- or number-valued setting reads
|
|
2927
3000
|
* as `undefined`.
|
|
2928
3001
|
*/
|
|
2929
3002
|
getBuildSetting(key) {
|
|
2930
|
-
const
|
|
3003
|
+
const targetConfiguration = defaultConfigurationOf(this.project, this.getString("buildConfigurationList"));
|
|
3004
|
+
const targetSettings = asDictionary(targetConfiguration?.properties["buildSettings"]);
|
|
2931
3005
|
if (targetSettings != null && key in targetSettings) return asString(targetSettings[key]);
|
|
2932
|
-
|
|
3006
|
+
const targetXcconfig = targetConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(targetConfiguration);
|
|
3007
|
+
if (targetXcconfig != null && key in targetXcconfig) return targetXcconfig[key];
|
|
3008
|
+
const projectConfiguration = defaultConfigurationOf(this.project, this.project.rootProject.getString("buildConfigurationList"));
|
|
3009
|
+
const projectSettings = asDictionary(projectConfiguration?.properties["buildSettings"]);
|
|
3010
|
+
if (projectSettings != null && key in projectSettings) return asString(projectSettings[key]);
|
|
3011
|
+
return (projectConfiguration == null ? void 0 : this.project.xcconfigSettingsOf(projectConfiguration))?.[key];
|
|
2933
3012
|
}
|
|
2934
3013
|
/**
|
|
2935
3014
|
* Writes a build setting on every configuration of the target, so Debug
|
|
@@ -3373,6 +3452,12 @@ var XcodeProject = class XcodeProject {
|
|
|
3373
3452
|
* access, so views of the same object compare with `===`.
|
|
3374
3453
|
*/
|
|
3375
3454
|
views = /* @__PURE__ */ new Map();
|
|
3455
|
+
/**
|
|
3456
|
+
* Flattened settings of registered `.xcconfig` files, keyed by the id
|
|
3457
|
+
* of the file reference configurations name in
|
|
3458
|
+
* `baseConfigurationReference`.
|
|
3459
|
+
*/
|
|
3460
|
+
xcconfigSettings = /* @__PURE__ */ new Map();
|
|
3376
3461
|
constructor(document) {
|
|
3377
3462
|
const objects = asDictionary(document["objects"]);
|
|
3378
3463
|
if (objects == null) throw new XcodeModelError("The document has no objects dictionary");
|
|
@@ -3481,6 +3566,27 @@ var XcodeProject = class XcodeProject {
|
|
|
3481
3566
|
return view;
|
|
3482
3567
|
}
|
|
3483
3568
|
/**
|
|
3569
|
+
* Registers the contents of a `.xcconfig` file so build-setting reads
|
|
3570
|
+
* can layer it below the configurations that are based on it. The
|
|
3571
|
+
* library never touches the filesystem, so the caller loads the file
|
|
3572
|
+
* and hands it over together with the file reference that
|
|
3573
|
+
* configurations name in `baseConfigurationReference`. Included files
|
|
3574
|
+
* take part through {@link XcconfigSettingsOptions.resolveInclude};
|
|
3575
|
+
* the settings are flattened once, at registration.
|
|
3576
|
+
*/
|
|
3577
|
+
registerXcconfig(reference, config, options = {}) {
|
|
3578
|
+
this.xcconfigSettings.set(reference.id, config.settings(options));
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* The flattened settings registered for the `.xcconfig` file a
|
|
3582
|
+
* configuration is based on, or `undefined` when the configuration
|
|
3583
|
+
* names none or the file was not registered.
|
|
3584
|
+
*/
|
|
3585
|
+
xcconfigSettingsOf(configuration) {
|
|
3586
|
+
const referenceId = configuration.getString("baseConfigurationReference");
|
|
3587
|
+
return referenceId == null ? void 0 : this.xcconfigSettings.get(referenceId);
|
|
3588
|
+
}
|
|
3589
|
+
/**
|
|
3484
3590
|
* The view of the document's root `PBXProject` object.
|
|
3485
3591
|
*
|
|
3486
3592
|
* @throws XcodeModelError when `rootObject` is missing or dangling,
|
|
@@ -4530,4 +4636,328 @@ function createXcscheme(options) {
|
|
|
4530
4636
|
};
|
|
4531
4637
|
}
|
|
4532
4638
|
//#endregion
|
|
4533
|
-
|
|
4639
|
+
//#region src/xcconfig/build.ts
|
|
4640
|
+
/**
|
|
4641
|
+
* Serializes a document back to `.xcconfig` text.
|
|
4642
|
+
*/
|
|
4643
|
+
function buildXcconfig(document) {
|
|
4644
|
+
let text = "";
|
|
4645
|
+
for (const statement of document.statements) text += statement.raw + statement.eol;
|
|
4646
|
+
return text;
|
|
4647
|
+
}
|
|
4648
|
+
//#endregion
|
|
4649
|
+
//#region src/xcconfig/parse.ts
|
|
4650
|
+
/**
|
|
4651
|
+
* Parser for Xcode build configuration files (`.xcconfig`).
|
|
4652
|
+
*
|
|
4653
|
+
* The grammar is line based. A line is blank, a `//` comment, an
|
|
4654
|
+
* `#include "path"` directive, or a `KEY[conditions] = value` assignment.
|
|
4655
|
+
* `//` starts a comment anywhere on a line, including inside values, which
|
|
4656
|
+
* matches Xcode's reading of the format. Every parsed statement keeps its
|
|
4657
|
+
* exact source text, so an untouched document rebuilds byte for byte.
|
|
4658
|
+
*
|
|
4659
|
+
* @module
|
|
4660
|
+
*/
|
|
4661
|
+
/**
|
|
4662
|
+
* Matches `#include "path"` and `#include? "path"`, with an optional
|
|
4663
|
+
* trailing comment.
|
|
4664
|
+
*/
|
|
4665
|
+
const INCLUDE_PATTERN = /^#include(\?)?\s*"([^"]*)"\s*(?:\/\/.*)?$/u;
|
|
4666
|
+
/**
|
|
4667
|
+
* Matches the head of an assignment: leading whitespace, the setting
|
|
4668
|
+
* name, and the raw conditions block up to the equals sign.
|
|
4669
|
+
*/
|
|
4670
|
+
const ASSIGNMENT_HEAD_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*((?:\[[^\]]*\])*)\s*=/u;
|
|
4671
|
+
/**
|
|
4672
|
+
* Matches one `[name=value]` condition group inside the conditions block.
|
|
4673
|
+
*/
|
|
4674
|
+
const CONDITION_PATTERN = /\[([^=\]]+)=([^\]]*)\]/gu;
|
|
4675
|
+
/**
|
|
4676
|
+
* Strips the trailing comment, surrounding whitespace, and one trailing
|
|
4677
|
+
* semicolon from an assignment's right-hand side. Xcode tolerates the
|
|
4678
|
+
* semicolon as a leftover from property-list habits and ignores it.
|
|
4679
|
+
*/
|
|
4680
|
+
function cleanValue(rightHandSide) {
|
|
4681
|
+
const commentStart = rightHandSide.indexOf("//");
|
|
4682
|
+
const trimmed = (commentStart === -1 ? rightHandSide : rightHandSide.slice(0, commentStart)).trim();
|
|
4683
|
+
return trimmed.endsWith(";") ? trimmed.slice(0, -1).trimEnd() : trimmed;
|
|
4684
|
+
}
|
|
4685
|
+
/**
|
|
4686
|
+
* Parses the conditions block of an assignment, for example
|
|
4687
|
+
* `[sdk=iphoneos*][arch=arm64]`.
|
|
4688
|
+
*
|
|
4689
|
+
* @throws XcconfigParseError when the block has content that is not a
|
|
4690
|
+
* well-formed `[name=value]` sequence.
|
|
4691
|
+
*/
|
|
4692
|
+
function parseConditions(block, source, blockOffset) {
|
|
4693
|
+
const conditions = [];
|
|
4694
|
+
let consumed = 0;
|
|
4695
|
+
for (const match of block.matchAll(CONDITION_PATTERN)) {
|
|
4696
|
+
if (match.index !== consumed) throw new XcconfigParseError("Malformed setting condition", source, blockOffset + consumed);
|
|
4697
|
+
conditions.push({
|
|
4698
|
+
name: match[1].trim(),
|
|
4699
|
+
value: match[2].trim()
|
|
4700
|
+
});
|
|
4701
|
+
consumed = match.index + match[0].length;
|
|
4702
|
+
}
|
|
4703
|
+
if (consumed !== block.length) throw new XcconfigParseError("Malformed setting condition", source, blockOffset + consumed);
|
|
4704
|
+
return conditions;
|
|
4705
|
+
}
|
|
4706
|
+
/**
|
|
4707
|
+
* Parses one line into a statement.
|
|
4708
|
+
*
|
|
4709
|
+
* @param content The line without its terminator.
|
|
4710
|
+
* @param source The full source text, for error positions.
|
|
4711
|
+
* @param lineOffset Offset of the line inside `source`.
|
|
4712
|
+
*/
|
|
4713
|
+
function parseLine(content, eol, source, lineOffset) {
|
|
4714
|
+
const trimmed = content.trim();
|
|
4715
|
+
if (trimmed === "") return {
|
|
4716
|
+
kind: "blank",
|
|
4717
|
+
raw: content,
|
|
4718
|
+
eol
|
|
4719
|
+
};
|
|
4720
|
+
if (trimmed.startsWith("//")) return {
|
|
4721
|
+
kind: "comment",
|
|
4722
|
+
text: trimmed.slice(2),
|
|
4723
|
+
raw: content,
|
|
4724
|
+
eol
|
|
4725
|
+
};
|
|
4726
|
+
if (trimmed.startsWith("#")) {
|
|
4727
|
+
const include = INCLUDE_PATTERN.exec(trimmed);
|
|
4728
|
+
if (include == null) throw new XcconfigParseError("Malformed #include directive", source, lineOffset + content.indexOf("#"));
|
|
4729
|
+
return {
|
|
4730
|
+
kind: "include",
|
|
4731
|
+
path: include[2],
|
|
4732
|
+
optional: include[1] === "?",
|
|
4733
|
+
raw: content,
|
|
4734
|
+
eol
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
const head = ASSIGNMENT_HEAD_PATTERN.exec(content);
|
|
4738
|
+
if (head == null) throw new XcconfigParseError("Expected a setting assignment, an #include directive, or a // comment", source, lineOffset + (content.length - content.trimStart().length));
|
|
4739
|
+
const conditionsBlock = head[2];
|
|
4740
|
+
const conditionsOffset = lineOffset + head[0].indexOf(conditionsBlock, head[1].length);
|
|
4741
|
+
const conditions = conditionsBlock === "" ? [] : parseConditions(conditionsBlock, source, conditionsOffset);
|
|
4742
|
+
return {
|
|
4743
|
+
kind: "assignment",
|
|
4744
|
+
key: head[1],
|
|
4745
|
+
conditions,
|
|
4746
|
+
value: cleanValue(content.slice(head[0].length)),
|
|
4747
|
+
raw: content,
|
|
4748
|
+
eol
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
/**
|
|
4752
|
+
* Parses `.xcconfig` text into its document form.
|
|
4753
|
+
*
|
|
4754
|
+
* @throws XcconfigParseError when a line is not a blank, a comment, an
|
|
4755
|
+
* include, or a well-formed assignment. The error carries the line and
|
|
4756
|
+
* column of the failure.
|
|
4757
|
+
*/
|
|
4758
|
+
function parseXcconfig(source) {
|
|
4759
|
+
if (source === "") return { statements: [] };
|
|
4760
|
+
const statements = [];
|
|
4761
|
+
let offset = 0;
|
|
4762
|
+
while (offset < source.length) {
|
|
4763
|
+
const lineFeed = source.indexOf("\n", offset);
|
|
4764
|
+
const end = lineFeed === -1 ? source.length : lineFeed;
|
|
4765
|
+
const hasCarriageReturn = end > offset && source.charCodeAt(end - 1) === 13;
|
|
4766
|
+
const content = source.slice(offset, hasCarriageReturn ? end - 1 : end);
|
|
4767
|
+
const eol = lineFeed === -1 ? "" : hasCarriageReturn ? "\r\n" : "\n";
|
|
4768
|
+
statements.push(parseLine(content, eol, source, offset));
|
|
4769
|
+
if (lineFeed === -1) break;
|
|
4770
|
+
offset = lineFeed + 1;
|
|
4771
|
+
}
|
|
4772
|
+
return { statements };
|
|
4773
|
+
}
|
|
4774
|
+
//#endregion
|
|
4775
|
+
//#region src/xcconfig/model.ts
|
|
4776
|
+
/**
|
|
4777
|
+
* Object model for Xcode build configuration files (`.xcconfig`).
|
|
4778
|
+
*
|
|
4779
|
+
* {@link Xcconfig} wraps a parsed document the way `XcodeProject` wraps a
|
|
4780
|
+
* pbxproj: the document stays the single source of truth, reads and
|
|
4781
|
+
* writes go through it, and {@link Xcconfig.build} emits it back with
|
|
4782
|
+
* untouched lines preserved byte for byte.
|
|
4783
|
+
*
|
|
4784
|
+
* @module
|
|
4785
|
+
*/
|
|
4786
|
+
/**
|
|
4787
|
+
* Whether a condition value matches a context value. A bare `*` matches
|
|
4788
|
+
* anything, a trailing `*` matches by prefix, and anything else matches
|
|
4789
|
+
* exactly.
|
|
4790
|
+
*/
|
|
4791
|
+
function matchesCondition(conditionValue, contextValue) {
|
|
4792
|
+
if (conditionValue === "*") return true;
|
|
4793
|
+
if (conditionValue.endsWith("*")) return contextValue.startsWith(conditionValue.slice(0, -1));
|
|
4794
|
+
return conditionValue === contextValue;
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Splices a prior value into `$(inherited)` and `${inherited}`
|
|
4798
|
+
* references. With no prior value the references stay literal, because
|
|
4799
|
+
* they then refer to layers below the file chain, which resolve later.
|
|
4800
|
+
*/
|
|
4801
|
+
function spliceInherited(value, prior) {
|
|
4802
|
+
if (prior == null) return value;
|
|
4803
|
+
return value.replaceAll("$(inherited)", prior).replaceAll("${inherited}", prior);
|
|
4804
|
+
}
|
|
4805
|
+
/**
|
|
4806
|
+
* A build configuration file with typed, mutable access to its settings.
|
|
4807
|
+
*
|
|
4808
|
+
* ```ts
|
|
4809
|
+
* const config = Xcconfig.parse(text);
|
|
4810
|
+
* config.get("PRODUCT_BUNDLE_IDENTIFIER");
|
|
4811
|
+
* config.set("MARKETING_VERSION", "1.2.0");
|
|
4812
|
+
* const updated = config.build();
|
|
4813
|
+
* ```
|
|
4814
|
+
*/
|
|
4815
|
+
var Xcconfig = class Xcconfig {
|
|
4816
|
+
/** The parsed document this model wraps. */
|
|
4817
|
+
document;
|
|
4818
|
+
constructor(document) {
|
|
4819
|
+
this.document = document;
|
|
4820
|
+
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Parses `.xcconfig` text and wraps it in a model.
|
|
4823
|
+
*
|
|
4824
|
+
* @throws XcconfigParseError when the text is malformed.
|
|
4825
|
+
*/
|
|
4826
|
+
static parse(text) {
|
|
4827
|
+
return new Xcconfig(parseXcconfig(text));
|
|
4828
|
+
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Creates an empty configuration file.
|
|
4831
|
+
*/
|
|
4832
|
+
static create() {
|
|
4833
|
+
return new Xcconfig({ statements: [] });
|
|
4834
|
+
}
|
|
4835
|
+
/**
|
|
4836
|
+
* Serializes the current document state to `.xcconfig` text.
|
|
4837
|
+
*/
|
|
4838
|
+
build() {
|
|
4839
|
+
return buildXcconfig(this.document);
|
|
4840
|
+
}
|
|
4841
|
+
/**
|
|
4842
|
+
* The names of the settings this file assigns unconditionally, in
|
|
4843
|
+
* first-assignment order. Conditional assignments such as
|
|
4844
|
+
* `KEY[sdk=iphoneos*]` are reachable through {@link assignments}.
|
|
4845
|
+
*/
|
|
4846
|
+
keys() {
|
|
4847
|
+
const keys = [];
|
|
4848
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.conditions.length === 0 && !keys.includes(statement.key)) keys.push(statement.key);
|
|
4849
|
+
return keys;
|
|
4850
|
+
}
|
|
4851
|
+
/**
|
|
4852
|
+
* Every assignment of this file, conditional ones included, in
|
|
4853
|
+
* document order. The items are the document's own nodes, not copies,
|
|
4854
|
+
* so treat them as read-only views. Writes belong on {@link set},
|
|
4855
|
+
* which keeps a statement's value and its source text in step.
|
|
4856
|
+
*/
|
|
4857
|
+
assignments() {
|
|
4858
|
+
return this.document.statements.filter((statement) => statement.kind === "assignment");
|
|
4859
|
+
}
|
|
4860
|
+
/**
|
|
4861
|
+
* The `#include` directives of this file, in document order. The items
|
|
4862
|
+
* are the document's own nodes, not copies, so treat them as read-only
|
|
4863
|
+
* views.
|
|
4864
|
+
*/
|
|
4865
|
+
includes() {
|
|
4866
|
+
return this.document.statements.filter((statement) => statement.kind === "include");
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* Reads the value of a setting from this file alone. When the key is
|
|
4870
|
+
* assigned more than once the last assignment wins, matching how Xcode
|
|
4871
|
+
* reads the file top to bottom. Conditional assignments are ignored.
|
|
4872
|
+
*/
|
|
4873
|
+
get(key) {
|
|
4874
|
+
let value;
|
|
4875
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.key === key && statement.conditions.length === 0) value = statement.value;
|
|
4876
|
+
return value;
|
|
4877
|
+
}
|
|
4878
|
+
/**
|
|
4879
|
+
* Writes a setting. The last unconditional assignment of the key is
|
|
4880
|
+
* rewritten in place as a canonical `KEY = value` line, replacing any
|
|
4881
|
+
* trailing comment the line carried. A key the file does not assign
|
|
4882
|
+
* yet is appended at the end, following the document's line-ending
|
|
4883
|
+
* convention.
|
|
4884
|
+
*/
|
|
4885
|
+
set(key, value) {
|
|
4886
|
+
let target;
|
|
4887
|
+
for (const statement of this.document.statements) if (statement.kind === "assignment" && statement.key === key && statement.conditions.length === 0) target = statement;
|
|
4888
|
+
if (target != null) {
|
|
4889
|
+
target.value = value;
|
|
4890
|
+
target.raw = `${key} = ${value}`;
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4893
|
+
const eol = this.document.statements.find((statement) => statement.eol !== "")?.eol ?? "\n";
|
|
4894
|
+
const last = this.document.statements.at(-1);
|
|
4895
|
+
if (last != null && last.eol === "") last.eol = eol;
|
|
4896
|
+
this.document.statements.push({
|
|
4897
|
+
kind: "assignment",
|
|
4898
|
+
key,
|
|
4899
|
+
conditions: [],
|
|
4900
|
+
value,
|
|
4901
|
+
raw: `${key} = ${value}`,
|
|
4902
|
+
eol
|
|
4903
|
+
});
|
|
4904
|
+
}
|
|
4905
|
+
/**
|
|
4906
|
+
* Removes every unconditional assignment of a setting.
|
|
4907
|
+
*
|
|
4908
|
+
* @returns True when at least one assignment was removed.
|
|
4909
|
+
*/
|
|
4910
|
+
remove(key) {
|
|
4911
|
+
const kept = this.document.statements.filter((statement) => statement.kind !== "assignment" || statement.key !== key || statement.conditions.length > 0);
|
|
4912
|
+
const removed = kept.length !== this.document.statements.length;
|
|
4913
|
+
this.document.statements = kept;
|
|
4914
|
+
return removed;
|
|
4915
|
+
}
|
|
4916
|
+
/**
|
|
4917
|
+
* Flattens the file into a settings dictionary the way Xcode reads it:
|
|
4918
|
+
* top to bottom with later assignments winning, and every `#include`
|
|
4919
|
+
* contributing its settings at the point of the directive, so lines
|
|
4920
|
+
* after an include override it.
|
|
4921
|
+
*
|
|
4922
|
+
* Conditional assignments apply when every condition matches
|
|
4923
|
+
* {@link XcconfigSettingsOptions.context}; without a context they are
|
|
4924
|
+
* skipped. `$(inherited)` references splice in the value accumulated
|
|
4925
|
+
* earlier in the chain, and stay literal when there is none, since
|
|
4926
|
+
* they then refer to layers below the file, which resolve later.
|
|
4927
|
+
*
|
|
4928
|
+
* Includes only take part when {@link XcconfigSettingsOptions.resolveInclude}
|
|
4929
|
+
* is provided, since the library never touches the filesystem itself.
|
|
4930
|
+
* A file included again later re-applies, exactly like pasting its text
|
|
4931
|
+
* a second time. Only re-entry while a file is still being expanded is
|
|
4932
|
+
* skipped, tracked by include path and by instance, so cyclic includes
|
|
4933
|
+
* terminate even when the resolver parses a fresh instance per call.
|
|
4934
|
+
*/
|
|
4935
|
+
settings(options = {}) {
|
|
4936
|
+
const merged = {};
|
|
4937
|
+
const pathStack = /* @__PURE__ */ new Set();
|
|
4938
|
+
const instanceStack = /* @__PURE__ */ new Set();
|
|
4939
|
+
const context = options.context;
|
|
4940
|
+
const applies = (statement) => statement.conditions.every((condition) => {
|
|
4941
|
+
const contextValue = condition.name === "sdk" || condition.name === "arch" || condition.name === "config" ? context?.[condition.name] : void 0;
|
|
4942
|
+
return contextValue != null && matchesCondition(condition.value, contextValue);
|
|
4943
|
+
});
|
|
4944
|
+
const visit = (config) => {
|
|
4945
|
+
if (instanceStack.has(config)) return;
|
|
4946
|
+
instanceStack.add(config);
|
|
4947
|
+
for (const statement of config.document.statements) if (statement.kind === "include") {
|
|
4948
|
+
if (pathStack.has(statement.path)) continue;
|
|
4949
|
+
const included = options.resolveInclude?.(statement.path, statement.optional);
|
|
4950
|
+
if (included != null) {
|
|
4951
|
+
pathStack.add(statement.path);
|
|
4952
|
+
visit(included);
|
|
4953
|
+
pathStack.delete(statement.path);
|
|
4954
|
+
}
|
|
4955
|
+
} else if (statement.kind === "assignment" && applies(statement)) merged[statement.key] = spliceInherited(statement.value, merged[statement.key]);
|
|
4956
|
+
instanceStack.delete(config);
|
|
4957
|
+
};
|
|
4958
|
+
visit(this);
|
|
4959
|
+
return merged;
|
|
4960
|
+
}
|
|
4961
|
+
};
|
|
4962
|
+
//#endregion
|
|
4963
|
+
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 };
|
package/package.json
CHANGED