From f37ddcb8d70bd98f9dbe0460bea5eb745471f2ef Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 20:42:07 +0100 Subject: [PATCH 1/8] Align Dependabot coverage and add the dev-fast fragment Bring `.github/dependabot.yml` to the estate baseline: one update stanza per package ecosystem the repository uses, each labelled `dependencies` plus its channel label, with GitHub Actions updates batched into a single pull request via a wildcard group. Add the opt-in dev-fast build fragment at `tools/dev-fast/config.toml` (Cranelift codegen for the dev profile and the mold linker on Linux) with `dev-build` and `dev-test` Make targets that pass it explicitly via `--config`, and signpost the workflow in `AGENTS.md`. Release, coverage, and verification builds are unaffected: the fragment is never auto-discovered. --- .github/dependabot.yml | 7 +++++++ AGENTS.md | 9 +++++++++ Makefile | 11 +++++++++++ tools/dev-fast/config.toml | 30 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 tools/dev-fast/config.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9b3a19c..e57b900 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,3 +28,10 @@ updates: cooldown: default-days: 7 semver-major-days: 14 + - package-ecosystem: rust-toolchain + directory: / + labels: + - dependencies + - rust-toolchain + schedule: + interval: weekly diff --git a/AGENTS.md b/AGENTS.md index fdd2710..f5e5bc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -378,3 +378,12 @@ The following tooling is available in this environment: These practices help maintain a high-quality codebase and facilitate collaboration. + +## Fast development builds + +`make dev-build` and `make dev-test` compile with the opt-in Cranelift +backend and the mold linker configured in `tools/dev-fast/config.toml`. +They require a nightly toolchain and, on Linux, a `mold` binary on the +`PATH`. The fragment is passed explicitly with `--config`, so release, +coverage, and verification builds are unaffected; never copy its contents +into `.cargo/config.toml`, which Cargo applies to every build. diff --git a/Makefile b/Makefile index 31454e6..aa71e9c 100644 --- a/Makefile +++ b/Makefile @@ -126,3 +126,14 @@ rust-audit: ## Audit the Rust workspace for known vulnerabilities help: ## Show available targets @grep -E '^[a-zA-Z_-]+:.*?##' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS=":"; printf "Available targets:\n"} {printf " %-20s %s\n", $$1, $$2}' + +# Opt-in accelerated debug builds (Cranelift + mold); requires a nightly +# toolchain. See AGENTS.md and tools/dev-fast/config.toml. +DEV_FAST_CONFIG ?= tools/dev-fast/config.toml + +.PHONY: dev-build dev-test +dev-build: ## Build debug binaries with Cranelift and mold + cargo --config "$(DEV_FAST_CONFIG)" build + +dev-test: ## Run tests with Cranelift and mold + cargo --config "$(DEV_FAST_CONFIG)" test diff --git a/tools/dev-fast/config.toml b/tools/dev-fast/config.toml new file mode 100644 index 0000000..75bfd76 --- /dev/null +++ b/tools/dev-fast/config.toml @@ -0,0 +1,30 @@ +# Canonical opt-in Cargo configuration fragment for accelerated local debug +# builds. +# +# This file is deliberately kept out of `.cargo/config.toml`. Cargo +# auto-discovers that path, so anything placed there applies to release +# packaging, coverage, and verification builds, which must keep the +# supported LLVM backend and platform linker. Copy this fragment to +# `tools/dev-fast/config.toml` and pass it explicitly with +# `cargo --config tools/dev-fast/config.toml ...` from `make dev-build` and +# `make dev-test`. +# +# Repositories that set repository-wide `rustflags` in `.cargo/config.toml` +# must restate them in the target table below: Cargo picks a single +# rustflags source rather than merging them, and a `[target.*]` table +# outranks the `[build]` table. + +[unstable] +codegen-backend = true + +# Cranelift trades runtime performance for compile speed, so it applies to +# the dev profile only. The `make dev-*` targets never build release +# artefacts. +[profile.dev] +codegen-backend = "cranelift" + +# mold ships for Linux only, so the flag is gated behind a target `cfg`. On +# macOS and Windows the table simply does not apply and the platform default +# linker is used. +[target.'cfg(target_os = "linux")'] +rustflags = ["-Clink-arg=-fuse-ld=mold"] From 061cdc613400ea7e0289ba659d4cb077f7f5ab63 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 22:16:13 +0100 Subject: [PATCH 2/8] Accept mold as the linker's name in the spelling config The dev-fast rollout writes "mold" (the linker) into AGENTS.md and the Makefile, and the en-GB-oxendict spelling gate reads it as a misspelling of "mould", failing CI. Accept the word via the estate's correction-to-itself pattern in `typos.local.toml`, and mirror it in the generated dictionary so local runs agree with the regenerated configuration. --- typos.local.toml | 3 +++ typos.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/typos.local.toml b/typos.local.toml index 89d67e3..47a7b9d 100644 --- a/typos.local.toml +++ b/typos.local.toml @@ -9,6 +9,9 @@ stems = [] accepted = [] [words.corrections] +# "mold" names the linker (https://github.com/rui314/mold); it is not a +# misspelling of "mould" in that context. +mold = "mold" [patterns] # Preserve complete formal names while allowing Markdown line wrapping. diff --git a/typos.toml b/typos.toml index 92a5a5b..078ebff 100644 --- a/typos.toml +++ b/typos.toml @@ -1482,6 +1482,7 @@ extend-ignore-re = [ "modularizers" = "modularizers" "modularizes" = "modularizes" "modularizing" = "modularizing" +"mold" = "mold" "monetisable" = "monetizable" "monetisably" = "monetizably" "monetisation" = "monetization" From cc6c2f330db1055e7aa2097e3975df4d7c65bfd2 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 23:35:16 +0100 Subject: [PATCH 3/8] Scope the mold spelling exception to linker contexts A blanket `mold = "mold"` correction disables detection of genuine "mould" misspellings everywhere in the repository. Replace it with pattern-scoped exemptions covering only the linker contexts the dev-fast rollout introduced (`-fuse-ld=mold`, "mold linker", "Cranelift + mold", "Cranelift and mold", and backticked `mold`), and mirror them in the generated dictionary. Text such as "the bread had mold growing on it" is flagged again. --- typos.local.toml | 10 +++++++--- typos.toml | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/typos.local.toml b/typos.local.toml index 47a7b9d..6eb34fc 100644 --- a/typos.local.toml +++ b/typos.local.toml @@ -9,13 +9,17 @@ stems = [] accepted = [] [words.corrections] -# "mold" names the linker (https://github.com/rui314/mold); it is not a -# misspelling of "mould" in that context. -mold = "mold" [patterns] # Preserve complete formal names while allowing Markdown line wrapping. ignore = [ + # "mold" names the linker (https://github.com/rui314/mold) in these + # contexts only; the mould correction stays active elsewhere. + "-fuse-ld=mold", + "mold linker", + "Cranelift \\+ mold", + "Cranelift and mold", + "`mold`", "Azure Architecture\\s+Center\\s+\\|\\s+Microsoft Learn", "GitHub\\s+Flavored\\s+Markdown", ] diff --git a/typos.toml b/typos.toml index 078ebff..303ab37 100644 --- a/typos.toml +++ b/typos.toml @@ -30,6 +30,11 @@ extend-exclude = [ [default] locale = "en-gb" extend-ignore-re = [ + "-fuse-ld=mold", + "mold linker", + "Cranelift \\+ mold", + "Cranelift and mold", + "`mold`", "(?s)```.*?```", "Azure Architecture\\s+Center\\s+\\|\\s+Microsoft Learn", "GitHub\\s+Flavored\\s+Markdown", @@ -1482,7 +1487,6 @@ extend-ignore-re = [ "modularizers" = "modularizers" "modularizes" = "modularizes" "modularizing" = "modularizing" -"mold" = "mold" "monetisable" = "monetizable" "monetisably" = "monetizably" "monetisation" = "monetization" From ec0eb8ff17649f95d2e1e9da5c4132e3022f00e7 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 23:38:59 +0100 Subject: [PATCH 4/8] Refresh the dev-fast fragment's comments from canon Estate review flagged two defects in the fragment's deployed comments: a stale "copy this fragment" instruction that reads as nonsense once the file is in place, and a mis-statement of Cargo's rustflags semantics (Cargo joins the entries of every matching `[target.*]` table; only the joined result takes precedence over `[build].rustflags` rather than merging). Both are corrected in the canonical source; take its bytes verbatim. No configuration key changes. --- tools/dev-fast/config.toml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tools/dev-fast/config.toml b/tools/dev-fast/config.toml index 75bfd76..01e7f89 100644 --- a/tools/dev-fast/config.toml +++ b/tools/dev-fast/config.toml @@ -1,18 +1,19 @@ # Canonical opt-in Cargo configuration fragment for accelerated local debug # builds. # -# This file is deliberately kept out of `.cargo/config.toml`. Cargo -# auto-discovers that path, so anything placed there applies to release -# packaging, coverage, and verification builds, which must keep the -# supported LLVM backend and platform linker. Copy this fragment to -# `tools/dev-fast/config.toml` and pass it explicitly with -# `cargo --config tools/dev-fast/config.toml ...` from `make dev-build` and -# `make dev-test`. +# This configuration is deliberately kept out of `.cargo/config.toml`. +# Cargo auto-discovers that path, so anything placed there applies to +# release packaging, coverage, and verification builds, which must keep +# the supported LLVM backend and platform linker. In a consuming +# repository this fragment lives at `tools/dev-fast/config.toml` and is +# passed explicitly with `cargo --config tools/dev-fast/config.toml ...` +# from `make dev-build` and `make dev-test`. # # Repositories that set repository-wide `rustflags` in `.cargo/config.toml` -# must restate them in the target table below: Cargo picks a single -# rustflags source rather than merging them, and a `[target.*]` table -# outranks the `[build]` table. +# must restate them in the target table below. Cargo joins the rustflags of +# every matching `[target.*]` entry (target-triple and `cfg` tables alike), +# but the joined target rustflags take precedence over `[build].rustflags` +# rather than merging with it. [unstable] codegen-backend = true From 84ce11b12ea3cd10b9071319047bd39dfb35bd67 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 23:47:01 +0100 Subject: [PATCH 5/8] Route dev-fast targets through the injectable CARGO variable `dev-build` and `dev-test` hard-coded `cargo` even though the Makefile already exposes `CARGO ?= cargo` for every other target. Substitute `$(CARGO)` so callers can point the dev-fast targets at an alternate Cargo binary, matching the rest of the Makefile's convention. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index aa71e9c..63ebae7 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,7 @@ DEV_FAST_CONFIG ?= tools/dev-fast/config.toml .PHONY: dev-build dev-test dev-build: ## Build debug binaries with Cranelift and mold - cargo --config "$(DEV_FAST_CONFIG)" build + $(CARGO) --config "$(DEV_FAST_CONFIG)" build dev-test: ## Run tests with Cranelift and mold - cargo --config "$(DEV_FAST_CONFIG)" test + $(CARGO) --config "$(DEV_FAST_CONFIG)" test From f0c40eef7adc915adc9d843880e5fa4e3b0cc5df Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 23:47:07 +0100 Subject: [PATCH 6/8] Add a contract test for the dev-fast Make targets Prove that `dev-build` and `dev-test` honour the injected `CARGO` variable and pass the dev-fast fragment via the explicit `--config` mechanism, by parsing `make --dry-run ... CARGO=probe-cargo` output and asserting `probe-cargo`, `--config`, and a dev-fast reference appear in that order. Also assert `tools/dev-fast/config.toml` exists, since both targets depend on it. Parsing dry-run output keeps the test fast and avoids requiring a nightly toolchain or the mold linker just to validate wiring. Add `rstest` and `camino` as dev-dependencies: `rstest` parametrizes the dev-build/dev-test cases, and `camino` builds the UTF-8 path used to check the configuration fragment exists. --- Cargo.lock | 306 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + tests/dev_fast_contract.rs | 77 ++++++++++ 3 files changed, 387 insertions(+) create mode 100644 tests/dev_fast_contract.rs diff --git a/Cargo.lock b/Cargo.lock index 7b6e2e6..d62705f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,312 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.119", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "statelet" version = "0.1.0" +dependencies = [ + "camino", + "rstest", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 9d521dd..7005a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,3 +79,7 @@ missing_docs = "deny" [lints.rustdoc] missing_crate_level_docs = "deny" + +[dev-dependencies] +camino = "1.2.5" +rstest = "0.26.1" diff --git a/tests/dev_fast_contract.rs b/tests/dev_fast_contract.rs new file mode 100644 index 0000000..6b9a441 --- /dev/null +++ b/tests/dev_fast_contract.rs @@ -0,0 +1,77 @@ +//! Contract tests for the opt-in dev-fast Make targets. +//! +//! `dev-build` and `dev-test` must honour the injectable `CARGO` variable +//! and pass the dev-fast fragment to Cargo explicitly with `--config`, +//! rather than relying on Cargo auto-discovering `.cargo/config.toml`. +//! These tests parse `make --dry-run` output rather than running a real +//! build, so they stay fast and do not require a nightly toolchain or the +//! mold linker. Invoking `make` is process spawning, which Whitaker's +//! capability-based filesystem rules do not constrain; only file access +//! goes through `camino`. + +use std::{io, process::Command}; + +use camino::{Utf8Path, Utf8PathBuf}; +use rstest::rstest; + +/// Returns the workspace root, derived from the manifest directory so the +/// test works regardless of the runner's current directory. +fn workspace_root() -> Utf8PathBuf { Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) } + +/// Runs `make --dry-run CARGO=probe-cargo` and returns its stdout. +fn dry_run(target: &str) -> Result { + let output = Command::new("make") + .current_dir(workspace_root()) + .args(["--dry-run", target, "CARGO=probe-cargo"]) + .output()?; + Ok(String::from_utf8_lossy(&output.stdout).into_owned()) +} + +/// Locates `probe-cargo`, `--config`, and the dev-fast fragment marker on a +/// recipe line, returning their byte offsets if all three are present. +fn substitution_offsets(line: &str) -> Option<(usize, usize, usize)> { + let cargo = line.find("probe-cargo")?; + let config = line.find("--config")?; + let dev_fast = line.find("dev-fast")?; + Some((cargo, config, dev_fast)) +} + +#[rstest] +#[case::dev_build("dev-build")] +#[case::dev_test("dev-test")] +fn dev_fast_target_substitutes_injected_cargo(#[case] target: &str) { + let stdout = dry_run(target).expect("make --dry-run should run"); + + let Some(recipe_line) = stdout.lines().find(|line| line.contains("probe-cargo")) else { + panic!("target {target} did not invoke the injected CARGO override:\n{stdout}"); + }; + + let Some((cargo_pos, config_pos, dev_fast_pos)) = substitution_offsets(recipe_line) else { + panic!( + "target {target} recipe line is missing probe-cargo, --config, or a dev-fast \ + reference:\n{recipe_line}" + ); + }; + + assert!( + cargo_pos < config_pos && config_pos < dev_fast_pos, + "target {target} emitted an out-of-order command:\n{recipe_line}" + ); +} + +#[test] +fn dev_fast_config_fragment_exists() { + let config_path: Utf8PathBuf = [ + workspace_root().as_str(), + "tools", + "dev-fast", + "config.toml", + ] + .iter() + .collect(); + + assert!( + Utf8Path::new(&config_path).exists(), + "{config_path} must exist for the dev-fast targets to function" + ); +} From 726a655dad57beee96a259319863cf887ffdcbe8 Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Thu, 13 Aug 2026 23:47:13 +0100 Subject: [PATCH 7/8] Document the dev-fast build targets in the developer guide Add a "Fast development builds" section covering `dev-build` and `dev-test`, the `DEV_FAST_CONFIG` variable and the explicit `--config` mechanism it feeds (never auto-discovered), the nightly toolchain and, on Linux, mold linker requirements, and the rule that Cranelift configuration must never be copied into `.cargo/config.toml`, which Cargo applies to release, coverage, and verification builds alike. --- docs/developers-guide.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index c1026da..406d0a1 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -59,6 +59,31 @@ LLVM-compatible linker behaviour. Install `clang`, `lld`, `mold`, `python3`, and `cargo-audit` before running the full generated workflow locally on Linux. +## Fast development builds + +`make dev-build` and `make dev-test` offer an opt-in, faster iteration loop +for local debug work. `dev-build` compiles debug binaries and `dev-test` runs +the test suite; both use the Cranelift codegen backend and the mold linker +configured in `tools/dev-fast/config.toml`. + +The `DEV_FAST_CONFIG` variable names that fragment, defaulting to +`tools/dev-fast/config.toml`, and both targets pass it to Cargo explicitly +with `--config "$(DEV_FAST_CONFIG)"`. Cargo never auto-discovers this +fragment; it takes effect only when a target invokes it directly, so other +`make` targets are unaffected. + +Using the fragment requires a nightly toolchain, because the Cranelift +codegen backend is unstable. On Linux it also requires the mold linker on +`PATH`; the fragment gates the linker flag behind a `target_os = "linux"` +`cfg` table, so other platforms fall back to their default linker. + +Cranelift configuration must never be copied into `.cargo/config.toml`. +Cargo auto-discovers that file and applies it to every invocation, which +would silently degrade release, coverage, and verification builds to the +faster but less optimizing backend. Keep the fast-build configuration +isolated in `tools/dev-fast/config.toml` and reach it only through +`make dev-build` and `make dev-test`. + ## Spelling policy The tracked `typos.toml` is generated from the shared estate dictionary and the From 1cf22424f863b9226fc356889cadcf78f463cc9d Mon Sep 17 00:00:00 2001 From: Payton McIntosh Date: Fri, 14 Aug 2026 00:58:55 +0100 Subject: [PATCH 8/8] Regenerate typos.toml to clear spelling-config drift `make spelling-config` reported drift because the committed `typos.toml` no longer matched what the pinned `typos-config-builder` would produce from `typos.local.toml` plus the shared en-GB-oxendict base. The likely cause is that earlier mechanical commits hand-edited the mold-related entries (adding, then rescoping, them) directly in `typos.toml`, in a form the builder orders differently from its own output. Regenerate with the pinned builder in write mode (`typos-config-builder --repository .`, no `--check`). The resulting diff only reorders the five entries the previous mold-scoping commits touched (`-fuse-ld=mold`, `mold linker`, `Cranelift + mold`, `Cranelift and mold`, `` `mold` ``) alongside the pre-existing code-block, Azure, GitHub Flavored Markdown, rust-analyzer, and backtick-span (`` `[^`\n]+` ``) exemptions. No pattern was added or removed, and the backtick-span exemption was present both before and after, so no `typos.local.toml` change is needed. Verified the scoped mold exemption still holds in both directions ("the mold linker" passes, "the bread had mold growing" is still flagged) and that `make spelling` now passes end to end. --- typos.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typos.toml b/typos.toml index 303ab37..fb0c159 100644 --- a/typos.toml +++ b/typos.toml @@ -30,16 +30,16 @@ extend-exclude = [ [default] locale = "en-gb" extend-ignore-re = [ + "(?s)```.*?```", "-fuse-ld=mold", - "mold linker", + "Azure Architecture\\s+Center\\s+\\|\\s+Microsoft Learn", "Cranelift \\+ mold", "Cranelift and mold", - "`mold`", - "(?s)```.*?```", - "Azure Architecture\\s+Center\\s+\\|\\s+Microsoft Learn", "GitHub\\s+Flavored\\s+Markdown", "\\brust-analyzer\\b", "`[^`\\n]+`", + "`mold`", + "mold linker", ] [default.extend-words]