@invarn/cibuild 2.7.1 → 2.7.2
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/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/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 +156 -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 +111 -0
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +28 -0
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrying a refused third-party dependency fetch, and naming the reason when
|
|
3
|
+
* the retry does not save it.
|
|
4
|
+
*
|
|
5
|
+
* A build that resolves its dependencies from public git repositories fetches
|
|
6
|
+
* them anonymously. When a host answers one of those fetches with HTTP 401,
|
|
7
|
+
* git has no credential to fall back on and prints
|
|
8
|
+
*
|
|
9
|
+
* fatal: could not read Username for 'https://<host>': terminal prompts disabled
|
|
10
|
+
* fatal: expected flush after ref listing
|
|
11
|
+
*
|
|
12
|
+
* Those two lines are one failure, not two: the transport child asks for a
|
|
13
|
+
* username, cannot prompt, and dies; the parent then finds the ref listing
|
|
14
|
+
* truncated. git asks for a username *only* on a 401, which makes the first
|
|
15
|
+
* line an exact signature for "the server refused this fetch".
|
|
16
|
+
*
|
|
17
|
+
* Such a refusal has been observed to be transient — the same repository
|
|
18
|
+
* readable again a minute or two later, from the same machine and the same
|
|
19
|
+
* address — while landing in the middle of a dependency resolution and taking
|
|
20
|
+
* a multi-minute build down with it.
|
|
21
|
+
*
|
|
22
|
+
* Two things are emitted here, and they are deliberately separate:
|
|
23
|
+
*
|
|
24
|
+
* - a retry around the fetch, gated on that signature, so a compile error
|
|
25
|
+
* still fails on the first attempt instead of three attempts later; and
|
|
26
|
+
* - a diagnostic that re-probes every refused URL and prints the HTTP status
|
|
27
|
+
* it gets back. Nothing else in the pipeline records that status, so a
|
|
28
|
+
* build failing this way otherwise reports a missing package rather than a
|
|
29
|
+
* refused fetch, and a recurrence is unattributable after the fact. The
|
|
30
|
+
* probe is the whole reason the second occurrence will be cheaper to
|
|
31
|
+
* explain than the first.
|
|
32
|
+
*
|
|
33
|
+
* The emitted shell is bash and runs under `set -e` / `set -o pipefail`, which
|
|
34
|
+
* is why the exit code is measured explicitly rather than inferred.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The line git prints when, and only when, a server answers a fetch with
|
|
38
|
+
* HTTP 401 and no credential is available.
|
|
39
|
+
*/
|
|
40
|
+
export const REFUSED_FETCH_SIGNATURE = 'could not read Username for';
|
|
41
|
+
/**
|
|
42
|
+
* Shell functions and state the retry depends on. Emit once per script, before
|
|
43
|
+
* the first `retryOnRefusedFetch` block.
|
|
44
|
+
*/
|
|
45
|
+
export function refusedFetchHelpers() {
|
|
46
|
+
return [
|
|
47
|
+
'# --- refused dependency fetch: retry, then explain -------------------------',
|
|
48
|
+
'CIBUILD_FETCH_LOG="$(mktemp -t cibuild-fetch)"',
|
|
49
|
+
'',
|
|
50
|
+
'# git asks for a username only when a server answers with HTTP 401, so this',
|
|
51
|
+
'# line means the fetch was refused, not that a package is missing.',
|
|
52
|
+
'cibuild_fetch_was_refused() {',
|
|
53
|
+
` grep -q '${REFUSED_FETCH_SIGNATURE}' "$CIBUILD_FETCH_LOG" 2>/dev/null`,
|
|
54
|
+
'}',
|
|
55
|
+
'',
|
|
56
|
+
'# The status that caused the refusal is recorded nowhere, so ask again and',
|
|
57
|
+
'# put it in the build log where the next person will find it.',
|
|
58
|
+
'cibuild_diagnose_refused_fetch() {',
|
|
59
|
+
' echo ""',
|
|
60
|
+
' echo "──────────────────────────────────────────────────────────────"',
|
|
61
|
+
' echo "A dependency fetch was refused."',
|
|
62
|
+
' echo ""',
|
|
63
|
+
' echo "git asked for a username, which it does only when the server answered"',
|
|
64
|
+
' echo "with HTTP 401. That is an authentication refusal, not a missing"',
|
|
65
|
+
' echo "package. Re-probing each repository named above:"',
|
|
66
|
+
' echo ""',
|
|
67
|
+
// Every stage here can legitimately match nothing, and under `set -e` with
|
|
68
|
+
// `pipefail` an empty grep would abort the step and replace the command's
|
|
69
|
+
// real exit code with 1. The diagnostic must never do that: it runs on the
|
|
70
|
+
// failure path, and its whole job is to explain a failure it did not cause.
|
|
71
|
+
` grep -oE 'https://[^ "'"'"']+' "$CIBUILD_FETCH_LOG" 2>/dev/null \\`,
|
|
72
|
+
` | sed -e "s/[.,:;')]*$//" -e 's/\\.git$//' \\`,
|
|
73
|
+
` | grep -E '^https://[^/]+/[^/]+/[^/]+$' \\`,
|
|
74
|
+
' | sort -u | head -20 \\',
|
|
75
|
+
' | while IFS= read -r CIBUILD_FETCH_PROBE_URL; do',
|
|
76
|
+
' CIBUILD_FETCH_PROBE_CODE="$(curl -sS -o /dev/null -m 20 \\',
|
|
77
|
+
` -w '%{http_code}' \\`,
|
|
78
|
+
' "$CIBUILD_FETCH_PROBE_URL/info/refs?service=git-upload-pack" 2>/dev/null)" \\',
|
|
79
|
+
' || CIBUILD_FETCH_PROBE_CODE="000"',
|
|
80
|
+
' case "$CIBUILD_FETCH_PROBE_CODE" in',
|
|
81
|
+
' 200)',
|
|
82
|
+
' echo " 200 $CIBUILD_FETCH_PROBE_URL"',
|
|
83
|
+
' echo " readable anonymously now — the refusal was transient"',
|
|
84
|
+
' ;;',
|
|
85
|
+
' 401|403)',
|
|
86
|
+
' echo " $CIBUILD_FETCH_PROBE_CODE $CIBUILD_FETCH_PROBE_URL"',
|
|
87
|
+
' echo " still refused — this repository needs a credential, or the"',
|
|
88
|
+
' echo " host is refusing anonymous reads from this machine"',
|
|
89
|
+
' ;;',
|
|
90
|
+
' 404)',
|
|
91
|
+
' echo " 404 $CIBUILD_FETCH_PROBE_URL"',
|
|
92
|
+
' echo " no such repository — check the dependency URL"',
|
|
93
|
+
' ;;',
|
|
94
|
+
' 000)',
|
|
95
|
+
' echo " --- $CIBUILD_FETCH_PROBE_URL"',
|
|
96
|
+
' echo " unreachable from this machine — network, DNS or TLS"',
|
|
97
|
+
' ;;',
|
|
98
|
+
' *)',
|
|
99
|
+
' echo " $CIBUILD_FETCH_PROBE_CODE $CIBUILD_FETCH_PROBE_URL"',
|
|
100
|
+
' ;;',
|
|
101
|
+
' esac',
|
|
102
|
+
' done || true',
|
|
103
|
+
' echo ""',
|
|
104
|
+
' echo "A 200 above means the fetch would succeed if it ran again now."',
|
|
105
|
+
' echo "──────────────────────────────────────────────────────────────"',
|
|
106
|
+
' echo ""',
|
|
107
|
+
'}',
|
|
108
|
+
'',
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Runs `command`, retrying it while the failure looks like a refused fetch.
|
|
113
|
+
* On the last failure the diagnostic runs and the step exits with the
|
|
114
|
+
* command's own status, so the reported exit code is still the real one.
|
|
115
|
+
*/
|
|
116
|
+
export function retryOnRefusedFetch(options) {
|
|
117
|
+
const attempts = options.attempts ?? 3;
|
|
118
|
+
const backoff = options.backoffSeconds ?? [15, 45];
|
|
119
|
+
const label = options.label.replace(/"/g, '\\"');
|
|
120
|
+
return [
|
|
121
|
+
`CIBUILD_FETCH_MAX_ATTEMPTS=${attempts}`,
|
|
122
|
+
`CIBUILD_FETCH_BACKOFF=(${backoff.join(' ')})`,
|
|
123
|
+
'CIBUILD_FETCH_ATTEMPT=1',
|
|
124
|
+
'while :; do',
|
|
125
|
+
' set +e',
|
|
126
|
+
` ${options.command} 2>&1 | tee "$CIBUILD_FETCH_LOG"`,
|
|
127
|
+
' CIBUILD_FETCH_RC=${PIPESTATUS[0]}',
|
|
128
|
+
' set -e',
|
|
129
|
+
' if [ "$CIBUILD_FETCH_RC" -eq 0 ]; then',
|
|
130
|
+
' break',
|
|
131
|
+
' fi',
|
|
132
|
+
// Anything that is not a refused fetch is a real failure: fail now.
|
|
133
|
+
' if ! cibuild_fetch_was_refused; then',
|
|
134
|
+
' break',
|
|
135
|
+
' fi',
|
|
136
|
+
' if [ "$CIBUILD_FETCH_ATTEMPT" -ge "$CIBUILD_FETCH_MAX_ATTEMPTS" ]; then',
|
|
137
|
+
' break',
|
|
138
|
+
' fi',
|
|
139
|
+
' CIBUILD_FETCH_WAIT="${CIBUILD_FETCH_BACKOFF[$((CIBUILD_FETCH_ATTEMPT - 1))]:-30}"',
|
|
140
|
+
` echo "⚠️ ${label} was refused by the server (attempt $CIBUILD_FETCH_ATTEMPT of $CIBUILD_FETCH_MAX_ATTEMPTS)."`,
|
|
141
|
+
' echo " A refused anonymous fetch is usually transient. Retrying in ${CIBUILD_FETCH_WAIT}s..."',
|
|
142
|
+
' sleep "$CIBUILD_FETCH_WAIT"',
|
|
143
|
+
' CIBUILD_FETCH_ATTEMPT=$((CIBUILD_FETCH_ATTEMPT + 1))',
|
|
144
|
+
'done',
|
|
145
|
+
'',
|
|
146
|
+
'if [ "$CIBUILD_FETCH_RC" -ne 0 ]; then',
|
|
147
|
+
' if cibuild_fetch_was_refused; then',
|
|
148
|
+
' cibuild_diagnose_refused_fetch',
|
|
149
|
+
' fi',
|
|
150
|
+
' exit "$CIBUILD_FETCH_RC"',
|
|
151
|
+
'fi',
|
|
152
|
+
'',
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=git-fetch-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-fetch-retry.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/git-fetch-retry.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -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,111 @@
|
|
|
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 } 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('cocoapods-install retries a refused pod source fetch', () => {
|
|
87
|
+
const build = async (inputs = {}) => {
|
|
88
|
+
const executor = new CocoapodsInstallStepExecutor();
|
|
89
|
+
const result = await executor.execute(inputs, {}, testConfig);
|
|
90
|
+
return result.script;
|
|
91
|
+
};
|
|
92
|
+
test('wraps the pod command in the retry', async () => {
|
|
93
|
+
const script = await build();
|
|
94
|
+
expect(script).toContain('cibuild_fetch_was_refused');
|
|
95
|
+
expect(script).toContain('CIBUILD_FETCH_MAX_ATTEMPTS');
|
|
96
|
+
});
|
|
97
|
+
test('still chooses between bundle exec and bare pod', async () => {
|
|
98
|
+
const script = await build();
|
|
99
|
+
expect(script).toContain('bundle exec pod');
|
|
100
|
+
expect(script).toContain('USE_BUNDLE_EXEC');
|
|
101
|
+
});
|
|
102
|
+
test('runs the pod command exactly once in the script', async () => {
|
|
103
|
+
// The old shape invoked pod in both branches of an if/else; the retry
|
|
104
|
+
// needs a single invocation or it would be wrapped twice.
|
|
105
|
+
expect(countOccurrences(await build(), 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
test('still reports completion', async () => {
|
|
108
|
+
expect(await build()).toContain('CocoaPods');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
//# 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":"AAAA;;GAEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEhF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,yBAAyB,CACvB,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAmDpB,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA4HzG;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACzD,yBAAyB,CACvB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAmDpB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAuExG;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kCAAkC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6EAA6E;IAC7E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC;iEAC6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;qDAGiD;IACjD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,gBAAgB;IAC5D,yBAAyB,CACvB,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IA6B1B,UAAU,IAAI,UAAU,EAAE;IASpB,OAAO,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAwU3G;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,yBAAyB,CACvB,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IA4BpB,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAyEzG;AAMD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;sDACkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,yBAAyB,CACvB,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAS1B,UAAU;;;;;IAUJ,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAuH3G;AAMD,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,gBAAgB;IACjE,yBAAyB,CACvB,MAAM,EAAE,uBAAuB,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAsB1B,UAAU,IAAI,UAAU,EAAE;IAOpB,OAAO,CAAC,MAAM,EAAE,uBAAuB,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA6GlH;AAMD,MAAM,WAAW,8BAA8B;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,oCAAqC,SAAQ,gBAAgB;IACxE,yBAAyB,CACvB,MAAM,EAAE,8BAA8B,EACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAgB1B,UAAU,IAAI,UAAU,EAAE;IAMpB,OAAO,CAAC,MAAM,EAAE,8BAA8B,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAkFzH;AAMD,MAAM,WAAW,4BAA4B;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,kCAAmC,SAAQ,gBAAgB;IACtE,yBAAyB,CACvB,MAAM,EAAE,4BAA4B,EACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAsB1B,UAAU,IAAI,UAAU,EAAE;IAMpB,OAAO,CAAC,MAAM,EAAE,4BAA4B,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CA0FvH;AAMD,MAAM,WAAW,qBAAqB;IACpC,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,gBAAgB;IAC/D,yBAAyB,CACvB,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAM1B,UAAU,IAAI,UAAU,EAAE;IAOpB,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAkHhH"}
|
|
@@ -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}'`;
|