@invarn/cibuild 2.8.4 → 2.8.6

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.
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Gradle's daemon heaps, fitted to the build machine; and a killed daemon,
3
+ * named as one.
4
+ *
5
+ * The fixtures are the `gradle.properties` of IacobIonut01/ReFra
6
+ * (`-Xmx8G`, inherited by the Kotlin daemon) and vitorpamplona/amethyst
7
+ * (`-Xmx6g` beside `kotlin.daemon.jvmargs=-Xmx8g`). Both died on a 10 GiB
8
+ * build machine with nothing in the log but "Gradle build daemon disappeared
9
+ * unexpectedly".
10
+ *
11
+ * The shell under test is the one that ships: it is executed here, not
12
+ * re-implemented.
13
+ */
14
+ import { execFileSync, spawnSync } from "node:child_process";
15
+ import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { AndroidBuildForUITestingStepExecutor, AndroidLintStepExecutor, AndroidUnitTestStepExecutor, GradleBuildStepExecutor, } from "./android.js";
20
+ import { GRADLE_MEMORY_FUNCTIONS } from "./gradle-memory.js";
21
+ import { testConfig } from "./test-config.js";
22
+ const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "../../../test/fixtures/gradle-memory");
23
+ let dir;
24
+ beforeEach(() => {
25
+ dir = mkdtempSync(join(tmpdir(), "cibuild-gradle-memory-"));
26
+ });
27
+ afterEach(() => {
28
+ rmSync(dir, { recursive: true, force: true });
29
+ });
30
+ /** Run the shipped plan in `dir`, the way the step does, with a given memory. */
31
+ function plan(memMib, preferred = "", userHome = join(dir, "no-user-home")) {
32
+ const script = `${GRADLE_MEMORY_FUNCTIONS}
33
+ cibuild_gradle_memory_plan "$1" gradle.properties "$2/gradle.properties" "$3"
34
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done
35
+ printf 'LINE %s\\n' "$CIBUILD_GRADLE_MEMORY_LINE"
36
+ printf 'SENTENCE %s\\n' "$CIBUILD_GRADLE_MEMORY_SENTENCE"`;
37
+ const out = execFileSync("bash", ["-c", script, "plan", memMib, userHome, preferred], {
38
+ cwd: dir,
39
+ encoding: "utf-8",
40
+ });
41
+ const lines = out.split("\n");
42
+ return {
43
+ args: lines.filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4)),
44
+ line: (lines.find((l) => l.startsWith("LINE ")) ?? "").slice(5),
45
+ sentence: (lines.find((l) => l.startsWith("SENTENCE ")) ?? "").slice(9),
46
+ };
47
+ }
48
+ const TEN_GIB = "10240";
49
+ describe("cibuild_gradle_memory_plan", () => {
50
+ test("ReFra: -Xmx8G, inherited by the Kotlin daemon, is capped on a 10 GiB machine", () => {
51
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
52
+ const p = plan(TEN_GIB);
53
+ // 75% of 10 GiB, halved because the Kotlin daemon inherits it. Their own
54
+ // CI runs 4g.
55
+ expect(p.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3840m -Dfile.encoding=UTF-8"]);
56
+ expect(p.line).toContain("org.gradle.jvmargs=-Xmx8G -Dfile.encoding=UTF-8 at gradle.properties:9");
57
+ expect(p.line).toContain("16.0 GiB");
58
+ expect(p.line).toContain("-Xmx3840m");
59
+ expect(p.sentence).toMatch(/^The build machine has 10\.0 GiB; this build's Gradle and Kotlin daemons asked for 16\.0 GiB/);
60
+ });
61
+ test("amethyst: both heaps are scaled, and the Kotlin one keeps its other flags", () => {
62
+ copyFileSync(join(FIXTURES, "amethyst.gradle.properties"), join(dir, "gradle.properties"));
63
+ const p = plan(TEN_GIB);
64
+ expect(p.args).toEqual([
65
+ "-Dorg.gradle.jvmargs=-Xmx3264m -Dfile.encoding=UTF-8",
66
+ "-Pkotlin.daemon.jvmargs=-Xmx4352m -XX:MaxMetaspaceSize=2g",
67
+ ]);
68
+ expect(p.line).toContain("kotlin.daemon.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=2g at gradle.properties:27");
69
+ expect(p.line).toContain("14.0 GiB");
70
+ });
71
+ test("heaps that fit are left alone, and nothing is printed", () => {
72
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
73
+ const p = plan("65536");
74
+ expect(p.args).toEqual([]);
75
+ expect(p.line).toBe("");
76
+ expect(p.sentence).toContain("asked for 16.0 GiB");
77
+ });
78
+ test("a project that declares nothing gets Gradle's default and no cap", () => {
79
+ const p = plan(TEN_GIB);
80
+ expect(p.args).toEqual([]);
81
+ expect(p.sentence).toContain("Gradle's default -Xmx512m");
82
+ });
83
+ test("in-process Kotlin compilation has no Kotlin daemon to count", () => {
84
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g\nkotlin.compiler.execution.strategy=in-process\n");
85
+ expect(plan("6144").args).toEqual([]);
86
+ expect(plan("4096").args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3072m"]);
87
+ });
88
+ test("the Gradle user home's value wins over the project's, as in Gradle", () => {
89
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx1g\n");
90
+ const home = join(dir, "home");
91
+ mkdirSync(home);
92
+ writeFileSync(join(home, "gradle.properties"), "org.gradle.jvmargs=-Xmx6g -XX:+UseParallelGC\n");
93
+ const p = plan(TEN_GIB, "", home);
94
+ expect(p.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3840m -XX:+UseParallelGC"]);
95
+ expect(p.line).toContain(`at ${home}/gradle.properties:1`);
96
+ });
97
+ test("JVM arguments the pipeline asks for replace the project's, and are still capped", () => {
98
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
99
+ const fits = plan(TEN_GIB, "-Xmx2g -Dfile.encoding=UTF-8");
100
+ expect(fits.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8"]);
101
+ expect(fits.line).toContain("as the pipeline asks");
102
+ const capped = plan("4096", "-Xmx4g -Dfile.encoding=UTF-8");
103
+ expect(capped.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx1536m -Dfile.encoding=UTF-8"]);
104
+ expect(capped.line).toContain("at the pipeline");
105
+ });
106
+ test("a value continued onto another line is left as it is, and says so", () => {
107
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx12g \\\n -Dfile.encoding=UTF-8\n");
108
+ const p = plan(TEN_GIB);
109
+ expect(p.args).toEqual([]);
110
+ expect(p.line).toContain("continues onto another line");
111
+ });
112
+ test("an unknown machine size caps nothing", () => {
113
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
114
+ const p = plan("");
115
+ expect(p.args).toEqual([]);
116
+ expect(p.sentence).toContain("could not be read");
117
+ });
118
+ test("-Xmx in every unit, and a comment is not an assignment", () => {
119
+ writeFileSync(join(dir, "gradle.properties"), "# org.gradle.jvmargs=-Xmx64g\norg.gradle.jvmargs = -Xms1g -Xmx6144m\nkotlin.daemon.jvmargs:-Xmx4194304k\n");
120
+ const p = plan("8192");
121
+ expect(p.args).toEqual([
122
+ "-Dorg.gradle.jvmargs=-Xms1g -Xmx3648m",
123
+ "-Pkotlin.daemon.jvmargs=-Xmx2432m",
124
+ ]);
125
+ });
126
+ });
127
+ /** A directory standing in for PATH, with a fake `dmesg` that prints `oom`. */
128
+ function fakeBin(oom) {
129
+ const bin = join(dir, "bin");
130
+ mkdirSync(bin, { recursive: true });
131
+ writeFileSync(join(bin, "dmesg"), `#!/bin/bash\nprintf '%s\\n' ${JSON.stringify(oom)}\n`);
132
+ chmodSync(join(bin, "dmesg"), 0o755);
133
+ return bin;
134
+ }
135
+ /** A `./gradlew` that records its arguments and fails the way it is told. */
136
+ function fakeGradlew(body) {
137
+ writeFileSync(join(dir, "gradlew"), `#!/bin/bash\nprintf '%s\\n' "$@" > "$(dirname "$0")/gradlew-args"\n${body}\n`);
138
+ chmodSync(join(dir, "gradlew"), 0o755);
139
+ }
140
+ function runStep(script, bin) {
141
+ writeFileSync(join(dir, "step.sh"), script);
142
+ return spawnSync("bash", [join(dir, "step.sh")], {
143
+ cwd: dir,
144
+ encoding: "utf-8",
145
+ env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "" },
146
+ });
147
+ }
148
+ describe("the Gradle steps", () => {
149
+ const OOM_LINE = "[ 412.9] Out of memory: Killed process 4242 (java) total-vm:14680064kB, anon-rss:9437184kB";
150
+ test("a killed daemon prints the kernel's out-of-memory lines and the sentence, and still fails", async () => {
151
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
152
+ fakeGradlew('echo "FAILURE: Build failed with an exception."\necho "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)"\nexit 1');
153
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
154
+ const run = runStep(step.script, fakeBin(OOM_LINE));
155
+ expect(run.status).toBe(1);
156
+ expect(run.stdout).toContain("Gradle memory: a Gradle daemon was killed rather than failing the build (exit 1).");
157
+ expect(run.stdout).toContain(` ${OOM_LINE}`);
158
+ expect(run.stdout).toMatch(/The build machine has [0-9.]+ GiB; this build's Gradle and Kotlin daemons asked for 16\.0 GiB/);
159
+ expect(run.stdout).not.toContain("Gradle build completed successfully");
160
+ });
161
+ test("an ordinary failure says nothing about memory", async () => {
162
+ fakeGradlew('echo "Compilation error"\nexit 1');
163
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
164
+ const run = runStep(step.script, fakeBin(OOM_LINE));
165
+ expect(run.status).toBe(1);
166
+ expect(run.stdout).not.toContain("Gradle memory:");
167
+ });
168
+ test("the pipeline's jvmargs reach Gradle as one argument, before the task", async () => {
169
+ fakeGradlew("exit 0");
170
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug", gradle_options: "--stacktrace", jvmargs: "-Xmx256m -Dfile.encoding=UTF-8" }, {}, testConfig);
171
+ const run = runStep(step.script, fakeBin(""));
172
+ expect(run.status).toBe(0);
173
+ const args = execFileSync("cat", [join(dir, "gradlew-args")], { encoding: "utf-8" }).trim().split("\n");
174
+ expect(args).toEqual(["-Dorg.gradle.jvmargs=-Xmx256m -Dfile.encoding=UTF-8", "assembleDebug", "--stacktrace"]);
175
+ });
176
+ test("every Gradle step runs through the plan, and every one is valid bash", async () => {
177
+ const steps = [
178
+ await new GradleBuildStepExecutor().execute({}, {}, testConfig),
179
+ await new AndroidLintStepExecutor().execute({}, {}, testConfig),
180
+ await new AndroidUnitTestStepExecutor().execute({}, {}, testConfig),
181
+ await new AndroidBuildForUITestingStepExecutor().execute({}, {}, testConfig),
182
+ ];
183
+ for (const step of steps) {
184
+ const script = step.script;
185
+ expect(script).toContain("cibuild_gradle_memory_cap ''");
186
+ execFileSync("bash", ["-n"], { input: script });
187
+ }
188
+ });
189
+ // cibuild's log is parsed for step-progress markers; nothing here may look
190
+ // like one.
191
+ test("no line the functions print can be taken for a step-progress marker", () => {
192
+ for (const line of GRADLE_MEMORY_FUNCTIONS.split("\n")) {
193
+ expect(line).not.toMatch(/INVARN_(STEP|PHASE)|▸/);
194
+ }
195
+ });
196
+ });
197
+ //# sourceMappingURL=gradle-memory.test.js.map
@@ -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;CA+FpB;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"}
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(' if command -v bundle &>/dev/null; then');
69
- commands.push(' USE_BUNDLE_EXEC="true"');
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(' if command -v bundle &>/dev/null; then');
75
- commands.push(' USE_BUNDLE_EXEC="true"');
76
- commands.push(' echo "Detected cocoapods in Gemfile — using bundle exec"');
77
- commands.push(' bundle install --quiet');
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="bundle exec pod"');
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');
@@ -27,10 +27,14 @@
27
27
  * left both `Debug` and `Debug-iphonesimulator` under Products.
28
28
  */
29
29
  import { describe, test, expect } from '@jest/globals';
30
+ import { execFileSync } from 'child_process';
31
+ import { mkdtempSync, realpathSync } from 'fs';
32
+ import { tmpdir } from 'os';
33
+ import { join } from 'path';
30
34
  import { XcodeBuildStepExecutor, XcodeBuildForTestStepExecutor, XcodeBuildForSimulatorStepExecutor, } from './xcode.js';
31
35
  import { APP_PRODUCT_RESOLVER_SOURCE } from './xcode-app-product.js';
32
36
  import { testConfig } from './test-config.js';
33
- const DERIVED = '-derivedDataPath "$OUTPUT_DIR/DerivedData"';
37
+ const DERIVED = '-derivedDataPath "$DERIVED_DATA_PATH"';
34
38
  const scriptFor = async (executor, inputs) => {
35
39
  const result = await executor.execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp', ...inputs }, {}, testConfig);
36
40
  return result.script;
@@ -71,7 +75,7 @@ describe('xcode-build-for-simulator finds the .app where it now lands', () => {
71
75
  const build = (inputs = {}) => scriptFor(new XcodeBuildForSimulatorStepExecutor(), inputs);
72
76
  test('searches under the per-platform Products directory', async () => {
73
77
  const script = await build();
74
- expect(script).toContain('PRODUCTS_DIR="$OUTPUT_DIR/DerivedData/Build/Products"');
78
+ expect(script).toContain('PRODUCTS_DIR="$DERIVED_DATA_PATH/Build/Products"');
75
79
  expect(script).toContain('find "$PRODUCTS_DIR"');
76
80
  });
77
81
  test('searches deep enough to reach <config>-<platform>/Foo.app', async () => {
@@ -165,4 +169,68 @@ describe('the output_dir input still steers where everything goes', () => {
165
169
  expect(script).toContain(DERIVED);
166
170
  });
167
171
  });
172
+ /**
173
+ * Derived data lives outside the source tree.
174
+ *
175
+ * With `-derivedDataPath "$OUTPUT_DIR/DerivedData"` and an output_dir of
176
+ * `build/app`, every resolved SPM checkout sat at
177
+ * `<repo>/build/app/DerivedData/SourcePackages/checkouts/` — inside the
178
+ * working tree an app's own SwiftLint build phase lints. vinhnx/Clendar went red
179
+ * on 145 serious violations, every one of them in a third-party package, none
180
+ * in the app. A developer's Xcode keeps derived data under ~/Library, so no
181
+ * repository has a reason to exclude it.
182
+ */
183
+ describe('derived data is outside the source tree', () => {
184
+ const definitionOf = (script) => {
185
+ const line = script.split('\n').find((l) => l.startsWith('DERIVED_DATA_PATH='));
186
+ expect(line).toBeDefined();
187
+ return line;
188
+ };
189
+ // Runs the step's own definition line, the way the step would.
190
+ const resolve = (script, env) => execFileSync('bash', ['-c', `${definitionOf(script)}\nprintf '%s' "$DERIVED_DATA_PATH"`], {
191
+ env: { PATH: process.env.PATH ?? '', ...env },
192
+ }).toString();
193
+ test.each(buildingExecutors())('%s never builds into the output dir', async (_name, executor) => {
194
+ expect(await scriptFor(executor, {})).not.toContain('$OUTPUT_DIR/DerivedData');
195
+ });
196
+ test.each(buildingExecutors())('%s puts derived data under the scratch root, not under the checkout', async (_name, executor) => {
197
+ const scratch = realpathSync(mkdtempSync(join(tmpdir(), 'scratch-')));
198
+ const repo = join(scratch, 'workspace', 'repo');
199
+ const path = resolve(await scriptFor(executor, { output_dir: 'build/app' }), {
200
+ CIBUILD_SCRATCH: scratch,
201
+ OUTPUT_DIR: join(repo, 'build', 'app'),
202
+ });
203
+ expect(path.startsWith(scratch + '/')).toBe(true);
204
+ expect(path.startsWith(repo + '/')).toBe(false);
205
+ });
206
+ test('falls back to TMPDIR outside a sandbox', async () => {
207
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'tmp-')));
208
+ const path = resolve(await scriptFor(new XcodeBuildStepExecutor(), {}), {
209
+ TMPDIR: tmp,
210
+ OUTPUT_DIR: '/work/repo/build',
211
+ });
212
+ expect(path.startsWith(tmp + '/')).toBe(true);
213
+ });
214
+ // The iOS template names the same output_dir on Build App and Build for
215
+ // Testing so the second step reads what the first compiled. That sharing has
216
+ // to survive the move: same output dir, same derived data.
217
+ test('two steps naming the same output_dir share derived data', async () => {
218
+ const env = { CIBUILD_SCRATCH: '/scratch', OUTPUT_DIR: '/work/repo/build/app' };
219
+ const app = resolve(await scriptFor(new XcodeBuildForSimulatorStepExecutor(), { output_dir: 'build/app' }), env);
220
+ const tests = resolve(await scriptFor(new XcodeBuildForTestStepExecutor(), { output_dir: 'build/app' }), env);
221
+ expect(tests).toBe(app);
222
+ });
223
+ test('two different output dirs stay apart', async () => {
224
+ const script = await scriptFor(new XcodeBuildStepExecutor(), {});
225
+ const a = resolve(script, { CIBUILD_SCRATCH: '/scratch', OUTPUT_DIR: '/work/repo/build/app' });
226
+ const b = resolve(script, { CIBUILD_SCRATCH: '/scratch', OUTPUT_DIR: '/work/repo/build' });
227
+ expect(a).not.toBe(b);
228
+ });
229
+ test('the product listing and the xctestrun search follow it', async () => {
230
+ const build = await scriptFor(new XcodeBuildStepExecutor(), {});
231
+ expect(build).toContain('ls -lhR "$DERIVED_DATA_PATH/Build/Products"');
232
+ const forTest = await scriptFor(new XcodeBuildForTestStepExecutor(), {});
233
+ expect(forTest).toContain('find "$DERIVED_DATA_PATH/Build/Products" -name "*.xctestrun"');
234
+ });
235
+ });
168
236
  //# sourceMappingURL=xcode-derived-data.test.js.map
@@ -52,7 +52,7 @@ describe('XcodeBuildForTestStepExecutor', () => {
52
52
  const executor = new XcodeBuildForTestStepExecutor();
53
53
  const result = await executor.execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp' }, {}, testConfig);
54
54
  expect(result.script).toContain('-derivedDataPath');
55
- expect(result.script).toContain('DerivedData');
55
+ expect(result.script).toContain('-derivedDataPath "$DERIVED_DATA_PATH"');
56
56
  });
57
57
  test('should locate .xctestrun file', async () => {
58
58
  const executor = new XcodeBuildForTestStepExecutor();
@@ -29,7 +29,7 @@ describe('xcode-build-for-test resolves before it builds', () => {
29
29
  const resolveLine = script
30
30
  .split('\n')
31
31
  .find((l) => l.includes('-resolvePackageDependencies'));
32
- expect(resolveLine).toContain('-derivedDataPath "$OUTPUT_DIR/DerivedData"');
32
+ expect(resolveLine).toContain('-derivedDataPath "$DERIVED_DATA_PATH"');
33
33
  });
34
34
  test('passes the same project type and scheme as the build', async () => {
35
35
  const resolveLine = (await build())
@@ -131,7 +131,7 @@ describe('cocoapods-install retries a refused pod source fetch', () => {
131
131
  });
132
132
  test('still chooses between bundle exec and bare pod', async () => {
133
133
  const script = await build();
134
- expect(script).toContain('bundle exec pod');
134
+ expect(script).toContain('$BUNDLE_CMD exec pod');
135
135
  expect(script).toContain('USE_BUNDLE_EXEC');
136
136
  });
137
137
  test('runs the pod command exactly once in the script', async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,32 @@ import { BaseStepExecutor } from './base.js';
6
6
  import { resolveDestinationInput } from './xcode-destination.js';
7
7
  import { APP_PRODUCT_RESOLVER_SOURCE } from './xcode-app-product.js';
8
8
  import { refusedFetchHelpers, retryOnRefusedFetch } from './git-fetch-retry.js';
9
+ /**
10
+ * Where derived data goes: outside the source tree, keyed by the output dir.
11
+ *
12
+ * It used to be "$DERIVED_DATA_PATH", and OUTPUT_DIR is inside the
13
+ * checkout — so every resolved SPM package sat in the working tree, where an
14
+ * app's own lint or format build phase walks it. vinhnx/Clendar's SwiftLint
15
+ * phase failed the build on 145 serious violations, all of them in
16
+ * third-party packages under build/app/DerivedData/SourcePackages/checkouts.
17
+ * A developer's Xcode keeps derived data under ~/Library, so no repository has
18
+ * a reason to exclude that directory, and it should not have to.
19
+ *
20
+ * CIBUILD_SCRATCH is the runner's per-build local root, which holds the
21
+ * checkout rather than sitting inside it; outside a sandbox it falls back to
22
+ * TMPDIR. The key is a checksum of the absolute output dir, so two steps that
23
+ * name the same output_dir still share derived data (the iOS template relies
24
+ * on that to avoid compiling the app twice) and two that name different ones
25
+ * stay apart, as they did before.
26
+ */
27
+ function derivedDataPathLines() {
28
+ return [
29
+ '# Derived data outside the source tree, keyed by the output dir',
30
+ 'DERIVED_DATA_PATH="${CIBUILD_SCRATCH:-${TMPDIR:-/tmp}}/cibuild-derived-data/$(printf \'%s\' "$OUTPUT_DIR" | cksum | cut -d\' \' -f1)"',
31
+ 'mkdir -p "$DERIVED_DATA_PATH"',
32
+ 'echo "Derived data: $DERIVED_DATA_PATH"',
33
+ ];
34
+ }
9
35
  /**
10
36
  * Xcodebuild step executor
11
37
  * Builds iOS/macOS projects using xcodebuild
@@ -64,6 +90,7 @@ export class XcodeBuildStepExecutor extends BaseStepExecutor {
64
90
  commands.push('# Create output directory and resolve to absolute path');
65
91
  commands.push(`mkdir -p '${this.escapeBash(outputDir)}'`);
66
92
  commands.push(`OUTPUT_DIR="$(cd '${this.escapeBash(outputDir)}' && pwd)"`);
93
+ commands.push(...derivedDataPathLines());
67
94
  // Create xcconfig file if content provided
68
95
  if (xcconfigContent && xcconfigContent.trim() !== '') {
69
96
  commands.push('');
@@ -97,7 +124,7 @@ export class XcodeBuildStepExecutor extends BaseStepExecutor {
97
124
  command: 'xcodebuild -resolvePackageDependencies' +
98
125
  ` "$PROJECT_TYPE" '${escapedPath}'` +
99
126
  ` -scheme '${this.escapeBash(scheme)}'` +
100
- ' -derivedDataPath "$OUTPUT_DIR/DerivedData"',
127
+ ' -derivedDataPath "$DERIVED_DATA_PATH"',
101
128
  }));
102
129
  // Build xcodebuild command
103
130
  commands.push('');
@@ -112,14 +139,14 @@ export class XcodeBuildStepExecutor extends BaseStepExecutor {
112
139
  xcodebuildCmd += ` -scheme '${this.escapeBash(scheme)}'`;
113
140
  xcodebuildCmd += ` -configuration '${this.escapeBash(configuration)}'`;
114
141
  xcodebuildCmd += ` -destination '${this.escapeBash(destination)}'`;
115
- // Derived data under the output dir, rather than CONFIGURATION_BUILD_DIR.
142
+ // A derived data path (see derivedDataPathLines), rather than CONFIGURATION_BUILD_DIR.
116
143
  // Same reasoning as XcodeBuildForSimulatorStepExecutor below, where the
117
144
  // full mechanism and the control are written up: the flat directory
118
145
  // collapses xcodebuild's per-configuration/per-platform product separation
119
146
  // and any graph with two tasks producing the same output name is refused
120
147
  // with "Multiple commands produce". It also desynchronises CocoaPods, whose
121
148
  // PODS_CONFIGURATION_BUILD_DIR follows BUILD_DIR and not this setting.
122
- xcodebuildCmd += ` -derivedDataPath "$OUTPUT_DIR/DerivedData"`;
149
+ xcodebuildCmd += ` -derivedDataPath "$DERIVED_DATA_PATH"`;
123
150
  // Add xcconfig if provided
124
151
  if (xcconfigContent && xcconfigContent.trim() !== '') {
125
152
  xcodebuildCmd += ' -xcconfig "$XCCONFIG_FILE"';
@@ -132,8 +159,8 @@ export class XcodeBuildStepExecutor extends BaseStepExecutor {
132
159
  // Products live under the per-platform Products directory now, not flat in
133
160
  // $OUTPUT_DIR, so point the listing at where they actually are — otherwise
134
161
  // the log says "artifacts in build/" above an empty directory.
135
- commands.push('echo "Build artifacts in: $OUTPUT_DIR/DerivedData/Build/Products"');
136
- commands.push('ls -lhR "$OUTPUT_DIR/DerivedData/Build/Products" 2>/dev/null || true');
162
+ commands.push('echo "Build artifacts in: $DERIVED_DATA_PATH/Build/Products"');
163
+ commands.push('ls -lhR "$DERIVED_DATA_PATH/Build/Products" 2>/dev/null || true');
137
164
  const script = this.createBashScriptFromCommands(commands, stepName);
138
165
  return this.createScriptStep(script, stepName);
139
166
  }
@@ -806,6 +833,7 @@ export class XcodeBuildForTestStepExecutor extends BaseStepExecutor {
806
833
  // Output directory
807
834
  commands.push(`mkdir -p '${this.escapeBash(outputDir)}'`);
808
835
  commands.push(`OUTPUT_DIR="$(cd '${this.escapeBash(outputDir)}' && pwd)"`);
836
+ commands.push(...derivedDataPathLines());
809
837
  commands.push('');
810
838
  // xcconfig
811
839
  if (xcconfigContent.trim()) {
@@ -833,7 +861,7 @@ export class XcodeBuildForTestStepExecutor extends BaseStepExecutor {
833
861
  command: 'xcodebuild -resolvePackageDependencies' +
834
862
  ` "$PROJECT_TYPE" '${escapedPath}'` +
835
863
  ` -scheme '${escapedScheme}'` +
836
- ' -derivedDataPath "$OUTPUT_DIR/DerivedData"',
864
+ ' -derivedDataPath "$DERIVED_DATA_PATH"',
837
865
  }));
838
866
  // Build command
839
867
  let cmd = 'xcodebuild build-for-testing';
@@ -841,7 +869,7 @@ export class XcodeBuildForTestStepExecutor extends BaseStepExecutor {
841
869
  cmd += ` -scheme '${escapedScheme}'`;
842
870
  cmd += ` -configuration '${this.escapeBash(configuration)}'`;
843
871
  cmd += ` -destination '${this.escapeBash(destination)}'`;
844
- cmd += ` -derivedDataPath "$OUTPUT_DIR/DerivedData"`;
872
+ cmd += ` -derivedDataPath "$DERIVED_DATA_PATH"`;
845
873
  if (testPlan) {
846
874
  cmd += ` -testPlan '${this.escapeBash(testPlan)}'`;
847
875
  }
@@ -855,7 +883,7 @@ export class XcodeBuildForTestStepExecutor extends BaseStepExecutor {
855
883
  commands.push('');
856
884
  // Locate .xctestrun file
857
885
  commands.push('# Locate generated .xctestrun file');
858
- commands.push('XCTESTRUN_PATH=$(find "$OUTPUT_DIR/DerivedData/Build/Products" -name "*.xctestrun" | head -1)');
886
+ commands.push('XCTESTRUN_PATH=$(find "$DERIVED_DATA_PATH/Build/Products" -name "*.xctestrun" | head -1)');
859
887
  commands.push('if [ -z "$XCTESTRUN_PATH" ]; then');
860
888
  commands.push(' echo "❌ Error: .xctestrun file not found"');
861
889
  commands.push(' exit 1');
@@ -1007,6 +1035,7 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
1007
1035
  // Output dir
1008
1036
  commands.push(`mkdir -p '${this.escapeBash(outputDir)}'`);
1009
1037
  commands.push(`OUTPUT_DIR="$(cd '${this.escapeBash(outputDir)}' && pwd)"`);
1038
+ commands.push(...derivedDataPathLines());
1010
1039
  commands.push('');
1011
1040
  // xcconfig
1012
1041
  commands.push('XCCONFIG_FILE="$(mktemp -t cibuild-xcconfig).xcconfig"');
@@ -1040,7 +1069,7 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
1040
1069
  command: 'xcodebuild -resolvePackageDependencies' +
1041
1070
  ` "$PROJECT_TYPE" '${escapedPath}'` +
1042
1071
  ` -scheme '${escapedScheme}'` +
1043
- ' -derivedDataPath "$OUTPUT_DIR/DerivedData"',
1072
+ ' -derivedDataPath "$DERIVED_DATA_PATH"',
1044
1073
  }));
1045
1074
  // Build command
1046
1075
  let cmd = 'xcodebuild';
@@ -1087,7 +1116,7 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
1087
1116
  // with no .app; -derivedDataPath reached BUILD SUCCEEDED in 2m06s and left
1088
1117
  // both Debug and Debug-iphonesimulator under Products — the separation the
1089
1118
  // flat directory destroys.
1090
- cmd += ` -derivedDataPath "$OUTPUT_DIR/DerivedData"`;
1119
+ cmd += ` -derivedDataPath "$DERIVED_DATA_PATH"`;
1091
1120
  cmd += ' -xcconfig "$XCCONFIG_FILE"';
1092
1121
  if (xcodebuildOptions) {
1093
1122
  cmd += ` ${xcodebuildOptions}`;
@@ -1106,7 +1135,7 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
1106
1135
  // answer — a project with no shared scheme is ordinary and builds fine, so
1107
1136
  // this resolves the same bundle it always did there.
1108
1137
  commands.push('# Locate generated .app — the one this scheme builds');
1109
- commands.push('PRODUCTS_DIR="$OUTPUT_DIR/DerivedData/Build/Products"');
1138
+ commands.push('PRODUCTS_DIR="$DERIVED_DATA_PATH/Build/Products"');
1110
1139
  commands.push('APP_RESOLVER="$(mktemp -t cibuild-app-product).cjs"');
1111
1140
  commands.push(`cat > "$APP_RESOLVER" << 'CIBUILD_APP_PRODUCT_EOF'`);
1112
1141
  commands.push(APP_PRODUCT_RESOLVER_SOURCE.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.8.4",
3
+ "version": "2.8.6",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",