@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.
- package/dist/cli.cjs +11 -11
- package/dist/src/yaml/steps/android.d.ts +8 -0
- package/dist/src/yaml/steps/android.d.ts.map +1 -1
- package/dist/src/yaml/steps/android.js +5 -0
- package/dist/src/yaml/steps/gradle-memory.d.ts +46 -0
- package/dist/src/yaml/steps/gradle-memory.d.ts.map +1 -0
- package/dist/src/yaml/steps/gradle-memory.js +268 -0
- package/dist/src/yaml/steps/gradle-memory.test.d.ts +2 -0
- package/dist/src/yaml/steps/gradle-memory.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/gradle-memory.test.js +197 -0
- package/package.json +1 -1
|
@@ -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":"AAAA;;GAEG;AAUH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
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
|