@invarn/cibuild 2.9.2 → 2.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cli.cjs +97 -12
  2. package/dist/src/yaml/steps/android.d.ts.map +1 -1
  3. package/dist/src/yaml/steps/android.js +11 -3
  4. package/dist/src/yaml/steps/capture-injection.d.ts +30 -0
  5. package/dist/src/yaml/steps/capture-injection.d.ts.map +1 -0
  6. package/dist/src/yaml/steps/capture-injection.js +88 -0
  7. package/dist/src/yaml/steps/capture-injection.test.d.ts +2 -0
  8. package/dist/src/yaml/steps/capture-injection.test.d.ts.map +1 -0
  9. package/dist/src/yaml/steps/capture-injection.test.js +163 -0
  10. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts +42 -0
  11. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts.map +1 -0
  12. package/dist/src/yaml/steps/cocoapods-deployment-floor.js +140 -0
  13. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts +11 -0
  14. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts.map +1 -0
  15. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.js +176 -0
  16. package/dist/src/yaml/steps/gradle-memory.d.ts +1 -1
  17. package/dist/src/yaml/steps/gradle-memory.d.ts.map +1 -1
  18. package/dist/src/yaml/steps/gradle-memory.js +78 -3
  19. package/dist/src/yaml/steps/gradle-memory.test.js +48 -1
  20. package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -1
  21. package/dist/src/yaml/steps/ios-deps.js +2 -0
  22. package/dist/src/yaml/steps/xcode-derived-data.test.js +47 -4
  23. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts +53 -0
  24. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts.map +1 -0
  25. package/dist/src/yaml/steps/xcode-simulator-archs.js +98 -0
  26. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts +21 -0
  27. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts.map +1 -0
  28. package/dist/src/yaml/steps/xcode-simulator-archs.test.js +155 -0
  29. package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
  30. package/dist/src/yaml/steps/xcode.js +43 -4
  31. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Pods below the app's own deployment target are raised to it, after pod
