@invarn/cibuild 2.9.4 → 2.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ import { execSync } from 'node:child_process';
11
11
  import { BaseStepExecutor, javaHomeVersionSpec } from './base.js';
12
12
  import { gradleMemoryCommands } from './gradle-memory.js';
13
13
  import { gradleSdkCmakeCommands } from './gradle-sdk-cmake.js';
14
+ import { appProjectOfTask, captureAssembleCommands, captureSetupCommands, isDebugAssembleTask } from './capture-injection.js';
14
15
  /**
15
16
  * Set Java version step executor
16
17
  * Sets the Java version to use for Android builds
@@ -469,11 +470,18 @@ export class GradleBuildStepExecutor extends BaseStepExecutor {
469
470
  commands.push('touch /tmp/.cibuild-build-marker');
470
471
  commands.push('');
471
472
  commands.push('# Run Gradle build');
472
- let gradleCmd = `$GRADLE_CMD ${this.escapeBash(gradleTask)}`;
473
+ let gradleArgs = this.escapeBash(gradleTask);
473
474
  if (gradleOptions && gradleOptions.trim() !== '') {
474
- gradleCmd += ` ${gradleOptions}`;
475
+ gradleArgs += ` ${gradleOptions}`;
476
+ }
477
+ if (isDebugAssembleTask(gradleTask)) {
478
+ // A debug assemble may carry the Invarn capture agent; see capture-injection.ts.
479
+ commands.push(...captureSetupCommands(appProjectOfTask(gradleTask), (v) => `'${this.escapeBash(v)}'`));
480
+ commands.push(...captureAssembleCommands(gradleArgs));
481
+ }
482
+ else {
483
+ commands.push(`$GRADLE_CMD ${gradleArgs}`);
475
484
  }
476
- commands.push(gradleCmd);
477
485
  commands.push('');
478
486
  commands.push('# Build completed');
479
487
  commands.push('echo "Gradle build completed successfully"');
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Invarn capture agent in a debug build.
3
+ *
4
+ * When the dispatch sets INVARN_CAPTURE_VERSION, INVARN_CAPTURE_TOOLKIT_URL and
5
+ * INVARN_CAPTURE_SHA256 in the build's environment, a debug `assemble` runs with
6
+ * the toolkit's Gradle init script, which adds the capture agent to the app. The
7
+ * three files are downloaded outside the checkout and each is checked against the
8
+ * sha256 the dispatch named; nothing is written into the repository.
9
+ *
10
+ * The build never fails because of the agent: a download that fails, a checksum
11
+ * that does not match, or an instrumented assemble that fails all lead to the same
12
+ * assemble without the agent, and a `▸ INVARN_CAPTURE:` line says which happened.
13
+ * Without those variables the step runs exactly as it always did.
14
+ */
15
+ /** A single Gradle task that assembles a debug variant: `assembleDebug`, `:app:assembleCiDynamicDebug`. */
16
+ export declare function isDebugAssembleTask(task: string): boolean;
17
+ /** The Gradle project the task assembles (`:app` for `:app:assembleDebug`), or null for a bare task. */
18
+ export declare function appProjectOfTask(task: string): string | null;
19
+ /**
20
+ * The shell that downloads and checks the three files, run before the assemble.
21
+ * Leaves CIBUILD_CAPTURE_INIT set to the init script's path when everything checked
22
+ * out, empty otherwise. Plain variables, no arrays: macOS ships bash 3.2.
23
+ */
24
+ export declare function captureSetupCommands(appProject: string | null, quote: (v: string) => string): string[];
25
+ /**
26
+ * The assemble itself: with the init script when the setup left one, and once more
27
+ * without it if that fails. Without an init script it is the plain command.
28
+ */
29
+ export declare function captureAssembleCommands(gradleArgs: string): string[];
30
+ //# sourceMappingURL=capture-injection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-injection.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/capture-injection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAAE,CAmCtG;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBpE"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The Invarn capture agent in a debug build.
3
+ *
4
+ * When the dispatch sets INVARN_CAPTURE_VERSION, INVARN_CAPTURE_TOOLKIT_URL and
5
+ * INVARN_CAPTURE_SHA256 in the build's environment, a debug `assemble` runs with
6
+ * the toolkit's Gradle init script, which adds the capture agent to the app. The
7
+ * three files are downloaded outside the checkout and each is checked against the
8
+ * sha256 the dispatch named; nothing is written into the repository.
9
+ *
10
+ * The build never fails because of the agent: a download that fails, a checksum
11
+ * that does not match, or an instrumented assemble that fails all lead to the same
12
+ * assemble without the agent, and a `▸ INVARN_CAPTURE:` line says which happened.
13
+ * Without those variables the step runs exactly as it always did.
14
+ */
15
+ /** A single Gradle task that assembles a debug variant: `assembleDebug`, `:app:assembleCiDynamicDebug`. */
16
+ export function isDebugAssembleTask(task) {
17
+ return /^(:[A-Za-z0-9_.-]+)*:?assemble[A-Za-z0-9]*Debug$/.test(task.trim());
18
+ }
19
+ /** The Gradle project the task assembles (`:app` for `:app:assembleDebug`), or null for a bare task. */
20
+ export function appProjectOfTask(task) {
21
+ const match = /^((?::[A-Za-z0-9_.-]+)+):assemble/.exec(task.trim());
22
+ return match ? match[1] : null;
23
+ }
24
+ /**
25
+ * The shell that downloads and checks the three files, run before the assemble.
26
+ * Leaves CIBUILD_CAPTURE_INIT set to the init script's path when everything checked
27
+ * out, empty otherwise. Plain variables, no arrays: macOS ships bash 3.2.
28
+ */
29
+ export function captureSetupCommands(appProject, quote) {
30
+ return [
31
+ '',
32
+ '# Invarn capture agent: only when the dispatch sent the toolkit',
33
+ 'CIBUILD_CAPTURE_INIT=""',
34
+ 'if [ -n "${INVARN_CAPTURE_VERSION:-}" ] && [ -n "${INVARN_CAPTURE_TOOLKIT_URL:-}" ] && [ -n "${INVARN_CAPTURE_SHA256:-}" ]; then',
35
+ ' CIBUILD_CAPTURE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cibuild-capture.XXXXXX")"',
36
+ ' CIBUILD_CAPTURE_SKIP=""',
37
+ ' for CIBUILD_CAPTURE_ROLE in init instrument agent; do',
38
+ ' case "$CIBUILD_CAPTURE_ROLE" in',
39
+ ' init) CIBUILD_CAPTURE_FILE="invarn-init-${INVARN_CAPTURE_VERSION}.gradle" ;;',
40
+ ' instrument) CIBUILD_CAPTURE_FILE="invarn-instrument-${INVARN_CAPTURE_VERSION}.jar" ;;',
41
+ ' agent) CIBUILD_CAPTURE_FILE="invarn-agent-${INVARN_CAPTURE_VERSION}.aar" ;;',
42
+ ' esac',
43
+ ' CIBUILD_CAPTURE_WANT="$(printf \'%s\' "$INVARN_CAPTURE_SHA256" | tr \',\' \'\\n\' | sed -n "s/^${CIBUILD_CAPTURE_ROLE}=//p")"',
44
+ ' if ! curl -fsSL --retry 2 --max-time 120 -o "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE" "${INVARN_CAPTURE_TOOLKIT_URL%/}/$CIBUILD_CAPTURE_FILE"; then',
45
+ ' CIBUILD_CAPTURE_SKIP="download"',
46
+ ' break',
47
+ ' fi',
48
+ ' CIBUILD_CAPTURE_GOT="$( (sha256sum "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE" 2>/dev/null || shasum -a 256 "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE") | cut -d\' \' -f1)"',
49
+ ' if [ -z "$CIBUILD_CAPTURE_WANT" ] || [ "$CIBUILD_CAPTURE_GOT" != "$CIBUILD_CAPTURE_WANT" ]; then',
50
+ ' CIBUILD_CAPTURE_SKIP="sha256"',
51
+ ' break',
52
+ ' fi',
53
+ ' done',
54
+ ' if [ -z "$CIBUILD_CAPTURE_SKIP" ]; then',
55
+ ' export INVARN_INSTRUMENT_JAR="$CIBUILD_CAPTURE_DIR/invarn-instrument-${INVARN_CAPTURE_VERSION}.jar"',
56
+ ' export INVARN_AGENT_AAR="$CIBUILD_CAPTURE_DIR/invarn-agent-${INVARN_CAPTURE_VERSION}.aar"',
57
+ ...(appProject ? [` export INVARN_APP_PROJECTS=${quote(appProject)}`] : []),
58
+ ' CIBUILD_CAPTURE_INIT="$CIBUILD_CAPTURE_DIR/invarn-init-${INVARN_CAPTURE_VERSION}.gradle"',
59
+ ' else',
60
+ ' echo "▸ INVARN_CAPTURE:skipped:$CIBUILD_CAPTURE_SKIP"',
61
+ ' fi',
62
+ 'fi',
63
+ ];
64
+ }
65
+ /**
66
+ * The assemble itself: with the init script when the setup left one, and once more
67
+ * without it if that fails. Without an init script it is the plain command.
68
+ */
69
+ export function captureAssembleCommands(gradleArgs) {
70
+ return [
71
+ // In an `if` condition, not between `set +e` and `set -e`: the memory wrapper turns `set -e` back on before it
72
+ // returns Gradle's status, and bash ignores `-e` for a whole function called as a condition.
73
+ 'if [ -n "$CIBUILD_CAPTURE_INIT" ]; then',
74
+ ` if $GRADLE_CMD --init-script "$CIBUILD_CAPTURE_INIT" ${gradleArgs}; then CIBUILD_CAPTURE_RC=0; else CIBUILD_CAPTURE_RC=$?; fi`,
75
+ ' if [ "$CIBUILD_CAPTURE_RC" -eq 0 ]; then',
76
+ ' echo "▸ INVARN_CAPTURE:injected:${INVARN_CAPTURE_VERSION}"',
77
+ ' else',
78
+ ' echo "▸ INVARN_CAPTURE:skipped:build_failed"',
79
+ ' echo "The build failed with the capture agent (exit $CIBUILD_CAPTURE_RC); building again without it."',
80
+ ' touch /tmp/.cibuild-build-marker',
81
+ ` $GRADLE_CMD ${gradleArgs}`,
82
+ ' fi',
83
+ 'else',
84
+ ` $GRADLE_CMD ${gradleArgs}`,
85
+ 'fi',
86
+ ];
87
+ }
88
+ //# sourceMappingURL=capture-injection.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=capture-injection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-injection.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/capture-injection.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The Invarn capture agent in a debug build: the shell that ships, executed here
3
+ * against a fake `./gradlew`, with the toolkit served from `file://` URLs.
4
+ *
5
+ * Three properties. Without the dispatch's variables the step is the step it was.
6
+ * With them, a debug assemble runs with the init script, and every way that can go
7
+ * wrong (a download, a checksum, the instrumented build itself) ends in the plain
8
+ * assemble, never in a failed build. Only a debug assemble is ever touched.
9
+ */
10
+ import { spawnSync } from "node:child_process";
11
+ import { createHash } from "node:crypto";
12
+ import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { pathToFileURL } from "node:url";
16
+ import { GradleBuildStepExecutor } from "./android.js";
17
+ import { appProjectOfTask, isDebugAssembleTask } from "./capture-injection.js";
18
+ import { testConfig } from "./test-config.js";
19
+ let dir;
20
+ let toolkit;
21
+ const VERSION = "0.14.2";
22
+ const FILES = {
23
+ init: `invarn-init-${VERSION}.gradle`,
24
+ instrument: `invarn-instrument-${VERSION}.jar`,
25
+ agent: `invarn-agent-${VERSION}.aar`,
26
+ };
27
+ function sha(path) {
28
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
29
+ }
30
+ beforeEach(() => {
31
+ dir = mkdtempSync(join(tmpdir(), "capture-step-"));
32
+ toolkit = mkdtempSync(join(tmpdir(), "capture-toolkit-"));
33
+ for (const [role, name] of Object.entries(FILES))
34
+ writeFileSync(join(toolkit, name), `${role} bytes`);
35
+ });
36
+ afterEach(() => {
37
+ rmSync(dir, { recursive: true, force: true });
38
+ rmSync(toolkit, { recursive: true, force: true });
39
+ });
40
+ /** The dispatch's three values, right unless a case says otherwise. */
41
+ function captureEnv(over = {}) {
42
+ const sums = Object.entries(FILES)
43
+ .map(([role, name]) => `${role}=${sha(join(toolkit, name))}`)
44
+ .join(",");
45
+ return {
46
+ INVARN_CAPTURE_VERSION: VERSION,
47
+ INVARN_CAPTURE_TOOLKIT_URL: pathToFileURL(toolkit).href,
48
+ INVARN_CAPTURE_SHA256: sums,
49
+ ...over,
50
+ };
51
+ }
52
+ /**
53
+ * A `./gradlew` that appends each run's arguments and the agent paths it was handed
54
+ * to a file, and fails when it is given an init script and FAIL_WITH_INIT=1.
55
+ */
56
+ function fakeGradlew() {
57
+ writeFileSync(join(dir, "gradlew"), [
58
+ "#!/bin/bash",
59
+ 'echo "RUN $* | jar=${INVARN_INSTRUMENT_JAR:-} aar=${INVARN_AGENT_AAR:-} app=${INVARN_APP_PROJECTS:-}" >> "$(dirname "$0")/runs"',
60
+ 'case " $* " in *" --init-script "*) [ "${FAIL_WITH_INIT:-}" = 1 ] && exit 1 ;; esac',
61
+ "exit 0",
62
+ ].join("\n"));
63
+ chmodSync(join(dir, "gradlew"), 0o755);
64
+ }
65
+ async function run(inputs, env) {
66
+ fakeGradlew();
67
+ const step = await new GradleBuildStepExecutor().execute(inputs, {}, testConfig);
68
+ writeFileSync(join(dir, "step.sh"), step.script);
69
+ const out = spawnSync("bash", [join(dir, "step.sh")], {
70
+ cwd: dir,
71
+ encoding: "utf-8",
72
+ env: { PATH: process.env.PATH, HOME: dir, TMPDIR: dir, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "", ...env },
73
+ });
74
+ const runs = existsSync(join(dir, "runs")) ? readFileSync(join(dir, "runs"), "utf-8").trim().split("\n") : [];
75
+ return { status: out.status, stdout: out.stdout, runs };
76
+ }
77
+ describe("which tasks may carry the agent", () => {
78
+ test.each(["assembleDebug", ":app:assembleDebug", ":app:assembleCiDynamicDebug", "assembleStagingDebug"])("%s does", (task) => {
79
+ expect(isDebugAssembleTask(task)).toBe(true);
80
+ });
81
+ test.each(["assembleRelease", "bundleDebug", "assembleDebug lint", ":app:testDebugUnitTest", "assembleDebugAndroidTest"])("%s does not", (task) => {
82
+ expect(isDebugAssembleTask(task)).toBe(false);
83
+ });
84
+ test("the app project is the task's own", () => {
85
+ expect(appProjectOfTask(":app:assembleDebug")).toBe(":app");
86
+ expect(appProjectOfTask(":apps:phone:assembleDebug")).toBe(":apps:phone");
87
+ expect(appProjectOfTask("assembleDebug")).toBeNull();
88
+ });
89
+ });
90
+ describe("a debug build without the dispatch's variables", () => {
91
+ test("runs the plain assemble once and says nothing of capture", async () => {
92
+ const r = await run({ gradle_task: "assembleDebug" }, {});
93
+ expect(r.status).toBe(0);
94
+ expect(r.runs).toHaveLength(1);
95
+ expect(r.runs[0]).not.toContain("--init-script");
96
+ expect(r.stdout).not.toContain("INVARN_CAPTURE");
97
+ });
98
+ });
99
+ describe("a debug build with them", () => {
100
+ test("assembles once with the init script and the checked files, and says so", async () => {
101
+ const r = await run({ gradle_task: ":app:assembleCiDynamicDebug" }, captureEnv());
102
+ expect(r.status).toBe(0);
103
+ expect(r.runs).toHaveLength(1);
104
+ expect(r.runs[0]).toMatch(new RegExp(`--init-script \\S+/${FILES.init} :app:assembleCiDynamicDebug`));
105
+ expect(r.runs[0]).toMatch(new RegExp(`jar=\\S+/${FILES.instrument} aar=\\S+/${FILES.agent} app=:app`));
106
+ expect(r.stdout).toContain(`▸ INVARN_CAPTURE:injected:${VERSION}`);
107
+ });
108
+ test("writes nothing into the checkout", async () => {
109
+ await run({ gradle_task: "assembleDebug" }, captureEnv());
110
+ for (const name of Object.values(FILES))
111
+ expect(existsSync(join(dir, name))).toBe(false);
112
+ });
113
+ test("a failed instrumented assemble is run again without the agent, and the build passes", async () => {
114
+ const r = await run({ gradle_task: "assembleDebug" }, captureEnv({ FAIL_WITH_INIT: "1" }));
115
+ expect(r.status).toBe(0);
116
+ expect(r.runs).toHaveLength(2);
117
+ expect(r.runs[0]).toContain("--init-script");
118
+ expect(r.runs[1]).not.toContain("--init-script");
119
+ expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:build_failed");
120
+ });
121
+ test("a checksum that does not match runs the plain assemble", async () => {
122
+ const r = await run({ gradle_task: "assembleDebug" }, captureEnv({ INVARN_CAPTURE_SHA256: "init=00,instrument=00,agent=00" }));
123
+ expect(r.status).toBe(0);
124
+ expect(r.runs).toHaveLength(1);
125
+ expect(r.runs[0]).not.toContain("--init-script");
126
+ expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:sha256");
127
+ });
128
+ test("a file that cannot be downloaded runs the plain assemble", async () => {
129
+ const env = captureEnv();
130
+ rmSync(join(toolkit, FILES.agent));
131
+ const r = await run({ gradle_task: "assembleDebug" }, env);
132
+ expect(r.status).toBe(0);
133
+ expect(r.runs).toHaveLength(1);
134
+ expect(r.runs[0]).not.toContain("--init-script");
135
+ expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:download");
136
+ });
137
+ test("a release is never touched", async () => {
138
+ const r = await run({ gradle_task: "assembleRelease" }, captureEnv());
139
+ expect(r.runs).toEqual([expect.not.stringContaining("--init-script")]);
140
+ expect(r.stdout).not.toContain("INVARN_CAPTURE");
141
+ });
142
+ });
143
+ describe("a debug build that fails on its own", () => {
144
+ test("still fails, after trying once without the agent", async () => {
145
+ fakeGradlew();
146
+ writeFileSync(join(dir, "gradlew"), "#!/bin/bash\necho \"RUN $*\" >> \"$(dirname \"$0\")/runs\"\nexit 1\n");
147
+ chmodSync(join(dir, "gradlew"), 0o755);
148
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
149
+ writeFileSync(join(dir, "step.sh"), step.script);
150
+ const out = spawnSync("bash", [join(dir, "step.sh")], {
151
+ cwd: dir,
152
+ encoding: "utf-8",
153
+ env: { PATH: process.env.PATH, HOME: dir, TMPDIR: dir, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "", ...captureEnv() },
154
+ });
155
+ expect(out.status).not.toBe(0);
156
+ expect(readFileSync(join(dir, "runs"), "utf-8").trim().split("\n")).toHaveLength(2);
157
+ });
158
+ });
159
+ test("the step is valid bash", async () => {
160
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: ":app:assembleDebug" }, {}, testConfig);
161
+ expect(spawnSync("bash", ["-n"], { input: step.script }).status).toBe(0);
162
+ });
163
+ //# sourceMappingURL=capture-injection.test.js.map
@@ -46,7 +46,7 @@
46
46
  */
