1use std::ops::{Deref, DerefMut};
2use std::path::PathBuf;
3#[cfg(feature = "universal2")]
4use std::process::Stdio;
5use std::process::{self, Child, Command};
6
7use anyhow::{Context, Result};
8use clap::Parser;
9
10use crate::zig::Zig;
11
12#[derive(Clone, Debug, Default, Parser)]
15#[command(
16 after_help = "Run `cargo help build` for more detailed information.",
17 display_order = 1
18)]
19pub struct Build {
20 #[command(flatten)]
21 pub cargo: cargo_options::Build,
22
23 #[arg(skip)]
25 pub disable_zig_linker: bool,
26
27 #[arg(skip)]
29 pub enable_zig_ar: bool,
30}
31
32impl Build {
33 #[allow(clippy::field_reassign_with_default)]
35 pub fn new(manifest_path: Option<PathBuf>) -> Self {
36 let mut build = Self::default();
37 build.manifest_path = manifest_path;
38 build
39 }
40
41 pub fn execute(&self) -> Result<()> {
43 let has_universal2 = self
44 .cargo
45 .target
46 .contains(&"universal2-apple-darwin".to_string());
47 let mut build = self.build_command()?;
48 let mut child = build.spawn().context("Failed to run cargo build")?;
49 if has_universal2 {
50 self.handle_universal2_build(child)?;
51 } else {
52 let status = child.wait().expect("Failed to wait on cargo build process");
53 if !status.success() {
54 process::exit(status.code().unwrap_or(1));
55 }
56 }
57 Ok(())
58 }
59
60 #[cfg(not(feature = "universal2"))]
61 fn handle_universal2_build(&self, mut _child: Child) -> Result<()> {
62 anyhow::bail!("Unsupported Rust target: universal2-apple-darwin")
63 }
64
65 #[cfg(feature = "universal2")]
66 fn handle_universal2_build(&self, mut child: Child) -> Result<()> {
67 use cargo_metadata::Message;
68 use std::io::BufReader;
69 use std::path::Path;
70
71 let manifest_path = self
73 .manifest_path
74 .as_deref()
75 .unwrap_or_else(|| Path::new("Cargo.toml"));
76 let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
77 metadata_cmd.manifest_path(manifest_path);
78 let metadata = metadata_cmd.exec()?;
79 let member_ids: std::collections::HashSet<_> =
80 metadata.workspace_members.iter().collect();
81
82 let mut x86_64_artifacts = Vec::new();
83 let mut aarch64_artifacts = Vec::new();
84
85 let stream = child
86 .stdout
87 .take()
88 .expect("Cargo build should have a stdout");
89 for message in Message::parse_stream(BufReader::new(stream)) {
90 let message = message.context("Failed to parse cargo metadata message")?;
91 match message {
92 Message::CompilerArtifact(artifact) => {
93 if member_ids.contains(&artifact.package_id) {
94 for filename in artifact.filenames {
95 if filename.as_str().contains("x86_64-apple-darwin") {
96 x86_64_artifacts.push(filename);
97 } else if filename.as_str().contains("aarch64-apple-darwin") {
98 aarch64_artifacts.push(filename);
99 }
100 }
101 }
102 }
103 Message::CompilerMessage(msg) => {
104 println!("{}", msg.message);
105 }
106 _ => {}
107 }
108 }
109 let status = child.wait().expect("Failed to wait on cargo build process");
110 if !status.success() {
111 process::exit(status.code().unwrap_or(1));
112 }
113 for (x86_64_path, aarch64_path) in x86_64_artifacts
115 .into_iter()
116 .zip(aarch64_artifacts.into_iter())
117 {
118 let mut fat = fat_macho::FatWriter::new();
119 match fat.add(fs_err::read(&x86_64_path)?) {
120 Err(fat_macho::Error::InvalidMachO(_)) => continue,
121 Err(e) => return Err(e)?,
122 Ok(()) => {}
123 }
124 match fat.add(fs_err::read(&aarch64_path)?) {
125 Err(fat_macho::Error::InvalidMachO(_)) => continue,
126 Err(e) => return Err(e)?,
127 Ok(()) => {}
128 }
129 let universal2_path = PathBuf::from(
130 x86_64_path
131 .to_string()
132 .replace("x86_64-apple-darwin", "universal2-apple-darwin"),
133 );
134 let universal2_dir = universal2_path.parent().unwrap();
135 fs_err::create_dir_all(universal2_dir)?;
136 fat.write_to_file(universal2_path)?;
137 }
138 Ok(())
139 }
140
141 #[cfg(not(feature = "universal2"))]
143 pub fn build_command(&self) -> Result<Command> {
144 let mut build = self.cargo.command();
145 if !self.disable_zig_linker {
146 Zig::apply_command_env(
147 self.manifest_path.as_deref(),
148 self.release,
149 &self.cargo.common,
150 &mut build,
151 self.enable_zig_ar,
152 )?;
153 }
154 Ok(build)
155 }
156
157 #[cfg(feature = "universal2")]
159 pub fn build_command(&self) -> Result<Command> {
160 let build = if let Some(index) = self
161 .cargo
162 .target
163 .iter()
164 .position(|t| t == "universal2-apple-darwin")
165 {
166 let mut cargo = self.cargo.clone();
167 cargo.target.remove(index);
168 if !cargo.target.contains(&"x86_64-apple-darwin".to_string()) {
169 cargo.target.push("x86_64-apple-darwin".to_string());
170 }
171 if !cargo.target.contains(&"aarch64-apple-darwin".to_string()) {
172 cargo.target.push("aarch64-apple-darwin".to_string());
173 }
174 if !cargo.message_format.iter().any(|f| f.starts_with("json")) {
175 cargo.message_format.push("json".to_string());
176 }
177 let mut build = cargo.command();
178 build.stdout(Stdio::piped()).stderr(Stdio::inherit());
179 if !self.disable_zig_linker {
180 Zig::apply_command_env(
181 self.manifest_path.as_deref(),
182 self.release,
183 &cargo.common,
184 &mut build,
185 self.enable_zig_ar,
186 )?;
187 }
188 build
189 } else {
190 let mut build = self.cargo.command();
191 if !self.disable_zig_linker {
192 Zig::apply_command_env(
193 self.manifest_path.as_deref(),
194 self.release,
195 &self.cargo.common,
196 &mut build,
197 self.enable_zig_ar,
198 )?;
199 }
200 build
201 };
202 Ok(build)
203 }
204}
205
206impl Deref for Build {
207 type Target = cargo_options::Build;
208
209 fn deref(&self) -> &Self::Target {
210 &self.cargo
211 }
212}
213
214impl DerefMut for Build {
215 fn deref_mut(&mut self) -> &mut Self::Target {
216 &mut self.cargo
217 }
218}
219
220impl From<cargo_options::Build> for Build {
221 fn from(cargo: cargo_options::Build) -> Self {
222 Self {
223 cargo,
224 ..Default::default()
225 }
226 }
227}