From 9adec6a59d25f7fe100eded52d2c3cb75bb5c582 Mon Sep 17 00:00:00 2001 From: David Calavera <1050+calavera@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:41:49 -0700 Subject: [PATCH] Rust: keep dependencies' build scripts in the target archive A workspace crate with a build.rs added the build-script-build target name to the skip pattern, which also matched every dependency's compiled build script under target/debug/build/-/. Cargo then rebuilt each dependency with a build script and all of its dependents on every warm run. Build-script targets are no longer part of the pattern; the package name already covers the workspace's own build directories. The Rust key gains an archive revision so entries saved without the scripts are replaced instead of kept as unchanged. --- src/lib/languages.js | 11 +++++++++-- test/cache.test.js | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/languages.js b/src/lib/languages.js index 3d67f6f..527fb4f 100644 --- a/src/lib/languages.js +++ b/src/lib/languages.js @@ -114,7 +114,12 @@ function workspaceArtifacts(roots) { if (!metadata) continue; try { for (const pkg of JSON.parse(metadata).packages) { - for (const name of [pkg.name, ...pkg.targets.map((target) => target.name)]) { + // Every build script's target is named build-script-build, so its name + // would match each dependency's compiled script, and a missing script + // rebuilds the crate and all its dependents. The package name already + // covers the workspace's own build/-/ directories. + const targets = pkg.targets.filter((target) => !target.kind.includes('custom-build')); + for (const name of [pkg.name, ...targets.map((target) => target.name)]) { names.add(name); names.add(name.replace(/-/g, '_')); } @@ -169,7 +174,9 @@ const SPECS = { if (fs.existsSync(path.join(bin, 'rustup')) || fs.existsSync(path.join(bin, 'cargo'))) core.addPath(bin); }, version() { - return commandOutput('rustc', ['-vV']); + // The revision changes the key when the archived files change, so + // entries saved without dependencies' build scripts are replaced. + return `${commandOutput('rustc', ['-vV'])}\narchive-revision: 2`; }, }, node: { diff --git a/test/cache.test.js b/test/cache.test.js index 9e59864..b87fbd8 100644 --- a/test/cache.test.js +++ b/test/cache.test.js @@ -440,6 +440,7 @@ test('drops workspace crate artifacts but keeps dependencies', { skip: !hasCargo const root = path.join(sandbox, 'crate'); write(path.join(root, 'Cargo.toml'), '[package]\nname = "my-app"\nversion = "0.1.0"\nedition = "2021"\n'); write(path.join(root, 'src', 'main.rs'), 'fn main() {}\n'); + write(path.join(root, 'build.rs'), 'fn main() {}\n'); const debug = path.join(root, 'target', 'debug'); fs.mkdirSync(path.join(debug, 'deps'), { recursive: true }); // cargo needs the real toolchain home to answer metadata. @@ -453,6 +454,9 @@ test('drops workspace crate artifacts but keeps dependencies', { skip: !hasCargo assert.ok(skip(debug, 'my-app'), 'uplifted binary'); for (const name of [`libserde-${hash}.rlib`, `my_app_utils-${hash}.rlib`, 'serde-1.0']) assert.ok(!skip(deps, name), name); assert.ok(!skip(path.join(root, 'elsewhere'), 'my-app'), 'uplifted names only next to deps/'); + // A dependency's compiled build script, which the workspace's build.rs shares a target name with. + const script = path.join(debug, 'build', `libc-${hash}`); + for (const name of [`build_script_build-${hash}`, `build_script_build-${hash}.d`, 'build-script-build']) assert.ok(!skip(script, name), name); }); test('keys on lockfiles, falling back to manifests', () => {