Repository: rust-cross/cargo-zigbuild (language: python)

Issue:
`cargo-zigbuild install`
`cargo install` but using zig as a linker


Discussion / hints:
While this seems useful, you usually install binary crates in host machine which `cargo install` should just work.
The use case here is for [`cargo-quickinstall`](https://github.com/cargo-bins/cargo-quickinstall) where we build popular crates on GHA and provides pre-built binaries to the users.
@NobodyXu Could you elaborate more on `cargo-quickinstall`'s use case? Doesn't `cargo zigbuild` already works for building crates?
`cargo-quickinstall` uses `cargo-install` to pull and build the latest version of popular crates.

While `cargo-zigbuild` is enough for building an already downloaded crate, for pulling crates down https://crates.io and install them, `cargo-install` is still the preferred method.
@messense We also use [`cargo-auditable`](https://github.com/rust-secure-code/cargo-auditable) to include the dependencies information in the crate so that tools like `cargo-audit` can scan for vulnerabilities.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index b145875..cb1e043 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,9 +25,9 @@ dependencies = [
 
 [[package]]
 name = "cargo-options"
-version = "0.5.3"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "860cd643171bc868500aff16c2405559b42fc71bd3130d761c2847b3e9e71cdc"
+checksum = "9b8e8daa6b2b84aa7cccd57317d9a9b36d969d75bb95923471f4eabbd36f2955"
 dependencies = [
  "clap",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 0cad601..8840bfc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ rust-version = "1.61"
 
 [dependencies]
 anyhow = "1.0.53"
-cargo-options = "0.5.3"
+cargo-options = "0.6.0"
 cargo_metadata = "0.15.2"
 clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
 dirs = "4.0.0"
diff --git a/src/bin/cargo-zigbuild.rs b/src/bin/cargo-zigbuild.rs
index 64e7861..dfddc27 100644
--- a/src/bin/cargo-zigbuild.rs
+++ b/src/bin/cargo-zigbuild.rs
@@ -3,7 +3,7 @@ use std::path::PathBuf;
 
 use anyhow::Context;
 use cargo_options::Metadata;
-use cargo_zigbuild::{Build, Check, Clippy, Run, Rustc, Test, Zig};
+use cargo_zigbuild::{Build, Check, Clippy, Install, Run, Rustc, Test, Zig};
 use clap::Parser;
 
 #[allow(clippy::large_enum_variant)]
@@ -16,6 +16,8 @@ pub enum Opt {
     Clippy(Clippy),
     #[command(name = "check", aliases = &["c"])]
     Check(Check),
+    #[command(name = "install")]
+    Install(Install),
     #[command(name = "metadata")]
     Metadata(Metadata),
     #[command(name = "rustc")]
@@ -52,6 +54,10 @@ fn main() -> anyhow::Result<()> {
                 check.enable_zig_ar = true;
                 check.execute()?
             }
+            Opt::Install(mut install) => {
+                install.enable_zig_ar = true;
+                install.execute()?
+            }
             Opt::Metadata(metadata) => {
                 let mut cmd = metadata.command();
                 let mut child = cmd.spawn().context("Failed to run cargo metadata")?;
diff --git a/src/build.rs b/src/build.rs
index 9fc23cb..29ddf1d 100644
--- a/src/build.rs
+++ b/src/build.rs
@@ -51,7 +51,13 @@ impl Build {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)
diff --git a/src/check.rs b/src/check.rs
index b6598dc..4a1a58a 100644
--- a/src/check.rs
+++ b/src/check.rs
@@ -50,7 +50,13 @@ impl Check {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)
diff --git a/src/clippy.rs b/src/clippy.rs
index fa61aef..b44ea2c 100644
--- a/src/clippy.rs
+++ b/src/clippy.rs
@@ -52,7 +52,13 @@ impl Clippy {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)
diff --git a/src/install.rs b/src/install.rs
new file mode 100644
index 0000000..1568cf6
--- /dev/null
+++ b/src/install.rs
@@ -0,0 +1,85 @@
+use std::ops::{Deref, DerefMut};
+use std::process::{self, Command};
+
+use anyhow::{Context, Result};
+use clap::Parser;
+
+use crate::zig::Zig;
+
+/// Install a Rust binary using zig as the linker. Default location is $HOME/.cargo/bin
+#[derive(Clone, Debug, Default, Parser)]
+#[command(
+    after_help = "Run `cargo help install` for more detailed information.",
+    display_order = 1
+)]
+pub struct Install {
+    #[command(flatten)]
+    pub cargo: cargo_options::Install,
+
+    /// Disable zig linker
+    #[arg(skip)]
+    pub disable_zig_linker: bool,
+
+    /// Enable zig ar
+    #[arg(skip)]
+    pub enable_zig_ar: bool,
+}
+
+impl Install {
+    /// Create a new install
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Execute `cargo install` command with zig as the linker
+    pub fn execute(&self) -> Result<()> {
+        let mut build = self.build_command()?;
+        let mut child = build.spawn().context("Failed to run cargo install")?;
+        let status = child
+            .wait()
+            .expect("Failed to wait on cargo install process");
+        if !status.success() {
+            process::exit(status.code().unwrap_or(1));
+        }
+        Ok(())
+    }
+
+    /// Generate cargo subcommand
+    pub fn build_command(&self) -> Result<Command> {
+        let mut install = self.cargo.command();
+        if !self.disable_zig_linker {
+            Zig::apply_command_env(
+                None,
+                !self.debug,
+                &self.cargo.common,
+                &mut install,
+                self.enable_zig_ar,
+            )?;
+        }
+
+        Ok(install)
+    }
+}
+
+impl Deref for Install {
+    type Target = cargo_options::Install;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cargo
+    }
+}
+
+impl DerefMut for Install {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cargo
+    }
+}
+
+impl From<cargo_options::Install> for Install {
+    fn from(cargo: cargo_options::Install) -> Self {
+        Self {
+            cargo,
+            ..Default::default()
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 1cf520f..72f1a88 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 mod build;
 mod check;
 mod clippy;
+mod install;
 pub mod linux;
 pub mod macos;
 mod run;
@@ -11,6 +12,7 @@ pub mod zig;
 pub use crate::clippy::Clippy;
 pub use build::Build;
 pub use check::Check;
+pub use install::Install;
 pub use run::Run;
 pub use rustc::Rustc;
 pub use test::Test;
diff --git a/src/run.rs b/src/run.rs
index b101839..76fa40d 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -51,7 +51,13 @@ impl Run {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)
diff --git a/src/rustc.rs b/src/rustc.rs
index 9416785..ce986d3 100644
--- a/src/rustc.rs
+++ b/src/rustc.rs
@@ -52,7 +52,13 @@ impl Rustc {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)
diff --git a/src/zig.rs b/src/zig.rs
index dfc8587..a69c67c 100644
--- a/src/zig.rs
+++ b/src/zig.rs
@@ -321,6 +321,8 @@ impl Zig {
     }
 
     pub(crate) fn apply_command_env(
+        manifest_path: Option<&Path>,
+        release: bool,
         cargo: &cargo_options::CommonOptions,
         cmd: &mut Command,
         enable_zig_ar: bool,
@@ -376,7 +378,7 @@ impl Zig {
                 add_env(format!("AR_{}", env_target), &zig_wrapper.ar);
             }
 
-            Self::setup_os_deps(cargo)?;
+            Self::setup_os_deps(manifest_path, release, cargo)?;
 
             let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{}", env_target);
             if env::var_os(&cmake_toolchain_file_env).is_none()
@@ -419,16 +421,17 @@ impl Zig {
         Ok(())
     }
 
-    fn setup_os_deps(cargo: &cargo_options::CommonOptions) -> Result<()> {
+    fn setup_os_deps(
+        manifest_path: Option<&Path>,
+        release: bool,
+        cargo: &cargo_options::CommonOptions,
+    ) -> Result<()> {
         for target in &cargo.target {
             if target.contains("apple") {
                 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
                     target_dir.join(target)
                 } else {
-                    let manifest_path = cargo
-                        .manifest_path
-                        .as_deref()
-                        .unwrap_or_else(|| Path::new("Cargo.toml"));
+                    let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
                     let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
                     metadata_cmd.manifest_path(manifest_path);
                     let metadata = metadata_cmd.exec()?;
@@ -439,7 +442,7 @@ impl Zig {
                     Some("release" | "bench") => "release",
                     Some(profile) => profile,
                     None => {
-                        if cargo.release {
+                        if release {
                             "release"
                         } else {
                             "debug"


Test patch:
diff --git a/src/test.rs b/src/test.rs
index bb9de88..efb5997 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -51,7 +51,13 @@ impl Test {
     pub fn build_command(&self) -> Result<Command> {
         let mut build = self.cargo.command();
         if !self.disable_zig_linker {
-            Zig::apply_command_env(&self.cargo.common, &mut build, self.enable_zig_ar)?;
+            Zig::apply_command_env(
+                self.manifest_path.as_deref(),
+                self.release,
+                &self.cargo.common,
+                &mut build,
+                self.enable_zig_ar,
+            )?;
         }
 
         Ok(build)