diff --git a/src/build.rs b/src/build.rs index b205a82..3ba38f0 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,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,40 @@ 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(["-Zbindeps"]) + /// .run(|build| { + /// build.cargo().args(&["test", "--all"]).run()?; + /// Ok(()) + /// })?; + /// # Ok(()) + /// # } + pub fn extra_cargo_args>( + mut self, + args: impl IntoIterator, + ) -> Self { + self.extra_cargo_args + .extend(args.into_iter().map(Into::into)); + 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 +197,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 +249,7 @@ impl BuildDirectory { krate, sandbox, patches: Vec::new(), + extra_cargo_args: Vec::new(), } } @@ -229,6 +271,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 +279,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 +457,7 @@ impl<'ws> Build<'ws> { self.toolchain, &self.host_source_dir(), targets, + &[], ) } } diff --git a/src/prepare.rs b/src/prepare.rs index 10e7515..4a58c91 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,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.iter().cloned()) .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.iter().cloned()); 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: &[OsString], ) -> anyhow::Result<()> { let mut cmd = Command::new(workspace, toolchain.cargo()) .args(["fetch", "--manifest-path", "Cargo.toml"]) + .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 0000000..8aaa600 --- /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 0000000..65d050a --- /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 0000000..421e195 --- /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 0000000..8e0fd87 --- /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 4ba998e..aaee692 100644 --- a/tests/buildtest/mod.rs +++ b/tests/buildtest/mod.rs @@ -341,6 +341,58 @@ fn test_cargo_workspace() { }); } +#[test] +fn test_extra_cargo_args() { + 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(()) + }); +} + +#[test] +fn test_extra_cargo_args_invalid() { + runner::run("hello-world", |run| { + 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!( + output.contains("metadata") && output.contains("--invalid-flag-that-does-not-exist"), + "output: {output:?}" + ); + Ok(()) + }); +} + test_prepare_error!( test_missing_cargotoml, "missing-cargotoml", @@ -433,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!(