@invarn/cibuild 2.9.4 → 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.
- package/dist/cli.cjs +14 -14
- package/dist/src/yaml/steps/a-missing-pinned-jdk-yields-for-every-gradle-run.test.d.ts +2 -0
- package/dist/src/yaml/steps/a-missing-pinned-jdk-yields-for-every-gradle-run.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/a-missing-pinned-jdk-yields-for-every-gradle-run.test.js +96 -0
- package/dist/src/yaml/steps/android.d.ts.map +1 -1
- package/dist/src/yaml/steps/android.js +20 -4
- package/dist/src/yaml/steps/capture-injection.d.ts +30 -0
- package/dist/src/yaml/steps/capture-injection.d.ts.map +1 -0
- package/dist/src/yaml/steps/capture-injection.js +88 -0
- package/dist/src/yaml/steps/capture-injection.test.d.ts +2 -0
- package/dist/src/yaml/steps/capture-injection.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/capture-injection.test.js +163 -0
- package/dist/src/yaml/steps/gradle-memory.d.ts +31 -1
- package/dist/src/yaml/steps/gradle-memory.d.ts.map +1 -1
- package/dist/src/yaml/steps/gradle-memory.js +202 -18
- package/dist/src/yaml/steps/gradle-memory.test.js +48 -1
- package/dist/src/yaml/steps/kotlin-metadata-compiles-on-a-daemon.test.d.ts +2 -0
- package/dist/src/yaml/steps/kotlin-metadata-compiles-on-a-daemon.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/kotlin-metadata-compiles-on-a-daemon.test.js +139 -0
- package/dist/src/yaml/steps/xcode-derived-data.test.js +30 -4
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +17 -7
- package/package.json +1 -1
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,9 @@
|
|
|
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
|
+
import { appProjectOfTask, captureAssembleCommands, captureSetupCommands, isDebugAssembleTask } from './capture-injection.js';
|
|
14
15
|
/**
|
|
15
16
|
* Set Java version step executor
|
|
16
17
|
* Sets the Java version to use for Android builds
|
|
@@ -246,6 +247,14 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
|
|
|
246
247
|
commands.push('');
|
|
247
248
|
commands.push('# Persist JAVA_HOME to envman for subsequent steps');
|
|
248
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
|
+
}
|
|
249
258
|
const script = this.createBashScriptFromCommands(commands, stepName);
|
|
250
259
|
return this.createScriptStep(script, stepName);
|
|
251
260
|
}
|
|
@@ -469,11 +478,18 @@ export class GradleBuildStepExecutor extends BaseStepExecutor {
|
|
|
469
478
|
commands.push('touch /tmp/.cibuild-build-marker');
|
|
470
479
|
commands.push('');
|
|
471
480
|
commands.push('# Run Gradle build');
|
|
472
|
-
let
|
|
481
|
+
let gradleArgs = this.escapeBash(gradleTask);
|
|
473
482
|
if (gradleOptions && gradleOptions.trim() !== '') {
|
|
474
|
-
|
|
483
|
+
gradleArgs += ` ${gradleOptions}`;
|
|
484
|
+
}
|
|
485
|
+
if (isDebugAssembleTask(gradleTask)) {
|
|
486
|
+
// A debug assemble may carry the Invarn capture agent; see capture-injection.ts.
|
|
487
|
+
commands.push(...captureSetupCommands(appProjectOfTask(gradleTask), (v) => `'${this.escapeBash(v)}'`));
|
|
488
|
+
commands.push(...captureAssembleCommands(gradleArgs));
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
commands.push(`$GRADLE_CMD ${gradleArgs}`);
|
|
475
492
|
}
|
|
476
|
-
commands.push(gradleCmd);
|
|
477
493
|
commands.push('');
|
|
478
494
|
commands.push('# Build completed');
|
|
479
495
|
commands.push('echo "Gradle build completed successfully"');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Invarn capture agent in a debug build.
|
|
3
|
+
*
|
|
4
|
+
* When the dispatch sets INVARN_CAPTURE_VERSION, INVARN_CAPTURE_TOOLKIT_URL and
|
|
5
|
+
* INVARN_CAPTURE_SHA256 in the build's environment, a debug `assemble` runs with
|
|
6
|
+
* the toolkit's Gradle init script, which adds the capture agent to the app. The
|
|
7
|
+
* three files are downloaded outside the checkout and each is checked against the
|
|
8
|
+
* sha256 the dispatch named; nothing is written into the repository.
|
|
9
|
+
*
|
|
10
|
+
* The build never fails because of the agent: a download that fails, a checksum
|
|
11
|
+
* that does not match, or an instrumented assemble that fails all lead to the same
|
|
12
|
+
* assemble without the agent, and a `▸ INVARN_CAPTURE:` line says which happened.
|
|
13
|
+
* Without those variables the step runs exactly as it always did.
|
|
14
|
+
*/
|
|
15
|
+
/** A single Gradle task that assembles a debug variant: `assembleDebug`, `:app:assembleCiDynamicDebug`. */
|
|
16
|
+
export declare function isDebugAssembleTask(task: string): boolean;
|
|
17
|
+
/** The Gradle project the task assembles (`:app` for `:app:assembleDebug`), or null for a bare task. */
|
|
18
|
+
export declare function appProjectOfTask(task: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The shell that downloads and checks the three files, run before the assemble.
|
|
21
|
+
* Leaves CIBUILD_CAPTURE_INIT set to the init script's path when everything checked
|
|
22
|
+
* out, empty otherwise. Plain variables, no arrays: macOS ships bash 3.2.
|
|
23
|
+
*/
|
|
24
|
+
export declare function captureSetupCommands(appProject: string | null, quote: (v: string) => string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* The assemble itself: with the init script when the setup left one, and once more
|
|
27
|
+
* without it if that fails. Without an init script it is the plain command.
|
|
28
|
+
*/
|
|
29
|
+
export declare function captureAssembleCommands(gradleArgs: string): string[];
|
|
30
|
+
//# sourceMappingURL=capture-injection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-injection.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/capture-injection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAAE,CAmCtG;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBpE"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Invarn capture agent in a debug build.
|
|
3
|
+
*
|
|
4
|
+
* When the dispatch sets INVARN_CAPTURE_VERSION, INVARN_CAPTURE_TOOLKIT_URL and
|
|
5
|
+
* INVARN_CAPTURE_SHA256 in the build's environment, a debug `assemble` runs with
|
|
6
|
+
* the toolkit's Gradle init script, which adds the capture agent to the app. The
|
|
7
|
+
* three files are downloaded outside the checkout and each is checked against the
|
|
8
|
+
* sha256 the dispatch named; nothing is written into the repository.
|
|
9
|
+
*
|
|
10
|
+
* The build never fails because of the agent: a download that fails, a checksum
|
|
11
|
+
* that does not match, or an instrumented assemble that fails all lead to the same
|
|
12
|
+
* assemble without the agent, and a `▸ INVARN_CAPTURE:` line says which happened.
|
|
13
|
+
* Without those variables the step runs exactly as it always did.
|
|
14
|
+
*/
|
|
15
|
+
/** A single Gradle task that assembles a debug variant: `assembleDebug`, `:app:assembleCiDynamicDebug`. */
|
|
16
|
+
export function isDebugAssembleTask(task) {
|
|
17
|
+
return /^(:[A-Za-z0-9_.-]+)*:?assemble[A-Za-z0-9]*Debug$/.test(task.trim());
|
|
18
|
+
}
|
|
19
|
+
/** The Gradle project the task assembles (`:app` for `:app:assembleDebug`), or null for a bare task. */
|
|
20
|
+
export function appProjectOfTask(task) {
|
|
21
|
+
const match = /^((?::[A-Za-z0-9_.-]+)+):assemble/.exec(task.trim());
|
|
22
|
+
return match ? match[1] : null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The shell that downloads and checks the three files, run before the assemble.
|
|
26
|
+
* Leaves CIBUILD_CAPTURE_INIT set to the init script's path when everything checked
|
|
27
|
+
* out, empty otherwise. Plain variables, no arrays: macOS ships bash 3.2.
|
|
28
|
+
*/
|
|
29
|
+
export function captureSetupCommands(appProject, quote) {
|
|
30
|
+
return [
|
|
31
|
+
'',
|
|
32
|
+
'# Invarn capture agent: only when the dispatch sent the toolkit',
|
|
33
|
+
'CIBUILD_CAPTURE_INIT=""',
|
|
34
|
+
'if [ -n "${INVARN_CAPTURE_VERSION:-}" ] && [ -n "${INVARN_CAPTURE_TOOLKIT_URL:-}" ] && [ -n "${INVARN_CAPTURE_SHA256:-}" ]; then',
|
|
35
|
+
' CIBUILD_CAPTURE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cibuild-capture.XXXXXX")"',
|
|
36
|
+
' CIBUILD_CAPTURE_SKIP=""',
|
|
37
|
+
' for CIBUILD_CAPTURE_ROLE in init instrument agent; do',
|
|
38
|
+
' case "$CIBUILD_CAPTURE_ROLE" in',
|
|
39
|
+
' init) CIBUILD_CAPTURE_FILE="invarn-init-${INVARN_CAPTURE_VERSION}.gradle" ;;',
|
|
40
|
+
' instrument) CIBUILD_CAPTURE_FILE="invarn-instrument-${INVARN_CAPTURE_VERSION}.jar" ;;',
|
|
41
|
+
' agent) CIBUILD_CAPTURE_FILE="invarn-agent-${INVARN_CAPTURE_VERSION}.aar" ;;',
|
|
42
|
+
' esac',
|
|
43
|
+
' CIBUILD_CAPTURE_WANT="$(printf \'%s\' "$INVARN_CAPTURE_SHA256" | tr \',\' \'\\n\' | sed -n "s/^${CIBUILD_CAPTURE_ROLE}=//p")"',
|
|
44
|
+
' if ! curl -fsSL --retry 2 --max-time 120 -o "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE" "${INVARN_CAPTURE_TOOLKIT_URL%/}/$CIBUILD_CAPTURE_FILE"; then',
|
|
45
|
+
' CIBUILD_CAPTURE_SKIP="download"',
|
|
46
|
+
' break',
|
|
47
|
+
' fi',
|
|
48
|
+
' CIBUILD_CAPTURE_GOT="$( (sha256sum "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE" 2>/dev/null || shasum -a 256 "$CIBUILD_CAPTURE_DIR/$CIBUILD_CAPTURE_FILE") | cut -d\' \' -f1)"',
|
|
49
|
+
' if [ -z "$CIBUILD_CAPTURE_WANT" ] || [ "$CIBUILD_CAPTURE_GOT" != "$CIBUILD_CAPTURE_WANT" ]; then',
|
|
50
|
+
' CIBUILD_CAPTURE_SKIP="sha256"',
|
|
51
|
+
' break',
|
|
52
|
+
' fi',
|
|
53
|
+
' done',
|
|
54
|
+
' if [ -z "$CIBUILD_CAPTURE_SKIP" ]; then',
|
|
55
|
+
' export INVARN_INSTRUMENT_JAR="$CIBUILD_CAPTURE_DIR/invarn-instrument-${INVARN_CAPTURE_VERSION}.jar"',
|
|
56
|
+
' export INVARN_AGENT_AAR="$CIBUILD_CAPTURE_DIR/invarn-agent-${INVARN_CAPTURE_VERSION}.aar"',
|
|
57
|
+
...(appProject ? [` export INVARN_APP_PROJECTS=${quote(appProject)}`] : []),
|
|
58
|
+
' CIBUILD_CAPTURE_INIT="$CIBUILD_CAPTURE_DIR/invarn-init-${INVARN_CAPTURE_VERSION}.gradle"',
|
|
59
|
+
' else',
|
|
60
|
+
' echo "▸ INVARN_CAPTURE:skipped:$CIBUILD_CAPTURE_SKIP"',
|
|
61
|
+
' fi',
|
|
62
|
+
'fi',
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The assemble itself: with the init script when the setup left one, and once more
|
|
67
|
+
* without it if that fails. Without an init script it is the plain command.
|
|
68
|
+
*/
|
|
69
|
+
export function captureAssembleCommands(gradleArgs) {
|
|
70
|
+
return [
|
|
71
|
+
// In an `if` condition, not between `set +e` and `set -e`: the memory wrapper turns `set -e` back on before it
|
|
72
|
+
// returns Gradle's status, and bash ignores `-e` for a whole function called as a condition.
|
|
73
|
+
'if [ -n "$CIBUILD_CAPTURE_INIT" ]; then',
|
|
74
|
+
` if $GRADLE_CMD --init-script "$CIBUILD_CAPTURE_INIT" ${gradleArgs}; then CIBUILD_CAPTURE_RC=0; else CIBUILD_CAPTURE_RC=$?; fi`,
|
|
75
|
+
' if [ "$CIBUILD_CAPTURE_RC" -eq 0 ]; then',
|
|
76
|
+
' echo "▸ INVARN_CAPTURE:injected:${INVARN_CAPTURE_VERSION}"',
|
|
77
|
+
' else',
|
|
78
|
+
' echo "▸ INVARN_CAPTURE:skipped:build_failed"',
|
|
79
|
+
' echo "The build failed with the capture agent (exit $CIBUILD_CAPTURE_RC); building again without it."',
|
|
80
|
+
' touch /tmp/.cibuild-build-marker',
|
|
81
|
+
` $GRADLE_CMD ${gradleArgs}`,
|
|
82
|
+
' fi',
|
|
83
|
+
'else',
|
|
84
|
+
` $GRADLE_CMD ${gradleArgs}`,
|
|
85
|
+
'fi',
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=capture-injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-injection.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/capture-injection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Invarn capture agent in a debug build: the shell that ships, executed here
|
|
3
|
+
* against a fake `./gradlew`, with the toolkit served from `file://` URLs.
|
|
4
|
+
*
|
|
5
|
+
* Three properties. Without the dispatch's variables the step is the step it was.
|
|
6
|
+
* With them, a debug assemble runs with the init script, and every way that can go
|
|
7
|
+
* wrong (a download, a checksum, the instrumented build itself) ends in the plain
|
|
8
|
+
* assemble, never in a failed build. Only a debug assemble is ever touched.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { GradleBuildStepExecutor } from "./android.js";
|
|
17
|
+
import { appProjectOfTask, isDebugAssembleTask } from "./capture-injection.js";
|
|
18
|
+
import { testConfig } from "./test-config.js";
|
|
19
|
+
let dir;
|
|
20
|
+
let toolkit;
|
|
21
|
+
const VERSION = "0.14.2";
|
|
22
|
+
const FILES = {
|
|
23
|
+
init: `invarn-init-${VERSION}.gradle`,
|
|
24
|
+
instrument: `invarn-instrument-${VERSION}.jar`,
|
|
25
|
+
agent: `invarn-agent-${VERSION}.aar`,
|
|
26
|
+
};
|
|
27
|
+
function sha(path) {
|
|
28
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
dir = mkdtempSync(join(tmpdir(), "capture-step-"));
|
|
32
|
+
toolkit = mkdtempSync(join(tmpdir(), "capture-toolkit-"));
|
|
33
|
+
for (const [role, name] of Object.entries(FILES))
|
|
34
|
+
writeFileSync(join(toolkit, name), `${role} bytes`);
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
rmSync(dir, { recursive: true, force: true });
|
|
38
|
+
rmSync(toolkit, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
/** The dispatch's three values, right unless a case says otherwise. */
|
|
41
|
+
function captureEnv(over = {}) {
|
|
42
|
+
const sums = Object.entries(FILES)
|
|
43
|
+
.map(([role, name]) => `${role}=${sha(join(toolkit, name))}`)
|
|
44
|
+
.join(",");
|
|
45
|
+
return {
|
|
46
|
+
INVARN_CAPTURE_VERSION: VERSION,
|
|
47
|
+
INVARN_CAPTURE_TOOLKIT_URL: pathToFileURL(toolkit).href,
|
|
48
|
+
INVARN_CAPTURE_SHA256: sums,
|
|
49
|
+
...over,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A `./gradlew` that appends each run's arguments and the agent paths it was handed
|
|
54
|
+
* to a file, and fails when it is given an init script and FAIL_WITH_INIT=1.
|
|
55
|
+
*/
|
|
56
|
+
function fakeGradlew() {
|
|
57
|
+
writeFileSync(join(dir, "gradlew"), [
|
|
58
|
+
"#!/bin/bash",
|
|
59
|
+
'echo "RUN $* | jar=${INVARN_INSTRUMENT_JAR:-} aar=${INVARN_AGENT_AAR:-} app=${INVARN_APP_PROJECTS:-}" >> "$(dirname "$0")/runs"',
|
|
60
|
+
'case " $* " in *" --init-script "*) [ "${FAIL_WITH_INIT:-}" = 1 ] && exit 1 ;; esac',
|
|
61
|
+
"exit 0",
|
|
62
|
+
].join("\n"));
|
|
63
|
+
chmodSync(join(dir, "gradlew"), 0o755);
|
|
64
|
+
}
|
|
65
|
+
async function run(inputs, env) {
|
|
66
|
+
fakeGradlew();
|
|
67
|
+
const step = await new GradleBuildStepExecutor().execute(inputs, {}, testConfig);
|
|
68
|
+
writeFileSync(join(dir, "step.sh"), step.script);
|
|
69
|
+
const out = spawnSync("bash", [join(dir, "step.sh")], {
|
|
70
|
+
cwd: dir,
|
|
71
|
+
encoding: "utf-8",
|
|
72
|
+
env: { PATH: process.env.PATH, HOME: dir, TMPDIR: dir, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "", ...env },
|
|
73
|
+
});
|
|
74
|
+
const runs = existsSync(join(dir, "runs")) ? readFileSync(join(dir, "runs"), "utf-8").trim().split("\n") : [];
|
|
75
|
+
return { status: out.status, stdout: out.stdout, runs };
|
|
76
|
+
}
|
|
77
|
+
describe("which tasks may carry the agent", () => {
|
|
78
|
+
test.each(["assembleDebug", ":app:assembleDebug", ":app:assembleCiDynamicDebug", "assembleStagingDebug"])("%s does", (task) => {
|
|
79
|
+
expect(isDebugAssembleTask(task)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
test.each(["assembleRelease", "bundleDebug", "assembleDebug lint", ":app:testDebugUnitTest", "assembleDebugAndroidTest"])("%s does not", (task) => {
|
|
82
|
+
expect(isDebugAssembleTask(task)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
test("the app project is the task's own", () => {
|
|
85
|
+
expect(appProjectOfTask(":app:assembleDebug")).toBe(":app");
|
|
86
|
+
expect(appProjectOfTask(":apps:phone:assembleDebug")).toBe(":apps:phone");
|
|
87
|
+
expect(appProjectOfTask("assembleDebug")).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("a debug build without the dispatch's variables", () => {
|
|
91
|
+
test("runs the plain assemble once and says nothing of capture", async () => {
|
|
92
|
+
const r = await run({ gradle_task: "assembleDebug" }, {});
|
|
93
|
+
expect(r.status).toBe(0);
|
|
94
|
+
expect(r.runs).toHaveLength(1);
|
|
95
|
+
expect(r.runs[0]).not.toContain("--init-script");
|
|
96
|
+
expect(r.stdout).not.toContain("INVARN_CAPTURE");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("a debug build with them", () => {
|
|
100
|
+
test("assembles once with the init script and the checked files, and says so", async () => {
|
|
101
|
+
const r = await run({ gradle_task: ":app:assembleCiDynamicDebug" }, captureEnv());
|
|
102
|
+
expect(r.status).toBe(0);
|
|
103
|
+
expect(r.runs).toHaveLength(1);
|
|
104
|
+
expect(r.runs[0]).toMatch(new RegExp(`--init-script \\S+/${FILES.init} :app:assembleCiDynamicDebug`));
|
|
105
|
+
expect(r.runs[0]).toMatch(new RegExp(`jar=\\S+/${FILES.instrument} aar=\\S+/${FILES.agent} app=:app`));
|
|
106
|
+
expect(r.stdout).toContain(`▸ INVARN_CAPTURE:injected:${VERSION}`);
|
|
107
|
+
});
|
|
108
|
+
test("writes nothing into the checkout", async () => {
|
|
109
|
+
await run({ gradle_task: "assembleDebug" }, captureEnv());
|
|
110
|
+
for (const name of Object.values(FILES))
|
|
111
|
+
expect(existsSync(join(dir, name))).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
test("a failed instrumented assemble is run again without the agent, and the build passes", async () => {
|
|
114
|
+
const r = await run({ gradle_task: "assembleDebug" }, captureEnv({ FAIL_WITH_INIT: "1" }));
|
|
115
|
+
expect(r.status).toBe(0);
|
|
116
|
+
expect(r.runs).toHaveLength(2);
|
|
117
|
+
expect(r.runs[0]).toContain("--init-script");
|
|
118
|
+
expect(r.runs[1]).not.toContain("--init-script");
|
|
119
|
+
expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:build_failed");
|
|
120
|
+
});
|
|
121
|
+
test("a checksum that does not match runs the plain assemble", async () => {
|
|
122
|
+
const r = await run({ gradle_task: "assembleDebug" }, captureEnv({ INVARN_CAPTURE_SHA256: "init=00,instrument=00,agent=00" }));
|
|
123
|
+
expect(r.status).toBe(0);
|
|
124
|
+
expect(r.runs).toHaveLength(1);
|
|
125
|
+
expect(r.runs[0]).not.toContain("--init-script");
|
|
126
|
+
expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:sha256");
|
|
127
|
+
});
|
|
128
|
+
test("a file that cannot be downloaded runs the plain assemble", async () => {
|
|
129
|
+
const env = captureEnv();
|
|
130
|
+
rmSync(join(toolkit, FILES.agent));
|
|
131
|
+
const r = await run({ gradle_task: "assembleDebug" }, env);
|
|
132
|
+
expect(r.status).toBe(0);
|
|
133
|
+
expect(r.runs).toHaveLength(1);
|
|
134
|
+
expect(r.runs[0]).not.toContain("--init-script");
|
|
135
|
+
expect(r.stdout).toContain("▸ INVARN_CAPTURE:skipped:download");
|
|
136
|
+
});
|
|
137
|
+
test("a release is never touched", async () => {
|
|
138
|
+
const r = await run({ gradle_task: "assembleRelease" }, captureEnv());
|
|
139
|
+
expect(r.runs).toEqual([expect.not.stringContaining("--init-script")]);
|
|
140
|
+
expect(r.stdout).not.toContain("INVARN_CAPTURE");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe("a debug build that fails on its own", () => {
|
|
144
|
+
test("still fails, after trying once without the agent", async () => {
|
|
145
|
+
fakeGradlew();
|
|
146
|
+
writeFileSync(join(dir, "gradlew"), "#!/bin/bash\necho \"RUN $*\" >> \"$(dirname \"$0\")/runs\"\nexit 1\n");
|
|
147
|
+
chmodSync(join(dir, "gradlew"), 0o755);
|
|
148
|
+
const step = await new GradleBuildStepExecutor().execute({ gradle_task: "assembleDebug" }, {}, testConfig);
|
|
149
|
+
writeFileSync(join(dir, "step.sh"), step.script);
|
|
150
|
+
const out = spawnSync("bash", [join(dir, "step.sh")], {
|
|
151
|
+
cwd: dir,
|
|
152
|
+
encoding: "utf-8",
|
|
153
|
+
env: { PATH: process.env.PATH, HOME: dir, TMPDIR: dir, JAVA_HOME: "", CIBUILD_SOURCE_DIR: "", ...captureEnv() },
|
|
154
|
+
});
|
|
155
|
+
expect(out.status).not.toBe(0);
|
|
156
|
+
expect(readFileSync(join(dir, "runs"), "utf-8").trim().split("\n")).toHaveLength(2);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
test("the step is valid bash", async () => {
|
|
160
|
+
const step = await new GradleBuildStepExecutor().execute({ gradle_task: ":app:assembleDebug" }, {}, testConfig);
|
|
161
|
+
expect(spawnSync("bash", ["-n"], { input: step.script }).status).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=capture-injection.test.js.map
|
|
@@ -46,7 +46,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# $1 a JVM argument string, $2 a heap in MiB: the same arguments with every\n# -Xmx replaced by that one (added in front when there was none).\ncibuild_with_xmx() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -Xmx*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -Xmx${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n [ \"$replaced\" -eq 1 ] || out=\" -Xmx${2}m$out\"\n printf '%s' \"${out# }\"\n)\n\n# MiB as GiB with one decimal.\ncibuild_gib() {\n awk -v m=\"$1\" 'BEGIN { printf \"%.1f\", m / 1024 }'\n}\n\n# A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.\ncibuild_scaled_heap() {\n local v=$(( $1 * $2 / $3 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n echo \"$v\"\n}\n\n# The plan, from its inputs only, so a test can run it:\n# $1 this machine's memory in MiB (empty when unknown)\n# $2 the project's gradle.properties $3 the Gradle user home's\n# $4 JVM arguments the pipeline asks for instead (may be empty)\n# Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),\n# CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when\n# nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a\n# daemon is killed).\n#\n# Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's\n# gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of\n# its own inherits the Gradle daemon's, and in-process compilation has no\n# Kotlin daemon at all.\ncibuild_gradle_memory_plan() {\n local mem=\"$1\" project=\"$2\" user=\"$3\" preferred=\"$4\" l\n local jvm=\"\" jvm_at=\"\" kd=\"\" kd_at=\"\" strategy=\"\"\n CIBUILD_GRADLE_MEMORY_ARGS=()\n CIBUILD_GRADLE_MEMORY_LINE=\"\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"\"\n\n if [ -n \"$preferred\" ]; then jvm=\"$preferred\"; jvm_at=\"the pipeline\"; fi\n for f in \"$user\" \"$project\"; do\n if [ -z \"$jvm_at\" ]; then\n l=$(cibuild_prop_line \"$f\" org.gradle.jvmargs)\n if [ -n \"$l\" ]; then jvm=\"${l#*\t}\"; jvm_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$kd_at\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.daemon.jvmargs)\n if [ -n \"$l\" ]; then kd=\"${l#*\t}\"; kd_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$strategy\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.compiler.execution.strategy)\n strategy=\"${l#*\t}\"\n fi\n done\n\n local g k kd_own=\"\"\n g=$(cibuild_xmx_mib \"$jvm\")\n [ -n \"$g\" ] || g=512\n if [ \"$strategy\" = \"in-process\" ]; then\n k=0\n else\n kd_own=$(cibuild_xmx_mib \"$kd\")\n k=\"${kd_own:-$g}\"\n fi\n local total=$(( g + k ))\n\n local asked\n if [ -n \"$jvm_at\" ]; then asked=\"org.gradle.jvmargs=$jvm at ${jvm_at}\"; else asked=\"Gradle's default -Xmx512m\"; fi\n if [ \"$k\" -eq 0 ]; then\n asked=\"$asked; Kotlin compiles in-process\"\n elif [ -n \"$kd_own\" ]; then\n asked=\"$asked, kotlin.daemon.jvmargs=$kd at ${kd_at}\"\n else\n asked=\"$asked; the Kotlin daemon inherits its -Xmx\"\n fi\n\n if [ -z \"$mem\" ]; then\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"This build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked); this machine's memory could not be read.\"\n if [ -n \"$preferred\" ]; then CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\"); fi\n return 0\n fi\n\n local budget=$(( mem * 75 / 100 ))\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"The build machine has $(cibuild_gib \"$mem\") GiB; this build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked).\"\n\n if [ \"$total\" -le \"$budget\" ]; then\n if [ -n \"$preferred\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\")\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line).\"\n fi\n return 0\n fi\n\n case \"$jvm\" in\n *\\\\)\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: the declared heaps ask for $(cibuild_gib \"$total\") GiB of this machine's $(cibuild_gib \"$mem\") GiB, but org.gradle.jvmargs at ${jvm_at} continues onto another line, so it is left as it is.\"\n return 0\n ;;\n esac\n\n # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was\n # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out\n # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.\n local ng now\n ng=$(cibuild_scaled_heap \"$budget\" 1 1)\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$(cibuild_with_xmx \"$jvm\" \"$ng\")\")\n now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -gt 0 ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.compiler.execution.strategy=in-process\")\n now=\"$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)\"\n fi\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: this machine has $(cibuild_gib \"$mem\") GiB and the declared heaps ask for $(cibuild_gib \"$total\") GiB ($asked), past the $(cibuild_gib \"$budget\") GiB this build allows them. Running with $now, set on the command line.\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now.\"\n}\n\n# $1 the number of Gradle workers the pipeline asks for (may be empty). Adds\n# --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.\n# Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).\ncibuild_gradle_workers_plan() {\n CIBUILD_GRADLE_WORKERS_LINE=\"\"\n [ -n \"$1\" ] || return 0\n case \"$1\" in\n *[!0-9]*|0*)\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: max_workers=$1 is not a positive whole number, so it is not passed and Gradle picks its own worker count.\"\n return 0\n ;;\n esac\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"--max-workers=$1\")\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: running with --max-workers=$1, as the pipeline asks (set on the command line, over any org.gradle.workers.max).\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE Gradle ran with --max-workers=$1.\"\n}\n\n# The kernel's out-of-memory lines, if this user may read the kernel log.\n# Linux guests restrict dmesg to root by default; their build user has\n# non-interactive sudo, so that is the second try.\ncibuild_kernel_oom_lines() {\n { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |\n grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true\n}\n\n# $1 Gradle's exit status, $2 a file holding its output. Says so when the\n# failure is a killed daemon rather than a failed build.\ncibuild_gradle_memory_report() {\n local kotlin_lost=\"\"\n if grep -q \"Connection to the Kotlin daemon has been unexpectedly lost\" \"$2\" 2>/dev/null; then kotlin_lost=1; fi\n if [ \"$1\" -ne 137 ] && [ -z \"$kotlin_lost\" ] && ! grep -q \"daemon disappeared unexpectedly\" \"$2\" 2>/dev/null; then\n return 0\n fi\n local oom\n oom=$(cibuild_kernel_oom_lines)\n echo \"\"\n if [ -n \"$kotlin_lost\" ]; then\n echo \"Gradle memory: the Kotlin daemon was lost mid-compile rather than the build failing (exit $1). Kotlin then compiled without it, in the Gradle daemon's heap.\"\n else\n echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\n fi\n if [ -n \"$oom\" ]; then\n echo \"The kernel's out-of-memory lines:\"\n printf '%s\\n' \"$oom\" | sed 's/^/ /'\n else\n echo \"The kernel log shows no out-of-memory kill, or this machine does not let the build read it.\"\n fi\n if [ -n \"$CIBUILD_GRADLE_MEMORY_SENTENCE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_SENTENCE\"; fi\n}\n\n# Runs Gradle with the plan's arguments, and reports a killed daemon.\ncibuild_gradle() {\n local log rc\n log=$(mktemp)\n set +e\n \"$CIBUILD_GRADLE_BIN\" \"${CIBUILD_GRADLE_MEMORY_ARGS[@]}\" \"$@\" 2>&1 | tee \"$log\"\n rc=${PIPESTATUS[0]}\n set -e\n if [ \"$rc\" -ne 0 ]; then cibuild_gradle_memory_report \"$rc\" \"$log\" || true; fi\n rm -f \"$log\"\n return \"$rc\"\n}\n\n# $1 JVM arguments the pipeline asks for, $2 the Gradle workers it asks for\n# (either may be empty). Run in the Gradle project directory, after GRADLE_CMD\n# is chosen.\ncibuild_gradle_memory_cap() {\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\"\n cibuild_gradle_workers_plan \"$2\"\n if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_WORKERS_LINE\" ]; then echo \"$CIBUILD_GRADLE_WORKERS_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
|
|
49
|
+
/**
|
|
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;
|
|
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"}
|