@invarn/cibuild 2.9.5 → 2.9.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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=a-missing-pinned-jdk-yields-for-every-gradle-run.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a-missing-pinned-jdk-yields-for-every-gradle-run.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/a-missing-pinned-jdk-yields-for-every-gradle-run.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * A project that pins org.gradle.java.home to a JDK this machine does not
3
+ * have is run with the build's JDK by every Gradle invocation in the build,
4
+ * not only the Gradle steps.
5
+ *
6
+ * The Gradle steps pass -Dorg.gradle.java.home on their own command line, but
7
+ * a script step that calls ./gradlew itself does not, and neither does a
8
+ * build phase Xcode runs. SEAbdulbasit/MusicApp-KMP got past its Android step
9
+ * that way and then stopped in a script step's ./gradlew on "Value
10
+ * '/Users/abdulbasit/...' given for org.gradle.java.home Gradle property is
11
+ * invalid". The one place every one of those reads is the checkout's
12
+ * gradle.properties, so set-java-version, which picks the build's JDK, writes
13
+ * it there.
14
+ *
15
+ * The fixture is MusicApp-KMP's own gradle.properties (test/fixtures/gradle-java-home/SOURCE.txt).
16
+ * The shell under test is the one that ships: it is executed here.
17
+ */
18
+ import { execFileSync } from "node:child_process";
19
+ import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { SetJavaVersionStepExecutor } from "./android.js";
24
+ import { GRADLE_JAVA_HOME_PIN_FUNCTIONS } from "./gradle-memory.js";
25
+ import { testConfig } from "./test-config.js";
26
+ const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), "../../../test/fixtures/gradle-java-home/musicapp-kmp.gradle.properties");
27
+ const PINNED = "/Users/abdulbasit/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home";
28
+ let dir;
29
+ let jdk;
30
+ beforeEach(() => {
31
+ dir = mkdtempSync(join(tmpdir(), "cibuild-java-home-pin-"));
32
+ jdk = join(dir, "jdk");
33
+ mkdirSync(jdk);
34
+ });
35
+ afterEach(() => rmSync(dir, { recursive: true, force: true }));
36
+ function pin(checkout, env) {
37
+ return execFileSync("bash", ["-c", `${GRADLE_JAVA_HOME_PIN_FUNCTIONS}\ncibuild_gradle_java_home_pin`], {
38
+ cwd: checkout,
39
+ encoding: "utf-8",
40
+ env: { PATH: process.env.PATH ?? "", ...env },
41
+ });
42
+ }
43
+ describe("cibuild_gradle_java_home_pin", () => {
44
+ test("MusicApp-KMP: the developer's JDK line names the build's, so a plain ./gradlew reads it too", () => {
45
+ const checkout = join(dir, "repo");
46
+ mkdirSync(checkout);
47
+ copyFileSync(FIXTURE, join(checkout, "gradle.properties"));
48
+ const out = pin(checkout, { JAVA_HOME: jdk });
49
+ const lines = readFileSync(join(checkout, "gradle.properties"), "utf-8").trimEnd().split("\n");
50
+ expect(lines[2]).toBe(`org.gradle.java.home=${jdk}`);
51
+ // Nothing else in the file moves (awk ends the last line with a newline).
52
+ const before = readFileSync(FIXTURE, "utf-8").split("\n");
53
+ expect(lines.filter((_, i) => i !== 2)).toEqual(before.filter((_, i) => i !== 2));
54
+ expect(out).toContain(`Gradle: org.gradle.java.home=${PINNED} at gradle.properties:3 is not on this machine`);
55
+ expect(out).toContain(jdk);
56
+ });
57
+ test("a nested Gradle project's file is found from the checkout root (CIBUILD_SOURCE_DIR)", () => {
58
+ const checkout = join(dir, "repo");
59
+ mkdirSync(join(checkout, "apps", "mobile"), { recursive: true });
60
+ copyFileSync(FIXTURE, join(checkout, "apps", "mobile", "gradle.properties"));
61
+ pin(dir, { JAVA_HOME: jdk, CIBUILD_SOURCE_DIR: checkout });
62
+ expect(readFileSync(join(checkout, "apps", "mobile", "gradle.properties"), "utf-8")).toContain(`org.gradle.java.home=${jdk}\n`);
63
+ });
64
+ test("a pinned JDK that is on this machine is left alone", () => {
65
+ const own = join(dir, "own-jdk");
66
+ mkdirSync(own);
67
+ writeFileSync(join(dir, "gradle.properties"), `org.gradle.java.home=${own}\n`);
68
+ expect(pin(dir, { JAVA_HOME: jdk })).toBe("");
69
+ expect(readFileSync(join(dir, "gradle.properties"), "utf-8")).toBe(`org.gradle.java.home=${own}\n`);
70
+ });
71
+ test("no pin, or no JDK of the build's own to name, changes nothing", () => {
72
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx2g\n");
73
+ expect(pin(dir, { JAVA_HOME: jdk })).toBe("");
74
+ copyFileSync(FIXTURE, join(dir, "gradle.properties"));
75
+ expect(pin(dir, { JAVA_HOME: join(dir, "missing") })).toBe("");
76
+ expect(readFileSync(join(dir, "gradle.properties"), "utf-8")).toBe(readFileSync(FIXTURE, "utf-8"));
77
+ });
78
+ test("a commented-out pin is not a pin", () => {
79
+ writeFileSync(join(dir, "gradle.properties"), `#org.gradle.java.home=${PINNED}\n`);
80
+ expect(pin(dir, { JAVA_HOME: jdk })).toBe("");
81
+ expect(readFileSync(join(dir, "gradle.properties"), "utf-8")).toBe(`#org.gradle.java.home=${PINNED}\n`);
82
+ });
83
+ });
84
+ describe("set-java-version", () => {
85
+ test("writes the build's JDK over a missing pin after choosing it", async () => {
86
+ const step = await new SetJavaVersionStepExecutor().execute({ java_version: "21" }, {}, testConfig);
87
+ const script = step.script ?? "";
88
+ expect(script).toContain("cibuild_gradle_java_home_pin");
89
+ expect(script.lastIndexOf("cibuild_gradle_java_home_pin")).toBeGreaterThan(script.indexOf("export JAVA_HOME"));
90
+ });
91
+ test("leaves a developer's own checkout alone on a local run", async () => {
92
+ const step = await new SetJavaVersionStepExecutor().execute({ java_version: "21" }, {}, { ...testConfig, local: true });
93
+ expect(step.script ?? "").not.toContain("cibuild_gradle_java_home_pin");
94
+ });
95
+ });
96
+ //# sourceMappingURL=a-missing-pinned-jdk-yields-for-every-gradle-run.test.js.map
@@ -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;AAIlE,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;IAChB,6IAA6I;IAC7I,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6IAA6I;IAC7I,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;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;IACrB,6IAA6I;IAC7I,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;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;CA0Q1G;AAED;;;GAGG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B,0WAQrC,CAAC;AAYH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAOD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC,GAAG,MAAM,EAAE,CAuCX;AAED,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,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;CAmM1G;AAED;;;GAGG;AACH,qBAAa,2BAA4B,SAAQ,gBAAgB;IAC/D,yBAAyB,CACvB,OAAO,EAAE,qBAAqB,EAC9B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAiBpB,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA8M9G;AAMD,MAAM,WAAW,8BAA8B;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6IAA6I;IAC7I,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,oCAAqC,SAAQ,gBAAgB;IACxE,yBAAyB,CACvB,OAAO,EAAE,8BAA8B,EACvC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAM1B,UAAU,IAAI,UAAU,EAAE;IAOpB,OAAO,CAAC,MAAM,EAAE,8BAA8B,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA4EzH"}
1
+ {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@
9
9
  import { existsSync, readFileSync } from 'node:fs';
10
10
  import { execSync } from 'node:child_process';
11
11
  import { BaseStepExecutor, javaHomeVersionSpec } from './base.js';
12
- import { gradleMemoryCommands } from './gradle-memory.js';
12
+ import { GRADLE_JAVA_HOME_PIN_FUNCTIONS, gradleMemoryCommands } from './gradle-memory.js';
13
13
  import { gradleSdkCmakeCommands } from './gradle-sdk-cmake.js';
14
14
  import { appProjectOfTask, captureAssembleCommands, captureSetupCommands, isDebugAssembleTask } from './capture-injection.js';
15
15
  /**
@@ -247,6 +247,14 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
247
247
  commands.push('');
248
248
  commands.push('# Persist JAVA_HOME to envman for subsequent steps');
249
249
  commands.push('envman add --key JAVA_HOME --value "$JAVA_HOME"');
250
+ // Every Gradle run in the build, not only the Gradle steps, must get past
251
+ // a project's pin of its developer's own JDK (nightly-baseline 92). Not on
252
+ // a local run: there the checkout is a developer's working copy.
253
+ if (!config.local) {
254
+ commands.push('');
255
+ commands.push(GRADLE_JAVA_HOME_PIN_FUNCTIONS);
256
+ commands.push('cibuild_gradle_java_home_pin');
257
+ }
250
258
  const script = this.createBashScriptFromCommands(commands, stepName);
251
259
  return this.createScriptStep(script, stepName);
252
260
  }
@@ -46,7 +46,24 @@
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# 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}";
49
+ /**
50
+ * Sends a Kotlin Multiplatform project's metadata compiles
51
+ * (compile*KotlinMetadata, the KotlinCompileCommon tasks) to the Kotlin daemon
52
+ * while every other Kotlin compile stays in-process. With
53
+ * -Pkotlin.compiler.execution.strategy=in-process, Kotlin 2.2.0 compiled
54
+ * DroidKaigi/conference-app-2025's common metadata in K1 mode and rejected its
55
+ * context parameters; the same task on the daemon passed (nightly-baseline 95).
56
+ *
57
+ * The Kotlin plugin's classes are not on an init script's classpath, so the
58
+ * task is matched by class name and the strategy enum is loaded through the
59
+ * task's own classloader. A Kotlin plugin without the property (older than the
60
+ * strategy API) is left alone: this script must never fail a build. Lazy
61
+ * (configureEach where Gradle has it), so it is configuration-cache safe and
62
+ * realizes no task. It runs once per build, included builds as well; they
63
+ * have no metadata compiles to touch.
64
+ */
65
+ export declare const KOTLIN_METADATA_DAEMON_INIT_SCRIPT = "// cibuild: Kotlin metadata compiles run in the Kotlin daemon (nightly-baseline 95).\nallprojects {\n def hook = { t ->\n if (!t.class.name.startsWith('org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon')) return\n try {\n def strategy = t.class.classLoader.loadClass('org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy')\n t.compilerExecutionStrategy.set(strategy.enumConstants.find { it.name() == 'DAEMON' })\n } catch (Throwable ignored) {\n }\n }\n try {\n tasks.configureEach(hook)\n } catch (MissingMethodException ignored) {\n tasks.all(hook)\n }\n}\n";
66
+ 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 Kotlin daemon heap a multiplatform project's metadata compiles get out\n# of a budget in MiB: an eighth, rounded down to 64 MiB, from 512 MiB to 1 GiB.\ncibuild_metadata_daemon_heap() {\n local v=$(( $1 / 8 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n [ \"$v\" -le 1024 ] || v=1024\n echo \"$v\"\n}\n\n# Whether the Gradle project here applies Kotlin Multiplatform: a build script\n# or version catalog that names the plugin. Convention plugins in buildSrc or\n# an included build sit a few directories down; build outputs are not read.\ncibuild_applies_kotlin_multiplatform() {\n local f\n while IFS= read -r -d '' f; do\n if grep -q -E 'kotlin\\(\"multiplatform\"\\)|org\\.jetbrains\\.kotlin\\.multiplatform|kotlin-multiplatform|kotlinMultiplatform' \"$f\" 2>/dev/null; then return 0; fi\n done < <(find . -maxdepth 6 \\( -name .git -o -name node_modules -o -name build -o -name .gradle \\) -prune -o -type f \\( -name '*.gradle' -o -name '*.gradle.kts' -o -name '*.versions.toml' \\) -print0 2>/dev/null)\n return 1\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# $5 the metadata init script, when the project applies Kotlin\n# Multiplatform (empty otherwise)\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\" init=\"${5:-}\" 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 # A multiplatform project's metadata compiles are the exception: they go to\n # a small Kotlin daemon (the init script), carved out of the same budget.\n local ng now newjvm mg mk md=\"\"\n if [ \"$k\" -gt 0 ] && [ -n \"$init\" ]; then md=$(cibuild_metadata_daemon_heap \"$budget\"); fi\n ng=$(cibuild_scaled_heap $(( budget - ${md:-0} )) 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 if [ -n \"$md\" ]; then\n now=\"$now, and Kotlin metadata compiles (compile*KotlinMetadata) in a Kotlin daemon of -Xmx${md}m beside it (in-process, Kotlin 2.2 compiled them in K1 mode)\"\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 if [ -n \"$md\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.daemon.jvmargs=-Xmx${md}m\" --init-script \"$init\")\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 local metadata_init=\"\"\n if cibuild_applies_kotlin_multiplatform; then\n metadata_init=\"$(mktemp -d)/cibuild-kotlin-metadata-daemon.init.gradle\"\n cat > \"$metadata_init\" <<'CIBUILD_KOTLIN_METADATA_INIT'\n// cibuild: Kotlin metadata compiles run in the Kotlin daemon (nightly-baseline 95).\nallprojects {\n def hook = { t ->\n if (!t.class.name.startsWith('org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon')) return\n try {\n def strategy = t.class.classLoader.loadClass('org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy')\n t.compilerExecutionStrategy.set(strategy.enumConstants.find { it.name() == 'DAEMON' })\n } catch (Throwable ignored) {\n }\n }\n try {\n tasks.configureEach(hook)\n } catch (MissingMethodException ignored) {\n tasks.all(hook)\n }\n}\nCIBUILD_KOTLIN_METADATA_INIT\n fi\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\" \"$metadata_init\"\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
67
  /**
51
68
  * The commands a Gradle step runs once it has chosen `GRADLE_CMD` and is in
52
69
  * the project directory. Afterwards `$GRADLE_CMD` runs Gradle with the plan's
@@ -58,4 +75,17 @@ export declare const GRADLE_MEMORY_FUNCTIONS = "# This machine's memory in MiB,
58
75
  * repository CI's `org.gradle.workers.max`), passed as `--max-workers`.
59
76
  */
60
77
  export declare function gradleMemoryCommands(jvmargs: string, escape: (value: string) => string, maxWorkers?: string): string[];
78
+ /**
79
+ * A pinned org.gradle.java.home that is not on this machine, made to name the
80
+ * build's JDK for every Gradle run in the build. The Gradle steps already pass
81
+ * -Dorg.gradle.java.home on their own command line; a script step's
82
+ * ./gradlew and a build phase Xcode runs do not, and they read the checkout's
83
+ * gradle.properties. SEAbdulbasit/MusicApp-KMP's Android step went on with the
84
+ * override and its KMM "Prepare the iOS build" script stopped on the same
85
+ * "given for org.gradle.java.home Gradle property is invalid"
86
+ * (nightly-baseline 92). So the line itself is rewritten, in every
87
+ * gradle.properties of the checkout, once the build's JDK is chosen. A path
88
+ * that exists, an empty value or a commented-out line is left alone.
89
+ */
90
+ export declare const GRADLE_JAVA_HOME_PIN_FUNCTIONS = "# $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# Run once JAVA_HOME is the build's JDK. Searches the checkout\n# (CIBUILD_SOURCE_DIR, else the current directory) to the depth a nested\n# Gradle project sits at, and prints one line per file it rewrites.\ncibuild_gradle_java_home_pin() {\n [ -n \"${JAVA_HOME:-}\" ] && [ -d \"$JAVA_HOME\" ] || return 0\n local root f l v tmp\n root=\"${CIBUILD_SOURCE_DIR:-.}\"\n while IFS= read -r f; do\n l=$(cibuild_prop_line \"$f\" org.gradle.java.home)\n [ -n \"$l\" ] || continue\n v=\"${l#*\t}\"\n [ -n \"$v\" ] || continue\n [ -d \"$v\" ] && continue\n tmp=$(mktemp)\n awk -v key=org.gradle.java.home -v home=\"$JAVA_HOME\" '\n { line = $0; sub(/^[ \\t]+/, \"\", line) }\n line !~ /^[#!]/ { k = line; sub(/[ \\t]*[=:].*$/, \"\", k); if (k == key) { print key \"=\" home; next } }\n { print }\n ' \"$f\" > \"$tmp\" && cat \"$tmp\" > \"$f\"\n rm -f \"$tmp\"\n echo \"Gradle: org.gradle.java.home=$v at ${f#\"$root\"/}:${l%%\t*} is not on this machine; that line now names this build's JDK, $JAVA_HOME, so every Gradle run in this build uses it (a script's ./gradlew and Xcode's build phases as well as the Gradle steps).\"\n done < <(find \"$root\" -maxdepth 4 \\( -name .git -o -name node_modules -o -name build -o -name .gradle \\) -prune -o -type f -name gradle.properties -print 2>/dev/null)\n}";
61
91
  //# sourceMappingURL=gradle-memory.d.ts.map
@@ -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,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"}
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;AAmB5C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kCAAkC,smBAgB9C,CAAC;AAEF,eAAO,MAAM,uBAAuB,gjgBAsVlC,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;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,y0DAwBzC,CAAC"}
@@ -46,18 +46,8 @@
46
46
  */
47
47
  /** The share of the machine's memory the two daemon heaps may take together. */
48
48
  export const GRADLE_HEAP_SHARE_PERCENT = 75;
49
- export const GRADLE_MEMORY_FUNCTIONS = `# This machine's memory in MiB, or nothing when it cannot be read.
50
- cibuild_mem_mib() {
51
- if [ -r /proc/meminfo ]; then
52
- awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo
53
- else
54
- local bytes
55
- bytes=$(sysctl -n hw.memsize 2>/dev/null || true)
56
- if [ -n "$bytes" ]; then echo $(( bytes / 1048576 )); fi
57
- fi
58
- }
59
-
60
- # $1 a .properties file, $2 a key. Prints "<line number><TAB><value>" for the
49
+ /** Reads one key of a .properties file, as Java does: the last assignment wins. */
50
+ const GRADLE_PROP_LINE_FUNCTION = `# $1 a .properties file, $2 a key. Prints "<line number><TAB><value>" for the
61
51
  # last assignment of the key, or nothing.
62
52
  cibuild_prop_line() {
63
53
  [ -f "$1" ] || return 0
@@ -70,8 +60,53 @@ cibuild_prop_line() {
70
60
  }
71
61
  END { if (found != "") print found }
72
62
  ' "$1"
63
+ }`;
64
+ /**
65
+ * Sends a Kotlin Multiplatform project's metadata compiles
66
+ * (compile*KotlinMetadata, the KotlinCompileCommon tasks) to the Kotlin daemon
67
+ * while every other Kotlin compile stays in-process. With
68
+ * -Pkotlin.compiler.execution.strategy=in-process, Kotlin 2.2.0 compiled
69
+ * DroidKaigi/conference-app-2025's common metadata in K1 mode and rejected its
70
+ * context parameters; the same task on the daemon passed (nightly-baseline 95).
71
+ *
72
+ * The Kotlin plugin's classes are not on an init script's classpath, so the
73
+ * task is matched by class name and the strategy enum is loaded through the
74
+ * task's own classloader. A Kotlin plugin without the property (older than the
75
+ * strategy API) is left alone: this script must never fail a build. Lazy
76
+ * (configureEach where Gradle has it), so it is configuration-cache safe and
77
+ * realizes no task. It runs once per build, included builds as well; they
78
+ * have no metadata compiles to touch.
79
+ */
80
+ export const KOTLIN_METADATA_DAEMON_INIT_SCRIPT = `// cibuild: Kotlin metadata compiles run in the Kotlin daemon (nightly-baseline 95).
81
+ allprojects {
82
+ def hook = { t ->
83
+ if (!t.class.name.startsWith('org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon')) return
84
+ try {
85
+ def strategy = t.class.classLoader.loadClass('org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy')
86
+ t.compilerExecutionStrategy.set(strategy.enumConstants.find { it.name() == 'DAEMON' })
87
+ } catch (Throwable ignored) {
88
+ }
89
+ }
90
+ try {
91
+ tasks.configureEach(hook)
92
+ } catch (MissingMethodException ignored) {
93
+ tasks.all(hook)
94
+ }
95
+ }
96
+ `;
97
+ export const GRADLE_MEMORY_FUNCTIONS = `# This machine's memory in MiB, or nothing when it cannot be read.
98
+ cibuild_mem_mib() {
99
+ if [ -r /proc/meminfo ]; then
100
+ awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo
101
+ else
102
+ local bytes
103
+ bytes=$(sysctl -n hw.memsize 2>/dev/null || true)
104
+ if [ -n "$bytes" ]; then echo $(( bytes / 1048576 )); fi
105
+ fi
73
106
  }
74
107
 
108
+ ${GRADLE_PROP_LINE_FUNCTION}
109
+
75
110
  # The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),
76
111
  # or nothing when it has none.
77
112
  cibuild_xmx_mib() {
@@ -153,10 +188,32 @@ cibuild_scaled_heap() {
153
188
  echo "$v"
154
189
  }
155
190
 
191
+ # The Kotlin daemon heap a multiplatform project's metadata compiles get out
192
+ # of a budget in MiB: an eighth, rounded down to 64 MiB, from 512 MiB to 1 GiB.
193
+ cibuild_metadata_daemon_heap() {
194
+ local v=$(( $1 / 8 / 64 * 64 ))
195
+ [ "$v" -ge 512 ] || v=512
196
+ [ "$v" -le 1024 ] || v=1024
197
+ echo "$v"
198
+ }
199
+
200
+ # Whether the Gradle project here applies Kotlin Multiplatform: a build script
201
+ # or version catalog that names the plugin. Convention plugins in buildSrc or
202
+ # an included build sit a few directories down; build outputs are not read.
203
+ cibuild_applies_kotlin_multiplatform() {
204
+ local f
205
+ while IFS= read -r -d '' f; do
206
+ if grep -q -E 'kotlin\\("multiplatform"\\)|org\\.jetbrains\\.kotlin\\.multiplatform|kotlin-multiplatform|kotlinMultiplatform' "$f" 2>/dev/null; then return 0; fi
207
+ done < <(find . -maxdepth 6 \\( -name .git -o -name node_modules -o -name build -o -name .gradle \\) -prune -o -type f \\( -name '*.gradle' -o -name '*.gradle.kts' -o -name '*.versions.toml' \\) -print0 2>/dev/null)
208
+ return 1
209
+ }
210
+
156
211
  # The plan, from its inputs only, so a test can run it:
157
212
  # $1 this machine's memory in MiB (empty when unknown)
158
213
  # $2 the project's gradle.properties $3 the Gradle user home's
159
214
  # $4 JVM arguments the pipeline asks for instead (may be empty)
215
+ # $5 the metadata init script, when the project applies Kotlin
216
+ # Multiplatform (empty otherwise)
160
217
  # Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),
161
218
  # CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when
162
219
  # nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a
@@ -167,7 +224,7 @@ cibuild_scaled_heap() {
167
224
  # its own inherits the Gradle daemon's, and in-process compilation has no
168
225
  # Kotlin daemon at all.
169
226
  cibuild_gradle_memory_plan() {
170
- local mem="$1" project="$2" user="$3" preferred="$4" l
227
+ local mem="$1" project="$2" user="$3" preferred="$4" init="\${5:-}" l
171
228
  local jvm="" jvm_at="" kd="" kd_at="" strategy=""
172
229
  CIBUILD_GRADLE_MEMORY_ARGS=()
173
230
  CIBUILD_GRADLE_MEMORY_LINE=""
@@ -237,8 +294,11 @@ cibuild_gradle_memory_plan() {
237
294
  # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was
238
295
  # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out
239
296
  # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.
240
- local ng now newjvm mg mk
241
- ng=$(cibuild_scaled_heap "$budget" 1 1)
297
+ # A multiplatform project's metadata compiles are the exception: they go to
298
+ # a small Kotlin daemon (the init script), carved out of the same budget.
299
+ local ng now newjvm mg mk md=""
300
+ if [ "$k" -gt 0 ] && [ -n "$init" ]; then md=$(cibuild_metadata_daemon_heap "$budget"); fi
301
+ ng=$(cibuild_scaled_heap $(( budget - \${md:-0} )) 1 1)
242
302
  newjvm=$(cibuild_with_xmx "$jvm" "$ng")
243
303
  now="Gradle -Xmx\${ng}m"
244
304
  if [ "$k" -gt 0 ]; then
@@ -258,10 +318,16 @@ cibuild_gradle_memory_plan() {
258
318
  fi
259
319
  now="$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)"
260
320
  fi
321
+ if [ -n "$md" ]; then
322
+ now="$now, and Kotlin metadata compiles (compile*KotlinMetadata) in a Kotlin daemon of -Xmx\${md}m beside it (in-process, Kotlin 2.2 compiled them in K1 mode)"
323
+ fi
261
324
  CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$newjvm")
262
325
  if [ "$k" -gt 0 ]; then
263
326
  CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.compiler.execution.strategy=in-process")
264
327
  fi
328
+ if [ -n "$md" ]; then
329
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.daemon.jvmargs=-Xmx\${md}m" --init-script "$init")
330
+ fi
265
331
  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."
266
332
  CIBUILD_GRADLE_MEMORY_SENTENCE="$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now."
267
333
  }
@@ -355,8 +421,14 @@ cibuild_gradle() {
355
421
  # (either may be empty). Run in the Gradle project directory, after GRADLE_CMD
356
422
  # is chosen.
357
423
  cibuild_gradle_memory_cap() {
424
+ local metadata_init=""
425
+ if cibuild_applies_kotlin_multiplatform; then
426
+ metadata_init="$(mktemp -d)/cibuild-kotlin-metadata-daemon.init.gradle"
427
+ cat > "$metadata_init" <<'CIBUILD_KOTLIN_METADATA_INIT'
428
+ ${KOTLIN_METADATA_DAEMON_INIT_SCRIPT}CIBUILD_KOTLIN_METADATA_INIT
429
+ fi
358
430
  cibuild_gradle_memory_plan "$(cibuild_mem_mib || true)" "gradle.properties" \\
359
- "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" "$1"
431
+ "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" "$1" "$metadata_init"
360
432
  cibuild_gradle_workers_plan "$2"
361
433
  cibuild_gradle_java_home_plan "gradle.properties" "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties"
362
434
  if [ -n "$CIBUILD_GRADLE_MEMORY_LINE" ]; then echo "$CIBUILD_GRADLE_MEMORY_LINE"; fi
@@ -383,4 +455,41 @@ export function gradleMemoryCommands(jvmargs, escape, maxWorkers = '') {
383
455
  `cibuild_gradle_memory_cap '${escape(jvmargs)}' '${escape(maxWorkers)}'`,
384
456
  ];
385
457
  }
458
+ /**
459
+ * A pinned org.gradle.java.home that is not on this machine, made to name the
460
+ * build's JDK for every Gradle run in the build. The Gradle steps already pass
461
+ * -Dorg.gradle.java.home on their own command line; a script step's
462
+ * ./gradlew and a build phase Xcode runs do not, and they read the checkout's
463
+ * gradle.properties. SEAbdulbasit/MusicApp-KMP's Android step went on with the
464
+ * override and its KMM "Prepare the iOS build" script stopped on the same
465
+ * "given for org.gradle.java.home Gradle property is invalid"
466
+ * (nightly-baseline 92). So the line itself is rewritten, in every
467
+ * gradle.properties of the checkout, once the build's JDK is chosen. A path
468
+ * that exists, an empty value or a commented-out line is left alone.
469
+ */
470
+ export const GRADLE_JAVA_HOME_PIN_FUNCTIONS = `${GRADLE_PROP_LINE_FUNCTION}
471
+
472
+ # Run once JAVA_HOME is the build's JDK. Searches the checkout
473
+ # (CIBUILD_SOURCE_DIR, else the current directory) to the depth a nested
474
+ # Gradle project sits at, and prints one line per file it rewrites.
475
+ cibuild_gradle_java_home_pin() {
476
+ [ -n "\${JAVA_HOME:-}" ] && [ -d "$JAVA_HOME" ] || return 0
477
+ local root f l v tmp
478
+ root="\${CIBUILD_SOURCE_DIR:-.}"
479
+ while IFS= read -r f; do
480
+ l=$(cibuild_prop_line "$f" org.gradle.java.home)
481
+ [ -n "$l" ] || continue
482
+ v="\${l#* }"
483
+ [ -n "$v" ] || continue
484
+ [ -d "$v" ] && continue
485
+ tmp=$(mktemp)
486
+ awk -v key=org.gradle.java.home -v home="$JAVA_HOME" '
487
+ { line = $0; sub(/^[ \\t]+/, "", line) }
488
+ line !~ /^[#!]/ { k = line; sub(/[ \\t]*[=:].*$/, "", k); if (k == key) { print key "=" home; next } }
489
+ { print }
490
+ ' "$f" > "$tmp" && cat "$tmp" > "$f"
491
+ rm -f "$tmp"
492
+ echo "Gradle: org.gradle.java.home=$v at \${f#"$root"/}:\${l%% *} is not on this machine; that line now names this build's JDK, $JAVA_HOME, so every Gradle run in this build uses it (a script's ./gradlew and Xcode's build phases as well as the Gradle steps)."
493
+ done < <(find "$root" -maxdepth 4 \\( -name .git -o -name node_modules -o -name build -o -name .gradle \\) -prune -o -type f -name gradle.properties -print 2>/dev/null)
494
+ }`;
386
495
  //# sourceMappingURL=gradle-memory.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kotlin-metadata-compiles-on-a-daemon.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kotlin-metadata-compiles-on-a-daemon.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/kotlin-metadata-compiles-on-a-daemon.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Kotlin metadata compiles run in a Kotlin daemon when the memory plan puts
3
+ * every other Kotlin compile in-process (nightly-baseline 95).
4
+ *
5
+ * DroidKaigi/conference-app-2025 asks for -Xmx6g; on a 6 GiB guest the plan
6
+ * (nightly-baseline 79) runs one heap and passes
7
+ * -Pkotlin.compiler.execution.strategy=in-process. With Kotlin 2.2.0, the
8
+ * in-process :core:common:compileCommonMainKotlinMetadata then failed with
9
+ * "Context parameters are not supported in K1 mode", while the same task on
10
+ * the Kotlin daemon passed (measured at the pin, five runs each). So a project
11
+ * that applies Kotlin Multiplatform gets an init script that sends only its
12
+ * metadata compiles to a small Kotlin daemon, carved out of the same budget.
13
+ *
14
+ * The shell under test is the one that ships: it is executed here.
15
+ */
16
+ import { execFileSync } from "node:child_process";
17
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { dirname, join } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { GRADLE_MEMORY_FUNCTIONS, KOTLIN_METADATA_DAEMON_INIT_SCRIPT } from "./gradle-memory.js";
22
+ const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "../../../test/fixtures/kotlin-metadata-daemon");
23
+ const SIX_GIB = "6144";
24
+ let dir;
25
+ beforeEach(() => {
26
+ dir = mkdtempSync(join(tmpdir(), "cibuild-kotlin-metadata-"));
27
+ });
28
+ afterEach(() => {
29
+ rmSync(dir, { recursive: true, force: true });
30
+ });
31
+ function bash(script, args = []) {
32
+ return execFileSync("bash", ["-c", `${GRADLE_MEMORY_FUNCTIONS}\n${script}`, "t", ...args], {
33
+ cwd: dir,
34
+ encoding: "utf-8",
35
+ });
36
+ }
37
+ /** The shipped plan, as the step runs it, with an init script path for a multiplatform project. */
38
+ function plan(memMib, initScript) {
39
+ const out = bash(`cibuild_gradle_memory_plan "$1" gradle.properties "$2/gradle.properties" "" "$3"
40
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done
41
+ printf 'LINE %s\\n' "$CIBUILD_GRADLE_MEMORY_LINE"`, [memMib, join(dir, "no-user-home"), initScript]);
42
+ const lines = out.split("\n");
43
+ return {
44
+ args: lines.filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4)),
45
+ line: (lines.find((l) => l.startsWith("LINE ")) ?? "").slice(5),
46
+ };
47
+ }
48
+ describe("a multiplatform project's metadata compiles leave the in-process plan", () => {
49
+ beforeEach(() => {
50
+ cpSync(join(FIXTURES, "droidkaigi"), dir, { recursive: true });
51
+ });
52
+ test("DroidKaigi on a 6 GiB guest: one Gradle heap, less a small Kotlin daemon for metadata", () => {
53
+ const p = plan(SIX_GIB, "/x/metadata.init.gradle");
54
+ // 75 % of 6 GiB is 4608 MiB: 576 of it for the metadata daemon, the rest Gradle's.
55
+ expect(p.args).toEqual([
56
+ "-Dorg.gradle.jvmargs=-Xmx4032m -XX:MaxMetaspaceSize=6144m -Dfile.encoding=UTF-8",
57
+ "-Pkotlin.compiler.execution.strategy=in-process",
58
+ "-Pkotlin.daemon.jvmargs=-Xmx576m",
59
+ "--init-script",
60
+ "/x/metadata.init.gradle",
61
+ ]);
62
+ expect(p.line).toContain("Kotlin metadata compiles (compile*KotlinMetadata) in a Kotlin daemon of -Xmx576m");
63
+ });
64
+ test("a project that is not multiplatform keeps 79's plan exactly", () => {
65
+ expect(plan(SIX_GIB, "").args).toEqual([
66
+ "-Dorg.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=6144m -Dfile.encoding=UTF-8",
67
+ "-Pkotlin.compiler.execution.strategy=in-process",
68
+ ]);
69
+ });
70
+ test("when the heaps fit, nothing is in-process and no init script is passed", () => {
71
+ expect(plan("32768", "/x/metadata.init.gradle").args).toEqual([]);
72
+ });
73
+ test("the metadata daemon's heap grows with the budget, to 1 GiB", () => {
74
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx16g\n");
75
+ const p = plan("16384", "/x/metadata.init.gradle");
76
+ expect(p.args).toContain("-Pkotlin.daemon.jvmargs=-Xmx1024m");
77
+ expect(p.args[0]).toBe("-Dorg.gradle.jvmargs=-Xmx11264m");
78
+ });
79
+ });
80
+ describe("cibuild_applies_kotlin_multiplatform", () => {
81
+ test("DroidKaigi's version catalog names the multiplatform plugin", () => {
82
+ cpSync(join(FIXTURES, "droidkaigi"), dir, { recursive: true });
83
+ expect(bash("cibuild_applies_kotlin_multiplatform && echo yes || echo no").trim()).toBe("yes");
84
+ });
85
+ test('kotlin("multiplatform") in a build script', () => {
86
+ mkdirSync(join(dir, "shared"));
87
+ writeFileSync(join(dir, "shared/build.gradle.kts"), 'plugins {\n kotlin("multiplatform")\n}\n');
88
+ expect(bash("cibuild_applies_kotlin_multiplatform && echo yes || echo no").trim()).toBe("yes");
89
+ });
90
+ test("an Android-only project is not", () => {
91
+ mkdirSync(join(dir, "app"));
92
+ writeFileSync(join(dir, "app/build.gradle.kts"), 'plugins {\n id("com.android.application")\n kotlin("android")\n}\n');
93
+ writeFileSync(join(dir, "settings.gradle.kts"), 'include(":app")\n');
94
+ expect(bash("cibuild_applies_kotlin_multiplatform && echo yes || echo no").trim()).toBe("no");
95
+ });
96
+ test("a build directory is not read", () => {
97
+ mkdirSync(join(dir, "build/tmp"), { recursive: true });
98
+ writeFileSync(join(dir, "build/tmp/x.gradle.kts"), 'kotlin("multiplatform")\n');
99
+ expect(bash("cibuild_applies_kotlin_multiplatform && echo yes || echo no").trim()).toBe("no");
100
+ });
101
+ });
102
+ describe("cibuild_gradle_memory_cap on a multiplatform project", () => {
103
+ test("writes the init script and runs Gradle with it", () => {
104
+ cpSync(join(FIXTURES, "droidkaigi"), dir, { recursive: true });
105
+ const out = bash(`cibuild_mem_mib() { echo ${SIX_GIB}; }
106
+ GRADLE_CMD=./gradlew
107
+ cibuild_gradle_memory_cap '' ''
108
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done`);
109
+ const args = out.split("\n").filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4));
110
+ const at = args.indexOf("--init-script");
111
+ expect(at).toBeGreaterThan(0);
112
+ const script = args[at + 1];
113
+ expect(existsSync(script)).toBe(true);
114
+ expect(readFileSync(script, "utf-8")).toBe(KOTLIN_METADATA_DAEMON_INIT_SCRIPT);
115
+ expect(out).toContain("Kotlin metadata compiles (compile*KotlinMetadata) in a Kotlin daemon");
116
+ rmSync(dirname(script), { recursive: true, force: true });
117
+ });
118
+ test("an Android-only project gets no init script", () => {
119
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx8g\n");
120
+ const out = bash(`cibuild_mem_mib() { echo ${SIX_GIB}; }
121
+ GRADLE_CMD=./gradlew
122
+ cibuild_gradle_memory_cap '' ''
123
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done`);
124
+ expect(out).not.toContain("--init-script");
125
+ expect(out).toContain("ARG -Pkotlin.compiler.execution.strategy=in-process");
126
+ });
127
+ });
128
+ describe("the init script", () => {
129
+ test("touches only the metadata compile task class, through the task's own classloader", () => {
130
+ expect(KOTLIN_METADATA_DAEMON_INIT_SCRIPT).toContain("org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon");
131
+ expect(KOTLIN_METADATA_DAEMON_INIT_SCRIPT).toContain("classLoader.loadClass('org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy')");
132
+ expect(KOTLIN_METADATA_DAEMON_INIT_SCRIPT).toContain("'DAEMON'");
133
+ });
134
+ test("never fails a build: an older Kotlin plugin without the strategy property is left alone", () => {
135
+ expect(KOTLIN_METADATA_DAEMON_INIT_SCRIPT).toMatch(/catch \(Throwable/);
136
+ expect(KOTLIN_METADATA_DAEMON_INIT_SCRIPT).not.toContain("${");
137
+ });
138
+ });
139
+ //# sourceMappingURL=kotlin-metadata-compiles-on-a-daemon.test.js.map