@invarn/cibuild 2.8.5 → 2.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,8 @@ export interface GradleBuildInputs {
20
20
  gradle_options?: string;
21
21
  variant?: string;
22
22
  module?: string;
23
+ /** JVM arguments to run the Gradle daemon with instead of the project's org.gradle.jvmargs; capped to the machine like the project's own. */
24
+ jvmargs?: string;
23
25
  }
24
26
  /**
25
27
  * Inputs for android-lint step
@@ -28,6 +30,8 @@ export interface AndroidLintInputs {
28
30
  project_location?: string;
29
31
  module?: string;
30
32
  variant?: string;
33
+ /** JVM arguments to run the Gradle daemon with instead of the project's org.gradle.jvmargs; capped to the machine like the project's own. */
34
+ jvmargs?: string;
31
35
  }
32
36
  /**
33
37
  * Inputs for android-unit-test step
@@ -37,6 +41,8 @@ export interface AndroidUnitTestInputs {
37
41
  module?: string;
38
42
  variant?: string;
39
43
  test_filter?: string;
44
+ /** JVM arguments to run the Gradle daemon with instead of the project's org.gradle.jvmargs; capped to the machine like the project's own. */
45
+ jvmargs?: string;
40
46
  }
41
47
  /**
42
48
  * Set Java version step executor
@@ -127,6 +133,8 @@ export interface AndroidBuildForUITestingInputs {
127
133
  module?: string;
128
134
  variant?: string;
129
135
  arguments?: string;
136
+ /** JVM arguments to run the Gradle daemon with instead of the project's org.gradle.jvmargs; capped to the machine like the project's own. */
137
+ jvmargs?: string;
130
138
  }
