@invarn/cibuild 2.6.0 → 2.6.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/meta-helpers.d.ts +9 -0
- package/dist/src/yaml/meta-helpers.d.ts.map +1 -1
- package/dist/src/yaml/meta-helpers.js +26 -0
- package/dist/src/yaml/step-validator.d.ts +1 -0
- package/dist/src/yaml/step-validator.d.ts.map +1 -1
- package/dist/src/yaml/step-validator.js +11 -1
- package/dist/src/yaml/step-validator.test.d.ts +18 -0
- package/dist/src/yaml/step-validator.test.d.ts.map +1 -0
- package/dist/src/yaml/step-validator.test.js +140 -0
- package/dist/src/yaml/steps/cache-pull-daemon.test.js +22 -2
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts +42 -0
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.js +200 -0
- package/dist/src/yaml/steps/cache-push-daemon.test.js +32 -2
- package/dist/src/yaml/steps/cache.d.ts.map +1 -1
- package/dist/src/yaml/steps/cache.js +185 -27
- package/package.json +1 -1
|
@@ -86,6 +86,126 @@ function resolvePresetChain(technology) {
|
|
|
86
86
|
}
|
|
87
87
|
return chain;
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves the `identifier:pattern` shorthand a `cache_paths` entry may use
|
|
91
|
+
* (`derived_data:MyApp-*`, `builds:nightly`) into a real path. Anything else is
|
|
92
|
+
* returned unchanged.
|
|
93
|
+
*
|
|
94
|
+
* Shared because the two sides used to disagree: `cache-push` resolved these
|
|
95
|
+
* and `cache-pull` did not, so the classification below would have put a
|
|
96
|
+
* `derived_data:` entry in the project-relative class on the way out and the
|
|
97
|
+
* absolute class on the way in.
|
|
98
|
+
*/
|
|
99
|
+
function resolveCachePathIdentifier(path, config) {
|
|
100
|
+
if (!path.includes(':'))
|
|
101
|
+
return path;
|
|
102
|
+
const [identifier, pattern] = path.split(':', 2);
|
|
103
|
+
switch (identifier) {
|
|
104
|
+
case 'derived_data':
|
|
105
|
+
return `${config.paths.derivedDataDir}/${pattern}`;
|
|
106
|
+
case 'builds':
|
|
107
|
+
return `${config.paths.buildsDir}/${pattern}`;
|
|
108
|
+
default:
|
|
109
|
+
return path;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Splits `cache_paths` into the two classes that decide where a member has to
|
|
114
|
+
* be extracted to.
|
|
115
|
+
*
|
|
116
|
+
* `cache-push` archives whatever `PATHS_TO_CACHE` holds. A `~`- or `/`-rooted
|
|
117
|
+
* entry is expanded to an absolute path and tar stores it with the leading `/`
|
|
118
|
+
* stripped (`/home/builder/.gradle/caches` -> `home/builder/.gradle/caches`),
|
|
119
|
+
* which round-trips through `-C /`. A project-relative entry (`Pods`,
|
|
120
|
+
* `.gradle`, `node_modules`) is stored as-is, relative to the step's working
|
|
121
|
+
* directory — and `-C /` then tries to create it at the root of the
|
|
122
|
+
* filesystem, which the build user cannot do.
|
|
123
|
+
*
|
|
124
|
+
* So the class is decided by the declared form, not by anything in the tarball,
|
|
125
|
+
* and both steps can compute it from the same input.
|
|
126
|
+
*/
|
|
127
|
+
function classifyCachePaths(paths, config) {
|
|
128
|
+
const absolute = [];
|
|
129
|
+
const relative = [];
|
|
130
|
+
for (const raw of paths) {
|
|
131
|
+
const path = resolveCachePathIdentifier(raw, config);
|
|
132
|
+
if (path.startsWith('~') || path.startsWith('/')) {
|
|
133
|
+
absolute.push(path);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
relative.push(path);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { absolute, relative };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Emits `__ci_cache_extract`, which every restore path pipes its tar stream
|
|
143
|
+
* into, plus the two member lists it reads.
|
|
144
|
+
*
|
|
145
|
+
* **The defect it closes.** Every restore used one `tar -xf - -C /`. For a
|
|
146
|
+
* `cache_paths` that mixes the two classes — which every Gradle/Android
|
|
147
|
+
* pipeline does, including the one the README ships — the absolute half landed
|
|
148
|
+
* (that *was* the warmth) and the relative half could not, so tar exited
|
|
149
|
+
* non-zero, the compound condition went false, and a build that had just been
|
|
150
|
+
* restored reported `CACHE_SOURCE=cold`. Worse, a pipeline whose paths are
|
|
151
|
+
* *entirely* project-relative (`Pods`, `Podfile.lock`, `node_modules`) had
|
|
152
|
+
* every member fail: those caches were written on every build and had never
|
|
153
|
+
* restored anything. Measured on GNU tar 1.35 and bsdtar 3.5.3 alike.
|
|
154
|
+
*
|
|
155
|
+
* On the filesystem transport the same failure was not a mis-report at all —
|
|
156
|
+
* the extraction is a bare command there, so under `set -e -o pipefail` it
|
|
157
|
+
* ended the step.
|
|
158
|
+
*
|
|
159
|
+
* **Cost.** A restore with no project-relative paths keeps today's single
|
|
160
|
+
* streaming pass and stages nothing, so the shape that already worked pays
|
|
161
|
+
* nothing. The mixed and all-relative shapes stage the tar and read it twice,
|
|
162
|
+
* which is the price of extracting two roots from one stream.
|
|
163
|
+
*
|
|
164
|
+
* Written for bash 3.2 — macOS ships it, and the generated script says
|
|
165
|
+
* `#!/bin/bash`.
|
|
166
|
+
*/
|
|
167
|
+
function emitCacheExtractHelper(paths, config, indent = '') {
|
|
168
|
+
const { absolute, relative } = classifyCachePaths(paths, config);
|
|
169
|
+
const lit = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
170
|
+
const out = [
|
|
171
|
+
// Captured before anything can cd: project-relative members are recorded
|
|
172
|
+
// against the step's working directory, so that is where they go back.
|
|
173
|
+
`${indent}__ci_cache_root="$PWD"`,
|
|
174
|
+
`${indent}__ci_cache_abs=()`,
|
|
175
|
+
`${indent}__ci_cache_rel=()`,
|
|
176
|
+
];
|
|
177
|
+
for (const p of absolute) {
|
|
178
|
+
// Expanded at runtime — `~` means ${CIBUILD_USER_HOME:-$HOME}, which the
|
|
179
|
+
// generator cannot know. The leading `/` is stripped to match how tar
|
|
180
|
+
// recorded the member.
|
|
181
|
+
out.push(`${indent}__ci_p=${lit(p)}`);
|
|
182
|
+
out.push(`${indent}__ci_p="\${__ci_p/#~/\${CIBUILD_USER_HOME:-$HOME}}"`);
|
|
183
|
+
out.push(`${indent}__ci_cache_abs+=("\${__ci_p#/}")`);
|
|
184
|
+
}
|
|
185
|
+
for (const p of relative) {
|
|
186
|
+
out.push(`${indent}__ci_cache_rel+=(${lit(p)})`);
|
|
187
|
+
}
|
|
188
|
+
out.push(`${indent}__ci_cache_extract() {`,
|
|
189
|
+
// Nothing project-relative: the archive is entirely absolute-derived and
|
|
190
|
+
// one streaming pass is both correct and cheapest.
|
|
191
|
+
`${indent} if [ \${#__ci_cache_rel[@]} -eq 0 ]; then`, `${indent} tar -xf - -C /`, `${indent} return`, `${indent} fi`, `${indent} local __t="\${TMPDIR:-/tmp}/cibuild-cache-extract.$$.tar"`, `${indent} local __rc=0 __top __c __is_rel __p`, `${indent} local __abs_ops __rel_ops`, `${indent} cat > "$__t" || { rm -f "$__t"; return 1; }`,
|
|
192
|
+
// The split is decided on the archive's own top-level names, not by
|
|
193
|
+
// `--exclude`. tar's exclude patterns are unanchored: `--exclude=.gradle`
|
|
194
|
+
// also matches `home/builder/.gradle`, so excluding the project-relative
|
|
195
|
+
// half from the pass that targets `/` silently dropped the absolute half
|
|
196
|
+
// too whenever the two shared a component name — which the canonical
|
|
197
|
+
// Gradle shape (`~/.gradle/caches` plus `.gradle`) always does.
|
|
198
|
+
`${indent} __top=$(tar -tf "$__t" 2>/dev/null | sed -e 's|^\\./||' -e 's|/.*||' | grep -v '^$' | sort -u) || __rc=$?`, `${indent} if [ "$__rc" -ne 0 ]; then rm -f "$__t"; return "$__rc"; fi`, `${indent} __abs_ops=()`, `${indent} __rel_ops=()`,
|
|
199
|
+
// Read rather than word-split: a cached path may contain spaces
|
|
200
|
+
// (`derived_data:My App-*`). Process substitution keeps the loop in this
|
|
201
|
+
// shell, so the arrays it fills survive it.
|
|
202
|
+
`${indent} while IFS= read -r __c; do`, `${indent} __is_rel=0`, `${indent} for __p in "\${__ci_cache_rel[@]}"; do`, `${indent} case "$__p" in "$__c"|"$__c"/*) __is_rel=1 ;; esac`, `${indent} done`,
|
|
203
|
+
// A name claimed by both classes resolves to `/`, which is what it did
|
|
204
|
+
// before this existed. It needs a project directory named after a
|
|
205
|
+
// filesystem root while also caching an absolute path under it.
|
|
206
|
+
`${indent} if [ "$__is_rel" -eq 1 ]; then __rel_ops+=("$__c"); else __abs_ops+=("$__c"); fi`, `${indent} done < <(printf '%s\\n' "$__top")`, `${indent} if [ \${#__abs_ops[@]} -gt 0 ]; then`, `${indent} tar -xf "$__t" -C / "\${__abs_ops[@]}" || __rc=$?`, `${indent} fi`, `${indent} if [ "$__rc" -eq 0 ] && [ \${#__rel_ops[@]} -gt 0 ]; then`, `${indent} tar -xf "$__t" -C "$__ci_cache_root" "\${__rel_ops[@]}" || __rc=$?`, `${indent} fi`, `${indent} rm -f "$__t"`, `${indent} return "$__rc"`, `${indent}}`);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
89
209
|
/** The token `%{http_code}` is reported under; see `daemonCurl`. */
|
|
90
210
|
const DAEMON_STATUS_TOKEN = 'ci_http_status';
|
|
91
211
|
/**
|
|
@@ -182,16 +302,53 @@ function daemonCurlKeepingErrorBody(args) {
|
|
|
182
302
|
* reported success. Every curated Invarn template uses the explicit-key form,
|
|
183
303
|
* so nothing was ever stored by any of them. One definition cannot diverge
|
|
184
304
|
* from itself.
|
|
305
|
+
*
|
|
306
|
+
* The failure message names the stage that failed, which took a second fix.
|
|
307
|
+
* `$( … 2>&1 )` captured curl's stdout, and `--fail-with-body` writes the
|
|
308
|
+
* daemon's response body there on success as well as on refusal — so when the
|
|
309
|
+
* *middle* stage died (no `zstd` on the image), pipefail took the else branch
|
|
310
|
+
* and printed the daemon's **success** body as the reason the upload failed:
|
|
311
|
+
*
|
|
312
|
+
* Warning: failed to upload cache to the daemon — {"ok":true}
|
|
313
|
+
*
|
|
314
|
+
* No stream separates those two cases, because the body is on stdout either
|
|
315
|
+
* way; only the stage's exit status does. Hence `PIPESTATUS` rather than the
|
|
316
|
+
* pull side's diag-file split — the file is still here, but it is the daemon's
|
|
317
|
+
* answer, and it is quoted only when curl is what refused. A rejection's
|
|
318
|
+
* reason still reaches the log, which `cache-wire-test.sh` asserts.
|
|
185
319
|
*/
|
|
186
320
|
function emitDaemonPushCommands() {
|
|
187
321
|
return [
|
|
188
|
-
|
|
322
|
+
// curl's own stdout and stderr, kept out of the pipeline's status. Mirrors
|
|
323
|
+
// the pull side's `$__ci_cache_diag`.
|
|
324
|
+
' __ci_push_out="${TMPDIR:-/tmp}/cibuild-cache-push.$$.out"',
|
|
325
|
+
` if tar -cf - "\${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 | ${daemonCurlKeepingErrorBody('-T - "$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst"')} >"$__ci_push_out" 2>&1; then`,
|
|
189
326
|
' echo "Cache created successfully"',
|
|
190
327
|
' else',
|
|
328
|
+
// Read first, before anything else in this branch: PIPESTATUS holds the
|
|
329
|
+
// last pipeline's statuses and the next command replaces it.
|
|
330
|
+
' __ci_push_rc=("${PIPESTATUS[@]}")',
|
|
191
331
|
// Never fatal: a build that produced good output should not be failed by
|
|
192
332
|
// a cache upload, and the next build simply misses. Saying *why* costs
|
|
193
333
|
// nothing and is the difference between a warning and a diagnosis.
|
|
194
|
-
|
|
334
|
+
' __ci_push_msg=""',
|
|
335
|
+
' if [ "${__ci_push_rc[2]}" != 0 ]; then',
|
|
336
|
+
// The daemon refused, so the daemon's own answer is the reason —
|
|
337
|
+
// `invalid_cache_key`, `cache_busy`, `empty_body`, an auth failure.
|
|
338
|
+
// `[ -s ]` guards the substitution: an assignment whose command
|
|
339
|
+
// substitution fails is a non-zero statement, and under errexit that ends
|
|
340
|
+
// the step.
|
|
341
|
+
' if [ -s "$__ci_push_out" ]; then',
|
|
342
|
+
" __ci_push_msg=$(tr '\\n' ' ' < \"$__ci_push_out\")",
|
|
343
|
+
' fi',
|
|
344
|
+
' elif [ "${__ci_push_rc[1]}" = 127 ]; then',
|
|
345
|
+
// curl was happy — it uploaded whatever reached it, which was nothing.
|
|
346
|
+
// 127 is the shell failing to find the command, and zstd itself never
|
|
347
|
+
// exits 127, so this is unambiguous and worth saying outright.
|
|
348
|
+
' __ci_push_msg="zstd is not installed on this image, so nothing was uploaded"',
|
|
349
|
+
' else',
|
|
350
|
+
' __ci_push_msg="nothing was uploaded — tar exited ${__ci_push_rc[0]}, zstd exited ${__ci_push_rc[1]}"',
|
|
351
|
+
' fi',
|
|
195
352
|
' __ci_push_msg=${__ci_push_msg:0:300}',
|
|
196
353
|
' if [ -n "$__ci_push_msg" ]; then',
|
|
197
354
|
' echo "Warning: failed to upload cache to the daemon — $__ci_push_msg"',
|
|
@@ -199,6 +356,7 @@ function emitDaemonPushCommands() {
|
|
|
199
356
|
' echo "Warning: failed to upload cache to the daemon"',
|
|
200
357
|
' fi',
|
|
201
358
|
' fi',
|
|
359
|
+
' rm -f "$__ci_push_out"',
|
|
202
360
|
];
|
|
203
361
|
}
|
|
204
362
|
/**
|
|
@@ -221,7 +379,7 @@ function emitDaemonPullCommands() {
|
|
|
221
379
|
// case and they both editorialise about it; pipefail still carries the
|
|
222
380
|
// exit status. curl's own stderr is kept, in the file the diagnostics
|
|
223
381
|
// above read: it is the only thing that knows *why*.
|
|
224
|
-
`if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc |
|
|
382
|
+
`if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc | __ci_cache_extract; } 2>/dev/null; then`,
|
|
225
383
|
' echo "Cache found (daemon), extracting..."',
|
|
226
384
|
' echo "CACHE_SOURCE=daemon CACHE_KEY=$CACHE_KEY"',
|
|
227
385
|
'else',
|
|
@@ -243,7 +401,7 @@ function emitDaemonPullCommands() {
|
|
|
243
401
|
// than seed from ancient state. Exact-key hits are unaffected.
|
|
244
402
|
' if [ "$__ci_fb_age_days" -le 30 ]; then',
|
|
245
403
|
' echo "Cache fallback (daemon): using prior tarball $__ci_fb_key (${__ci_fb_age_days}d old)"',
|
|
246
|
-
` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$__ci_fb_key.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc |
|
|
404
|
+
` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$__ci_fb_key.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc | __ci_cache_extract; } 2>/dev/null; then`,
|
|
247
405
|
' echo "CACHE_SOURCE=fallback_daemon CACHE_KEY=$CACHE_KEY FALLBACK_KEY=$__ci_fb_key"',
|
|
248
406
|
' else',
|
|
249
407
|
' echo "Cache fallback $__ci_fb_key could not be fetched — going cold"',
|
|
@@ -326,6 +484,11 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
326
484
|
commands.push(`PEER_CACHE_DIR="${this.escapeBash(peerCacheDir)}"`);
|
|
327
485
|
commands.push(`PEER_CACHE_FILE="$PEER_CACHE_DIR/${this.escapeBash(cacheKey)}.tar.zst"`);
|
|
328
486
|
}
|
|
487
|
+
// Where each class of member is restored to. Emitted before every
|
|
488
|
+
// transport, because all of them extract.
|
|
489
|
+
commands.push('');
|
|
490
|
+
commands.push('# Restore roots');
|
|
491
|
+
commands.push(...emitCacheExtractHelper(cachePaths, config));
|
|
329
492
|
// Check if cache file exists
|
|
330
493
|
commands.push('');
|
|
331
494
|
commands.push('# Check if cache exists');
|
|
@@ -333,7 +496,7 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
333
496
|
// to fall back to, so the daemon side is the exact key or nothing.
|
|
334
497
|
commands.push('if [ -n "$CIBUILD_CACHE_DAEMON" ]; then');
|
|
335
498
|
commands.push(...emitDaemonPullDiagnostics(' '));
|
|
336
|
-
commands.push(` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc |
|
|
499
|
+
commands.push(` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc | __ci_cache_extract; } 2>/dev/null; then`);
|
|
337
500
|
commands.push(' echo "Cache found (daemon), extracting..."');
|
|
338
501
|
commands.push(' echo "CACHE_SOURCE=daemon"');
|
|
339
502
|
commands.push(' else');
|
|
@@ -348,13 +511,13 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
348
511
|
if (isDebugMode) {
|
|
349
512
|
commands.push(' echo "Cache file size: $(du -h "$CACHE_FILE" | cut -f1)"');
|
|
350
513
|
}
|
|
351
|
-
commands.push(' zstd -dc "$CACHE_FILE" |
|
|
514
|
+
commands.push(' zstd -dc "$CACHE_FILE" | __ci_cache_extract');
|
|
352
515
|
commands.push(' echo "CACHE_SOURCE=local"');
|
|
353
516
|
// Peer fallback
|
|
354
517
|
if (peerCacheDir) {
|
|
355
518
|
commands.push('elif [ -f "$PEER_CACHE_FILE" ] 2>/dev/null; then');
|
|
356
519
|
commands.push(' echo "Cache found (peer), extracting..."');
|
|
357
|
-
commands.push(' tee "$CACHE_FILE.tmp" < "$PEER_CACHE_FILE" | zstd -dc |
|
|
520
|
+
commands.push(' tee "$CACHE_FILE.tmp" < "$PEER_CACHE_FILE" | zstd -dc | __ci_cache_extract');
|
|
358
521
|
commands.push(' mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
359
522
|
commands.push(' echo "CACHE_SOURCE=peer"');
|
|
360
523
|
}
|
|
@@ -446,6 +609,13 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
446
609
|
}
|
|
447
610
|
commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
|
|
448
611
|
commands.push('');
|
|
612
|
+
// Where each class of member is restored to. Every path below extracts, so
|
|
613
|
+
// this is emitted once, above the transport split. `allPaths` is the same
|
|
614
|
+
// union cache-push archives from, which is what keeps the two sides
|
|
615
|
+
// agreeing about which class a path is in.
|
|
616
|
+
commands.push('# Restore roots');
|
|
617
|
+
commands.push(...emitCacheExtractHelper([...new Set(chain.flatMap(p => p.paths))], config));
|
|
618
|
+
commands.push('');
|
|
449
619
|
// Transport split. On a runner the cache lives on the host and is reached
|
|
450
620
|
// over HTTP; standalone, it is a directory on this machine. The daemon URL
|
|
451
621
|
// is the switch, so the same generated script serves both.
|
|
@@ -465,7 +635,7 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
465
635
|
if (isDebugMode) {
|
|
466
636
|
commands.push(' echo "Cache file size: $(du -h "$CACHE_FILE" | cut -f1)"');
|
|
467
637
|
}
|
|
468
|
-
commands.push(' zstd -dc "$CACHE_FILE" |
|
|
638
|
+
commands.push(' zstd -dc "$CACHE_FILE" | __ci_cache_extract');
|
|
469
639
|
// LRU bump: lift this tarball to the top of `ls -t` so retention's age
|
|
470
640
|
// cap counts last-used time, not last-written. Without this, a tarball
|
|
471
641
|
// hit daily would still be reaped on its 31st day from creation.
|
|
@@ -479,7 +649,7 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
479
649
|
commands.push(' echo "Peer cache file size: $(du -h "$PEER_CACHE_FILE" | cut -f1)"');
|
|
480
650
|
}
|
|
481
651
|
// Single NFS read: tee forks the stream to local cache file AND decompression
|
|
482
|
-
commands.push(' tee "$CACHE_FILE.tmp" < "$PEER_CACHE_FILE" | zstd -dc |
|
|
652
|
+
commands.push(' tee "$CACHE_FILE.tmp" < "$PEER_CACHE_FILE" | zstd -dc | __ci_cache_extract');
|
|
483
653
|
commands.push(' mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
484
654
|
commands.push(' echo "CACHE_SOURCE=peer CACHE_KEY=$CACHE_KEY"');
|
|
485
655
|
}
|
|
@@ -518,7 +688,7 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
518
688
|
if (isDebugMode) {
|
|
519
689
|
commands.push(' echo "Fallback size: $(du -h "$__ci_fb" | cut -f1)"');
|
|
520
690
|
}
|
|
521
|
-
commands.push(' zstd -dc "$__ci_fb" |
|
|
691
|
+
commands.push(' zstd -dc "$__ci_fb" | __ci_cache_extract');
|
|
522
692
|
// LRU bump for the fallback tarball — we restored from it, so it earns
|
|
523
693
|
// its place at the top of the retention sort even if the exact-key
|
|
524
694
|
// tarball is the one that gets created in this run's cache-push.
|
|
@@ -623,23 +793,11 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
623
793
|
commands.push('# Check which paths exist');
|
|
624
794
|
commands.push('PATHS_TO_CACHE=()');
|
|
625
795
|
for (const path of cachePaths) {
|
|
626
|
-
// Resolve path identifiers (e.g., "derived_data:pattern" -> "~/Library/.../DerivedData/pattern")
|
|
627
|
-
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
switch (identifier) {
|
|
632
|
-
case 'derived_data':
|
|
633
|
-
resolvedPath = `${config.paths.derivedDataDir}/${pattern}`;
|
|
634
|
-
break;
|
|
635
|
-
case 'builds':
|
|
636
|
-
resolvedPath = `${config.paths.buildsDir}/${pattern}`;
|
|
637
|
-
break;
|
|
638
|
-
default:
|
|
639
|
-
// Unknown identifier, use as-is
|
|
640
|
-
resolvedPath = path;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
796
|
+
// Resolve path identifiers (e.g., "derived_data:pattern" -> "~/Library/.../DerivedData/pattern").
|
|
797
|
+
// Shared with cache-pull's member classification: if only one side
|
|
798
|
+
// resolved these, the two would disagree about which restore root the
|
|
799
|
+
// path belongs to.
|
|
800
|
+
const resolvedPath = resolveCachePathIdentifier(path, config);
|
|
643
801
|
// Expand tilde by using parameter expansion
|
|
644
802
|
commands.push(`EXPANDED_PATH="${this.escapeBash(resolvedPath)}"`);
|
|
645
803
|
commands.push('EXPANDED_PATH="${EXPANDED_PATH/#~/${CIBUILD_USER_HOME:-$HOME}}"');
|