@invarn/cibuild 2.5.9 → 2.6.0
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/envman/envstore-round-trip.test.d.ts +27 -0
- package/dist/src/envman/envstore-round-trip.test.d.ts.map +1 -0
- package/dist/src/envman/envstore-round-trip.test.js +130 -0
- package/dist/src/envman/shim.d.ts +16 -0
- package/dist/src/envman/shim.d.ts.map +1 -0
- package/dist/src/envman/shim.js +66 -0
- package/dist/src/runner.d.ts.map +1 -1
- package/dist/src/runner.js +2 -50
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts +27 -0
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/cache-pull-daemon.test.js +183 -0
- package/dist/src/yaml/steps/cache.d.ts.map +1 -1
- package/dist/src/yaml/steps/cache.js +77 -6
- package/dist/src/yaml/steps/steps.test.js +20 -9
- package/package.json +1 -1
|
@@ -86,13 +86,73 @@ function resolvePresetChain(technology) {
|
|
|
86
86
|
}
|
|
87
87
|
return chain;
|
|
88
88
|
}
|
|
89
|
+
/** The token `%{http_code}` is reported under; see `daemonCurl`. */
|
|
90
|
+
const DAEMON_STATUS_TOKEN = 'ci_http_status';
|
|
89
91
|
/**
|
|
90
92
|
* Emits the `curl` invocation the cache steps talk to the daemon with. The
|
|
91
93
|
* bearer token is per-build and only present on the Invarn runner, so it is
|
|
92
94
|
* expanded conditionally and the same script works without one.
|
|
95
|
+
*
|
|
96
|
+
* Every request also reports the HTTP status it got, on **stderr** — the
|
|
97
|
+
* response body is the tarball and goes to stdout, so a status written there
|
|
98
|
+
* would corrupt it. `%{stderr}` is curl 7.63+ (2018); an older curl prints the
|
|
99
|
+
* token literally instead of a number, which the reader below tolerates.
|
|
100
|
+
*
|
|
101
|
+
* `-f`, not `--fail-with-body`: on the pull side the error body would be piped
|
|
102
|
+
* straight into `zstd`. The status is what distinguishes the answers anyway,
|
|
103
|
+
* and it now arrives without the body.
|
|
93
104
|
*/
|
|
94
105
|
function daemonCurl(args) {
|
|
95
|
-
return `curl -fsS \${CIBUILD_CACHE_TOKEN:+-H "Authorization: Bearer $CIBUILD_CACHE_TOKEN"} ${args}`;
|
|
106
|
+
return `curl -fsS -w '%{stderr}${DAEMON_STATUS_TOKEN}=%{http_code}\\n' \${CIBUILD_CACHE_TOKEN:+-H "Authorization: Bearer $CIBUILD_CACHE_TOKEN"} ${args}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Emitted once inside a daemon pull branch, before the first fetch: a file for
|
|
110
|
+
* curl's stderr, and the reader that turns it into one line — or into nothing
|
|
111
|
+
* at all when the daemon simply had no such entry.
|
|
112
|
+
*
|
|
113
|
+
* `{ curl | zstd | tar; } 2>/dev/null` used to swallow curl's message
|
|
114
|
+
* entirely, so a 401, a 400, a 503, a 500 and a daemon that was not listening
|
|
115
|
+
* all printed the same `No cache found for key: …` as an empty cache. That is
|
|
116
|
+
* the same class as the push-side defect: the instrument reported the normal
|
|
117
|
+
* case for every abnormal one.
|
|
118
|
+
*
|
|
119
|
+
* **A miss stays quiet and a failure gets loud.** A cold cache is the normal
|
|
120
|
+
* case and must not start printing errors; the point is only that a broken
|
|
121
|
+
* daemon stops looking like one.
|
|
122
|
+
*/
|
|
123
|
+
function emitDaemonPullDiagnostics(indent = '') {
|
|
124
|
+
return [
|
|
125
|
+
`${indent}__ci_cache_diag="\${TMPDIR:-/tmp}/cibuild-cache-pull.$$.diag"`,
|
|
126
|
+
`${indent}__ci_cache_daemon_said() {`,
|
|
127
|
+
`${indent} local __said=''`,
|
|
128
|
+
`${indent} if [ -s "$__ci_cache_diag" ]; then`,
|
|
129
|
+
`${indent} __said=$(tr '\\n' ' ' < "$__ci_cache_diag")`,
|
|
130
|
+
`${indent} fi`,
|
|
131
|
+
`${indent} case "$__said" in`,
|
|
132
|
+
// 404 is the daemon's answer for `cache_miss` and `no_cache_for_scope` —
|
|
133
|
+
// an empty cache, which is normal. The second pattern reads curl's own
|
|
134
|
+
// English for a curl too old to honour `%{stderr}`, so neither reader
|
|
135
|
+
// failing alone can turn a cold cache noisy.
|
|
136
|
+
`${indent} *${DAEMON_STATUS_TOKEN}=404*|*'error: 404'*) __said='' ;;`,
|
|
137
|
+
`${indent} esac`,
|
|
138
|
+
// Always exits 0. A non-zero return would be the assignment's status at
|
|
139
|
+
// every call site, and under errexit that ends the step on a cache miss.
|
|
140
|
+
`${indent} printf '%s' "\${__said:0:300}"`,
|
|
141
|
+
`${indent}}`,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The `else` arm of a daemon fetch: says why it did not restore, unless the
|
|
146
|
+
* daemon simply had no such entry. Never fatal — a build that cannot read a
|
|
147
|
+
* cache still builds, it just builds cold.
|
|
148
|
+
*/
|
|
149
|
+
function emitDaemonPullFailureDiagnosis(indent, what) {
|
|
150
|
+
return [
|
|
151
|
+
`${indent}__ci_cache_why=$(__ci_cache_daemon_said)`,
|
|
152
|
+
`${indent}if [ -n "$__ci_cache_why" ]; then`,
|
|
153
|
+
`${indent} echo "Warning: cache-pull could not restore ${what} from the daemon — $__ci_cache_why"`,
|
|
154
|
+
`${indent}fi`,
|
|
155
|
+
];
|
|
96
156
|
}
|
|
97
157
|
/**
|
|
98
158
|
* The same request, keeping the response body when the daemon refuses.
|
|
@@ -156,12 +216,18 @@ function emitDaemonPushCommands() {
|
|
|
156
216
|
function emitDaemonPullCommands() {
|
|
157
217
|
return [
|
|
158
218
|
'echo "Cache daemon: $CIBUILD_CACHE_DAEMON"',
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
219
|
+
...emitDaemonPullDiagnostics(),
|
|
220
|
+
// Exact key. zstd's and tar's stderr is silenced — a miss is the normal
|
|
221
|
+
// case and they both editorialise about it; pipefail still carries the
|
|
222
|
+
// exit status. curl's own stderr is kept, in the file the diagnostics
|
|
223
|
+
// 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 | tar -xf - -C /; } 2>/dev/null; then`,
|
|
162
225
|
' echo "Cache found (daemon), extracting..."',
|
|
163
226
|
' echo "CACHE_SOURCE=daemon CACHE_KEY=$CACHE_KEY"',
|
|
164
227
|
'else',
|
|
228
|
+
// Said before the fallback is attempted, so the reason names the request
|
|
229
|
+
// it belongs to rather than whichever request happened to fail last.
|
|
230
|
+
...emitDaemonPullFailureDiagnosis(' ', '$CACHE_KEY'),
|
|
165
231
|
// Scope fallback: the newest tarball for this <keyPrefix>-<projectId>.
|
|
166
232
|
// Gradle/Xcode re-hash their inputs on top of the warm directory, hitting
|
|
167
233
|
// the unchanged work and rebuilding only what actually moved.
|
|
@@ -177,10 +243,11 @@ function emitDaemonPullCommands() {
|
|
|
177
243
|
// than seed from ancient state. Exact-key hits are unaffected.
|
|
178
244
|
' if [ "$__ci_fb_age_days" -le 30 ]; then',
|
|
179
245
|
' echo "Cache fallback (daemon): using prior tarball $__ci_fb_key (${__ci_fb_age_days}d old)"',
|
|
180
|
-
` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$__ci_fb_key.tar.zst"')} | zstd -dc | tar -xf - -C /; } 2>/dev/null; then`,
|
|
246
|
+
` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$__ci_fb_key.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc | tar -xf - -C /; } 2>/dev/null; then`,
|
|
181
247
|
' echo "CACHE_SOURCE=fallback_daemon CACHE_KEY=$CACHE_KEY FALLBACK_KEY=$__ci_fb_key"',
|
|
182
248
|
' else',
|
|
183
249
|
' echo "Cache fallback $__ci_fb_key could not be fetched — going cold"',
|
|
250
|
+
...emitDaemonPullFailureDiagnosis(' ', '$__ci_fb_key'),
|
|
184
251
|
' echo "CACHE_SOURCE=cold CACHE_KEY=$CACHE_KEY"',
|
|
185
252
|
' fi',
|
|
186
253
|
' else',
|
|
@@ -192,6 +259,7 @@ function emitDaemonPullCommands() {
|
|
|
192
259
|
' echo "CACHE_SOURCE=cold CACHE_KEY=$CACHE_KEY"',
|
|
193
260
|
' fi',
|
|
194
261
|
'fi',
|
|
262
|
+
'rm -f "$__ci_cache_diag"',
|
|
195
263
|
];
|
|
196
264
|
}
|
|
197
265
|
/**
|
|
@@ -264,13 +332,16 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
264
332
|
// Transport split — see the preset path. An explicit-key step has no scope
|
|
265
333
|
// to fall back to, so the daemon side is the exact key or nothing.
|
|
266
334
|
commands.push('if [ -n "$CIBUILD_CACHE_DAEMON" ]; then');
|
|
267
|
-
commands.push(
|
|
335
|
+
commands.push(...emitDaemonPullDiagnostics(' '));
|
|
336
|
+
commands.push(` if { ${daemonCurl('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" 2>"$__ci_cache_diag"')} | zstd -dc | tar -xf - -C /; } 2>/dev/null; then`);
|
|
268
337
|
commands.push(' echo "Cache found (daemon), extracting..."');
|
|
269
338
|
commands.push(' echo "CACHE_SOURCE=daemon"');
|
|
270
339
|
commands.push(' else');
|
|
340
|
+
commands.push(...emitDaemonPullFailureDiagnosis(' ', '$CACHE_KEY'));
|
|
271
341
|
commands.push(` echo "No cache found for key: ${this.escapeBash(cacheKey)}"`);
|
|
272
342
|
commands.push(' echo "CACHE_SOURCE=cold"');
|
|
273
343
|
commands.push(' fi');
|
|
344
|
+
commands.push(' rm -f "$__ci_cache_diag"');
|
|
274
345
|
commands.push('else');
|
|
275
346
|
commands.push('if [ -f "$CACHE_FILE" ]; then');
|
|
276
347
|
commands.push(' echo "Cache found (local), extracting..."');
|
|
@@ -218,17 +218,28 @@ describe('Step Implementations', () => {
|
|
|
218
218
|
mkdirSync(swiftpm, { recursive: true });
|
|
219
219
|
writeFileSync(join(swiftpm, 'Package.resolved'), '{"pins":[],"version":3}');
|
|
220
220
|
}
|
|
221
|
-
test('a
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
221
|
+
test('a daemon that is not listening is named once, and leaks no raw noise', async () => {
|
|
222
|
+
// A closed port: curl errors instantly with no server handle to leak.
|
|
223
|
+
//
|
|
224
|
+
// This used to assert that the output contained no `curl:` at all —
|
|
225
|
+
// curl's stderr was silenced whatever the failure mode was, which is
|
|
226
|
+
// how a 401, a 500 and a dead daemon all came to print the same
|
|
227
|
+
// `No cache found` as an empty cache. The contract is now narrower: a
|
|
228
|
+
// *miss* is silent (the 404 case, covered against a real listener in
|
|
229
|
+
// cache-pull-daemon.test.ts), a *failure* is named exactly once, and
|
|
230
|
+
// neither leaks the raw pipeline chatter that made the old blanket
|
|
231
|
+
// silence attractive in the first place.
|
|
232
|
+
const { stdout, stderr, combined } = await runIosPull(writeSpmFixture, {
|
|
229
233
|
CIBUILD_CACHE_DAEMON: 'http://127.0.0.1:1',
|
|
230
234
|
});
|
|
231
|
-
expect(
|
|
235
|
+
expect(stdout).toContain('Warning: cache-pull could not restore');
|
|
236
|
+
expect(stdout).toMatch(/Failed to connect|Couldn't connect|Connection refused/);
|
|
237
|
+
// Once, not once per request the step happens to make.
|
|
238
|
+
expect(stdout.match(/Warning: cache-pull could not restore/gu)).toHaveLength(1);
|
|
239
|
+
// Whatever curl said arrives inside that one line, on stdout. Nothing
|
|
240
|
+
// reaches the build log as loose interleaved stderr, and zstd/tar still
|
|
241
|
+
// do not editorialise about a stream that never arrived.
|
|
242
|
+
expect(stderr).not.toContain('curl:');
|
|
232
243
|
expect(combined).not.toContain('unexpected end of file');
|
|
233
244
|
// Still resolves the SPM key and reports a cold miss.
|
|
234
245
|
expect(stdout).toContain('CACHE_SOURCE=cold');
|