From 9fb92f17b1161e8123921dd0603018322f72ce29 Mon Sep 17 00:00:00 2001 From: Ellen Marie Dash Date: Fri, 13 Dec 2024 16:39:50 -0500 Subject: [PATCH] Fail early if required tools can't be found. --- cargo-dist/src/errors.rs | 8 ++++ cargo-dist/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++ cargo-dist/src/tasks.rs | 59 ++++++++++++++++++++++++++ cargo-dist/src/tests/mock.rs | 4 ++ 4 files changed, 153 insertions(+) diff --git a/cargo-dist/src/errors.rs b/cargo-dist/src/errors.rs index d939798d4..2d8349030 100644 --- a/cargo-dist/src/errors.rs +++ b/cargo-dist/src/errors.rs @@ -361,6 +361,14 @@ pub enum DistError { tool: String, }, + /// One or more required tools are missing. + #[error("The following tools are required to run this task, but are missing:\n- {}", tools.join("\n- "))] + #[diagnostic(help("Please install the tools mentioned above and try again."))] + EnvToolsMissing { + /// the names of the missing tools + tools: Vec, + }, + /// Unknown target requested #[error( "A build was requested for {target}, but the standalone updater isn't available for it." diff --git a/cargo-dist/src/lib.rs b/cargo-dist/src/lib.rs index e50ee53fa..5669e395f 100644 --- a/cargo-dist/src/lib.rs +++ b/cargo-dist/src/lib.rs @@ -57,8 +57,90 @@ pub mod tasks; #[cfg(test)] mod tests; +/// dist env test -- make sure we have everything we need for a build. +pub fn do_env_test(cfg: &Config) -> DistResult<()> { + let (dist, _manifest) = tasks::gather_work(cfg)?; + + let builds = dist.config.builds; + + let need_cargo_auditable = builds.cargo.cargo_auditable; + let need_cargo_cyclonedx = builds.cargo.cargo_cyclonedx; + let need_omnibor = builds.omnibor; + let mut need_xwin = false; + let mut need_zigbuild = false; + + let tools = dist.tools; + + let cargo = tools.cargo()?; + let host = cargo.host_target.parse()?; + + for step in dist + .local_build_steps + .iter() + .chain(dist.global_build_steps.iter()) + { + // Can't require cross-compilation tools if we aren't compiling. + if cfg.artifact_mode == ArtifactMode::Lies { + break; + } + + match step { + BuildStep::Cargo(step) => { + let target = step.target_triple.parse()?; + let wrapper = tasks::build_wrapper_for_cross(&host, &target)?; + + match wrapper { + Some(CargoBuildWrapper::Xwin) => { + need_xwin = true; + } + Some(CargoBuildWrapper::ZigBuild) => { + need_zigbuild = true; + } + None => {} + } + } + _ => {} + } + } + + // These are all of the tools we can check for. + // + // bool::then(f) returns an Option, so we start with a + // Vec>>. + let all_tools: Vec>> = vec![ + need_cargo_auditable.then(|| tools.cargo_auditable()), + need_cargo_cyclonedx.then(|| tools.cargo_cyclonedx()), + need_omnibor.then(|| tools.omnibor()), + need_xwin.then(|| tools.cargo_xwin()), + need_zigbuild.then(|| tools.cargo_zigbuild()), + ]; + + // Drop `None`s, then extract the values from the remaining `Option`s. + let needed_tools = all_tools.into_iter().flatten(); + + let missing: Vec = needed_tools + .filter_map(|t| match t { + // The tool was found. + Ok(_) => None, + // The tool is missing + Err(DistError::ToolMissing { tool: ref name }) => Some(name.to_owned()), + // This should never happen, but I can't find a way to enforce + // it at the type system level. ;~; -@duckinator + Err(_) => unreachable!( + "do_env_test() got an Err that wasn't DistError::ToolMissing. This is a dist bug." + ), + }) + .collect(); + + missing + .is_empty() + .then_some(()) + .ok_or(DistError::EnvToolsMissing { tools: missing }) +} + /// dist build -- actually build binaries and installers! pub fn do_build(cfg: &Config) -> DistResult { + do_env_test(cfg)?; check_integrity(cfg)?; let (dist, mut manifest) = tasks::gather_work(cfg)?; diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs index ffd9154a8..4c83ec88f 100644 --- a/cargo-dist/src/tasks.rs +++ b/cargo-dist/src/tasks.rs @@ -281,6 +281,14 @@ pub struct Tools { /// /// pub code_sign_tool: Option, + /// cargo-auditable, used for auditable builds + pub cargo_auditable: Option, + /// cargo-cyclonedx, for generating CycloneDX artifacts + pub cargo_cyclonedx: Option, + /// cargo-xwin, for some cross builds + pub cargo_xwin: Option, + /// cargo-zigbuild, for some cross builds + pub cargo_zigbuild: Option, } impl Tools { @@ -297,6 +305,34 @@ impl Tools { tool: "omnibor-cli".to_owned(), }) } + + /// Returns cargo-auditable info or an error + pub fn cargo_auditable(&self) -> DistResult<&Tool> { + self.cargo_auditable.as_ref().ok_or(DistError::ToolMissing { + tool: "cargo-auditable".to_owned(), + }) + } + + /// Returns cargo-cyclonedx info or an error + pub fn cargo_cyclonedx(&self) -> DistResult<&Tool> { + self.cargo_cyclonedx.as_ref().ok_or(DistError::ToolMissing { + tool: "cargo-cyclonedx".to_owned(), + }) + } + + /// Returns cargo-xwin info or an error + pub fn cargo_xwin(&self) -> DistResult<&Tool> { + self.cargo_xwin.as_ref().ok_or(DistError::ToolMissing { + tool: "cargo-xwin".to_owned(), + }) + } + + /// Returns cargo-zigbuild info or an error + pub fn cargo_zigbuild(&self) -> DistResult<&Tool> { + self.cargo_zigbuild.as_ref().ok_or(DistError::ToolMissing { + tool: "cargo-zigbuild".to_owned(), + }) + } } /// Info about the cargo toolchain we're using @@ -3180,6 +3216,29 @@ fn tool_info() -> DistResult { omnibor: find_tool("omnibor", "--version"), // Computed later if needed code_sign_tool: None, + + // NOTE: This doesn't actually give us cargo-auditable's version info, + // but it does confirm it's installed, which is what we care about. + cargo_auditable: find_cargo_subcommand("cargo", "auditable", "--version"), + + cargo_cyclonedx: find_cargo_subcommand("cargo", "cyclonedx", "--version"), + cargo_xwin: find_cargo_subcommand("cargo", "xwin", "--version"), + cargo_zigbuild: find_tool("cargo-zigbuild", "--version"), + }) +} + +fn find_cargo_subcommand(name: &str, arg: &str, test_flag: &str) -> Option { + let output = Cmd::new(name, "detect tool") + .arg(arg) + .arg(test_flag) + .check(false) + .output() + .ok()?; + let string_output = String::from_utf8(output.stdout).ok()?; + let version = string_output.lines().next()?; + Some(Tool { + cmd: format!("{} {}", name, arg), + version: version.to_owned(), }) } diff --git a/cargo-dist/src/tests/mock.rs b/cargo-dist/src/tests/mock.rs index d9b49f99d..1ae6be245 100644 --- a/cargo-dist/src/tests/mock.rs +++ b/cargo-dist/src/tests/mock.rs @@ -84,6 +84,10 @@ pub fn mock_tools() -> Tools { git: None, omnibor: None, code_sign_tool: None, + cargo_auditable: None, + cargo_cyclonedx: None, + cargo_xwin: None, + cargo_zigbuild: None, } }