@invarn/cibuild 2.7.1 → 2.7.3
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 +10 -10
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts +20 -0
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts.map +1 -0
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.js +201 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +32 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +1 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.js +301 -0
- package/dist/src/yaml/env-resolver.d.ts +33 -0
- package/dist/src/yaml/env-resolver.d.ts.map +1 -1
- package/dist/src/yaml/env-resolver.js +54 -2
- package/dist/src/yaml/steps/file.d.ts +25 -0
- package/dist/src/yaml/steps/file.d.ts.map +1 -1
- package/dist/src/yaml/steps/file.js +75 -2
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts +10 -0
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +162 -0
- package/dist/src/yaml/steps/git-fetch-retry.d.ts +62 -0
- package/dist/src/yaml/steps/git-fetch-retry.d.ts.map +1 -0
- package/dist/src/yaml/steps/git-fetch-retry.js +155 -0
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts +5 -0
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/git-fetch-retry.test.js +86 -0
- package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -1
- package/dist/src/yaml/steps/ios-deps.js +14 -4
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts +9 -0
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode-resolve-retry.test.js +146 -0
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +41 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for retrying a refused third-party dependency fetch.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, test, expect } from '@jest/globals';
|
|
5
|
+
import { REFUSED_FETCH_SIGNATURE, refusedFetchHelpers, retryOnRefusedFetch, } from './git-fetch-retry.js';
|
|
6
|
+
const helpers = () => refusedFetchHelpers().join('\n');
|
|
7
|
+
const retry = (o) => retryOnRefusedFetch(o).join('\n');
|
|
8
|
+
describe('refusedFetchHelpers', () => {
|
|
9
|
+
test('keys off the line git prints only for an HTTP 401', () => {
|
|
10
|
+
expect(REFUSED_FETCH_SIGNATURE).toBe('could not read Username for');
|
|
11
|
+
expect(helpers()).toContain(REFUSED_FETCH_SIGNATURE);
|
|
12
|
+
});
|
|
13
|
+
test('captures output to a log the diagnostic can re-read', () => {
|
|
14
|
+
const script = helpers();
|
|
15
|
+
expect(script).toContain('CIBUILD_FETCH_LOG=');
|
|
16
|
+
expect(script).toContain('mktemp');
|
|
17
|
+
});
|
|
18
|
+
test('re-probes each refused URL for its HTTP status', () => {
|
|
19
|
+
const script = helpers();
|
|
20
|
+
// The whole point: the 401 is recorded nowhere, so ask again and print it.
|
|
21
|
+
expect(script).toContain('info/refs?service=git-upload-pack');
|
|
22
|
+
expect(script).toContain("-w '%{http_code}'");
|
|
23
|
+
expect(script).toContain('cibuild_diagnose_refused_fetch()');
|
|
24
|
+
});
|
|
25
|
+
test('explains each status it can get back', () => {
|
|
26
|
+
const script = helpers();
|
|
27
|
+
expect(script).toContain('200)');
|
|
28
|
+
expect(script).toContain('401|403)');
|
|
29
|
+
expect(script).toContain('404)');
|
|
30
|
+
// An unreachable host must not be reported as a refusal.
|
|
31
|
+
expect(script).toContain('000)');
|
|
32
|
+
expect(script).toContain('unreachable from this machine');
|
|
33
|
+
});
|
|
34
|
+
test('never lets a probe failure abort the step', () => {
|
|
35
|
+
// The diagnostic runs on the failure path; it must not mask the real error.
|
|
36
|
+
expect(helpers()).toContain('|| CIBUILD_FETCH_PROBE_CODE="000"');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('retryOnRefusedFetch', () => {
|
|
40
|
+
const opts = { label: 'Package resolution', command: 'xcodebuild -resolvePackageDependencies' };
|
|
41
|
+
test('runs the command it was given', () => {
|
|
42
|
+
expect(retry(opts)).toContain('xcodebuild -resolvePackageDependencies');
|
|
43
|
+
});
|
|
44
|
+
test('streams output live while still capturing it', () => {
|
|
45
|
+
const script = retry(opts);
|
|
46
|
+
expect(script).toContain('| tee "$CIBUILD_FETCH_LOG"');
|
|
47
|
+
// tee would otherwise mask the command's own exit code.
|
|
48
|
+
expect(script).toContain('${PIPESTATUS[0]}');
|
|
49
|
+
});
|
|
50
|
+
test('retries only when the failure is a refused fetch', () => {
|
|
51
|
+
const script = retry(opts);
|
|
52
|
+
// A compile error must fail on the first attempt, not after three.
|
|
53
|
+
expect(script).toContain('if ! cibuild_fetch_was_refused; then');
|
|
54
|
+
expect(script).toContain('break');
|
|
55
|
+
});
|
|
56
|
+
test('backs off between attempts', () => {
|
|
57
|
+
const script = retry({ ...opts, attempts: 3, backoffSeconds: [15, 45] });
|
|
58
|
+
expect(script).toContain('CIBUILD_FETCH_BACKOFF=(15 45)');
|
|
59
|
+
expect(script).toContain('sleep');
|
|
60
|
+
});
|
|
61
|
+
test('defaults to three attempts', () => {
|
|
62
|
+
expect(retry(opts)).toContain('CIBUILD_FETCH_MAX_ATTEMPTS=3');
|
|
63
|
+
});
|
|
64
|
+
test('honours a custom attempt count', () => {
|
|
65
|
+
expect(retry({ ...opts, attempts: 5 })).toContain('CIBUILD_FETCH_MAX_ATTEMPTS=5');
|
|
66
|
+
});
|
|
67
|
+
test('diagnoses and then fails with the original exit code', () => {
|
|
68
|
+
const script = retry(opts);
|
|
69
|
+
expect(script).toContain('cibuild_diagnose_refused_fetch');
|
|
70
|
+
expect(script).toContain('exit "$CIBUILD_FETCH_RC"');
|
|
71
|
+
});
|
|
72
|
+
test('names the thing being fetched in its retry message', () => {
|
|
73
|
+
expect(retry(opts)).toContain('Package resolution');
|
|
74
|
+
});
|
|
75
|
+
test('does not use a bare && before break, which set -e would trip on', () => {
|
|
76
|
+
// `[ x ] && break` returns 1 when the test is false and aborts under set -e.
|
|
77
|
+
expect(retry(opts)).not.toMatch(/\]\s*&&\s*break/);
|
|
78
|
+
});
|
|
79
|
+
test('restores set -e after measuring the exit code', () => {
|
|
80
|
+
const script = retry(opts);
|
|
81
|
+
expect(script).toContain('set +e');
|
|
82
|
+
expect(script).toContain('set -e');
|
|
83
|
+
expect(script.indexOf('set +e')).toBeLessThan(script.lastIndexOf('set -e'));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=git-fetch-retry.test.js.map
|
|
@@ -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;
|
|
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,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;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;CAkFpB;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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* iOS dependency management steps: cocoapods-install, carthage
|
|
3
3
|
*/
|
|
4
4
|
import { BaseStepExecutor } from './base.js';
|
|
5
|
+
import { refusedFetchHelpers, retryOnRefusedFetch } from './git-fetch-retry.js';
|
|
5
6
|
/**
|
|
6
7
|
* Runs `pod install` or `pod update`.
|
|
7
8
|
* Auto-detects Gemfile and uses `bundle exec pod` when appropriate.
|
|
@@ -66,13 +67,22 @@ export class CocoapodsInstallStepExecutor extends BaseStepExecutor {
|
|
|
66
67
|
const verboseFlag = verbose ? ' --verbose' : '';
|
|
67
68
|
commands.push(`POD_CMD="${this.escapeBash(command)}${verboseFlag}"`);
|
|
68
69
|
commands.push('');
|
|
70
|
+
// One invocation, chosen up front, so the retry wraps it once rather than
|
|
71
|
+
// once per branch. `pod install` clones each pod's source over https, and a
|
|
72
|
+
// refused anonymous clone there fails the step exactly as it does during a
|
|
73
|
+
// Swift package resolve — same signature, same fix.
|
|
69
74
|
commands.push('if [ "$USE_BUNDLE_EXEC" = "true" ]; then');
|
|
70
|
-
commands.push('
|
|
71
|
-
commands.push(' bundle exec pod $POD_CMD');
|
|
75
|
+
commands.push(' POD_INVOCATION="bundle exec pod"');
|
|
72
76
|
commands.push('else');
|
|
73
|
-
commands.push('
|
|
74
|
-
commands.push(' pod $POD_CMD');
|
|
77
|
+
commands.push(' POD_INVOCATION="pod"');
|
|
75
78
|
commands.push('fi');
|
|
79
|
+
commands.push('echo "Running: $POD_INVOCATION $POD_CMD"');
|
|
80
|
+
commands.push('');
|
|
81
|
+
commands.push(...refusedFetchHelpers());
|
|
82
|
+
commands.push(...retryOnRefusedFetch({
|
|
83
|
+
label: 'CocoaPods dependency fetch',
|
|
84
|
+
command: '$POD_INVOCATION $POD_CMD',
|
|
85
|
+
}));
|
|
76
86
|
commands.push('');
|
|
77
87
|
commands.push('echo "✅ CocoaPods ${POD_CMD%% *} completed"');
|
|
78
88
|
const script = this.createBashScriptFromCommands(commands, stepName);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency resolution is separated from the build and retried.
|
|
3
|
+
*
|
|
4
|
+
* Resolution takes seconds and the build that follows takes minutes, so a
|
|
5
|
+
* refused fetch should cost a retry of the cheap half, not a re-run of the
|
|
6
|
+
* expensive one. These assert the split actually lands in the emitted script.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=xcode-resolve-retry.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcode-resolve-retry.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode-resolve-retry.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency resolution is separated from the build and retried.
|
|
3
|
+
*
|
|
4
|
+
* Resolution takes seconds and the build that follows takes minutes, so a
|
|
5
|
+
* refused fetch should cost a retry of the cheap half, not a re-run of the
|
|
6
|
+
* expensive one. These assert the split actually lands in the emitted script.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, test, expect } from '@jest/globals';
|
|
9
|
+
import { XcodeBuildStepExecutor, XcodeBuildForTestStepExecutor, XcodeBuildForSimulatorStepExecutor, } from './xcode.js';
|
|
10
|
+
import { CocoapodsInstallStepExecutor } from './ios-deps.js';
|
|
11
|
+
import { testConfig } from './test-config.js';
|
|
12
|
+
const countOccurrences = (haystack, needle) => haystack.split(needle).length - 1;
|
|
13
|
+
describe('xcode-build-for-test resolves before it builds', () => {
|
|
14
|
+
const build = async (inputs = {}) => {
|
|
15
|
+
const executor = new XcodeBuildForTestStepExecutor();
|
|
16
|
+
const result = await executor.execute({ project_path: 'MyApp.xcworkspace', scheme: 'MyApp', ...inputs }, {}, testConfig);
|
|
17
|
+
return result.script;
|
|
18
|
+
};
|
|
19
|
+
test('resolves package dependencies as its own command', async () => {
|
|
20
|
+
expect(await build()).toContain('xcodebuild -resolvePackageDependencies');
|
|
21
|
+
});
|
|
22
|
+
test('resolves BEFORE build-for-testing', async () => {
|
|
23
|
+
const script = await build();
|
|
24
|
+
expect(script.indexOf('xcodebuild -resolvePackageDependencies')).toBeLessThan(script.indexOf('xcodebuild build-for-testing'));
|
|
25
|
+
});
|
|
26
|
+
test('resolves into the same derived data the build will read', async () => {
|
|
27
|
+
// Otherwise the resolve is wasted work and the build fetches all over again.
|
|
28
|
+
const script = await build();
|
|
29
|
+
const resolveLine = script
|
|
30
|
+
.split('\n')
|
|
31
|
+
.find((l) => l.includes('-resolvePackageDependencies'));
|
|
32
|
+
expect(resolveLine).toContain('-derivedDataPath "$OUTPUT_DIR/DerivedData"');
|
|
33
|
+
});
|
|
34
|
+
test('passes the same project type and scheme as the build', async () => {
|
|
35
|
+
const resolveLine = (await build())
|
|
36
|
+
.split('\n')
|
|
37
|
+
.find((l) => l.includes('-resolvePackageDependencies'));
|
|
38
|
+
expect(resolveLine).toContain('"$PROJECT_TYPE"');
|
|
39
|
+
expect(resolveLine).toContain("'MyApp.xcworkspace'");
|
|
40
|
+
expect(resolveLine).toContain("-scheme 'MyApp'");
|
|
41
|
+
});
|
|
42
|
+
test('wraps the resolve in the refused-fetch retry', async () => {
|
|
43
|
+
const script = await build();
|
|
44
|
+
expect(script).toContain('cibuild_fetch_was_refused');
|
|
45
|
+
expect(script).toContain('CIBUILD_FETCH_MAX_ATTEMPTS');
|
|
46
|
+
});
|
|
47
|
+
test('defines the retry helpers exactly once', async () => {
|
|
48
|
+
const script = await build();
|
|
49
|
+
expect(countOccurrences(script, 'cibuild_diagnose_refused_fetch()')).toBe(1);
|
|
50
|
+
expect(countOccurrences(script, 'cibuild_fetch_was_refused()')).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
test('does not retry the expensive build step itself', async () => {
|
|
53
|
+
const script = await build();
|
|
54
|
+
// exactly one retry loop, and it is the resolve
|
|
55
|
+
expect(countOccurrences(script, 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
56
|
+
const loopStart = script.indexOf('CIBUILD_FETCH_MAX_ATTEMPTS=');
|
|
57
|
+
expect(loopStart).toBeLessThan(script.indexOf('xcodebuild build-for-testing'));
|
|
58
|
+
});
|
|
59
|
+
test('still builds and still exports its outputs', async () => {
|
|
60
|
+
const script = await build();
|
|
61
|
+
expect(script).toContain('xcodebuild build-for-testing');
|
|
62
|
+
expect(script).toContain('envman add --key CIBUILD_XCTESTRUN_FILE_PATH');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('xcodebuild resolves before it builds', () => {
|
|
66
|
+
const build = async (inputs = {}) => {
|
|
67
|
+
const executor = new XcodeBuildStepExecutor();
|
|
68
|
+
const result = await executor.execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp', ...inputs }, {}, testConfig);
|
|
69
|
+
return result.script;
|
|
70
|
+
};
|
|
71
|
+
test('resolves package dependencies before building', async () => {
|
|
72
|
+
const script = await build();
|
|
73
|
+
expect(script).toContain('xcodebuild -resolvePackageDependencies');
|
|
74
|
+
expect(script.indexOf('xcodebuild -resolvePackageDependencies')).toBeLessThan(script.indexOf('xcodebuild build '));
|
|
75
|
+
});
|
|
76
|
+
test('wraps the resolve in the refused-fetch retry', async () => {
|
|
77
|
+
expect(await build()).toContain('cibuild_fetch_was_refused');
|
|
78
|
+
});
|
|
79
|
+
test('leaves the build invocation itself unwrapped', async () => {
|
|
80
|
+
expect(countOccurrences(await build(), 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
81
|
+
});
|
|
82
|
+
test('still honours a clean build', async () => {
|
|
83
|
+
expect(await build({ is_clean_build: true })).toContain('xcodebuild clean build');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('xcode-build-for-simulator resolves before it builds', () => {
|
|
87
|
+
// This is the step the iOS template's "Build App" is, and it is the first
|
|
88
|
+
// step in that pipeline that resolves a package graph — so it is the one a
|
|
89
|
+
// refused fetch actually lands on. It was missed on the first pass.
|
|
90
|
+
const build = async (inputs = {}) => {
|
|
91
|
+
const executor = new XcodeBuildForSimulatorStepExecutor();
|
|
92
|
+
const result = await executor.execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp', ...inputs }, {}, testConfig);
|
|
93
|
+
return result.script;
|
|
94
|
+
};
|
|
95
|
+
test('resolves package dependencies before building', async () => {
|
|
96
|
+
const script = await build();
|
|
97
|
+
expect(script).toContain('xcodebuild -resolvePackageDependencies');
|
|
98
|
+
expect(script.indexOf('xcodebuild -resolvePackageDependencies')).toBeLessThan(script.indexOf('xcodebuild build '));
|
|
99
|
+
});
|
|
100
|
+
test('wraps the resolve in the refused-fetch retry', async () => {
|
|
101
|
+
const script = await build();
|
|
102
|
+
expect(script).toContain('cibuild_fetch_was_refused');
|
|
103
|
+
expect(script).toContain('CIBUILD_FETCH_MAX_ATTEMPTS');
|
|
104
|
+
});
|
|
105
|
+
test('leaves the build invocation itself unwrapped', async () => {
|
|
106
|
+
expect(countOccurrences(await build(), 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
test('passes the same project type and scheme as the build', async () => {
|
|
109
|
+
const resolveLine = (await build())
|
|
110
|
+
.split('\n')
|
|
111
|
+
.find((l) => l.includes('-resolvePackageDependencies'));
|
|
112
|
+
expect(resolveLine).toContain('"$PROJECT_TYPE"');
|
|
113
|
+
expect(resolveLine).toContain("-scheme 'MyApp'");
|
|
114
|
+
});
|
|
115
|
+
test('still honours a clean action and still finds the .app', async () => {
|
|
116
|
+
const script = await build({ perform_clean_action: 'yes' });
|
|
117
|
+
expect(script).toContain('xcodebuild clean build');
|
|
118
|
+
expect(script).toContain('CIBUILD_APP_DIR_PATH');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('cocoapods-install retries a refused pod source fetch', () => {
|
|
122
|
+
const build = async (inputs = {}) => {
|
|
123
|
+
const executor = new CocoapodsInstallStepExecutor();
|
|
124
|
+
const result = await executor.execute(inputs, {}, testConfig);
|
|
125
|
+
return result.script;
|
|
126
|
+
};
|
|
127
|
+
test('wraps the pod command in the retry', async () => {
|
|
128
|
+
const script = await build();
|
|
129
|
+
expect(script).toContain('cibuild_fetch_was_refused');
|
|
130
|
+
expect(script).toContain('CIBUILD_FETCH_MAX_ATTEMPTS');
|
|
131
|
+
});
|
|
132
|
+
test('still chooses between bundle exec and bare pod', async () => {
|
|
133
|
+
const script = await build();
|
|
134
|
+
expect(script).toContain('bundle exec pod');
|
|
135
|
+
expect(script).toContain('USE_BUNDLE_EXEC');
|
|
136
|
+
});
|
|
137
|
+
test('runs the pod command exactly once in the script', async () => {
|
|
138
|
+
// The old shape invoked pod in both branches of an if/else; the retry
|
|
139
|
+
// needs a single invocation or it would be wrapped twice.
|
|
140
|
+
expect(countOccurrences(await build(), 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
test('still reports completion', async () => {
|
|
143
|
+
expect(await build()).toContain('CocoaPods');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=xcode-resolve-retry.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { BaseStepExecutor } from './base.js';
|
|
6
6
|
import { resolveDestinationInput } from './xcode-destination.js';
|
|
7
|
+
import { refusedFetchHelpers, retryOnRefusedFetch } from './git-fetch-retry.js';
|
|
7
8
|
/**
|
|
8
9
|
* Xcodebuild step executor
|
|
9
10
|
* Builds iOS/macOS projects using xcodebuild
|
|
@@ -78,6 +79,20 @@ export class XcodeBuildStepExecutor extends BaseStepExecutor {
|
|
|
78
79
|
commands.push('else');
|
|
79
80
|
commands.push(' PROJECT_TYPE="-project"');
|
|
80
81
|
commands.push('fi');
|
|
82
|
+
// Resolve the package graph before building, and retry that on a refused
|
|
83
|
+
// fetch. Resolution is seconds and the build is minutes, so the retry is
|
|
84
|
+
// spent on the cheap half. No -derivedDataPath here: this step does not
|
|
85
|
+
// set one for the build either, so both use the default location and the
|
|
86
|
+
// build finds the graph this just resolved.
|
|
87
|
+
commands.push('');
|
|
88
|
+
commands.push('# Resolve package dependencies first — see git-fetch-retry');
|
|
89
|
+
commands.push(...refusedFetchHelpers());
|
|
90
|
+
commands.push(...retryOnRefusedFetch({
|
|
91
|
+
label: 'Package resolution',
|
|
92
|
+
command: 'xcodebuild -resolvePackageDependencies' +
|
|
93
|
+
` "$PROJECT_TYPE" '${escapedPath}'` +
|
|
94
|
+
` -scheme '${this.escapeBash(scheme)}'`,
|
|
95
|
+
}));
|
|
81
96
|
// Build xcodebuild command
|
|
82
97
|
commands.push('');
|
|
83
98
|
commands.push('# Build xcodebuild command');
|
|
@@ -792,6 +807,19 @@ export class XcodeBuildForTestStepExecutor extends BaseStepExecutor {
|
|
|
792
807
|
commands.push(' PROJECT_TYPE="-project"');
|
|
793
808
|
commands.push('fi');
|
|
794
809
|
commands.push('');
|
|
810
|
+
// Resolve the package graph on its own, ahead of the build, so a refused
|
|
811
|
+
// fetch costs a retry of something that takes seconds rather than a re-run
|
|
812
|
+
// of build-for-testing, which takes minutes. The build that follows reads
|
|
813
|
+
// the same derived data and finds the graph already resolved.
|
|
814
|
+
commands.push('# Resolve package dependencies first — see git-fetch-retry');
|
|
815
|
+
commands.push(...refusedFetchHelpers());
|
|
816
|
+
commands.push(...retryOnRefusedFetch({
|
|
817
|
+
label: 'Package resolution',
|
|
818
|
+
command: 'xcodebuild -resolvePackageDependencies' +
|
|
819
|
+
` "$PROJECT_TYPE" '${escapedPath}'` +
|
|
820
|
+
` -scheme '${escapedScheme}'` +
|
|
821
|
+
' -derivedDataPath "$OUTPUT_DIR/DerivedData"',
|
|
822
|
+
}));
|
|
795
823
|
// Build command
|
|
796
824
|
let cmd = 'xcodebuild build-for-testing';
|
|
797
825
|
cmd += ` "$PROJECT_TYPE" '${escapedPath}'`;
|
|
@@ -983,6 +1011,19 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
|
|
|
983
1011
|
commands.push(' PROJECT_TYPE="-project"');
|
|
984
1012
|
commands.push('fi');
|
|
985
1013
|
commands.push('');
|
|
1014
|
+
// Resolve the package graph before building, under the refused-fetch
|
|
1015
|
+
// retry. This is the step an iOS app is normally built with, so it is the
|
|
1016
|
+
// one a refused dependency fetch lands on first. No -derivedDataPath: the
|
|
1017
|
+
// build below does not set one either, so both use the default location
|
|
1018
|
+
// and the build finds the graph this just resolved.
|
|
1019
|
+
commands.push('# Resolve package dependencies first — see git-fetch-retry');
|
|
1020
|
+
commands.push(...refusedFetchHelpers());
|
|
1021
|
+
commands.push(...retryOnRefusedFetch({
|
|
1022
|
+
label: 'Package resolution',
|
|
1023
|
+
command: 'xcodebuild -resolvePackageDependencies' +
|
|
1024
|
+
` "$PROJECT_TYPE" '${escapedPath}'` +
|
|
1025
|
+
` -scheme '${escapedScheme}'`,
|
|
1026
|
+
}));
|
|
986
1027
|
// Build command
|
|
987
1028
|
let cmd = 'xcodebuild';
|
|
988
1029
|
if (performClean === 'yes') {
|