Skip to content

Commit b5dfe83

Browse files
authored
Reworked WIT syncing to avoid retriggered builds on repeated cargo make build (#3670)
* Reworked WIT syncing to avoid retriggered builds on repeated `cargo make build` * cleanups * harden dir-mirror flag parsing * test build tools as the "first step" before build, and not as a unit test * portable diff-wit * cleanups
1 parent 63e9843 commit b5dfe83

10 files changed

Lines changed: 650 additions & 63 deletions

File tree

.agents/skills/modifying-wit-interfaces/SKILL.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ Edit the relevant `.wit` file in the root `wit/` directory (e.g., `wit/host.wit`
8282
cargo make wit
8383
```
8484

85-
This removes all `wit/deps/` directories in sub-projects and re-copies the correct subset from the root.
85+
This mirrors the correct subset of the root `wit/deps/` into each sub-project, idempotently (it rewrites only files whose bytes changed, so unchanged files keep their mtime — avoiding needless rebuilds).
8686

8787
### Step 3: Verify synchronization
8888

8989
```shell
9090
cargo make check-wit
9191
```
9292

93-
This re-runs the sync and then `git diff --exit-code wit golem-common/wit` to ensure the committed WIT files match what the sync produces. If this fails in CI, you forgot to run `cargo make wit` (or you hand-edited a generated sub-project copy).
93+
This re-runs the sync and then `git status` over every per-crate `wit/deps` copy (golem-common, golem-cli, and all four SDKs) to ensure the committed WIT files match what the sync produces. CI runs this; if it fails, you forgot to run `cargo make wit` (or you hand-edited a generated sub-project copy).
9494

9595
### Step 4: Build and verify
9696

@@ -110,10 +110,11 @@ truth — there is no `deps.toml` and nothing is fetched.
110110

111111
### Step 2: Wire it into the sync tasks
112112

113-
Edit `Makefile.toml` so the new package is copied where it's needed. The
114-
`wit-sdks` task copies **all** root deps to every SDK automatically, but the
115-
`wit-golem-common` and `wit-golem-cli` tasks copy an explicit subset — add a
116-
`cp wit/deps/<package> <target>/wit/deps` line there if those crates need it.
113+
Edit `Makefile.toml` so the new package is mirrored where it's needed. The
114+
`wit-sdks` task mirrors **all** root deps to every SDK automatically, but the
115+
`wit-golem-common` and `wit-golem-cli` tasks mirror an explicit subset — add a
116+
`wit/deps/<package> <target>/wit/deps/<package>` source/target pair to the
117+
`dir-mirror` args there if those crates need it.
117118

118119
### Step 3: Sync and verify
119120

.github/workflows/ci.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ jobs:
4949
- uses: ./.github/actions/setup-rust
5050
with:
5151
cache-save-if: 'true'
52+
# Test the build tooling (dev-tools/) first so a broken tool fails fast
53+
- name: Test build tools (dev-tools)
54+
run: cargo make --profile ci dev-tools-tests
5255
- uses: taiki-e/install-action@v2
5356
with:
5457
tool: nextest
@@ -125,6 +128,8 @@ jobs:
125128
run: cargo make --profile ci check-docs-skills
126129
- name: Check configs are up to date
127130
run: cargo make --profile ci check-configs
131+
- name: Check WIT deps are synced
132+
run: cargo make --profile ci check-wit
128133
- name: Unit tests
129134
run: cargo make --profile ci unit-tests
130135
- uses: ./.github/actions/publish-test-report

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ members = [
2424

2525
exclude = [
2626
"test-components",
27-
"sdks/golem-rust"
27+
"sdks/golem-rust",
28+
"dev-tools"
2829
]
2930

3031
[workspace.metadata]

Makefile.toml

Lines changed: 117 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# List of top-level tasks intended for use:
22
#
33
# - `cargo make dev-flow` or just `cargo make`: runs a full development flow, including fixing format and clippy, building and running tests and generating OpenAPI specs
4-
# - `cargo make wit`: fetches the WIT dependencies based on wit/deps.toml
5-
# - `cargo make check-wit`: deletes then fetches the WIT dependencies based on wit/deps.toml, then checks if it's up-to-date
4+
# - `cargo make wit`: syncs the per-crate wit/deps copies from the canonical top-level wit/deps
5+
# - `cargo make check-wit`: re-syncs the per-crate wit/deps and fails if the committed copies are out of date
66
# - `cargo make build`: builds everything in debug mode
77
# - `cargo make build-release`: builds everything in release mode. customizable with PLATFORM_OVERRIDE env variable for docker builds
88
# - `cargo make check`: runs rustfmt and clippy checks without applying any fix
@@ -72,11 +72,17 @@ dependencies = [
7272
]
7373

7474
# WIT DEPENDENCIES
75+
#
76+
# Sync the per-crate wit/deps copies from the canonical top-level wit/deps with the
77+
# dir-mirror tool (dev-tools/dir-mirror) rather than cp: it preserves the mtime of
78+
# unchanged files, so cargo (which tracks these .wit files via rerun-if-changed from
79+
# bindgen!) doesn't rebuild the workspace on every run. The <source> <target> pairs
80+
# below replace the old cp/glob_cp lines one-for-one. golem-common/cli take a per-subdir
81+
# subset of the deps, so removing a dep means also removing its pair here.
7582
[tasks.wit]
76-
description = "Fetches the WIT dependencies based on wit/deps.toml"
83+
description = "Syncs the per-crate WIT dependency copies from the canonical wit/deps"
7784
run_task = [
7885
{ name = [
79-
"remove-wit-deps",
8086
"wit-golem-common",
8187
"wit-golem-cli",
8288
"wit-sdks",
@@ -85,67 +91,66 @@ run_task = [
8591

8692
[tasks.wit-golem-common]
8793
private = true
88-
script_runner = "@duckscript"
89-
script = """
90-
rm -r golem-common/wit/deps
91-
mkdir golem-common/wit/deps
92-
cp wit/deps/io golem-common/wit/deps
93-
cp wit/deps/clocks golem-common/wit/deps
94-
cp wit/deps/golem-1.x golem-common/wit/deps
95-
cp wit/deps/golem-core-v2 golem-common/wit/deps
96-
cp wit/deps/golem-agent golem-common/wit/deps
97-
cp wit/deps/golem-secrets golem-common/wit/deps
98-
cp wit/deps/golem-tool golem-common/wit/deps
99-
"""
94+
command = "cargo"
95+
args = [
96+
"run", "--quiet", "--manifest-path", "dev-tools/Cargo.toml", "-p", "dir-mirror", "--",
97+
"--src", "wit/deps/io", "--dst", "golem-common/wit/deps/io",
98+
"--src", "wit/deps/clocks", "--dst", "golem-common/wit/deps/clocks",
99+
"--src", "wit/deps/golem-1.x", "--dst", "golem-common/wit/deps/golem-1.x",
100+
"--src", "wit/deps/golem-core-v2", "--dst", "golem-common/wit/deps/golem-core-v2",
101+
"--src", "wit/deps/golem-agent", "--dst", "golem-common/wit/deps/golem-agent",
102+
"--src", "wit/deps/golem-secrets", "--dst", "golem-common/wit/deps/golem-secrets",
103+
"--src", "wit/deps/golem-tool", "--dst", "golem-common/wit/deps/golem-tool",
104+
]
100105

101106
[tasks.wit-sdks]
102107
private = true
103-
script_runner = "@duckscript"
104-
script = """
105-
rm -r sdks/rust/golem-rust/wit/deps
106-
rm -r sdks/ts/wit/deps
107-
rm -r sdks/scala/wit/deps
108-
rm -r sdks/moonbit/golem_sdk/wit/deps
109-
mkdir sdks/rust/golem-rust/wit/deps
110-
mkdir sdks/ts/wit/deps
111-
mkdir sdks/scala/wit/deps
112-
mkdir sdks/moonbit/golem_sdk/wit/deps
113-
glob_cp wit/deps/**/* sdks/rust/golem-rust/wit/deps
114-
glob_cp sdks/rust/golem-rust/wit/golem-ai/**/* sdks/rust/golem-rust/wit/deps
115-
glob_cp wit/deps/**/* sdks/ts/wit/deps
116-
glob_cp sdks/ts/wit/golem-ai/**/* sdks/ts/wit/deps
117-
glob_cp wit/deps/**/* sdks/scala/wit/deps
118-
glob_cp wit/deps/**/* sdks/moonbit/golem_sdk/wit/deps
119-
"""
108+
command = "cargo"
109+
args = [
110+
"run", "--quiet", "--manifest-path", "dev-tools/Cargo.toml", "-p", "dir-mirror", "--",
111+
"--src", "wit/deps", "--dst", "sdks/rust/golem-rust/wit/deps",
112+
"--src", "wit/deps", "--dst", "sdks/ts/wit/deps",
113+
"--src", "wit/deps", "--dst", "sdks/scala/wit/deps",
114+
"--src", "wit/deps", "--dst", "sdks/moonbit/golem_sdk/wit/deps",
115+
]
120116

121117
[tasks.wit-golem-cli]
122118
private = true
123-
script_runner = "@duckscript"
124-
script = """
125-
rm -r cli/golem-cli/wit/deps
126-
mkdir cli/golem-cli/wit/deps
127-
cp wit/deps/clocks cli/golem-cli/wit/deps
128-
cp wit/deps/io cli/golem-cli/wit/deps
129-
cp wit/deps/golem-1.x cli/golem-cli/wit/deps
130-
cp wit/deps/golem-core-v2 cli/golem-cli/wit/deps
131-
cp wit/deps/golem-agent cli/golem-cli/wit/deps
132-
cp wit/deps/logging cli/golem-cli/wit/deps
133-
"""
134-
135-
[tasks.remove-wit-deps]
136-
private = true
137-
script_runner = "@duckscript"
138-
script = """
139-
rm -rf golem-common/wit/deps
140-
rm -rf cli/golem-cli/wit/deps
141-
"""
119+
command = "cargo"
120+
args = [
121+
"run", "--quiet", "--manifest-path", "dev-tools/Cargo.toml", "-p", "dir-mirror", "--",
122+
"--src", "wit/deps/clocks", "--dst", "cli/golem-cli/wit/deps/clocks",
123+
"--src", "wit/deps/io", "--dst", "cli/golem-cli/wit/deps/io",
124+
"--src", "wit/deps/golem-1.x", "--dst", "cli/golem-cli/wit/deps/golem-1.x",
125+
"--src", "wit/deps/golem-core-v2", "--dst", "cli/golem-cli/wit/deps/golem-core-v2",
126+
"--src", "wit/deps/golem-agent", "--dst", "cli/golem-cli/wit/deps/golem-agent",
127+
"--src", "wit/deps/logging", "--dst", "cli/golem-cli/wit/deps/logging",
128+
]
142129

143130
[tasks.diff-wit]
144131
private = true
145-
script = "git diff --exit-code wit golem-common/wit"
132+
script_runner = "@duckscript"
133+
script = '''
134+
status = exec git status --porcelain -- golem-common/wit/deps cli/golem-cli/wit/deps sdks/rust/golem-rust/wit/deps sdks/ts/wit/deps sdks/scala/wit/deps sdks/moonbit/golem_sdk/wit/deps
135+
ok = eq ${status.code} 0
136+
if not ${ok}
137+
echo "git status failed (exit ${status.code}):"
138+
echo ${status.stderr}
139+
trigger_error "git status failed while checking wit/deps"
140+
end
141+
changes = trim ${status.stdout}
142+
empty = is_empty ${changes}
143+
if not ${empty}
144+
echo "Per-crate wit/deps copies are out of sync with the canonical wit/deps:"
145+
echo ${changes}
146+
echo "Run 'cargo make wit' and commit the result."
147+
trigger_error "wit/deps copies are out of sync"
148+
end
149+
'''
146150

147151
[tasks.check-wit]
148-
run_task = [{ name = ["remove-wit-deps", "wit", "diff-wit"] }]
152+
description = "Re-syncs wit/deps and fails if the committed per-crate copies are out of date"
153+
run_task = [{ name = ["wit", "diff-wit"] }]
149154

150155
# BUILD
151156

@@ -414,7 +419,13 @@ rm -f target/.local-publish-marker
414419

415420
[tasks.check]
416421
description = "Runs rustfmt and clippy checks without applying any fix"
417-
dependencies = ["wit", "check-clippy", "check-rustfmt"]
422+
dependencies = [
423+
"wit",
424+
"check-clippy",
425+
"check-rustfmt",
426+
"check-clippy-dev-tools",
427+
"check-rustfmt-dev-tools",
428+
]
418429

419430
[tasks.check-rustfmt]
420431
description = "Runs rustfmt checks without applying any fix"
@@ -428,12 +439,36 @@ install_crate = "clippy"
428439
command = "cargo"
429440
args = ["clippy", "--all-targets", "--", "--no-deps", "-Dwarnings"]
430441

442+
# The dev-tools workspace is separate, so it needs its own fmt/clippy invocations.
443+
[tasks.check-rustfmt-dev-tools]
444+
description = "Runs rustfmt checks on the dev-tools workspace without applying any fix"
445+
command = "cargo"
446+
args = ["fmt", "--manifest-path", "dev-tools/Cargo.toml", "--all", "--", "--check"]
447+
448+
[tasks.check-clippy-dev-tools]
449+
description = "Runs clippy checks on the dev-tools workspace without applying any fix"
450+
command = "cargo"
451+
args = [
452+
"clippy",
453+
"--manifest-path",
454+
"dev-tools/Cargo.toml",
455+
"--all-targets",
456+
"--",
457+
"--no-deps",
458+
"-Dwarnings",
459+
]
460+
431461
## ** FIX **
432462

433463
[tasks.fix]
434464
description = "Runs rustfmt and clippy checks and applies fixes"
435465
#dependencies = ["wit", "fix-clippy", "fix-rustfmt"]
436-
dependencies = ["fix-clippy", "fix-rustfmt"]
466+
dependencies = [
467+
"fix-clippy",
468+
"fix-rustfmt",
469+
"fix-clippy-dev-tools",
470+
"fix-rustfmt-dev-tools",
471+
]
437472

438473
[tasks.fix-rustfmt]
439474
description = "Runs rustfmt checks and applies fixes"
@@ -456,6 +491,27 @@ args = [
456491
"-Dwarnings",
457492
]
458493

494+
[tasks.fix-rustfmt-dev-tools]
495+
description = "Runs rustfmt on the dev-tools workspace and applies fixes"
496+
command = "cargo"
497+
args = ["fmt", "--manifest-path", "dev-tools/Cargo.toml", "--all"]
498+
499+
[tasks.fix-clippy-dev-tools]
500+
description = "Runs clippy on the dev-tools workspace and applies fixes"
501+
command = "cargo"
502+
args = [
503+
"clippy",
504+
"--manifest-path",
505+
"dev-tools/Cargo.toml",
506+
"--all-targets",
507+
"--fix",
508+
"--allow-dirty",
509+
"--allow-staged",
510+
"--",
511+
"--no-deps",
512+
"-Dwarnings",
513+
]
514+
459515
## ** TEST **
460516

461517
[tasks.test]
@@ -473,6 +529,11 @@ script = '''
473529
cargo-test-r run --workspace --lib -- --nocapture --report-time $JUNIT_OPTS
474530
'''
475531

532+
[tasks.dev-tools-tests]
533+
description = "Runs the dev-tools workspace unit tests"
534+
command = "cargo"
535+
args = ["test", "--manifest-path", "dev-tools/Cargo.toml", "--lib"]
536+
476537
[tasks.worker-executor-tests]
477538
dependencies = ["wit"]
478539
description = "Runs worker executor tests only"

dev-tools/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-tools/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Separate workspace for dev/build tooling, kept out of the main workspace's
2+
# --workspace/clippy/udeps/release. Tools here are intentionally std-only (no
3+
# dependencies) so they compile/check/test almost instantly — see README.md.
4+
# Add more tool crates as members.
5+
[workspace]
6+
resolver = "2"
7+
members = ["dir-mirror"]

dev-tools/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# dev-tools
2+
3+
A **separate cargo workspace** for small build/dev tooling, kept out of the main
4+
workspace (so it isn't pulled into `cargo build --workspace`, clippy, udeps, release,
5+
or `set-version`).
6+
7+
## Intentionally std-only
8+
9+
Tools here use **only the Rust standard library — no dependencies, not even for tests.**
10+
This is deliberate: these tools run on the hot path (e.g. `dir-mirror` is invoked by the
11+
`wit` cargo-make task before every build), so they must compile, check, and test almost
12+
instantly. Pulling in a dependency tree (proc-macros, async runtimes, a test framework,
13+
etc.) would add seconds of cold-compile time to the build and to CI for no real benefit
14+
at this size.
15+
16+
Concretely:
17+
18+
- **No runtime/build dependencies** — the `[dependencies]` table stays empty.
19+
- **No dev-dependencies** — tests are plain `#[test]` (libtest) with small std helpers
20+
(e.g. a tiny temp-dir struct), not a test framework. This differs from the main
21+
workspace, which uses `test-r`/`cargo-test-r`; that machinery is not worth its
22+
compile cost here.
23+
- Run the tests with plain `cargo test` (the cargo-make `dev-tools-tests` task), **not**
24+
`cargo-test-r` — in CI cargo-test-r runs in nextest-archive-reuse mode for the main
25+
workspace and rejects `--manifest-path` for a separate workspace.
26+
27+
If a tool ever genuinely needs a dependency, reconsider whether it belongs here or in the
28+
main workspace.
29+
30+
## Tools
31+
32+
- **`dir-mirror`** — idempotent directory mirror: makes a destination directory a
33+
byte-identical copy of a source directory, rewriting only changed files (preserving
34+
mtimes) and pruning stale ones. Used by the `wit` task to sync the per-crate
35+
`wit/deps` copies without triggering needless rebuilds.
36+
37+
## CI / cargo-make
38+
39+
- `cargo make dev-tools-tests` — runs the tests (plain `cargo test`). In CI this runs
40+
first in the `build-and-store` job, to fail fast if the build tooling is broken.
41+
- `cargo make check` / `cargo make fix` — also lint this workspace (the
42+
`*-dev-tools` rustfmt/clippy tasks).

dev-tools/dir-mirror/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "dir-mirror"
3+
version = "0.0.0"
4+
edition = "2024"
5+
license-file = "../../LICENSE"
6+
publish = false
7+
description = "Idempotent directory mirror used to keep per-crate wit/deps copies in sync"
8+
9+
# No dependencies — std only, on purpose (see dev-tools/README.md). Tests use plain
10+
# `cargo test` (libtest), so there are no dev-dependencies either.
11+
12+
[[bin]]
13+
name = "dir-mirror"
14+
test = false

0 commit comments

Comments
 (0)