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_riscv64(&self) -> bool {
110 self.target
111 .as_ref()
112 .map(|x| x.starts_with("riscv64"))
113 .unwrap_or_default()
114 }
115
116 fn is_riscv32(&self) -> bool {
117 self.target
118 .as_ref()
119 .map(|x| x.starts_with("riscv32"))
120 .unwrap_or_default()
121 }
122
123 fn is_mips32(&self) -> bool {
124 self.target
125 .as_ref()
126 .map(|x| x.starts_with("mips") && !x.starts_with("mips64"))
127 .unwrap_or_default()
128 }
129
130 fn is_musl(&self) -> bool {
132 self.target
133 .as_ref()
134 .map(|x| x.contains("musl"))
135 .unwrap_or_default()
136 }
137
138 fn is_macos(&self) -> bool {
140 self.target
141 .as_ref()
142 .map(|x| x.contains("macos"))
143 .unwrap_or_default()
144 }
145
146 fn is_darwin(&self) -> bool {
147 self.target
148 .as_ref()
149 .map(|x| x.contains("darwin"))
150 .unwrap_or_default()
151 }
152
153 fn is_apple_platform(&self) -> bool {
154 self.target
155 .as_ref()
156 .map(|x| {
157 x.contains("macos")
158 || x.contains("darwin")
159 || x.contains("ios")
160 || x.contains("tvos")
161 || x.contains("watchos")
162 || x.contains("visionos")
163 })
164 .unwrap_or_default()
165 }
166
167 fn is_ios(&self) -> bool {
168 self.target
169 .as_ref()
170 .map(|x| x.contains("ios") && !x.contains("visionos"))
171 .unwrap_or_default()
172 }
173
174 fn is_tvos(&self) -> bool {
175 self.target
176 .as_ref()
177 .map(|x| x.contains("tvos"))
178 .unwrap_or_default()
179 }
180
181 fn is_watchos(&self) -> bool {
182 self.target
183 .as_ref()
184 .map(|x| x.contains("watchos"))
185 .unwrap_or_default()
186 }
187
188 fn is_visionos(&self) -> bool {
189 self.target
190 .as_ref()
191 .map(|x| x.contains("visionos"))
192 .unwrap_or_default()
193 }
194
195 fn apple_cpu(&self) -> &'static str {
197 if self.is_macos() || self.is_darwin() {
198 "apple_m1" } else if self.is_visionos() {
200 "apple_m2" } else if self.is_watchos() {
202 "apple_s5" } else if self.is_ios() || self.is_tvos() {
204 "apple_a14" } else {
206 "generic"
207 }
208 }
209
210 fn is_freebsd(&self) -> bool {
211 self.target
212 .as_ref()
213 .map(|x| x.contains("freebsd"))
214 .unwrap_or_default()
215 }
216
217 fn is_windows_gnu(&self) -> bool {
218 self.target
219 .as_ref()
220 .map(|x| x.contains("windows-gnu"))
221 .unwrap_or_default()
222 }
223
224 fn is_windows_msvc(&self) -> bool {
225 self.target
226 .as_ref()
227 .map(|x| x.contains("windows-msvc"))
228 .unwrap_or_default()
229 }
230
231 fn is_ohos(&self) -> bool {
232 self.target
233 .as_ref()
234 .map(|x| x.contains("ohos"))
235 .unwrap_or_default()
236 }
237}
238
239impl Zig {
240 pub fn execute(&self) -> Result<()> {
242 match self {
243 Zig::Cc { args } => self.execute_compiler("cc", args),
244 Zig::Cxx { args } => self.execute_compiler("c++", args),
245 Zig::Ar { args } => self.execute_tool("ar", args),
246 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
247 Zig::Lib { args } => self.execute_compiler("lib", args),
248 Zig::Dlltool { args } => self.execute_tool("dlltool", args),
249 }
250 }
251
252 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
254 let target = cmd_args
255 .iter()
256 .position(|x| x == "-target")
257 .and_then(|index| cmd_args.get(index + 1));
258 let target_info = TargetInfo::new(target);
259
260 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
261 Ok(version) => version.parse()?,
262 Err(_) => rustc_version::version()?,
263 };
264 let zig_version = Zig::zig_version()?;
265
266 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
267 let mut skip_next_arg = false;
268 for arg in cmd_args {
269 if skip_next_arg {
270 skip_next_arg = false;
271 continue;
272 }
273 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
274 vec![self.process_linker_response_file(
275 arg,
276 &rustc_ver,
277 &zig_version,
278 &target_info,
279 )?]
280 } else {
281 self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info)
282 };
283 for arg in args {
284 if arg == "-Wl,-exported_symbols_list" || arg == "-Wl,--dynamic-list" {
285 skip_next_arg = true;
289 } else {
290 new_cmd_args.push(arg);
291 }
292 }
293 }
294
295 if target_info.is_mips32() {
296 new_cmd_args.push("-Wl,-z,notext".to_string());
298 }
299
300 if self.has_undefined_dynamic_lookup(cmd_args) {
301 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
302 }
303 if target_info.is_macos() {
304 if self.should_add_libcharset(cmd_args, &zig_version) {
305 new_cmd_args.push("-lcharset".to_string());
306 }
307 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
308 }
309
310 let mut command = Self::command()?;
313 if (zig_version.major, zig_version.minor) >= (0, 15) {
314 if let Some(sdkroot) = Self::macos_sdk_root() {
315 command.env("SDKROOT", sdkroot);
316 }
317 }
318
319 let mut child = command
320 .arg(cmd)
321 .args(new_cmd_args)
322 .spawn()
323 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
324 let status = child.wait().expect("Failed to wait on zig child process");
325 if !status.success() {
326 process::exit(status.code().unwrap_or(1));
327 }
328 Ok(())
329 }
330
331 fn process_linker_response_file(
332 &self,
333 arg: &str,
334 rustc_ver: &rustc_version::Version,
335 zig_version: &semver::Version,
336 target_info: &TargetInfo,
337 ) -> Result<String> {
338 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
342 let content = if target_info.is_windows_msvc() {
343 if content_bytes[0..2] != [255, 254] {
344 bail!(
345 "linker response file `{}` didn't start with a utf16 BOM",
346 &arg
347 );
348 }
349 let content_utf16: Vec<u16> = content_bytes[2..]
350 .chunks_exact(2)
351 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
352 .collect();
353 String::from_utf16(&content_utf16).with_context(|| {
354 format!(
355 "linker response file `{}` didn't contain valid utf16 content",
356 &arg
357 )
358 })?
359 } else {
360 String::from_utf8(content_bytes).with_context(|| {
361 format!(
362 "linker response file `{}` didn't contain valid utf8 content",
363 &arg
364 )
365 })?
366 };
367 let mut link_args: Vec<_> = content
368 .split('\n')
369 .flat_map(|arg| self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info))
370 .collect();
371 if self.has_undefined_dynamic_lookup(&link_args) {
372 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
373 }
374 if target_info.is_macos() && self.should_add_libcharset(&link_args, &zig_version) {
375 link_args.push("-lcharset".to_string());
376 }
377 if target_info.is_windows_msvc() {
378 let new_content = link_args.join("\n");
379 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
380 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
382 out.push(c as u8);
384 out.push((c >> 8) as u8);
385 }
386 fs::write(arg.trim_start_matches('@'), out)?;
387 } else {
388 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
389 }
390 Ok(arg.to_string())
391 }
392
393 fn filter_linker_arg(
394 &self,
395 arg: &str,
396 rustc_ver: &rustc_version::Version,
397 zig_version: &semver::Version,
398 target_info: &TargetInfo,
399 ) -> Vec<String> {
400 if arg == "-lgcc_s" {
401 return vec!["-lunwind".to_string()];
403 } else if arg.starts_with("--target=") {
404 return vec![];
406 } else if arg.starts_with("-e") && arg.len() > 2 && !arg.starts_with("-export") {
407 let entry = &arg[2..];
411 return vec![format!("-Wl,--entry={}", entry)];
412 }
413 if (target_info.is_arm() || target_info.is_windows_gnu())
414 && arg.ends_with(".rlib")
415 && arg.contains("libcompiler_builtins-")
416 {
417 return vec![];
419 }
420 if target_info.is_windows_gnu() {
421 #[allow(clippy::if_same_then_else)]
422 if arg == "-lgcc_eh" {
423 return vec!["-lc++".to_string()];
426 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
427 return vec!["-Wl,-search_paths_first".to_owned()];
432 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
433 return vec![];
434 } else if arg == "-Wl,--disable-auto-image-base"
435 || arg == "-Wl,--dynamicbase"
436 || arg == "-Wl,--large-address-aware"
437 || (arg.starts_with("-Wl,")
438 && (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
439 {
440 return vec![];
445 } else if arg == "-lmsvcrt" {
446 return vec![];
447 }
448 } else if arg == "-Wl,--no-undefined-version" {
449 return vec![];
452 } else if arg == "-Wl,-znostart-stop-gc" {
453 return vec![];
456 } else if arg.starts_with("-Wl,-plugin-opt") {
457 return vec![];
461 }
462 if target_info.is_musl() || target_info.is_ohos() {
463 if arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt") {
465 return vec![];
466 } else if arg == "-Wl,-melf_i386" {
467 return vec![];
469 }
470 if rustc_ver.major == 1
471 && rustc_ver.minor < 59
472 && arg.ends_with(".rlib")
473 && arg.contains("liblibc-")
474 {
475 return vec![];
478 }
479 if arg == "-lc" {
480 return vec![];
481 }
482 }
483 if arg.starts_with("-march=") {
484 if target_info.is_arm() || target_info.is_i386() {
486 return vec![];
487 } else if target_info.is_riscv64() {
488 return vec!["-march=generic_rv64".to_string()];
489 } else if target_info.is_riscv32() {
490 return vec!["-march=generic_rv32".to_string()];
491 } else if arg.starts_with("-march=armv") {
492 if target_info.is_aarch64() || target_info.is_aarch64_be() {
495 let march_value = arg.strip_prefix("-march=").unwrap();
497 let features = if let Some(pos) = march_value.find('+') {
499 &march_value[pos..]
500 } else {
501 ""
502 };
503 let base_cpu = if target_info.is_apple_platform() {
504 target_info.apple_cpu()
505 } else {
506 "generic"
507 };
508 let mut result = vec![format!("-mcpu={}{}", base_cpu, features)];
509 if features.contains("+crypto") {
510 result.append(&mut vec!["-Xassembler".to_owned(), arg.to_string()]);
516 }
517 return result;
518 }
519 }
520 }
521 if target_info.is_macos() {
522 if arg.starts_with("-Wl,-exported_symbols_list,") {
523 return vec![];
526 }
527 if arg == "-Wl,-dylib" {
528 return vec![];
530 }
531 }
532 if target_info.is_freebsd() {
533 let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
534 if ignored_libs.contains(&arg) {
535 return vec![];
536 }
537 }
538 vec![arg.to_string()]
539 }
540
541 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
542 let undefined = args
543 .iter()
544 .position(|x| x == "-undefined")
545 .and_then(|i| args.get(i + 1));
546 matches!(undefined, Some(x) if x == "dynamic_lookup")
547 }
548
549 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
550 if (zig_version.major, zig_version.minor) >= (0, 12) {
552 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
553 } else {
554 false
555 }
556 }
557
558 fn add_macos_specific_args(
559 &self,
560 new_cmd_args: &mut Vec<String>,
561 zig_version: &semver::Version,
562 ) -> Result<()> {
563 let sdkroot = Self::macos_sdk_root();
564 if (zig_version.major, zig_version.minor) >= (0, 12) {
565 if let Some(ref sdkroot) = sdkroot {
569 if (zig_version.major, zig_version.minor) < (0, 15) {
570 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
571 }
572 }
574 }
575 if let Some(ref sdkroot) = sdkroot {
576 if (zig_version.major, zig_version.minor) < (0, 15) {
577 new_cmd_args.extend_from_slice(&[
579 "-isystem".to_string(),
580 format!("{}", sdkroot.join("usr").join("include").display()),
581 format!("-L{}", sdkroot.join("usr").join("lib").display()),
582 format!(
583 "-F{}",
584 sdkroot
585 .join("System")
586 .join("Library")
587 .join("Frameworks")
588 .display()
589 ),
590 "-DTARGET_OS_IPHONE=0".to_string(),
591 ]);
592 } else {
593 new_cmd_args.extend_from_slice(&[
596 "-isystem".to_string(),
597 format!("{}", sdkroot.join("usr").join("include").display()),
598 format!("-L{}", sdkroot.join("usr").join("lib").display()),
599 "-iframework".to_string(),
600 format!(
601 "{}",
602 sdkroot
603 .join("System")
604 .join("Library")
605 .join("Frameworks")
606 .display()
607 ),
608 "-DTARGET_OS_IPHONE=0".to_string(),
609 ]);
610 }
611 }
612
613 let cache_dir = cache_dir();
615 let deps_dir = cache_dir.join("deps");
616 fs::create_dir_all(&deps_dir)?;
617 write_tbd_files(&deps_dir)?;
618 new_cmd_args.push("-L".to_string());
619 new_cmd_args.push(format!("{}", deps_dir.display()));
620 Ok(())
621 }
622
623 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
625 let mut child = Self::command()?
626 .arg(cmd)
627 .args(cmd_args)
628 .spawn()
629 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
630 let status = child.wait().expect("Failed to wait on zig child process");
631 if !status.success() {
632 process::exit(status.code().unwrap_or(1));
633 }
634 Ok(())
635 }
636
637 pub fn command() -> Result<Command> {
639 let (zig, zig_args) = Self::find_zig()?;
640 let mut cmd = Command::new(zig);
641 cmd.args(zig_args);
642 Ok(cmd)
643 }
644
645 fn zig_version() -> Result<semver::Version> {
646 static ZIG_VERSION: OnceLock<semver::Version> = OnceLock::new();
647
648 if let Some(version) = ZIG_VERSION.get() {
649 return Ok(version.clone());
650 }
651 if let Ok(version_str) = env::var("CARGO_ZIGBUILD_ZIG_VERSION") {
653 if let Ok(version) = semver::Version::parse(&version_str) {
654 return Ok(ZIG_VERSION.get_or_init(|| version).clone());
655 }
656 }
657 let output = Self::command()?.arg("version").output()?;
658 let version_str =
659 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
660 let version = semver::Version::parse(version_str.trim())?;
661 Ok(ZIG_VERSION.get_or_init(|| version).clone())
662 }
663
664 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
666 static ZIG_PATH: OnceLock<(PathBuf, Vec<String>)> = OnceLock::new();
667
668 if let Some(cached) = ZIG_PATH.get() {
669 return Ok(cached.clone());
670 }
671 let result = Self::find_zig_python()
672 .or_else(|_| Self::find_zig_bin())
673 .context("Failed to find zig")?;
674 Ok(ZIG_PATH.get_or_init(|| result).clone())
675 }
676
677 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
679 let zig_path = zig_path()?;
680 let output = Command::new(&zig_path).arg("version").output()?;
681
682 let version_str = str::from_utf8(&output.stdout).with_context(|| {
683 format!("`{} version` didn't return utf8 output", zig_path.display())
684 })?;
685 Self::validate_zig_version(version_str)?;
686 Ok((zig_path, Vec::new()))
687 }
688
689 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
691 let python_path = python_path()?;
692 let output = Command::new(&python_path)
693 .args(["-m", "ziglang", "version"])
694 .output()?;
695
696 let version_str = str::from_utf8(&output.stdout).with_context(|| {
697 format!(
698 "`{} -m ziglang version` didn't return utf8 output",
699 python_path.display()
700 )
701 })?;
702 Self::validate_zig_version(version_str)?;
703 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
704 }
705
706 fn validate_zig_version(version: &str) -> Result<()> {
707 let min_ver = semver::Version::new(0, 9, 0);
708 let version = semver::Version::parse(version.trim())?;
709 if version >= min_ver {
710 Ok(())
711 } else {
712 bail!(
713 "zig version {} is too old, need at least {}",
714 version,
715 min_ver
716 )
717 }
718 }
719
720 pub fn lib_dir() -> Result<PathBuf> {
722 static LIB_DIR: OnceLock<PathBuf> = OnceLock::new();
723
724 if let Some(cached) = LIB_DIR.get() {
725 return Ok(cached.clone());
726 }
727 let (zig, zig_args) = Self::find_zig()?;
728 let output = Command::new(zig).args(zig_args).arg("env").output()?;
729 let zig_env: ZigEnv = serde_json::from_slice(&output.stdout)?;
730 Ok(LIB_DIR
731 .get_or_init(|| PathBuf::from(zig_env.lib_dir))
732 .clone())
733 }
734
735 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
736 where
737 K: AsRef<OsStr>,
738 V: AsRef<OsStr>,
739 {
740 let command_env_contains_no_key =
741 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
742
743 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
744 command.env(name, value);
745 }
746 }
747
748 pub(crate) fn apply_command_env(
749 manifest_path: Option<&Path>,
750 release: bool,
751 cargo: &cargo_options::CommonOptions,
752 cmd: &mut Command,
753 enable_zig_ar: bool,
754 ) -> Result<()> {
755 let rust_targets = cargo
757 .target
758 .iter()
759 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
760 .collect::<Vec<&str>>();
761 let rustc_meta = rustc_version::version_meta()?;
762 Self::add_env_if_missing(
763 cmd,
764 "CARGO_ZIGBUILD_RUSTC_VERSION",
765 rustc_meta.semver.to_string(),
766 );
767 let host_target = &rustc_meta.host;
768 for (parsed_target, raw_target) in rust_targets.iter().zip(&cargo.target) {
769 let env_target = parsed_target.replace('-', "_");
770 let zig_wrapper = prepare_zig_linker(raw_target)?;
771
772 if is_mingw_shell() {
773 let zig_cc = zig_wrapper.cc.to_slash_lossy();
774 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
775 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
776 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
777 if !parsed_target.contains("wasm") {
778 Self::add_env_if_missing(
779 cmd,
780 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
781 &*zig_cc,
782 );
783 }
784 } else {
785 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
786 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
787 if !parsed_target.contains("wasm") {
788 Self::add_env_if_missing(
789 cmd,
790 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
791 &zig_wrapper.cc,
792 );
793 }
794 }
795
796 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
797 if enable_zig_ar {
800 if parsed_target.contains("msvc") {
801 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
802 } else {
803 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
804 }
805 }
806
807 Self::setup_os_deps(manifest_path, release, cargo)?;
808
809 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
810 if env::var_os(&cmake_toolchain_file_env).is_none()
811 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
812 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
813 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
814 {
815 if let Ok(cmake_toolchain_file) =
816 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
817 {
818 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
819 }
820 }
821
822 if raw_target.contains("windows-gnu") {
823 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
824 let cache_dir = cache_dir();
827 let existing_path = env::var_os("PATH").unwrap_or_default();
828 let paths = std::iter::once(cache_dir).chain(env::split_paths(&existing_path));
829 if let Ok(new_path) = env::join_paths(paths) {
830 cmd.env("PATH", new_path);
831 }
832 }
833
834 if raw_target.contains("apple-darwin") {
835 if let Some(sdkroot) = Self::macos_sdk_root() {
836 if env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none() {
837 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
839 }
840 }
841 }
842
843 if host_target == parsed_target {
846 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
847 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
850 }
851 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
852 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
853 }
854
855 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
857 .context("Failed to collect `zig cc` options")?;
858 if raw_target.contains("apple-darwin") {
859 options.push("-DTARGET_OS_IPHONE=0".to_string());
861 }
862 let escaped_options = shell_words::join(options.iter().map(|s| &s[..]));
863 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
864 let fallback_value = env::var(bindgen_env);
865 for target in [&env_target[..], parsed_target] {
866 let name = format!("{bindgen_env}_{target}");
867 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
868 if shell_words::split(&value).is_err() {
869 value = shell_words::quote(&value).into_owned();
871 }
872 if !value.is_empty() {
873 value.push(' ');
874 }
875 value.push_str(&escaped_options);
876 unsafe { env::set_var(name, value) };
877 } else {
878 unsafe { env::set_var(name, escaped_options.clone()) };
879 }
880 }
881 }
882 Ok(())
883 }
884
885 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
889 #[derive(Debug, PartialEq, Eq)]
890 enum Kind {
891 Normal,
892 Framework,
893 }
894
895 #[derive(Debug)]
896 struct PerLanguageOptions {
897 glibc_minor_ver: Option<u32>,
898 include_paths: Vec<(Kind, String)>,
899 }
900
901 fn collect_per_language_options(
902 program: &Path,
903 ext: &str,
904 raw_target: &str,
905 ) -> Result<PerLanguageOptions> {
906 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
908 if !empty_file_path.exists() {
909 fs::write(&empty_file_path, "")?;
910 }
911
912 let output = Command::new(program)
913 .arg("-E")
914 .arg(&empty_file_path)
915 .arg("-v")
916 .output()?;
917 let stderr = String::from_utf8(output.stderr)?;
919 if !output.status.success() {
920 bail!(
921 "Failed to run `zig cc -v` with status {}: {}",
922 output.status,
923 stderr.trim(),
924 );
925 }
926
927 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
931 let stderr = &stderr[start + 16..];
932 let end = stderr
933 .find(|c: char| !c.is_ascii_digit())
934 .unwrap_or(stderr.len());
935 stderr[..end].parse().ok()
936 } else {
937 None
938 };
939
940 let start = stderr
941 .find("#include <...> search starts here:")
942 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
943 + 34;
944 let end = stderr
945 .find("End of search list.")
946 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
947
948 let mut include_paths = Vec::new();
949 for mut line in stderr[start..end].lines() {
950 line = line.trim();
951 let mut kind = Kind::Normal;
952 if line.ends_with(" (framework directory)") {
953 line = line[..line.len() - 22].trim();
954 kind = Kind::Framework;
955 } else if line.ends_with(" (headermap)") {
956 bail!("C/C++ search path includes header maps, which are not supported");
957 }
958 if !line.is_empty() {
959 include_paths.push((kind, line.to_owned()));
960 }
961 }
962
963 if raw_target.contains("ohos") {
965 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
966 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
967 }
968
969 Ok(PerLanguageOptions {
970 include_paths,
971 glibc_minor_ver,
972 })
973 }
974
975 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
976 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
977
978 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
980 bail!(
981 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
982 c_opts.glibc_minor_ver,
983 cpp_opts.glibc_minor_ver,
984 );
985 }
986 let c_paths = c_opts.include_paths;
987 let mut cpp_paths = cpp_opts.include_paths;
988 let cpp_pre_len = cpp_paths
989 .iter()
990 .position(|p| {
991 p == c_paths
992 .iter()
993 .filter(|(kind, _)| *kind == Kind::Normal)
994 .next()
995 .unwrap()
996 })
997 .unwrap_or_default();
998 let cpp_post_len = cpp_paths.len()
999 - cpp_paths
1000 .iter()
1001 .position(|p| p == c_paths.last().unwrap())
1002 .unwrap_or_default()
1003 - 1;
1004
1005 let mut args = Vec::new();
1058
1059 args.push("-nostdinc".to_owned());
1062
1063 if raw_target.contains("musl") || raw_target.contains("ohos") {
1071 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
1072 args.push("-D_LARGEFILE64_SOURCE".to_owned());
1075 }
1076 args.extend(
1077 [
1078 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
1079 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
1080 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
1081 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
1082 "-D_LIBCPP_ABI_VERSION=1",
1083 "-D_LIBCPP_ABI_NAMESPACE=__1",
1084 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
1085 "-D_LIBCPP_HAS_LOCALIZATION=1",
1087 "-D_LIBCPP_HAS_WIDE_CHARACTERS=1",
1088 "-D_LIBCPP_HAS_UNICODE=1",
1089 "-D_LIBCPP_HAS_THREADS=1",
1090 "-D_LIBCPP_HAS_MONOTONIC_CLOCK",
1091 ]
1092 .into_iter()
1093 .map(ToString::to_string),
1094 );
1095 if let Some(ver) = c_opts.glibc_minor_ver {
1096 args.push(format!("-D__GLIBC_MINOR__={ver}"));
1098 }
1099
1100 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
1101 if kind != Kind::Normal {
1102 continue;
1104 }
1105 args.push("-cxx-isystem".to_owned());
1111 args.push(path);
1112 }
1113
1114 for (kind, path) in c_paths {
1115 match kind {
1116 Kind::Normal => {
1117 args.push("-Xclang".to_owned());
1119 args.push("-c-isystem".to_owned());
1120 args.push("-Xclang".to_owned());
1121 args.push(path.clone());
1122 args.push("-cxx-isystem".to_owned());
1123 args.push(path);
1124 }
1125 Kind::Framework => {
1126 args.push("-iframework".to_owned());
1127 args.push(path);
1128 }
1129 }
1130 }
1131
1132 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
1133 assert!(kind == Kind::Normal);
1134 args.push("-cxx-isystem".to_owned());
1135 args.push(path);
1136 }
1137
1138 Ok(args)
1139 }
1140
1141 fn setup_os_deps(
1142 manifest_path: Option<&Path>,
1143 release: bool,
1144 cargo: &cargo_options::CommonOptions,
1145 ) -> Result<()> {
1146 for target in &cargo.target {
1147 if target.contains("apple") {
1148 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
1149 target_dir.join(target)
1150 } else {
1151 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
1152 if !manifest_path.exists() {
1153 continue;
1155 }
1156 let metadata = cargo_metadata::MetadataCommand::new()
1157 .manifest_path(manifest_path)
1158 .no_deps()
1159 .exec()?;
1160 metadata.target_directory.into_std_path_buf().join(target)
1161 };
1162 let profile = match cargo.profile.as_deref() {
1163 Some("dev" | "test") => "debug",
1164 Some("release" | "bench") => "release",
1165 Some(profile) => profile,
1166 None => {
1167 if release {
1168 "release"
1169 } else {
1170 "debug"
1171 }
1172 }
1173 };
1174 let deps_dir = target_dir.join(profile).join("deps");
1175 fs::create_dir_all(&deps_dir)?;
1176 if !target_dir.join("CACHEDIR.TAG").is_file() {
1177 let _ = write_file(
1179 &target_dir.join("CACHEDIR.TAG"),
1180 "Signature: 8a477f597d28d172789f06886806bc55
1181# This file is a cache directory tag created by cargo.
1182# For information about cache directory tags see https://bford.info/cachedir/
1183",
1184 );
1185 }
1186 write_tbd_files(&deps_dir)?;
1187 } else if target.contains("arm") && target.contains("linux") {
1188 if let Ok(lib_dir) = Zig::lib_dir() {
1190 let arm_features_h = lib_dir
1191 .join("libc")
1192 .join("glibc")
1193 .join("sysdeps")
1194 .join("arm")
1195 .join("arm-features.h");
1196 if !arm_features_h.is_file() {
1197 fs::write(arm_features_h, ARM_FEATURES_H)?;
1198 }
1199 }
1200 } else if target.contains("windows-gnu") {
1201 if let Ok(lib_dir) = Zig::lib_dir() {
1202 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
1203 let synchronization_def = lib_common.join("synchronization.def");
1204 if !synchronization_def.is_file() {
1205 let api_ms_win_core_synch_l1_2_0_def =
1206 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
1207 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
1209 }
1210 }
1211 }
1212 }
1213 Ok(())
1214 }
1215
1216 fn setup_cmake_toolchain(
1217 target: &str,
1218 zig_wrapper: &ZigWrapper,
1219 enable_zig_ar: bool,
1220 ) -> Result<PathBuf> {
1221 let cmake = cache_dir().join("cmake");
1222 fs::create_dir_all(&cmake)?;
1223
1224 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
1225 let triple: Triple = target.parse()?;
1226 let os = triple.operating_system.to_string();
1227 let arch = triple.architecture.to_string();
1228 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1229 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1230 ("darwin", "aarch64") => ("Darwin", "arm64"),
1231 ("linux", arch) => {
1232 let cmake_arch = match arch {
1233 "powerpc" => "ppc",
1234 "powerpc64" => "ppc64",
1235 "powerpc64le" => "ppc64le",
1236 _ => arch,
1237 };
1238 ("Linux", cmake_arch)
1239 }
1240 ("windows", "x86_64") => ("Windows", "AMD64"),
1241 ("windows", "i686") => ("Windows", "X86"),
1242 ("windows", "aarch64") => ("Windows", "ARM64"),
1243 (os, arch) => (os, arch),
1244 };
1245 let mut content = format!(
1246 r#"
1247set(CMAKE_SYSTEM_NAME {system_name})
1248set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1249set(CMAKE_C_COMPILER {cc})
1250set(CMAKE_CXX_COMPILER {cxx})
1251set(CMAKE_RANLIB {ranlib})
1252set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1253set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1254 system_name = system_name,
1255 system_processor = system_processor,
1256 cc = zig_wrapper.cc.to_slash_lossy(),
1257 cxx = zig_wrapper.cxx.to_slash_lossy(),
1258 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1259 );
1260 if enable_zig_ar {
1261 content.push_str(&format!(
1262 "\nset(CMAKE_AR {})\n",
1263 zig_wrapper.ar.to_slash_lossy()
1264 ));
1265 }
1266 write_file(&toolchain_file, &content)?;
1267 Ok(toolchain_file)
1268 }
1269
1270 #[cfg(target_os = "macos")]
1271 fn macos_sdk_root() -> Option<PathBuf> {
1272 static SDK_ROOT: OnceLock<Option<PathBuf>> = OnceLock::new();
1273
1274 SDK_ROOT
1275 .get_or_init(|| match env::var_os("SDKROOT") {
1276 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1277 _ => {
1278 let output = Command::new("xcrun")
1279 .args(["--sdk", "macosx", "--show-sdk-path"])
1280 .output()
1281 .ok()?;
1282 if output.status.success() {
1283 let stdout = String::from_utf8(output.stdout).ok()?;
1284 let stdout = stdout.trim();
1285 if !stdout.is_empty() {
1286 return Some(stdout.into());
1287 }
1288 }
1289 None
1290 }
1291 })
1292 .clone()
1293 }
1294
1295 #[cfg(not(target_os = "macos"))]
1296 fn macos_sdk_root() -> Option<PathBuf> {
1297 match env::var_os("SDKROOT") {
1298 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1299 _ => None,
1300 }
1301 }
1302}
1303
1304fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1305 let existing_content = fs::read_to_string(path).unwrap_or_default();
1306 if existing_content != content {
1307 fs::write(path, content)?;
1308 }
1309 Ok(())
1310}
1311
1312fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1313 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1314 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1315 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1316 Ok(())
1317}
1318
1319fn cache_dir() -> PathBuf {
1320 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1321 .ok()
1322 .map(|s| s.into())
1323 .or_else(dirs::cache_dir)
1324 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1326 .join(env!("CARGO_PKG_NAME"))
1327 .join(env!("CARGO_PKG_VERSION"))
1328}
1329
1330#[derive(Debug, Deserialize)]
1331struct ZigEnv {
1332 lib_dir: String,
1333}
1334
1335#[derive(Debug, Clone)]
1337pub struct ZigWrapper {
1338 pub cc: PathBuf,
1339 pub cxx: PathBuf,
1340 pub ar: PathBuf,
1341 pub ranlib: PathBuf,
1342 pub lib: PathBuf,
1343}
1344
1345#[derive(Debug, Clone, Default, PartialEq)]
1346struct TargetFlags {
1347 pub target_cpu: String,
1348 pub target_feature: String,
1349}
1350
1351impl TargetFlags {
1352 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1353 let mut parsed = Self::default();
1354
1355 let f = rustflags::from_encoded(encoded);
1356 for flag in f {
1357 if let rustflags::Flag::Codegen { opt, value } = flag {
1358 let key = opt.replace('-', "_");
1359 match key.as_str() {
1360 "target_cpu" => {
1361 if let Some(value) = value {
1362 parsed.target_cpu = value;
1363 }
1364 }
1365 "target_feature" => {
1366 if let Some(value) = value {
1368 if !parsed.target_feature.is_empty() {
1369 parsed.target_feature.push(',');
1370 }
1371 parsed.target_feature.push_str(&value);
1372 }
1373 }
1374 _ => {}
1375 }
1376 }
1377 }
1378 Ok(parsed)
1379 }
1380}
1381
1382#[allow(clippy::blocks_in_conditions)]
1391pub fn prepare_zig_linker(target: &str) -> Result<ZigWrapper> {
1392 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1393 let abi_suffix = if abi_suffix.is_empty() {
1394 String::new()
1395 } else {
1396 if abi_suffix
1397 .split_once('.')
1398 .filter(|(x, y)| {
1399 !x.is_empty()
1400 && x.chars().all(|c| c.is_ascii_digit())
1401 && !y.is_empty()
1402 && y.chars().all(|c| c.is_ascii_digit())
1403 })
1404 .is_none()
1405 {
1406 bail!("Malformed zig target abi suffix.")
1407 }
1408 format!(".{abi_suffix}")
1409 };
1410 let triple: Triple = rust_target
1411 .parse()
1412 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1413 let arch = triple.architecture.to_string();
1414 let target_env = match (triple.architecture, triple.environment) {
1415 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1416 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1417 (_, Environment::GnuLlvm) => Environment::Gnu,
1418 (_, environment) => environment,
1419 };
1420 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1421 let file_target = target.trim_end_matches('.');
1422
1423 let mut cc_args = vec![
1424 "-g".to_owned(),
1426 "-fno-sanitize=all".to_owned(),
1428 ];
1429
1430 let zig_mcpu_default = match triple.operating_system {
1433 OperatingSystem::Linux => {
1434 match arch.as_str() {
1435 "arm" => match target_env {
1437 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1438 Environment::Gnueabihf | Environment::Musleabihf => {
1439 "generic+v6+strict_align+vfp2-d32"
1440 }
1441 _ => "",
1442 },
1443 "armv5te" => "generic+soft_float+strict_align",
1444 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1445 arch_str @ ("i586" | "i686") => {
1446 if arch_str == "i586" {
1447 "pentium"
1448 } else {
1449 "pentium4"
1450 }
1451 }
1452 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1453 "s390x" => "z10-vector",
1454 _ => "",
1455 }
1456 }
1457 _ => "",
1458 };
1459
1460 let zig_mcpu_override = {
1464 let cargo_config = cargo_config2::Config::load()?;
1465 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1466 let encoded_rust_flags = rust_flags.encode()?;
1467 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1468 target_flags.target_cpu.replace('-', "_")
1471 };
1472
1473 if !zig_mcpu_override.is_empty() {
1474 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1475 } else if !zig_mcpu_default.is_empty() {
1476 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1477 }
1478
1479 match triple.operating_system {
1480 OperatingSystem::Linux => {
1481 let zig_arch = match arch.as_str() {
1482 "arm" => "arm",
1484 "armv5te" => "arm",
1485 "armv7" => "arm",
1486 "i586" | "i686" => {
1487 let zig_version = Zig::zig_version()?;
1488 if zig_version.major == 0 && zig_version.minor >= 11 {
1489 "x86"
1490 } else {
1491 "i386"
1492 }
1493 }
1494 "riscv64gc" => "riscv64",
1495 "s390x" => "s390x",
1496 _ => arch.as_str(),
1497 };
1498 let mut zig_target_env = target_env.to_string();
1499
1500 let zig_version = Zig::zig_version()?;
1501
1502 if zig_version >= semver::Version::new(0, 15, 0)
1506 && arch.as_str() == "armv7"
1507 && target_env == Environment::Ohos
1508 {
1509 zig_target_env = "ohoseabi".to_string();
1510 }
1511
1512 cc_args.push("-target".to_string());
1513 cc_args.push(format!("{zig_arch}-linux-{zig_target_env}{abi_suffix}"));
1514 }
1515 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1516 let zig_version = Zig::zig_version()?;
1517 if zig_version > semver::Version::new(0, 9, 1) {
1520 cc_args.push("-target".to_string());
1521 cc_args.push(format!("{arch}-macos-none{abi_suffix}"));
1522 } else {
1523 cc_args.push("-target".to_string());
1524 cc_args.push(format!("{arch}-macos-gnu{abi_suffix}"));
1525 }
1526 }
1527 OperatingSystem::Windows { .. } => {
1528 let zig_arch = match arch.as_str() {
1529 "i686" => {
1530 let zig_version = Zig::zig_version()?;
1531 if zig_version.major == 0 && zig_version.minor >= 11 {
1532 "x86"
1533 } else {
1534 "i386"
1535 }
1536 }
1537 arch => arch,
1538 };
1539 cc_args.push("-target".to_string());
1540 cc_args.push(format!("{zig_arch}-windows-{target_env}{abi_suffix}"));
1541 }
1542 OperatingSystem::Emscripten => {
1543 cc_args.push("-target".to_string());
1544 cc_args.push(format!("{arch}-emscripten{abi_suffix}"));
1545 }
1546 OperatingSystem::Wasi => {
1547 cc_args.push("-target".to_string());
1548 cc_args.push(format!("{arch}-wasi{abi_suffix}"));
1549 }
1550 OperatingSystem::WasiP1 => {
1551 cc_args.push("-target".to_string());
1552 cc_args.push(format!("{arch}-wasi.0.1.0{abi_suffix}"));
1553 }
1554 OperatingSystem::Freebsd => {
1555 let zig_arch = match arch.as_str() {
1556 "i686" => {
1557 let zig_version = Zig::zig_version()?;
1558 if zig_version.major == 0 && zig_version.minor >= 11 {
1559 "x86"
1560 } else {
1561 "i386"
1562 }
1563 }
1564 arch => arch,
1565 };
1566 cc_args.push("-target".to_string());
1567 cc_args.push(format!("{zig_arch}-freebsd"));
1568 }
1569 OperatingSystem::Unknown => {
1570 if triple.architecture == Architecture::Wasm32
1571 || triple.architecture == Architecture::Wasm64
1572 {
1573 cc_args.push("-target".to_string());
1574 cc_args.push(format!("{arch}-freestanding{abi_suffix}"));
1575 } else {
1576 bail!("unsupported target '{rust_target}'")
1577 }
1578 }
1579 _ => bail!(format!("unsupported target '{rust_target}'")),
1580 };
1581
1582 let zig_linker_dir = cache_dir();
1583 fs::create_dir_all(&zig_linker_dir)?;
1584
1585 if triple.operating_system == OperatingSystem::Linux {
1586 if matches!(
1587 triple.environment,
1588 Environment::Gnu
1589 | Environment::Gnuspe
1590 | Environment::Gnux32
1591 | Environment::Gnueabi
1592 | Environment::Gnuabi64
1593 | Environment::GnuIlp32
1594 | Environment::Gnueabihf
1595 ) {
1596 let glibc_version = if abi_suffix.is_empty() {
1597 (2, 17)
1598 } else {
1599 let mut parts = abi_suffix[1..].split('.');
1600 let major: usize = parts.next().unwrap().parse()?;
1601 let minor: usize = parts.next().unwrap().parse()?;
1602 (major, minor)
1603 };
1604 if glibc_version < (2, 28) {
1606 use crate::linux::{FCNTL_H, FCNTL_MAP};
1607
1608 let zig_version = Zig::zig_version()?;
1609 if zig_version.major == 0 && zig_version.minor < 11 {
1610 let fcntl_map = zig_linker_dir.join("fcntl.map");
1611 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1612 if existing_content != FCNTL_MAP {
1613 fs::write(&fcntl_map, FCNTL_MAP)?;
1614 }
1615 let fcntl_h = zig_linker_dir.join("fcntl.h");
1616 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1617 if existing_content != FCNTL_H {
1618 fs::write(&fcntl_h, FCNTL_H)?;
1619 }
1620
1621 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1622 cc_args.push("-include".to_string());
1623 cc_args.push(fcntl_h.display().to_string());
1624 }
1625 }
1626 } else if matches!(
1627 triple.environment,
1628 Environment::Musl
1629 | Environment::Muslabi64
1630 | Environment::Musleabi
1631 | Environment::Musleabihf
1632 ) {
1633 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1634
1635 let zig_version = Zig::zig_version()?;
1636 let rustc_version = rustc_version::version_meta()?.semver;
1637
1638 if (zig_version.major, zig_version.minor) >= (0, 11)
1643 && (rustc_version.major, rustc_version.minor) < (1, 72)
1644 {
1645 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1646 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1647
1648 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1649 }
1650 }
1651 }
1652
1653 let cc_args_str = join_args_for_script(&cc_args);
1656 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1657 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1658 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1659 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1660 let zig_version = Zig::zig_version()?;
1661 write_linker_wrapper(&zig_cc, "cc", &cc_args_str, &zig_version)?;
1662 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str, &zig_version)?;
1663 write_linker_wrapper(&zig_ranlib, "ranlib", "", &zig_version)?;
1664
1665 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1666 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1667 symlink_wrapper(&zig_ar)?;
1668 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1669 symlink_wrapper(&zig_lib)?;
1670
1671 if matches!(triple.operating_system, OperatingSystem::Windows)
1677 && matches!(triple.environment, Environment::Gnu)
1678 {
1679 let dlltool_name: &str = if cfg!(windows) {
1680 "dlltool"
1681 } else {
1682 match triple.architecture {
1683 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
1684 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
1685 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
1686 _ => "dlltool",
1687 }
1688 };
1689 let zig_dlltool = zig_linker_dir.join(format!("{dlltool_name}{exe_ext}"));
1690 symlink_wrapper(&zig_dlltool)?;
1691 }
1692
1693 Ok(ZigWrapper {
1694 cc: zig_cc,
1695 cxx: zig_cxx,
1696 ar: zig_ar,
1697 ranlib: zig_ranlib,
1698 lib: zig_lib,
1699 })
1700}
1701
1702fn symlink_wrapper(target: &Path) -> Result<()> {
1703 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1704 PathBuf::from(exe)
1705 } else {
1706 env::current_exe()?
1707 };
1708 #[cfg(windows)]
1709 {
1710 if !target.exists() {
1711 if std::fs::hard_link(¤t_exe, target).is_err() {
1713 std::fs::copy(¤t_exe, target)?;
1715 }
1716 }
1717 }
1718
1719 #[cfg(unix)]
1720 {
1721 if !target.exists() {
1722 if fs::read_link(target).is_ok() {
1723 fs::remove_file(target)?;
1725 }
1726 std::os::unix::fs::symlink(current_exe, target)?;
1727 }
1728 }
1729 Ok(())
1730}
1731
1732#[cfg(target_family = "unix")]
1734fn join_args_for_script<I, S>(args: I) -> String
1735where
1736 I: IntoIterator<Item = S>,
1737 S: AsRef<str>,
1738{
1739 shell_words::join(args)
1740}
1741
1742#[cfg(not(target_family = "unix"))]
1748fn quote_for_batch(s: &str) -> String {
1749 let needs_quoting_or_escaping = s.is_empty()
1750 || s.contains(|c: char| {
1751 matches!(
1752 c,
1753 ' ' | '\t' | '"' | '&' | '|' | '<' | '>' | '^' | '%' | '(' | ')' | '!'
1754 )
1755 });
1756
1757 if !needs_quoting_or_escaping {
1758 return s.to_string();
1759 }
1760
1761 let mut out = String::with_capacity(s.len() + 8);
1762 out.push('"');
1763 for c in s.chars() {
1764 match c {
1765 '"' => out.push_str("\"\""),
1766 '%' => out.push_str("%%"),
1767 _ => out.push(c),
1768 }
1769 }
1770 out.push('"');
1771 out
1772}
1773
1774#[cfg(not(target_family = "unix"))]
1776fn join_args_for_script<I, S>(args: I) -> String
1777where
1778 I: IntoIterator<Item = S>,
1779 S: AsRef<str>,
1780{
1781 args.into_iter()
1782 .map(|s| quote_for_batch(s.as_ref()))
1783 .collect::<Vec<_>>()
1784 .join(" ")
1785}
1786
1787#[cfg(target_family = "unix")]
1789fn write_linker_wrapper(
1790 path: &Path,
1791 command: &str,
1792 args: &str,
1793 zig_version: &semver::Version,
1794) -> Result<()> {
1795 let mut buf = Vec::<u8>::new();
1796 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1797 PathBuf::from(exe)
1798 } else {
1799 env::current_exe()?
1800 };
1801 writeln!(&mut buf, "#!/bin/sh")?;
1802
1803 writeln!(
1805 &mut buf,
1806 "export CARGO_ZIGBUILD_ZIG_VERSION={}",
1807 zig_version
1808 )?;
1809
1810 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
1812
1813 writeln!(
1814 &mut buf,
1815 "exec \"{}\" zig {} -- {} \"$@\"",
1816 current_exe.display(),
1817 command,
1818 args
1819 )?;
1820
1821 let existing_content = fs::read(path).unwrap_or_default();
1825 if existing_content != buf {
1826 OpenOptions::new()
1827 .create(true)
1828 .write(true)
1829 .truncate(true)
1830 .mode(0o700)
1831 .open(path)?
1832 .write_all(&buf)?;
1833 }
1834 Ok(())
1835}
1836
1837#[cfg(not(target_family = "unix"))]
1839fn write_linker_wrapper(
1840 path: &Path,
1841 command: &str,
1842 args: &str,
1843 zig_version: &semver::Version,
1844) -> Result<()> {
1845 let mut buf = Vec::<u8>::new();
1846 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1847 PathBuf::from(exe)
1848 } else {
1849 env::current_exe()?
1850 };
1851 let current_exe = if is_mingw_shell() {
1852 current_exe.to_slash_lossy().to_string()
1853 } else {
1854 current_exe.display().to_string()
1855 };
1856 writeln!(&mut buf, "@echo off")?;
1857 writeln!(&mut buf, "setlocal DisableDelayedExpansion")?;
1859 writeln!(&mut buf, "set CARGO_ZIGBUILD_ZIG_VERSION={}", zig_version)?;
1861 writeln!(
1862 &mut buf,
1863 "\"{}\" zig {} -- {} %*",
1864 adjust_canonicalization(current_exe),
1865 command,
1866 args
1867 )?;
1868
1869 let existing_content = fs::read(path).unwrap_or_default();
1870 if existing_content != buf {
1871 fs::write(path, buf)?;
1872 }
1873 Ok(())
1874}
1875
1876pub(crate) fn is_mingw_shell() -> bool {
1877 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
1878}
1879
1880#[cfg(target_os = "windows")]
1882pub fn adjust_canonicalization(p: String) -> String {
1883 const VERBATIM_PREFIX: &str = r#"\\?\"#;
1884 if p.starts_with(VERBATIM_PREFIX) {
1885 p[VERBATIM_PREFIX.len()..].to_string()
1886 } else {
1887 p
1888 }
1889}
1890
1891fn python_path() -> Result<PathBuf> {
1892 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
1893 Ok(which::which(python)?)
1894}
1895
1896fn zig_path() -> Result<PathBuf> {
1897 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
1898 Ok(which::which(zig)?)
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903 use super::*;
1904
1905 #[test]
1906 fn test_target_flags() {
1907 let cases = [
1908 ("-C target-feature=-crt-static", "", "-crt-static"),
1910 ("-C target-cpu=native", "native", ""),
1911 (
1912 "--deny warnings --codegen target-feature=+crt-static",
1913 "",
1914 "+crt-static",
1915 ),
1916 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
1917 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
1918 (
1919 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
1920 "native",
1921 "-avx512bf16,-avx512bitalg",
1922 ),
1923 (
1924 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
1925 "x86-64",
1926 "",
1927 ),
1928 (
1929 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
1930 "",
1931 "+crt-static,+avx",
1932 ),
1933 ];
1934
1935 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
1936 let args = cargo_config2::Flags::from_space_separated(input);
1937 let encoded_rust_flags = args.encode().unwrap();
1938 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
1939 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
1940 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
1941 }
1942 }
1943
1944 #[test]
1945 fn test_join_args_for_script() {
1946 let args = vec!["-target", "x86_64-linux-gnu"];
1948 let result = join_args_for_script(&args);
1949 assert!(result.contains("-target"));
1950 assert!(result.contains("x86_64-linux-gnu"));
1951 }
1952
1953 #[test]
1954 #[cfg(not(target_family = "unix"))]
1955 fn test_quote_for_batch() {
1956 assert_eq!(quote_for_batch("-target"), "-target");
1958 assert_eq!(quote_for_batch("x86_64-linux-gnu"), "x86_64-linux-gnu");
1959
1960 assert_eq!(
1962 quote_for_batch("C:\\Users\\John Doe\\path"),
1963 "\"C:\\Users\\John Doe\\path\""
1964 );
1965
1966 assert_eq!(quote_for_batch(""), "\"\"");
1968
1969 assert_eq!(quote_for_batch("foo&bar"), "\"foo&bar\"");
1971 assert_eq!(quote_for_batch("foo|bar"), "\"foo|bar\"");
1972 assert_eq!(quote_for_batch("foo<bar"), "\"foo<bar\"");
1973 assert_eq!(quote_for_batch("foo>bar"), "\"foo>bar\"");
1974 assert_eq!(quote_for_batch("foo^bar"), "\"foo^bar\"");
1975 assert_eq!(quote_for_batch("foo%bar"), "\"foo%bar\"");
1976
1977 assert_eq!(quote_for_batch("foo\"bar"), "\"foo\"\"bar\"");
1979 }
1980
1981 #[test]
1982 #[cfg(not(target_family = "unix"))]
1983 fn test_join_args_for_script_windows() {
1984 let args = vec![
1986 "-target",
1987 "x86_64-linux-gnu",
1988 "-L",
1989 "C:\\Users\\John Doe\\path",
1990 ];
1991 let result = join_args_for_script(&args);
1992 assert!(result.contains("\"C:\\Users\\John Doe\\path\""));
1994 assert!(result.contains("-target"));
1996 assert!(!result.contains("\"-target\""));
1997 }
1998}