1mod cargo_env;
2mod cli_config;
3mod linker_args;
4mod locate;
5mod target_info;
6mod wrapper;
7
8use std::env;
9use std::process;
10
11use anyhow::{Context, Result, bail};
12use fs_err as fs;
13use target_lexicon::Architecture;
14
15use cargo_env::{write_file, write_tbd_files};
16use linker_args::{FilteredArg, dedup_apple_link_libs, filter_linker_arg, filter_linker_args};
17use locate::cache_dir;
18use target_info::TargetInfo;
19
20pub use cli_config::CliConfig;
21#[cfg(target_os = "windows")]
22pub use wrapper::adjust_canonicalization;
23pub use wrapper::{ZigWrapper, prepare_zig_linker, prepare_zig_linker_with_cli_config};
24
25#[derive(Clone, Debug, clap::Subcommand)]
27pub enum Zig {
28 #[command(name = "cc")]
30 Cc {
31 #[arg(num_args = 1.., trailing_var_arg = true)]
33 args: Vec<String>,
34 },
35 #[command(name = "c++")]
37 Cxx {
38 #[arg(num_args = 1.., trailing_var_arg = true)]
40 args: Vec<String>,
41 },
42 #[command(name = "ar")]
44 Ar {
45 #[arg(num_args = 1.., trailing_var_arg = true)]
47 args: Vec<String>,
48 },
49 #[command(name = "ranlib")]
51 Ranlib {
52 #[arg(num_args = 1.., trailing_var_arg = true)]
54 args: Vec<String>,
55 },
56 #[command(name = "lib")]
58 Lib {
59 #[arg(num_args = 1.., trailing_var_arg = true)]
61 args: Vec<String>,
62 },
63 #[command(name = "dlltool")]
65 Dlltool {
66 #[arg(num_args = 1.., trailing_var_arg = true)]
68 args: Vec<String>,
69 },
70}
71
72impl Zig {
73 pub fn execute(&self) -> Result<()> {
75 match self {
76 Zig::Cc { args } => self.execute_compiler("cc", args),
77 Zig::Cxx { args } => self.execute_compiler("c++", args),
78 Zig::Ar { args } => self.execute_tool("ar", args),
79 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
80 Zig::Lib { args } => self.execute_compiler("lib", args),
81 Zig::Dlltool { args } => self.execute_dlltool(args),
82 }
83 }
84
85 pub fn execute_dlltool(&self, cmd_args: &[String]) -> Result<()> {
88 let zig_version = Zig::zig_version()?;
89 let needs_filtering = zig_version.major == 0 && zig_version.minor < 12;
90
91 if !needs_filtering {
92 return self.execute_tool("dlltool", cmd_args);
93 }
94
95 let mut filtered_args = Vec::with_capacity(cmd_args.len());
98 let mut skip_next = false;
99 for arg in cmd_args {
100 if skip_next {
101 skip_next = false;
102 continue;
103 }
104 if arg == "--no-leading-underscore" {
105 continue;
106 }
107 if arg == "--temp-prefix" || arg == "-t" {
108 skip_next = true;
110 continue;
111 }
112 if arg.starts_with("--temp-prefix=") || arg.starts_with("-t=") {
114 continue;
115 }
116 filtered_args.push(arg.clone());
117 }
118
119 self.execute_tool("dlltool", &filtered_args)
120 }
121
122 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
124 let target = cmd_args
125 .iter()
126 .position(|x| x == "-target")
127 .and_then(|index| cmd_args.get(index + 1));
128 let target_info = TargetInfo::new(target);
129
130 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
131 Ok(version) => version.parse()?,
132 Err(_) => rustc_version::version()?,
133 };
134 let zig_version = Zig::zig_version()?;
135
136 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
137 let mut skip_next_arg = false;
138 let mut seen_target = false;
139 for arg in cmd_args {
140 if skip_next_arg {
141 skip_next_arg = false;
142 continue;
143 }
144 if arg == "-target" {
148 if seen_target {
149 skip_next_arg = true;
150 continue;
151 }
152 seen_target = true;
153 }
154 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
155 vec![self.process_linker_response_file(
156 arg,
157 &rustc_ver,
158 &zig_version,
159 &target_info,
160 )?]
161 } else {
162 match self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info) {
163 FilteredArg::Keep(filtered) => filtered,
164 FilteredArg::Skip => continue,
165 FilteredArg::SkipWithNext => {
166 skip_next_arg = true;
167 continue;
168 }
169 }
170 };
171 new_cmd_args.extend(args);
172 }
173
174 if target_info.is_apple_platform() {
175 new_cmd_args = dedup_apple_link_libs(new_cmd_args);
176 }
177
178 if target_info.is_mips32() {
179 new_cmd_args.push("-Wl,-z,notext".to_string());
181 }
182
183 if target_info.is_arm() && !cmd_args.iter().any(|x| x == "-c" || x == "-E" || x == "-S") {
187 let cache_dir = cache_dir();
188 fs::create_dir_all(&cache_dir)?;
189 let shim_path = cache_dir.join("aeabi_unaligned.c");
190 write_file(&shim_path, AEABI_UNALIGNED_C)?;
191 new_cmd_args.push(shim_path.display().to_string());
192 }
193
194 if target_info.is_windows_gnu() && (zig_version.major, zig_version.minor) >= (0, 16) {
195 new_cmd_args.push("-lcompiler_rt".to_string());
196 }
197
198 if self.has_undefined_dynamic_lookup(cmd_args) {
199 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
200 }
201 if target_info.is_macos() {
202 if self.should_add_libcharset(cmd_args, &zig_version) {
203 new_cmd_args.push("-lcharset".to_string());
204 }
205 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
206 }
207
208 let mut command = Self::command()?;
211 if (zig_version.major, zig_version.minor) >= (0, 15)
212 && let Some(sdkroot) = Self::macos_sdk_root()
213 {
214 command.env("SDKROOT", sdkroot);
215 }
216
217 let mut child = command
218 .arg(cmd)
219 .args(new_cmd_args)
220 .spawn()
221 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
222 let status = child.wait().expect("Failed to wait on zig child process");
223 if !status.success() {
224 process::exit(status.code().unwrap_or(1));
225 }
226 Ok(())
227 }
228
229 fn process_linker_response_file(
230 &self,
231 arg: &str,
232 rustc_ver: &rustc_version::Version,
233 zig_version: &semver::Version,
234 target_info: &TargetInfo,
235 ) -> Result<String> {
236 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
240 let content = if target_info.is_windows_msvc() {
241 if content_bytes[0..2] != [255, 254] {
242 bail!(
243 "linker response file `{}` didn't start with a utf16 BOM",
244 &arg
245 );
246 }
247 let content_utf16: Vec<u16> = content_bytes[2..]
248 .chunks_exact(2)
249 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
250 .collect();
251 String::from_utf16(&content_utf16).with_context(|| {
252 format!(
253 "linker response file `{}` didn't contain valid utf16 content",
254 &arg
255 )
256 })?
257 } else {
258 String::from_utf8(content_bytes).with_context(|| {
259 format!(
260 "linker response file `{}` didn't contain valid utf8 content",
261 &arg
262 )
263 })?
264 };
265 let mut link_args: Vec<_> = filter_linker_args(
266 content.split('\n').map(|s| s.to_string()),
267 rustc_ver,
268 zig_version,
269 target_info,
270 );
271 if self.has_undefined_dynamic_lookup(&link_args) {
272 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
273 }
274 if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
275 link_args.push("-lcharset".to_string());
276 }
277 if target_info.is_windows_msvc() {
278 let new_content = link_args.join("\n");
279 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
280 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
282 out.push(c as u8);
284 out.push((c >> 8) as u8);
285 }
286 fs::write(arg.trim_start_matches('@'), out)?;
287 } else {
288 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
289 }
290 Ok(arg.to_string())
291 }
292
293 fn filter_linker_arg(
294 &self,
295 arg: &str,
296 rustc_ver: &rustc_version::Version,
297 zig_version: &semver::Version,
298 target_info: &TargetInfo,
299 ) -> FilteredArg {
300 filter_linker_arg(arg, rustc_ver, zig_version, target_info)
301 }
302
303 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
304 let undefined = args
305 .iter()
306 .position(|x| x == "-undefined")
307 .and_then(|i| args.get(i + 1));
308 matches!(undefined, Some(x) if x == "dynamic_lookup")
309 }
310
311 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
312 if (zig_version.major, zig_version.minor) >= (0, 12) {
314 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
315 } else {
316 false
317 }
318 }
319
320 fn add_macos_specific_args(
321 &self,
322 new_cmd_args: &mut Vec<String>,
323 zig_version: &semver::Version,
324 ) -> Result<()> {
325 let sdkroot = Self::macos_sdk_root();
326 if (zig_version.major, zig_version.minor) >= (0, 12) {
327 if let Some(ref sdkroot) = sdkroot
331 && (zig_version.major, zig_version.minor) < (0, 15)
332 {
333 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
334 }
335 }
337 if let Some(ref sdkroot) = sdkroot {
338 if (zig_version.major, zig_version.minor) < (0, 15) {
339 new_cmd_args.extend_from_slice(&[
341 "-isystem".to_string(),
342 format!("{}", sdkroot.join("usr").join("include").display()),
343 format!("-L{}", sdkroot.join("usr").join("lib").display()),
344 format!(
345 "-F{}",
346 sdkroot
347 .join("System")
348 .join("Library")
349 .join("Frameworks")
350 .display()
351 ),
352 "-DTARGET_OS_IPHONE=0".to_string(),
353 ]);
354 } else {
355 new_cmd_args.extend_from_slice(&[
358 "-isystem".to_string(),
359 format!("{}", sdkroot.join("usr").join("include").display()),
360 format!("-L{}", sdkroot.join("usr").join("lib").display()),
361 format!(
362 "-F{}",
363 sdkroot
364 .join("System")
365 .join("Library")
366 .join("Frameworks")
367 .display()
368 ),
369 "-iframework".to_string(),
371 format!(
372 "{}",
373 sdkroot
374 .join("System")
375 .join("Library")
376 .join("Frameworks")
377 .display()
378 ),
379 "-DTARGET_OS_IPHONE=0".to_string(),
380 ]);
381 }
382 }
383
384 let cache_dir = cache_dir();
386 let deps_dir = cache_dir.join("deps");
387 fs::create_dir_all(&deps_dir)?;
388 write_tbd_files(&deps_dir)?;
389 new_cmd_args.push("-L".to_string());
390 new_cmd_args.push(format!("{}", deps_dir.display()));
391 Ok(())
392 }
393
394 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
396 let mut child = Self::command()?
397 .arg(cmd)
398 .args(cmd_args)
399 .spawn()
400 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
401 let status = child.wait().expect("Failed to wait on zig child process");
402 if !status.success() {
403 process::exit(status.code().unwrap_or(1));
404 }
405 Ok(())
406 }
407}
408
409const AEABI_UNALIGNED_C: &str = r#"
414#ifdef __cplusplus
415extern "C" {
416#endif
417__attribute__((weak)) int __aeabi_uread4(void *address) {
418 int value;
419 __builtin_memcpy(&value, address, 4);
420 return value;
421}
422__attribute__((weak)) int __aeabi_uwrite4(int value, void *address) {
423 __builtin_memcpy(address, &value, 4);
424 return value;
425}
426__attribute__((weak)) long long __aeabi_uread8(void *address) {
427 long long value;
428 __builtin_memcpy(&value, address, 8);
429 return value;
430}
431__attribute__((weak)) long long __aeabi_uwrite8(long long value, void *address) {
432 __builtin_memcpy(address, &value, 8);
433 return value;
434}
435#ifdef __cplusplus
436}
437#endif
438"#;
439
440pub(crate) fn get_dlltool_name(arch: &Architecture) -> &'static str {
444 if cfg!(windows) {
445 "dlltool"
446 } else {
447 match arch {
448 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
449 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
450 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
451 _ => "dlltool",
452 }
453 }
454}
455
456pub(crate) fn has_system_dlltool(arch: &Architecture) -> bool {
459 which::which(get_dlltool_name(arch)).is_ok()
460}