1use std::env;
2use std::ffi::OsStr;
3#[cfg(target_family = "unix")]
4use std::fs::OpenOptions;
5use std::io::Write;
6#[cfg(target_family = "unix")]
7use std::os::unix::fs::OpenOptionsExt;
8use std::path::{Path, PathBuf};
9use std::process::{self, Command};
10use std::str;
11use std::sync::OnceLock;
12
13use anyhow::{Context, Result, anyhow, bail};
14use fs_err as fs;
15use path_slash::PathBufExt;
16use serde::Deserialize;
17use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
18
19use crate::linux::ARM_FEATURES_H;
20use crate::macos::{LIBCHARSET_TBD, LIBICONV_TBD};
21
22#[derive(Clone, Debug, clap::Subcommand)]
24pub enum Zig {
25 #[command(name = "cc")]
27 Cc {
28 #[arg(num_args = 1.., trailing_var_arg = true)]
30 args: Vec<String>,
31 },
32 #[command(name = "c++")]
34 Cxx {
35 #[arg(num_args = 1.., trailing_var_arg = true)]
37 args: Vec<String>,
38 },
39 #[command(name = "ar")]
41 Ar {
42 #[arg(num_args = 1.., trailing_var_arg = true)]
44 args: Vec<String>,
45 },
46 #[command(name = "ranlib")]
48 Ranlib {
49 #[arg(num_args = 1.., trailing_var_arg = true)]
51 args: Vec<String>,
52 },
53 #[command(name = "lib")]
55 Lib {
56 #[arg(num_args = 1.., trailing_var_arg = true)]
58 args: Vec<String>,
59 },
60 #[command(name = "dlltool")]
62 Dlltool {
63 #[arg(num_args = 1.., trailing_var_arg = true)]
65 args: Vec<String>,
66 },
67}
68
69struct TargetInfo {
70 target: Option<String>,
71}
72
73impl TargetInfo {
74 fn new(target: Option<&String>) -> Self {
75 Self {
76 target: target.cloned(),
77 }
78 }
79
80 fn is_arm(&self) -> bool {
82 self.target
83 .as_ref()
84 .map(|x| x.starts_with("arm"))
85 .unwrap_or_default()
86 }
87
88 fn is_aarch64(&self) -> bool {
89 self.target
90 .as_ref()
91 .map(|x| x.starts_with("aarch64"))
92 .unwrap_or_default()
93 }
94
95 fn is_aarch64_be(&self) -> bool {
96 self.target
97 .as_ref()
98 .map(|x| x.starts_with("aarch64_be"))
99 .unwrap_or_default()
100 }
101
102 fn is_i386(&self) -> bool {
103 self.target
104 .as_ref()
105 .map(|x| x.starts_with("i386"))
106 .unwrap_or_default()
107 }
108
109 fn is_i686(&self) -> bool {
110 self.target
111 .as_ref()
112 .map(|x| x.starts_with("i686") || x.starts_with("x86-"))
113 .unwrap_or_default()
114 }
115
116 fn is_riscv64(&self) -> bool {
117 self.target
118 .as_ref()
119 .map(|x| x.starts_with("riscv64"))
120 .unwrap_or_default()
121 }
122
123 fn is_riscv32(&self) -> bool {
124 self.target
125 .as_ref()
126 .map(|x| x.starts_with("riscv32"))
127 .unwrap_or_default()
128 }
129
130 fn is_mips32(&self) -> bool {
131 self.target
132 .as_ref()
133 .map(|x| x.starts_with("mips") && !x.starts_with("mips64"))
134 .unwrap_or_default()
135 }
136
137 fn is_musl(&self) -> bool {
139 self.target
140 .as_ref()
141 .map(|x| x.contains("musl"))
142 .unwrap_or_default()
143 }
144
145 fn is_macos(&self) -> bool {
147 self.target
148 .as_ref()
149 .map(|x| x.contains("macos"))
150 .unwrap_or_default()
151 }
152
153 fn is_darwin(&self) -> bool {
154 self.target
155 .as_ref()
156 .map(|x| x.contains("darwin"))
157 .unwrap_or_default()
158 }
159
160 fn is_apple_platform(&self) -> bool {
161 self.target
162 .as_ref()
163 .map(|x| {
164 x.contains("macos")
165 || x.contains("darwin")
166 || x.contains("ios")
167 || x.contains("tvos")
168 || x.contains("watchos")
169 || x.contains("visionos")
170 })
171 .unwrap_or_default()
172 }
173
174 fn is_ios(&self) -> bool {
175 self.target
176 .as_ref()
177 .map(|x| x.contains("ios") && !x.contains("visionos"))
178 .unwrap_or_default()
179 }
180
181 fn is_tvos(&self) -> bool {
182 self.target
183 .as_ref()
184 .map(|x| x.contains("tvos"))
185 .unwrap_or_default()
186 }
187
188 fn is_watchos(&self) -> bool {
189 self.target
190 .as_ref()
191 .map(|x| x.contains("watchos"))
192 .unwrap_or_default()
193 }
194
195 fn is_visionos(&self) -> bool {
196 self.target
197 .as_ref()
198 .map(|x| x.contains("visionos"))
199 .unwrap_or_default()
200 }
201
202 fn apple_cpu(&self) -> &'static str {
204 if self.is_macos() || self.is_darwin() {
205 "apple_m1" } else if self.is_visionos() {
207 "apple_m2" } else if self.is_watchos() {
209 "apple_s5" } else if self.is_ios() || self.is_tvos() {
211 "apple_a14" } else {
213 "generic"
214 }
215 }
216
217 fn is_freebsd(&self) -> bool {
218 self.target
219 .as_ref()
220 .map(|x| x.contains("freebsd"))
221 .unwrap_or_default()
222 }
223
224 fn is_windows_gnu(&self) -> bool {
225 self.target
226 .as_ref()
227 .map(|x| x.contains("windows-gnu"))
228 .unwrap_or_default()
229 }
230
231 fn is_windows_msvc(&self) -> bool {
232 self.target
233 .as_ref()
234 .map(|x| x.contains("windows-msvc"))
235 .unwrap_or_default()
236 }
237
238 fn is_ohos(&self) -> bool {
239 self.target
240 .as_ref()
241 .map(|x| x.contains("ohos"))
242 .unwrap_or_default()
243 }
244}
245
246impl Zig {
247 pub fn execute(&self) -> Result<()> {
249 match self {
250 Zig::Cc { args } => self.execute_compiler("cc", args),
251 Zig::Cxx { args } => self.execute_compiler("c++", args),
252 Zig::Ar { args } => self.execute_tool("ar", args),
253 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
254 Zig::Lib { args } => self.execute_compiler("lib", args),
255 Zig::Dlltool { args } => self.execute_dlltool(args),
256 }
257 }
258
259 pub fn execute_dlltool(&self, cmd_args: &[String]) -> Result<()> {
262 let zig_version = Zig::zig_version()?;
263 let needs_filtering = zig_version.major == 0 && zig_version.minor < 12;
264
265 if !needs_filtering {
266 return self.execute_tool("dlltool", cmd_args);
267 }
268
269 let mut filtered_args = Vec::with_capacity(cmd_args.len());
272 let mut skip_next = false;
273 for arg in cmd_args {
274 if skip_next {
275 skip_next = false;
276 continue;
277 }
278 if arg == "--no-leading-underscore" {
279 continue;
280 }
281 if arg == "--temp-prefix" || arg == "-t" {
282 skip_next = true;
284 continue;
285 }
286 if arg.starts_with("--temp-prefix=") || arg.starts_with("-t=") {
288 continue;
289 }
290 filtered_args.push(arg.clone());
291 }
292
293 self.execute_tool("dlltool", &filtered_args)
294 }
295
296 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
298 let target = cmd_args
299 .iter()
300 .position(|x| x == "-target")
301 .and_then(|index| cmd_args.get(index + 1));
302 let target_info = TargetInfo::new(target);
303
304 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
305 Ok(version) => version.parse()?,
306 Err(_) => rustc_version::version()?,
307 };
308 let zig_version = Zig::zig_version()?;
309
310 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
311 let mut skip_next_arg = false;
312 for arg in cmd_args {
313 if skip_next_arg {
314 skip_next_arg = false;
315 continue;
316 }
317 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
318 vec![self.process_linker_response_file(
319 arg,
320 &rustc_ver,
321 &zig_version,
322 &target_info,
323 )?]
324 } else {
325 match self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info) {
326 FilteredArg::Keep(filtered) => filtered,
327 FilteredArg::Skip => continue,
328 FilteredArg::SkipWithNext => {
329 skip_next_arg = true;
330 continue;
331 }
332 }
333 };
334 new_cmd_args.extend(args);
335 }
336
337 if target_info.is_mips32() {
338 new_cmd_args.push("-Wl,-z,notext".to_string());
340 }
341
342 if self.has_undefined_dynamic_lookup(cmd_args) {
343 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
344 }
345 if target_info.is_macos() {
346 if self.should_add_libcharset(cmd_args, &zig_version) {
347 new_cmd_args.push("-lcharset".to_string());
348 }
349 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
350 }
351
352 let mut command = Self::command()?;
355 if (zig_version.major, zig_version.minor) >= (0, 15)
356 && let Some(sdkroot) = Self::macos_sdk_root()
357 {
358 command.env("SDKROOT", sdkroot);
359 }
360
361 let mut child = command
362 .arg(cmd)
363 .args(new_cmd_args)
364 .spawn()
365 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
366 let status = child.wait().expect("Failed to wait on zig child process");
367 if !status.success() {
368 process::exit(status.code().unwrap_or(1));
369 }
370 Ok(())
371 }
372
373 fn process_linker_response_file(
374 &self,
375 arg: &str,
376 rustc_ver: &rustc_version::Version,
377 zig_version: &semver::Version,
378 target_info: &TargetInfo,
379 ) -> Result<String> {
380 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
384 let content = if target_info.is_windows_msvc() {
385 if content_bytes[0..2] != [255, 254] {
386 bail!(
387 "linker response file `{}` didn't start with a utf16 BOM",
388 &arg
389 );
390 }
391 let content_utf16: Vec<u16> = content_bytes[2..]
392 .chunks_exact(2)
393 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
394 .collect();
395 String::from_utf16(&content_utf16).with_context(|| {
396 format!(
397 "linker response file `{}` didn't contain valid utf16 content",
398 &arg
399 )
400 })?
401 } else {
402 String::from_utf8(content_bytes).with_context(|| {
403 format!(
404 "linker response file `{}` didn't contain valid utf8 content",
405 &arg
406 )
407 })?
408 };
409 let mut link_args: Vec<_> = filter_linker_args(
410 content.split('\n').map(|s| s.to_string()),
411 rustc_ver,
412 zig_version,
413 target_info,
414 );
415 if self.has_undefined_dynamic_lookup(&link_args) {
416 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
417 }
418 if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
419 link_args.push("-lcharset".to_string());
420 }
421 if target_info.is_windows_msvc() {
422 let new_content = link_args.join("\n");
423 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
424 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
426 out.push(c as u8);
428 out.push((c >> 8) as u8);
429 }
430 fs::write(arg.trim_start_matches('@'), out)?;
431 } else {
432 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
433 }
434 Ok(arg.to_string())
435 }
436
437 fn filter_linker_arg(
438 &self,
439 arg: &str,
440 rustc_ver: &rustc_version::Version,
441 zig_version: &semver::Version,
442 target_info: &TargetInfo,
443 ) -> FilteredArg {
444 filter_linker_arg(arg, rustc_ver, zig_version, target_info)
445 }
446}
447
448enum FilteredArg {
449 Keep(Vec<String>),
450 Skip,
451 SkipWithNext,
452}
453
454fn filter_linker_args(
455 args: impl IntoIterator<Item = String>,
456 rustc_ver: &rustc_version::Version,
457 zig_version: &semver::Version,
458 target_info: &TargetInfo,
459) -> Vec<String> {
460 let mut result = Vec::new();
461 let mut skip_next = false;
462 for arg in args {
463 if skip_next {
464 skip_next = false;
465 continue;
466 }
467 match filter_linker_arg(&arg, rustc_ver, zig_version, target_info) {
468 FilteredArg::Keep(filtered) => result.extend(filtered),
469 FilteredArg::Skip => {}
470 FilteredArg::SkipWithNext => {
471 skip_next = true;
472 }
473 }
474 }
475 result
476}
477
478fn filter_linker_arg(
479 arg: &str,
480 rustc_ver: &rustc_version::Version,
481 zig_version: &semver::Version,
482 target_info: &TargetInfo,
483) -> FilteredArg {
484 if arg == "-lgcc_s" {
485 return FilteredArg::Keep(vec!["-lunwind".to_string()]);
486 } else if arg.starts_with("--target=") {
487 return FilteredArg::Skip;
488 } else if arg.starts_with("-e") && arg.len() > 2 && !arg.starts_with("-export") {
489 let entry = &arg[2..];
490 return FilteredArg::Keep(vec![format!("-Wl,--entry={}", entry)]);
491 }
492 if (target_info.is_arm() || target_info.is_windows_gnu())
493 && arg.ends_with(".rlib")
494 && arg.contains("libcompiler_builtins-")
495 {
496 return FilteredArg::Skip;
497 }
498 if target_info.is_windows_gnu() {
499 #[allow(clippy::if_same_then_else)]
500 if arg == "-lgcc_eh"
501 && ((zig_version.major, zig_version.minor) < (0, 14) || target_info.is_i686())
502 {
503 return FilteredArg::Keep(vec!["-lc++".to_string()]);
504 } else if arg.ends_with("rsbegin.o") || arg.ends_with("rsend.o") {
505 if target_info.is_i686() {
506 return FilteredArg::Skip;
507 }
508 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
509 return FilteredArg::Keep(vec!["-Wl,-search_paths_first".to_owned()]);
510 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
511 return FilteredArg::Skip;
512 } else if arg == "-Wl,--disable-auto-image-base"
513 || arg == "-Wl,--dynamicbase"
514 || arg == "-Wl,--large-address-aware"
515 || (arg.starts_with("-Wl,")
516 && (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
517 {
518 return FilteredArg::Skip;
519 } else if arg == "-lmsvcrt" {
520 return FilteredArg::Skip;
521 }
522 } else if arg == "-Wl,--no-undefined-version"
523 || arg == "-Wl,-znostart-stop-gc"
524 || arg.starts_with("-Wl,-plugin-opt")
525 {
526 return FilteredArg::Skip;
527 }
528 if target_info.is_musl() || target_info.is_ohos() {
529 if (arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt"))
530 || arg == "-Wl,-melf_i386"
531 {
532 return FilteredArg::Skip;
533 }
534 if rustc_ver.major == 1
535 && rustc_ver.minor < 59
536 && arg.ends_with(".rlib")
537 && arg.contains("liblibc-")
538 {
539 return FilteredArg::Skip;
540 }
541 if arg == "-lc" {
542 return FilteredArg::Skip;
543 }
544 }
545 if arg.starts_with("-Wp,")
549 && !arg.starts_with("-Wp,-MD")
550 && !arg.starts_with("-Wp,-MMD")
551 && !arg.starts_with("-Wp,-MT")
552 {
553 return FilteredArg::Skip;
554 }
555 if arg.starts_with("-march=") {
556 if target_info.is_arm() || target_info.is_i386() {
557 return FilteredArg::Skip;
558 } else if target_info.is_riscv64() {
559 return FilteredArg::Keep(vec!["-march=generic_rv64".to_string()]);
560 } else if target_info.is_riscv32() {
561 return FilteredArg::Keep(vec!["-march=generic_rv32".to_string()]);
562 } else if arg.starts_with("-march=armv")
563 && (target_info.is_aarch64() || target_info.is_aarch64_be())
564 {
565 let march_value = arg.strip_prefix("-march=").unwrap();
566 let features = if let Some(pos) = march_value.find('+') {
567 &march_value[pos..]
568 } else {
569 ""
570 };
571 let base_cpu = if target_info.is_apple_platform() {
572 target_info.apple_cpu()
573 } else {
574 "generic"
575 };
576 let mut result = vec![format!("-mcpu={}{}", base_cpu, features)];
577 if features.contains("+crypto") {
578 result.append(&mut vec!["-Xassembler".to_owned(), arg.to_string()]);
579 }
580 return FilteredArg::Keep(result);
581 }
582 }
583 if target_info.is_apple_platform() {
584 if (zig_version.major, zig_version.minor) < (0, 16) {
585 if arg.starts_with("-Wl,-exported_symbols_list,") {
586 return FilteredArg::Skip;
587 }
588 if arg == "-Wl,-exported_symbols_list" {
589 return FilteredArg::SkipWithNext;
590 }
591 }
592 if arg == "-Wl,-dylib" {
593 return FilteredArg::Skip;
594 }
595 }
596 if (zig_version.major, zig_version.minor) < (0, 16) {
598 if arg == "-Wl,-exported_symbols_list" || arg == "-Wl,--dynamic-list" {
599 return FilteredArg::SkipWithNext;
600 }
601 if arg.starts_with("-Wl,-exported_symbols_list,") || arg.starts_with("-Wl,--dynamic-list,")
602 {
603 return FilteredArg::Skip;
604 }
605 }
606 if target_info.is_freebsd() {
607 let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
608 if ignored_libs.contains(&arg) {
609 return FilteredArg::Skip;
610 }
611 }
612 FilteredArg::Keep(vec![arg.to_string()])
613}
614
615impl Zig {
616 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
617 let undefined = args
618 .iter()
619 .position(|x| x == "-undefined")
620 .and_then(|i| args.get(i + 1));
621 matches!(undefined, Some(x) if x == "dynamic_lookup")
622 }
623
624 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
625 if (zig_version.major, zig_version.minor) >= (0, 12) {
627 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
628 } else {
629 false
630 }
631 }
632
633 fn add_macos_specific_args(
634 &self,
635 new_cmd_args: &mut Vec<String>,
636 zig_version: &semver::Version,
637 ) -> Result<()> {
638 let sdkroot = Self::macos_sdk_root();
639 if (zig_version.major, zig_version.minor) >= (0, 12) {
640 if let Some(ref sdkroot) = sdkroot
644 && (zig_version.major, zig_version.minor) < (0, 15)
645 {
646 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
647 }
648 }
650 if let Some(ref sdkroot) = sdkroot {
651 if (zig_version.major, zig_version.minor) < (0, 15) {
652 new_cmd_args.extend_from_slice(&[
654 "-isystem".to_string(),
655 format!("{}", sdkroot.join("usr").join("include").display()),
656 format!("-L{}", sdkroot.join("usr").join("lib").display()),
657 format!(
658 "-F{}",
659 sdkroot
660 .join("System")
661 .join("Library")
662 .join("Frameworks")
663 .display()
664 ),
665 "-DTARGET_OS_IPHONE=0".to_string(),
666 ]);
667 } else {
668 new_cmd_args.extend_from_slice(&[
671 "-isystem".to_string(),
672 format!("{}", sdkroot.join("usr").join("include").display()),
673 format!("-L{}", sdkroot.join("usr").join("lib").display()),
674 format!(
675 "-F{}",
676 sdkroot
677 .join("System")
678 .join("Library")
679 .join("Frameworks")
680 .display()
681 ),
682 "-iframework".to_string(),
684 format!(
685 "{}",
686 sdkroot
687 .join("System")
688 .join("Library")
689 .join("Frameworks")
690 .display()
691 ),
692 "-DTARGET_OS_IPHONE=0".to_string(),
693 ]);
694 }
695 }
696
697 let cache_dir = cache_dir();
699 let deps_dir = cache_dir.join("deps");
700 fs::create_dir_all(&deps_dir)?;
701 write_tbd_files(&deps_dir)?;
702 new_cmd_args.push("-L".to_string());
703 new_cmd_args.push(format!("{}", deps_dir.display()));
704 Ok(())
705 }
706
707 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
709 let mut child = Self::command()?
710 .arg(cmd)
711 .args(cmd_args)
712 .spawn()
713 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
714 let status = child.wait().expect("Failed to wait on zig child process");
715 if !status.success() {
716 process::exit(status.code().unwrap_or(1));
717 }
718 Ok(())
719 }
720
721 pub fn command() -> Result<Command> {
723 let (zig, zig_args) = Self::find_zig()?;
724 let mut cmd = Command::new(zig);
725 cmd.args(zig_args);
726 Ok(cmd)
727 }
728
729 fn zig_version() -> Result<semver::Version> {
730 static ZIG_VERSION: OnceLock<semver::Version> = OnceLock::new();
731
732 if let Some(version) = ZIG_VERSION.get() {
733 return Ok(version.clone());
734 }
735 if let Ok(version_str) = env::var("CARGO_ZIGBUILD_ZIG_VERSION")
737 && let Ok(version) = semver::Version::parse(&version_str)
738 {
739 return Ok(ZIG_VERSION.get_or_init(|| version).clone());
740 }
741 let output = Self::command()?.arg("version").output()?;
742 let version_str =
743 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
744 let version = semver::Version::parse(version_str.trim())?;
745 Ok(ZIG_VERSION.get_or_init(|| version).clone())
746 }
747
748 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
750 static ZIG_PATH: OnceLock<(PathBuf, Vec<String>)> = OnceLock::new();
751
752 if let Some(cached) = ZIG_PATH.get() {
753 return Ok(cached.clone());
754 }
755 let result = Self::find_zig_python()
756 .or_else(|_| Self::find_zig_bin())
757 .context("Failed to find zig")?;
758 Ok(ZIG_PATH.get_or_init(|| result).clone())
759 }
760
761 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
763 let zig_path = zig_path()?;
764 let output = Command::new(&zig_path).arg("version").output()?;
765
766 let version_str = str::from_utf8(&output.stdout).with_context(|| {
767 format!("`{} version` didn't return utf8 output", zig_path.display())
768 })?;
769 Self::validate_zig_version(version_str)?;
770 Ok((zig_path, Vec::new()))
771 }
772
773 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
775 let python_path = python_path()?;
776 let output = Command::new(&python_path)
777 .args(["-m", "ziglang", "version"])
778 .output()?;
779
780 let version_str = str::from_utf8(&output.stdout).with_context(|| {
781 format!(
782 "`{} -m ziglang version` didn't return utf8 output",
783 python_path.display()
784 )
785 })?;
786 Self::validate_zig_version(version_str)?;
787 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
788 }
789
790 fn validate_zig_version(version: &str) -> Result<()> {
791 let min_ver = semver::Version::new(0, 9, 0);
792 let version = semver::Version::parse(version.trim())?;
793 if version >= min_ver {
794 Ok(())
795 } else {
796 bail!(
797 "zig version {} is too old, need at least {}",
798 version,
799 min_ver
800 )
801 }
802 }
803
804 pub fn lib_dir() -> Result<PathBuf> {
806 static LIB_DIR: OnceLock<PathBuf> = OnceLock::new();
807
808 if let Some(cached) = LIB_DIR.get() {
809 return Ok(cached.clone());
810 }
811 let (zig, zig_args) = Self::find_zig()?;
812 let zig_version = Self::zig_version()?;
813 let output = Command::new(zig).args(zig_args).arg("env").output()?;
814 let parse_zon_lib_dir = || -> Result<PathBuf> {
815 let output_str =
816 str::from_utf8(&output.stdout).context("`zig env` didn't return utf8 output")?;
817 let lib_dir = output_str
818 .find(".lib_dir")
819 .and_then(|idx| {
820 let bytes = output_str.as_bytes();
821 let mut start = idx;
822 while start < bytes.len() && bytes[start] != b'"' {
823 start += 1;
824 }
825 if start >= bytes.len() {
826 return None;
827 }
828 let mut end = start + 1;
829 while end < bytes.len() && bytes[end] != b'"' {
830 end += 1;
831 }
832 if end >= bytes.len() {
833 return None;
834 }
835 Some(&output_str[start + 1..end])
836 })
837 .context("Failed to parse lib_dir from `zig env` ZON output")?;
838 Ok(PathBuf::from(lib_dir))
839 };
840 let lib_dir = if zig_version >= semver::Version::new(0, 15, 0) {
841 parse_zon_lib_dir()?
842 } else {
843 serde_json::from_slice::<ZigEnv>(&output.stdout)
844 .map(|zig_env| PathBuf::from(zig_env.lib_dir))
845 .or_else(|_| parse_zon_lib_dir())?
846 };
847 Ok(LIB_DIR.get_or_init(|| lib_dir).clone())
848 }
849
850 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
851 where
852 K: AsRef<OsStr>,
853 V: AsRef<OsStr>,
854 {
855 let command_env_contains_no_key =
856 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
857
858 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
859 command.env(name, value);
860 }
861 }
862
863 pub(crate) fn apply_command_env(
864 manifest_path: Option<&Path>,
865 release: bool,
866 cargo: &cargo_options::CommonOptions,
867 cmd: &mut Command,
868 enable_zig_ar: bool,
869 ) -> Result<()> {
870 let cargo_config = cargo_config2::Config::load()?;
872 let config_targets;
874 let raw_targets: &[String] = if cargo.target.is_empty() {
875 if let Some(targets) = &cargo_config.build.target {
876 config_targets = targets
877 .iter()
878 .map(|t| t.triple().to_string())
879 .collect::<Vec<_>>();
880 &config_targets
881 } else {
882 &cargo.target
883 }
884 } else {
885 &cargo.target
886 };
887 let rust_targets = raw_targets
888 .iter()
889 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
890 .collect::<Vec<&str>>();
891 let rustc_meta = rustc_version::version_meta()?;
892 Self::add_env_if_missing(
893 cmd,
894 "CARGO_ZIGBUILD_RUSTC_VERSION",
895 rustc_meta.semver.to_string(),
896 );
897 let host_target = &rustc_meta.host;
898 for (parsed_target, raw_target) in rust_targets.iter().zip(raw_targets) {
899 let env_target = parsed_target.replace('-', "_");
900 let zig_wrapper = prepare_zig_linker(raw_target, &cargo_config)?;
901
902 if is_mingw_shell() {
903 let zig_cc = zig_wrapper.cc.to_slash_lossy();
904 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
905 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
906 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
907 if !parsed_target.contains("wasm") {
908 Self::add_env_if_missing(
909 cmd,
910 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
911 &*zig_cc,
912 );
913 }
914 } else {
915 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
916 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
917 if !parsed_target.contains("wasm") {
918 Self::add_env_if_missing(
919 cmd,
920 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
921 &zig_wrapper.cc,
922 );
923 }
924 }
925
926 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
927 if enable_zig_ar {
930 if parsed_target.contains("msvc") {
931 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
932 } else {
933 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
934 }
935 }
936
937 Self::setup_os_deps(manifest_path, release, cargo)?;
938
939 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
940 if env::var_os(&cmake_toolchain_file_env).is_none()
941 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
942 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
943 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
944 && let Ok(cmake_toolchain_file) =
945 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
946 {
947 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
948 }
949
950 if cfg!(target_os = "windows")
955 && env::var_os("CMAKE_GENERATOR").is_none()
956 && which::which("ninja").is_ok()
957 {
958 cmd.env("CMAKE_GENERATOR", "Ninja");
959 }
960
961 if raw_target.contains("windows-gnu") {
962 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
963 let triple: Triple = parsed_target.parse().unwrap_or_else(|_| Triple::unknown());
967 if !has_system_dlltool(&triple.architecture) {
968 let cache_dir = cache_dir();
969 let existing_path = env::var_os("PATH").unwrap_or_default();
970 let paths = std::iter::once(cache_dir).chain(env::split_paths(&existing_path));
971 if let Ok(new_path) = env::join_paths(paths) {
972 cmd.env("PATH", new_path);
973 }
974 }
975 }
976
977 if raw_target.contains("apple-darwin")
978 && let Some(sdkroot) = Self::macos_sdk_root()
979 && env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none()
980 {
981 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
983 }
984
985 if host_target == parsed_target {
988 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
989 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
992 }
993 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
994 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
995 }
996
997 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
999 .context("Failed to collect `zig cc` options")?;
1000 if raw_target.contains("apple-darwin") {
1001 options.push("-DTARGET_OS_IPHONE=0".to_string());
1003 }
1004 let escaped_options = shell_words::join(options.iter().map(|s| &s[..]));
1005 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
1006 let fallback_value = env::var(bindgen_env);
1007 for target in [&env_target[..], parsed_target] {
1008 let name = format!("{bindgen_env}_{target}");
1009 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
1010 if shell_words::split(&value).is_err() {
1011 value = shell_words::quote(&value).into_owned();
1013 }
1014 if !value.is_empty() {
1015 value.push(' ');
1016 }
1017 value.push_str(&escaped_options);
1018 unsafe { env::set_var(name, value) };
1019 } else {
1020 unsafe { env::set_var(name, escaped_options.clone()) };
1021 }
1022 }
1023 }
1024 Ok(())
1025 }
1026
1027 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
1031 #[derive(Debug, PartialEq, Eq)]
1032 enum Kind {
1033 Normal,
1034 Framework,
1035 }
1036
1037 #[derive(Debug)]
1038 struct PerLanguageOptions {
1039 glibc_minor_ver: Option<u32>,
1040 include_paths: Vec<(Kind, String)>,
1041 }
1042
1043 fn collect_per_language_options(
1044 program: &Path,
1045 ext: &str,
1046 raw_target: &str,
1047 ) -> Result<PerLanguageOptions> {
1048 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
1050 if !empty_file_path.exists() {
1051 fs::write(&empty_file_path, "")?;
1052 }
1053
1054 let output = Command::new(program)
1055 .arg("-E")
1056 .arg(&empty_file_path)
1057 .arg("-v")
1058 .output()?;
1059 let stderr = String::from_utf8(output.stderr)?;
1061 if !output.status.success() {
1062 bail!(
1063 "Failed to run `zig cc -v` with status {}: {}",
1064 output.status,
1065 stderr.trim(),
1066 );
1067 }
1068
1069 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
1073 let stderr = &stderr[start + 16..];
1074 let end = stderr
1075 .find(|c: char| !c.is_ascii_digit())
1076 .unwrap_or(stderr.len());
1077 stderr[..end].parse().ok()
1078 } else {
1079 None
1080 };
1081
1082 let start = stderr
1083 .find("#include <...> search starts here:")
1084 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
1085 + 34;
1086 let end = stderr
1087 .find("End of search list.")
1088 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
1089
1090 let mut include_paths = Vec::new();
1091 for mut line in stderr[start..end].lines() {
1092 line = line.trim();
1093 let mut kind = Kind::Normal;
1094 if line.ends_with(" (framework directory)") {
1095 line = line[..line.len() - 22].trim();
1096 kind = Kind::Framework;
1097 } else if line.ends_with(" (headermap)") {
1098 bail!("C/C++ search path includes header maps, which are not supported");
1099 }
1100 if !line.is_empty() {
1101 include_paths.push((kind, line.to_owned()));
1102 }
1103 }
1104
1105 if raw_target.contains("ohos") {
1107 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
1108 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
1109 }
1110
1111 Ok(PerLanguageOptions {
1112 include_paths,
1113 glibc_minor_ver,
1114 })
1115 }
1116
1117 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
1118 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
1119
1120 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
1122 bail!(
1123 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
1124 c_opts.glibc_minor_ver,
1125 cpp_opts.glibc_minor_ver,
1126 );
1127 }
1128 let c_paths = c_opts.include_paths;
1129 let mut cpp_paths = cpp_opts.include_paths;
1130 let cpp_pre_len = cpp_paths
1131 .iter()
1132 .position(|p| {
1133 p == c_paths
1134 .iter()
1135 .find(|(kind, _)| *kind == Kind::Normal)
1136 .unwrap()
1137 })
1138 .unwrap_or_default();
1139 let cpp_post_len = cpp_paths.len()
1140 - cpp_paths
1141 .iter()
1142 .position(|p| p == c_paths.last().unwrap())
1143 .unwrap_or_default()
1144 - 1;
1145
1146 let mut args = Vec::new();
1199
1200 args.push("-nostdinc".to_owned());
1203
1204 if raw_target.contains("musl") || raw_target.contains("ohos") {
1212 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
1213 args.push("-D_LARGEFILE64_SOURCE".to_owned());
1216 }
1217 args.extend(
1218 [
1219 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
1220 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
1221 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
1222 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
1223 "-D_LIBCPP_ABI_VERSION=1",
1224 "-D_LIBCPP_ABI_NAMESPACE=__1",
1225 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
1226 "-D_LIBCPP_HAS_LOCALIZATION=1",
1228 "-D_LIBCPP_HAS_WIDE_CHARACTERS=1",
1229 "-D_LIBCPP_HAS_UNICODE=1",
1230 "-D_LIBCPP_HAS_THREADS=1",
1231 "-D_LIBCPP_HAS_MONOTONIC_CLOCK",
1232 ]
1233 .into_iter()
1234 .map(ToString::to_string),
1235 );
1236 if let Some(ver) = c_opts.glibc_minor_ver {
1237 args.push(format!("-D__GLIBC_MINOR__={ver}"));
1239 }
1240
1241 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
1242 if kind != Kind::Normal {
1243 continue;
1245 }
1246 args.push("-cxx-isystem".to_owned());
1252 args.push(path);
1253 }
1254
1255 for (kind, path) in c_paths {
1256 match kind {
1257 Kind::Normal => {
1258 args.push("-Xclang".to_owned());
1260 args.push("-c-isystem".to_owned());
1261 args.push("-Xclang".to_owned());
1262 args.push(path.clone());
1263 args.push("-cxx-isystem".to_owned());
1264 args.push(path);
1265 }
1266 Kind::Framework => {
1267 args.push("-iframework".to_owned());
1268 args.push(path);
1269 }
1270 }
1271 }
1272
1273 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
1274 assert!(kind == Kind::Normal);
1275 args.push("-cxx-isystem".to_owned());
1276 args.push(path);
1277 }
1278
1279 Ok(args)
1280 }
1281
1282 fn setup_os_deps(
1283 manifest_path: Option<&Path>,
1284 release: bool,
1285 cargo: &cargo_options::CommonOptions,
1286 ) -> Result<()> {
1287 for target in &cargo.target {
1288 if target.contains("apple") {
1289 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
1290 target_dir.join(target)
1291 } else {
1292 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
1293 if !manifest_path.exists() {
1294 continue;
1296 }
1297 let metadata = cargo_metadata::MetadataCommand::new()
1298 .manifest_path(manifest_path)
1299 .no_deps()
1300 .exec()?;
1301 metadata.target_directory.into_std_path_buf().join(target)
1302 };
1303 let profile = match cargo.profile.as_deref() {
1304 Some("dev" | "test") => "debug",
1305 Some("release" | "bench") => "release",
1306 Some(profile) => profile,
1307 None => {
1308 if release {
1309 "release"
1310 } else {
1311 "debug"
1312 }
1313 }
1314 };
1315 let deps_dir = target_dir.join(profile).join("deps");
1316 fs::create_dir_all(&deps_dir)?;
1317 if !target_dir.join("CACHEDIR.TAG").is_file() {
1318 let _ = write_file(
1320 &target_dir.join("CACHEDIR.TAG"),
1321 "Signature: 8a477f597d28d172789f06886806bc55
1322# This file is a cache directory tag created by cargo.
1323# For information about cache directory tags see https://bford.info/cachedir/
1324",
1325 );
1326 }
1327 write_tbd_files(&deps_dir)?;
1328 } else if target.contains("arm") && target.contains("linux") {
1329 if let Ok(lib_dir) = Zig::lib_dir() {
1331 let arm_features_h = lib_dir
1332 .join("libc")
1333 .join("glibc")
1334 .join("sysdeps")
1335 .join("arm")
1336 .join("arm-features.h");
1337 if !arm_features_h.is_file() {
1338 fs::write(arm_features_h, ARM_FEATURES_H)?;
1339 }
1340 }
1341 } else if target.contains("windows-gnu")
1342 && let Ok(lib_dir) = Zig::lib_dir()
1343 {
1344 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
1345 let synchronization_def = lib_common.join("synchronization.def");
1346 if !synchronization_def.is_file() {
1347 let api_ms_win_core_synch_l1_2_0_def =
1348 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
1349 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
1351 }
1352 }
1353 }
1354 Ok(())
1355 }
1356
1357 fn setup_cmake_toolchain(
1358 target: &str,
1359 zig_wrapper: &ZigWrapper,
1360 enable_zig_ar: bool,
1361 ) -> Result<PathBuf> {
1362 let cmake = cache_dir().join("cmake");
1363 fs::create_dir_all(&cmake)?;
1364
1365 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
1366 let triple: Triple = target.parse()?;
1367 let os = triple.operating_system.to_string();
1368 let arch = triple.architecture.to_string();
1369 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1370 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1371 ("darwin", "aarch64") => ("Darwin", "arm64"),
1372 ("linux", arch) => {
1373 let cmake_arch = match arch {
1374 "powerpc" => "ppc",
1375 "powerpc64" => "ppc64",
1376 "powerpc64le" => "ppc64le",
1377 _ => arch,
1378 };
1379 ("Linux", cmake_arch)
1380 }
1381 ("windows", "x86_64") => ("Windows", "AMD64"),
1382 ("windows", "i686") => ("Windows", "X86"),
1383 ("windows", "aarch64") => ("Windows", "ARM64"),
1384 (os, arch) => (os, arch),
1385 };
1386 let mut content = format!(
1387 r#"
1388set(CMAKE_SYSTEM_NAME {system_name})
1389set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1390set(CMAKE_C_COMPILER {cc})
1391set(CMAKE_CXX_COMPILER {cxx})
1392set(CMAKE_RANLIB {ranlib})
1393set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1394set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1395 system_name = system_name,
1396 system_processor = system_processor,
1397 cc = zig_wrapper.cc.to_slash_lossy(),
1398 cxx = zig_wrapper.cxx.to_slash_lossy(),
1399 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1400 );
1401 if enable_zig_ar {
1402 content.push_str(&format!(
1403 "\nset(CMAKE_AR {})\n",
1404 zig_wrapper.ar.to_slash_lossy()
1405 ));
1406 }
1407 if system_name == "Darwin" && !cfg!(target_os = "macos") {
1412 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1413 let install_name_tool = cache_dir().join(format!("install_name_tool{exe_ext}"));
1414 symlink_wrapper(&install_name_tool)?;
1415 content.push_str(&format!(
1416 "\nset(CMAKE_INSTALL_NAME_TOOL {})",
1417 install_name_tool.to_slash_lossy()
1418 ));
1419
1420 if which::which("otool").is_err() {
1421 let script_ext = if cfg!(windows) { "bat" } else { "sh" };
1422 let otool = cmake.join(format!("otool.{script_ext}"));
1423 write_noop_script(&otool)?;
1424 content.push_str(&format!("\nset(CMAKE_OTOOL {})", otool.to_slash_lossy()));
1425 }
1426 }
1427 content.push_str(
1431 r#"
1432set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
1433set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
1434set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
1435set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)"#,
1436 );
1437 write_file(&toolchain_file, &content)?;
1438 Ok(toolchain_file)
1439 }
1440
1441 #[cfg(target_os = "macos")]
1442 fn macos_sdk_root() -> Option<PathBuf> {
1443 static SDK_ROOT: OnceLock<Option<PathBuf>> = OnceLock::new();
1444
1445 SDK_ROOT
1446 .get_or_init(|| match env::var_os("SDKROOT") {
1447 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1448 _ => {
1449 let output = Command::new("xcrun")
1450 .args(["--sdk", "macosx", "--show-sdk-path"])
1451 .output()
1452 .ok()?;
1453 if output.status.success() {
1454 let stdout = String::from_utf8(output.stdout).ok()?;
1455 let stdout = stdout.trim();
1456 if !stdout.is_empty() {
1457 return Some(stdout.into());
1458 }
1459 }
1460 None
1461 }
1462 })
1463 .clone()
1464 }
1465
1466 #[cfg(not(target_os = "macos"))]
1467 fn macos_sdk_root() -> Option<PathBuf> {
1468 match env::var_os("SDKROOT") {
1469 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1470 _ => None,
1471 }
1472 }
1473}
1474
1475fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1476 let existing_content = fs::read_to_string(path).unwrap_or_default();
1477 if existing_content != content {
1478 fs::write(path, content)?;
1479 }
1480 Ok(())
1481}
1482
1483#[cfg(target_family = "unix")]
1487fn write_noop_script(path: &Path) -> Result<()> {
1488 let content = "#!/bin/sh\nexit 0\n";
1489 let existing = fs::read_to_string(path).unwrap_or_default();
1490 if existing != content {
1491 OpenOptions::new()
1492 .create(true)
1493 .write(true)
1494 .truncate(true)
1495 .mode(0o700)
1496 .open(path)?
1497 .write_all(content.as_bytes())?;
1498 }
1499 Ok(())
1500}
1501
1502#[cfg(not(target_family = "unix"))]
1503fn write_noop_script(path: &Path) -> Result<()> {
1504 let content = "@echo off\r\nexit /b 0\r\n";
1505 let existing = fs::read_to_string(path).unwrap_or_default();
1506 if existing != content {
1507 fs::write(path, content)?;
1508 }
1509 Ok(())
1510}
1511
1512fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1513 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1514 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1515 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1516 Ok(())
1517}
1518
1519fn cache_dir() -> PathBuf {
1520 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1521 .ok()
1522 .map(|s| s.into())
1523 .or_else(dirs::cache_dir)
1524 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1526 .join(env!("CARGO_PKG_NAME"))
1527 .join(env!("CARGO_PKG_VERSION"))
1528}
1529
1530#[derive(Debug, Deserialize)]
1531struct ZigEnv {
1532 lib_dir: String,
1533}
1534
1535#[derive(Debug, Clone)]
1537pub struct ZigWrapper {
1538 pub cc: PathBuf,
1539 pub cxx: PathBuf,
1540 pub ar: PathBuf,
1541 pub ranlib: PathBuf,
1542 pub lib: PathBuf,
1543}
1544
1545#[derive(Debug, Clone, Default, PartialEq)]
1546struct TargetFlags {
1547 pub target_cpu: String,
1548 pub target_feature: String,
1549}
1550
1551impl TargetFlags {
1552 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1553 let mut parsed = Self::default();
1554
1555 let f = rustflags::from_encoded(encoded);
1556 for flag in f {
1557 if let rustflags::Flag::Codegen { opt, value } = flag {
1558 let key = opt.replace('-', "_");
1559 match key.as_str() {
1560 "target_cpu" => {
1561 if let Some(value) = value {
1562 parsed.target_cpu = value;
1563 }
1564 }
1565 "target_feature" => {
1566 if let Some(value) = value {
1568 if !parsed.target_feature.is_empty() {
1569 parsed.target_feature.push(',');
1570 }
1571 parsed.target_feature.push_str(&value);
1572 }
1573 }
1574 _ => {}
1575 }
1576 }
1577 }
1578 Ok(parsed)
1579 }
1580}
1581
1582#[allow(clippy::blocks_in_conditions)]
1591pub fn prepare_zig_linker(
1592 target: &str,
1593 cargo_config: &cargo_config2::Config,
1594) -> Result<ZigWrapper> {
1595 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1596 let abi_suffix = if abi_suffix.is_empty() {
1597 String::new()
1598 } else {
1599 if abi_suffix
1600 .split_once('.')
1601 .filter(|(x, y)| {
1602 !x.is_empty()
1603 && x.chars().all(|c| c.is_ascii_digit())
1604 && !y.is_empty()
1605 && y.chars().all(|c| c.is_ascii_digit())
1606 })
1607 .is_none()
1608 {
1609 bail!("Malformed zig target abi suffix.")
1610 }
1611 format!(".{abi_suffix}")
1612 };
1613 let triple: Triple = rust_target
1614 .parse()
1615 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1616 let arch = triple.architecture.to_string();
1617 let target_env = match (triple.architecture, triple.environment) {
1618 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1619 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1620 (_, Environment::GnuLlvm) => Environment::Gnu,
1621 (_, environment) => environment,
1622 };
1623 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1624 let file_target = target.trim_end_matches('.');
1625
1626 let mut cc_args = vec![
1627 "-g".to_owned(),
1629 "-fno-sanitize=all".to_owned(),
1631 ];
1632
1633 let zig_mcpu_default = match triple.operating_system {
1636 OperatingSystem::Linux => {
1637 match arch.as_str() {
1638 "arm" => match target_env {
1640 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1641 Environment::Gnueabihf | Environment::Musleabihf => {
1642 "generic+v6+strict_align+vfp2-d32"
1643 }
1644 _ => "",
1645 },
1646 "armv5te" => "generic+soft_float+strict_align",
1647 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1648 arch_str @ ("i586" | "i686") => {
1649 if arch_str == "i586" {
1650 "pentium"
1651 } else {
1652 "pentium4"
1653 }
1654 }
1655 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1656 "s390x" => "z10-vector",
1657 _ => "",
1658 }
1659 }
1660 _ => "",
1661 };
1662
1663 let zig_mcpu_override = {
1667 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1668 let encoded_rust_flags = rust_flags.encode()?;
1669 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1670 target_flags.target_cpu.replace('-', "_")
1673 };
1674
1675 if !zig_mcpu_override.is_empty() {
1676 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1677 } else if !zig_mcpu_default.is_empty() {
1678 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1679 }
1680
1681 match triple.operating_system {
1682 OperatingSystem::Linux => {
1683 let zig_arch = match arch.as_str() {
1684 "arm" => "arm",
1686 "armv5te" => "arm",
1687 "armv7" => "arm",
1688 "i586" | "i686" => {
1689 let zig_version = Zig::zig_version()?;
1690 if zig_version.major == 0 && zig_version.minor >= 11 {
1691 "x86"
1692 } else {
1693 "i386"
1694 }
1695 }
1696 "riscv64gc" => "riscv64",
1697 "s390x" => "s390x",
1698 _ => arch.as_str(),
1699 };
1700 let mut zig_target_env = target_env.to_string();
1701
1702 let zig_version = Zig::zig_version()?;
1703
1704 if zig_version >= semver::Version::new(0, 15, 0)
1708 && arch.as_str() == "armv7"
1709 && target_env == Environment::Ohos
1710 {
1711 zig_target_env = "ohoseabi".to_string();
1712 }
1713
1714 cc_args.push("-target".to_string());
1715 cc_args.push(format!("{zig_arch}-linux-{zig_target_env}{abi_suffix}"));
1716 }
1717 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1718 let zig_version = Zig::zig_version()?;
1719 if zig_version > semver::Version::new(0, 9, 1) {
1722 cc_args.push("-target".to_string());
1723 cc_args.push(format!("{arch}-macos-none{abi_suffix}"));
1724 } else {
1725 cc_args.push("-target".to_string());
1726 cc_args.push(format!("{arch}-macos-gnu{abi_suffix}"));
1727 }
1728 }
1729 OperatingSystem::Windows => {
1730 let zig_arch = match arch.as_str() {
1731 "i686" => {
1732 let zig_version = Zig::zig_version()?;
1733 if zig_version.major == 0 && zig_version.minor >= 11 {
1734 "x86"
1735 } else {
1736 "i386"
1737 }
1738 }
1739 arch => arch,
1740 };
1741 cc_args.push("-target".to_string());
1742 cc_args.push(format!("{zig_arch}-windows-{target_env}{abi_suffix}"));
1743 }
1744 OperatingSystem::Emscripten => {
1745 cc_args.push("-target".to_string());
1746 cc_args.push(format!("{arch}-emscripten{abi_suffix}"));
1747 }
1748 OperatingSystem::Wasi => {
1749 cc_args.push("-target".to_string());
1750 cc_args.push(format!("{arch}-wasi{abi_suffix}"));
1751 }
1752 OperatingSystem::WasiP1 => {
1753 cc_args.push("-target".to_string());
1754 cc_args.push(format!("{arch}-wasi.0.1.0{abi_suffix}"));
1755 }
1756 OperatingSystem::Freebsd => {
1757 let zig_arch = match arch.as_str() {
1758 "i686" => {
1759 let zig_version = Zig::zig_version()?;
1760 if zig_version.major == 0 && zig_version.minor >= 11 {
1761 "x86"
1762 } else {
1763 "i386"
1764 }
1765 }
1766 arch => arch,
1767 };
1768 cc_args.push("-target".to_string());
1769 cc_args.push(format!("{zig_arch}-freebsd"));
1770 }
1771 OperatingSystem::Unknown => {
1772 if triple.architecture == Architecture::Wasm32
1773 || triple.architecture == Architecture::Wasm64
1774 {
1775 cc_args.push("-target".to_string());
1776 cc_args.push(format!("{arch}-freestanding{abi_suffix}"));
1777 } else {
1778 bail!("unsupported target '{rust_target}'")
1779 }
1780 }
1781 _ => bail!(format!("unsupported target '{rust_target}'")),
1782 };
1783
1784 let zig_linker_dir = cache_dir();
1785 fs::create_dir_all(&zig_linker_dir)?;
1786
1787 if triple.operating_system == OperatingSystem::Linux {
1788 if matches!(
1789 triple.environment,
1790 Environment::Gnu
1791 | Environment::Gnuspe
1792 | Environment::Gnux32
1793 | Environment::Gnueabi
1794 | Environment::Gnuabi64
1795 | Environment::GnuIlp32
1796 | Environment::Gnueabihf
1797 ) {
1798 let glibc_version = if abi_suffix.is_empty() {
1799 (2, 17)
1800 } else {
1801 let mut parts = abi_suffix[1..].split('.');
1802 let major: usize = parts.next().unwrap().parse()?;
1803 let minor: usize = parts.next().unwrap().parse()?;
1804 (major, minor)
1805 };
1806 if glibc_version < (2, 28) {
1808 use crate::linux::{FCNTL_H, FCNTL_MAP};
1809
1810 let zig_version = Zig::zig_version()?;
1811 if zig_version.major == 0 && zig_version.minor < 11 {
1812 let fcntl_map = zig_linker_dir.join("fcntl.map");
1813 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1814 if existing_content != FCNTL_MAP {
1815 fs::write(&fcntl_map, FCNTL_MAP)?;
1816 }
1817 let fcntl_h = zig_linker_dir.join("fcntl.h");
1818 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1819 if existing_content != FCNTL_H {
1820 fs::write(&fcntl_h, FCNTL_H)?;
1821 }
1822
1823 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1824 cc_args.push("-include".to_string());
1825 cc_args.push(fcntl_h.display().to_string());
1826 }
1827 }
1828 } else if matches!(
1829 triple.environment,
1830 Environment::Musl
1831 | Environment::Muslabi64
1832 | Environment::Musleabi
1833 | Environment::Musleabihf
1834 ) {
1835 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1836
1837 let zig_version = Zig::zig_version()?;
1838 let rustc_version = rustc_version::version_meta()?.semver;
1839
1840 if (zig_version.major, zig_version.minor) >= (0, 11)
1845 && (rustc_version.major, rustc_version.minor) < (1, 72)
1846 {
1847 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1848 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1849
1850 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1851 }
1852 }
1853 }
1854
1855 let cc_args_str = join_args_for_script(&cc_args);
1858 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1859 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1860 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1861 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1862 let zig_version = Zig::zig_version()?;
1863 write_linker_wrapper(&zig_cc, "cc", &cc_args_str, &zig_version)?;
1864 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str, &zig_version)?;
1865 write_linker_wrapper(&zig_ranlib, "ranlib", "", &zig_version)?;
1866
1867 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1868 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1869 symlink_wrapper(&zig_ar)?;
1870 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1871 symlink_wrapper(&zig_lib)?;
1872
1873 if matches!(triple.operating_system, OperatingSystem::Windows)
1879 && matches!(triple.environment, Environment::Gnu)
1880 {
1881 if !has_system_dlltool(&triple.architecture) {
1884 let dlltool_name = get_dlltool_name(&triple.architecture);
1885 let zig_dlltool = zig_linker_dir.join(format!("{dlltool_name}{exe_ext}"));
1886 symlink_wrapper(&zig_dlltool)?;
1887 }
1888 }
1889
1890 Ok(ZigWrapper {
1891 cc: zig_cc,
1892 cxx: zig_cxx,
1893 ar: zig_ar,
1894 ranlib: zig_ranlib,
1895 lib: zig_lib,
1896 })
1897}
1898
1899fn symlink_wrapper(target: &Path) -> Result<()> {
1900 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1901 PathBuf::from(exe)
1902 } else {
1903 env::current_exe()?
1904 };
1905 #[cfg(windows)]
1906 {
1907 if !target.exists() {
1908 if std::fs::hard_link(¤t_exe, target).is_err() {
1910 std::fs::copy(¤t_exe, target)?;
1912 }
1913 }
1914 }
1915
1916 #[cfg(unix)]
1917 {
1918 if !target.exists() {
1919 if fs::read_link(target).is_ok() {
1920 fs::remove_file(target)?;
1922 }
1923 std::os::unix::fs::symlink(current_exe, target)?;
1924 }
1925 }
1926 Ok(())
1927}
1928
1929#[cfg(target_family = "unix")]
1931fn join_args_for_script<I, S>(args: I) -> String
1932where
1933 I: IntoIterator<Item = S>,
1934 S: AsRef<str>,
1935{
1936 shell_words::join(args)
1937}
1938
1939#[cfg(not(target_family = "unix"))]
1945fn quote_for_batch(s: &str) -> String {
1946 let needs_quoting_or_escaping = s.is_empty()
1947 || s.contains(|c: char| {
1948 matches!(
1949 c,
1950 ' ' | '\t' | '"' | '&' | '|' | '<' | '>' | '^' | '%' | '(' | ')' | '!'
1951 )
1952 });
1953
1954 if !needs_quoting_or_escaping {
1955 return s.to_string();
1956 }
1957
1958 let mut out = String::with_capacity(s.len() + 8);
1959 out.push('"');
1960 for c in s.chars() {
1961 match c {
1962 '"' => out.push_str("\"\""),
1963 '%' => out.push_str("%%"),
1964 _ => out.push(c),
1965 }
1966 }
1967 out.push('"');
1968 out
1969}
1970
1971#[cfg(not(target_family = "unix"))]
1973fn join_args_for_script<I, S>(args: I) -> String
1974where
1975 I: IntoIterator<Item = S>,
1976 S: AsRef<str>,
1977{
1978 args.into_iter()
1979 .map(|s| quote_for_batch(s.as_ref()))
1980 .collect::<Vec<_>>()
1981 .join(" ")
1982}
1983
1984#[cfg(target_family = "unix")]
1986fn write_linker_wrapper(
1987 path: &Path,
1988 command: &str,
1989 args: &str,
1990 zig_version: &semver::Version,
1991) -> Result<()> {
1992 let mut buf = Vec::<u8>::new();
1993 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1994 PathBuf::from(exe)
1995 } else {
1996 env::current_exe()?
1997 };
1998 writeln!(&mut buf, "#!/bin/sh")?;
1999
2000 writeln!(
2002 &mut buf,
2003 "export CARGO_ZIGBUILD_ZIG_VERSION={}",
2004 zig_version
2005 )?;
2006
2007 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
2009
2010 writeln!(
2011 &mut buf,
2012 "exec \"{}\" zig {} -- {} \"$@\"",
2013 current_exe.display(),
2014 command,
2015 args
2016 )?;
2017
2018 let existing_content = fs::read(path).unwrap_or_default();
2022 if existing_content != buf {
2023 OpenOptions::new()
2024 .create(true)
2025 .write(true)
2026 .truncate(true)
2027 .mode(0o700)
2028 .open(path)?
2029 .write_all(&buf)?;
2030 }
2031 Ok(())
2032}
2033
2034#[cfg(not(target_family = "unix"))]
2036fn write_linker_wrapper(
2037 path: &Path,
2038 command: &str,
2039 args: &str,
2040 zig_version: &semver::Version,
2041) -> Result<()> {
2042 let mut buf = Vec::<u8>::new();
2043 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
2044 PathBuf::from(exe)
2045 } else {
2046 env::current_exe()?
2047 };
2048 let current_exe = if is_mingw_shell() {
2049 current_exe.to_slash_lossy().to_string()
2050 } else {
2051 current_exe.display().to_string()
2052 };
2053 writeln!(&mut buf, "@echo off")?;
2054 writeln!(&mut buf, "setlocal DisableDelayedExpansion")?;
2056 writeln!(&mut buf, "set CARGO_ZIGBUILD_ZIG_VERSION={}", zig_version)?;
2058 writeln!(
2059 &mut buf,
2060 "\"{}\" zig {} -- {} %*",
2061 adjust_canonicalization(current_exe),
2062 command,
2063 args
2064 )?;
2065
2066 let existing_content = fs::read(path).unwrap_or_default();
2067 if existing_content != buf {
2068 fs::write(path, buf)?;
2069 }
2070 Ok(())
2071}
2072
2073pub(crate) fn is_mingw_shell() -> bool {
2074 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
2075}
2076
2077#[cfg(target_os = "windows")]
2079pub fn adjust_canonicalization(p: String) -> String {
2080 const VERBATIM_PREFIX: &str = r#"\\?\"#;
2081 if p.starts_with(VERBATIM_PREFIX) {
2082 p[VERBATIM_PREFIX.len()..].to_string()
2083 } else {
2084 p
2085 }
2086}
2087
2088fn python_path() -> Result<PathBuf> {
2089 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
2090 Ok(which::which(python)?)
2091}
2092
2093fn zig_path() -> Result<PathBuf> {
2094 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
2095 Ok(which::which(zig)?)
2096}
2097
2098fn get_dlltool_name(arch: &Architecture) -> &'static str {
2102 if cfg!(windows) {
2103 "dlltool"
2104 } else {
2105 match arch {
2106 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
2107 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
2108 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
2109 _ => "dlltool",
2110 }
2111 }
2112}
2113
2114fn has_system_dlltool(arch: &Architecture) -> bool {
2117 which::which(get_dlltool_name(arch)).is_ok()
2118}
2119
2120#[cfg(test)]
2121mod tests {
2122 use super::*;
2123
2124 #[test]
2125 fn test_target_flags() {
2126 let cases = [
2127 ("-C target-feature=-crt-static", "", "-crt-static"),
2129 ("-C target-cpu=native", "native", ""),
2130 (
2131 "--deny warnings --codegen target-feature=+crt-static",
2132 "",
2133 "+crt-static",
2134 ),
2135 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
2136 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
2137 (
2138 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
2139 "native",
2140 "-avx512bf16,-avx512bitalg",
2141 ),
2142 (
2143 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
2144 "x86-64",
2145 "",
2146 ),
2147 (
2148 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
2149 "",
2150 "+crt-static,+avx",
2151 ),
2152 ];
2153
2154 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
2155 let args = cargo_config2::Flags::from_space_separated(input);
2156 let encoded_rust_flags = args.encode().unwrap();
2157 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
2158 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
2159 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
2160 }
2161 }
2162
2163 #[test]
2164 fn test_join_args_for_script() {
2165 let args = vec!["-target", "x86_64-linux-gnu"];
2167 let result = join_args_for_script(&args);
2168 assert!(result.contains("-target"));
2169 assert!(result.contains("x86_64-linux-gnu"));
2170 }
2171
2172 #[test]
2173 #[cfg(not(target_family = "unix"))]
2174 fn test_quote_for_batch() {
2175 assert_eq!(quote_for_batch("-target"), "-target");
2177 assert_eq!(quote_for_batch("x86_64-linux-gnu"), "x86_64-linux-gnu");
2178
2179 assert_eq!(
2181 quote_for_batch("C:\\Users\\John Doe\\path"),
2182 "\"C:\\Users\\John Doe\\path\""
2183 );
2184
2185 assert_eq!(quote_for_batch(""), "\"\"");
2187
2188 assert_eq!(quote_for_batch("foo&bar"), "\"foo&bar\"");
2190 assert_eq!(quote_for_batch("foo|bar"), "\"foo|bar\"");
2191 assert_eq!(quote_for_batch("foo<bar"), "\"foo<bar\"");
2192 assert_eq!(quote_for_batch("foo>bar"), "\"foo>bar\"");
2193 assert_eq!(quote_for_batch("foo^bar"), "\"foo^bar\"");
2194 assert_eq!(quote_for_batch("foo%bar"), "\"foo%bar\"");
2195
2196 assert_eq!(quote_for_batch("foo\"bar"), "\"foo\"\"bar\"");
2198 }
2199
2200 #[test]
2201 #[cfg(not(target_family = "unix"))]
2202 fn test_join_args_for_script_windows() {
2203 let args = vec![
2205 "-target",
2206 "x86_64-linux-gnu",
2207 "-L",
2208 "C:\\Users\\John Doe\\path",
2209 ];
2210 let result = join_args_for_script(&args);
2211 assert!(result.contains("\"C:\\Users\\John Doe\\path\""));
2213 assert!(result.contains("-target"));
2215 assert!(!result.contains("\"-target\""));
2216 }
2217
2218 fn make_rustc_ver(major: u64, minor: u64, patch: u64) -> rustc_version::Version {
2219 rustc_version::Version::new(major, minor, patch)
2220 }
2221
2222 fn make_zig_ver(major: u64, minor: u64, patch: u64) -> semver::Version {
2223 semver::Version::new(major, minor, patch)
2224 }
2225
2226 fn run_filter(args: &[&str], target: Option<&str>, zig_ver: (u64, u64)) -> Vec<String> {
2227 let rustc_ver = make_rustc_ver(1, 80, 0);
2228 let zig_version = make_zig_ver(0, zig_ver.0, zig_ver.1);
2229 let target_info = TargetInfo::new(target.map(|s| s.to_string()).as_ref());
2230 filter_linker_args(
2231 args.iter().map(|s| s.to_string()),
2232 &rustc_ver,
2233 &zig_version,
2234 &target_info,
2235 )
2236 }
2237
2238 fn run_filter_one(arg: &str, target: Option<&str>, zig_ver: (u64, u64)) -> Vec<String> {
2239 run_filter(&[arg], target, zig_ver)
2240 }
2241
2242 fn run_filter_one_rustc(
2243 arg: &str,
2244 target: Option<&str>,
2245 zig_ver: (u64, u64),
2246 rustc_minor: u64,
2247 ) -> Vec<String> {
2248 let rustc_ver = make_rustc_ver(1, rustc_minor, 0);
2249 let zig_version = make_zig_ver(0, zig_ver.0, zig_ver.1);
2250 let target_info = TargetInfo::new(target.map(|s| s.to_string()).as_ref());
2251 filter_linker_args(
2252 std::iter::once(arg.to_string()),
2253 &rustc_ver,
2254 &zig_version,
2255 &target_info,
2256 )
2257 }
2258
2259 #[test]
2260 fn test_filter_common_replacements() {
2261 let linux = Some("x86_64-unknown-linux-gnu");
2262 assert_eq!(run_filter_one("-lgcc_s", linux, (13, 0)), vec!["-lunwind"]);
2264 assert!(run_filter_one("--target=x86_64-unknown-linux-gnu", linux, (13, 0)).is_empty());
2266 assert_eq!(
2268 run_filter_one("-emain", linux, (13, 0)),
2269 vec!["-Wl,--entry=main"]
2270 );
2271 assert_eq!(
2273 run_filter_one("-export-dynamic", linux, (13, 0)),
2274 vec!["-export-dynamic"]
2275 );
2276 }
2277
2278 #[test]
2279 fn test_filter_compiler_builtins_removed() {
2280 for target in &["armv7-unknown-linux-gnueabihf", "x86_64-pc-windows-gnu"] {
2281 let result = run_filter_one(
2282 "/path/to/libcompiler_builtins-abc123.rlib",
2283 Some(target),
2284 (13, 0),
2285 );
2286 assert!(
2287 result.is_empty(),
2288 "compiler_builtins should be removed for {target}"
2289 );
2290 }
2291 }
2292
2293 #[test]
2294 fn test_filter_windows_gnu_args() {
2295 let gnu = Some("x86_64-pc-windows-gnu");
2296 let removed: &[&str] = &[
2298 "-lwindows",
2299 "-l:libpthread.a",
2300 "-lgcc",
2301 "-Wl,--disable-auto-image-base",
2302 "-Wl,--dynamicbase",
2303 "-Wl,--large-address-aware",
2304 "-Wl,/path/to/list.def",
2305 "-Wl,C:\\path\\to\\list.def",
2306 "-lmsvcrt",
2307 ];
2308 for arg in removed {
2309 let result = run_filter_one(arg, gnu, (13, 0));
2310 assert!(result.is_empty(), "{arg} should be removed for windows-gnu");
2311 }
2312 let replaced: &[(&str, (u64, u64), &str)] = &[
2314 ("-lgcc_eh", (13, 0), "-lc++"),
2315 ("-Wl,-Bdynamic", (13, 0), "-Wl,-search_paths_first"),
2316 ];
2317 for (arg, zig_ver, expected) in replaced {
2318 let result = run_filter_one(arg, gnu, *zig_ver);
2319 assert_eq!(result, vec![*expected], "filter({arg})");
2320 }
2321 let result = run_filter_one("-lgcc_eh", gnu, (14, 0));
2323 assert_eq!(result, vec!["-lgcc_eh"]);
2324 }
2325
2326 #[test]
2327 fn test_filter_windows_gnu_rsbegin() {
2328 let result = run_filter_one("/path/to/rsbegin.o", Some("i686-pc-windows-gnu"), (13, 0));
2330 assert!(result.is_empty());
2331 let result = run_filter_one("/path/to/rsbegin.o", Some("x86_64-pc-windows-gnu"), (13, 0));
2333 assert_eq!(result, vec!["/path/to/rsbegin.o"]);
2334 }
2335
2336 #[test]
2337 fn test_filter_unsupported_linker_args() {
2338 let linux = Some("x86_64-unknown-linux-gnu");
2339 let removed: &[&str] = &[
2340 "-Wl,--no-undefined-version",
2341 "-Wl,-znostart-stop-gc",
2342 "-Wl,-plugin-opt=O2",
2343 ];
2344 for arg in removed {
2345 let result = run_filter_one(arg, linux, (13, 0));
2346 assert!(result.is_empty(), "{arg} should be removed");
2347 }
2348 }
2349
2350 #[test]
2351 fn test_filter_wp_args() {
2352 let linux = Some("x86_64-unknown-linux-gnu");
2353 for arg in &[
2355 "-Wp,-U_FORTIFY_SOURCE",
2356 "-Wp,-DFOO=1",
2357 "-Wp,-MF,/tmp/t.d",
2358 "-Wp,-MQ,foo",
2359 "-Wp,-MP",
2360 ] {
2361 let result = run_filter_one(arg, linux, (13, 0));
2362 assert!(result.is_empty(), "{arg} should be removed");
2363 }
2364 for arg in &["-Wp,-MD,/tmp/test.d", "-Wp,-MMD,/tmp/test.d", "-Wp,-MT,foo"] {
2366 let result = run_filter_one(arg, linux, (13, 0));
2367 assert_eq!(result, vec![*arg], "{arg} should be kept");
2368 }
2369 let result = run_filter_one("-U_FORTIFY_SOURCE", linux, (13, 0));
2371 assert_eq!(result, vec!["-U_FORTIFY_SOURCE"]);
2372 let result = run_filter_one("-DFOO=1", linux, (13, 0));
2373 assert_eq!(result, vec!["-DFOO=1"]);
2374 }
2375
2376 #[test]
2377 fn test_filter_musl_args() {
2378 let musl = Some("x86_64-unknown-linux-musl");
2379 let removed: &[&str] = &["/path/self-contained/crt1.o", "-lc"];
2380 for arg in removed {
2381 let result = run_filter_one(arg, musl, (13, 0));
2382 assert!(result.is_empty(), "{arg} should be removed for musl");
2383 }
2384 let result = run_filter_one("-Wl,-melf_i386", Some("i686-unknown-linux-musl"), (13, 0));
2386 assert!(result.is_empty());
2387 let result = run_filter_one_rustc("/path/to/liblibc-abc123.rlib", musl, (13, 0), 58);
2389 assert!(result.is_empty());
2390 let result = run_filter_one_rustc("/path/to/liblibc-abc123.rlib", musl, (13, 0), 59);
2391 assert_eq!(result, vec!["/path/to/liblibc-abc123.rlib"]);
2392 }
2393
2394 #[test]
2395 fn test_filter_march_args() {
2396 let cases: &[(&str, &str, &[&str])] = &[
2398 ("-march=armv7-a", "armv7-unknown-linux-gnueabihf", &[]),
2400 (
2402 "-march=rv64gc",
2403 "riscv64gc-unknown-linux-gnu",
2404 &["-march=generic_rv64"],
2405 ),
2406 (
2408 "-march=rv32imac",
2409 "riscv32imac-unknown-none-elf",
2410 &["-march=generic_rv32"],
2411 ),
2412 (
2414 "-march=armv8.4-a",
2415 "aarch64-unknown-linux-gnu",
2416 &["-mcpu=generic"],
2417 ),
2418 (
2420 "-march=armv8.4-a+crypto",
2421 "aarch64-unknown-linux-gnu",
2422 &[
2423 "-mcpu=generic+crypto",
2424 "-Xassembler",
2425 "-march=armv8.4-a+crypto",
2426 ],
2427 ),
2428 (
2430 "-march=armv8.4-a",
2431 "aarch64-apple-darwin",
2432 &["-mcpu=apple_m1"],
2433 ),
2434 ];
2435 for (input, target, expected) in cases {
2436 let result = run_filter_one(input, Some(target), (13, 0));
2437 assert_eq!(&result, expected, "filter({input}, {target})");
2438 }
2439 }
2440
2441 #[test]
2442 fn test_filter_apple_args() {
2443 let darwin = Some("aarch64-apple-darwin");
2444 let result = run_filter_one("-Wl,-dylib", darwin, (13, 0));
2445 assert!(result.is_empty());
2446 }
2447
2448 #[test]
2449 fn test_filter_freebsd_libs_removed() {
2450 for lib in &["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"] {
2451 let result = run_filter_one(lib, Some("x86_64-unknown-freebsd"), (13, 0));
2452 assert!(result.is_empty(), "{lib} should be removed for freebsd");
2453 }
2454 }
2455
2456 #[test]
2457 fn test_filter_exported_symbols_list_two_arg_apple() {
2458 let result = run_filter(
2459 &[
2460 "-arch",
2461 "arm64",
2462 "-Wl,-exported_symbols_list",
2463 "-Wl,/tmp/rustcXXX/list",
2464 "-o",
2465 "output.dylib",
2466 ],
2467 Some("aarch64-apple-darwin"),
2468 (13, 0),
2469 );
2470 assert_eq!(result, vec!["-arch", "arm64", "-o", "output.dylib"]);
2471 }
2472
2473 #[test]
2474 fn test_filter_exported_symbols_list_two_arg_cross_platform() {
2475 let result = run_filter(
2476 &[
2477 "-arch",
2478 "arm64",
2479 "-Wl,-exported_symbols_list",
2480 "-Wl,C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\rustcXXX\\list",
2481 "-o",
2482 "output.dylib",
2483 ],
2484 None,
2485 (13, 0),
2486 );
2487 assert_eq!(result, vec!["-arch", "arm64", "-o", "output.dylib"]);
2488 }
2489
2490 #[test]
2491 fn test_filter_exported_symbols_list_single_arg_comma() {
2492 let result = run_filter(
2493 &[
2494 "-Wl,-exported_symbols_list,/tmp/rustcXXX/list",
2495 "-o",
2496 "output.dylib",
2497 ],
2498 Some("aarch64-apple-darwin"),
2499 (13, 0),
2500 );
2501 assert_eq!(result, vec!["-o", "output.dylib"]);
2502 }
2503
2504 #[test]
2505 fn test_filter_exported_symbols_list_not_filtered_zig_016() {
2506 let result = run_filter(
2507 &[
2508 "-Wl,-exported_symbols_list",
2509 "-Wl,/tmp/rustcXXX/list",
2510 "-o",
2511 "output.dylib",
2512 ],
2513 Some("aarch64-apple-darwin"),
2514 (16, 0),
2515 );
2516 assert_eq!(
2517 result,
2518 vec![
2519 "-Wl,-exported_symbols_list",
2520 "-Wl,/tmp/rustcXXX/list",
2521 "-o",
2522 "output.dylib"
2523 ]
2524 );
2525 }
2526
2527 #[test]
2528 fn test_filter_dynamic_list_two_arg() {
2529 let result = run_filter(
2530 &[
2531 "-Wl,--dynamic-list",
2532 "-Wl,/tmp/rustcXXX/list",
2533 "-o",
2534 "output.so",
2535 ],
2536 Some("x86_64-unknown-linux-gnu"),
2537 (13, 0),
2538 );
2539 assert_eq!(result, vec!["-o", "output.so"]);
2540 }
2541
2542 #[test]
2543 fn test_filter_dynamic_list_single_arg_comma() {
2544 let result = run_filter(
2545 &["-Wl,--dynamic-list,/tmp/rustcXXX/list", "-o", "output.so"],
2546 Some("x86_64-unknown-linux-gnu"),
2547 (13, 0),
2548 );
2549 assert_eq!(result, vec!["-o", "output.so"]);
2550 }
2551
2552 #[test]
2553 fn test_filter_preserves_normal_args() {
2554 let result = run_filter(
2555 &["-arch", "arm64", "-lSystem", "-lc", "-o", "output"],
2556 Some("aarch64-apple-darwin"),
2557 (13, 0),
2558 );
2559 assert_eq!(
2560 result,
2561 vec!["-arch", "arm64", "-lSystem", "-lc", "-o", "output"]
2562 );
2563 }
2564
2565 #[test]
2566 fn test_filter_skip_next_at_end_of_args() {
2567 let result = run_filter(
2568 &["-o", "output", "-Wl,-exported_symbols_list"],
2569 Some("aarch64-apple-darwin"),
2570 (13, 0),
2571 );
2572 assert_eq!(result, vec!["-o", "output"]);
2573 }
2574}