diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 140b0bc655..9d30554189 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,9 +101,8 @@ jobs: - name: Select pinned Rust shell: pwsh run: | - $pins = @(Get-ChildItem bin/.rust-*.pkg) - if ($pins.Count -ne 1) { throw "Expected exactly one repository Rust pin" } - $version = $pins[0].Name -replace '^\.rust-(.*)\.pkg$', '$1' + $version = (Select-String -Path rust-toolchain.toml -Pattern '^channel = "(.+)"$').Matches.Groups[1].Value + if (!$version) { throw "Expected a rust-toolchain.toml channel" } rustup toolchain install $version --profile minimal --component clippy if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } "RUSTUP_TOOLCHAIN=$version" >> $env:GITHUB_ENV diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88b204f995..b8e9aa14ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -305,12 +305,15 @@ jobs: id: pins shell: pwsh run: | - foreach ($tool in @('rust', 'node', 'pnpm')) { + foreach ($tool in @('node', 'pnpm')) { $pins = @(Get-ChildItem "bin/.$tool-*.pkg") if ($pins.Count -ne 1) { throw "Expected one $tool pin" } $version = $pins[0].Name -replace "^\.$tool-(.*)\.pkg$", '$1' "$tool=$version" >> $env:GITHUB_OUTPUT } + $rust = (Select-String -Path rust-toolchain.toml -Pattern '^channel = "(.+)"$').Matches.Groups[1].Value + if (!$rust) { throw "Expected a rust-toolchain.toml channel" } + "rust=$rust" >> $env:GITHUB_OUTPUT - name: Select pinned Rust shell: pwsh env: diff --git a/AGENTS.md b/AGENTS.md index 25640c2a83..14f0e9351e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,8 @@ scripts/bootstrap-worktree.sh /absolute/path/to/source/checkout Do not start development before bootstrap completes. The script copies the git-ignored `.env.local` without overwriting an existing target, then uses that -worktree's Hermit proxy to run `bin/pnpm install --frozen-lockfile`. Do not copy +worktree's Hermit proxies to run `bin/rustup toolchain install` and +`bin/pnpm install --frozen-lockfile`. Do not copy other ignored paths: Keychain credentials and pnpm's package cache are machine-shared, while dependencies and build output are regenerated. Git hooks need no per-worktree setup; see [Git hooks](docs/contributing.md#git-hooks). diff --git a/bin/.rust-1.98.1.pkg b/bin/.rustup-1.29.2.pkg similarity index 100% rename from bin/.rust-1.98.1.pkg rename to bin/.rustup-1.29.2.pkg diff --git a/bin/cargo b/bin/cargo index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo +++ b/bin/cargo @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-clippy b/bin/cargo-clippy index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo-clippy +++ b/bin/cargo-clippy @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-fmt b/bin/cargo-fmt index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo-fmt +++ b/bin/cargo-fmt @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-miri b/bin/cargo-miri new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/cargo-miri @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/clippy-driver b/bin/clippy-driver index 6219148844..f4fc0d11c0 120000 --- a/bin/clippy-driver +++ b/bin/clippy-driver @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rls b/bin/rls new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/rls @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-analyzer b/bin/rust-analyzer index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-analyzer +++ b/bin/rust-analyzer @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-gdb b/bin/rust-gdb index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-gdb +++ b/bin/rust-gdb @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-gdbgui b/bin/rust-gdbgui index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-gdbgui +++ b/bin/rust-gdbgui @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-lldb b/bin/rust-lldb index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-lldb +++ b/bin/rust-lldb @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustc b/bin/rustc index 6219148844..f4fc0d11c0 120000 --- a/bin/rustc +++ b/bin/rustc @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustdoc b/bin/rustdoc index 6219148844..f4fc0d11c0 120000 --- a/bin/rustdoc +++ b/bin/rustdoc @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustfmt b/bin/rustfmt index 6219148844..f4fc0d11c0 120000 --- a/bin/rustfmt +++ b/bin/rustfmt @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustup b/bin/rustup new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/rustup @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index 41a665fb67..f5fdfeac8c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,8 +1,9 @@ # Contribution workflow The repository pins just 1.58.0, Node.js 24.18.0, pnpm 11.8.0, Lefthook 2.2.2, -and Rust 1.98.1 -(including Cargo, rustfmt, and Clippy) with [Hermit](https://cashapp.github.io/hermit/). +and rustup with [Hermit](https://cashapp.github.io/hermit/). `rust-toolchain.toml` +pins Rust 1.98.1 (including Cargo, rustfmt, and Clippy), which rustup installs +under the machine-shared `~/.rustup`. No global tool installation is required: `bin/hermit` bootstraps Hermit and tools are downloaded on first use. Desktop development still requires the [Tauri platform prerequisites](https://v2.tauri.app/start/prerequisites/). @@ -11,9 +12,18 @@ From the repository root: ```sh source bin/activate-hermit # bash/zsh; fish: source bin/activate-hermit.fish +rustup toolchain install just --list ``` +Run `rustup toolchain install` after cloning and after `rust-toolchain.toml` +changes, before an editor or parallel commands start the Rust proxies. rustup +does not serialize installs, so concurrent first use can leave the toolchain +missing a component's binaries while rustup reports it installed (for example, +`cargo clippy` fails although `rustup component add clippy` says it is up to +date). To repair it, reinstall the toolchain: +`rustup toolchain uninstall 1.98.1 && rustup toolchain install`. + Without activation, use `bin/just`, `bin/pnpm`, or `bin/cargo` from the root; these proxies supply the pinned environment to child commands too. For example, `bin/just web` needs no shell setup. With activation, commands also work from @@ -171,8 +181,9 @@ scripts/bootstrap-worktree.sh /absolute/path/to/source/checkout ``` The idempotent script copies the source checkout's git-ignored `.env.local` -without overwriting an existing target, then uses the new worktree's Hermit proxy -to run `bin/pnpm install --frozen-lockfile`. It rejects checkouts from another +without overwriting an existing target, then uses the new worktree's Hermit +proxies to run `bin/rustup toolchain install` and +`bin/pnpm install --frozen-lockfile`. It rejects checkouts from another repository. Keychain credentials and pnpm's package cache remain machine-shared; do not copy private keys, `node_modules`, build output, `.npmrc`, or other ignored files. Git hooks need no per-worktree step; see [Git hooks](#git-hooks). @@ -240,7 +251,7 @@ Buzz library are described in [Agents](agents.md#community-inventory). ## Running the app -Hermit pins just, Node.js 24, pnpm 11.8.0, and Rust in `bin/`; no global tool +Hermit pins just, Node.js 24, pnpm 11.8.0, and rustup in `bin/`; no global tool installation is needed. From the repository root, activate the pinned environment: ```sh diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..86507a6ea7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.98.1" +profile = "minimal" +components = ["clippy", "rustfmt", "rust-analyzer"] diff --git a/scripts/bootstrap-worktree.sh b/scripts/bootstrap-worktree.sh index 18fe59340f..08b8296883 100755 --- a/scripts/bootstrap-worktree.sh +++ b/scripts/bootstrap-worktree.sh @@ -29,4 +29,8 @@ else echo "No .env.local to copy from $source" fi +# Install the pinned toolchain before editors or parallel commands use the Rust +# proxies: concurrent first-use installs into the shared ~/.rustup can corrupt it. +(cd "$target" && bin/rustup toolchain install) + "$target/bin/pnpm" install --dir "$target" --frozen-lockfile diff --git a/tests/integration/hooks.test.mjs b/tests/integration/hooks.test.mjs index 01b073d842..50d11292be 100644 --- a/tests/integration/hooks.test.mjs +++ b/tests/integration/hooks.test.mjs @@ -71,6 +71,8 @@ function fixture(t, { install = true } = {}) { "package.json", "lefthook.yml", "justfile", + // rustup selects the pinned toolchain from the working directory. + "rust-toolchain.toml", "scripts", ...biomePlugins.map((plugin) => path.normalize(plugin)), ];