47
47
  /** The share of the machine's memory the two daemon heaps may take together. */
48
48
  export declare const GRADLE_HEAP_SHARE_PERCENT = 75;
49
- export declare const GRADLE_MEMORY_FUNCTIONS = "# This machine's memory in MiB, or nothing when it cannot be read.\ncibuild_mem_mib() {\n if [ -r /proc/meminfo ]; then\n awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo\n else\n local bytes\n bytes=$(sysctl -n hw.memsize 2>/dev/null || true)\n if [ -n \"$bytes\" ]; then echo $(( bytes / 1048576 )); fi\n fi\n}\n\n# $1 a .properties file, $2 a key. Prints \"<line number><TAB><value>\" for the\n# last assignment of the key, or nothing.\ncibuild_prop_line() {\n [ -f \"$1\" ] || return 0\n awk -v key=\"$2\" '\n { line = $0; sub(/^[ \\t]+/, \"\", line) }\n line ~ /^[#!]/ { next }\n {\n k = line; sub(/[ \\t]*[=:].*$/, \"\", k)\n if (k == key) { v = line; sub(/^[^=:]*[=:][ \\t]*/, \"\", v); sub(/[ \\t\\r]+$/, \"\", v); found = NR \"\\t\" v }\n }\n END { if (found != \"\") print found }\n ' \"$1\"\n}\n\n# The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),\n# or nothing when it has none.\ncibuild_xmx_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t' '\\n\\n' | awk '\n /^-Xmx[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 5); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# $1 a JVM argument string, $2 a heap in MiB: the same arguments with every\n# -Xmx replaced by that one (added in front when there was none).\ncibuild_with_xmx() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -Xmx*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -Xmx${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n [ \"$replaced\" -eq 1 ] || out=\" -Xmx${2}m$out\"\n printf '%s' \"${out# }\"\n)\n\n# MiB as GiB with one decimal.\ncibuild_gib() {\n awk -v m=\"$1\" 'BEGIN { printf \"%.1f\", m / 1024 }'\n}\n\n# A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.\ncibuild_scaled_heap() {\n local v=$(( $1 * $2 / $3 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n echo \"$v\"\n}\n\n# The plan, from its inputs only, so a test can run it:\n# $1 this machine's memory in MiB (empty when unknown)\n# $2 the project's gradle.properties $3 the Gradle user home's\n# $4 JVM arguments the pipeline asks for instead (may be empty)\n# Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),\n# CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when\n# nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a\n# daemon is killed).\n#\n# Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's\n# gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of\n# its own inherits the Gradle daemon's, and in-process compilation has no\n# Kotlin daemon at all.\ncibuild_gradle_memory_plan() {\n local mem=\"$1\" project=\"$2\" user=\"$3\" preferred=\"$4\" l\n local jvm=\"\" jvm_at=\"\" kd=\"\" kd_at=\"\" strategy=\"\"\n CIBUILD_GRADLE_MEMORY_ARGS=()\n CIBUILD_GRADLE_MEMORY_LINE=\"\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"\"\n\n if [ -n \"$preferred\" ]; then jvm=\"$preferred\"; jvm_at=\"the pipeline\"; fi\n for f in \"$user\" \"$project\"; do\n if [ -z \"$jvm_at\" ]; then\n l=$(cibuild_prop_line \"$f\" org.gradle.jvmargs)\n if [ -n \"$l\" ]; then jvm=\"${l#*\t}\"; jvm_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$kd_at\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.daemon.jvmargs)\n if [ -n \"$l\" ]; then kd=\"${l#*\t}\"; kd_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$strategy\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.compiler.execution.strategy)\n strategy=\"${l#*\t}\"\n fi\n done\n\n local g k kd_own=\"\"\n g=$(cibuild_xmx_mib \"$jvm\")\n [ -n \"$g\" ] || g=512\n if [ \"$strategy\" = \"in-process\" ]; then\n k=0\n else\n kd_own=$(cibuild_xmx_mib \"$kd\")\n k=\"${kd_own:-$g}\"\n fi\n local total=$(( g + k ))\n\n local asked\n if [ -n \"$jvm_at\" ]; then asked=\"org.gradle.jvmargs=$jvm at ${jvm_at}\"; else asked=\"Gradle's default -Xmx512m\"; fi\n if [ \"$k\" -eq 0 ]; then\n asked=\"$asked; Kotlin compiles in-process\"\n elif [ -n \"$kd_own\" ]; then\n asked=\"$asked, kotlin.daemon.jvmargs=$kd at ${kd_at}\"\n else\n asked=\"$asked; the Kotlin daemon inherits its -Xmx\"\n fi\n\n if [ -z \"$mem\" ]; then\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"This build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked); this machine's memory could not be read.\"\n if [ -n \"$preferred\" ]; then CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\"); fi\n return 0\n fi\n\n local budget=$(( mem * 75 / 100 ))\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"The build machine has $(cibuild_gib \"$mem\") GiB; this build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked).\"\n\n if [ \"$total\" -le \"$budget\" ]; then\n if [ -n \"$preferred\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\")\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line).\"\n fi\n return 0\n fi\n\n case \"$jvm\" in\n *\\\\)\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: the declared heaps ask for $(cibuild_gib \"$total\") GiB of this machine's $(cibuild_gib \"$mem\") GiB, but org.gradle.jvmargs at ${jvm_at} continues onto another line, so it is left as it is.\"\n return 0\n ;;\n esac\n\n # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was\n # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out\n # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.\n local ng now\n ng=$(cibuild_scaled_heap \"$budget\" 1 1)\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$(cibuild_with_xmx \"$jvm\" \"$ng\")\")\n now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -gt 0 ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.compiler.execution.strategy=in-process\")\n now=\"$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)\"\n fi\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: this machine has $(cibuild_gib \"$mem\") GiB and the declared heaps ask for $(cibuild_gib \"$total\") GiB ($asked), past the $(cibuild_gib \"$budget\") GiB this build allows them. Running with $now, set on the command line.\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now.\"\n}\n\n# $1 the number of Gradle workers the pipeline asks for (may be empty). Adds\n# --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.\n# Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).\ncibuild_gradle_workers_plan() {\n CIBUILD_GRADLE_WORKERS_LINE=\"\"\n [ -n \"$1\" ] || return 0\n case \"$1\" in\n *[!0-9]*|0*)\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: max_workers=$1 is not a positive whole number, so it is not passed and Gradle picks its own worker count.\"\n return 0\n ;;\n esac\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"--max-workers=$1\")\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: running with --max-workers=$1, as the pipeline asks (set on the command line, over any org.gradle.workers.max).\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE Gradle ran with --max-workers=$1.\"\n}\n\n# The kernel's out-of-memory lines, if this user may read the kernel log.\n# Linux guests restrict dmesg to root by default; their build user has\n# non-interactive sudo, so that is the second try.\ncibuild_kernel_oom_lines() {\n { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |\n grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true\n}\n\n# $1 Gradle's exit status, $2 a file holding its output. Says so when the\n# failure is a killed daemon rather than a failed build.\ncibuild_gradle_memory_report() {\n local kotlin_lost=\"\"\n if grep -q \"Connection to the Kotlin daemon has been unexpectedly lost\" \"$2\" 2>/dev/null; then kotlin_lost=1; fi\n if [ \"$1\" -ne 137 ] && [ -z \"$kotlin_lost\" ] && ! grep -q \"daemon disappeared unexpectedly\" \"$2\" 2>/dev/null; then\n return 0\n fi\n local oom\n oom=$(cibuild_kernel_oom_lines)\n echo \"\"\n if [ -n \"$kotlin_lost\" ]; then\n echo \"Gradle memory: the Kotlin daemon was lost mid-compile rather than the build failing (exit $1). Kotlin then compiled without it, in the Gradle daemon's heap.\"\n else\n echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\n fi\n if [ -n \"$oom\" ]; then\n echo \"The kernel's out-of-memory lines:\"\n printf '%s\\n' \"$oom\" | sed 's/^/ /'\n else\n echo \"The kernel log shows no out-of-memory kill, or this machine does not let the build read it.\"\n fi\n if [ -n \"$CIBUILD_GRADLE_MEMORY_SENTENCE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_SENTENCE\"; fi\n}\n\n# Runs Gradle with the plan's arguments, and reports a killed daemon.\ncibuild_gradle() {\n local log rc\n log=$(mktemp)\n set +e\n \"$CIBUILD_GRADLE_BIN\" \"${CIBUILD_GRADLE_MEMORY_ARGS[@]}\" \"$@\" 2>&1 | tee \"$log\"\n rc=${PIPESTATUS[0]}\n set -e\n if [ \"$rc\" -ne 0 ]; then cibuild_gradle_memory_report \"$rc\" \"$log\" || true; fi\n rm -f \"$log\"\n return \"$rc\"\n}\n\n# $1 JVM arguments the pipeline asks for, $2 the Gradle workers it asks for\n# (either may be empty). Run in the Gradle project directory, after GRADLE_CMD\n# is chosen.\ncibuild_gradle_memory_cap() {\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\"\n cibuild_gradle_workers_plan \"$2\"\n if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_WORKERS_LINE\" ]; then echo \"$CIBUILD_GRADLE_WORKERS_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
49
+ export declare const GRADLE_MEMORY_FUNCTIONS = "# This machine's memory in MiB, or nothing when it cannot be read.\ncibuild_mem_mib() {\n if [ -r /proc/meminfo ]; then\n awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo\n else\n local bytes\n bytes=$(sysctl -n hw.memsize 2>/dev/null || true)\n if [ -n \"$bytes\" ]; then echo $(( bytes / 1048576 )); fi\n fi\n}\n\n# $1 a .properties file, $2 a key. Prints \"<line number><TAB><value>\" for the\n# last assignment of the key, or nothing.\ncibuild_prop_line() {\n [ -f \"$1\" ] || return 0\n awk -v key=\"$2\" '\n { line = $0; sub(/^[ \\t]+/, \"\", line) }\n line ~ /^[#!]/ { next }\n {\n k = line; sub(/[ \\t]*[=:].*$/, \"\", k)\n if (k == key) { v = line; sub(/^[^=:]*[=:][ \\t]*/, \"\", v); sub(/[ \\t\\r]+$/, \"\", v); found = NR \"\\t\" v }\n }\n END { if (found != \"\") print found }\n ' \"$1\"\n}\n\n# The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),\n# or nothing when it has none.\ncibuild_xmx_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t' '\\n\\n' | awk '\n /^-Xmx[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 5); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# The -XX:MaxMetaspaceSize of a JVM argument string in MiB (the last one\n# wins), or nothing when it has none.\ncibuild_metaspace_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t,' '\\n\\n\\n' | awk '\n /^-XX:MaxMetaspaceSize=[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 22); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# $1 a JVM argument string, $2 a size in MiB: the same arguments with every\n# -XX:MaxMetaspaceSize replaced by that one.\ncibuild_with_metaspace() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -XX:MaxMetaspaceSize=*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -XX:MaxMetaspaceSize=${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n printf '%s' \"${out# }\"\n)\n\n# $1 a JVM argument string, $2 a heap in MiB: the same arguments with every\n# -Xmx replaced by that one (added in front when there was none).\ncibuild_with_xmx() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -Xmx*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -Xmx${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n [ \"$replaced\" -eq 1 ] || out=\" -Xmx${2}m$out\"\n printf '%s' \"${out# }\"\n)\n\n# MiB as GiB with one decimal.\ncibuild_gib() {\n awk -v m=\"$1\" 'BEGIN { printf \"%.1f\", m / 1024 }'\n}\n\n# A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.\ncibuild_scaled_heap() {\n local v=$(( $1 * $2 / $3 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n echo \"$v\"\n}\n\n# The plan, from its inputs only, so a test can run it:\n# $1 this machine's memory in MiB (empty when unknown)\n# $2 the project's gradle.properties $3 the Gradle user home's\n# $4 JVM arguments the pipeline asks for instead (may be empty)\n# Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),\n# CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when\n# nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a\n# daemon is killed).\n#\n# Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's\n# gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of\n# its own inherits the Gradle daemon's, and in-process compilation has no\n# Kotlin daemon at all.\ncibuild_gradle_memory_plan() {\n local mem=\"$1\" project=\"$2\" user=\"$3\" preferred=\"$4\" l\n local jvm=\"\" jvm_at=\"\" kd=\"\" kd_at=\"\" strategy=\"\"\n CIBUILD_GRADLE_MEMORY_ARGS=()\n CIBUILD_GRADLE_MEMORY_LINE=\"\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"\"\n\n if [ -n \"$preferred\" ]; then jvm=\"$preferred\"; jvm_at=\"the pipeline\"; fi\n for f in \"$user\" \"$project\"; do\n if [ -z \"$jvm_at\" ]; then\n l=$(cibuild_prop_line \"$f\" org.gradle.jvmargs)\n if [ -n \"$l\" ]; then jvm=\"${l#*\t}\"; jvm_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$kd_at\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.daemon.jvmargs)\n if [ -n \"$l\" ]; then kd=\"${l#*\t}\"; kd_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$strategy\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.compiler.execution.strategy)\n strategy=\"${l#*\t}\"\n fi\n done\n\n local g k kd_own=\"\"\n g=$(cibuild_xmx_mib \"$jvm\")\n [ -n \"$g\" ] || g=512\n if [ \"$strategy\" = \"in-process\" ]; then\n k=0\n else\n kd_own=$(cibuild_xmx_mib \"$kd\")\n k=\"${kd_own:-$g}\"\n fi\n local total=$(( g + k ))\n\n local asked\n if [ -n \"$jvm_at\" ]; then asked=\"org.gradle.jvmargs=$jvm at ${jvm_at}\"; else asked=\"Gradle's default -Xmx512m\"; fi\n if [ \"$k\" -eq 0 ]; then\n asked=\"$asked; Kotlin compiles in-process\"\n elif [ -n \"$kd_own\" ]; then\n asked=\"$asked, kotlin.daemon.jvmargs=$kd at ${kd_at}\"\n else\n asked=\"$asked; the Kotlin daemon inherits its -Xmx\"\n fi\n\n if [ -z \"$mem\" ]; then\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"This build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked); this machine's memory could not be read.\"\n if [ -n \"$preferred\" ]; then CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\"); fi\n return 0\n fi\n\n local budget=$(( mem * 75 / 100 ))\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"The build machine has $(cibuild_gib \"$mem\") GiB; this build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked).\"\n\n if [ \"$total\" -le \"$budget\" ]; then\n if [ -n \"$preferred\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\")\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line).\"\n fi\n return 0\n fi\n\n case \"$jvm\" in\n *\\\\)\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: the declared heaps ask for $(cibuild_gib \"$total\") GiB of this machine's $(cibuild_gib \"$mem\") GiB, but org.gradle.jvmargs at ${jvm_at} continues onto another line, so it is left as it is.\"\n return 0\n ;;\n esac\n\n # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was\n # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out\n # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.\n local ng now newjvm mg mk\n ng=$(cibuild_scaled_heap \"$budget\" 1 1)\n newjvm=$(cibuild_with_xmx \"$jvm\" \"$ng\")\n now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -gt 0 ]; then\n # One JVM now holds what two did, so a Metaspace cap Gradle declared must\n # hold Kotlin's too: the Kotlin daemon's own (kotlin.daemon.jvmargs, or\n # -Dkotlin.daemon.jvm.options in Gradle's), else the Gradle one it\n # inherits. GetStream/whatsApp-clone-compose capped Gradle at 512m and gave\n # Kotlin 1g, and in-process compileDebugKotlin ran out of Metaspace\n # (nightly-baseline 79). No Gradle cap, nothing to raise.\n mg=$(cibuild_metaspace_mib \"$jvm\")\n if [ -n \"$mg\" ]; then\n mk=$(cibuild_metaspace_mib \"$kd\")\n [ -n \"$mk\" ] || mk=$(cibuild_metaspace_mib \"$(printf '%s\\n' \"$jvm\" | tr ' \\t' '\\n\\n' | sed -n 's/^-Dkotlin\\.daemon\\.jvm\\.options=//p')\")\n [ -n \"$mk\" ] || mk=\"$mg\"\n newjvm=$(cibuild_with_metaspace \"$newjvm\" $(( mg + mk )))\n now=\"$now with -XX:MaxMetaspaceSize=$(( mg + mk ))m (Gradle's ${mg}m and Kotlin's ${mk}m, in one JVM now)\"\n fi\n now=\"$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)\"\n fi\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$newjvm\")\n if [ \"$k\" -gt 0 ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.compiler.execution.strategy=in-process\")\n fi\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: this machine has $(cibuild_gib \"$mem\") GiB and the declared heaps ask for $(cibuild_gib \"$total\") GiB ($asked), past the $(cibuild_gib \"$budget\") GiB this build allows them. Running with $now, set on the command line.\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now.\"\n}\n\n# $1 the project's gradle.properties, $2 the Gradle user home's. A JDK path the\n# project pins with org.gradle.java.home that is not on this machine \u2014 a\n# developer's own, like SEAbdulbasit/MusicApp-KMP's\n# /Users/abdulbasit/Library/Java/... (nightly-baseline 83) \u2014 stops Gradle\n# before it starts: \"given for org.gradle.java.home Gradle property is\n# invalid\". On the command line, -Dorg.gradle.java.home beats both files\n# (measured, Gradle 8.14.3), so the build's own JDK is passed instead. A path\n# that exists is left alone. Sets CIBUILD_GRADLE_JAVA_HOME_LINE.\ncibuild_gradle_java_home_plan() {\n local l v=\"\" at=\"\"\n CIBUILD_GRADLE_JAVA_HOME_LINE=\"\"\n for f in \"$2\" \"$1\"; do\n l=$(cibuild_prop_line \"$f\" org.gradle.java.home)\n if [ -n \"$l\" ]; then v=\"${l#*\t}\"; at=\"$f:${l%%\t*}\"; break; fi\n done\n [ -n \"$v\" ] || return 0\n [ -d \"$v\" ] && return 0\n [ -n \"${JAVA_HOME:-}\" ] && [ -d \"$JAVA_HOME\" ] || return 0\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Dorg.gradle.java.home=$JAVA_HOME\")\n CIBUILD_GRADLE_JAVA_HOME_LINE=\"Gradle: org.gradle.java.home=$v at $at is not on this machine; running with this build's JDK, $JAVA_HOME (-Dorg.gradle.java.home on the command line).\"\n}\n\n# $1 the number of Gradle workers the pipeline asks for (may be empty). Adds\n# --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.\n# Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).\ncibuild_gradle_workers_plan() {\n CIBUILD_GRADLE_WORKERS_LINE=\"\"\n [ -n \"$1\" ] || return 0\n case \"$1\" in\n *[!0-9]*|0*)\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: max_workers=$1 is not a positive whole number, so it is not passed and Gradle picks its own worker count.\"\n return 0\n ;;\n esac\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"--max-workers=$1\")\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: running with --max-workers=$1, as the pipeline asks (set on the command line, over any org.gradle.workers.max).\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE Gradle ran with --max-workers=$1.\"\n}\n\n# The kernel's out-of-memory lines, if this user may read the kernel log.\n# Linux guests restrict dmesg to root by default; their build user has\n# non-interactive sudo, so that is the second try.\ncibuild_kernel_oom_lines() {\n { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |\n grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true\n}\n\n# $1 Gradle's exit status, $2 a file holding its output. Says so when the\n# failure is a killed daemon rather than a failed build.\ncibuild_gradle_memory_report() {\n local kotlin_lost=\"\"\n if grep -q \"Connection to the Kotlin daemon has been unexpectedly lost\" \"$2\" 2>/dev/null; then kotlin_lost=1; fi\n if [ \"$1\" -ne 137 ] && [ -z \"$kotlin_lost\" ] && ! grep -q \"daemon disappeared unexpectedly\" \"$2\" 2>/dev/null; then\n return 0\n fi\n local oom\n oom=$(cibuild_kernel_oom_lines)\n echo \"\"\n if [ -n \"$kotlin_lost\" ]; then\n echo \"Gradle memory: the Kotlin daemon was lost mid-compile rather than the build failing (exit $1). Kotlin then compiled without it, in the Gradle daemon's heap.\"\n else\n echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\n fi\n if [ -n \"$oom\" ]; then\n echo \"The kernel's out-of-memory lines:\"\n printf '%s\\n' \"$oom\" | sed 's/^/ /'\n else\n echo \"The kernel log shows no out-of-memory kill, or this machine does not let the build read it.\"\n fi\n if [ -n \"$CIBUILD_GRADLE_MEMORY_SENTENCE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_SENTENCE\"; fi\n}\n\n# Runs Gradle with the plan's arguments, and reports a killed daemon.\ncibuild_gradle() {\n local log rc\n log=$(mktemp)\n set +e\n \"$CIBUILD_GRADLE_BIN\" \"${CIBUILD_GRADLE_MEMORY_ARGS[@]}\" \"$@\" 2>&1 | tee \"$log\"\n rc=${PIPESTATUS[0]}\n set -e\n if [ \"$rc\" -ne 0 ]; then cibuild_gradle_memory_report \"$rc\" \"$log\" || true; fi\n rm -f \"$log\"\n return \"$rc\"\n}\n\n# $1 JVM arguments the pipeline asks for, $2 the Gradle workers it asks for\n# (either may be empty). Run in the Gradle project directory, after GRADLE_CMD\n# is chosen.\ncibuild_gradle_memory_cap() {\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\"\n cibuild_gradle_workers_plan \"$2\"\n cibuild_gradle_java_home_plan \"gradle.properties\" \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\"\n if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_WORKERS_LINE\" ]; then echo \"$CIBUILD_GRADLE_WORKERS_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_JAVA_HOME_LINE\" ]; then echo \"$CIBUILD_GRADLE_JAVA_HOME_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
50
50
  /**
51
51
  * The commands a Gradle step runs once it has chosen `GRADLE_CMD` and is in
52
52
  * the project directory. Afterwards `$GRADLE_CMD` runs Gradle with the plan's
@@ -1 +1 @@
1
- {"version":3,"file":"gradle-memory.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAG5C,eAAO,MAAM,uBAAuB,ohUAmPlC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACjC,UAAU,SAAK,GACd,MAAM,EAAE,CAOV"}
1
+ {"version":3,"file":"gradle-memory.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAG5C,eAAO,MAAM,uBAAuB,k9aA8TlC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACjC,UAAU,SAAK,GACd,MAAM,EAAE,CAOV"}
@@ -91,6 +91,40 @@ cibuild_xmx_mib() {
91
91
  END { if (r != "") print r }'
92
92
  }
93
93
 
94
+ # The -XX:MaxMetaspaceSize of a JVM argument string in MiB (the last one
95
+ # wins), or nothing when it has none.
96
+ cibuild_metaspace_mib() {
97
+ printf '%s\\n' "$1" | tr ' \\t,' '\\n\\n\\n' | awk '
98
+ /^-XX:MaxMetaspaceSize=[0-9]+[kKmMgGtT]?$/ {
99
+ n = substr($0, 22); u = substr(n, length(n))
100
+ if (u ~ /[0-9]/) { v = n / 1048576 }
101
+ else {
102
+ n = substr(n, 1, length(n) - 1)
103
+ if (u ~ /[kK]/) v = n / 1024
104
+ else if (u ~ /[mM]/) v = n
105
+ else if (u ~ /[gG]/) v = n * 1024
106
+ else v = n * 1048576
107
+ }
108
+ r = int(v)
109
+ }
110
+ END { if (r != "") print r }'
111
+ }
112
+
113
+ # $1 a JVM argument string, $2 a size in MiB: the same arguments with every
114
+ # -XX:MaxMetaspaceSize replaced by that one.
115
+ cibuild_with_metaspace() (
116
+ set -f
117
+ out=""
118
+ replaced=0
119
+ for arg in $1; do
120
+ case "$arg" in
121
+ -XX:MaxMetaspaceSize=*) if [ "$replaced" -eq 0 ]; then out="$out -XX:MaxMetaspaceSize=\${2}m"; replaced=1; fi ;;
122
+ *) out="$out $arg" ;;
123
+ esac
124
+ done
125
+ printf '%s' "\${out# }"
126
+ )
127
+
94
128
  # $1 a JVM argument string, $2 a heap in MiB: the same arguments with every
95
129
  # -Xmx replaced by that one (added in front when there was none).
96
130
  cibuild_with_xmx() (
@@ -203,18 +237,57 @@ cibuild_gradle_memory_plan() {
203
237
  # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was
204
238
  # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out
205
239
  # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.
206
- local ng now
240
+ local ng now newjvm mg mk
207
241
  ng=$(cibuild_scaled_heap "$budget" 1 1)
208
- CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$(cibuild_with_xmx "$jvm" "$ng")")
242
+ newjvm=$(cibuild_with_xmx "$jvm" "$ng")
209
243
  now="Gradle -Xmx\${ng}m"
210
244
  if [ "$k" -gt 0 ]; then
211
- CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.compiler.execution.strategy=in-process")
245
+ # One JVM now holds what two did, so a Metaspace cap Gradle declared must
246
+ # hold Kotlin's too: the Kotlin daemon's own (kotlin.daemon.jvmargs, or
247
+ # -Dkotlin.daemon.jvm.options in Gradle's), else the Gradle one it
248
+ # inherits. GetStream/whatsApp-clone-compose capped Gradle at 512m and gave
249
+ # Kotlin 1g, and in-process compileDebugKotlin ran out of Metaspace
250
+ # (nightly-baseline 79). No Gradle cap, nothing to raise.
251
+ mg=$(cibuild_metaspace_mib "$jvm")
252
+ if [ -n "$mg" ]; then
253
+ mk=$(cibuild_metaspace_mib "$kd")
254
+ [ -n "$mk" ] || mk=$(cibuild_metaspace_mib "$(printf '%s\\n' "$jvm" | tr ' \\t' '\\n\\n' | sed -n 's/^-Dkotlin\\.daemon\\.jvm\\.options=//p')")
255
+ [ -n "$mk" ] || mk="$mg"
256
+ newjvm=$(cibuild_with_metaspace "$newjvm" $(( mg + mk )))
257
+ now="$now with -XX:MaxMetaspaceSize=$(( mg + mk ))m (Gradle's \${mg}m and Kotlin's \${mk}m, in one JVM now)"
258
+ fi
212
259
  now="$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)"
213
260
  fi
261
+ CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$newjvm")
262
+ if [ "$k" -gt 0 ]; then
263
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.compiler.execution.strategy=in-process")
264
+ fi
214
265
  CIBUILD_GRADLE_MEMORY_LINE="Gradle memory: this machine has $(cibuild_gib "$mem") GiB and the declared heaps ask for $(cibuild_gib "$total") GiB ($asked), past the $(cibuild_gib "$budget") GiB this build allows them. Running with $now, set on the command line."
215
266
  CIBUILD_GRADLE_MEMORY_SENTENCE="$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now."
216
267
  }
217
268
 
269
+ # $1 the project's gradle.properties, $2 the Gradle user home's. A JDK path the
270
+ # project pins with org.gradle.java.home that is not on this machine — a
271
+ # developer's own, like SEAbdulbasit/MusicApp-KMP's
272
+ # /Users/abdulbasit/Library/Java/... (nightly-baseline 83) — stops Gradle
273
+ # before it starts: "given for org.gradle.java.home Gradle property is
274
+ # invalid". On the command line, -Dorg.gradle.java.home beats both files
275
+ # (measured, Gradle 8.14.3), so the build's own JDK is passed instead. A path
276
+ # that exists is left alone. Sets CIBUILD_GRADLE_JAVA_HOME_LINE.
277
+ cibuild_gradle_java_home_plan() {
278
+ local l v="" at=""
279
+ CIBUILD_GRADLE_JAVA_HOME_LINE=""
280
+ for f in "$2" "$1"; do
281
+ l=$(cibuild_prop_line "$f" org.gradle.java.home)
282
+ if [ -n "$l" ]; then v="\${l#* }"; at="$f:\${l%% *}"; break; fi
283
+ done
284
+ [ -n "$v" ] || return 0
285
+ [ -d "$v" ] && return 0
286
+ [ -n "\${JAVA_HOME:-}" ] && [ -d "$JAVA_HOME" ] || return 0
287
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Dorg.gradle.java.home=$JAVA_HOME")
288
+ CIBUILD_GRADLE_JAVA_HOME_LINE="Gradle: org.gradle.java.home=$v at $at is not on this machine; running with this build's JDK, $JAVA_HOME (-Dorg.gradle.java.home on the command line)."
289
+ }
290
+
218
291
  # $1 the number of Gradle workers the pipeline asks for (may be empty). Adds
219
292
  # --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.
220
293
  # Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).
@@ -285,8 +358,10 @@ cibuild_gradle_memory_cap() {
285
358
  cibuild_gradle_memory_plan "$(cibuild_mem_mib || true)" "gradle.properties" \\
286
359
  "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" "$1"
287
360
  cibuild_gradle_workers_plan "$2"
361
+ cibuild_gradle_java_home_plan "gradle.properties" "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties"
288
362
  if [ -n "$CIBUILD_GRADLE_MEMORY_LINE" ]; then echo "$CIBUILD_GRADLE_MEMORY_LINE"; fi
289
363
  if [ -n "$CIBUILD_GRADLE_WORKERS_LINE" ]; then echo "$CIBUILD_GRADLE_WORKERS_LINE"; fi
364
+ if [ -n "$CIBUILD_GRADLE_JAVA_HOME_LINE" ]; then echo "$CIBUILD_GRADLE_JAVA_HOME_LINE"; fi
290
365
  CIBUILD_GRADLE_BIN="$GRADLE_CMD"
291
366
  GRADLE_CMD=cibuild_gradle
292
367
  }`;
@@ -67,10 +67,27 @@ describe("cibuild_gradle_memory_plan", () => {
67
67
  test("mifos-mobile on its 6 GiB mac guest gets the one heap too", () => {
68
68
  writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g\n");
69
69
  expect(plan("6144").args).toEqual([
70
- "-Dorg.gradle.jvmargs=-Xmx4608m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g",
70
+ // Its Kotlin daemon inherited the 1g Metaspace cap; in one JVM the cap holds both.
71
+ "-Dorg.gradle.jvmargs=-Xmx4608m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=2048m",
71
72
  "-Pkotlin.compiler.execution.strategy=in-process",
72
73
  ]);
73
74
  });
75
+ // GetStream/whatsApp-clone-compose (nightly-baseline 79): Gradle capped at
76
+ // 512m Metaspace and Kotlin given 1g of its own; in-process, the one JVM
77
+ // ran out of Metaspace in compileDebugKotlin.
78
+ test("whatsApp-clone-compose: a Metaspace cap holds Kotlin's too once Kotlin is in-process", () => {
79
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true\n");
80
+ const p = plan("9933");
81
+ expect(p.args).toEqual([
82
+ "-Dorg.gradle.jvmargs=-Xmx7424m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=1536m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true",
83
+ "-Pkotlin.compiler.execution.strategy=in-process",
84
+ ]);
85
+ expect(p.line).toContain("with -XX:MaxMetaspaceSize=1536m (Gradle's 512m and Kotlin's 1024m, in one JVM now)");
86
+ });
87
+ test("a Kotlin daemon with no Metaspace of its own inherits Gradle's, and the cap doubles", () => {
88
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g\n");
89
+ expect(plan(TEN_GIB).args[0]).toBe("-Dorg.gradle.jvmargs=-Xmx7680m -XX:MaxMetaspaceSize=2048m");
90
+ });
74
91
  test("the one heap never goes below 512 MiB", () => {
75
92
  writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g\n");
76
93
  expect(plan("600").args).toEqual(["-Dorg.gradle.jvmargs=-Xmx512m", "-Pkotlin.compiler.execution.strategy=in-process"]);
@@ -157,6 +174,36 @@ printf 'SENTENCE %s\\n' "$CIBUILD_GRADLE_MEMORY_SENTENCE"`;
157
174
  // ReFra was killed again with its heap capped: off-heap memory and parallel
158
175
  // native compilers are past what a heap cap bounds. Its CI also sets
159
176
  // org.gradle.workers.max=2, which the pipeline carries as max_workers.
177
+ // nightly-baseline 83: SEAbdulbasit/MusicApp-KMP pins its developer's JDK in
178
+ // gradle.properties, and Gradle refused to start.
179
+ describe("cibuild_gradle_java_home_plan", () => {
180
+ const javaHomePlan = (env) => {
181
+ const out = execFileSync("bash", ["-c", `${GRADLE_MEMORY_FUNCTIONS}
182
+ CIBUILD_GRADLE_MEMORY_ARGS=()
183
+ cibuild_gradle_java_home_plan gradle.properties "$1/gradle.properties"
184
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done
185
+ printf 'LINE %s\\n' "$CIBUILD_GRADLE_JAVA_HOME_LINE"`, "plan", join(dir, "no-user-home")], { cwd: dir, encoding: "utf-8", env: { PATH: process.env.PATH ?? "", ...env } });
186
+ const lines = out.split("\n");
187
+ return {
188
+ args: lines.filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4)),
189
+ line: (lines.find((l) => l.startsWith("LINE ")) ?? "").slice(5),
190
+ };
191
+ };
192
+ test("a pinned JDK that is not on this machine is replaced by the build's", () => {
193
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx2g\norg.gradle.java.home=/Users/abdulbasit/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home\n");
194
+ const p = javaHomePlan({ JAVA_HOME: dir });
195
+ expect(p.args).toEqual([`-Dorg.gradle.java.home=${dir}`]);
196
+ expect(p.line).toContain("org.gradle.java.home=/Users/abdulbasit/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home at gradle.properties:2 is not on this machine");
197
+ });
198
+ test("a pinned JDK that exists is left alone", () => {
199
+ writeFileSync(join(dir, "gradle.properties"), `org.gradle.java.home=${dir}\n`);
200
+ expect(javaHomePlan({ JAVA_HOME: "/elsewhere" }).args).toEqual([]);
201
+ });
202
+ test("no pin, nothing", () => {
203
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx2g\n");
204
+ expect(javaHomePlan({ JAVA_HOME: dir })).toEqual({ args: [], line: "" });
205
+ });
206
+ });
160
207
  describe("cibuild_gradle_workers_plan", () => {
161
208
  test("ReFra: the worker count goes after the heap cap, and the sentence names it", () => {
162
209
  copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));