@smoothbricks/cli 0.11.10 → 0.11.11
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.
|
@@ -31,20 +31,16 @@ add_repo_paths() {
|
|
|
31
31
|
# `--accept-flake-config` makes it worse than untidy: it pre-trusts the
|
|
32
32
|
# substituters declared by a commit nobody looked at.
|
|
33
33
|
#
|
|
34
|
-
# Resolved lazily, on the
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
if [ -n "${DEVENV_FLAKE:-}" ]; then
|
|
38
|
-
printf '%s' "$DEVENV_FLAKE"
|
|
39
|
-
return 0
|
|
40
|
-
fi
|
|
34
|
+
# Resolved lazily, on the paths that need it, because a host runner never does
|
|
35
|
+
# and that is the one branch which must not pay for a `nix eval`.
|
|
36
|
+
devenv_locked_rev() {
|
|
41
37
|
local lock="$repo_root/tooling/direnv/devenv.lock" rev
|
|
42
38
|
# nix, not jq: jq arrives with the shell this script is about to build, while
|
|
43
39
|
# nix is guaranteed by the install step before it. fromJSON also beats
|
|
44
40
|
# hand-rolled parsing of a file whose key order is not ours to assume.
|
|
45
41
|
# Absolute-path readFile needs --impure; nix's own error is left on stderr
|
|
46
|
-
# rather than swallowed, because an unresolvable pin is the failure
|
|
47
|
-
#
|
|
42
|
+
# rather than swallowed, because an unresolvable pin is the failure these
|
|
43
|
+
# functions exist to prevent.
|
|
48
44
|
rev="$(nix eval --raw --impure --expr \
|
|
49
45
|
"(builtins.fromJSON (builtins.readFile \"$lock\")).nodes.devenv.locked.rev")" || rev=""
|
|
50
46
|
if [ -z "$rev" ]; then
|
|
@@ -52,26 +48,89 @@ devenv_flake() {
|
|
|
52
48
|
echo " set DEVENV_FLAKE to install a devenv explicitly" >&2
|
|
53
49
|
return 1
|
|
54
50
|
fi
|
|
55
|
-
printf '
|
|
51
|
+
printf '%s' "$rev"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Whether a devenv binary is the commit the lock names. `devenv version` prints
|
|
55
|
+
# "devenv <semver>+<short rev> (<system>)", so the abbreviated commit is right
|
|
56
|
+
# there and a prefix test against the full rev is the entire comparison — no
|
|
57
|
+
# assumption about how many characters devenv chooses to abbreviate to. A build
|
|
58
|
+
# with no "+<rev>" at all cannot prefix a 40-char hex rev, so it reads as a
|
|
59
|
+
# mismatch, which is the safe direction.
|
|
60
|
+
devenv_matches_lock() {
|
|
61
|
+
local bin="$1" rev="$2" printed
|
|
62
|
+
printed="$("$bin" version 2>/dev/null)" || return 1
|
|
63
|
+
printed="${printed#*+}"
|
|
64
|
+
printed="${printed%% *}"
|
|
65
|
+
[ -n "$printed" ] || return 1
|
|
66
|
+
case "$rev" in
|
|
67
|
+
"$printed"*) return 0 ;;
|
|
68
|
+
esac
|
|
69
|
+
return 1
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
install_devenv() {
|
|
59
|
-
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
73
|
+
local rev flake found=""
|
|
74
|
+
# Host runners own their Nix profile, and that profile roots a /nix/store the
|
|
75
|
+
# whole fleet shares, so an image-provided devenv is authoritative here
|
|
76
|
+
# whatever commit it is — and a wrong one is never REPLACED, because
|
|
77
|
+
# `nix profile remove --all` on a host would take the fleet's other roots with
|
|
78
|
+
# it. SMOO_HOST_RUNNER comes from setup-devenv's runner-kind detection.
|
|
79
|
+
#
|
|
80
|
+
# With none present these runners install from the FLOATING branch, not the
|
|
81
|
+
# locked rev, which is the one place this script deliberately accepts drift.
|
|
82
|
+
# The pin exists to make the ephemeral runners' cached devenv reproducible;
|
|
83
|
+
# a host installs into a long-lived shared profile whose blast radius is the
|
|
84
|
+
# whole fleet, and pinning it down to an older CLI than the fleet had been
|
|
85
|
+
# running broke it: run 34374650577 segfaulted the Rust linker
|
|
86
|
+
# (devenv-rust-linker, clang exit 139) building cowshed-cli tests, where run
|
|
87
|
+
# 34371205556 on the same commit range was green with floating 2a399e9.
|
|
88
|
+
# 190959a is TWO COMMITS OLDER than what these hosts had been getting, so the
|
|
89
|
+
# pin was moving them backwards. A host's toolchain moves when its image or
|
|
90
|
+
# the lock moves, not when a repository decides to hold it back.
|
|
91
|
+
if [ "${SMOO_HOST_RUNNER:-false}" = true ]; then
|
|
92
|
+
if command -v devenv >/dev/null 2>&1; then
|
|
93
|
+
echo "using host devenv: $(command -v devenv) ($(devenv version))"
|
|
94
|
+
else
|
|
95
|
+
flake="${DEVENV_FLAKE:-github:cachix/devenv}"
|
|
96
|
+
echo "host runner has no devenv; nix profile add ${flake}"
|
|
97
|
+
nix profile add --accept-flake-config "$flake"
|
|
98
|
+
fi
|
|
99
|
+
devenv_path_and_caches
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
rev="$(devenv_locked_rev)"
|
|
104
|
+
# The store cache carries content only, never the Nix profiles, so the normal
|
|
105
|
+
# ephemeral case is that nothing is installed yet and this profile-adds the
|
|
106
|
+
# locked rev against an already-warm /nix — an evaluation and a link, not a
|
|
107
|
+
# download. Anything that does turn up on PATH is still checked rather than
|
|
108
|
+
# trusted, because "a devenv exists" was never the question.
|
|
65
109
|
if command -v devenv >/dev/null 2>&1; then
|
|
66
|
-
|
|
67
|
-
elif [ -x "$
|
|
68
|
-
|
|
110
|
+
found="$(command -v devenv)"
|
|
111
|
+
elif [ -x "$HOME/.nix-profile/bin/devenv" ]; then
|
|
112
|
+
found="$HOME/.nix-profile/bin/devenv"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if [ -n "$found" ] && devenv_matches_lock "$found" "$rev"; then
|
|
116
|
+
echo "using locked devenv: $found ($("$found" version))"
|
|
69
117
|
else
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
flake="${DEVENV_FLAKE:-github:cachix/devenv/$rev}"
|
|
119
|
+
if [ -n "$found" ]; then
|
|
120
|
+
# Replacing needs the old entry gone first: `nix profile add` would
|
|
121
|
+
# otherwise refuse on a bin/devenv collision at equal priority. --all is
|
|
122
|
+
# exact rather than blunt, because devenv is the only thing this script
|
|
123
|
+
# ever profile-adds, so a wrong devenv means a wrong profile.
|
|
124
|
+
echo "replacing devenv $("$found" version 2>/dev/null) — lock names ${rev:0:7}"
|
|
125
|
+
nix profile remove --all
|
|
126
|
+
fi
|
|
72
127
|
echo "nix profile add ${flake}"
|
|
73
128
|
nix profile add --accept-flake-config "$flake"
|
|
74
129
|
fi
|
|
130
|
+
devenv_path_and_caches
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
devenv_path_and_caches() {
|
|
75
134
|
if [ -d "$HOME/.nix-profile/bin" ]; then
|
|
76
135
|
echo "$HOME/.nix-profile/bin" >> "${GITHUB_PATH:-/dev/null}"
|
|
77
136
|
fi
|
|
@@ -119,9 +119,29 @@ runs:
|
|
|
119
119
|
keep-env-derivations = true
|
|
120
120
|
|
|
121
121
|
# The store itself, restored as files: /nix/store plus the Nix database,
|
|
122
|
-
# merged into the fresh install.
|
|
123
|
-
#
|
|
124
|
-
#
|
|
122
|
+
# merged into the fresh install. The post phase collects garbage down to the
|
|
123
|
+
# live closure and saves when the primary key missed.
|
|
124
|
+
#
|
|
125
|
+
# CONTENT ONLY — never the Nix profiles. `~/.nix-profile` and
|
|
126
|
+
# ~/.local/state/nix/profiles are mutable places whose whole job is to be
|
|
127
|
+
# gcroots, and restoring a previous run's copies over a freshly installed
|
|
128
|
+
# single-user Nix de-roots the Nix binary that is currently running. The post
|
|
129
|
+
# phase then garbage-collects it and cache-nix-action's own `nix --version`
|
|
130
|
+
# probe fails, at which point it skips the upload and still reports the step
|
|
131
|
+
# as a success — so the store cache silently stops being written and every
|
|
132
|
+
# later run pays a cold shell. Measured exactly that way: run 34371205556
|
|
133
|
+
# logged `nix (Nix) 2.34.7`, freed 2.7 GiB, then `nix: command not found`,
|
|
134
|
+
# and no `Cache saved` line for this key ever appeared.
|
|
135
|
+
#
|
|
136
|
+
# It only surfaced now because it needs a restore-prefix fallback rather than
|
|
137
|
+
# a cold store or an exact hit: a matching key restores profiles consistent
|
|
138
|
+
# with the store, while the diet rotated the key and pulled the previous
|
|
139
|
+
# store's profiles onto a different Nix.
|
|
140
|
+
#
|
|
141
|
+
# Dropping them costs nothing and buys determinism. devenv's closure still
|
|
142
|
+
# arrives inside /nix, so install-devenv's `nix profile add` of the locked
|
|
143
|
+
# rev is a link rather than a download, and the devenv in play is the one
|
|
144
|
+
# devenv.lock names instead of whichever run last populated the prefix.
|
|
125
145
|
- name: 🧊 Cache Nix store
|
|
126
146
|
id: nix-cache
|
|
127
147
|
if: steps.runner-kind.outputs.host != 'true'
|
|
@@ -129,12 +149,10 @@ runs:
|
|
|
129
149
|
with:
|
|
130
150
|
paths: |
|
|
131
151
|
/nix
|
|
132
|
-
~/.nix-profile
|
|
133
|
-
~/.local/state/nix/profiles
|
|
134
152
|
~/.cache/nix
|
|
135
153
|
# prettier-ignore
|
|
136
|
-
primary-key: ${{ runner.os }}-${{ runner.arch }}-nix-
|
|
137
|
-
restore-prefixes-first-match: ${{ runner.os }}-${{ runner.arch }}-nix-
|
|
154
|
+
primary-key: ${{ runner.os }}-${{ runner.arch }}-nix-v5-${{ hashFiles('tooling/direnv/devenv.yaml', 'tooling/direnv/devenv.nix', 'tooling/direnv/devenv.lock') }}
|
|
155
|
+
restore-prefixes-first-match: ${{ runner.os }}-${{ runner.arch }}-nix-v5-
|
|
138
156
|
gc-max-store-size: 1G
|
|
139
157
|
|
|
140
158
|
- name: ⚡ Enable Cachix
|
|
@@ -143,9 +161,17 @@ runs:
|
|
|
143
161
|
with:
|
|
144
162
|
name: devenv
|
|
145
163
|
|
|
164
|
+
# A host runner's devenv belongs to its image, so install-devenv accepts
|
|
165
|
+
# whatever is on PATH there. On an ephemeral runner the devenv came out of a
|
|
166
|
+
# cache this workflow wrote, so it is held to devenv.lock's rev: the store
|
|
167
|
+
# cache restores ~/.nix-profile wholesale and a prefix match hands the job
|
|
168
|
+
# the CLI of whichever run last populated the key, which outlived a lock bump
|
|
169
|
+
# until the check existed.
|
|
146
170
|
- name: 🧰 Install devenv
|
|
147
171
|
shell: bash
|
|
148
172
|
working-directory: tooling/direnv
|
|
173
|
+
env:
|
|
174
|
+
SMOO_HOST_RUNNER: ${{ steps.runner-kind.outputs.host }}
|
|
149
175
|
run: ./github-actions-bootstrap.sh install-devenv
|
|
150
176
|
|
|
151
177
|
# node_modules + ttsc plugin binaries: GH Actions cache on ephemeral runners.
|
package/package.json
CHANGED
|
@@ -140,7 +140,7 @@ interface InstallRun {
|
|
|
140
140
|
nixCalls: string;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function runInstallDevenv(lock: string, options: {
|
|
143
|
+
function runInstallDevenv(lock: string, options: { devenvVersion?: string; hostRunner?: boolean } = {}): InstallRun {
|
|
144
144
|
const dir = mkdtempSync(join(tmpdir(), 'gab-install-'));
|
|
145
145
|
try {
|
|
146
146
|
const direnv = join(dir, 'root', 'tooling', 'direnv');
|
|
@@ -154,20 +154,22 @@ function runInstallDevenv(lock: string, options: { devenvOnPath?: boolean } = {}
|
|
|
154
154
|
const stub = join(bin, 'nix');
|
|
155
155
|
writeFileSync(stub, NIX_STUB);
|
|
156
156
|
chmodSync(stub, 0o755);
|
|
157
|
-
if (options.
|
|
157
|
+
if (options.devenvVersion !== undefined) {
|
|
158
|
+
// Shaped like the real thing: `devenv <semver>+<short rev> (<system>)`.
|
|
158
159
|
const devenv = join(bin, 'devenv');
|
|
159
|
-
writeFileSync(devenv,
|
|
160
|
+
writeFileSync(devenv, `#!/usr/bin/env bash\necho "${options.devenvVersion}"\n`);
|
|
160
161
|
chmodSync(devenv, 0o755);
|
|
161
162
|
}
|
|
162
163
|
const r = spawnSync('bash', [join(direnv, 'github-actions-bootstrap.sh'), 'install-devenv'], {
|
|
163
164
|
encoding: 'utf8',
|
|
164
|
-
// A bare PATH on purpose: an ambient devenv would
|
|
165
|
-
//
|
|
165
|
+
// A bare PATH on purpose: an ambient devenv would decide these cases
|
|
166
|
+
// instead of the stub.
|
|
166
167
|
env: {
|
|
167
168
|
PATH: `${bin}:${dirname(REAL_NIX)}:/usr/bin:/bin`,
|
|
168
169
|
HOME: join(dir, 'home'),
|
|
169
170
|
NIX_CALLS: nixCalls,
|
|
170
171
|
REAL_NIX,
|
|
172
|
+
...(options.hostRunner ? { SMOO_HOST_RUNNER: 'true' } : {}),
|
|
171
173
|
},
|
|
172
174
|
});
|
|
173
175
|
return {
|
|
@@ -181,10 +183,12 @@ function runInstallDevenv(lock: string, options: { devenvOnPath?: boolean } = {}
|
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
const REV = 'f'.repeat(40);
|
|
187
|
+
|
|
184
188
|
const LOCK_WITH_REV = JSON.stringify({
|
|
185
189
|
nodes: {
|
|
186
190
|
devenv: {
|
|
187
|
-
locked: { dir: 'src/modules', owner: 'cachix', repo: 'devenv', rev:
|
|
191
|
+
locked: { dir: 'src/modules', owner: 'cachix', repo: 'devenv', rev: REV, type: 'github' },
|
|
188
192
|
original: { dir: 'src/modules', owner: 'cachix', repo: 'devenv', type: 'github' },
|
|
189
193
|
},
|
|
190
194
|
},
|
|
@@ -202,10 +206,10 @@ describe('github-actions-bootstrap install-devenv', () => {
|
|
|
202
206
|
// The rev comes out of the lock, so the CLI is the commit whose modules the
|
|
203
207
|
// shell is locked to. An unpinned `github:cachix/devenv` would install
|
|
204
208
|
// whatever HEAD is on the day a cold runner misses the store cache.
|
|
205
|
-
expect(run.nixCalls).toContain(`nix profile add --accept-flake-config github:cachix/devenv/${
|
|
209
|
+
expect(run.nixCalls).toContain(`nix profile add --accept-flake-config github:cachix/devenv/${REV}`);
|
|
206
210
|
expect(run.nixCalls).not.toContain('github:cachix/devenv\n');
|
|
207
211
|
// Announced, so a run's log names the version it installed.
|
|
208
|
-
expect(run.stdout).toContain(`github:cachix/devenv/${
|
|
212
|
+
expect(run.stdout).toContain(`github:cachix/devenv/${REV}`);
|
|
209
213
|
});
|
|
210
214
|
|
|
211
215
|
it('refuses to install anything when the lock names no devenv rev', () => {
|
|
@@ -216,10 +220,60 @@ describe('github-actions-bootstrap install-devenv', () => {
|
|
|
216
220
|
expect(run.nixCalls).not.toContain('profile add');
|
|
217
221
|
});
|
|
218
222
|
|
|
219
|
-
it('keeps
|
|
220
|
-
const run = runInstallDevenv(LOCK_WITH_REV, {
|
|
223
|
+
it('keeps a restored devenv built from the locked commit', () => {
|
|
224
|
+
const run = runInstallDevenv(LOCK_WITH_REV, { devenvVersion: 'devenv 2.3.1+fffffff (aarch64-darwin)' });
|
|
225
|
+
expect(run.status).toBe(0);
|
|
226
|
+
expect(run.stdout).toContain('using locked devenv');
|
|
227
|
+
// The warm path must cost nothing: no eval of the flake, no profile writes.
|
|
228
|
+
expect(run.nixCalls).toBe('');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('replaces a restored devenv that is a different commit than the lock', () => {
|
|
232
|
+
// The real drift: the store cache restores ~/.nix-profile wholesale, so a
|
|
233
|
+
// rotated key hands the job the CLI of whichever run last populated the
|
|
234
|
+
// prefix. Observed in run 34369909403 as devenv 2.3.1+2a399e9 against a
|
|
235
|
+
// lock naming 190959a.
|
|
236
|
+
const run = runInstallDevenv(LOCK_WITH_REV, { devenvVersion: 'devenv 2.3.1+2a399e9 (aarch64-darwin)' });
|
|
237
|
+
if (run.status !== 0) {
|
|
238
|
+
printCommandOutput(run.stdout, run.stderr);
|
|
239
|
+
}
|
|
240
|
+
expect(run.status).toBe(0);
|
|
241
|
+
// Removal first: `nix profile add` collides on bin/devenv at equal priority.
|
|
242
|
+
const removeAt = run.nixCalls.indexOf('profile remove --all');
|
|
243
|
+
const addAt = run.nixCalls.indexOf(`profile add --accept-flake-config github:cachix/devenv/${REV}`);
|
|
244
|
+
expect(removeAt).toBeGreaterThanOrEqual(0);
|
|
245
|
+
expect(addAt).toBeGreaterThan(removeAt);
|
|
246
|
+
// Said out loud, with both commits, so a log explains the replacement.
|
|
247
|
+
expect(run.stdout).toContain('replacing devenv');
|
|
248
|
+
expect(run.stdout).toContain('2a399e9');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('leaves a host runner its own devenv, whatever commit it is', () => {
|
|
252
|
+
// Host runners own their Nix profile; a repository rewriting it would take
|
|
253
|
+
// the whole fleet's shared store with it.
|
|
254
|
+
const run = runInstallDevenv(LOCK_WITH_REV, {
|
|
255
|
+
devenvVersion: 'devenv 2.3.1+2a399e9 (x86_64-linux)',
|
|
256
|
+
hostRunner: true,
|
|
257
|
+
});
|
|
221
258
|
expect(run.status).toBe(0);
|
|
222
|
-
expect(run.stdout).toContain('using
|
|
259
|
+
expect(run.stdout).toContain('using host devenv');
|
|
223
260
|
expect(run.nixCalls).toBe('');
|
|
224
261
|
});
|
|
262
|
+
|
|
263
|
+
it('installs from the floating branch on a host runner with no devenv', () => {
|
|
264
|
+
// The one place drift is accepted deliberately. A host installs into a
|
|
265
|
+
// long-lived profile that roots the fleet's shared store, and holding it
|
|
266
|
+
// back to an older CLI than the fleet was running segfaulted the Rust
|
|
267
|
+
// linker in run 34374650577. Refusing to install at all was also wrong —
|
|
268
|
+
// that failed linux-release-candidate in run 34373420924.
|
|
269
|
+
const run = runInstallDevenv(LOCK_WITH_REV, { hostRunner: true });
|
|
270
|
+
if (run.status !== 0) {
|
|
271
|
+
printCommandOutput(run.stdout, run.stderr);
|
|
272
|
+
}
|
|
273
|
+
expect(run.status).toBe(0);
|
|
274
|
+
expect(run.nixCalls).toContain('nix profile add --accept-flake-config github:cachix/devenv\n');
|
|
275
|
+
expect(run.nixCalls).not.toContain(REV);
|
|
276
|
+
// Never on a host: the profile roots a store the whole fleet shares.
|
|
277
|
+
expect(run.nixCalls).not.toContain('profile remove');
|
|
278
|
+
});
|
|
225
279
|
});
|