3
+ * install, and nothing else moves. Witness: YuigaWada/MissCat (nightly-baseline
4
+ * issue 53) — SwiftLinkPreview 3.1.0 at iOS 8.0 linked libarclite, which
5
+ * Xcode 14.3 removed, in an app that targets iOS 12.0.
6
+ *
7
+ * The Ruby runs for real against projects the xcodeproj gem writes, when this
8
+ * machine's ruby can load it; otherwise those tests are skipped and say so.
9
+ */
10
+ import { describe, test, expect } from '@jest/globals';
11
+ import { execFileSync } from 'node:child_process';
12
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { POD_DEPLOYMENT_FLOOR_SOURCE, podDeploymentFloorLines } from './cocoapods-deployment-floor.js';
16
+ import { CocoapodsInstallStepExecutor } from './ios-deps.js';
17
+ import { testConfig } from './test-config.js';
18
+ const hasXcodeproj = (() => {
19
+ try {
20
+ execFileSync('ruby', ['-e', 'require %q(xcodeproj)'], { stdio: 'ignore' });
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ })();
27
+ /**
28
+ * MissCat's shape: an app at 12.0 and a share extension at 11.0, both
29
+ * integrated by CocoaPods; pods at 8.0 (SwiftLinkPreview), 11.0 and 13.0; a
30
+ * library target in the app's project that CocoaPods does not integrate, at 8.0.
31
+ */
32
+ const FIXTURE = String.raw `
33
+ require 'xcodeproj'
34
+ require 'fileutils'
35
+ dir = ARGV[0]
36
+ FileUtils.mkdir_p(File.join(dir, 'Pods', 'Target Support Files', 'Pods-App'))
37
+ app = Xcodeproj::Project.new(File.join(dir, 'App.xcodeproj'))
38
+ xc = app.new_file('Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig')
39
+ [['App', :application, '12.0', true], ['Share', :app_extension, '11.0', true], ['Unpodded', :framework, '8.0', false]].each do |name, type, dt, podded|
40
+ t = app.new_target(type, name, :ios, dt)
41
+ t.build_configurations.each { |c| c.base_configuration_reference = xc if podded }
42
+ end
43
+ app.save
44
+ pods = Xcodeproj::Project.new(File.join(dir, 'Pods', 'Pods.xcodeproj'))
45
+ pods.new_target(:framework, 'Pods-App', :ios, '12.0')
46
+ [['SwiftLinkPreview', '8.0'], ['MidPod', '11.0'], ['NewPod', '13.0']].each { |n, dt| pods.new_target(:framework, n, :ios, dt) }
47
+ pods.save
48
+ ws = Xcodeproj::Workspace.new(nil)
49
+ ws << 'App.xcodeproj'
50
+ ws << 'Pods/Pods.xcodeproj'
51
+ ws.save_as(File.join(dir, 'App.xcworkspace'))
52
+ `;
53
+ const DUMP = String.raw `
54
+ require 'xcodeproj'
55
+ p = Xcodeproj::Project.open(ARGV[0])
56
+ p.targets.each { |t| t.build_configurations.each { |c| puts "#{t.name} #{c.name} #{c.build_settings['IPHONEOS_DEPLOYMENT_TARGET']}" } }
57
+ `;
58
+ const ruby = (source, args, cwd) => {
59
+ const dir = mkdtempSync(join(tmpdir(), 'pod-floor-rb-'));
60
+ try {
61
+ const f = join(dir, 's.rb');
62
+ writeFileSync(f, source);
63
+ return execFileSync('ruby', [f, ...args], { encoding: 'utf8', cwd });
64
+ }
65
+ finally {
66
+ rmSync(dir, { recursive: true, force: true });
67
+ }
68
+ };
69
+ (hasXcodeproj ? describe : describe.skip)('the raise, on projects the xcodeproj gem wrote', () => {
70
+ const withFixture = (fn) => {
71
+ const dir = mkdtempSync(join(tmpdir(), 'pod-floor-'));
72
+ try {
73
+ ruby(FIXTURE, [dir]);
74
+ fn(dir);
75
+ }
76
+ finally {
77
+ rmSync(dir, { recursive: true, force: true });
78
+ }
79
+ };
80
+ const settings = (projectPath) => Object.fromEntries(ruby(DUMP, [projectPath])
81
+ .trim()
82
+ .split('\n')
83
+ .map((l) => {
84
+ const [t, c, v] = l.split(' ');
85
+ return [`${t} ${c}`, v];
86
+ }));
87
+ test('raises pods below the lowest integrated user target to it, and only those', () => {
88
+ withFixture((dir) => {
89
+ const out = ruby(POD_DEPLOYMENT_FLOOR_SOURCE, ['Pods'], dir);
90
+ const pods = settings(join(dir, 'Pods', 'Pods.xcodeproj'));
91
+ // The floor is the share extension's 11.0, the lowest the app promises.
92
+ expect(pods['SwiftLinkPreview Debug']).toBe('11.0');
93
+ expect(pods['SwiftLinkPreview Release']).toBe('11.0');
94
+ expect(pods['MidPod Debug']).toBe('11.0');
95
+ expect(pods['NewPod Debug']).toBe('13.0');
96
+ expect(pods['Pods-App Debug']).toBe('12.0');
97
+ expect(out).toContain('IPHONEOS_DEPLOYMENT_TARGET 11.0');
98
+ expect(out).toContain('Raised 1 pod target(s)');
99
+ expect(out).toContain('SwiftLinkPreview 8.0');
100
+ });
101
+ });
102
+ test("never touches the app's own project, not even a target CocoaPods does not integrate", () => {
103
+ withFixture((dir) => {
104
+ const before = readFileSync(join(dir, 'App.xcodeproj', 'project.pbxproj'), 'utf8');
105
+ ruby(POD_DEPLOYMENT_FLOOR_SOURCE, ['Pods'], dir);
106
+ expect(readFileSync(join(dir, 'App.xcodeproj', 'project.pbxproj'), 'utf8')).toBe(before);
107
+ // ...and the unintegrated 8.0 library is not the floor: the pod was raised.
108
+ expect(settings(join(dir, 'Pods', 'Pods.xcodeproj'))['SwiftLinkPreview Debug']).toBe('11.0');
109
+ });
110
+ });
111
+ test('a second run changes nothing', () => {
112
+ withFixture((dir) => {
113
+ ruby(POD_DEPLOYMENT_FLOOR_SOURCE, ['Pods'], dir);
114
+ const once = readFileSync(join(dir, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'), 'utf8');
115
+ expect(ruby(POD_DEPLOYMENT_FLOOR_SOURCE, ['Pods'], dir)).toContain('No pod target is below it');
116
+ expect(readFileSync(join(dir, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'), 'utf8')).toBe(once);
117
+ });
118
+ });
119
+ test('no workspace to read the app from: says so, raises nothing, exits 0', () => {
120
+ withFixture((dir) => {
121
+ rmSync(join(dir, 'App.xcworkspace'), { recursive: true, force: true });
122
+ const before = readFileSync(join(dir, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'), 'utf8');
123
+ expect(ruby(POD_DEPLOYMENT_FLOOR_SOURCE, ['Pods'], dir)).toContain('left as they are');
124
+ expect(readFileSync(join(dir, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'), 'utf8')).toBe(before);
125
+ });
126
+ });
127
+ });
128
+ if (!hasXcodeproj) {
129
+ test.skip('the raise on real projects — this ruby cannot load xcodeproj', () => { });
130
+ }
131
+ describe('cocoapods-install runs the raise after pod install', () => {
132
+ const script = async (inputs = {}) => (await new CocoapodsInstallStepExecutor().execute(inputs, {}, testConfig)).script;
133
+ test('after the pod command, before the step reports done', async () => {
134
+ const s = await script();
135
+ const raise = s.indexOf('cibuild-pod-floor');
136
+ expect(raise).toBeGreaterThan(s.indexOf('$POD_INVOCATION $POD_CMD'));
137
+ expect(raise).toBeLessThan(s.indexOf('✅ CocoaPods'));
138
+ });
139
+ test('carries the Ruby verbatim, in a quoted heredoc', async () => {
140
+ const s = await script();
141
+ expect(s).toContain(`<< 'CIBUILD_POD_FLOOR_EOF'`);
142
+ expect(s).toContain(POD_DEPLOYMENT_FLOOR_SOURCE.trim());
143
+ });
144
+ test('never fails the step', () => {
145
+ const lines = podDeploymentFloorLines().join('\n');
146
+ expect(lines).toContain('|| echo "⚠️ Could not raise pods');
147
+ expect(lines).not.toMatch(/\bexit 1\b/);
148
+ });
149
+ test('the emitted script is valid bash', async () => {
150
+ const dir = mkdtempSync(join(tmpdir(), 'pod-floor-sh-'));
151
+ try {
152
+ const f = join(dir, 's.sh');
153
+ writeFileSync(f, await script({ skip_if_absent: true }));
154
+ expect(() => execFileSync('bash', ['-n', f])).not.toThrow();
155
+ }
156
+ finally {
157
+ rmSync(dir, { recursive: true, force: true });
158
+ }
159
+ });
160
+ test('the emitted lines run end to end: the raise happens through the bash', () => {
161
+ if (!hasXcodeproj)
162
+ return;
163
+ const dir = mkdtempSync(join(tmpdir(), 'pod-floor-e2e-'));
164
+ try {
165
+ ruby(FIXTURE, [dir]);
166
+ const f = join(dir, 'raise.sh');
167
+ writeFileSync(f, ['USE_BUNDLE_EXEC=false', 'BUNDLE_CMD=bundle', ...podDeploymentFloorLines()].join('\n'));
168
+ const out = execFileSync('bash', [f], { cwd: dir, encoding: 'utf8' });
169
+ expect(out).toContain('Raised 1 pod target(s)');
170
+ }
171
+ finally {
172
+ rmSync(dir, { recursive: true, force: true });
173
+ }
174
+ });
175
+ });
176
+ //# sourceMappingURL=cocoapods-deployment-floor.test.js.map
@@ -46,7 +46,7 @@
46
46
  */
47
47
  /** The share of the machine's memory the two daemon heaps may take together. */
48
48
  export declare const GRADLE_HEAP_SHARE_PERCENT = 75;
49
- export declare const GRADLE_MEMORY_FUNCTIONS = "# This machine's memory in MiB, or nothing when it cannot be read.\ncibuild_mem_mib() {\n if [ -r /proc/meminfo ]; then\n awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo\n else\n local bytes\n bytes=$(sysctl -n hw.memsize 2>/dev/null || true)\n if [ -n \"$bytes\" ]; then echo $(( bytes / 1048576 )); fi\n fi\n}\n\n# $1 a .properties file, $2 a key. Prints \"<line number><TAB><value>\" for the\n# last assignment of the key, or nothing.\ncibuild_prop_line() {\n [ -f \"$1\" ] || return 0\n awk -v key=\"$2\" '\n { line = $0; sub(/^[ \\t]+/, \"\", line) }\n line ~ /^[#!]/ { next }\n {\n k = line; sub(/[ \\t]*[=:].*$/, \"\", k)\n if (k == key) { v = line; sub(/^[^=:]*[=:][ \\t]*/, \"\", v); sub(/[ \\t\\r]+$/, \"\", v); found = NR \"\\t\" v }\n }\n END { if (found != \"\") print found }\n ' \"$1\"\n}\n\n# The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),\n# or nothing when it has none.\ncibuild_xmx_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t' '\\n\\n' | awk '\n /^-Xmx[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 5); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# $1 a JVM argument string, $2 a heap in MiB: the same arguments with every\n# -Xmx replaced by that one (added in front when there was none).\ncibuild_with_xmx() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -Xmx*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -Xmx${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n [ \"$replaced\" -eq 1 ] || out=\" -Xmx${2}m$out\"\n printf '%s' \"${out# }\"\n)\n\n# MiB as GiB with one decimal.\ncibuild_gib() {\n awk -v m=\"$1\" 'BEGIN { printf \"%.1f\", m / 1024 }'\n}\n\n# A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.\ncibuild_scaled_heap() {\n local v=$(( $1 * $2 / $3 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n echo \"$v\"\n}\n\n# The plan, from its inputs only, so a test can run it:\n# $1 this machine's memory in MiB (empty when unknown)\n# $2 the project's gradle.properties $3 the Gradle user home's\n# $4 JVM arguments the pipeline asks for instead (may be empty)\n# Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),\n# CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when\n# nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a\n# daemon is killed).\n#\n# Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's\n# gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of\n# its own inherits the Gradle daemon's, and in-process compilation has no\n# Kotlin daemon at all.\ncibuild_gradle_memory_plan() {\n local mem=\"$1\" project=\"$2\" user=\"$3\" preferred=\"$4\" l\n local jvm=\"\" jvm_at=\"\" kd=\"\" kd_at=\"\" strategy=\"\"\n CIBUILD_GRADLE_MEMORY_ARGS=()\n CIBUILD_GRADLE_MEMORY_LINE=\"\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"\"\n\n if [ -n \"$preferred\" ]; then jvm=\"$preferred\"; jvm_at=\"the pipeline\"; fi\n for f in \"$user\" \"$project\"; do\n if [ -z \"$jvm_at\" ]; then\n l=$(cibuild_prop_line \"$f\" org.gradle.jvmargs)\n if [ -n \"$l\" ]; then jvm=\"${l#*\t}\"; jvm_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$kd_at\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.daemon.jvmargs)\n if [ -n \"$l\" ]; then kd=\"${l#*\t}\"; kd_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$strategy\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.compiler.execution.strategy)\n strategy=\"${l#*\t}\"\n fi\n done\n\n local g k kd_own=\"\"\n g=$(cibuild_xmx_mib \"$jvm\")\n [ -n \"$g\" ] || g=512\n if [ \"$strategy\" = \"in-process\" ]; then\n k=0\n else\n kd_own=$(cibuild_xmx_mib \"$kd\")\n k=\"${kd_own:-$g}\"\n fi\n local total=$(( g + k ))\n\n local asked\n if [ -n \"$jvm_at\" ]; then asked=\"org.gradle.jvmargs=$jvm at ${jvm_at}\"; else asked=\"Gradle's default -Xmx512m\"; fi\n if [ \"$k\" -eq 0 ]; then\n asked=\"$asked; Kotlin compiles in-process\"\n elif [ -n \"$kd_own\" ]; then\n asked=\"$asked, kotlin.daemon.jvmargs=$kd at ${kd_at}\"\n else\n asked=\"$asked; the Kotlin daemon inherits its -Xmx\"\n fi\n\n if [ -z \"$mem\" ]; then\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"This build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked); this machine's memory could not be read.\"\n if [ -n \"$preferred\" ]; then CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\"); fi\n return 0\n fi\n\n local budget=$(( mem * 75 / 100 ))\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"The build machine has $(cibuild_gib \"$mem\") GiB; this build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked).\"\n\n if [ \"$total\" -le \"$budget\" ]; then\n if [ -n \"$preferred\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\")\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line).\"\n fi\n return 0\n fi\n\n case \"$jvm\" in\n *\\\\)\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: the declared heaps ask for $(cibuild_gib \"$total\") GiB of this machine's $(cibuild_gib \"$mem\") GiB, but org.gradle.jvmargs at ${jvm_at} continues onto another line, so it is left as it is.\"\n return 0\n ;;\n esac\n\n # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was\n # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out\n # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.\n local ng now\n ng=$(cibuild_scaled_heap \"$budget\" 1 1)\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$(cibuild_with_xmx \"$jvm\" \"$ng\")\")\n now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -gt 0 ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.compiler.execution.strategy=in-process\")\n now=\"$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)\"\n fi\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: this machine has $(cibuild_gib \"$mem\") GiB and the declared heaps ask for $(cibuild_gib \"$total\") GiB ($asked), past the $(cibuild_gib \"$budget\") GiB this build allows them. Running with $now, set on the command line.\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now.\"\n}\n\n# $1 the number of Gradle workers the pipeline asks for (may be empty). Adds\n# --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.\n# Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).\ncibuild_gradle_workers_plan() {\n CIBUILD_GRADLE_WORKERS_LINE=\"\"\n [ -n \"$1\" ] || return 0\n case \"$1\" in\n *[!0-9]*|0*)\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: max_workers=$1 is not a positive whole number, so it is not passed and Gradle picks its own worker count.\"\n return 0\n ;;\n esac\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"--max-workers=$1\")\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: running with --max-workers=$1, as the pipeline asks (set on the command line, over any org.gradle.workers.max).\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE Gradle ran with --max-workers=$1.\"\n}\n\n# The kernel's out-of-memory lines, if this user may read the kernel log.\n# Linux guests restrict dmesg to root by default; their build user has\n# non-interactive sudo, so that is the second try.\ncibuild_kernel_oom_lines() {\n { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |\n grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true\n}\n\n# $1 Gradle's exit status, $2 a file holding its output. Says so when the\n# failure is a killed daemon rather than a failed build.\ncibuild_gradle_memory_report() {\n local kotlin_lost=\"\"\n if grep -q \"Connection to the Kotlin daemon has been unexpectedly lost\" \"$2\" 2>/dev/null; then kotlin_lost=1; fi\n if [ \"$1\" -ne 137 ] && [ -z \"$kotlin_lost\" ] && ! grep -q \"daemon disappeared unexpectedly\" \"$2\" 2>/dev/null; then\n return 0\n fi\n local oom\n oom=$(cibuild_kernel_oom_lines)\n echo \"\"\n if [ -n \"$kotlin_lost\" ]; then\n echo \"Gradle memory: the Kotlin daemon was lost mid-compile rather than the build failing (exit $1). Kotlin then compiled without it, in the Gradle daemon's heap.\"\n else\n echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\n fi\n if [ -n \"$oom\" ]; then\n echo \"The kernel's out-of-memory lines:\"\n printf '%s\\n' \"$oom\" | sed 's/^/ /'\n else\n echo \"The kernel log shows no out-of-memory kill, or this machine does not let the build read it.\"\n fi\n if [ -n \"$CIBUILD_GRADLE_MEMORY_SENTENCE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_SENTENCE\"; fi\n}\n\n# Runs Gradle with the plan's arguments, and reports a killed daemon.\ncibuild_gradle() {\n local log rc\n log=$(mktemp)\n set +e\n \"$CIBUILD_GRADLE_BIN\" \"${CIBUILD_GRADLE_MEMORY_ARGS[@]}\" \"$@\" 2>&1 | tee \"$log\"\n rc=${PIPESTATUS[0]}\n set -e\n if [ \"$rc\" -ne 0 ]; then cibuild_gradle_memory_report \"$rc\" \"$log\" || true; fi\n rm -f \"$log\"\n return \"$rc\"\n}\n\n# $1 JVM arguments the pipeline asks for, $2 the Gradle workers it asks for\n# (either may be empty). Run in the Gradle project directory, after GRADLE_CMD\n# is chosen.\ncibuild_gradle_memory_cap() {\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\"\n cibuild_gradle_workers_plan \"$2\"\n if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_WORKERS_LINE\" ]; then echo \"$CIBUILD_GRADLE_WORKERS_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
49
+ export declare const GRADLE_MEMORY_FUNCTIONS = "# This machine's memory in MiB, or nothing when it cannot be read.\ncibuild_mem_mib() {\n if [ -r /proc/meminfo ]; then\n awk '/^MemTotal:/ { print int($2 / 1024); exit }' /proc/meminfo\n else\n local bytes\n bytes=$(sysctl -n hw.memsize 2>/dev/null || true)\n if [ -n \"$bytes\" ]; then echo $(( bytes / 1048576 )); fi\n fi\n}\n\n# $1 a .properties file, $2 a key. Prints \"<line number><TAB><value>\" for the\n# last assignment of the key, or nothing.\ncibuild_prop_line() {\n [ -f \"$1\" ] || return 0\n awk -v key=\"$2\" '\n { line = $0; sub(/^[ \\t]+/, \"\", line) }\n line ~ /^[#!]/ { next }\n {\n k = line; sub(/[ \\t]*[=:].*$/, \"\", k)\n if (k == key) { v = line; sub(/^[^=:]*[=:][ \\t]*/, \"\", v); sub(/[ \\t\\r]+$/, \"\", v); found = NR \"\\t\" v }\n }\n END { if (found != \"\") print found }\n ' \"$1\"\n}\n\n# The -Xmx of a JVM argument string in MiB (the last one wins, as in the JVM),\n# or nothing when it has none.\ncibuild_xmx_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t' '\\n\\n' | awk '\n /^-Xmx[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 5); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# The -XX:MaxMetaspaceSize of a JVM argument string in MiB (the last one\n# wins), or nothing when it has none.\ncibuild_metaspace_mib() {\n printf '%s\\n' \"$1\" | tr ' \\t,' '\\n\\n\\n' | awk '\n /^-XX:MaxMetaspaceSize=[0-9]+[kKmMgGtT]?$/ {\n n = substr($0, 22); u = substr(n, length(n))\n if (u ~ /[0-9]/) { v = n / 1048576 }\n else {\n n = substr(n, 1, length(n) - 1)\n if (u ~ /[kK]/) v = n / 1024\n else if (u ~ /[mM]/) v = n\n else if (u ~ /[gG]/) v = n * 1024\n else v = n * 1048576\n }\n r = int(v)\n }\n END { if (r != \"\") print r }'\n}\n\n# $1 a JVM argument string, $2 a size in MiB: the same arguments with every\n# -XX:MaxMetaspaceSize replaced by that one.\ncibuild_with_metaspace() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -XX:MaxMetaspaceSize=*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -XX:MaxMetaspaceSize=${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n printf '%s' \"${out# }\"\n)\n\n# $1 a JVM argument string, $2 a heap in MiB: the same arguments with every\n# -Xmx replaced by that one (added in front when there was none).\ncibuild_with_xmx() (\n set -f\n out=\"\"\n replaced=0\n for arg in $1; do\n case \"$arg\" in\n -Xmx*) if [ \"$replaced\" -eq 0 ]; then out=\"$out -Xmx${2}m\"; replaced=1; fi ;;\n *) out=\"$out $arg\" ;;\n esac\n done\n [ \"$replaced\" -eq 1 ] || out=\" -Xmx${2}m$out\"\n printf '%s' \"${out# }\"\n)\n\n# MiB as GiB with one decimal.\ncibuild_gib() {\n awk -v m=\"$1\" 'BEGIN { printf \"%.1f\", m / 1024 }'\n}\n\n# A heap scaled by budget/total, rounded down to 64 MiB, never below 512 MiB.\ncibuild_scaled_heap() {\n local v=$(( $1 * $2 / $3 / 64 * 64 ))\n [ \"$v\" -ge 512 ] || v=512\n echo \"$v\"\n}\n\n# The plan, from its inputs only, so a test can run it:\n# $1 this machine's memory in MiB (empty when unknown)\n# $2 the project's gradle.properties $3 the Gradle user home's\n# $4 JVM arguments the pipeline asks for instead (may be empty)\n# Sets CIBUILD_GRADLE_MEMORY_ARGS (extra Gradle arguments),\n# CIBUILD_GRADLE_MEMORY_LINE (what to print before Gradle runs; empty when\n# nothing changes) and CIBUILD_GRADLE_MEMORY_SENTENCE (what to print if a\n# daemon is killed).\n#\n# Precedence is Gradle's own: the command line beats GRADLE_USER_HOME's\n# gradle.properties, which beats the project's. A Kotlin daemon with no -Xmx of\n# its own inherits the Gradle daemon's, and in-process compilation has no\n# Kotlin daemon at all.\ncibuild_gradle_memory_plan() {\n local mem=\"$1\" project=\"$2\" user=\"$3\" preferred=\"$4\" l\n local jvm=\"\" jvm_at=\"\" kd=\"\" kd_at=\"\" strategy=\"\"\n CIBUILD_GRADLE_MEMORY_ARGS=()\n CIBUILD_GRADLE_MEMORY_LINE=\"\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"\"\n\n if [ -n \"$preferred\" ]; then jvm=\"$preferred\"; jvm_at=\"the pipeline\"; fi\n for f in \"$user\" \"$project\"; do\n if [ -z \"$jvm_at\" ]; then\n l=$(cibuild_prop_line \"$f\" org.gradle.jvmargs)\n if [ -n \"$l\" ]; then jvm=\"${l#*\t}\"; jvm_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$kd_at\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.daemon.jvmargs)\n if [ -n \"$l\" ]; then kd=\"${l#*\t}\"; kd_at=\"$f:${l%%\t*}\"; fi\n fi\n if [ -z \"$strategy\" ]; then\n l=$(cibuild_prop_line \"$f\" kotlin.compiler.execution.strategy)\n strategy=\"${l#*\t}\"\n fi\n done\n\n local g k kd_own=\"\"\n g=$(cibuild_xmx_mib \"$jvm\")\n [ -n \"$g\" ] || g=512\n if [ \"$strategy\" = \"in-process\" ]; then\n k=0\n else\n kd_own=$(cibuild_xmx_mib \"$kd\")\n k=\"${kd_own:-$g}\"\n fi\n local total=$(( g + k ))\n\n local asked\n if [ -n \"$jvm_at\" ]; then asked=\"org.gradle.jvmargs=$jvm at ${jvm_at}\"; else asked=\"Gradle's default -Xmx512m\"; fi\n if [ \"$k\" -eq 0 ]; then\n asked=\"$asked; Kotlin compiles in-process\"\n elif [ -n \"$kd_own\" ]; then\n asked=\"$asked, kotlin.daemon.jvmargs=$kd at ${kd_at}\"\n else\n asked=\"$asked; the Kotlin daemon inherits its -Xmx\"\n fi\n\n if [ -z \"$mem\" ]; then\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"This build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked); this machine's memory could not be read.\"\n if [ -n \"$preferred\" ]; then CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\"); fi\n return 0\n fi\n\n local budget=$(( mem * 75 / 100 ))\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"The build machine has $(cibuild_gib \"$mem\") GiB; this build's Gradle and Kotlin daemons asked for $(cibuild_gib \"$total\") GiB ($asked).\"\n\n if [ \"$total\" -le \"$budget\" ]; then\n if [ -n \"$preferred\" ]; then\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$preferred\")\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: org.gradle.jvmargs=$preferred, as the pipeline asks (-Dorg.gradle.jvmargs on the command line).\"\n fi\n return 0\n fi\n\n case \"$jvm\" in\n *\\\\)\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: the declared heaps ask for $(cibuild_gib \"$total\") GiB of this machine's $(cibuild_gib \"$mem\") GiB, but org.gradle.jvmargs at ${jvm_at} continues onto another line, so it is left as it is.\"\n return 0\n ;;\n esac\n\n # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was\n # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out\n # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.\n local ng now newjvm mg mk\n ng=$(cibuild_scaled_heap \"$budget\" 1 1)\n newjvm=$(cibuild_with_xmx \"$jvm\" \"$ng\")\n now=\"Gradle -Xmx${ng}m\"\n if [ \"$k\" -gt 0 ]; then\n # One JVM now holds what two did, so a Metaspace cap Gradle declared must\n # hold Kotlin's too: the Kotlin daemon's own (kotlin.daemon.jvmargs, or\n # -Dkotlin.daemon.jvm.options in Gradle's), else the Gradle one it\n # inherits. GetStream/whatsApp-clone-compose capped Gradle at 512m and gave\n # Kotlin 1g, and in-process compileDebugKotlin ran out of Metaspace\n # (nightly-baseline 79). No Gradle cap, nothing to raise.\n mg=$(cibuild_metaspace_mib \"$jvm\")\n if [ -n \"$mg\" ]; then\n mk=$(cibuild_metaspace_mib \"$kd\")\n [ -n \"$mk\" ] || mk=$(cibuild_metaspace_mib \"$(printf '%s\\n' \"$jvm\" | tr ' \\t' '\\n\\n' | sed -n 's/^-Dkotlin\\.daemon\\.jvm\\.options=//p')\")\n [ -n \"$mk\" ] || mk=\"$mg\"\n newjvm=$(cibuild_with_metaspace \"$newjvm\" $(( mg + mk )))\n now=\"$now with -XX:MaxMetaspaceSize=$(( mg + mk ))m (Gradle's ${mg}m and Kotlin's ${mk}m, in one JVM now)\"\n fi\n now=\"$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)\"\n fi\n CIBUILD_GRADLE_MEMORY_ARGS=(\"-Dorg.gradle.jvmargs=$newjvm\")\n if [ \"$k\" -gt 0 ]; then\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Pkotlin.compiler.execution.strategy=in-process\")\n fi\n CIBUILD_GRADLE_MEMORY_LINE=\"Gradle memory: this machine has $(cibuild_gib \"$mem\") GiB and the declared heaps ask for $(cibuild_gib \"$total\") GiB ($asked), past the $(cibuild_gib \"$budget\") GiB this build allows them. Running with $now, set on the command line.\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now.\"\n}\n\n# $1 the project's gradle.properties, $2 the Gradle user home's. A JDK path the\n# project pins with org.gradle.java.home that is not on this machine \u2014 a\n# developer's own, like SEAbdulbasit/MusicApp-KMP's\n# /Users/abdulbasit/Library/Java/... (nightly-baseline 83) \u2014 stops Gradle\n# before it starts: \"given for org.gradle.java.home Gradle property is\n# invalid\". On the command line, -Dorg.gradle.java.home beats both files\n# (measured, Gradle 8.14.3), so the build's own JDK is passed instead. A path\n# that exists is left alone. Sets CIBUILD_GRADLE_JAVA_HOME_LINE.\ncibuild_gradle_java_home_plan() {\n local l v=\"\" at=\"\"\n CIBUILD_GRADLE_JAVA_HOME_LINE=\"\"\n for f in \"$2\" \"$1\"; do\n l=$(cibuild_prop_line \"$f\" org.gradle.java.home)\n if [ -n \"$l\" ]; then v=\"${l#*\t}\"; at=\"$f:${l%%\t*}\"; break; fi\n done\n [ -n \"$v\" ] || return 0\n [ -d \"$v\" ] && return 0\n [ -n \"${JAVA_HOME:-}\" ] && [ -d \"$JAVA_HOME\" ] || return 0\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"-Dorg.gradle.java.home=$JAVA_HOME\")\n CIBUILD_GRADLE_JAVA_HOME_LINE=\"Gradle: org.gradle.java.home=$v at $at is not on this machine; running with this build's JDK, $JAVA_HOME (-Dorg.gradle.java.home on the command line).\"\n}\n\n# $1 the number of Gradle workers the pipeline asks for (may be empty). Adds\n# --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.\n# Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).\ncibuild_gradle_workers_plan() {\n CIBUILD_GRADLE_WORKERS_LINE=\"\"\n [ -n \"$1\" ] || return 0\n case \"$1\" in\n *[!0-9]*|0*)\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: max_workers=$1 is not a positive whole number, so it is not passed and Gradle picks its own worker count.\"\n return 0\n ;;\n esac\n CIBUILD_GRADLE_MEMORY_ARGS+=(\"--max-workers=$1\")\n CIBUILD_GRADLE_WORKERS_LINE=\"Gradle memory: running with --max-workers=$1, as the pipeline asks (set on the command line, over any org.gradle.workers.max).\"\n CIBUILD_GRADLE_MEMORY_SENTENCE=\"$CIBUILD_GRADLE_MEMORY_SENTENCE Gradle ran with --max-workers=$1.\"\n}\n\n# The kernel's out-of-memory lines, if this user may read the kernel log.\n# Linux guests restrict dmesg to root by default; their build user has\n# non-interactive sudo, so that is the second try.\ncibuild_kernel_oom_lines() {\n { dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || true; } |\n grep -i -E 'out of memory|oom-kill|oom_reaper|killed process' | tail -n 20 || true\n}\n\n# $1 Gradle's exit status, $2 a file holding its output. Says so when the\n# failure is a killed daemon rather than a failed build.\ncibuild_gradle_memory_report() {\n local kotlin_lost=\"\"\n if grep -q \"Connection to the Kotlin daemon has been unexpectedly lost\" \"$2\" 2>/dev/null; then kotlin_lost=1; fi\n if [ \"$1\" -ne 137 ] && [ -z \"$kotlin_lost\" ] && ! grep -q \"daemon disappeared unexpectedly\" \"$2\" 2>/dev/null; then\n return 0\n fi\n local oom\n oom=$(cibuild_kernel_oom_lines)\n echo \"\"\n if [ -n \"$kotlin_lost\" ]; then\n echo \"Gradle memory: the Kotlin daemon was lost mid-compile rather than the build failing (exit $1). Kotlin then compiled without it, in the Gradle daemon's heap.\"\n else\n echo \"Gradle memory: a Gradle daemon was killed rather than failing the build (exit $1).\"\n fi\n if [ -n \"$oom\" ]; then\n echo \"The kernel's out-of-memory lines:\"\n printf '%s\\n' \"$oom\" | sed 's/^/ /'\n else\n echo \"The kernel log shows no out-of-memory kill, or this machine does not let the build read it.\"\n fi\n if [ -n \"$CIBUILD_GRADLE_MEMORY_SENTENCE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_SENTENCE\"; fi\n}\n\n# Runs Gradle with the plan's arguments, and reports a killed daemon.\ncibuild_gradle() {\n local log rc\n log=$(mktemp)\n set +e\n \"$CIBUILD_GRADLE_BIN\" \"${CIBUILD_GRADLE_MEMORY_ARGS[@]}\" \"$@\" 2>&1 | tee \"$log\"\n rc=${PIPESTATUS[0]}\n set -e\n if [ \"$rc\" -ne 0 ]; then cibuild_gradle_memory_report \"$rc\" \"$log\" || true; fi\n rm -f \"$log\"\n return \"$rc\"\n}\n\n# $1 JVM arguments the pipeline asks for, $2 the Gradle workers it asks for\n# (either may be empty). Run in the Gradle project directory, after GRADLE_CMD\n# is chosen.\ncibuild_gradle_memory_cap() {\n cibuild_gradle_memory_plan \"$(cibuild_mem_mib || true)\" \"gradle.properties\" \\\n \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\" \"$1\"\n cibuild_gradle_workers_plan \"$2\"\n cibuild_gradle_java_home_plan \"gradle.properties\" \"${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties\"\n if [ -n \"$CIBUILD_GRADLE_MEMORY_LINE\" ]; then echo \"$CIBUILD_GRADLE_MEMORY_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_WORKERS_LINE\" ]; then echo \"$CIBUILD_GRADLE_WORKERS_LINE\"; fi\n if [ -n \"$CIBUILD_GRADLE_JAVA_HOME_LINE\" ]; then echo \"$CIBUILD_GRADLE_JAVA_HOME_LINE\"; fi\n CIBUILD_GRADLE_BIN=\"$GRADLE_CMD\"\n GRADLE_CMD=cibuild_gradle\n}";
50
50
  /**
51
51
  * The commands a Gradle step runs once it has chosen `GRADLE_CMD` and is in
52
52
  * the project directory. Afterwards `$GRADLE_CMD` runs Gradle with the plan's
@@ -1 +1 @@
1
- {"version":3,"file":"gradle-memory.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAG5C,eAAO,MAAM,uBAAuB,ohUAmPlC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACjC,UAAU,SAAK,GACd,MAAM,EAAE,CAOV"}
1
+ {"version":3,"file":"gradle-memory.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/gradle-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAG5C,eAAO,MAAM,uBAAuB,k9aA8TlC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACjC,UAAU,SAAK,GACd,MAAM,EAAE,CAOV"}
@@ -91,6 +91,40 @@ cibuild_xmx_mib() {
91
91
  END { if (r != "") print r }'
92
92
  }
93
93
 
94
+ # The -XX:MaxMetaspaceSize of a JVM argument string in MiB (the last one
95
+ # wins), or nothing when it has none.
96
+ cibuild_metaspace_mib() {
97
+ printf '%s\\n' "$1" | tr ' \\t,' '\\n\\n\\n' | awk '
98
+ /^-XX:MaxMetaspaceSize=[0-9]+[kKmMgGtT]?$/ {
99
+ n = substr($0, 22); u = substr(n, length(n))
100
+ if (u ~ /[0-9]/) { v = n / 1048576 }
101
+ else {
102
+ n = substr(n, 1, length(n) - 1)
103
+ if (u ~ /[kK]/) v = n / 1024
104
+ else if (u ~ /[mM]/) v = n
105
+ else if (u ~ /[gG]/) v = n * 1024
106
+ else v = n * 1048576
107
+ }
108
+ r = int(v)
109
+ }
110
+ END { if (r != "") print r }'
111
+ }
112
+
113
+ # $1 a JVM argument string, $2 a size in MiB: the same arguments with every
114
+ # -XX:MaxMetaspaceSize replaced by that one.
115
+ cibuild_with_metaspace() (
116
+ set -f
117
+ out=""
118
+ replaced=0
119
+ for arg in $1; do
120
+ case "$arg" in
121
+ -XX:MaxMetaspaceSize=*) if [ "$replaced" -eq 0 ]; then out="$out -XX:MaxMetaspaceSize=\${2}m"; replaced=1; fi ;;
122
+ *) out="$out $arg" ;;
123
+ esac
124
+ done
125
+ printf '%s' "\${out# }"
126
+ )
127
+
94
128
  # $1 a JVM argument string, $2 a heap in MiB: the same arguments with every
95
129
  # -Xmx replaced by that one (added in front when there was none).
96
130
  cibuild_with_xmx() (
@@ -203,18 +237,57 @@ cibuild_gradle_memory_plan() {
203
237
  # One heap, the whole budget. Two did not fit: ReFra's Kotlin daemon was
204
238
  # lost mid-compile at a 60/40 split, and mifos-mobile's Gradle daemon ran out
205
239
  # of its 40 %. In-process, Kotlin compiles in the Gradle daemon's heap.
206
- local ng now
240
+ local ng now newjvm mg mk
207
241
  ng=$(cibuild_scaled_heap "$budget" 1 1)
208
- CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$(cibuild_with_xmx "$jvm" "$ng")")
242
+ newjvm=$(cibuild_with_xmx "$jvm" "$ng")
209
243
  now="Gradle -Xmx\${ng}m"
210
244
  if [ "$k" -gt 0 ]; then
211
- CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.compiler.execution.strategy=in-process")
245
+ # One JVM now holds what two did, so a Metaspace cap Gradle declared must
246
+ # hold Kotlin's too: the Kotlin daemon's own (kotlin.daemon.jvmargs, or
247
+ # -Dkotlin.daemon.jvm.options in Gradle's), else the Gradle one it
248
+ # inherits. GetStream/whatsApp-clone-compose capped Gradle at 512m and gave
249
+ # Kotlin 1g, and in-process compileDebugKotlin ran out of Metaspace
250
+ # (nightly-baseline 79). No Gradle cap, nothing to raise.
251
+ mg=$(cibuild_metaspace_mib "$jvm")
252
+ if [ -n "$mg" ]; then
253
+ mk=$(cibuild_metaspace_mib "$kd")
254
+ [ -n "$mk" ] || mk=$(cibuild_metaspace_mib "$(printf '%s\\n' "$jvm" | tr ' \\t' '\\n\\n' | sed -n 's/^-Dkotlin\\.daemon\\.jvm\\.options=//p')")
255
+ [ -n "$mk" ] || mk="$mg"
256
+ newjvm=$(cibuild_with_metaspace "$newjvm" $(( mg + mk )))
257
+ now="$now with -XX:MaxMetaspaceSize=$(( mg + mk ))m (Gradle's \${mg}m and Kotlin's \${mk}m, in one JVM now)"
258
+ fi
212
259
  now="$now and Kotlin compiled in-process in it (kotlin.compiler.execution.strategy=in-process: one heap, not a Gradle and a Kotlin daemon)"
213
260
  fi
261
+ CIBUILD_GRADLE_MEMORY_ARGS=("-Dorg.gradle.jvmargs=$newjvm")
262
+ if [ "$k" -gt 0 ]; then
263
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Pkotlin.compiler.execution.strategy=in-process")
264
+ fi
214
265
  CIBUILD_GRADLE_MEMORY_LINE="Gradle memory: this machine has $(cibuild_gib "$mem") GiB and the declared heaps ask for $(cibuild_gib "$total") GiB ($asked), past the $(cibuild_gib "$budget") GiB this build allows them. Running with $now, set on the command line."
215
266
  CIBUILD_GRADLE_MEMORY_SENTENCE="$CIBUILD_GRADLE_MEMORY_SENTENCE This build ran them capped: $now."
216
267
  }
217
268
 
269
+ # $1 the project's gradle.properties, $2 the Gradle user home's. A JDK path the
270
+ # project pins with org.gradle.java.home that is not on this machine — a
271
+ # developer's own, like SEAbdulbasit/MusicApp-KMP's
272
+ # /Users/abdulbasit/Library/Java/... (nightly-baseline 83) — stops Gradle
273
+ # before it starts: "given for org.gradle.java.home Gradle property is
274
+ # invalid". On the command line, -Dorg.gradle.java.home beats both files
275
+ # (measured, Gradle 8.14.3), so the build's own JDK is passed instead. A path
276
+ # that exists is left alone. Sets CIBUILD_GRADLE_JAVA_HOME_LINE.
277
+ cibuild_gradle_java_home_plan() {
278
+ local l v="" at=""
279
+ CIBUILD_GRADLE_JAVA_HOME_LINE=""
280
+ for f in "$2" "$1"; do
281
+ l=$(cibuild_prop_line "$f" org.gradle.java.home)
282
+ if [ -n "$l" ]; then v="\${l#* }"; at="$f:\${l%% *}"; break; fi
283
+ done
284
+ [ -n "$v" ] || return 0
285
+ [ -d "$v" ] && return 0
286
+ [ -n "\${JAVA_HOME:-}" ] && [ -d "$JAVA_HOME" ] || return 0
287
+ CIBUILD_GRADLE_MEMORY_ARGS+=("-Dorg.gradle.java.home=$JAVA_HOME")
288
+ CIBUILD_GRADLE_JAVA_HOME_LINE="Gradle: org.gradle.java.home=$v at $at is not on this machine; running with this build's JDK, $JAVA_HOME (-Dorg.gradle.java.home on the command line)."
289
+ }
290
+
218
291
  # $1 the number of Gradle workers the pipeline asks for (may be empty). Adds
219
292
  # --max-workers to the plan's arguments; run after cibuild_gradle_memory_plan.
220
293
  # Sets CIBUILD_GRADLE_WORKERS_LINE (what to print; empty when nothing asked).
@@ -285,8 +358,10 @@ cibuild_gradle_memory_cap() {
285
358
  cibuild_gradle_memory_plan "$(cibuild_mem_mib || true)" "gradle.properties" \\
286
359
  "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" "$1"
287
360
  cibuild_gradle_workers_plan "$2"
361
+ cibuild_gradle_java_home_plan "gradle.properties" "\${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties"
288
362
  if [ -n "$CIBUILD_GRADLE_MEMORY_LINE" ]; then echo "$CIBUILD_GRADLE_MEMORY_LINE"; fi
289
363
  if [ -n "$CIBUILD_GRADLE_WORKERS_LINE" ]; then echo "$CIBUILD_GRADLE_WORKERS_LINE"; fi
364
+ if [ -n "$CIBUILD_GRADLE_JAVA_HOME_LINE" ]; then echo "$CIBUILD_GRADLE_JAVA_HOME_LINE"; fi
290
365
  CIBUILD_GRADLE_BIN="$GRADLE_CMD"
291
366
  GRADLE_CMD=cibuild_gradle
292
367
  }`;
@@ -67,10 +67,27 @@ describe("cibuild_gradle_memory_plan", () => {
67
67
  test("mifos-mobile on its 6 GiB mac guest gets the one heap too", () => {
68
68
  writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g\n");
69
69
  expect(plan("6144").args).toEqual([
70
- "-Dorg.gradle.jvmargs=-Xmx4608m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g",
70
+ // Its Kotlin daemon inherited the 1g Metaspace cap; in one JVM the cap holds both.
71
+ "-Dorg.gradle.jvmargs=-Xmx4608m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=2048m",
71
72
  "-Pkotlin.compiler.execution.strategy=in-process",
72
73
  ]);
73
74
  });
75
+ // GetStream/whatsApp-clone-compose (nightly-baseline 79): Gradle capped at
76
+ // 512m Metaspace and Kotlin given 1g of its own; in-process, the one JVM
77
+ // ran out of Metaspace in compileDebugKotlin.
78
+ test("whatsApp-clone-compose: a Metaspace cap holds Kotlin's too once Kotlin is in-process", () => {
79
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true\n");
80
+ const p = plan("9933");
81
+ expect(p.args).toEqual([
82
+ "-Dorg.gradle.jvmargs=-Xmx7424m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=1536m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true",
83
+ "-Pkotlin.compiler.execution.strategy=in-process",
84
+ ]);
85
+ expect(p.line).toContain("with -XX:MaxMetaspaceSize=1536m (Gradle's 512m and Kotlin's 1024m, in one JVM now)");
86
+ });
87
+ test("a Kotlin daemon with no Metaspace of its own inherits Gradle's, and the cap doubles", () => {
88
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g\n");
89
+ expect(plan(TEN_GIB).args[0]).toBe("-Dorg.gradle.jvmargs=-Xmx7680m -XX:MaxMetaspaceSize=2048m");
90
+ });
74
91
  test("the one heap never goes below 512 MiB", () => {
75
92
  writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx4g\n");
76
93
  expect(plan("600").args).toEqual(["-Dorg.gradle.jvmargs=-Xmx512m", "-Pkotlin.compiler.execution.strategy=in-process"]);
@@ -157,6 +174,36 @@ printf 'SENTENCE %s\\n' "$CIBUILD_GRADLE_MEMORY_SENTENCE"`;
157
174
  // ReFra was killed again with its heap capped: off-heap memory and parallel
158
175
  // native compilers are past what a heap cap bounds. Its CI also sets
159
176
  // org.gradle.workers.max=2, which the pipeline carries as max_workers.
177
+ // nightly-baseline 83: SEAbdulbasit/MusicApp-KMP pins its developer's JDK in
178
+ // gradle.properties, and Gradle refused to start.
179
+ describe("cibuild_gradle_java_home_plan", () => {
180
+ const javaHomePlan = (env) => {
181
+ const out = execFileSync("bash", ["-c", `${GRADLE_MEMORY_FUNCTIONS}
182
+ CIBUILD_GRADLE_MEMORY_ARGS=()
183
+ cibuild_gradle_java_home_plan gradle.properties "$1/gradle.properties"
184
+ for a in "\${CIBUILD_GRADLE_MEMORY_ARGS[@]}"; do printf 'ARG %s\\n' "$a"; done
185
+ printf 'LINE %s\\n' "$CIBUILD_GRADLE_JAVA_HOME_LINE"`, "plan", join(dir, "no-user-home")], { cwd: dir, encoding: "utf-8", env: { PATH: process.env.PATH ?? "", ...env } });
186
+ const lines = out.split("\n");
187
+ return {
188
+ args: lines.filter((l) => l.startsWith("ARG ")).map((l) => l.slice(4)),
189
+ line: (lines.find((l) => l.startsWith("LINE ")) ?? "").slice(5),
190
+ };
191
+ };
192
+ test("a pinned JDK that is not on this machine is replaced by the build's", () => {
193
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx2g\norg.gradle.java.home=/Users/abdulbasit/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home\n");
194
+ const p = javaHomePlan({ JAVA_HOME: dir });
195
+ expect(p.args).toEqual([`-Dorg.gradle.java.home=${dir}`]);
196
+ expect(p.line).toContain("org.gradle.java.home=/Users/abdulbasit/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home at gradle.properties:2 is not on this machine");
197
+ });
198
+ test("a pinned JDK that exists is left alone", () => {
199
+ writeFileSync(join(dir, "gradle.properties"), `org.gradle.java.home=${dir}\n`);
200
+ expect(javaHomePlan({ JAVA_HOME: "/elsewhere" }).args).toEqual([]);
201
+ });
202
+ test("no pin, nothing", () => {
203
+ writeFileSync(join(dir, "gradle.properties"), "org.gradle.jvmargs=-Xmx2g\n");
204
+ expect(javaHomePlan({ JAVA_HOME: dir })).toEqual({ args: [], line: "" });
205
+ });
206
+ });
160
207
  describe("cibuild_gradle_workers_plan", () => {
161
208
  test("ReFra: the worker count goes after the heap cap, and the sentence names it", () => {
162
209
  copyFileSync(join(FIXTURES, "refra.gradle.properties"), join(dir, "gradle.properties"));
@@ -1 +1 @@
1
- {"version":3,"file":"ios-deps.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ios-deps.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAc,MAAM,wBAAwB,CAAC;AAMhF,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,gBAAgB;IAChE,yBAAyB,CACvB,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CA+MpB;AAMD,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;IACxD,yBAAyB,CACvB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAyDpB"}
1
+ {"version":3,"file":"ios-deps.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ios-deps.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAc,MAAM,wBAAwB,CAAC;AAMhF,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,gBAAgB;IAChE,yBAAyB,CACvB,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAgNpB;AAMD,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;IACxD,yBAAyB,CACvB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAUpB,OAAO,CACX,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAyDpB"}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { BaseStepExecutor } from './base.js';
5
5
  import { refusedFetchHelpers, retryOnRefusedFetch } from './git-fetch-retry.js';
6
+ import { podDeploymentFloorLines } from './cocoapods-deployment-floor.js';
6
7
  /**
7
8
  * Runs `pod install` or `pod update`.
8
9
  * Auto-detects Gemfile and uses `bundle exec pod` when appropriate.
@@ -210,6 +211,7 @@ export class CocoapodsInstallStepExecutor extends BaseStepExecutor {
210
211
  command: '$POD_INVOCATION $POD_CMD',
211
212
  }));
212
213
  commands.push('');
214
+ commands.push(...podDeploymentFloorLines());
213
215
  commands.push('echo "✅ CocoaPods ${POD_CMD%% *} completed"');
214
216
  const script = this.createBashScriptFromCommands(commands, stepName);
215
217
  return this.createScriptStep(script, stepName);
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import { describe, test, expect } from '@jest/globals';
30
30
  import { execFileSync } from 'child_process';
31
- import { mkdtempSync, realpathSync } from 'fs';
31
+ import { mkdtempSync, realpathSync, symlinkSync } from 'fs';
32
32
  import { tmpdir } from 'os';
33
33
  import { join } from 'path';
34
34
  import { XcodeBuildStepExecutor, XcodeBuildForTestStepExecutor, XcodeBuildForSimulatorStepExecutor, } from './xcode.js';
@@ -181,10 +181,14 @@ describe('the output_dir input still steers where everything goes', () => {
181
181
  * repository has a reason to exclude it.
182
182
  */
183
183
  describe('derived data is outside the source tree', () => {
184
+ // The lines that choose the path: the root, then the definition.
184
185
  const definitionOf = (script) => {
185
- const line = script.split('\n').find((l) => l.startsWith('DERIVED_DATA_PATH='));
186
- expect(line).toBeDefined();
187
- return line;
186
+ const lines = script.split('\n');
187
+ const from = lines.findIndex((l) => l.startsWith('cibuild_dd_root='));
188
+ const to = lines.findIndex((l) => l.startsWith('DERIVED_DATA_PATH='));
189
+ expect(from).toBeGreaterThan(-1);
190
+ expect(to).toBeGreaterThan(from);
191
+ return lines.slice(from, to + 1).join('\n');
188
192
  };
189
193
  // Runs the step's own definition line, the way the step would.
190
194
  const resolve = (script, env) => execFileSync('bash', ['-c', `${definitionOf(script)}\nprintf '%s' "$DERIVED_DATA_PATH"`], {
@@ -226,6 +230,45 @@ describe('derived data is outside the source tree', () => {
226
230
  const b = resolve(script, { CIBUILD_SCRATCH: '/scratch', OUTPUT_DIR: '/work/repo/build' });
227
231
  expect(a).not.toBe(b);
228
232
  });
233
+ // analogcode/Swift-Radio-Pro: LNPopupController's Package.swift lists its
234
+ // header directories with FileManager (real paths, /private/tmp/...) and
235
+ // strips packageBase.path (/tmp/...) by text replacement, so every header
236
+ // search path came out eight characters short and '_LNPopupGlassUtils.h'
237
+ // was not found. On macOS /tmp and TMPDIR's /var are symlinks into
238
+ // /private; derived data has to be named by its real path.
239
+ test('derived data is named by its real path, not through a symlink', async () => {
240
+ const real = realpathSync(mkdtempSync(join(tmpdir(), 'real-')));
241
+ const link = join(realpathSync(mkdtempSync(join(tmpdir(), 'link-'))), 'tmp');
242
+ symlinkSync(real, link);
243
+ const script = await scriptFor(new XcodeBuildStepExecutor(), {});
244
+ const lines = script.split('\n');
245
+ const from = lines.findIndex((l) => l.startsWith('cibuild_dd_root='));
246
+ const to = lines.findIndex((l) => l.startsWith('echo "Derived data:'));
247
+ const path = execFileSync('bash', ['-c', `set -e\n${lines.slice(from, to).join('\n')}\nprintf '%s' "$DERIVED_DATA_PATH"`], { env: { PATH: process.env.PATH ?? '', TMPDIR: link, OUTPUT_DIR: '/work/repo/build' } }).toString();
248
+ expect(path.startsWith(real + '/')).toBe(true);
249
+ });
250
+ // nightly-baseline 58, measured on runner-mac-01: xcodebuild folds
251
+ // /private/tmp back to /tmp and FileManager does not, so LNPopupController's
252
+ // text-built header paths came out eight characters short under the guest's
253
+ // /tmp/cibuild-work-<id> root, and built under the user's home.
254
+ const tmpIsALink = (() => {
255
+ try {
256
+ return realpathSync('/tmp') !== '/tmp';
257
+ }
258
+ catch {
259
+ return false;
260
+ }
261
+ })();
262
+ (tmpIsALink ? test : test.skip)('a scratch root under /tmp is traded for the build user\'s home', async () => {
263
+ const scratch = mkdtempSync('/tmp/cibuild-work-');
264
+ const home = realpathSync(mkdtempSync(join(tmpdir(), 'home-')));
265
+ const path = resolve(await scriptFor(new XcodeBuildStepExecutor(), {}), {
266
+ CIBUILD_SCRATCH: scratch,
267
+ CIBUILD_USER_HOME: home,
268
+ OUTPUT_DIR: '/work/repo/build',
269
+ });
270
+ expect(path.startsWith(`${home}/Library/Developer/cibuild/cibuild-derived-data/`)).toBe(true);
271
+ });
229
272
  test('the product listing and the xctestrun search follow it', async () => {
230
273
  const build = await scriptFor(new XcodeBuildStepExecutor(), {});
231
274
  expect(build).toContain('ls -lhR "$DERIVED_DATA_PATH/Build/Products"');
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The simulator architecture, chosen at run time.
3
+ *
4
+ * A pipeline pins `ARCHS=arm64` on its simulator builds: a *generic* simulator
5
+ * destination otherwise exports `ARCHS='arm64 x86_64'`, and anything that maps
6
+ * ARCHS onto a second toolchain meets an architecture nothing on the runner
7
+ * builds. But a project can exclude arm64 from the simulator
8
+ * (`EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64`, an Intel-era workaround),
9
+ * and then the pin leaves nothing to build: "None of the architectures in
10
+ * ARCHS (arm64) are valid", and CocoaPods' `[CP] Embed Pods Frameworks` dies
11
+ * on `ARCHS[@]: unbound variable`.
12
+ *
13
+ * Invarn switches to x86_64 at pipeline creation when the exclusion is in the
14
+ * repository (the pbxproj or the Podfile). A pod can also carry it, in its
15
+ * podspec's `user_target_xcconfig`, which CocoaPods writes into the app's
16
+ * `Pods-…debug.xcconfig` — and that exists only after `pod install`
17
+ * (ExistOrLive/GithubClient: WoodPeckeriOS 1.4.3, nightly-baseline issue 54).
18
+ * So the step asks Xcode, right before it builds, for the effective settings:
19
+ * `-showBuildSettings` reads the pbxproj, every xcconfig and the pods at once.
20
+ *
21
+ * Only an `ARCHS=arm64` pin is checked. An `ARCHS=x86_64` chosen at creation
22
+ * is left as it is, and options without a pin have nothing to decide.
23
+ */
24
+ /** The variable the build line reads its simulator architecture from. */
25
+ export declare const SIMULATOR_ARCHS_VAR = "CIBUILD_SIMULATOR_ARCHS";
26
+ /** Whether `xcodebuild_options` pins the simulator build to arm64. */
27
+ export declare function pinsSimulatorArm64(xcodebuildOptions: string): boolean;
28
+ /** The options with the arm64 pin replaced by the run-time choice. */
29
+ export declare function withRuntimeSimulatorArchs(xcodebuildOptions: string): string;
30
+ /**
31
+ * Reads `xcodebuild -showBuildSettings -json` on stdin and prints one line
32
+ * naming the target that excludes arm64, or nothing. The application target
33
+ * decides when the scheme lists one; a framework the scheme also lists is not
34
+ * what is being built for the simulator. Output it cannot read is no
35
+ * evidence: it prints nothing and exits 0, and the build keeps arm64.
36
+ */
37
+ export declare const ARM64_EXCLUSION_READER_SOURCE: string;
38
+ /**
39
+ * The lines that set `$CIBUILD_SIMULATOR_ARCHS`: arm64, or x86_64 when the
40
+ * project's effective simulator settings exclude arm64, saying so in the log.
41
+ * They expect `$PROJECT_TYPE` and `$DERIVED_DATA_PATH` to be set, and go after
42
+ * package resolution, so the settings are read from the graph the build uses.
43
+ */
44
+ export declare function simulatorArchsLines(opts: {
45
+ /** Already escaped for a single-quoted bash string. */
46
+ projectPath: string;
47
+ scheme: string;
48
+ configuration: string;
49
+ destination: string;
50
+ /** Pass `-xcconfig "$XCCONFIG_FILE"` too, as the build does. */
51
+ xcconfig: boolean;
52
+ }): string[];
53
+ //# sourceMappingURL=xcode-simulator-archs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xcode-simulator-archs.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode-simulator-archs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,sEAAsE;AACtE,wBAAgB,yBAAyB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAqB3C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,EAAE,CAyBX"}