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