@invarn/cibuild 2.8.3 → 2.8.5
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 +11 -11
- package/dist/src/yaml/steps/android-variant-resolution.test.js +144 -1
- package/dist/src/yaml/steps/android.d.ts +8 -0
- package/dist/src/yaml/steps/android.d.ts.map +1 -1
- package/dist/src/yaml/steps/android.js +36 -11
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts +17 -0
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.js +145 -0
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +1 -1
- package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -1
- package/dist/src/yaml/steps/ios-deps.js +62 -10
- package/dist/src/yaml/steps/xcode-derived-data.test.js +70 -2
- package/dist/src/yaml/steps/xcode-new.test.js +1 -1
- package/dist/src/yaml/steps/xcode-resolve-retry.test.js +2 -2
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +40 -11
- package/package.json +1 -1
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
* not re-implemented.
|
|
16
16
|
*/
|
|
17
17
|
import { execFileSync } from "node:child_process";
|
|
18
|
-
import {
|
|
18
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { AndroidBuildForUITestingStepExecutor, AndroidLintStepExecutor, AndroidUnitTestStepExecutor, GradleBuildStepExecutor, PICK_VARIANT_TASK_FUNCTION, gradleProjectDir, gradleTaskPath, variantTaskCommands, } from "./android.js";
|
|
22
|
+
import { testConfig } from "./test-config.js";
|
|
19
23
|
/** Run the shipped shell function over some Gradle output. */
|
|
20
24
|
function pick(gradleOutput, prefix, suffix, buildType) {
|
|
21
25
|
const script = `${PICK_VARIANT_TASK_FUNCTION}\ncibuild_pick_variant_task "${prefix}" "${suffix}" "${buildType}"`;
|
|
@@ -114,4 +118,143 @@ describe("the step that uses it", () => {
|
|
|
114
118
|
expect(script).toContain('exit "$VARIANT_STATUS"');
|
|
115
119
|
});
|
|
116
120
|
});
|
|
121
|
+
/**
|
|
122
|
+
* An app whose root project is the application: no `include`, the Android
|
|
123
|
+
* application plugin applied in the root build file, sources in `src/`.
|
|
124
|
+
* Gradle's path for that project is `:`, so a pipeline says `module: ":"`.
|
|
125
|
+
*/
|
|
126
|
+
describe("a module that names the root project", () => {
|
|
127
|
+
const escape = (value) => value;
|
|
128
|
+
test("runs the root project's own task, with no empty module segment", () => {
|
|
129
|
+
const script = variantTaskCommands({
|
|
130
|
+
module: ":",
|
|
131
|
+
variant: "debug",
|
|
132
|
+
prefix: "test",
|
|
133
|
+
suffix: "UnitTest",
|
|
134
|
+
escape,
|
|
135
|
+
}).join("\n");
|
|
136
|
+
expect(script).toContain("$GRADLE_CMD :testDebugUnitTest");
|
|
137
|
+
expect(script).not.toContain("::");
|
|
138
|
+
});
|
|
139
|
+
test("retries the resolved task on the root project too", () => {
|
|
140
|
+
const script = variantTaskCommands({
|
|
141
|
+
module: ":",
|
|
142
|
+
variant: "debug",
|
|
143
|
+
prefix: "lint",
|
|
144
|
+
suffix: "",
|
|
145
|
+
escape,
|
|
146
|
+
}).join("\n");
|
|
147
|
+
expect(script).toContain("$GRADLE_CMD :lintDebug");
|
|
148
|
+
expect(script).toContain("$GRADLE_CMD :$RESOLVED_TASK");
|
|
149
|
+
expect(script).toContain('echo "Using :$RESOLVED_TASK');
|
|
150
|
+
expect(script).not.toContain("::");
|
|
151
|
+
});
|
|
152
|
+
// An explicit empty string reaches the step as is; it must not become
|
|
153
|
+
// `::testDebugUnitTest`.
|
|
154
|
+
test("treats an empty module as the root", () => {
|
|
155
|
+
const script = variantTaskCommands({
|
|
156
|
+
module: "",
|
|
157
|
+
variant: "debug",
|
|
158
|
+
prefix: "test",
|
|
159
|
+
suffix: "UnitTest",
|
|
160
|
+
escape,
|
|
161
|
+
}).join("\n");
|
|
162
|
+
expect(script).toContain("$GRADLE_CMD :testDebugUnitTest");
|
|
163
|
+
expect(script).not.toContain("::");
|
|
164
|
+
});
|
|
165
|
+
test("leaves a named module exactly as it was", () => {
|
|
166
|
+
const options = { variant: "debug", prefix: "test", suffix: "UnitTest", escape };
|
|
167
|
+
const script = variantTaskCommands({ module: "app", ...options }).join("\n");
|
|
168
|
+
expect(script).toContain("$GRADLE_CMD :app:testDebugUnitTest 2>&1");
|
|
169
|
+
expect(script).toContain(" echo \"Using :app:$RESOLVED_TASK — resolved VARIANT=$RESOLVED_VARIANT\"");
|
|
170
|
+
expect(script).toContain(" $GRADLE_CMD :app:$RESOLVED_TASK");
|
|
171
|
+
});
|
|
172
|
+
test("keeps a nested module's path", () => {
|
|
173
|
+
const script = variantTaskCommands({
|
|
174
|
+
module: "feature:store",
|
|
175
|
+
variant: "debug",
|
|
176
|
+
prefix: "lint",
|
|
177
|
+
suffix: "",
|
|
178
|
+
escape,
|
|
179
|
+
}).join("\n");
|
|
180
|
+
expect(script).toContain("$GRADLE_CMD :feature:store:lintDebug");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe("gradleTaskPath and gradleProjectDir", () => {
|
|
184
|
+
test("compose a task path", () => {
|
|
185
|
+
expect(gradleTaskPath(":", "lintDebug")).toBe(":lintDebug");
|
|
186
|
+
expect(gradleTaskPath("", "lintDebug")).toBe(":lintDebug");
|
|
187
|
+
expect(gradleTaskPath("app", "lintDebug")).toBe(":app:lintDebug");
|
|
188
|
+
});
|
|
189
|
+
test("compose the project directory, empty for the root", () => {
|
|
190
|
+
expect(gradleProjectDir(":")).toBe("");
|
|
191
|
+
expect(gradleProjectDir("")).toBe("");
|
|
192
|
+
expect(gradleProjectDir("app")).toBe("app/");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe("the steps, for the root project", () => {
|
|
196
|
+
test("lint runs :lintDebug and reports under build/", async () => {
|
|
197
|
+
const result = await new AndroidLintStepExecutor().execute({ module: ":" }, {}, testConfig);
|
|
198
|
+
const script = result.script;
|
|
199
|
+
expect(script).toContain("$GRADLE_CMD :lintDebug");
|
|
200
|
+
expect(script).not.toContain("::");
|
|
201
|
+
expect(script).toContain('echo "Lint report location: build/reports/lint-results-debug.html"');
|
|
202
|
+
expect(script).toContain('echo "Running lint on the root project"');
|
|
203
|
+
});
|
|
204
|
+
test("unit tests run :testDebugUnitTest and report under build/", async () => {
|
|
205
|
+
const result = await new AndroidUnitTestStepExecutor().execute({ module: ":" }, {}, testConfig);
|
|
206
|
+
const script = result.script;
|
|
207
|
+
expect(script).toContain("$GRADLE_CMD :testDebugUnitTest");
|
|
208
|
+
expect(script).not.toContain("::");
|
|
209
|
+
expect(script).toContain('echo "Test report location: build/reports/tests/testDebugUnitTest/index.html"');
|
|
210
|
+
});
|
|
211
|
+
test("an absent module still means app", async () => {
|
|
212
|
+
const lint = await new AndroidLintStepExecutor().execute({}, {}, testConfig);
|
|
213
|
+
const unit = await new AndroidUnitTestStepExecutor().execute({}, {}, testConfig);
|
|
214
|
+
expect(lint.script).toContain("$GRADLE_CMD :app:lintDebug");
|
|
215
|
+
expect(lint.script).toContain('echo "Running lint on module: app"');
|
|
216
|
+
expect(unit.script).toContain('echo "Running unit tests on module: app"');
|
|
217
|
+
expect(lint.script).toContain('echo "Lint report location: app/build/reports/lint-results-debug.html"');
|
|
218
|
+
expect(unit.script).toContain("$GRADLE_CMD :app:testDebugUnitTest");
|
|
219
|
+
});
|
|
220
|
+
test("UI testing assembles on the root and finds its APKs", async () => {
|
|
221
|
+
const result = await new AndroidBuildForUITestingStepExecutor().execute({ module: ":" }, {}, testConfig);
|
|
222
|
+
const script = result.script;
|
|
223
|
+
expect(script).toContain("$GRADLE_CMD :assembleDebug :assembleDebugAndroidTest");
|
|
224
|
+
expect(script).not.toContain("::");
|
|
225
|
+
const find = script.split("\n").find((l) => l.startsWith("APP_APK="));
|
|
226
|
+
const pattern = find.match(/-path "([^"]+)"/)[1];
|
|
227
|
+
const dir = mkdtempSync(join(tmpdir(), "cibuild-root-apk-"));
|
|
228
|
+
try {
|
|
229
|
+
mkdirSync(join(dir, "build/outputs/apk/debug"), { recursive: true });
|
|
230
|
+
writeFileSync(join(dir, "build/outputs/apk/debug/x.apk"), "");
|
|
231
|
+
const found = execFileSync("find", [".", "-path", pattern], { cwd: dir, encoding: "utf-8" }).trim();
|
|
232
|
+
expect(found).toBe("./build/outputs/apk/debug/x.apk");
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
rmSync(dir, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
/**
|
|
240
|
+
* gradle-build takes a bare `assembleDebug`, which a root project satisfies.
|
|
241
|
+
* Its artifact collection must still see `./build/outputs/...`.
|
|
242
|
+
*/
|
|
243
|
+
describe("gradle-build's artifact collection", () => {
|
|
244
|
+
test("finds an APK the root project wrote", async () => {
|
|
245
|
+
const result = await new GradleBuildStepExecutor().execute({}, {}, testConfig);
|
|
246
|
+
const script = result.script;
|
|
247
|
+
const pattern = script.match(/-path "(\*\/build\/outputs\/apk\/[^"]+)"/)[1];
|
|
248
|
+
const dir = mkdtempSync(join(tmpdir(), "cibuild-root-apk-"));
|
|
249
|
+
try {
|
|
250
|
+
mkdirSync(join(dir, "build/outputs/apk/debug"), { recursive: true });
|
|
251
|
+
writeFileSync(join(dir, "build/outputs/apk/debug/x.apk"), "");
|
|
252
|
+
const found = execFileSync("find", [".", "-path", pattern], { cwd: dir, encoding: "utf-8" }).trim();
|
|
253
|
+
expect(found).toBe("./build/outputs/apk/debug/x.apk");
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
rmSync(dir, { recursive: true, force: true });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
});
|
|
117
260
|
//# sourceMappingURL=android-variant-resolution.test.js.map
|
|
@@ -85,6 +85,14 @@ export declare class GradleBuildStepExecutor extends BaseStepExecutor {
|
|
|
85
85
|
* Written as a shell function so what ships is what the test runs.
|
|
86
86
|
*/
|
|
87
87
|
export declare const PICK_VARIANT_TASK_FUNCTION = "cibuild_pick_variant_task() {\n # $1 task prefix (lint, test), $2 task suffix (empty, UnitTest),\n # $3 the build type to prefer (Debug). Gradle's output arrives on stdin.\n awk '/Candidates are:/ { sub(/.*Candidates are:/, \"\"); print; exit }' |\n tr ',' '\\n' |\n tr -d \" '\\\".\" |\n grep -E \"^${1}[A-Za-z0-9]*${3}${2}$\" |\n head -1\n}";
|
|
88
|
+
/**
|
|
89
|
+
* The Gradle path of a task in a module: `:app:lintDebug`, or `:lintDebug`
|
|
90
|
+
* for an app whose root project is the application. The leading `:` keeps it
|
|
91
|
+
* the root project's own task rather than every project's task of that name.
|
|
92
|
+
*/
|
|
93
|
+
export declare function gradleTaskPath(module: string, task: string): string;
|
|
94
|
+
/** A module's directory with a trailing slash, or `''` for the root project. */
|
|
95
|
+
export declare function gradleProjectDir(module: string): string;
|
|
88
96
|
/**
|
|
89
97
|
* The commands that run a variant-specific Gradle task and, if the variant
|
|
90
98
|
* turns out to be a guess Gradle cannot resolve, resolve it and retry once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEhF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,gBAAgB;IAC9D,yBAAyB,CACvB,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAyB1B;;;OAGG;YACW,4BAA4B;IAwF1C,UAAU,IAAI,UAAU,EAAE;IAUpB,OAAO,CAAC,MAAM,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA6H7G;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,UAAU,IAAI,UAAU,EAAE;IAe1B,yBAAyB,CACvB,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAiBpB,OAAO,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA8P1G;AAED;;;GAGG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B,0WAQrC,CAAC;
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -544,6 +544,31 @@ export const PICK_VARIANT_TASK_FUNCTION = `cibuild_pick_variant_task() {
|
|
|
544
544
|
grep -E "^\${1}[A-Za-z0-9]*\${3}\${2}$" |
|
|
545
545
|
head -1
|
|
546
546
|
}`;
|
|
547
|
+
/**
|
|
548
|
+
* Whether a `module` input names the root project. Gradle's own path for the
|
|
549
|
+
* root is `:`; an explicit empty string is taken to mean the same, since it
|
|
550
|
+
* would otherwise compose `::<task>`. An absent input never reaches here —
|
|
551
|
+
* the steps default it to `app`.
|
|
552
|
+
*/
|
|
553
|
+
function isRootProject(module) {
|
|
554
|
+
return module === ':' || module === '';
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The Gradle path of a task in a module: `:app:lintDebug`, or `:lintDebug`
|
|
558
|
+
* for an app whose root project is the application. The leading `:` keeps it
|
|
559
|
+
* the root project's own task rather than every project's task of that name.
|
|
560
|
+
*/
|
|
561
|
+
export function gradleTaskPath(module, task) {
|
|
562
|
+
return isRootProject(module) ? `:${task}` : `:${module}:${task}`;
|
|
563
|
+
}
|
|
564
|
+
/** A module's directory with a trailing slash, or `''` for the root project. */
|
|
565
|
+
export function gradleProjectDir(module) {
|
|
566
|
+
return isRootProject(module) ? '' : `${module}/`;
|
|
567
|
+
}
|
|
568
|
+
/** How a log line names the project a step runs on. */
|
|
569
|
+
function gradleProjectLabel(module) {
|
|
570
|
+
return isRootProject(module) ? 'the root project' : `module: ${module}`;
|
|
571
|
+
}
|
|
547
572
|
/**
|
|
548
573
|
* The commands that run a variant-specific Gradle task and, if the variant
|
|
549
574
|
* turns out to be a guess Gradle cannot resolve, resolve it and retry once.
|
|
@@ -555,7 +580,7 @@ export const PICK_VARIANT_TASK_FUNCTION = `cibuild_pick_variant_task() {
|
|
|
555
580
|
export function variantTaskCommands(options) {
|
|
556
581
|
const { module, variant, prefix, suffix, gradleArgs = '', escape } = options;
|
|
557
582
|
const capitalized = variant.charAt(0).toUpperCase() + variant.slice(1);
|
|
558
|
-
const task =
|
|
583
|
+
const task = gradleTaskPath(escape(module), `${prefix}${capitalized}${suffix}`);
|
|
559
584
|
const args = gradleArgs === '' ? '' : ` ${gradleArgs}`;
|
|
560
585
|
return [
|
|
561
586
|
'',
|
|
@@ -580,10 +605,10 @@ export function variantTaskCommands(options) {
|
|
|
580
605
|
` RESOLVED_VARIANT=\${RESOLVED_TASK#${prefix}}`,
|
|
581
606
|
...(suffix === '' ? [] : [` RESOLVED_VARIANT=\${RESOLVED_VARIANT%${suffix}}`]),
|
|
582
607
|
` RESOLVED_VARIANT="$(echo "\${RESOLVED_VARIANT:0:1}" | tr '[:upper:]' '[:lower:]')\${RESOLVED_VARIANT:1}"`,
|
|
583
|
-
` echo "Using
|
|
608
|
+
` echo "Using ${gradleTaskPath(escape(module), '$RESOLVED_TASK')} — resolved VARIANT=$RESOLVED_VARIANT"`,
|
|
584
609
|
// Best-effort: the discovery is worth having even where envman is not.
|
|
585
610
|
' envman add --key VARIANT --value "$RESOLVED_VARIANT" || true',
|
|
586
|
-
` $GRADLE_CMD
|
|
611
|
+
` $GRADLE_CMD ${gradleTaskPath(escape(module), '$RESOLVED_TASK')}${args}`,
|
|
587
612
|
'elif [ "$VARIANT_STATUS" -ne 0 ]; then',
|
|
588
613
|
' rm -f "$VARIANT_LOG"',
|
|
589
614
|
' exit "$VARIANT_STATUS"',
|
|
@@ -736,7 +761,7 @@ export class AndroidLintStepExecutor extends BaseStepExecutor {
|
|
|
736
761
|
commands.push('PROJECT_DIR="${CIBUILD_SOURCE_DIR:-.}"');
|
|
737
762
|
}
|
|
738
763
|
commands.push('echo "Project directory: $PROJECT_DIR"');
|
|
739
|
-
commands.push(`echo "Running lint on
|
|
764
|
+
commands.push(`echo "Running lint on ${gradleProjectLabel(this.escapeBash(module))}"`);
|
|
740
765
|
commands.push(`echo "Variant: ${this.escapeBash(variant)}"`);
|
|
741
766
|
// Change to project directory
|
|
742
767
|
commands.push('');
|
|
@@ -769,7 +794,7 @@ export class AndroidLintStepExecutor extends BaseStepExecutor {
|
|
|
769
794
|
commands.push('');
|
|
770
795
|
commands.push('# Lint completed');
|
|
771
796
|
commands.push('echo "Lint analysis completed successfully"');
|
|
772
|
-
commands.push(`echo "Lint report location: ${this.escapeBash(module)}
|
|
797
|
+
commands.push(`echo "Lint report location: ${gradleProjectDir(this.escapeBash(module))}build/reports/lint-results-${this.escapeBash(variant)}.html"`);
|
|
773
798
|
const script = this.createBashScriptFromCommands(commands, stepName);
|
|
774
799
|
return this.createScriptStep(script, stepName);
|
|
775
800
|
}
|
|
@@ -925,7 +950,7 @@ export class AndroidUnitTestStepExecutor extends BaseStepExecutor {
|
|
|
925
950
|
commands.push('PROJECT_DIR="${CIBUILD_SOURCE_DIR:-.}"');
|
|
926
951
|
}
|
|
927
952
|
commands.push('echo "Project directory: $PROJECT_DIR"');
|
|
928
|
-
commands.push(`echo "Running unit tests on
|
|
953
|
+
commands.push(`echo "Running unit tests on ${gradleProjectLabel(this.escapeBash(module))}"`);
|
|
929
954
|
commands.push(`echo "Variant: ${this.escapeBash(variant)}"`);
|
|
930
955
|
// Change to project directory
|
|
931
956
|
commands.push('');
|
|
@@ -964,7 +989,7 @@ export class AndroidUnitTestStepExecutor extends BaseStepExecutor {
|
|
|
964
989
|
commands.push('');
|
|
965
990
|
commands.push('# Tests completed');
|
|
966
991
|
commands.push('echo "Unit tests completed successfully"');
|
|
967
|
-
commands.push(`echo "Test report location: ${this.escapeBash(module)}
|
|
992
|
+
commands.push(`echo "Test report location: ${gradleProjectDir(this.escapeBash(module))}build/reports/tests/test${variantCapitalized}UnitTest/index.html"`);
|
|
968
993
|
const script = this.createBashScriptFromCommands(commands, stepName);
|
|
969
994
|
return this.createScriptStep(script, stepName);
|
|
970
995
|
}
|
|
@@ -1013,8 +1038,8 @@ export class AndroidBuildForUITestingStepExecutor extends BaseStepExecutor {
|
|
|
1013
1038
|
commands.push('fi');
|
|
1014
1039
|
commands.push('');
|
|
1015
1040
|
// Build both app and test APKs
|
|
1016
|
-
const appTask =
|
|
1017
|
-
const testTask =
|
|
1041
|
+
const appTask = gradleTaskPath(this.escapeBash(module), `assemble${variantCap}`);
|
|
1042
|
+
const testTask = gradleTaskPath(this.escapeBash(module), `assemble${variantCap}AndroidTest`);
|
|
1018
1043
|
let cmd = `$GRADLE_CMD ${appTask} ${testTask}`;
|
|
1019
1044
|
if (extraArgs) {
|
|
1020
1045
|
cmd += ` ${extraArgs}`;
|
|
@@ -1024,8 +1049,8 @@ export class AndroidBuildForUITestingStepExecutor extends BaseStepExecutor {
|
|
|
1024
1049
|
commands.push('');
|
|
1025
1050
|
// Locate APKs
|
|
1026
1051
|
commands.push('# Locate generated APKs');
|
|
1027
|
-
commands.push(`APP_APK=$(find . -path "*/${this.escapeBash(module)}
|
|
1028
|
-
commands.push(`TEST_APK=$(find . -path "*/${this.escapeBash(module)}
|
|
1052
|
+
commands.push(`APP_APK=$(find . -path "*/${gradleProjectDir(this.escapeBash(module))}build/outputs/apk/*/*.apk" ! -name "*androidTest*" | head -1)`);
|
|
1053
|
+
commands.push(`TEST_APK=$(find . -path "*/${gradleProjectDir(this.escapeBash(module))}build/outputs/apk/*/*.apk" -name "*androidTest*" | head -1)`);
|
|
1029
1054
|
commands.push('');
|
|
1030
1055
|
commands.push('if [ -z "$APP_APK" ]; then');
|
|
1031
1056
|
commands.push(' echo "⚠️ App APK not found"');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Gemfile.lock names the bundler that must run it, and the step has to run
|
|
3
|
+
* that one.
|
|
4
|
+
*
|
|
5
|
+
* `BUNDLED WITH 2.3.21` is routine. The step found `bundle` on PATH — on a mac
|
|
6
|
+
* guest that is /usr/bin/bundle, system Ruby 2.6 — and ran `bundle install`,
|
|
7
|
+
* and RubyGems refused to start any bundler but the locked one:
|
|
8
|
+
*
|
|
9
|
+
* Could not find 'bundler' (2.3.21) required by your Gemfile.lock.
|
|
10
|
+
* (Gem::GemNotFoundException)
|
|
11
|
+
*
|
|
12
|
+
* System Ruby cannot `gem install` without sudo either, so nothing in the step
|
|
13
|
+
* could recover. These tests run the generated script against stand-in `gem`,
|
|
14
|
+
* `bundle`, `pod` and `brew` executables and read back what it invoked.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=cocoapods-locked-bundler.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cocoapods-locked-bundler.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cocoapods-locked-bundler.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Gemfile.lock names the bundler that must run it, and the step has to run
|
|
3
|
+
* that one.
|
|
4
|
+
*
|
|
5
|
+
* `BUNDLED WITH 2.3.21` is routine. The step found `bundle` on PATH — on a mac
|
|
6
|
+
* guest that is /usr/bin/bundle, system Ruby 2.6 — and ran `bundle install`,
|
|
7
|
+
* and RubyGems refused to start any bundler but the locked one:
|
|
8
|
+
*
|
|
9
|
+
* Could not find 'bundler' (2.3.21) required by your Gemfile.lock.
|
|
10
|
+
* (Gem::GemNotFoundException)
|
|
11
|
+
*
|
|
12
|
+
* System Ruby cannot `gem install` without sudo either, so nothing in the step
|
|
13
|
+
* could recover. These tests run the generated script against stand-in `gem`,
|
|
14
|
+
* `bundle`, `pod` and `brew` executables and read back what it invoked.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { describe, test, expect } from '@jest/globals';
|
|
21
|
+
import { CocoapodsInstallStepExecutor } from './ios-deps.js';
|
|
22
|
+
import { testConfig } from './test-config.js';
|
|
23
|
+
const LOCK_WITH_BUNDLER = `GEM
|
|
24
|
+
remote: https://rubygems.org/
|
|
25
|
+
specs:
|
|
26
|
+
cocoapods (1.15.2)
|
|
27
|
+
|
|
28
|
+
PLATFORMS
|
|
29
|
+
ruby
|
|
30
|
+
|
|
31
|
+
DEPENDENCIES
|
|
32
|
+
cocoapods
|
|
33
|
+
|
|
34
|
+
BUNDLED WITH
|
|
35
|
+
2.3.21
|
|
36
|
+
`;
|
|
37
|
+
const executable = (path, body) => {
|
|
38
|
+
writeFileSync(path, `#!/bin/bash\n${body}\n`);
|
|
39
|
+
chmodSync(path, 0o755);
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Runs the step in a fresh project directory. `installed` lists the bundler
|
|
43
|
+
* versions the stand-in `gem` reports as already present.
|
|
44
|
+
*/
|
|
45
|
+
const run = async (opts) => {
|
|
46
|
+
const root = mkdtempSync(join(tmpdir(), 'cibuild-bundler-'));
|
|
47
|
+
const project = join(root, 'repo');
|
|
48
|
+
const bin = join(root, 'bin');
|
|
49
|
+
const scratch = join(root, 'scratch');
|
|
50
|
+
const log = join(root, 'calls.log');
|
|
51
|
+
mkdirSync(project);
|
|
52
|
+
mkdirSync(bin);
|
|
53
|
+
mkdirSync(scratch);
|
|
54
|
+
writeFileSync(join(project, 'Podfile'), "platform :ios, '15.0'\n");
|
|
55
|
+
if (opts.lock !== undefined)
|
|
56
|
+
writeFileSync(join(project, 'Gemfile.lock'), opts.lock);
|
|
57
|
+
writeFileSync(join(project, 'Gemfile'), opts.gemfile ?? "gem 'cocoapods'\n");
|
|
58
|
+
const installed = (opts.installed ?? []).join(' ');
|
|
59
|
+
executable(join(bin, 'gem'), `echo "gem $*" >> '${log}'
|
|
60
|
+
if [ "$1" = "list" ]; then
|
|
61
|
+
for v in ${installed}; do [ "$v" = "$5" ] && exit 0; done
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
if [ "$1" = "install" ]; then
|
|
65
|
+
${opts.gemInstallFails ? 'exit 1' : 'mkdir -p "$GEM_HOME/bin"; cp "$(dirname "$0")/bundle" "$GEM_HOME/bin/bundle"; exit 0'}
|
|
66
|
+
fi`);
|
|
67
|
+
executable(join(bin, 'bundle'), `echo "bundle $* (GEM_HOME=$GEM_HOME)" >> '${log}'`);
|
|
68
|
+
executable(join(bin, 'pod'), `echo "pod $*" >> '${log}'`);
|
|
69
|
+
executable(join(bin, 'ruby'), `echo "ruby $*" >> '${log}'; printf '2.6.10'`);
|
|
70
|
+
if (opts.brewRuby) {
|
|
71
|
+
const keg = join(root, 'homebrew-ruby');
|
|
72
|
+
mkdirSync(join(keg, 'bin'), { recursive: true });
|
|
73
|
+
executable(join(keg, 'bin', 'ruby'), `echo "brew-ruby $*" >> '${log}'; printf '3.4.1'`);
|
|
74
|
+
executable(join(bin, 'brew'), `if [ "$*" = "--prefix --installed ruby" ]; then echo '${keg}'; exit 0; fi; exit 1`);
|
|
75
|
+
}
|
|
76
|
+
const script = (await new CocoapodsInstallStepExecutor().execute({ source_root_path: project }, {}, testConfig)).script;
|
|
77
|
+
const scriptPath = join(root, 'step.sh');
|
|
78
|
+
writeFileSync(scriptPath, script);
|
|
79
|
+
let output = '';
|
|
80
|
+
try {
|
|
81
|
+
output = execFileSync('bash', [scriptPath], {
|
|
82
|
+
cwd: root,
|
|
83
|
+
env: { PATH: `${bin}:/usr/bin:/bin`, HOME: root, CIBUILD_SCRATCH: scratch },
|
|
84
|
+
stdio: 'pipe',
|
|
85
|
+
}).toString();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
output = `${err.stdout ?? ''}${err.stderr ?? ''}\nEXIT ${err.status}`;
|
|
89
|
+
}
|
|
90
|
+
const calls = existsSync(log) ? readFileSync(log, 'utf-8').trim().split('\n') : [];
|
|
91
|
+
return { calls, output, gemHome: join(scratch, 'cibuild-gems'), scratch };
|
|
92
|
+
};
|
|
93
|
+
describe('cocoapods-install runs the bundler Gemfile.lock names', () => {
|
|
94
|
+
test('installs the locked bundler when it is missing', async () => {
|
|
95
|
+
const { calls } = await run({ lock: LOCK_WITH_BUNDLER });
|
|
96
|
+
expect(calls).toContain('gem install bundler -v 2.3.21 --no-document');
|
|
97
|
+
});
|
|
98
|
+
test('installs it into a writable GEM_HOME outside the checkout', async () => {
|
|
99
|
+
const { calls, gemHome } = await run({ lock: LOCK_WITH_BUNDLER });
|
|
100
|
+
const install = calls.find((c) => c.startsWith('bundle _2.3.21_ install'));
|
|
101
|
+
expect(install).toBeDefined();
|
|
102
|
+
expect(install).toContain(`GEM_HOME=${gemHome}`);
|
|
103
|
+
});
|
|
104
|
+
test('runs install and pod through that exact version', async () => {
|
|
105
|
+
const { calls } = await run({ lock: LOCK_WITH_BUNDLER });
|
|
106
|
+
expect(calls.some((c) => c.startsWith('bundle _2.3.21_ install'))).toBe(true);
|
|
107
|
+
expect(calls.some((c) => c.startsWith('bundle _2.3.21_ exec pod install'))).toBe(true);
|
|
108
|
+
// Never an unversioned bundle, which is what RubyGems refused.
|
|
109
|
+
expect(calls.some((c) => /^bundle (install|exec)/.test(c))).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
test('does not reinstall a bundler that is already there', async () => {
|
|
112
|
+
const { calls } = await run({ lock: LOCK_WITH_BUNDLER, installed: ['2.3.21'] });
|
|
113
|
+
expect(calls.some((c) => c.startsWith('gem install'))).toBe(false);
|
|
114
|
+
expect(calls.some((c) => c.startsWith('bundle _2.3.21_ exec pod install'))).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
test('a lockfile without BUNDLED WITH keeps plain bundle', async () => {
|
|
117
|
+
const { calls } = await run({ lock: LOCK_WITH_BUNDLER.replace(/BUNDLED WITH\n.*\n/, '') });
|
|
118
|
+
expect(calls.some((c) => c.startsWith('gem install'))).toBe(false);
|
|
119
|
+
expect(calls.some((c) => c.startsWith('bundle exec pod install'))).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
test('a Gemfile with no lock keeps plain bundle', async () => {
|
|
122
|
+
const { calls } = await run({});
|
|
123
|
+
expect(calls.some((c) => c.startsWith('bundle install'))).toBe(true);
|
|
124
|
+
expect(calls.some((c) => c.startsWith('bundle exec pod install'))).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
test('a project without cocoapods in its Gemfile runs pod directly', async () => {
|
|
127
|
+
const { calls } = await run({ gemfile: "gem 'fastlane'\n" });
|
|
128
|
+
expect(calls).toContain('pod install');
|
|
129
|
+
expect(calls.some((c) => c.startsWith('bundle'))).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
test('says which bundler it could not install, and fails', async () => {
|
|
132
|
+
const { output } = await run({ lock: LOCK_WITH_BUNDLER, gemInstallFails: true });
|
|
133
|
+
expect(output).toContain('bundler 2.3.21');
|
|
134
|
+
expect(output).toContain('BUNDLED WITH');
|
|
135
|
+
expect(output).toMatch(/EXIT [1-9]/);
|
|
136
|
+
});
|
|
137
|
+
// Homebrew's ruby is keg-only, so a guest whose CocoaPods and fastlane came
|
|
138
|
+
// from Homebrew has it installed and still resolves `ruby` to the system 2.6.
|
|
139
|
+
test('prefers an installed Homebrew Ruby over system Ruby', async () => {
|
|
140
|
+
const { calls } = await run({ lock: LOCK_WITH_BUNDLER, brewRuby: true });
|
|
141
|
+
expect(calls.some((c) => c.startsWith('brew-ruby'))).toBe(true);
|
|
142
|
+
expect(calls.some((c) => c.startsWith('ruby '))).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=cocoapods-locked-bundler.test.js.map
|
|
@@ -66,7 +66,7 @@ describe('skip_if_absent turns it into a clean no-op', () => {
|
|
|
66
66
|
test('still chooses between bundle exec and bare pod', async () => {
|
|
67
67
|
const script = await build({ skip_if_absent: true });
|
|
68
68
|
expect(script).toContain('USE_BUNDLE_EXEC');
|
|
69
|
-
expect(script).toContain('
|
|
69
|
+
expect(script).toContain('$BUNDLE_CMD exec pod');
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios-deps.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ios-deps.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAc,MAAM,wBAAwB,CAAC;AAMhF,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,gBAAgB;IAChE,yBAAyB,CACvB,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ios-deps.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ios-deps.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAc,MAAM,wBAAwB,CAAC;AAMhF,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,gBAAgB;IAChE,yBAAyB,CACvB,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAoJpB;AAMD,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;IACxD,yBAAyB,CACvB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAyDpB"}
|
|
@@ -65,17 +65,69 @@ export class CocoapodsInstallStepExecutor extends BaseStepExecutor {
|
|
|
65
65
|
commands.push('# Detect Gemfile with cocoapods gem → use bundle exec');
|
|
66
66
|
commands.push('USE_BUNDLE_EXEC="false"');
|
|
67
67
|
commands.push('if [ -f "Gemfile.lock" ] && grep -q "cocoapods" "Gemfile.lock" 2>/dev/null; then');
|
|
68
|
-
commands.push('
|
|
69
|
-
commands.push('
|
|
70
|
-
commands.push(' echo "Detected cocoapods in Gemfile.lock — using bundle exec"');
|
|
71
|
-
commands.push(' bundle install --quiet');
|
|
72
|
-
commands.push(' fi');
|
|
68
|
+
commands.push(' USE_BUNDLE_EXEC="true"');
|
|
69
|
+
commands.push(' echo "Detected cocoapods in Gemfile.lock — using bundle exec"');
|
|
73
70
|
commands.push('elif [ -f "Gemfile" ] && grep -q "cocoapods" "Gemfile" 2>/dev/null; then');
|
|
74
|
-
commands.push('
|
|
75
|
-
commands.push('
|
|
76
|
-
commands.push('
|
|
77
|
-
commands.push('
|
|
71
|
+
commands.push(' USE_BUNDLE_EXEC="true"');
|
|
72
|
+
commands.push(' echo "Detected cocoapods in Gemfile — using bundle exec"');
|
|
73
|
+
commands.push('fi');
|
|
74
|
+
commands.push('');
|
|
75
|
+
// Run the bundler the lockfile names, from a Ruby that can install it.
|
|
76
|
+
//
|
|
77
|
+
// A Gemfile.lock ends in `BUNDLED WITH <version>`, and RubyGems refuses to
|
|
78
|
+
// start any other bundler for it: "Could not find 'bundler' (2.3.21)
|
|
79
|
+
// required by your Gemfile.lock". The step used to run whatever `bundle`
|
|
80
|
+
// was on PATH, which on a mac guest is /usr/bin/bundle, system Ruby 2.6 —
|
|
81
|
+
// and system Ruby cannot `gem install` without sudo, so nothing in the step
|
|
82
|
+
// could recover.
|
|
83
|
+
//
|
|
84
|
+
// Three moves, each conditional, so a machine missing a piece keeps the
|
|
85
|
+
// old behaviour rather than failing on it:
|
|
86
|
+
// - prefer an installed Homebrew Ruby. It is keg-only, so it is not on
|
|
87
|
+
// PATH even where it is installed (Homebrew's cocoapods and fastlane
|
|
88
|
+
// formulae depend on it), and the system 2.6 wins by default;
|
|
89
|
+
// - point GEM_HOME at a writable directory outside the checkout, so both
|
|
90
|
+
// `gem install bundler` and `bundle install` can write without sudo,
|
|
91
|
+
// and put its bin directory first;
|
|
92
|
+
// - install the locked bundler if it is absent and invoke it by version,
|
|
93
|
+
// `bundle _<version>_`, which is the form RubyGems accepts.
|
|
94
|
+
commands.push('BUNDLE_CMD="bundle"');
|
|
95
|
+
commands.push('if [ "$USE_BUNDLE_EXEC" = "true" ]; then');
|
|
96
|
+
commands.push(' if command -v brew &>/dev/null; then');
|
|
97
|
+
commands.push(' BREW_RUBY="$(brew --prefix --installed ruby 2>/dev/null || true)"');
|
|
98
|
+
commands.push(' if [ -n "$BREW_RUBY" ] && [ -x "$BREW_RUBY/bin/ruby" ]; then');
|
|
99
|
+
commands.push(' export PATH="$BREW_RUBY/bin:$PATH"');
|
|
100
|
+
commands.push(' fi');
|
|
101
|
+
commands.push(' fi');
|
|
102
|
+
commands.push(' if ! command -v bundle &>/dev/null && ! command -v gem &>/dev/null; then');
|
|
103
|
+
commands.push(' USE_BUNDLE_EXEC="false"');
|
|
104
|
+
commands.push(' echo "No bundle or gem on PATH — running pod directly"');
|
|
105
|
+
commands.push(' fi');
|
|
106
|
+
commands.push('fi');
|
|
107
|
+
commands.push('if [ "$USE_BUNDLE_EXEC" = "true" ]; then');
|
|
108
|
+
commands.push(' export GEM_HOME="${CIBUILD_SCRATCH:-${TMPDIR:-/tmp}}/cibuild-gems"');
|
|
109
|
+
commands.push(' mkdir -p "$GEM_HOME"');
|
|
110
|
+
commands.push(' export PATH="$GEM_HOME/bin:$PATH"');
|
|
111
|
+
commands.push(' RUBY_VERSION_USED="$(ruby -e \'print RUBY_VERSION\' 2>/dev/null || echo unknown)"');
|
|
112
|
+
commands.push(' echo "Ruby: $(command -v ruby || echo none) ($RUBY_VERSION_USED), GEM_HOME: $GEM_HOME"');
|
|
113
|
+
commands.push(' LOCKED_BUNDLER=""');
|
|
114
|
+
commands.push(' if [ -f "Gemfile.lock" ]; then');
|
|
115
|
+
commands.push(' LOCKED_BUNDLER="$(awk \'/^BUNDLED WITH/ { getline; gsub(/[[:space:]]/, ""); print; exit }\' Gemfile.lock)"');
|
|
116
|
+
commands.push(' fi');
|
|
117
|
+
commands.push(' if [ -n "$LOCKED_BUNDLER" ]; then');
|
|
118
|
+
commands.push(' if ! gem list -i bundler -v "$LOCKED_BUNDLER" &>/dev/null; then');
|
|
119
|
+
commands.push(' echo "Installing bundler $LOCKED_BUNDLER, which Gemfile.lock names under BUNDLED WITH"');
|
|
120
|
+
commands.push(' if ! gem install bundler -v "$LOCKED_BUNDLER" --no-document; then');
|
|
121
|
+
commands.push(' echo "❌ Error: could not install bundler $LOCKED_BUNDLER (Gemfile.lock BUNDLED WITH) under Ruby $RUBY_VERSION_USED"');
|
|
122
|
+
commands.push(' exit 1');
|
|
123
|
+
commands.push(' fi');
|
|
124
|
+
commands.push(' fi');
|
|
125
|
+
commands.push(' BUNDLE_CMD="bundle _${LOCKED_BUNDLER}_"');
|
|
126
|
+
commands.push(' elif ! command -v bundle &>/dev/null; then');
|
|
127
|
+
commands.push(' echo "Installing bundler — none on PATH and Gemfile.lock names no version"');
|
|
128
|
+
commands.push(' gem install bundler --no-document');
|
|
78
129
|
commands.push(' fi');
|
|
130
|
+
commands.push(' $BUNDLE_CMD install --quiet');
|
|
79
131
|
commands.push('fi');
|
|
80
132
|
commands.push('');
|
|
81
133
|
// Build pod command
|
|
@@ -87,7 +139,7 @@ export class CocoapodsInstallStepExecutor extends BaseStepExecutor {
|
|
|
87
139
|
// refused anonymous clone there fails the step exactly as it does during a
|
|
88
140
|
// Swift package resolve — same signature, same fix.
|
|
89
141
|
commands.push('if [ "$USE_BUNDLE_EXEC" = "true" ]; then');
|
|
90
|
-
commands.push(' POD_INVOCATION="
|
|
142
|
+
commands.push(' POD_INVOCATION="$BUNDLE_CMD exec pod"');
|
|
91
143
|
commands.push('else');
|
|
92
144
|
commands.push(' POD_INVOCATION="pod"');
|
|
93
145
|
commands.push('fi');
|