@invarn/cibuild 1.9.3 → 1.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkBxD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sHAAsH;IACtH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUrD,CAAC;AAmFF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YA0GzF,iBAAiB;CA8LhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkBxD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sHAAsH;IACtH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUrD,CAAC;AAmFF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YA0GzF,iBAAiB;CA8LhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAgKzF,iBAAiB;CA4HhC"}
|
|
@@ -484,9 +484,13 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
484
484
|
commands.push(' echo "Paths to cache:"');
|
|
485
485
|
commands.push(' printf " %s\\n" "${PATHS_TO_CACHE[@]}"');
|
|
486
486
|
}
|
|
487
|
-
// Atomic write
|
|
488
|
-
//
|
|
489
|
-
|
|
487
|
+
// Atomic write into a per-process-unique temp (CBR-12). The "runs serialized
|
|
488
|
+
// per runner" assumption behind a fixed "$CACHE_FILE.tmp" is false with two VM
|
|
489
|
+
// slots and collides with the host cache daemon mutating this VirtioFS-shared
|
|
490
|
+
// dir; a unique name (PID + $RANDOM) removes the clash. The atomic mv is kept,
|
|
491
|
+
// and a failed compress/mv removes its own temp (no orphan) and stays non-fatal.
|
|
492
|
+
commands.push(' CACHE_TMP="$CACHE_FILE.$$.$RANDOM.tmp"');
|
|
493
|
+
commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_TMP" && mv "$CACHE_TMP" "$CACHE_FILE" || rm -f "$CACHE_TMP"');
|
|
490
494
|
commands.push(' if [ -f "$CACHE_FILE" ]; then');
|
|
491
495
|
commands.push(' echo "Cache created successfully"');
|
|
492
496
|
if (isDebugMode) {
|
|
@@ -587,40 +591,25 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
587
591
|
// build. Atomic .tmp + mv overwrites any prior tarball under this key.
|
|
588
592
|
commands.push('if [ ${#PATHS_TO_CACHE[@]} -gt 0 ]; then');
|
|
589
593
|
commands.push(' echo "Caching ${#PATHS_TO_CACHE[@]} path(s)..."');
|
|
590
|
-
|
|
594
|
+
// Per-process-unique temp (CBR-12). The old fixed "$CACHE_FILE.tmp" assumed
|
|
595
|
+
// a single serialized writer — false with two VM slots, and it collides with
|
|
596
|
+
// the host cache daemon mutating this VirtioFS-shared dir, which (a) made the
|
|
597
|
+
// guest's own .tmp transiently vanish at `mv` (silent clobber, masked by
|
|
598
|
+
// `|| true`) and (b) dropped VirtioFS off its async fast path. A unique name
|
|
599
|
+
// removes the collision; the atomic mv into place is preserved, and a failed
|
|
600
|
+
// compress/mv removes its own temp (no orphan) while staying non-fatal.
|
|
601
|
+
commands.push(' CACHE_TMP="$CACHE_FILE.$$.$RANDOM.tmp"');
|
|
602
|
+
commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_TMP" && mv "$CACHE_TMP" "$CACHE_FILE" || rm -f "$CACHE_TMP"');
|
|
591
603
|
commands.push(' if [ -f "$CACHE_FILE" ]; then');
|
|
592
604
|
commands.push(' echo "Cache created successfully"');
|
|
593
605
|
if (isDebugMode) {
|
|
594
606
|
commands.push(' echo "Cache size: $(du -h "$CACHE_FILE" | cut -f1)"');
|
|
595
607
|
}
|
|
596
|
-
// Retention
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
// count/age pass is about to delete and over-evicts.
|
|
602
|
-
commands.push(' __ci_ret_scope="${CACHE_KEY%-*}"');
|
|
603
|
-
commands.push(' __ci_ret_count_cap=5');
|
|
604
|
-
commands.push(' __ci_ret_age_cap=$((30 * 86400))');
|
|
605
|
-
commands.push(' __ci_ret_size_cap_kb="${CIBUILD_CACHE_SCOPE_BUDGET_KB:-10485760}"');
|
|
606
|
-
commands.push(' __ci_ret_now=$(date +%s)');
|
|
607
|
-
commands.push(' __ci_ret_idx=0');
|
|
608
|
-
commands.push(' while IFS= read -r __ci_ret_f; do');
|
|
609
|
-
commands.push(' __ci_ret_idx=$((__ci_ret_idx + 1))');
|
|
610
|
-
commands.push(' __ci_ret_age=$(( __ci_ret_now - $(stat -f %m "$__ci_ret_f" 2>/dev/null || echo "$__ci_ret_now") ))');
|
|
611
|
-
commands.push(' if [ "$__ci_ret_idx" -gt "$__ci_ret_count_cap" ] || [ "$__ci_ret_age" -gt "$__ci_ret_age_cap" ]; then');
|
|
612
|
-
commands.push(' rm -f "$__ci_ret_f"');
|
|
613
|
-
commands.push(' fi');
|
|
614
|
-
commands.push(' done < <(ls -t "$CACHE_DIR"/"$__ci_ret_scope"-*.tar.zst 2>/dev/null)');
|
|
615
|
-
commands.push(' __ci_ret_running_kb=0');
|
|
616
|
-
commands.push(' while IFS= read -r __ci_ret_f; do');
|
|
617
|
-
commands.push(' __ci_ret_size=$(du -k "$__ci_ret_f" 2>/dev/null | cut -f1)');
|
|
618
|
-
commands.push(' __ci_ret_size=${__ci_ret_size:-0}');
|
|
619
|
-
commands.push(' __ci_ret_running_kb=$((__ci_ret_running_kb + __ci_ret_size))');
|
|
620
|
-
commands.push(' if [ "$__ci_ret_running_kb" -gt "$__ci_ret_size_cap_kb" ]; then');
|
|
621
|
-
commands.push(' rm -f "$__ci_ret_f"');
|
|
622
|
-
commands.push(' fi');
|
|
623
|
-
commands.push(' done < <(ls -t "$CACHE_DIR"/"$__ci_ret_scope"-*.tar.zst 2>/dev/null)');
|
|
608
|
+
// Retention is no longer applied here (ADR 0002 / CBR-6). It moved into the
|
|
609
|
+
// long-lived peer cache daemon, which owns ~/cache and — unlike the guest —
|
|
610
|
+
// knows (via the manager) which scopes this runner OWNS and must never evict.
|
|
611
|
+
// A guest-side prune would have to run blind to ownership and could reap the
|
|
612
|
+
// fleet's single durable copy, so the daemon owns the whole policy now.
|
|
624
613
|
commands.push(' else');
|
|
625
614
|
commands.push(' echo "Warning: Failed to create cache file"');
|
|
626
615
|
commands.push(' fi');
|
|
@@ -336,6 +336,20 @@ describe('Step Implementations', () => {
|
|
|
336
336
|
const result = await executor.execute(inputs, {}, testConfig);
|
|
337
337
|
expect(result.script).toContain('skipped');
|
|
338
338
|
});
|
|
339
|
+
test('uses a per-process-unique temp for the atomic write (CBR-12)', async () => {
|
|
340
|
+
const executor = new CachePushStepExecutor();
|
|
341
|
+
const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
|
|
342
|
+
// The old fixed "$CACHE_FILE.tmp" assumed a single writer ("runs
|
|
343
|
+
// serialized per runner") — false with two VM slots, and it collides with
|
|
344
|
+
// the host cache daemon mutating the same VirtioFS-shared dir. A unique
|
|
345
|
+
// temp (PID + $RANDOM) removes the collision; the atomic mv is preserved.
|
|
346
|
+
expect(result.script).not.toContain('> "$CACHE_FILE.tmp"');
|
|
347
|
+
expect(result.script).toContain('CACHE_TMP="$CACHE_FILE.$$.$RANDOM.tmp"');
|
|
348
|
+
expect(result.script).toContain('mv "$CACHE_TMP" "$CACHE_FILE"');
|
|
349
|
+
// A failed compress/mv cleans its own temp instead of leaking orphans,
|
|
350
|
+
// and stays non-fatal (never fails the build).
|
|
351
|
+
expect(result.script).toContain('rm -f "$CACHE_TMP"');
|
|
352
|
+
});
|
|
339
353
|
test('should require cache_paths', async () => {
|
|
340
354
|
const executor = new CachePushStepExecutor();
|
|
341
355
|
const inputs = {
|
|
@@ -421,20 +435,24 @@ describe('Step Implementations', () => {
|
|
|
421
435
|
test('should use atomic write (tmp + mv) for preset push', async () => {
|
|
422
436
|
const executor = new CachePushStepExecutor();
|
|
423
437
|
const result = await executor.execute({ technology: 'gradle' }, {}, testConfig);
|
|
424
|
-
expect(result.script).toContain('> "$
|
|
425
|
-
expect(result.script).toContain('mv "$
|
|
426
|
-
});
|
|
427
|
-
test('should
|
|
438
|
+
expect(result.script).toContain('> "$CACHE_TMP"');
|
|
439
|
+
expect(result.script).toContain('mv "$CACHE_TMP" "$CACHE_FILE"');
|
|
440
|
+
});
|
|
441
|
+
test('should NOT emit guest-side retention (the peer cache daemon owns it, CBR-6)', async () => {
|
|
442
|
+
// ADR 0002 / CBR-6: retention moved into the long-lived peer cache daemon,
|
|
443
|
+
// which knows (via the manager) which scopes this runner owns and must never
|
|
444
|
+
// evict. The guest cache-push no longer prunes — it could only reap an owner's
|
|
445
|
+
// durable copy, having no ownership knowledge of its own.
|
|
428
446
|
const executor = new CachePushStepExecutor();
|
|
429
447
|
const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
|
|
430
|
-
expect(result.script).toContain('__ci_ret_scope
|
|
431
|
-
expect(result.script).toContain('
|
|
432
|
-
expect(result.script).toContain('
|
|
433
|
-
expect(result.script).toContain('
|
|
434
|
-
|
|
435
|
-
expect(result.script).toContain('
|
|
436
|
-
});
|
|
437
|
-
test('should not
|
|
448
|
+
expect(result.script).not.toContain('__ci_ret_scope');
|
|
449
|
+
expect(result.script).not.toContain('__ci_ret_count_cap');
|
|
450
|
+
expect(result.script).not.toContain('__ci_ret_age_cap');
|
|
451
|
+
expect(result.script).not.toContain('CIBUILD_CACHE_SCOPE_BUDGET_KB');
|
|
452
|
+
// The successful-push path itself is unchanged.
|
|
453
|
+
expect(result.script).toContain('Cache created successfully');
|
|
454
|
+
});
|
|
455
|
+
test('should not emit retention for a manual cache_key push either', async () => {
|
|
438
456
|
const executor = new CachePushStepExecutor();
|
|
439
457
|
const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
|
|
440
458
|
expect(result.script).not.toContain('__ci_ret_scope');
|
|
@@ -443,8 +461,8 @@ describe('Step Implementations', () => {
|
|
|
443
461
|
test('should use atomic write (tmp + mv) for manual cache_key push', async () => {
|
|
444
462
|
const executor = new CachePushStepExecutor();
|
|
445
463
|
const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
|
|
446
|
-
expect(result.script).toContain('> "$
|
|
447
|
-
expect(result.script).toContain('mv "$
|
|
464
|
+
expect(result.script).toContain('> "$CACHE_TMP"');
|
|
465
|
+
expect(result.script).toContain('mv "$CACHE_TMP" "$CACHE_FILE"');
|
|
448
466
|
});
|
|
449
467
|
});
|
|
450
468
|
describe('XcodeBuildStepExecutor', () => {
|