131
139
  /**
132
140
  * Builds app APK and test APK for Android instrumented (UI) tests.
@@ -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":"AAAA;;GAEG;AAUH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,WAAW,CAAC;AAElE,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;CAClB;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;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6IAA6I;IAC7I,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;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;CA+P1G;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;CA8L1G;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;CAyM9G;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;CAClB;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;CAuEzH"}
@@ -9,6 +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
13
  /**
13
14
  * Set Java version step executor
14
15
  * Sets the Java version to use for Android builds
@@ -459,6 +460,7 @@ export class GradleBuildStepExecutor extends BaseStepExecutor {
459
460
  commands.push(' echo "Error: Neither ./gradlew nor gradle command found"');
460
461
  commands.push(' exit 1');
461
462
  commands.push('fi');
463
+ commands.push(...gradleMemoryCommands(this.getInput(inputs, 'jvmargs', ''), (v) => this.escapeBash(v)));
462
464
  // Build gradle command
463
465
  commands.push('');
464
466
  commands.push('# Timestamp marker — used to collect only artifacts from this build');
@@ -780,6 +782,7 @@ export class AndroidLintStepExecutor extends BaseStepExecutor {
780
782
  commands.push(' echo "Error: Neither ./gradlew nor gradle command found"');
781
783
  commands.push(' exit 1');
782
784
  commands.push('fi');
785
+ commands.push(...gradleMemoryCommands(this.getInput(inputs, 'jvmargs', ''), (v) => this.escapeBash(v)));
783
786
  // Build lint task name
784
787
  commands.push('');
785
788
  commands.push('# Run lint');
@@ -969,6 +972,7 @@ export class AndroidUnitTestStepExecutor extends BaseStepExecutor {
969
972
  commands.push(' echo "Error: Neither ./gradlew nor gradle command found"');
970
973
  commands.push(' exit 1');
971
974
  commands.push('fi');
975
+ commands.push(...gradleMemoryCommands(this.getInput(inputs, 'jvmargs', ''), (v) => this.escapeBash(v)));
972
976
  // Build test task name
973
977
  commands.push('');
974
978
  commands.push('# Run unit tests');
@@ -1036,6 +1040,7 @@ export class AndroidBuildForUITestingStepExecutor extends BaseStepExecutor {
1036
1040
  commands.push('else');
1037
1041
  commands.push(' GRADLE_CMD="gradle"');
1038
1042
  commands.push('fi');
1043
+ commands.push(...gradleMemoryCommands(this.getInput(inputs, 'jvmargs', ''), (v) => this.escapeBash(v)));
1039
1044
  commands.push('');
1040
1045
  // Build both app and test APKs
1041
1046
  const appTask = gradleTaskPath(this.escapeBash(module), `assemble${variantCap}`);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Gradle's daemon heaps, fitted to the machine the build runs on.
3
+ *
4
+ * A project's `gradle.properties` sizes its heaps for a developer laptop. On a
5
+ * 10 GiB build guest, `org.gradle.jvmargs=-Xmx8G` (which the Kotlin daemon
6
+ * inherits) or `-Xmx6g` beside `kotlin.daemon.jvmargs=-Xmx8g` asks for more
7
+ * than the machine has, and the kernel kills a daemon mid-build. All the log
8
+ * then says is "Gradle build daemon disappeared unexpectedly". IacobIonut01/ReFra
9
+ * and vitorpamplona/amethyst are the two measured cases; ReFra's own CI lowers
10
+ * its heap to 4g before building.
11
+ *
12
+ * Two things, both at run time, in the Gradle project directory:
13
+ *
14
+ * 1. **Cap.** Read the machine's memory and the declared `-Xmx` of the Gradle
15
+ * daemon and the Kotlin daemon. When their sum is past
16
+ * `GRADLE_HEAP_SHARE_PERCENT` of memory, scale both down to fit, and pass
17
+ * them on the command line (`-Dorg.gradle.jvmargs=`,
18
+ * `-Pkotlin.daemon.jvmargs=`). One line names the old values and the new.
19
+ * 2. **Say so.** When a Gradle run exits 137 or its output says the daemon
20
+ * disappeared, print the kernel's out-of-memory lines and one sentence
21
+ * with the machine's memory and what the build asked for.
22
+ *
23
+ * Precedence, measured with Gradle 8.14.3 and 9.5.0 (daemon and --no-daemon):
24
+ * `-Dorg.gradle.jvmargs` on the command line beats both the Gradle user
25
+ * home's and the project's `gradle.properties` for the daemon's heap, and
26
+ * `-P` beats `gradle.properties` for a Gradle property, which is how the
27
+ * Kotlin Gradle plugin reads `kotlin.daemon.jvmargs`. `GRADLE_OPTS` is not
28
+ * used: it sizes the client JVM, not the daemon.
29
+ *
30
+ * Written as shell functions so what ships is what the tests run. Its lines
31
+ * start with "Gradle memory:", never with a step-progress marker.
32
+ */
33
+ /** The share of the machine's memory the two daemon heaps may take together. */
34
+ export declare const GRADLE_HEAP_SHARE_PERCENT = 75;
35
+ 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 local ng nk new_jvm\n ng=$(cibuild_scaled_heap \"$g\" \"$budget\" \"$total\")\n new_jvm=$(cibuild_with_xmx \"$jvm\" \"$ng\")\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$new_jvm\")\n local now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -eq 0 ]; then\n :\n elif [ -n \"$kd_own\" ]; then\n nk=$(cibuild_scaled_heap \"$k\" \"$budget\" \"$total\")\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.daemon.jvmargs=$(cibuild_with_xmx \"$kd\" \"$nk\")\")\n now=\"$now and the Kotlin daemon -Xmx${nk}m\"\n else\n now=\"$now, which the Kotlin daemon inherits\"\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# 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 if [ \"$1\" -ne 137 ] && ! 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 echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\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 (may be empty). Run in the Gradle\n# project directory, after GRADLE_CMD 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 if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
36
+ /**
37
+ * The commands a Gradle step runs once it has chosen `GRADLE_CMD` and is in
38
+ * the project directory. Afterwards `$GRADLE_CMD` runs Gradle with the plan's
39
+ * arguments and reports a killed daemon.
40
+ *
41
+ * `jvmargs` is what the pipeline asks for instead of the project's
42
+ * `org.gradle.jvmargs` (a value the repository's own CI sets, say); the cap
43
+ * still applies to it.
44
+ */
45
+ export declare function gradleMemoryCommands(jvmargs: string, escape: (value: string) => string): string[];
46
+ //# sourceMappingURL=gradle-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradle-memory.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,eAAO,MAAM,uBAAuB,i/QA4NlC,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAAE,CAEjG"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Gradle's daemon heaps, fitted to the machine the build runs on.
3
+ *
4
+ * A project's `gradle.properties` sizes its heaps for a developer laptop. On a
5
+ * 10 GiB build guest, `org.gradle.jvmargs=-Xmx8G` (which the Kotlin daemon
6
+ * inherits) or `-Xmx6g` beside `kotlin.daemon.jvmargs=-Xmx8g` asks for more
7
+ * than the machine has, and the kernel kills a daemon mid-build. All the log
8
+ * then says is "Gradle build daemon disappeared unexpectedly". IacobIonut01/ReFra
9
+ * and vitorpamplona/amethyst are the two measured cases; ReFra's own CI lowers
10
+ * its heap to 4g before building.
11
+ *
12
+ * Two things, both at run time, in the Gradle project directory:
13
+ *
14
+ * 1. **Cap.** Read the machine's memory and the declared `-Xmx` of the Gradle
15
+ * daemon and the Kotlin daemon. When their sum is past
16
+ * `GRADLE_HEAP_SHARE_PERCENT` of memory, scale both down to fit, and pass
17
+ * them on the command line (`-Dorg.gradle.jvmargs=`,
18
+ * `-Pkotlin.daemon.jvmargs=`). One line names the old values and the new.
19
+ * 2. **Say so.** When a Gradle run exits 137 or its output says the daemon
20
+ * disappeared, print the kernel's out-of-memory lines and one sentence
21
+ * with the machine's memory and what the build asked for.
22
+ *
23
+ * Precedence, measured with Gradle 8.14.3 and 9.5.0 (daemon and --no-daemon):
24
+ * `-Dorg.gradle.jvmargs` on the command line beats both the Gradle user
25
+ * home's and the project's `gradle.properties` for the daemon's heap, and
26
+ * `-P` beats `gradle.properties` for a Gradle property, which is how the
27
+ * Kotlin Gradle plugin reads `kotlin.daemon.jvmargs`. `GRADLE_OPTS` is not
28
+ * used: it sizes the client JVM, not the daemon.
29
+ *
30
+ * Written as shell functions so what ships is what the tests run. Its lines
31
+ * start with "Gradle memory:", never with a step-progress marker.
32
+ */
33
+ /** The share of the machine's memory the two daemon heaps may take together. */
34
+ export const GRADLE_HEAP_SHARE_PERCENT = 75;
35
+ export const GRADLE_MEMORY_FUNCTIONS = `# This machine's memory in MiB, or nothing when it cannot be read.
36
+ cibuild_mem_mib() {
37
+ if [ -r /proc/meminfo ]; then
38
+ awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo
39
+ else
40
+ local bytes
41
+ bytes=$(sysctl -n hw.memsize 2>/dev/null || true)
42
+ if [ -n "$bytes" ]; then echo $(( bytes / 1048576 )); fi
43
+ fi
44
+ }
45
+
46
+ # $1 a .properties file, $2 a key. Prints "<line number><TAB><value>" for the
47
+ # last assignment of the key, or nothing.
48
+ cibuild_prop_line() {
49
+ [ -f "$1" ] || return 0
50
+ awk -v key="$2" '
51
+ { line = $0; sub(/^[ \\t]+/, "", line) }
52
+ line ~ /^[#!]/ { next }
53
+ {
54
+ k = line; sub(/[ \\t]*[=:].*$/, "", k)
55
+ if (k == key) { v = line; sub(/^[^=:]*[=:][ \\t]*/, "", v); sub(/[ \\t\\r]+$/, "", v); found = NR "\\t" v }
56
+ }
57
+ END { if (found != "") print found }
58
+ ' "$1"
59
+ }
60
+
61
+ # The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),
62
+ # or nothing when it has none.
63
+ cibuild_xmx_mib() {
64
+ printf '%s\\n' "$1" | tr ' \\t' '\\n\\n' | awk '
65
+ /^-Xmx[0-9]+[kKmMgGtT]?$/ {
66
+ n = substr($0, 5); u = substr(n, length(n))
67
+ if (u ~ /[0-9]/) { v = n / 1048576 }
68
+ else {
69
+ n = substr(n, 1, length(n) - 1)
70
+ if (u ~ /[kK]/) v = n / 1024
71
+ else if (u ~ /[mM]/) v = n
72
+ else if (u ~ /[gG]/) v = n * 1024
73
+ else v = n * 1048576
74
+ }
75
+ r = int(v)
76
+ }
77
+ END { if (r != "") print r }'
78
+ }
79
+
80
+ # $1 a JVM argument string, $2 a heap in MiB: the same arguments with every
81
+ # -Xmx replaced by that one (added in front when there was none).
82
+ cibuild_with_xmx() (
83
+ set -f
84
+ out=""
85
+ replaced=0
86
+ for arg in $1; do
87
+ case "$arg" in
88
+ -Xmx*) if [ "$replaced" -eq 0 ]; then out="$out -Xmx\${2}m"; replaced=1; fi ;;
89
+ *) out="$out $arg" ;;
90
+ esac
91
+ done
92
+ [ "$replaced" -eq 1 ] || out=" -Xmx\${2}m$out"
93
+ printf '%s' "\${out# }"
94
+ )
95
+
96
+ # MiB as GiB with one decimal.
97
+ cibuild_gib() {
98
+ awk -v m="$1" 'BEGIN { printf "%.1f", m / 1024 }'
99
+ }
100
+
101
+ # A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.
102
+ cibuild_scaled_heap() {
103
+ local v=$(( $1 * $2 / $3 / 64 * 64 ))
104
+ [ "$v" -ge 512 ] || v=512
105
+ echo "$v"
106
+ }
107
+
108
+ # The plan, from its inputs only, so a test can run it:
109
+ # $1 this machine's memory in MiB (empty when unknown)
110
+ # $2 the project's gradle.properties $3 the Gradle user home's
111
+ # $4 JVM arguments the pipeline asks for instead (may be empty)
112
+ # Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),
113
+ # CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when
114
+ # nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a
115
+ # daemon is killed).
116
+ #
117
+ # Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's
118
+ # gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of
119
+ # its own inherits the Gradle daemon's, and in-process compilation has no
120
+ # Kotlin daemon at all.
121
+ cibuild_gradle_memory_plan() {
122
+ local mem="$1" project="$2" user="$3" preferred="$4" l
123
+ local jvm="" jvm_at="" kd="" kd_at="" strategy=""
124
+ CIBUILD_GRADLE_MEMORY_ARGS=()
125
+ CIBUILD_GRADLE_MEMORY_LINE=""
126
+ CIBUILD_GRADLE_MEMORY_SENTENCE=""
127
+
128
+ if [ -n "$preferred" ]; then jvm="$preferred"; jvm_at="the pipeline"; fi
129
+ for f in "$user" "$project"; do
130
+ if [ -z "$jvm_at" ]; then
131
+ l=$(cibuild_prop_line "$f" org.gradle.jvmargs)
132
+ if [ -n "$l" ]; then jvm="\${l#* }"; jvm_at="$f:\${l%% *}"; fi
133
+ fi
134
+ if [ -z "$kd_at" ]; then
135
+ l=$(cibuild_prop_line "$f" kotlin.daemon.jvmargs)
136
+ if [ -n "$l" ]; then kd="\${l#* }"; kd_at="$f:\${l%% *}"; fi
137
+ fi
138
+ if [ -z "$strategy" ]; then
139
+ l=$(cibuild_prop_line "$f" kotlin.compiler.execution.strategy)
140
+ strategy="\${l#* }"
141
+ fi
142
+ done
143
+
144
+ local g k kd_own=""
145
+ g=$(cibuild_xmx_mib "$jvm")
146
+ [ -n "$g" ] || g=512
147
+ if [ "$strategy" = "in-process" ]; then
148
+ k=0
149
+ else
150
+ kd_own=$(cibuild_xmx_mib "$kd")
151
+ k="\${kd_own:-$g}"
152
+ fi
153
+ local total=$(( g + k ))
154
+
155
+ local asked
156
+ if [ -n "$jvm_at" ]; then asked="org.gradle.jvmargs=$jvm at \${jvm_at}"; else asked="Gradle's default -Xmx512m"; fi
157
+ if [ "$k" -eq 0 ]; then
158
+ asked="$asked; Kotlin compiles in-process"
159
+ elif [ -n "$kd_own" ]; then
160
+ asked="$asked, kotlin.daemon.jvmargs=$kd at \${kd_at}"
161
+ else
162
+ asked="$asked; the Kotlin daemon inherits its -Xmx"
163
+ fi
164
+
165
+ if [ -z "$mem" ]; then
166
+ 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."
167
+ if [ -n "$preferred" ]; then CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$preferred"); fi
168
+ return 0
169
+ fi
170
+
171
+ local budget=$(( mem * ${GRADLE_HEAP_SHARE_PERCENT} / 100 ))
172
+ 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)."
173
+
174
+ if [ "$total" -le "$budget" ]; then
175
+ if [ -n "$preferred" ]; then
176
+ CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$preferred")
177
+ CIBUILD_GRADLE_MEMORY_LINE="Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line)."
178
+ fi
179
+ return 0
180
+ fi
181
+
182
+ case "$jvm" in
183
+ *\\\\)
184
+ 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."
185
+ return 0
186
+ ;;
187
+ esac
188
+
189
+ local ng nk new_jvm
190
+ ng=$(cibuild_scaled_heap "$g" "$budget" "$total")
191
+ new_jvm=$(cibuild_with_xmx "$jvm" "$ng")
192
+ CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$new_jvm")
193
+ local now="Gradle -Xmx\${ng}m"
194
+ if [ "$k" -eq 0 ]; then
195
+ :
196
+ elif [ -n "$kd_own" ]; then
197
+ nk=$(cibuild_scaled_heap "$k" "$budget" "$total")
198
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.daemon.jvmargs=$(cibuild_with_xmx "$kd" "$nk")")
199
+ now="$now and the Kotlin daemon -Xmx\${nk}m"
200
+ else
201
+ now="$now, which the Kotlin daemon inherits"
202
+ fi
203
+ 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."
204
+ CIBUILD_GRADLE_MEMORY_SENTENCE="$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now."
205
+ }
206
+
207
+ # The kernel's out-of-memory lines, if this user may read the kernel log.
208
+ # Linux guests restrict dmesg to root by default; their build user has
209
+ # non-interactive sudo, so that is the second try.
210
+ cibuild_kernel_oom_lines() {
211
+ { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |
212
+ grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true
213
+ }
214
+
215
+ # $1 Gradle's exit status, $2 a file holding its output. Says so when the
216
+ # failure is a killed daemon rather than a failed build.
217
+ cibuild_gradle_memory_report() {
218
+ if [ "$1" -ne 137 ] && ! grep -q "daemon disappeared unexpectedly" "$2" 2>/dev/null; then
219
+ return 0
220
+ fi
221
+ local oom
222
+ oom=$(cibuild_kernel_oom_lines)
223
+ echo ""
224
+ echo "Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1)."
225
+ if [ -n "$oom" ]; then
226
+ echo "The kernel's out-of-memory lines:"
227
+ printf '%s\\n' "$oom" | sed 's/^/ /'
228
+ else
229
+ echo "The kernel log shows no out-of-memory kill, or this machine does not let the build read it."
230
+ fi
231
+ if [ -n "$CIBUILD_GRADLE_MEMORY_SENTENCE" ]; then echo "$CIBUILD_GRADLE_MEMORY_SENTENCE"; fi
232
+ }
233
+
234
+ # Runs Gradle with the plan's arguments, and reports a killed daemon.
235
+ cibuild_gradle() {
236
+ local log rc
237
+ log=$(mktemp)
238
+ set +e
239
+ "$CIBUILD_GRADLE_BIN" "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}" "$@" 2>&1 | tee "$log"
240
+ rc=\${PIPESTATUS[0]}
241
+ set -e
242
+ if [ "$rc" -ne 0 ]; then cibuild_gradle_memory_report "$rc" "$log" || true; fi
243
+ rm -f "$log"
244
+ return "$rc"
245
+ }
246
+
247
+ # $1 JVM arguments the pipeline asks for (may be empty). Run in the Gradle
248
+ # project directory, after GRADLE_CMD is chosen.
249
+ cibuild_gradle_memory_cap() {
250
+ cibuild_gradle_memory_plan "$(cibuild_mem_mib || true)" "gradle.properties" \\
251
+ "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" "$1"
252
+ if [ -n "$CIBUILD_GRADLE_MEMORY_LINE" ]; then echo "$CIBUILD_GRADLE_MEMORY_LINE"; fi
253
+ CIBUILD_GRADLE_BIN="$GRADLE_CMD"
254
+ GRADLE_CMD=cibuild_gradle
255
+ }`;
256
+ /**
257
+ * The commands a Gradle step runs once it has chosen `GRADLE_CMD` and is in
258
+ * the project directory. Afterwards `$GRADLE_CMD` runs Gradle with the plan's
259
+ * arguments and reports a killed daemon.
260
+ *
261
+ * `jvmargs` is what the pipeline asks for instead of the project's
262
+ * `org.gradle.jvmargs` (a value the repository's own CI sets, say); the cap
263
+ * still applies to it.
264
+ */
265
+ export function gradleMemoryCommands(jvmargs, escape) {
266
+ return ['', GRADLE_MEMORY_FUNCTIONS, '', `cibuild_gradle_memory_cap '${escape(jvmargs)}'`];
267
+ }
268
+ //# sourceMappingURL=gradle-memory.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gradle-memory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradle-memory.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Gradle's daemon heaps, fitted to the build machine; and a killed daemon,
3
+ * named as one.
4
+ *
5
+ * The fixtures are the `gradle.properties` of IacobIonut01/ReFra
6
+ * (`-Xmx8G`, inherited by the Kotlin daemon) and vitorpamplona/amethyst
7
+ * (`-Xmx6g` beside `kotlin.daemon.jvmargs=-Xmx8g`). Both died on a 10 GiB
8
+ * build machine with nothing in the log but "Gradle build daemon disappeared
9
+ * unexpectedly".
10
+ *
11
+ * The shell under test is the one that ships: it is executed here, not
12
+ * re-implemented.
13
+ */
14
+ import { execFileSync, spawnSync } from "node:child_process";
15
+ import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { AndroidBuildForUITestingStepExecutor, AndroidLintStepExecutor, AndroidUnitTestStepExecutor, GradleBuildStepExecutor, } from "./android.js";
20
+ import { GRADLE_MEMORY_FUNCTIONS } from "./gradle-memory.js";
21
+ import { testConfig } from "./test-config.js";
22
+ const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "../../../test/fixtures/gradle-memory");
23
+ let dir;
24
+ beforeEach(() => {
25
+ dir = mkdtempSync(join(tmpdir(), "cibuild-gradle-memory-"));
26
+ });
27
+ afterEach(() => {
28
+ rmSync(dir, { recursive: true, force: true });
29
+ });
30
+ /** Run the shipped plan in `dir`, the way the step does, with a given memory. */
31
+ function plan(memMib, preferred = "", userHome = join(dir, "no-user-home")) {
32
+ const script = `${GRADLE_MEMORY_FUNCTIONS}
33
+ cibuild_gradle_memory_plan "$1" gradle.properties "$2/gradle.properties" "$3"
34
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done
35
+ printf 'LINE %s\\n' "$CIBUILD_GRADLE_MEMORY_LINE"
36
+ printf 'SENTENCE %s\\n' "$CIBUILD_GRADLE_MEMORY_SENTENCE"`;
37
+ const out = execFileSync("bash", ["-c", script, "plan", memMib, userHome, preferred], {
38
+ cwd: dir,
39
+ encoding: "utf-8",
40
+ });
41
+ const lines = out.split("\n");
42
+ return {
43
+ args: lines.filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4)),
44
+ line: (lines.find((l) => l.startsWith("LINE ")) ?? "").slice(5),
45
+ sentence: (lines.find((l) => l.startsWith("SENTENCE ")) ?? "").slice(9),
46
+ };
47
+ }
48
+ const TEN_GIB = "10240";
49
+ describe("cibuild_gradle_memory_plan", () => {
50
+ test("ReFra: -Xmx8G, inherited by the Kotlin daemon, is capped on a 10 GiB machine", () => {
51
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
52
+ const p = plan(TEN_GIB);
53
+ // 75% of 10 GiB, halved because the Kotlin daemon inherits it. Their own
54
+ // CI runs 4g.
55
+ expect(p.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3840m -Dfile.encoding=UTF-8"]);
56
+ expect(p.line).toContain("org.gradle.jvmargs=-Xmx8G -Dfile.encoding=UTF-8 at gradle.properties:9");
57
+ expect(p.line).toContain("16.0 GiB");
58
+ expect(p.line).toContain("-Xmx3840m");
59
+ expect(p.sentence).toMatch(/^The build machine has 10\.0 GiB; this build's Gradle and Kotlin daemons asked for 16\.0 GiB/);
60
+ });
61
+ test("amethyst: both heaps are scaled, and the Kotlin one keeps its other flags", () => {
62
+ copyFileSync(join(FIXTURES, "amethyst.gradle.properties"), join(dir, "gradle.properties"));
63
+ const p = plan(TEN_GIB);
64
+ expect(p.args).toEqual([
65
+ "-Dorg.gradle.jvmargs=-Xmx3264m -Dfile.encoding=UTF-8",
66
+ "-Pkotlin.daemon.jvmargs=-Xmx4352m -XX:MaxMetaspaceSize=2g",
67
+ ]);
68
+ expect(p.line).toContain("kotlin.daemon.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=2g at gradle.properties:27");
69
+ expect(p.line).toContain("14.0 GiB");
70
+ });
71
+ test("heaps that fit are left alone, and nothing is printed", () => {
72
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
73
+ const p = plan("65536");
74
+ expect(p.args).toEqual([]);
75
+ expect(p.line).toBe("");
76
+ expect(p.sentence).toContain("asked for 16.0 GiB");
77
+ });
78
+ test("a project that declares nothing gets Gradle's default and no cap", () => {
79
+ const p = plan(TEN_GIB);
80
+ expect(p.args).toEqual([]);
81
+ expect(p.sentence).toContain("Gradle's default -Xmx512m");
82
+ });
83
+ test("in-process Kotlin compilation has no Kotlin daemon to count", () => {
84
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g\nkotlin.compiler.execution.strategy=in-process\n");
85
+ expect(plan("6144").args).toEqual([]);
86
+ expect(plan("4096").args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3072m"]);
87
+ });
88
+ test("the Gradle user home's value wins over the project's, as in Gradle", () => {
89
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx1g\n");
90
+ const home = join(dir, "home");
91
+ mkdirSync(home);
92
+ writeFileSync(join(home, "gradle.properties"), "org.gradle.jvmargs=-Xmx6g -XX:+UseParallelGC\n");
93
+ const p = plan(TEN_GIB, "", home);
94
+ expect(p.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx3840m -XX:+UseParallelGC"]);
95
+ expect(p.line).toContain(`at ${home}/gradle.properties:1`);
96
+ });
97
+ test("JVM arguments the pipeline asks for replace the project's, and are still capped", () => {
98
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
99
+ const fits = plan(TEN_GIB, "-Xmx2g -Dfile.encoding=UTF-8");
100
+ expect(fits.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8"]);
101
+ expect(fits.line).toContain("as the pipeline asks");
102
+ const capped = plan("4096", "-Xmx4g -Dfile.encoding=UTF-8");
103
+ expect(capped.args).toEqual(["-Dorg.gradle.jvmargs=-Xmx1536m -Dfile.encoding=UTF-8"]);
104
+ expect(capped.line).toContain("at the pipeline");
105
+ });
106
+ test("a value continued onto another line is left as it is, and says so", () => {
107
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx12g \\\n -Dfile.encoding=UTF-8\n");
108
+ const p = plan(TEN_GIB);
109
+ expect(p.args).toEqual([]);
110
+ expect(p.line).toContain("continues onto another line");
111
+ });
112
+ test("an unknown machine size caps nothing", () => {
113
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
114
+ const p = plan("");
115
+ expect(p.args).toEqual([]);
116
+ expect(p.sentence).toContain("could not be read");
117
+ });
118
+ test("-Xmx in every unit, and a comment is not an assignment", () => {
119
+ writeFileSync(join(dir, "gradle.properties"), "# org.gradle.jvmargs=-Xmx64g\norg.gradle.jvmargs = -Xms1g -Xmx6144m\nkotlin.daemon.jvmargs:-Xmx4194304k\n");
120
+ const p = plan("8192");
121
+ expect(p.args).toEqual([
122
+ "-Dorg.gradle.jvmargs=-Xms1g -Xmx3648m",
123
+ "-Pkotlin.daemon.jvmargs=-Xmx2432m",
124
+ ]);
125
+ });
126
+ });
127
+ /** A directory standing in for PATH, with a fake `dmesg` that prints `oom`. */
128
+ function fakeBin(oom) {
129
+ const bin = join(dir, "bin");
130
+ mkdirSync(bin, { recursive: true });
131
+ writeFileSync(join(bin, "dmesg"), `#!/bin/bash\nprintf '%s\\n' ${JSON.stringify(oom)}\n`);
132
+ chmodSync(join(bin, "dmesg"), 0o755);
133
+ return bin;
134
+ }
135
+ /** A `./gradlew` that records its arguments and fails the way it is told. */
136
+ function fakeGradlew(body) {
137
+ writeFileSync(join(dir, "gradlew"), `#!/bin/bash\nprintf '%s\\n' "$@" > "$(dirname "$0")/gradlew-args"\n${body}\n`);
138
+ chmodSync(join(dir, "gradlew"), 0o755);
139
+ }
140
+ function runStep(script, bin) {
141
+ writeFileSync(join(dir, "step.sh"), script);
142
+ return spawnSync("bash", [join(dir, "step.sh")], {
143
+ cwd: dir,
144
+ encoding: "utf-8",
145
+ env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "" },
146
+ });
147
+ }
148
+ describe("the Gradle steps", () => {
149
+ const OOM_LINE = "[ 412.9] Out of memory: Killed process 4242 (java) total-vm:14680064kB, anon-rss:9437184kB";
150
+ test("a killed daemon prints the kernel's out-of-memory lines and the sentence, and still fails", async () => {
151
+ copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
152
+ fakeGradlew('echo "FAILURE: Build failed with an exception."\necho "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)"\nexit 1');
153
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
154
+ const run = runStep(step.script, fakeBin(OOM_LINE));
155
+ expect(run.status).toBe(1);
156
+ expect(run.stdout).toContain("Gradle memory: a Gradle daemon was killed rather than failing the build (exit 1).");
157
+ expect(run.stdout).toContain(` ${OOM_LINE}`);
158
+ expect(run.stdout).toMatch(/The build machine has [0-9.]+ GiB; this build's Gradle and Kotlin daemons asked for 16\.0 GiB/);
159
+ expect(run.stdout).not.toContain("Gradle build completed successfully");
160
+ });
161
+ test("an ordinary failure says nothing about memory", async () => {
162
+ fakeGradlew('echo "Compilation error"\nexit 1');
163
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
164
+ const run = runStep(step.script, fakeBin(OOM_LINE));
165
+ expect(run.status).toBe(1);
166
+ expect(run.stdout).not.toContain("Gradle memory:");
167
+ });
168
+ test("the pipeline's jvmargs reach Gradle as one argument, before the task", async () => {
169
+ fakeGradlew("exit 0");
170
+ const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug", gradle_options: "--stacktrace", jvmargs: "-Xmx256m -Dfile.encoding=UTF-8" }, {}, testConfig);
171
+ const run = runStep(step.script, fakeBin(""));
172
+ expect(run.status).toBe(0);
173
+ const args = execFileSync("cat", [join(dir, "gradlew-args")], { encoding: "utf-8" }).trim().split("\n");
174
+ expect(args).toEqual(["-Dorg.gradle.jvmargs=-Xmx256m -Dfile.encoding=UTF-8", "assembleDebug", "--stacktrace"]);
175
+ });
176
+ test("every Gradle step runs through the plan, and every one is valid bash", async () => {
177
+ const steps = [
178
+ await new GradleBuildStepExecutor().execute({}, {}, testConfig),
179
+ await new AndroidLintStepExecutor().execute({}, {}, testConfig),
180
+ await new AndroidUnitTestStepExecutor().execute({}, {}, testConfig),
181
+ await new AndroidBuildForUITestingStepExecutor().execute({}, {}, testConfig),
182
+ ];
183
+ for (const step of steps) {
184
+ const script = step.script;
185
+ expect(script).toContain("cibuild_gradle_memory_cap ''");
186
+ execFileSync("bash", ["-n"], { input: script });
187
+ }
188
+ });
189
+ // cibuild's log is parsed for step-progress markers; nothing here may look
190
+ // like one.
191
+ test("no line the functions print can be taken for a step-progress marker", () => {
192
+ for (const line of GRADLE_MEMORY_FUNCTIONS.split("\n")) {
193
+ expect(line).not.toMatch(/INVARN_(STEP|PHASE)|▸/);
194
+ }
195
+ });
196
+ });
197
+ //# sourceMappingURL=gradle-memory.test.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.8.5",
3
+ "version": "2.8.6",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",