Repository: axodotdev/cargo-dist (rust)

Issue:
cross compile aarch64-apple-darwin by default
because this is made dead simple by apple (we need only add the target), i think we should include compiling to aarch64-apple-darwin by default, ahead of any larger cross compilation story (which will require more configuration and tooling)

this plus #82/#100  will bring us to target parity with a lot of folks looking to migrate to using cargo-dist

Pull request description:
Use rustup to install the appropriate macOS target when doing mac->mac crosses
Currently semi-WIP, I need to actually dig out my macbook to test this lol

fixes #133 

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index b7d463f8..2adf9e5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ ci = ["github"]
 # The installers to generate for each app
 installers = ["shell", "powershell"]
 # Target platforms to build apps for (Rust target-triple syntax)
-targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc"]
+targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-apple-darwin"]
 
 # The profile that 'cargo dist' will build with
 [profile.dist]
diff --git a/cargo-dist/src/lib.rs b/cargo-dist/src/lib.rs
index eaaeb86b..5639b139 100644
--- a/cargo-dist/src/lib.rs
+++ b/cargo-dist/src/lib.rs
@@ -217,6 +217,7 @@ fn manifest_artifact(
 fn run_build_step(dist_graph: &DistGraph, target: &BuildStep) -> Result<()> {
     match target {
         BuildStep::Cargo(target) => build_cargo_target(dist_graph, target),
+        BuildStep::Rustup(cmd) => rustup_toolchain(dist_graph, cmd),
         BuildStep::CopyFile(CopyFileStep {
             src_path,
             dest_path,
@@ -376,6 +377,23 @@ fn build_cargo_target(dist_graph: &DistGraph, target: &CargoBuildStep) -> Result
     Ok(())
 }
 
+/// Build a cargo target
+fn rustup_toolchain(_dist_graph: &DistGraph, cmd: &RustupStep) -> Result<()> {
+    eprintln!("running rustup to ensure you have {} installed", cmd.target);
+    let status = Command::new(&cmd.rustup.cmd)
+        .arg("target")
+        .arg("add")
+        .arg(&cmd.target)
+        .status()
+        .into_diagnostic()
+        .wrap_err("Failed to install rustup toolchain")?;
+
+    if !status.success() {
+        return Err(miette!("Failed to install rustup toolchain"));
+    }
+    Ok(())
+}
+
 /// Initialize the dir for an artifact (and delete the old artifact file).
 fn init_artifact_dir(_dist: &DistGraph, artifact: &Artifact) -> Result<()> {
     // Delete any existing bundle
diff --git a/cargo-dist/src/main.rs b/cargo-dist/src/main.rs
index b52f045e..d4d6cb21 100644
--- a/cargo-dist/src/main.rs
+++ b/cargo-dist/src/main.rs
@@ -284,17 +284,11 @@ fn cmd_init(cli: &Cli, _args: &InitArgs) -> Result<(), miette::Report> {
 }
 
 fn cmd_generate_ci(cli: &Cli, _args: &GenerateCiArgs) -> Result<(), miette::Report> {
-    // This command is more automagic, so provide default targets if none are chosen
-    let targets = if cli.target.is_empty() {
-        default_desktop_targets()
-    } else {
-        cli.target.clone()
-    };
     let config = cargo_dist::Config {
         needs_coherent_announcement_tag: false,
         artifact_mode: cargo_dist::ArtifactMode::All,
         no_local_paths: cli.no_local_paths,
-        targets,
+        targets: cli.target.clone(),
         ci: cli.ci.iter().map(|ci| ci.to_lib()).collect(),
         installers: cli.installer.iter().map(|ins| ins.to_lib()).collect(),
         announcement_tag: cli.tag.clone(),
@@ -305,13 +299,15 @@ fn cmd_generate_ci(cli: &Cli, _args: &GenerateCiArgs) -> Result<(), miette::Repo
 
 fn default_desktop_targets() -> Vec<String> {
     vec![
+        // Everyone can build x64!
         "x86_64-unknown-linux-gnu".to_owned(),
         "x86_64-apple-darwin".to_owned(),
         "x86_64-pc-windows-msvc".to_owned(),
-        // cross-compiles not yet supported
+        // Apple is really easy to cross from Apple
+        "aarch64-apple-darwin".to_owned(),
+        // other cross-compiles not yet supported
         // "aarch64-gnu-unknown-linux".to_owned(),
         // "aarch64-pc-windows-msvc".to_owned(),
-        // "aarch64-apple-darwin".to_owned(),
     ]
 }
 
diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs
index 25df4aa0..337b728b 100644
--- a/cargo-dist/src/tasks.rs
+++ b/cargo-dist/src/tasks.rs
@@ -339,6 +339,9 @@ pub struct DistGraph {
     /// The desired rust toolchain for handling this project
     pub desired_rust_toolchain: Option<String>,
     /// The desired ci backends for this project
+    pub tools: Tools,
+    /// The target triple of the current system
+    pub host_target: String,
 
     /// Styles of CI we want to support
     pub ci_style: Vec<CiStyle>,
@@ -369,6 +372,22 @@ pub struct DistGraph {
     pub releases: Vec<Release>,
 }
 
+/// Various tools we have found installed on the system
+#[derive(Debug, Clone, Default)]
+pub struct Tools {
+    /// rustup, useful for getting specific toolchains
+    pub rustup: Option<Tool>,
+}
+
+/// A tool we have found installed on the system
+#[derive(Debug, Clone, Default)]
+pub struct Tool {
+    /// The string to pass to Command::new
+    pub cmd: String,
+    /// The version the tool reported (in case useful)
+    pub version: String,
+}
+
 /// A binary we want to build (specific to a Variant)
 #[derive(Debug)]
 pub struct Binary {
@@ -399,6 +418,8 @@ pub struct Binary {
 pub enum BuildStep {
     /// Do a cargo build (and copy the outputs to various locations)
     Cargo(CargoBuildStep),
+    /// Run rustup to get a toolchain
+    Rustup(RustupStep),
     /// Copy a file
     CopyFile(CopyFileStep),
     /// Copy a dir
@@ -428,6 +449,15 @@ pub struct CargoBuildStep {
     pub expected_binaries: Vec<BinaryIdx>,
 }
 
+/// A cargo build (and copy the outputs to various locations)
+#[derive(Debug)]
+pub struct RustupStep {
+    /// The rustup to invoke (mostly here to prove you Have rustup)
+    pub rustup: Tool,
+    /// The target to install
+    pub target: String,
+}
+
 /// zip/tarball some directory
 #[derive(Debug)]
 pub struct ZipDirStep {
@@ -805,6 +835,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
         cargo: String,
         workspace: &'pkg_graph WorkspaceInfo<'pkg_graph>,
         artifact_mode: ArtifactMode,
+        host_target: String,
     ) -> Self {
         let target_dir = workspace.info.target_directory().to_owned();
         let workspace_dir = workspace.info.root().to_owned();
@@ -819,6 +850,8 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
                 dist_dir,
                 desired_cargo_dist_version: workspace.desired_cargo_dist_version.clone(),
                 desired_rust_toolchain: workspace.desired_rust_toolchain.clone(),
+                tools: Tools::default(),
+                host_target,
                 announcement_tag: None,
                 announcement_is_prerelease: false,
                 announcement_changelog: None,
@@ -1248,7 +1281,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
 
         let mut build_steps = vec![];
         let cargo_builds = self.compute_cargo_builds();
-        build_steps.extend(cargo_builds.into_iter().map(BuildStep::Cargo));
+        build_steps.extend(cargo_builds.into_iter());
 
         for artifact in &self.inner.artifacts {
             match &artifact.kind {
@@ -1301,7 +1334,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
         self.inner.build_steps = build_steps;
     }
 
-    fn compute_cargo_builds(&mut self) -> Vec<CargoBuildStep> {
+    fn compute_cargo_builds(&mut self) -> Vec<BuildStep> {
         // For now we can be really simplistic and just do a workspace build for every
         // target-triple we have a binary-that-needs-a-real-build for.
         let mut targets = SortedMap::<TargetTriple, Vec<BinaryIdx>>::new();
@@ -1332,7 +1365,22 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
                 rustflags.push_str(" -Ctarget-feature=+crt-static");
             }
 
-            builds.push(CargoBuildStep {
+            // If we're trying to cross-compile on macOS, ensure the rustup toolchain
+            // is setup!
+            if target.ends_with("apple-darwin")
+                && self.inner.host_target.ends_with("apple-darwin")
+                && target != self.inner.host_target
+            {
+                if let Some(rustup) = self.inner.tools.rustup.clone() {
+                    builds.push(BuildStep::Rustup(RustupStep {
+                        rustup,
+                        target: target.clone(),
+                    }));
+                } else {
+                    warn!("You're trying to cross-compile on macOS, but I can't find rustup to ensure you have the rust toolchains for it!")
+                }
+            }
+            builds.push(BuildStep::Cargo(CargoBuildStep {
                 target_triple: target.clone(),
                 // Just build the whole workspace for now
                 package: CargoTargetPackages::Workspace,
@@ -1344,7 +1392,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
                 rustflags,
                 profile: String::from(PROFILE_DIST),
                 expected_binaries: binaries,
-            });
+            }));
         }
         builds
     }
@@ -1522,9 +1570,6 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
         self.inner.announcement_github_body = Some(gh_body);
     }
 
-    fn cargo(&self) -> &str {
-        &self.inner.cargo
-    }
     fn workspace(&self) -> &'pkg_graph WorkspaceInfo<'pkg_graph> {
         self.workspace
     }
@@ -1595,7 +1640,9 @@ pub fn gather_work(cfg: &Config) -> Result<DistGraph> {
     let cargo = cargo()?;
     let package_graph = package_graph(&cargo)?;
     let workspace = workspace_info(&package_graph)?;
-    let mut graph = DistGraphBuilder::new(cargo, &workspace, cfg.artifact_mode);
+    let host_target = get_host_target(&cargo)?;
+    let mut graph = DistGraphBuilder::new(cargo, &workspace, cfg.artifact_mode, host_target);
+    graph.inner.tools = tool_info();
 
     // First thing's first: if they gave us an announcement tag then we should try to parse it
     let mut announcing_package = None;
@@ -1643,7 +1690,7 @@ pub fn gather_work(cfg: &Config) -> Result<DistGraph> {
     }
 
     // If no targets were specified, just use the host target
-    let host_target_triple = [get_host_target(graph.cargo())?];
+    let host_target_triple = [graph.inner.host_target.clone()];
     // If all targets specified, union together the targets our packages support
     // Note that this uses BTreeSet as an intermediate to make the order stable
     let all_target_triples = graph
@@ -2332,3 +2379,19 @@ fn try_extract_changelog_normalized(
 
     Some((title.trim().to_string(), release_notes.notes.to_string()))
 }
+
+fn tool_info() -> Tools {
+    Tools {
+        rustup: find_tool("rustup"),
+    }
+}
+
+fn find_tool(name: &str) -> Option<Tool> {
+    let output = Command::new(name).arg("-V").output().ok()?;
+    let string_output = String::from_utf8(output.stdout).ok()?;
+    let version = string_output.lines().next()?;
+    Some(Tool {
+        cmd: name.to_owned(),
+        version: version.to_owned(),
+    })
+}


Test patch:
diff --git a/cargo-dist/tests/snapshots/cli_tests__manifest.snap b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
index 1a6c255f..05a2b3ab 100644
--- a/cargo-dist/tests/snapshots/cli_tests__manifest.snap
+++ b/cargo-dist/tests/snapshots/cli_tests__manifest.snap
@@ -1,5 +1,6 @@
 ---
 source: cargo-dist/tests/cli-tests.rs
+assertion_line: 92
 expression: format_outputs(&output)
 ---
 stdout:
@@ -17,6 +18,7 @@ stdout:
       "artifacts": [
         "cargo-dist-v1.0.0-FAKEVERSION-installer.sh",
         "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1",
+        "cargo-dist-v1.0.0-FAKEVERSION-aarch64-apple-darwin.tar.xz",
         "cargo-dist-v1.0.0-FAKEVERSION-x86_64-apple-darwin.tar.xz",
         "cargo-dist-v1.0.0-FAKEVERSION-x86_64-pc-windows-msvc.zip",
         "cargo-dist-v1.0.0-FAKEVERSION-x86_64-unknown-linux-gnu.tar.xz"
@@ -24,6 +26,40 @@ stdout:
     }
   ],
   "artifacts": {
+    "cargo-dist-v1.0.0-FAKEVERSION-aarch64-apple-darwin.tar.xz": {
+      "name": "cargo-dist-v1.0.0-FAKEVERSION-aarch64-apple-darwin.tar.xz",
+      "kind": "executable-zip",
+      "target_triples": [
+        "aarch64-apple-darwin"
+      ],
+      "assets": [
+        {
+          "name": "CHANGELOG.md",
+          "path": "CHANGELOG.md",
+          "kind": "changelog"
+        },
+        {
+          "name": "LICENSE-APACHE",
+          "path": "LICENSE-APACHE",
+          "kind": "license"
+        },
+        {
+          "name": "LICENSE-MIT",
+          "path": "LICENSE-MIT",
+          "kind": "license"
+        },
+        {
+          "name": "README.md",
+          "path": "README.md",
+          "kind": "readme"
+        },
+        {
+          "name": "cargo-dist",
+          "path": "cargo-dist",
+          "kind": "executable"
+        }
+      ]
+    },
     "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1": {
       "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.ps1",
       "kind": "installer",
@@ -37,6 +73,7 @@ stdout:
       "name": "cargo-dist-v1.0.0-FAKEVERSION-installer.sh",
       "kind": "installer",
       "target_triples": [
+        "aarch64-apple-darwin",
         "x86_64-apple-darwin",
         "x86_64-unknown-linux-gnu"
       ],