@invarn/cibuild 2.7.9 → 2.8.1
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/dist/cli.cjs +143 -10
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +2 -0
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +1 -0
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +218 -0
- package/dist/src/commands/android-java-version.test.js +93 -10
- package/dist/src/commands/android-scanner.d.ts +109 -8
- package/dist/src/commands/android-scanner.d.ts.map +1 -1
- package/dist/src/commands/android-scanner.js +397 -27
- package/dist/src/commands/build.d.ts +8 -0
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +17 -3
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +3 -0
- package/dist/src/commands/ios-scanner.d.ts.map +1 -1
- package/dist/src/commands/ios-scanner.js +15 -24
- package/dist/src/commands/ios-scheme-ranking.test.js +5 -0
- package/dist/src/shared/detect-project.d.ts +32 -0
- package/dist/src/shared/detect-project.d.ts.map +1 -1
- package/dist/src/shared/detect-project.js +62 -10
- package/dist/src/shared/xcode-container.test.d.ts +2 -0
- package/dist/src/shared/xcode-container.test.d.ts.map +1 -0
- package/dist/src/shared/xcode-container.test.js +123 -0
- package/dist/src/yaml/steps/xcode-app-product.d.ts +90 -0
- package/dist/src/yaml/steps/xcode-app-product.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode-app-product.js +247 -0
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts +2 -0
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode-app-product.test.js +202 -0
- package/dist/src/yaml/steps/xcode-derived-data.test.js +66 -1
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +50 -3
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve, relative } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { xcodeContainersIn } from "../shared/detect-project.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// File discovery
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -18,17 +19,16 @@ function relPath(root, filePath) {
|
|
|
18
19
|
* Finds the primary Xcode project path.
|
|
19
20
|
* Prefers .xcworkspace over .xcodeproj (CocoaPods projects use workspace).
|
|
20
21
|
* Returns the relative path from root, or empty string if not found.
|
|
22
|
+
*
|
|
23
|
+
* A candidate must be a real container — `isXcodeContainer` — and not merely
|
|
24
|
+
* a directory with the right suffix. `Uwi0/Oakane` ships `iosApp.xcodeproj`
|
|
25
|
+
* with no `project.pbxproj` beside the `oakane.xcodeproj` that has one, and
|
|
26
|
+
* this function returned the husk because it was first.
|
|
21
27
|
*/
|
|
22
28
|
function findXcodeProjectPath(root) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return "";
|
|
29
|
-
}
|
|
30
|
-
const workspaces = entries.filter((e) => e.endsWith(".xcworkspace"));
|
|
31
|
-
const projects = entries.filter((e) => e.endsWith(".xcodeproj"));
|
|
29
|
+
const containers = xcodeContainersIn(root);
|
|
30
|
+
const workspaces = containers.filter((e) => e.endsWith(".xcworkspace"));
|
|
31
|
+
const projects = containers.filter((e) => e.endsWith(".xcodeproj"));
|
|
32
32
|
// Prefer workspace (CocoaPods / multi-package setups use these)
|
|
33
33
|
if (workspaces.length > 0)
|
|
34
34
|
return workspaces[0];
|
|
@@ -43,14 +43,7 @@ function findXcodeProjectPath(root) {
|
|
|
43
43
|
*/
|
|
44
44
|
const COMPANION_SCHEME = /(extension|widget|clip|intents?|notification|tvos|macos|watchos|visionos|tests?|screenshots?|staging|prototype|codegen)/i;
|
|
45
45
|
function readSchemeFacts(root, name) {
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
entries = readdirSync(root);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
for (const entry of entries) {
|
|
46
|
+
for (const entry of xcodeContainersIn(root)) {
|
|
54
47
|
if (!entry.endsWith(".xcodeproj"))
|
|
55
48
|
continue;
|
|
56
49
|
const path = resolve(root, entry, "xcshareddata", "xcschemes", `${name}.xcscheme`);
|
|
@@ -194,13 +187,11 @@ export function rankSchemesWithReason(root, projectPath, schemes) {
|
|
|
194
187
|
*/
|
|
195
188
|
function detectSchemes(root) {
|
|
196
189
|
const schemes = new Set();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return [];
|
|
203
|
-
}
|
|
190
|
+
// Real projects only. The fallback below invents a scheme from the
|
|
191
|
+
// directory's name, so a husk with no shared schemes contributes a scheme
|
|
192
|
+
// named after a project that cannot be opened — which is how `Uwi0/Oakane`
|
|
193
|
+
// got `IOS_SCHEME: iosApp` as well as `IOS_PROJECT_PATH: iosApp.xcodeproj`.
|
|
194
|
+
const entries = xcodeContainersIn(root);
|
|
204
195
|
for (const entry of entries) {
|
|
205
196
|
if (!entry.endsWith(".xcodeproj"))
|
|
206
197
|
continue;
|
|
@@ -30,6 +30,11 @@ afterEach(() => {
|
|
|
30
30
|
function scheme(project, name, { runnable, testable, appExtension }) {
|
|
31
31
|
const dir = join(root, `${project}.xcodeproj`, "xcshareddata", "xcschemes");
|
|
32
32
|
mkdirSync(dir, { recursive: true });
|
|
33
|
+
// A `.xcodeproj` is its `project.pbxproj`; without one Xcode refuses to open
|
|
34
|
+
// it and the scanners now skip it, so a fixture that omits it is describing
|
|
35
|
+
// a directory that cannot exist outside a test. Content is irrelevant here —
|
|
36
|
+
// the scheme XML is what the ranking reads — but it has to be there.
|
|
37
|
+
writeFileSync(join(root, `${project}.xcodeproj`, "project.pbxproj"), "// !$*UTF8*$!\n");
|
|
33
38
|
writeFileSync(join(dir, `${name}.xcscheme`), `<?xml version="1.0" encoding="UTF-8"?>
|
|
34
39
|
<Scheme LastUpgradeVersion = "1600"${appExtension ? `\n wasCreatedForAppExtension = "YES"` : ""} version = "1.7">
|
|
35
40
|
<TestAction buildConfiguration = "Debug">
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
export type MobileProjectType = "android" | "ios" | "kmm";
|
|
2
2
|
declare function isDirectory(path: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* True when a path that is *named* like an Xcode container actually is one.
|
|
5
|
+
*
|
|
6
|
+
* **The** predicate — every question of the form "is this `.xcodeproj` a
|
|
7
|
+
* project" goes through here, and nothing else in cibuild joins a directory
|
|
8
|
+
* name to "there is a project there".
|
|
9
|
+
*
|
|
10
|
+
* A `.xcodeproj` is a directory whose whole content is `project.pbxproj`;
|
|
11
|
+
* without it `xcodebuild` refuses to open the project at all — "missing its
|
|
12
|
+
* project.pbxproj file". A `.xcworkspace` is `contents.xcworkspacedata` in
|
|
13
|
+
* the same way. Anything else ending in those suffixes is a directory with a
|
|
14
|
+
* suggestive name, and offering one is offering something Xcode would reject.
|
|
15
|
+
*
|
|
16
|
+
* `Uwi0/Oakane` is the row that found it. Its `iosApp/` ships two: a tracked
|
|
17
|
+
* `iosApp.xcodeproj` holding nothing but `project.xcworkspace/`, and the real
|
|
18
|
+
* `oakane.xcodeproj` beside it with a 20 217-byte `project.pbxproj`. The husk
|
|
19
|
+
* carries the name the KMM wizard gives, so the conventional name outranked
|
|
20
|
+
* the project that exists — and it supplied BOTH `IOS_PROJECT_PATH` and, via
|
|
21
|
+
* the scheme scan's project-name fallback, `IOS_SCHEME`. The build reached
|
|
22
|
+
* step 9 and died on "Unable to read project 'iosApp.xcodeproj'".
|
|
23
|
+
*
|
|
24
|
+
* This is the iOS twin of the Gradle module scan once treating "a directory
|
|
25
|
+
* holding a file called build.gradle" as a module: wrong in the same
|
|
26
|
+
* direction, and fixed the same way — one reader, used by everyone.
|
|
27
|
+
*
|
|
28
|
+
* The `.pbxproj` is deliberately NOT parsed. Its existence and non-emptiness
|
|
29
|
+
* is the whole question; a shared `.xcscheme` remains the oracle for what a
|
|
30
|
+
* project builds.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isXcodeContainer(path: string): boolean;
|
|
33
|
+
/** The entries of `dir` that are real Xcode containers, in `readdir` order. */
|
|
34
|
+
export declare function xcodeContainersIn(dir: string): string[];
|
|
3
35
|
/**
|
|
4
36
|
* Detects whether the given directory is the root of an Android, iOS, or
|
|
5
37
|
* KMM project. Returns the detected project type, or null if none match.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../../src/shared/detect-project.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAoB1D,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM1C;
|
|
1
|
+
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../../src/shared/detect-project.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAoB1D,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM1C;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYtD;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAYvD;AAuED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA4B7E;AAED,oEAAoE;AACpE,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -33,6 +33,62 @@ function readOrNull(path) {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* True when a path that is *named* like an Xcode container actually is one.
|
|
38
|
+
*
|
|
39
|
+
* **The** predicate — every question of the form "is this `.xcodeproj` a
|
|
40
|
+
* project" goes through here, and nothing else in cibuild joins a directory
|
|
41
|
+
* name to "there is a project there".
|
|
42
|
+
*
|
|
43
|
+
* A `.xcodeproj` is a directory whose whole content is `project.pbxproj`;
|
|
44
|
+
* without it `xcodebuild` refuses to open the project at all — "missing its
|
|
45
|
+
* project.pbxproj file". A `.xcworkspace` is `contents.xcworkspacedata` in
|
|
46
|
+
* the same way. Anything else ending in those suffixes is a directory with a
|
|
47
|
+
* suggestive name, and offering one is offering something Xcode would reject.
|
|
48
|
+
*
|
|
49
|
+
* `Uwi0/Oakane` is the row that found it. Its `iosApp/` ships two: a tracked
|
|
50
|
+
* `iosApp.xcodeproj` holding nothing but `project.xcworkspace/`, and the real
|
|
51
|
+
* `oakane.xcodeproj` beside it with a 20 217-byte `project.pbxproj`. The husk
|
|
52
|
+
* carries the name the KMM wizard gives, so the conventional name outranked
|
|
53
|
+
* the project that exists — and it supplied BOTH `IOS_PROJECT_PATH` and, via
|
|
54
|
+
* the scheme scan's project-name fallback, `IOS_SCHEME`. The build reached
|
|
55
|
+
* step 9 and died on "Unable to read project 'iosApp.xcodeproj'".
|
|
56
|
+
*
|
|
57
|
+
* This is the iOS twin of the Gradle module scan once treating "a directory
|
|
58
|
+
* holding a file called build.gradle" as a module: wrong in the same
|
|
59
|
+
* direction, and fixed the same way — one reader, used by everyone.
|
|
60
|
+
*
|
|
61
|
+
* The `.pbxproj` is deliberately NOT parsed. Its existence and non-emptiness
|
|
62
|
+
* is the whole question; a shared `.xcscheme` remains the oracle for what a
|
|
63
|
+
* project builds.
|
|
64
|
+
*/
|
|
65
|
+
export function isXcodeContainer(path) {
|
|
66
|
+
const manifest = path.endsWith(".xcworkspace")
|
|
67
|
+
? "contents.xcworkspacedata"
|
|
68
|
+
: path.endsWith(".xcodeproj")
|
|
69
|
+
? "project.pbxproj"
|
|
70
|
+
: null;
|
|
71
|
+
if (manifest === null)
|
|
72
|
+
return false;
|
|
73
|
+
try {
|
|
74
|
+
return statSync(resolve(path, manifest)).size > 0;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** The entries of `dir` that are real Xcode containers, in `readdir` order. */
|
|
81
|
+
export function xcodeContainersIn(dir) {
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = readdirSync(dir);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
return entries.filter((e) => (e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace")) &&
|
|
90
|
+
isXcodeContainer(resolve(dir, e)));
|
|
91
|
+
}
|
|
36
92
|
/** True when this directory holds something an Apple build reads. */
|
|
37
93
|
function holdsAppleConsumer(dir) {
|
|
38
94
|
let entries;
|
|
@@ -42,7 +98,7 @@ function holdsAppleConsumer(dir) {
|
|
|
42
98
|
catch {
|
|
43
99
|
return false;
|
|
44
100
|
}
|
|
45
|
-
if (
|
|
101
|
+
if (xcodeContainersIn(dir).length > 0) {
|
|
46
102
|
return true;
|
|
47
103
|
}
|
|
48
104
|
if (entries.includes("Podfile"))
|
|
@@ -144,15 +200,11 @@ export function detectMobileProjectRoot(dir) {
|
|
|
144
200
|
const iosFileIndicators = ["Podfile"];
|
|
145
201
|
const hasIosFile = iosFileIndicators.some((f) => existsSync(resolve(dir, f)));
|
|
146
202
|
if (!hasIosFile) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
// Unreadable directory — fall through.
|
|
155
|
-
}
|
|
203
|
+
// A husk — a directory named `*.xcodeproj` with no `project.pbxproj` —
|
|
204
|
+
// does not make this an iOS project root. Saying `ios` on the strength of
|
|
205
|
+
// one writes a pipeline aimed at something xcodebuild cannot open.
|
|
206
|
+
if (xcodeContainersIn(dir).length > 0)
|
|
207
|
+
return "ios";
|
|
156
208
|
}
|
|
157
209
|
else {
|
|
158
210
|
return "ios";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcode-container.test.d.ts","sourceRoot":"","sources":["../../../src/shared/xcode-container.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `.xcodeproj` with no `project.pbxproj` is not a project.
|
|
3
|
+
*
|
|
4
|
+
* `Uwi0/Oakane` is the row. Its `iosApp/` ships two Xcode projects: a tracked
|
|
5
|
+
* `iosApp.xcodeproj` holding nothing but `project.xcworkspace/`, and the real
|
|
6
|
+
* `oakane.xcodeproj` beside it. The scan chose the husk — `iosApp` is the name
|
|
7
|
+
* the KMM wizard gives, so the conventional name outranked the project that
|
|
8
|
+
* exists — and the build died at step 9 on
|
|
9
|
+
*
|
|
10
|
+
* xcodebuild: error: Unable to read project 'iosApp.xcodeproj' …
|
|
11
|
+
* Reason: … missing its project.pbxproj file.
|
|
12
|
+
*
|
|
13
|
+
* The husk supplied `IOS_SCHEME` too: with no shared schemes anywhere in that
|
|
14
|
+
* repository, `detectSchemes` falls back to each project's own directory name,
|
|
15
|
+
* so the husk contributed the scheme `iosApp` as well as the project path.
|
|
16
|
+
*
|
|
17
|
+
* This is the iOS twin of the Gradle module scan once treating "a directory
|
|
18
|
+
* holding a file called build.gradle" as a module, and it is fixed the same
|
|
19
|
+
* way: one reader, used by everyone.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { isXcodeContainer, xcodeContainersIn, detectMobileProjectRoot } from "./detect-project.js";
|
|
25
|
+
import { scanIosProject } from "../commands/ios-scanner.js";
|
|
26
|
+
let root;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
root = mkdtempSync(join(tmpdir(), "cibuild-husk-"));
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
rmSync(root, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
/** A project Xcode could open. */
|
|
34
|
+
function project(name) {
|
|
35
|
+
mkdirSync(join(root, `${name}.xcodeproj`), { recursive: true });
|
|
36
|
+
writeFileSync(join(root, `${name}.xcodeproj`, "project.pbxproj"), "// !$*UTF8*$!\n");
|
|
37
|
+
}
|
|
38
|
+
/** Oakane's shape: the name, the inner workspace, and no project at all. */
|
|
39
|
+
function husk(name) {
|
|
40
|
+
mkdirSync(join(root, `${name}.xcodeproj`, "project.xcworkspace"), { recursive: true });
|
|
41
|
+
writeFileSync(join(root, `${name}.xcodeproj`, "project.xcworkspace", "contents.xcworkspacedata"), '<?xml version="1.0" encoding="UTF-8"?>\n<Workspace version = "1.0"></Workspace>\n');
|
|
42
|
+
}
|
|
43
|
+
function workspace(name) {
|
|
44
|
+
mkdirSync(join(root, `${name}.xcworkspace`), { recursive: true });
|
|
45
|
+
writeFileSync(join(root, `${name}.xcworkspace`, "contents.xcworkspacedata"), '<?xml version="1.0" encoding="UTF-8"?>\n<Workspace version = "1.0"></Workspace>\n');
|
|
46
|
+
}
|
|
47
|
+
describe("isXcodeContainer", () => {
|
|
48
|
+
test("a project with a project.pbxproj is one", () => {
|
|
49
|
+
project("Real");
|
|
50
|
+
expect(isXcodeContainer(join(root, "Real.xcodeproj"))).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
test("a project with no project.pbxproj is not", () => {
|
|
53
|
+
husk("iosApp");
|
|
54
|
+
expect(isXcodeContainer(join(root, "iosApp.xcodeproj"))).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
test("an empty project.pbxproj is not a project either", () => {
|
|
57
|
+
mkdirSync(join(root, "Empty.xcodeproj"), { recursive: true });
|
|
58
|
+
writeFileSync(join(root, "Empty.xcodeproj", "project.pbxproj"), "");
|
|
59
|
+
expect(isXcodeContainer(join(root, "Empty.xcodeproj"))).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
test("a workspace needs its contents.xcworkspacedata", () => {
|
|
62
|
+
workspace("Real");
|
|
63
|
+
mkdirSync(join(root, "Husk.xcworkspace"), { recursive: true });
|
|
64
|
+
expect(isXcodeContainer(join(root, "Real.xcworkspace"))).toBe(true);
|
|
65
|
+
expect(isXcodeContainer(join(root, "Husk.xcworkspace"))).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
test("a path that is not named like a container is never one", () => {
|
|
68
|
+
mkdirSync(join(root, "Sources"), { recursive: true });
|
|
69
|
+
expect(isXcodeContainer(join(root, "Sources"))).toBe(false);
|
|
70
|
+
expect(isXcodeContainer(join(root, "does-not-exist"))).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("xcodeContainersIn", () => {
|
|
74
|
+
test("keeps the real projects and drops the husks", () => {
|
|
75
|
+
husk("iosApp");
|
|
76
|
+
project("oakane");
|
|
77
|
+
expect(xcodeContainersIn(root)).toEqual(["oakane.xcodeproj"]);
|
|
78
|
+
});
|
|
79
|
+
test("an unreadable directory is empty, not a throw", () => {
|
|
80
|
+
expect(xcodeContainersIn(join(root, "nowhere"))).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("detectMobileProjectRoot", () => {
|
|
84
|
+
test("a husk alone is not an iOS project root", () => {
|
|
85
|
+
husk("iosApp");
|
|
86
|
+
expect(detectMobileProjectRoot(root)).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
test("a real project beside a husk still is", () => {
|
|
89
|
+
husk("iosApp");
|
|
90
|
+
project("oakane");
|
|
91
|
+
expect(detectMobileProjectRoot(root)).toBe("ios");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("scanIosProject", () => {
|
|
95
|
+
test("Oakane's shape resolves the project that exists, and its scheme", async () => {
|
|
96
|
+
husk("iosApp");
|
|
97
|
+
project("oakane");
|
|
98
|
+
const scan = await scanIosProject(root);
|
|
99
|
+
expect(scan.projectPath).toBe("oakane.xcodeproj");
|
|
100
|
+
// No shared schemes anywhere, so each project falls back to its own name —
|
|
101
|
+
// and only one of them is a project.
|
|
102
|
+
expect(scan.detectedSchemes).toEqual(["oakane"]);
|
|
103
|
+
});
|
|
104
|
+
test("a repository whose only project is a husk resolves nothing", async () => {
|
|
105
|
+
husk("iosApp");
|
|
106
|
+
const scan = await scanIosProject(root);
|
|
107
|
+
expect(scan.projectPath).toBe("");
|
|
108
|
+
expect(scan.detectedSchemes).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
test("a single valid project is unchanged", async () => {
|
|
111
|
+
project("Contributions");
|
|
112
|
+
const scan = await scanIosProject(root);
|
|
113
|
+
expect(scan.projectPath).toBe("Contributions.xcodeproj");
|
|
114
|
+
expect(scan.detectedSchemes).toEqual(["Contributions"]);
|
|
115
|
+
});
|
|
116
|
+
test("a workspace still outranks a project beside it", async () => {
|
|
117
|
+
project("QiniuSDK");
|
|
118
|
+
workspace("QiniuSDK");
|
|
119
|
+
const scan = await scanIosProject(root);
|
|
120
|
+
expect(scan.projectPath).toBe("QiniuSDK.xcworkspace");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=xcode-container.test.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which built `.app` belongs to the scheme that was built.
|
|
3
|
+
*
|
|
4
|
+
* A simulator build leaves every application bundle its graph produced under
|
|
5
|
+
* `DerivedData/Build/Products/<configuration>-<platform>/`, and a scheme that
|
|
6
|
+
* builds a watchOS or tvOS companion alongside the app produces two. Taking
|
|
7
|
+
* whichever one a directory scan reaches first is a coin toss, and it lands the
|
|
8
|
+
* wrong way often enough to matter: a project whose scheme builds both an iOS
|
|
9
|
+
* app and a watch companion exported the 588 KB watch bundle as the app under
|
|
10
|
+
* test. The build was green, the artifact manifest was non-empty, and the
|
|
11
|
+
* thing a user downloaded could not be installed on the device they built for.
|
|
12
|
+
* That is worse than delivering nothing, because nothing is visibly wrong and
|
|
13
|
+
* a plausible wrong answer is not.
|
|
14
|
+
*
|
|
15
|
+
* The scheme already knows. A shared `.xcscheme` lists its build entries with a
|
|
16
|
+
* `BuildableName` whose extension IS the product type, and `buildForRunning`
|
|
17
|
+
* separates what the scheme builds from what it merely references — a scheme
|
|
18
|
+
* may list several `.app` entries it does not build, so "mentions an app" is
|
|
19
|
+
* not the question and a check that asked it would pick just as badly.
|
|
20
|
+
*
|
|
21
|
+
* Where the scheme gives no answer this falls back rather than failing. A
|
|
22
|
+
* project with no shared scheme at all is ordinary — plenty of them never
|
|
23
|
+
* commit one — and those builds work. Absence of evidence is not evidence, so
|
|
24
|
+
* the destination's own platform directory decides, and failing to resolve an
|
|
25
|
+
* app is left to the caller's existing guard.
|
|
26
|
+
*
|
|
27
|
+
* The whole resolver runs on the machine that did the build, against the
|
|
28
|
+
* products that build actually produced, so it needs nothing from the step
|
|
29
|
+
* generator but the scheme name and the destination.
|
|
30
|
+
*/
|
|
31
|
+
/** A built application bundle, as found under `Build/Products`. */
|
|
32
|
+
export interface BuiltApp {
|
|
33
|
+
/** The `<configuration>-<platform>` directory, or `''` directly under Products. */
|
|
34
|
+
platform: string;
|
|
35
|
+
/** The bundle's own name, e.g. `MyApp.app`. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Absolute path to the bundle. */
|
|
38
|
+
path: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The SDK suffix the Products directory carries for a given destination.
|
|
42
|
+
*
|
|
43
|
+
* `-destination 'generic/platform=iOS Simulator'` builds into
|
|
44
|
+
* `Debug-iphonesimulator`; the watchOS and tvOS spellings are the ones that
|
|
45
|
+
* matter here, because a companion target is exactly what produces the second
|
|
46
|
+
* bundle this resolver exists to skip past.
|
|
47
|
+
*/
|
|
48
|
+
export declare function platformSuffixForDestination(destination: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The application bundles a scheme's build action actually builds.
|
|
51
|
+
*
|
|
52
|
+
* `buildForRunning="NO"` entries are listed and not built. A scheme can carry
|
|
53
|
+
* three `.app` entries that are API-compatibility testers in other containers
|
|
54
|
+
* beside the one framework it really produces, so the attribute is the whole
|
|
55
|
+
* filter rather than a refinement of one.
|
|
56
|
+
*
|
|
57
|
+
* Returns an empty list for a file that cannot be read or carries no build
|
|
58
|
+
* action — no evidence, which the caller must not read as "builds no app".
|
|
59
|
+
*/
|
|
60
|
+
export declare function runnableAppProducts(schemeXml: string | null): string[];
|
|
61
|
+
/**
|
|
62
|
+
* The bundle to export, given everything that was built and what the scheme
|
|
63
|
+
* says it builds.
|
|
64
|
+
*
|
|
65
|
+
* In order: the scheme's own product on the destination's platform, the
|
|
66
|
+
* scheme's own product anywhere, anything on the destination's platform,
|
|
67
|
+
* anything at all. The last two are the no-shared-scheme path, and they are
|
|
68
|
+
* what keeps a project that never committed a scheme building exactly as it
|
|
69
|
+
* did before.
|
|
70
|
+
*/
|
|
71
|
+
export declare function chooseAppProduct(apps: BuiltApp[], schemeProducts: string[], destination: string): BuiltApp | null;
|
|
72
|
+
/**
|
|
73
|
+
* The resolver as it runs on the build machine: dependency-free CommonJS,
|
|
74
|
+
* written to a temp file and invoked with
|
|
75
|
+
* `node <file> <productsDir> <projectPath> <scheme> <destination>`.
|
|
76
|
+
*
|
|
77
|
+
* It prints the chosen bundle's path and nothing else, and exits non-zero with
|
|
78
|
+
* no output when it cannot choose — which is the signal for the caller's
|
|
79
|
+
* existing scan to take over, so a machine without a usable node is no worse
|
|
80
|
+
* off than before this existed.
|
|
81
|
+
*
|
|
82
|
+
* Kept as source text rather than bundled because the step ships a shell
|
|
83
|
+
* script, not a module graph. The three functions above are the same rules in
|
|
84
|
+
* testable form, and `xcode-app-product.test.ts` holds them to each other.
|
|
85
|
+
*
|
|
86
|
+
* Must contain no backtick and no dollar-brace: it is emitted into a bash
|
|
87
|
+
* heredoc, and it is a TypeScript template literal on the way there.
|
|
88
|
+
*/
|
|
89
|
+
export declare const APP_PRODUCT_RESOLVER_SOURCE: string;
|
|
90
|
+
//# sourceMappingURL=xcode-app-product.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcode-app-product.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode-app-product.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,mEAAmE;AACnE,MAAM,WAAW,QAAQ;IACvB,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAatE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,EAAE,EAChB,cAAc,EAAE,MAAM,EAAE,EACxB,WAAW,EAAE,MAAM,GAClB,QAAQ,GAAG,IAAI,CAOjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAqIzC,CAAC"}
|