From e4ae0d476ad7e8465be11cf7006989c37fbf2286 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:31:54 +0300 Subject: [PATCH 1/4] Add extra_cargo_args support for prepare phase --- src/build.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- src/prepare.rs | 22 +++++++++++++++------ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/build.rs b/src/build.rs index b205a82e..28f7630d 100644 --- a/src/build.rs +++ b/src/build.rs @@ -46,6 +46,7 @@ pub struct BuildBuilder<'a> { krate: &'a Crate, sandbox: SandboxBuilder, patches: Vec, + extra_cargo_args: Vec, } /// Output of a completed build together with build-level statistics. @@ -131,6 +132,36 @@ impl BuildBuilder<'_> { self } + /// Add extra arguments passed to cargo commands during the prepare phase + /// (manifest validation, lockfile generation, dependency fetching). + /// + /// This is useful for passing unstable cargo flags (e.g. `-Zbindeps`) that + /// are required for cargo to parse the crate's manifest. + /// + /// # Example + /// + /// ```no_run + /// # use rustwide::{WorkspaceBuilder, Toolchain, Crate, cmd::SandboxBuilder}; + /// # use std::error::Error; + /// # fn main() -> anyhow::Result<(), Box> { + /// # let workspace = WorkspaceBuilder::new("".as_ref(), "").init()?; + /// # let toolchain = Toolchain::dist(""); + /// # let krate = Crate::local("".as_ref()); + /// # let sandbox = SandboxBuilder::new(); + /// let mut build_dir = workspace.build_dir("foo"); + /// build_dir.build(&toolchain, &krate, sandbox) + /// .extra_cargo_args(vec!["-Zbindeps".into()]) + /// .run(|build| { + /// build.cargo().args(&["test", "--all"]).run()?; + /// Ok(()) + /// })?; + /// # Ok(()) + /// # } + pub fn extra_cargo_args(mut self, args: Vec) -> Self { + self.extra_cargo_args = args; + self + } + /// Run a sandboxed build of the provided crate with the provided toolchain. The closure will /// be provided an instance of [`Build`](struct.Build.html) that allows spawning new processes /// inside the sandbox. @@ -162,8 +193,14 @@ impl BuildBuilder<'_> { self, f: F, ) -> anyhow::Result> { - self.build_dir - .run(self.toolchain, self.krate, self.sandbox, self.patches, f) + self.build_dir.run( + self.toolchain, + self.krate, + self.sandbox, + self.patches, + self.extra_cargo_args, + f, + ) } } @@ -208,6 +245,7 @@ impl BuildDirectory { krate, sandbox, patches: Vec::new(), + extra_cargo_args: Vec::new(), } } @@ -229,6 +267,7 @@ impl BuildDirectory { krate: &Crate, sandbox: SandboxBuilder, patches: Vec, + extra_cargo_args: Vec, f: F, ) -> anyhow::Result> { let source_dir = self.source_dir(); @@ -236,7 +275,14 @@ impl BuildDirectory { crate::utils::remove_dir_all(&source_dir)?; } - let mut prepare = Prepare::new(&self.workspace, toolchain, krate, &source_dir, patches); + let mut prepare = Prepare::new( + &self.workspace, + toolchain, + krate, + &source_dir, + patches, + extra_cargo_args, + ); prepare.prepare().map_err(|err| { if err.downcast_ref::().is_none() { err.context(PrepareError::Uncategorized) @@ -407,6 +453,7 @@ impl<'ws> Build<'ws> { self.toolchain, &self.host_source_dir(), targets, + &[], ) } } diff --git a/src/prepare.rs b/src/prepare.rs index 10e75159..a43e6da0 100644 --- a/src/prepare.rs +++ b/src/prepare.rs @@ -14,6 +14,7 @@ pub(crate) struct Prepare<'a> { krate: &'a Crate, source_dir: &'a Path, patches: Vec, + extra_cargo_args: Vec, } impl<'a> Prepare<'a> { @@ -23,6 +24,7 @@ impl<'a> Prepare<'a> { krate: &'a Crate, source_dir: &'a Path, patches: Vec, + extra_cargo_args: Vec, ) -> Self { Self { workspace, @@ -30,6 +32,7 @@ impl<'a> Prepare<'a> { krate, source_dir, patches, + extra_cargo_args, } } @@ -70,6 +73,7 @@ impl<'a> Prepare<'a> { let res = Command::new(self.workspace, self.toolchain.cargo()) .args(["metadata", "--manifest-path", "Cargo.toml", "--no-deps"]) + .args(&self.extra_cargo_args) .current_directory(self.source_dir) .log_output(false) .run(); @@ -117,11 +121,9 @@ impl<'a> Prepare<'a> { return Ok(()); } - let mut cmd = Command::new(self.workspace, self.toolchain.cargo()).args([ - "generate-lockfile", - "--manifest-path", - "Cargo.toml", - ]); + let mut cmd = Command::new(self.workspace, self.toolchain.cargo()) + .args(["generate-lockfile", "--manifest-path", "Cargo.toml"]) + .args(&self.extra_cargo_args); if !self.workspace.fetch_registry_index_during_builds() { cmd = cmd .args(["-Zno-index-update"]) @@ -133,7 +135,13 @@ impl<'a> Prepare<'a> { #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] fn fetch_deps(&mut self) -> anyhow::Result<()> { - fetch_deps(self.workspace, self.toolchain, self.source_dir, &[]) + fetch_deps( + self.workspace, + self.toolchain, + self.source_dir, + &[], + &self.extra_cargo_args, + ) } } @@ -153,9 +161,11 @@ pub(crate) fn fetch_deps( toolchain: &Toolchain, source_dir: &Path, fetch_build_std_targets: &[&str], + extra_cargo_args: &[String], ) -> anyhow::Result<()> { let mut cmd = Command::new(workspace, toolchain.cargo()) .args(["fetch", "--manifest-path", "Cargo.toml"]) + .args(extra_cargo_args) .current_directory(source_dir); // Pass `-Zbuild-std` in case a build in the sandbox wants to use it; // build-std has to have the source for libstd's dependencies available. From 41fb7ec9aa89ebb6b7e21fe62936618d3e25a8a3 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:17:01 +0300 Subject: [PATCH 2/4] Add integration tests for extra_cargo_args Test that extra cargo args are forwarded to cargo during the prepare phase: a positive test with --quiet and a negative test with an invalid flag to prove args are actually passed through. --- tests/buildtest/mod.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/buildtest/mod.rs b/tests/buildtest/mod.rs index 4ba998e7..91b7337c 100644 --- a/tests/buildtest/mod.rs +++ b/tests/buildtest/mod.rs @@ -341,6 +341,37 @@ fn test_cargo_workspace() { }); } +#[test] +fn test_extra_cargo_args() { + runner::run("hello-world", |run| { + run.build(SandboxBuilder::new().enable_networking(false), |builder| { + builder + .extra_cargo_args(vec!["--quiet".into()]) + .run(|build| { + build.cargo().args(&["run"]).run()?; + Ok(()) + }) + })?; + Ok(()) + }); +} + +#[test] +fn test_extra_cargo_args_invalid() { + runner::run("hello-world", |run| { + let res = run.build(SandboxBuilder::new().enable_networking(false), |builder| { + builder + .extra_cargo_args(vec!["--invalid-flag-that-does-not-exist".into()]) + .run(|_build| Ok(())) + }); + assert!( + res.is_err(), + "expected extra cargo args to cause a prepare failure" + ); + Ok(()) + }); +} + test_prepare_error!( test_missing_cargotoml, "missing-cargotoml", From f608503deff0afd30b491c5f6e7840662b7db0f6 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Tue, 16 Jun 2026 02:51:25 +0300 Subject: [PATCH 3/4] Address extra_cargo_args review feedback --- src/build.rs | 16 ++++--- src/prepare.rs | 14 +++--- .../crates/extra-cargo-args/Cargo.toml | 7 +++ .../extra-cargo-args-dep/Cargo.toml | 4 ++ .../extra-cargo-args-dep/src/lib.rs | 1 + .../crates/extra-cargo-args/src/main.rs | 3 ++ tests/buildtest/mod.rs | 45 ++++++++++++++----- 7 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 tests/buildtest/crates/extra-cargo-args/Cargo.toml create mode 100644 tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/Cargo.toml create mode 100644 tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/src/lib.rs create mode 100644 tests/buildtest/crates/extra-cargo-args/src/main.rs diff --git a/src/build.rs b/src/build.rs index 28f7630d..3ba38f06 100644 --- a/src/build.rs +++ b/src/build.rs @@ -6,9 +6,9 @@ use crate::{ }, prepare::Prepare, }; -use std::path::PathBuf; use std::vec::Vec; use std::{cell::RefCell, rc::Rc}; +use std::{ffi::OsString, path::PathBuf}; #[derive(Clone)] pub(crate) enum CratePatch { @@ -46,7 +46,7 @@ pub struct BuildBuilder<'a> { krate: &'a Crate, sandbox: SandboxBuilder, patches: Vec, - extra_cargo_args: Vec, + extra_cargo_args: Vec, } /// Output of a completed build together with build-level statistics. @@ -150,15 +150,19 @@ impl BuildBuilder<'_> { /// # let sandbox = SandboxBuilder::new(); /// let mut build_dir = workspace.build_dir("foo"); /// build_dir.build(&toolchain, &krate, sandbox) - /// .extra_cargo_args(vec!["-Zbindeps".into()]) + /// .extra_cargo_args(["-Zbindeps"]) /// .run(|build| { /// build.cargo().args(&["test", "--all"]).run()?; /// Ok(()) /// })?; /// # Ok(()) /// # } - pub fn extra_cargo_args(mut self, args: Vec) -> Self { - self.extra_cargo_args = args; + pub fn extra_cargo_args>( + mut self, + args: impl IntoIterator, + ) -> Self { + self.extra_cargo_args + .extend(args.into_iter().map(Into::into)); self } @@ -267,7 +271,7 @@ impl BuildDirectory { krate: &Crate, sandbox: SandboxBuilder, patches: Vec, - extra_cargo_args: Vec, + extra_cargo_args: Vec, f: F, ) -> anyhow::Result> { let source_dir = self.source_dir(); diff --git a/src/prepare.rs b/src/prepare.rs index a43e6da0..4a58c913 100644 --- a/src/prepare.rs +++ b/src/prepare.rs @@ -2,7 +2,7 @@ use crate::cmd::{Command, CommandError, ProcessLinesActions}; use crate::{Crate, Toolchain, Workspace, build::CratePatch}; use anyhow::Context as _; use log::info; -use std::path::Path; +use std::{ffi::OsString, path::Path}; use toml::{ Value, value::{Array, Table}, @@ -14,7 +14,7 @@ pub(crate) struct Prepare<'a> { krate: &'a Crate, source_dir: &'a Path, patches: Vec, - extra_cargo_args: Vec, + extra_cargo_args: Vec, } impl<'a> Prepare<'a> { @@ -24,7 +24,7 @@ impl<'a> Prepare<'a> { krate: &'a Crate, source_dir: &'a Path, patches: Vec, - extra_cargo_args: Vec, + extra_cargo_args: Vec, ) -> Self { Self { workspace, @@ -73,7 +73,7 @@ impl<'a> Prepare<'a> { let res = Command::new(self.workspace, self.toolchain.cargo()) .args(["metadata", "--manifest-path", "Cargo.toml", "--no-deps"]) - .args(&self.extra_cargo_args) + .args(self.extra_cargo_args.iter().cloned()) .current_directory(self.source_dir) .log_output(false) .run(); @@ -123,7 +123,7 @@ impl<'a> Prepare<'a> { let mut cmd = Command::new(self.workspace, self.toolchain.cargo()) .args(["generate-lockfile", "--manifest-path", "Cargo.toml"]) - .args(&self.extra_cargo_args); + .args(self.extra_cargo_args.iter().cloned()); if !self.workspace.fetch_registry_index_during_builds() { cmd = cmd .args(["-Zno-index-update"]) @@ -161,11 +161,11 @@ pub(crate) fn fetch_deps( toolchain: &Toolchain, source_dir: &Path, fetch_build_std_targets: &[&str], - extra_cargo_args: &[String], + extra_cargo_args: &[OsString], ) -> anyhow::Result<()> { let mut cmd = Command::new(workspace, toolchain.cargo()) .args(["fetch", "--manifest-path", "Cargo.toml"]) - .args(extra_cargo_args) + .args(extra_cargo_args.iter().cloned()) .current_directory(source_dir); // Pass `-Zbuild-std` in case a build in the sandbox wants to use it; // build-std has to have the source for libstd's dependencies available. diff --git a/tests/buildtest/crates/extra-cargo-args/Cargo.toml b/tests/buildtest/crates/extra-cargo-args/Cargo.toml new file mode 100644 index 00000000..8aaa6000 --- /dev/null +++ b/tests/buildtest/crates/extra-cargo-args/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "extra-cargo-args" +version = "0.1.0" +edition = "2021" + +[dependencies] +extra-cargo-args-dep = { path = "extra-cargo-args-dep" } diff --git a/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/Cargo.toml b/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/Cargo.toml new file mode 100644 index 00000000..65d050a3 --- /dev/null +++ b/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "extra-cargo-args-dep" +version = "0.1.0" +edition = "2021" diff --git a/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/src/lib.rs b/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/src/lib.rs new file mode 100644 index 00000000..421e195a --- /dev/null +++ b/tests/buildtest/crates/extra-cargo-args/extra-cargo-args-dep/src/lib.rs @@ -0,0 +1 @@ +pub fn hello() {} diff --git a/tests/buildtest/crates/extra-cargo-args/src/main.rs b/tests/buildtest/crates/extra-cargo-args/src/main.rs new file mode 100644 index 00000000..8e0fd873 --- /dev/null +++ b/tests/buildtest/crates/extra-cargo-args/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + extra_cargo_args_dep::hello(); +} diff --git a/tests/buildtest/mod.rs b/tests/buildtest/mod.rs index 91b7337c..12a47edc 100644 --- a/tests/buildtest/mod.rs +++ b/tests/buildtest/mod.rs @@ -343,15 +343,25 @@ fn test_cargo_workspace() { #[test] fn test_extra_cargo_args() { - runner::run("hello-world", |run| { - run.build(SandboxBuilder::new().enable_networking(false), |builder| { - builder - .extra_cargo_args(vec!["--quiet".into()]) - .run(|build| { - build.cargo().args(&["run"]).run()?; + runner::run("extra-cargo-args", |run| { + let storage = rustwide::logging::LogStorage::new(LevelFilter::Info); + rustwide::logging::capture(&storage, || -> anyhow::Result<_> { + run.build(SandboxBuilder::new().enable_networking(false), |builder| { + builder.extra_cargo_args(["--quiet"]).run(|build| { + build.cargo().args(["run"]).run()?; Ok(()) }) + })?; + Ok(()) })?; + + let output = storage.to_string(); + assert!( + output.contains("generate-lockfile") + && output.contains("--quiet") + && !output.contains("Locking 1 package"), + "output: {output:?}" + ); Ok(()) }); } @@ -359,14 +369,25 @@ fn test_extra_cargo_args() { #[test] fn test_extra_cargo_args_invalid() { runner::run("hello-world", |run| { - let res = run.build(SandboxBuilder::new().enable_networking(false), |builder| { - builder - .extra_cargo_args(vec!["--invalid-flag-that-does-not-exist".into()]) - .run(|_build| Ok(())) + let storage = rustwide::logging::LogStorage::new(LevelFilter::Info); + let res = rustwide::logging::capture(&storage, || { + run.build(SandboxBuilder::new().enable_networking(false), |builder| { + builder + .extra_cargo_args(["--invalid-flag-that-does-not-exist"]) + .run(|_build| Ok(())) + }) }); + + match res.err().and_then(|err| err.downcast().ok()) { + Some(rustwide::PrepareError::InvalidCargoTomlSyntax) => {} + Some(other) => panic!("expected InvalidCargoTomlSyntax, got {other:?}"), + None => panic!("expected InvalidCargoTomlSyntax, got Ok"), + } + + let output = storage.to_string(); assert!( - res.is_err(), - "expected extra cargo args to cause a prepare failure" + output.contains("metadata") && output.contains("--invalid-flag-that-does-not-exist"), + "output: {output:?}" ); Ok(()) }); From 207b58ee52a6a5004d1170c0ea18e7751deb45f2 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:04:27 +0300 Subject: [PATCH 4/4] Update missing dependency typo expectation --- tests/buildtest/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/buildtest/mod.rs b/tests/buildtest/mod.rs index 12a47edc..aaee6927 100644 --- a/tests/buildtest/mod.rs +++ b/tests/buildtest/mod.rs @@ -485,7 +485,7 @@ test_prepare_error_stderr!( test_missing_deps_typo, "missing-deps-typo", MissingDependencies, - "error: no matching package found" + "error: no matching package named `build_rs` found" ); test_prepare_error_stderr!(