diff --git a/Cargo.lock b/Cargo.lock index 686440ab9..03e789f08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8037,18 +8037,3 @@ dependencies = [ "syn 3.0.6", "winnow", ] - -[[patch.unused]] -name = "egui_demo_lib" -version = "0.37.0" -source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a" - -[[patch.unused]] -name = "egui_inspection" -version = "0.37.0" -source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a" - -[[patch.unused]] -name = "egui_system_font_provider" -version = "0.37.0" -source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a" diff --git a/Cargo.toml b/Cargo.toml index 10055a63f..6f96cb012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -254,8 +254,7 @@ egui-wgpu = { git = "https://github.com/storytold/egui.git", rev = "6d518fc79968 egui_glow = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } egui_extras = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } egui_kittest = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } -egui_system_font_provider = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } eframe = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } -egui_demo_lib = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } -egui_inspection = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } +# Patch a crate only together with the dependency that needs it: an unused patch makes the Nix +# build, which uses vendored sources, fail (`cargo xtask lock-patches`). winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" } diff --git a/docs/testing.md b/docs/testing.md index 54152b9dc..e6cd8df68 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -2,7 +2,7 @@ ```sh cargo test --workspace # everything -cargo xtask ci # fmt, clippy -D warnings, tests, pdf-deps, layers, assets, wasm +cargo xtask ci # fmt, clippy -D warnings, tests, pdf-deps, lock-patches, layers, assets, wasm ``` The After Effects test projects of `crates/aep` are not in the repository; without them the diff --git a/xtask/src/lock_patches.rs b/xtask/src/lock_patches.rs new file mode 100644 index 000000000..30cc2c8b2 --- /dev/null +++ b/xtask/src/lock_patches.rs @@ -0,0 +1,71 @@ +//! Fail when `Cargo.lock` records a `[patch]` entry nothing uses. +//! +//! An unused patch only makes cargo warn, but the Nix package builds offline from vendored +//! sources, which hold only the packages the build uses: it cannot find the patched package and +//! refuses to build (#3403). Patch a crate together with the dependency that needs it. + +use std::path::Path; + +pub fn run(root: &Path) -> Result<(), String> { + let lock = std::fs::read_to_string(root.join("Cargo.lock")).map_err(|e| format!("Cargo.lock: {e}"))?; + let unused = unused_patches(&lock); + if unused.is_empty() { + println!("Cargo.lock: every [patch] entry is used"); + return Ok(()); + } + Err(format!( + "Cargo.lock has [[patch.unused]] entries: {}. Remove them from [patch] in Cargo.toml (add a patch together with the dependency that needs it); the Nix build, which uses vendored sources, fails on them (#3403)", + unused.join(", ") + )) +} + +/// The package names of the `[[patch.unused]]` entries in the text of a `Cargo.lock`. +fn unused_patches(lock: &str) -> Vec { + let mut names = Vec::new(); + let mut in_unused = false; + for line in lock.lines() { + let line = line.trim(); + if line.starts_with('[') { + in_unused = line == "[[patch.unused]]"; + } else if let Some(name) = line.strip_prefix("name = ").filter(|_| in_unused) { + names.push(name.trim_matches('"').to_string()); + } + } + names +} + +#[cfg(test)] +mod tests { + use super::*; + + const LOCK: &str = r#" +[[package]] +name = "egui" +version = "0.37.0" + +[[patch.unused]] +name = "egui_demo_lib" +version = "0.37.0" +source = "git+https://github.com/storytold/egui.git?rev=6d518fc#6d518fc" + +[[patch.unused]] +name = "egui_inspection" +version = "0.37.0" +"#; + + #[test] + fn lists_the_unused_patches_and_only_those() { + assert_eq!(unused_patches(LOCK), ["egui_demo_lib", "egui_inspection"]); + } + + #[test] + fn a_lock_without_unused_patches_is_accepted() { + assert!(unused_patches("[[package]]\nname = \"egui\"\nversion = \"0.37.0\"\n").is_empty()); + } + + #[test] + fn the_repository_lock_has_no_unused_patches() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); + assert!(run(&root).is_ok(), "{:?}", run(&root)); + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 24023ee88..06d32e51a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,4 @@ -//! Workspace automation: `cargo xtask `. +//! Workspace automation: `cargo xtask `. //! //! - `layers`: enforces the dependency layering of `docs/architecture.md` §1 (downward-only edges, //! listed same-layer edges, no UI/OS crates below L5). @@ -6,6 +6,7 @@ //! and the web app. //! - `web [--dev] [--serve PORT]`: build the web app (`apps/effectcraft-web`) into //! `/web/dist` with `wasm-bindgen` (docs/web.md); `--serve` serves it on localhost. +//! - `lock-patches`: `Cargo.lock` records no unused `[patch]` entry (the Nix build cannot vendor one). //! - `assets`: every asset file (image, icon, font, LUT, audio, video…) has a complete //! `.attribution` sidecar and an entry in `ATTRIBUTION.md` (AGENTS.md §1). //! - `aep-corpus [--dir DIR]`: fetch the After Effects test corpus at the commit pinned in @@ -19,6 +20,7 @@ mod aep_corpus; mod bench; mod ico; +mod lock_patches; mod pdf_deps; mod version; @@ -534,12 +536,13 @@ type Stage = fn() -> Result<(), String>; /// Every stage runs even when an earlier one fails, so one run reports all the problems. fn ci() -> Result<(), String> { - let stages: [(&str, Stage); 8] = [ + let stages: [(&str, Stage); 9] = [ ("audio backend", audio_backend), ("fmt", || run(Command::new(env!("CARGO")).args(["fmt", "--check"]))), ("clippy", || run(Command::new(env!("CARGO")).args(["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"]))), ("tests", tests), ("pdf-deps", || pdf_deps::run(&root())), + ("lock-patches", || lock_patches::run(&root())), ("layers", layers), ("assets", assets), ("wasm", wasm), @@ -555,6 +558,7 @@ fn main() -> ExitCode { "wasm" => wasm(), "assets" => assets(), "pdf-deps" => pdf_deps::run(&root()), + "lock-patches" => lock_patches::run(&root()), "aep-corpus" => aep_corpus::run(&root(), &std::env::args().skip(2).collect::>()), "ci" => ci(), "audio-backend" => audio_backend(), @@ -568,7 +572,7 @@ fn main() -> ExitCode { let rest: Vec = std::env::args().skip(2).collect(); version::run(&root(), &rest.iter().map(String::as_str).collect::>()) } - _ => Err("usage: cargo xtask ".into()), + _ => Err("usage: cargo xtask ".into()), }; match r { Ok(()) => ExitCode::SUCCESS,