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::{anyhow, bail, Context, Result};
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 if (zig_version.major, zig_version.minor) < (0, 16)
322 && (arg == "-Wl,-exported_symbols_list" || arg == "-Wl,--dynamic-list")
323 {
324 skip_next_arg = true;
325 continue;
326 }
327 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
328 vec![self.process_linker_response_file(
329 arg,
330 &rustc_ver,
331 &zig_version,
332 &target_info,
333 )?]
334 } else {
335 self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info)
336 };
337 new_cmd_args.extend(args);
338 }
339
340 if target_info.is_mips32() {
341 new_cmd_args.push("-Wl,-z,notext".to_string());
343 }
344
345 if self.has_undefined_dynamic_lookup(cmd_args) {
346 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
347 }
348 if target_info.is_macos() {
349 if self.should_add_libcharset(cmd_args, &zig_version) {
350 new_cmd_args.push("-lcharset".to_string());
351 }
352 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
353 }
354
355 let mut command = Self::command()?;
358 if (zig_version.major, zig_version.minor) >= (0, 15) {
359 if let Some(sdkroot) = Self::macos_sdk_root() {
360 command.env("SDKROOT", sdkroot);
361 }
362 }
363
364 let mut child = command
365 .arg(cmd)
366 .args(new_cmd_args)
367 .spawn()
368 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
369 let status = child.wait().expect("Failed to wait on zig child process");
370 if !status.success() {
371 process::exit(status.code().unwrap_or(1));
372 }
373 Ok(())
374 }
375
376 fn process_linker_response_file(
377 &self,
378 arg: &str,
379 rustc_ver: &rustc_version::Version,
380 zig_version: &semver::Version,
381 target_info: &TargetInfo,
382 ) -> Result<String> {
383 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
387 let content = if target_info.is_windows_msvc() {
388 if content_bytes[0..2] != [255, 254] {
389 bail!(
390 "linker response file `{}` didn't start with a utf16 BOM",
391 &arg
392 );
393 }
394 let content_utf16: Vec<u16> = content_bytes[2..]
395 .chunks_exact(2)
396 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
397 .collect();
398 String::from_utf16(&content_utf16).with_context(|| {
399 format!(
400 "linker response file `{}` didn't contain valid utf16 content",
401 &arg
402 )
403 })?
404 } else {
405 String::from_utf8(content_bytes).with_context(|| {
406 format!(
407 "linker response file `{}` didn't contain valid utf8 content",
408 &arg
409 )
410 })?
411 };
412 let mut link_args: Vec<_> = content
413 .split('\n')
414 .flat_map(|arg| self.filter_linker_arg(arg, rustc_ver, zig_version, target_info))
415 .collect();
416 if (zig_version.major, zig_version.minor) < (0, 16) {
421 let mut filtered = Vec::with_capacity(link_args.len());
422 let mut skip_next = false;
423 for arg in link_args {
424 if skip_next {
425 skip_next = false;
426 continue;
427 }
428 if arg == "-Wl,-exported_symbols_list" || arg == "-Wl,--dynamic-list" {
429 skip_next = true;
430 continue;
431 }
432 filtered.push(arg);
433 }
434 link_args = filtered;
435 }
436 if self.has_undefined_dynamic_lookup(&link_args) {
437 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
438 }
439 if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
440 link_args.push("-lcharset".to_string());
441 }
442 if target_info.is_windows_msvc() {
443 let new_content = link_args.join("\n");
444 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
445 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
447 out.push(c as u8);
449 out.push((c >> 8) as u8);
450 }
451 fs::write(arg.trim_start_matches('@'), out)?;
452 } else {
453 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
454 }
455 Ok(arg.to_string())
456 }
457
458 fn filter_linker_arg(
459 &self,
460 arg: &str,
461 rustc_ver: &rustc_version::Version,
462 zig_version: &semver::Version,
463 target_info: &TargetInfo,
464 ) -> Vec<String> {
465 if arg == "-lgcc_s" {
466 return vec!["-lunwind".to_string()];
468 } else if arg.starts_with("--target=") {
469 return vec![];
471 } else if arg.starts_with("-e") && arg.len() > 2 && !arg.starts_with("-export") {
472 let entry = &arg[2..];
476 return vec![format!("-Wl,--entry={}", entry)];
477 }
478 if (target_info.is_arm() || target_info.is_windows_gnu())
479 && arg.ends_with(".rlib")
480 && arg.contains("libcompiler_builtins-")
481 {
482 return vec![];
484 }
485 if target_info.is_windows_gnu() {
486 #[allow(clippy::if_same_then_else)]
487 if arg == "-lgcc_eh"
488 && ((zig_version.major, zig_version.minor) < (0, 14) || target_info.is_i686())
489 {
490 return vec!["-lc++".to_string()];
495 } else if arg.ends_with("rsbegin.o") || arg.ends_with("rsend.o") {
496 if target_info.is_i686() {
502 return vec![];
503 }
504 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
505 return vec!["-Wl,-search_paths_first".to_owned()];
510 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
511 return vec![];
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 vec![];
523 } else if arg == "-lmsvcrt" {
524 return vec![];
525 }
526 } else if arg == "-Wl,--no-undefined-version" {
527 return vec![];
530 } else if arg == "-Wl,-znostart-stop-gc" {
531 return vec![];
534 } else if arg.starts_with("-Wl,-plugin-opt") {
535 return vec![];
539 }
540 if target_info.is_musl() || target_info.is_ohos() {
541 if arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt") {
543 return vec![];
544 } else if arg == "-Wl,-melf_i386" {
545 return vec![];
547 }
548 if rustc_ver.major == 1
549 && rustc_ver.minor < 59
550 && arg.ends_with(".rlib")
551 && arg.contains("liblibc-")
552 {
553 return vec![];
556 }
557 if arg == "-lc" {
558 return vec![];
559 }
560 }
561 if arg.starts_with("-march=") {
562 if target_info.is_arm() || target_info.is_i386() {
564 return vec![];
565 } else if target_info.is_riscv64() {
566 return vec!["-march=generic_rv64".to_string()];
567 } else if target_info.is_riscv32() {
568 return vec!["-march=generic_rv32".to_string()];
569 } else if arg.starts_with("-march=armv") {
570 if target_info.is_aarch64() || target_info.is_aarch64_be() {
573 let march_value = arg.strip_prefix("-march=").unwrap();
575 let features = if let Some(pos) = march_value.find('+') {
577 &march_value[pos..]
578 } else {
579 ""
580 };
581 let base_cpu = if target_info.is_apple_platform() {
582 target_info.apple_cpu()
583 } else {
584 "generic"
585 };
586 let mut result = vec![format!("-mcpu={}{}", base_cpu, features)];
587 if features.contains("+crypto") {
588 result.append(&mut vec!["-Xassembler".to_owned(), arg.to_string()]);
594 }
595 return result;
596 }
597 }
598 }
599 if target_info.is_apple_platform() {
600 if (zig_version.major, zig_version.minor) < (0, 16)
601 && (arg.starts_with("-Wl,-exported_symbols_list,")
602 || arg == "-Wl,-exported_symbols_list")
603 {
604 return vec![];
607 }
608 if arg == "-Wl,-dylib" {
609 return vec![];
611 }
612 }
613 if target_info.is_freebsd() {
614 let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
615 if ignored_libs.contains(&arg) {
616 return vec![];
617 }
618 }
619 vec![arg.to_string()]
620 }
621
622 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
623 let undefined = args
624 .iter()
625 .position(|x| x == "-undefined")
626 .and_then(|i| args.get(i + 1));
627 matches!(undefined, Some(x) if x == "dynamic_lookup")
628 }
629
630 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
631 if (zig_version.major, zig_version.minor) >= (0, 12) {
633 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
634 } else {
635 false
636 }
637 }
638
639 fn add_macos_specific_args(
640 &self,
641 new_cmd_args: &mut Vec<String>,
642 zig_version: &semver::Version,
643 ) -> Result<()> {
644 let sdkroot = Self::macos_sdk_root();
645 if (zig_version.major, zig_version.minor) >= (0, 12) {
646 if let Some(ref sdkroot) = sdkroot {
650 if (zig_version.major, zig_version.minor) < (0, 15) {
651 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
652 }
653 }
655 }
656 if let Some(ref sdkroot) = sdkroot {
657 if (zig_version.major, zig_version.minor) < (0, 15) {
658 new_cmd_args.extend_from_slice(&[
660 "-isystem".to_string(),
661 format!("{}", sdkroot.join("usr").join("include").display()),
662 format!("-L{}", sdkroot.join("usr").join("lib").display()),
663 format!(
664 "-F{}",
665 sdkroot
666 .join("System")
667 .join("Library")
668 .join("Frameworks")
669 .display()
670 ),
671 "-DTARGET_OS_IPHONE=0".to_string(),
672 ]);
673 } else {
674 new_cmd_args.extend_from_slice(&[
677 "-isystem".to_string(),
678 format!("{}", sdkroot.join("usr").join("include").display()),
679 format!("-L{}", sdkroot.join("usr").join("lib").display()),
680 format!(
681 "-F{}",
682 sdkroot
683 .join("System")
684 .join("Library")
685 .join("Frameworks")
686 .display()
687 ),
688 "-iframework".to_string(),
690 format!(
691 "{}",
692 sdkroot
693 .join("System")
694 .join("Library")
695 .join("Frameworks")
696 .display()
697 ),
698 "-DTARGET_OS_IPHONE=0".to_string(),
699 ]);
700 }
701 }
702
703 let cache_dir = cache_dir();
705 let deps_dir = cache_dir.join("deps");
706 fs::create_dir_all(&deps_dir)?;
707 write_tbd_files(&deps_dir)?;
708 new_cmd_args.push("-L".to_string());
709 new_cmd_args.push(format!("{}", deps_dir.display()));
710 Ok(())
711 }
712
713 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
715 let mut child = Self::command()?
716 .arg(cmd)
717 .args(cmd_args)
718 .spawn()
719 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
720 let status = child.wait().expect("Failed to wait on zig child process");
721 if !status.success() {
722 process::exit(status.code().unwrap_or(1));
723 }
724 Ok(())
725 }
726
727 pub fn command() -> Result<Command> {
729 let (zig, zig_args) = Self::find_zig()?;
730 let mut cmd = Command::new(zig);
731 cmd.args(zig_args);
732 Ok(cmd)
733 }
734
735 fn zig_version() -> Result<semver::Version> {
736 static ZIG_VERSION: OnceLock<semver::Version> = OnceLock::new();
737
738 if let Some(version) = ZIG_VERSION.get() {
739 return Ok(version.clone());
740 }
741 if let Ok(version_str) = env::var("CARGO_ZIGBUILD_ZIG_VERSION") {
743 if let Ok(version) = semver::Version::parse(&version_str) {
744 return Ok(ZIG_VERSION.get_or_init(|| version).clone());
745 }
746 }
747 let output = Self::command()?.arg("version").output()?;
748 let version_str =
749 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
750 let version = semver::Version::parse(version_str.trim())?;
751 Ok(ZIG_VERSION.get_or_init(|| version).clone())
752 }
753
754 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
756 static ZIG_PATH: OnceLock<(PathBuf, Vec<String>)> = OnceLock::new();
757
758 if let Some(cached) = ZIG_PATH.get() {
759 return Ok(cached.clone());
760 }
761 let result = Self::find_zig_python()
762 .or_else(|_| Self::find_zig_bin())
763 .context("Failed to find zig")?;
764 Ok(ZIG_PATH.get_or_init(|| result).clone())
765 }
766
767 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
769 let zig_path = zig_path()?;
770 let output = Command::new(&zig_path).arg("version").output()?;
771
772 let version_str = str::from_utf8(&output.stdout).with_context(|| {
773 format!("`{} version` didn't return utf8 output", zig_path.display())
774 })?;
775 Self::validate_zig_version(version_str)?;
776 Ok((zig_path, Vec::new()))
777 }
778
779 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
781 let python_path = python_path()?;
782 let output = Command::new(&python_path)
783 .args(["-m", "ziglang", "version"])
784 .output()?;
785
786 let version_str = str::from_utf8(&output.stdout).with_context(|| {
787 format!(
788 "`{} -m ziglang version` didn't return utf8 output",
789 python_path.display()
790 )
791 })?;
792 Self::validate_zig_version(version_str)?;
793 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
794 }
795
796 fn validate_zig_version(version: &str) -> Result<()> {
797 let min_ver = semver::Version::new(0, 9, 0);
798 let version = semver::Version::parse(version.trim())?;
799 if version >= min_ver {
800 Ok(())
801 } else {
802 bail!(
803 "zig version {} is too old, need at least {}",
804 version,
805 min_ver
806 )
807 }
808 }
809
810 pub fn lib_dir() -> Result<PathBuf> {
812 static LIB_DIR: OnceLock<PathBuf> = OnceLock::new();
813
814 if let Some(cached) = LIB_DIR.get() {
815 return Ok(cached.clone());
816 }
817 let (zig, zig_args) = Self::find_zig()?;
818 let zig_version = Self::zig_version()?;
819 let output = Command::new(zig).args(zig_args).arg("env").output()?;
820 let parse_zon_lib_dir = || -> Result<PathBuf> {
821 let output_str =
822 str::from_utf8(&output.stdout).context("`zig env` didn't return utf8 output")?;
823 let lib_dir = output_str
824 .find(".lib_dir")
825 .and_then(|idx| {
826 let bytes = output_str.as_bytes();
827 let mut start = idx;
828 while start < bytes.len() && bytes[start] != b'"' {
829 start += 1;
830 }
831 if start >= bytes.len() {
832 return None;
833 }
834 let mut end = start + 1;
835 while end < bytes.len() && bytes[end] != b'"' {
836 end += 1;
837 }
838 if end >= bytes.len() {
839 return None;
840 }
841 Some(&output_str[start + 1..end])
842 })
843 .context("Failed to parse lib_dir from `zig env` ZON output")?;
844 Ok(PathBuf::from(lib_dir))
845 };
846 let lib_dir = if zig_version >= semver::Version::new(0, 15, 0) {
847 parse_zon_lib_dir()?
848 } else {
849 serde_json::from_slice::<ZigEnv>(&output.stdout)
850 .map(|zig_env| PathBuf::from(zig_env.lib_dir))
851 .or_else(|_| parse_zon_lib_dir())?
852 };
853 Ok(LIB_DIR.get_or_init(|| lib_dir).clone())
854 }
855
856 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
857 where
858 K: AsRef<OsStr>,
859 V: AsRef<OsStr>,
860 {
861 let command_env_contains_no_key =
862 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
863
864 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
865 command.env(name, value);
866 }
867 }
868
869 pub(crate) fn apply_command_env(
870 manifest_path: Option<&Path>,
871 release: bool,
872 cargo: &cargo_options::CommonOptions,
873 cmd: &mut Command,
874 enable_zig_ar: bool,
875 ) -> Result<()> {
876 let rust_targets = cargo
878 .target
879 .iter()
880 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
881 .collect::<Vec<&str>>();
882 let rustc_meta = rustc_version::version_meta()?;
883 Self::add_env_if_missing(
884 cmd,
885 "CARGO_ZIGBUILD_RUSTC_VERSION",
886 rustc_meta.semver.to_string(),
887 );
888 let host_target = &rustc_meta.host;
889 for (parsed_target, raw_target) in rust_targets.iter().zip(&cargo.target) {
890 let env_target = parsed_target.replace('-', "_");
891 let zig_wrapper = prepare_zig_linker(raw_target)?;
892
893 if is_mingw_shell() {
894 let zig_cc = zig_wrapper.cc.to_slash_lossy();
895 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
896 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
897 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
898 if !parsed_target.contains("wasm") {
899 Self::add_env_if_missing(
900 cmd,
901 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
902 &*zig_cc,
903 );
904 }
905 } else {
906 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
907 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
908 if !parsed_target.contains("wasm") {
909 Self::add_env_if_missing(
910 cmd,
911 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
912 &zig_wrapper.cc,
913 );
914 }
915 }
916
917 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
918 if enable_zig_ar {
921 if parsed_target.contains("msvc") {
922 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
923 } else {
924 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
925 }
926 }
927
928 Self::setup_os_deps(manifest_path, release, cargo)?;
929
930 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
931 if env::var_os(&cmake_toolchain_file_env).is_none()
932 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
933 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
934 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
935 {
936 if let Ok(cmake_toolchain_file) =
937 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
938 {
939 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
940 }
941 }
942
943 if raw_target.contains("windows-gnu") {
944 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
945 let triple: Triple = parsed_target.parse().unwrap_or_else(|_| Triple::unknown());
949 if !has_system_dlltool(&triple.architecture) {
950 let cache_dir = cache_dir();
951 let existing_path = env::var_os("PATH").unwrap_or_default();
952 let paths = std::iter::once(cache_dir).chain(env::split_paths(&existing_path));
953 if let Ok(new_path) = env::join_paths(paths) {
954 cmd.env("PATH", new_path);
955 }
956 }
957 }
958
959 if raw_target.contains("apple-darwin") {
960 if let Some(sdkroot) = Self::macos_sdk_root() {
961 if env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none() {
962 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
964 }
965 }
966 }
967
968 if host_target == parsed_target {
971 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
972 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
975 }
976 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
977 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
978 }
979
980 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
982 .context("Failed to collect `zig cc` options")?;
983 if raw_target.contains("apple-darwin") {
984 options.push("-DTARGET_OS_IPHONE=0".to_string());
986 }
987 let escaped_options = shell_words::join(options.iter().map(|s| &s[..]));
988 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
989 let fallback_value = env::var(bindgen_env);
990 for target in [&env_target[..], parsed_target] {
991 let name = format!("{bindgen_env}_{target}");
992 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
993 if shell_words::split(&value).is_err() {
994 value = shell_words::quote(&value).into_owned();
996 }
997 if !value.is_empty() {
998 value.push(' ');
999 }
1000 value.push_str(&escaped_options);
1001 unsafe { env::set_var(name, value) };
1002 } else {
1003 unsafe { env::set_var(name, escaped_options.clone()) };
1004 }
1005 }
1006 }
1007 Ok(())
1008 }
1009
1010 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
1014 #[derive(Debug, PartialEq, Eq)]
1015 enum Kind {
1016 Normal,
1017 Framework,
1018 }
1019
1020 #[derive(Debug)]
1021 struct PerLanguageOptions {
1022 glibc_minor_ver: Option<u32>,
1023 include_paths: Vec<(Kind, String)>,
1024 }
1025
1026 fn collect_per_language_options(
1027 program: &Path,
1028 ext: &str,
1029 raw_target: &str,
1030 ) -> Result<PerLanguageOptions> {
1031 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
1033 if !empty_file_path.exists() {
1034 fs::write(&empty_file_path, "")?;
1035 }
1036
1037 let output = Command::new(program)
1038 .arg("-E")
1039 .arg(&empty_file_path)
1040 .arg("-v")
1041 .output()?;
1042 let stderr = String::from_utf8(output.stderr)?;
1044 if !output.status.success() {
1045 bail!(
1046 "Failed to run `zig cc -v` with status {}: {}",
1047 output.status,
1048 stderr.trim(),
1049 );
1050 }
1051
1052 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
1056 let stderr = &stderr[start + 16..];
1057 let end = stderr
1058 .find(|c: char| !c.is_ascii_digit())
1059 .unwrap_or(stderr.len());
1060 stderr[..end].parse().ok()
1061 } else {
1062 None
1063 };
1064
1065 let start = stderr
1066 .find("#include <...> search starts here:")
1067 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
1068 + 34;
1069 let end = stderr
1070 .find("End of search list.")
1071 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
1072
1073 let mut include_paths = Vec::new();
1074 for mut line in stderr[start..end].lines() {
1075 line = line.trim();
1076 let mut kind = Kind::Normal;
1077 if line.ends_with(" (framework directory)") {
1078 line = line[..line.len() - 22].trim();
1079 kind = Kind::Framework;
1080 } else if line.ends_with(" (headermap)") {
1081 bail!("C/C++ search path includes header maps, which are not supported");
1082 }
1083 if !line.is_empty() {
1084 include_paths.push((kind, line.to_owned()));
1085 }
1086 }
1087
1088 if raw_target.contains("ohos") {
1090 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
1091 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
1092 }
1093
1094 Ok(PerLanguageOptions {
1095 include_paths,
1096 glibc_minor_ver,
1097 })
1098 }
1099
1100 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
1101 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
1102
1103 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
1105 bail!(
1106 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
1107 c_opts.glibc_minor_ver,
1108 cpp_opts.glibc_minor_ver,
1109 );
1110 }
1111 let c_paths = c_opts.include_paths;
1112 let mut cpp_paths = cpp_opts.include_paths;
1113 let cpp_pre_len = cpp_paths
1114 .iter()
1115 .position(|p| {
1116 p == c_paths
1117 .iter()
1118 .find(|(kind, _)| *kind == Kind::Normal)
1119 .unwrap()
1120 })
1121 .unwrap_or_default();
1122 let cpp_post_len = cpp_paths.len()
1123 - cpp_paths
1124 .iter()
1125 .position(|p| p == c_paths.last().unwrap())
1126 .unwrap_or_default()
1127 - 1;
1128
1129 let mut args = Vec::new();
1182
1183 args.push("-nostdinc".to_owned());
1186
1187 if raw_target.contains("musl") || raw_target.contains("ohos") {
1195 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
1196 args.push("-D_LARGEFILE64_SOURCE".to_owned());
1199 }
1200 args.extend(
1201 [
1202 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
1203 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
1204 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
1205 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
1206 "-D_LIBCPP_ABI_VERSION=1",
1207 "-D_LIBCPP_ABI_NAMESPACE=__1",
1208 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
1209 "-D_LIBCPP_HAS_LOCALIZATION=1",
1211 "-D_LIBCPP_HAS_WIDE_CHARACTERS=1",
1212 "-D_LIBCPP_HAS_UNICODE=1",
1213 "-D_LIBCPP_HAS_THREADS=1",
1214 "-D_LIBCPP_HAS_MONOTONIC_CLOCK",
1215 ]
1216 .into_iter()
1217 .map(ToString::to_string),
1218 );
1219 if let Some(ver) = c_opts.glibc_minor_ver {
1220 args.push(format!("-D__GLIBC_MINOR__={ver}"));
1222 }
1223
1224 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
1225 if kind != Kind::Normal {
1226 continue;
1228 }
1229 args.push("-cxx-isystem".to_owned());
1235 args.push(path);
1236 }
1237
1238 for (kind, path) in c_paths {
1239 match kind {
1240 Kind::Normal => {
1241 args.push("-Xclang".to_owned());
1243 args.push("-c-isystem".to_owned());
1244 args.push("-Xclang".to_owned());
1245 args.push(path.clone());
1246 args.push("-cxx-isystem".to_owned());
1247 args.push(path);
1248 }
1249 Kind::Framework => {
1250 args.push("-iframework".to_owned());
1251 args.push(path);
1252 }
1253 }
1254 }
1255
1256 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
1257 assert!(kind == Kind::Normal);
1258 args.push("-cxx-isystem".to_owned());
1259 args.push(path);
1260 }
1261
1262 Ok(args)
1263 }
1264
1265 fn setup_os_deps(
1266 manifest_path: Option<&Path>,
1267 release: bool,
1268 cargo: &cargo_options::CommonOptions,
1269 ) -> Result<()> {
1270 for target in &cargo.target {
1271 if target.contains("apple") {
1272 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
1273 target_dir.join(target)
1274 } else {
1275 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
1276 if !manifest_path.exists() {
1277 continue;
1279 }
1280 let metadata = cargo_metadata::MetadataCommand::new()
1281 .manifest_path(manifest_path)
1282 .no_deps()
1283 .exec()?;
1284 metadata.target_directory.into_std_path_buf().join(target)
1285 };
1286 let profile = match cargo.profile.as_deref() {
1287 Some("dev" | "test") => "debug",
1288 Some("release" | "bench") => "release",
1289 Some(profile) => profile,
1290 None => {
1291 if release {
1292 "release"
1293 } else {
1294 "debug"
1295 }
1296 }
1297 };
1298 let deps_dir = target_dir.join(profile).join("deps");
1299 fs::create_dir_all(&deps_dir)?;
1300 if !target_dir.join("CACHEDIR.TAG").is_file() {
1301 let _ = write_file(
1303 &target_dir.join("CACHEDIR.TAG"),
1304 "Signature: 8a477f597d28d172789f06886806bc55
1305# This file is a cache directory tag created by cargo.
1306# For information about cache directory tags see https://bford.info/cachedir/
1307",
1308 );
1309 }
1310 write_tbd_files(&deps_dir)?;
1311 } else if target.contains("arm") && target.contains("linux") {
1312 if let Ok(lib_dir) = Zig::lib_dir() {
1314 let arm_features_h = lib_dir
1315 .join("libc")
1316 .join("glibc")
1317 .join("sysdeps")
1318 .join("arm")
1319 .join("arm-features.h");
1320 if !arm_features_h.is_file() {
1321 fs::write(arm_features_h, ARM_FEATURES_H)?;
1322 }
1323 }
1324 } else if target.contains("windows-gnu") {
1325 if let Ok(lib_dir) = Zig::lib_dir() {
1326 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
1327 let synchronization_def = lib_common.join("synchronization.def");
1328 if !synchronization_def.is_file() {
1329 let api_ms_win_core_synch_l1_2_0_def =
1330 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
1331 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
1333 }
1334 }
1335 }
1336 }
1337 Ok(())
1338 }
1339
1340 fn setup_cmake_toolchain(
1341 target: &str,
1342 zig_wrapper: &ZigWrapper,
1343 enable_zig_ar: bool,
1344 ) -> Result<PathBuf> {
1345 let cmake = cache_dir().join("cmake");
1346 fs::create_dir_all(&cmake)?;
1347
1348 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
1349 let triple: Triple = target.parse()?;
1350 let os = triple.operating_system.to_string();
1351 let arch = triple.architecture.to_string();
1352 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1353 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1354 ("darwin", "aarch64") => ("Darwin", "arm64"),
1355 ("linux", arch) => {
1356 let cmake_arch = match arch {
1357 "powerpc" => "ppc",
1358 "powerpc64" => "ppc64",
1359 "powerpc64le" => "ppc64le",
1360 _ => arch,
1361 };
1362 ("Linux", cmake_arch)
1363 }
1364 ("windows", "x86_64") => ("Windows", "AMD64"),
1365 ("windows", "i686") => ("Windows", "X86"),
1366 ("windows", "aarch64") => ("Windows", "ARM64"),
1367 (os, arch) => (os, arch),
1368 };
1369 let mut content = format!(
1370 r#"
1371set(CMAKE_SYSTEM_NAME {system_name})
1372set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1373set(CMAKE_C_COMPILER {cc})
1374set(CMAKE_CXX_COMPILER {cxx})
1375set(CMAKE_RANLIB {ranlib})
1376set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1377set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1378 system_name = system_name,
1379 system_processor = system_processor,
1380 cc = zig_wrapper.cc.to_slash_lossy(),
1381 cxx = zig_wrapper.cxx.to_slash_lossy(),
1382 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1383 );
1384 if enable_zig_ar {
1385 content.push_str(&format!(
1386 "\nset(CMAKE_AR {})\n",
1387 zig_wrapper.ar.to_slash_lossy()
1388 ));
1389 }
1390 write_file(&toolchain_file, &content)?;
1391 Ok(toolchain_file)
1392 }
1393
1394 #[cfg(target_os = "macos")]
1395 fn macos_sdk_root() -> Option<PathBuf> {
1396 static SDK_ROOT: OnceLock<Option<PathBuf>> = OnceLock::new();
1397
1398 SDK_ROOT
1399 .get_or_init(|| match env::var_os("SDKROOT") {
1400 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1401 _ => {
1402 let output = Command::new("xcrun")
1403 .args(["--sdk", "macosx", "--show-sdk-path"])
1404 .output()
1405 .ok()?;
1406 if output.status.success() {
1407 let stdout = String::from_utf8(output.stdout).ok()?;
1408 let stdout = stdout.trim();
1409 if !stdout.is_empty() {
1410 return Some(stdout.into());
1411 }
1412 }
1413 None
1414 }
1415 })
1416 .clone()
1417 }
1418
1419 #[cfg(not(target_os = "macos"))]
1420 fn macos_sdk_root() -> Option<PathBuf> {
1421 match env::var_os("SDKROOT") {
1422 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1423 _ => None,
1424 }
1425 }
1426}
1427
1428fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1429 let existing_content = fs::read_to_string(path).unwrap_or_default();
1430 if existing_content != content {
1431 fs::write(path, content)?;
1432 }
1433 Ok(())
1434}
1435
1436fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1437 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1438 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1439 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1440 Ok(())
1441}
1442
1443fn cache_dir() -> PathBuf {
1444 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1445 .ok()
1446 .map(|s| s.into())
1447 .or_else(dirs::cache_dir)
1448 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1450 .join(env!("CARGO_PKG_NAME"))
1451 .join(env!("CARGO_PKG_VERSION"))
1452}
1453
1454#[derive(Debug, Deserialize)]
1455struct ZigEnv {
1456 lib_dir: String,
1457}
1458
1459#[derive(Debug, Clone)]
1461pub struct ZigWrapper {
1462 pub cc: PathBuf,
1463 pub cxx: PathBuf,
1464 pub ar: PathBuf,
1465 pub ranlib: PathBuf,
1466 pub lib: PathBuf,
1467}
1468
1469#[derive(Debug, Clone, Default, PartialEq)]
1470struct TargetFlags {
1471 pub target_cpu: String,
1472 pub target_feature: String,
1473}
1474
1475impl TargetFlags {
1476 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1477 let mut parsed = Self::default();
1478
1479 let f = rustflags::from_encoded(encoded);
1480 for flag in f {
1481 if let rustflags::Flag::Codegen { opt, value } = flag {
1482 let key = opt.replace('-', "_");
1483 match key.as_str() {
1484 "target_cpu" => {
1485 if let Some(value) = value {
1486 parsed.target_cpu = value;
1487 }
1488 }
1489 "target_feature" => {
1490 if let Some(value) = value {
1492 if !parsed.target_feature.is_empty() {
1493 parsed.target_feature.push(',');
1494 }
1495 parsed.target_feature.push_str(&value);
1496 }
1497 }
1498 _ => {}
1499 }
1500 }
1501 }
1502 Ok(parsed)
1503 }
1504}
1505
1506#[allow(clippy::blocks_in_conditions)]
1515pub fn prepare_zig_linker(target: &str) -> Result<ZigWrapper> {
1516 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1517 let abi_suffix = if abi_suffix.is_empty() {
1518 String::new()
1519 } else {
1520 if abi_suffix
1521 .split_once('.')
1522 .filter(|(x, y)| {
1523 !x.is_empty()
1524 && x.chars().all(|c| c.is_ascii_digit())
1525 && !y.is_empty()
1526 && y.chars().all(|c| c.is_ascii_digit())
1527 })
1528 .is_none()
1529 {
1530 bail!("Malformed zig target abi suffix.")
1531 }
1532 format!(".{abi_suffix}")
1533 };
1534 let triple: Triple = rust_target
1535 .parse()
1536 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1537 let arch = triple.architecture.to_string();
1538 let target_env = match (triple.architecture, triple.environment) {
1539 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1540 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1541 (_, Environment::GnuLlvm) => Environment::Gnu,
1542 (_, environment) => environment,
1543 };
1544 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1545 let file_target = target.trim_end_matches('.');
1546
1547 let mut cc_args = vec![
1548 "-g".to_owned(),
1550 "-fno-sanitize=all".to_owned(),
1552 ];
1553
1554 let zig_mcpu_default = match triple.operating_system {
1557 OperatingSystem::Linux => {
1558 match arch.as_str() {
1559 "arm" => match target_env {
1561 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1562 Environment::Gnueabihf | Environment::Musleabihf => {
1563 "generic+v6+strict_align+vfp2-d32"
1564 }
1565 _ => "",
1566 },
1567 "armv5te" => "generic+soft_float+strict_align",
1568 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1569 arch_str @ ("i586" | "i686") => {
1570 if arch_str == "i586" {
1571 "pentium"
1572 } else {
1573 "pentium4"
1574 }
1575 }
1576 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1577 "s390x" => "z10-vector",
1578 _ => "",
1579 }
1580 }
1581 _ => "",
1582 };
1583
1584 let zig_mcpu_override = {
1588 let cargo_config = cargo_config2::Config::load()?;
1589 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1590 let encoded_rust_flags = rust_flags.encode()?;
1591 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1592 target_flags.target_cpu.replace('-', "_")
1595 };
1596
1597 if !zig_mcpu_override.is_empty() {
1598 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1599 } else if !zig_mcpu_default.is_empty() {
1600 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1601 }
1602
1603 match triple.operating_system {
1604 OperatingSystem::Linux => {
1605 let zig_arch = match arch.as_str() {
1606 "arm" => "arm",
1608 "armv5te" => "arm",
1609 "armv7" => "arm",
1610 "i586" | "i686" => {
1611 let zig_version = Zig::zig_version()?;
1612 if zig_version.major == 0 && zig_version.minor >= 11 {
1613 "x86"
1614 } else {
1615 "i386"
1616 }
1617 }
1618 "riscv64gc" => "riscv64",
1619 "s390x" => "s390x",
1620 _ => arch.as_str(),
1621 };
1622 let mut zig_target_env = target_env.to_string();
1623
1624 let zig_version = Zig::zig_version()?;
1625
1626 if zig_version >= semver::Version::new(0, 15, 0)
1630 && arch.as_str() == "armv7"
1631 && target_env == Environment::Ohos
1632 {
1633 zig_target_env = "ohoseabi".to_string();
1634 }
1635
1636 cc_args.push("-target".to_string());
1637 cc_args.push(format!("{zig_arch}-linux-{zig_target_env}{abi_suffix}"));
1638 }
1639 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1640 let zig_version = Zig::zig_version()?;
1641 if zig_version > semver::Version::new(0, 9, 1) {
1644 cc_args.push("-target".to_string());
1645 cc_args.push(format!("{arch}-macos-none{abi_suffix}"));
1646 } else {
1647 cc_args.push("-target".to_string());
1648 cc_args.push(format!("{arch}-macos-gnu{abi_suffix}"));
1649 }
1650 }
1651 OperatingSystem::Windows => {
1652 let zig_arch = match arch.as_str() {
1653 "i686" => {
1654 let zig_version = Zig::zig_version()?;
1655 if zig_version.major == 0 && zig_version.minor >= 11 {
1656 "x86"
1657 } else {
1658 "i386"
1659 }
1660 }
1661 arch => arch,
1662 };
1663 cc_args.push("-target".to_string());
1664 cc_args.push(format!("{zig_arch}-windows-{target_env}{abi_suffix}"));
1665 }
1666 OperatingSystem::Emscripten => {
1667 cc_args.push("-target".to_string());
1668 cc_args.push(format!("{arch}-emscripten{abi_suffix}"));
1669 }
1670 OperatingSystem::Wasi => {
1671 cc_args.push("-target".to_string());
1672 cc_args.push(format!("{arch}-wasi{abi_suffix}"));
1673 }
1674 OperatingSystem::WasiP1 => {
1675 cc_args.push("-target".to_string());
1676 cc_args.push(format!("{arch}-wasi.0.1.0{abi_suffix}"));
1677 }
1678 OperatingSystem::Freebsd => {
1679 let zig_arch = match arch.as_str() {
1680 "i686" => {
1681 let zig_version = Zig::zig_version()?;
1682 if zig_version.major == 0 && zig_version.minor >= 11 {
1683 "x86"
1684 } else {
1685 "i386"
1686 }
1687 }
1688 arch => arch,
1689 };
1690 cc_args.push("-target".to_string());
1691 cc_args.push(format!("{zig_arch}-freebsd"));
1692 }
1693 OperatingSystem::Unknown => {
1694 if triple.architecture == Architecture::Wasm32
1695 || triple.architecture == Architecture::Wasm64
1696 {
1697 cc_args.push("-target".to_string());
1698 cc_args.push(format!("{arch}-freestanding{abi_suffix}"));
1699 } else {
1700 bail!("unsupported target '{rust_target}'")
1701 }
1702 }
1703 _ => bail!(format!("unsupported target '{rust_target}'")),
1704 };
1705
1706 let zig_linker_dir = cache_dir();
1707 fs::create_dir_all(&zig_linker_dir)?;
1708
1709 if triple.operating_system == OperatingSystem::Linux {
1710 if matches!(
1711 triple.environment,
1712 Environment::Gnu
1713 | Environment::Gnuspe
1714 | Environment::Gnux32
1715 | Environment::Gnueabi
1716 | Environment::Gnuabi64
1717 | Environment::GnuIlp32
1718 | Environment::Gnueabihf
1719 ) {
1720 let glibc_version = if abi_suffix.is_empty() {
1721 (2, 17)
1722 } else {
1723 let mut parts = abi_suffix[1..].split('.');
1724 let major: usize = parts.next().unwrap().parse()?;
1725 let minor: usize = parts.next().unwrap().parse()?;
1726 (major, minor)
1727 };
1728 if glibc_version < (2, 28) {
1730 use crate::linux::{FCNTL_H, FCNTL_MAP};
1731
1732 let zig_version = Zig::zig_version()?;
1733 if zig_version.major == 0 && zig_version.minor < 11 {
1734 let fcntl_map = zig_linker_dir.join("fcntl.map");
1735 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1736 if existing_content != FCNTL_MAP {
1737 fs::write(&fcntl_map, FCNTL_MAP)?;
1738 }
1739 let fcntl_h = zig_linker_dir.join("fcntl.h");
1740 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1741 if existing_content != FCNTL_H {
1742 fs::write(&fcntl_h, FCNTL_H)?;
1743 }
1744
1745 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1746 cc_args.push("-include".to_string());
1747 cc_args.push(fcntl_h.display().to_string());
1748 }
1749 }
1750 } else if matches!(
1751 triple.environment,
1752 Environment::Musl
1753 | Environment::Muslabi64
1754 | Environment::Musleabi
1755 | Environment::Musleabihf
1756 ) {
1757 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1758
1759 let zig_version = Zig::zig_version()?;
1760 let rustc_version = rustc_version::version_meta()?.semver;
1761
1762 if (zig_version.major, zig_version.minor) >= (0, 11)
1767 && (rustc_version.major, rustc_version.minor) < (1, 72)
1768 {
1769 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1770 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1771
1772 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1773 }
1774 }
1775 }
1776
1777 let cc_args_str = join_args_for_script(&cc_args);
1780 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1781 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1782 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1783 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1784 let zig_version = Zig::zig_version()?;
1785 write_linker_wrapper(&zig_cc, "cc", &cc_args_str, &zig_version)?;
1786 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str, &zig_version)?;
1787 write_linker_wrapper(&zig_ranlib, "ranlib", "", &zig_version)?;
1788
1789 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1790 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1791 symlink_wrapper(&zig_ar)?;
1792 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1793 symlink_wrapper(&zig_lib)?;
1794
1795 if matches!(triple.operating_system, OperatingSystem::Windows)
1801 && matches!(triple.environment, Environment::Gnu)
1802 {
1803 if !has_system_dlltool(&triple.architecture) {
1806 let dlltool_name = get_dlltool_name(&triple.architecture);
1807 let zig_dlltool = zig_linker_dir.join(format!("{dlltool_name}{exe_ext}"));
1808 symlink_wrapper(&zig_dlltool)?;
1809 }
1810 }
1811
1812 Ok(ZigWrapper {
1813 cc: zig_cc,
1814 cxx: zig_cxx,
1815 ar: zig_ar,
1816 ranlib: zig_ranlib,
1817 lib: zig_lib,
1818 })
1819}
1820
1821fn symlink_wrapper(target: &Path) -> Result<()> {
1822 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1823 PathBuf::from(exe)
1824 } else {
1825 env::current_exe()?
1826 };
1827 #[cfg(windows)]
1828 {
1829 if !target.exists() {
1830 if std::fs::hard_link(¤t_exe, target).is_err() {
1832 std::fs::copy(¤t_exe, target)?;
1834 }
1835 }
1836 }
1837
1838 #[cfg(unix)]
1839 {
1840 if !target.exists() {
1841 if fs::read_link(target).is_ok() {
1842 fs::remove_file(target)?;
1844 }
1845 std::os::unix::fs::symlink(current_exe, target)?;
1846 }
1847 }
1848 Ok(())
1849}
1850
1851#[cfg(target_family = "unix")]
1853fn join_args_for_script<I, S>(args: I) -> String
1854where
1855 I: IntoIterator<Item = S>,
1856 S: AsRef<str>,
1857{
1858 shell_words::join(args)
1859}
1860
1861#[cfg(not(target_family = "unix"))]
1867fn quote_for_batch(s: &str) -> String {
1868 let needs_quoting_or_escaping = s.is_empty()
1869 || s.contains(|c: char| {
1870 matches!(
1871 c,
1872 ' ' | '\t' | '"' | '&' | '|' | '<' | '>' | '^' | '%' | '(' | ')' | '!'
1873 )
1874 });
1875
1876 if !needs_quoting_or_escaping {
1877 return s.to_string();
1878 }
1879
1880 let mut out = String::with_capacity(s.len() + 8);
1881 out.push('"');
1882 for c in s.chars() {
1883 match c {
1884 '"' => out.push_str("\"\""),
1885 '%' => out.push_str("%%"),
1886 _ => out.push(c),
1887 }
1888 }
1889 out.push('"');
1890 out
1891}
1892
1893#[cfg(not(target_family = "unix"))]
1895fn join_args_for_script<I, S>(args: I) -> String
1896where
1897 I: IntoIterator<Item = S>,
1898 S: AsRef<str>,
1899{
1900 args.into_iter()
1901 .map(|s| quote_for_batch(s.as_ref()))
1902 .collect::<Vec<_>>()
1903 .join(" ")
1904}
1905
1906#[cfg(target_family = "unix")]
1908fn write_linker_wrapper(
1909 path: &Path,
1910 command: &str,
1911 args: &str,
1912 zig_version: &semver::Version,
1913) -> Result<()> {
1914 let mut buf = Vec::<u8>::new();
1915 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1916 PathBuf::from(exe)
1917 } else {
1918 env::current_exe()?
1919 };
1920 writeln!(&mut buf, "#!/bin/sh")?;
1921
1922 writeln!(
1924 &mut buf,
1925 "export CARGO_ZIGBUILD_ZIG_VERSION={}",
1926 zig_version
1927 )?;
1928
1929 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
1931
1932 writeln!(
1933 &mut buf,
1934 "exec \"{}\" zig {} -- {} \"$@\"",
1935 current_exe.display(),
1936 command,
1937 args
1938 )?;
1939
1940 let existing_content = fs::read(path).unwrap_or_default();
1944 if existing_content != buf {
1945 OpenOptions::new()
1946 .create(true)
1947 .write(true)
1948 .truncate(true)
1949 .mode(0o700)
1950 .open(path)?
1951 .write_all(&buf)?;
1952 }
1953 Ok(())
1954}
1955
1956#[cfg(not(target_family = "unix"))]
1958fn write_linker_wrapper(
1959 path: &Path,
1960 command: &str,
1961 args: &str,
1962 zig_version: &semver::Version,
1963) -> Result<()> {
1964 let mut buf = Vec::<u8>::new();
1965 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1966 PathBuf::from(exe)
1967 } else {
1968 env::current_exe()?
1969 };
1970 let current_exe = if is_mingw_shell() {
1971 current_exe.to_slash_lossy().to_string()
1972 } else {
1973 current_exe.display().to_string()
1974 };
1975 writeln!(&mut buf, "@echo off")?;
1976 writeln!(&mut buf, "setlocal DisableDelayedExpansion")?;
1978 writeln!(&mut buf, "set CARGO_ZIGBUILD_ZIG_VERSION={}", zig_version)?;
1980 writeln!(
1981 &mut buf,
1982 "\"{}\" zig {} -- {} %*",
1983 adjust_canonicalization(current_exe),
1984 command,
1985 args
1986 )?;
1987
1988 let existing_content = fs::read(path).unwrap_or_default();
1989 if existing_content != buf {
1990 fs::write(path, buf)?;
1991 }
1992 Ok(())
1993}
1994
1995pub(crate) fn is_mingw_shell() -> bool {
1996 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
1997}
1998
1999#[cfg(target_os = "windows")]
2001pub fn adjust_canonicalization(p: String) -> String {
2002 const VERBATIM_PREFIX: &str = r#"\\?\"#;
2003 if p.starts_with(VERBATIM_PREFIX) {
2004 p[VERBATIM_PREFIX.len()..].to_string()
2005 } else {
2006 p
2007 }
2008}
2009
2010fn python_path() -> Result<PathBuf> {
2011 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
2012 Ok(which::which(python)?)
2013}
2014
2015fn zig_path() -> Result<PathBuf> {
2016 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
2017 Ok(which::which(zig)?)
2018}
2019
2020fn get_dlltool_name(arch: &Architecture) -> &'static str {
2024 if cfg!(windows) {
2025 "dlltool"
2026 } else {
2027 match arch {
2028 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
2029 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
2030 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
2031 _ => "dlltool",
2032 }
2033 }
2034}
2035
2036fn has_system_dlltool(arch: &Architecture) -> bool {
2039 which::which(get_dlltool_name(arch)).is_ok()
2040}
2041
2042#[cfg(test)]
2043mod tests {
2044 use super::*;
2045
2046 #[test]
2047 fn test_target_flags() {
2048 let cases = [
2049 ("-C target-feature=-crt-static", "", "-crt-static"),
2051 ("-C target-cpu=native", "native", ""),
2052 (
2053 "--deny warnings --codegen target-feature=+crt-static",
2054 "",
2055 "+crt-static",
2056 ),
2057 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
2058 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
2059 (
2060 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
2061 "native",
2062 "-avx512bf16,-avx512bitalg",
2063 ),
2064 (
2065 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
2066 "x86-64",
2067 "",
2068 ),
2069 (
2070 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
2071 "",
2072 "+crt-static,+avx",
2073 ),
2074 ];
2075
2076 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
2077 let args = cargo_config2::Flags::from_space_separated(input);
2078 let encoded_rust_flags = args.encode().unwrap();
2079 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
2080 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
2081 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
2082 }
2083 }
2084
2085 #[test]
2086 fn test_join_args_for_script() {
2087 let args = vec!["-target", "x86_64-linux-gnu"];
2089 let result = join_args_for_script(&args);
2090 assert!(result.contains("-target"));
2091 assert!(result.contains("x86_64-linux-gnu"));
2092 }
2093
2094 #[test]
2095 #[cfg(not(target_family = "unix"))]
2096 fn test_quote_for_batch() {
2097 assert_eq!(quote_for_batch("-target"), "-target");
2099 assert_eq!(quote_for_batch("x86_64-linux-gnu"), "x86_64-linux-gnu");
2100
2101 assert_eq!(
2103 quote_for_batch("C:\\Users\\John Doe\\path"),
2104 "\"C:\\Users\\John Doe\\path\""
2105 );
2106
2107 assert_eq!(quote_for_batch(""), "\"\"");
2109
2110 assert_eq!(quote_for_batch("foo&bar"), "\"foo&bar\"");
2112 assert_eq!(quote_for_batch("foo|bar"), "\"foo|bar\"");
2113 assert_eq!(quote_for_batch("foo<bar"), "\"foo<bar\"");
2114 assert_eq!(quote_for_batch("foo>bar"), "\"foo>bar\"");
2115 assert_eq!(quote_for_batch("foo^bar"), "\"foo^bar\"");
2116 assert_eq!(quote_for_batch("foo%bar"), "\"foo%bar\"");
2117
2118 assert_eq!(quote_for_batch("foo\"bar"), "\"foo\"\"bar\"");
2120 }
2121
2122 #[test]
2123 #[cfg(not(target_family = "unix"))]
2124 fn test_join_args_for_script_windows() {
2125 let args = vec![
2127 "-target",
2128 "x86_64-linux-gnu",
2129 "-L",
2130 "C:\\Users\\John Doe\\path",
2131 ];
2132 let result = join_args_for_script(&args);
2133 assert!(result.contains("\"C:\\Users\\John Doe\\path\""));
2135 assert!(result.contains("-target"));
2137 assert!(!result.contains("\"-target\""));
2138 }
2139}