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 is_musl: bool,
72 is_windows_gnu: bool,
73 is_windows_msvc: bool,
74 is_arm: bool,
75 is_i386: bool,
76 is_riscv64: bool,
77 is_mips32: bool,
78 is_macos: bool,
79 is_ohos: bool,
80 is_freebsd: bool,
81}
82
83impl TargetInfo {
84 fn new(target: Option<&String>) -> Self {
85 Self {
86 target: target.cloned(),
87 is_musl: target.map(|x| x.contains("musl")).unwrap_or_default(),
88 is_windows_gnu: target
89 .map(|x| x.contains("windows-gnu"))
90 .unwrap_or_default(),
91 is_windows_msvc: target
92 .map(|x| x.contains("windows-msvc"))
93 .unwrap_or_default(),
94 is_arm: target.map(|x| x.starts_with("arm")).unwrap_or_default(),
95 is_i386: target.map(|x| x.starts_with("i386")).unwrap_or_default(),
96 is_riscv64: target.map(|x| x.starts_with("riscv64")).unwrap_or_default(),
97 is_mips32: target
98 .map(|x| x.starts_with("mips") && !x.starts_with("mips64"))
99 .unwrap_or_default(),
100 is_macos: target.map(|x| x.contains("macos")).unwrap_or_default(),
101 is_ohos: target.map(|x| x.contains("ohos")).unwrap_or_default(),
102 is_freebsd: target.map(|x| x.contains("freebsd")).unwrap_or_default(),
103 }
104 }
105}
106
107impl Zig {
108 pub fn execute(&self) -> Result<()> {
110 match self {
111 Zig::Cc { args } => self.execute_compiler("cc", args),
112 Zig::Cxx { args } => self.execute_compiler("c++", args),
113 Zig::Ar { args } => self.execute_tool("ar", args),
114 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
115 Zig::Lib { args } => self.execute_compiler("lib", args),
116 Zig::Dlltool { args } => self.execute_tool("dlltool", args),
117 }
118 }
119
120 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
122 let target = cmd_args
123 .iter()
124 .position(|x| x == "-target")
125 .and_then(|index| cmd_args.get(index + 1));
126 let target_info = TargetInfo::new(target);
127
128 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
129 Ok(version) => version.parse()?,
130 Err(_) => rustc_version::version()?,
131 };
132 let zig_version = Zig::zig_version()?;
133
134 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
135 let mut skip_next_arg = false;
136 for arg in cmd_args {
137 if skip_next_arg {
138 skip_next_arg = false;
139 continue;
140 }
141 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
142 vec![self.process_linker_response_file(
143 arg,
144 &rustc_ver,
145 &zig_version,
146 &target_info,
147 )?]
148 } else {
149 self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info)
150 };
151 for arg in args {
152 if arg == "-Wl,-exported_symbols_list" {
153 skip_next_arg = true;
155 } else {
156 new_cmd_args.push(arg);
157 }
158 }
159 }
160
161 if target_info.is_mips32 {
162 new_cmd_args.push("-Wl,-z,notext".to_string());
164 }
165
166 if self.has_undefined_dynamic_lookup(cmd_args) {
167 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
168 }
169 if target_info.is_macos {
170 if self.should_add_libcharset(cmd_args, &zig_version) {
171 new_cmd_args.push("-lcharset".to_string());
172 }
173 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
174 }
175
176 let mut command = Self::command()?;
179 if (zig_version.major, zig_version.minor) >= (0, 15) {
180 if let Some(sdkroot) = Self::macos_sdk_root() {
181 command.env("SDKROOT", sdkroot);
182 }
183 }
184
185 let mut child = command
186 .arg(cmd)
187 .args(new_cmd_args)
188 .spawn()
189 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
190 let status = child.wait().expect("Failed to wait on zig child process");
191 if !status.success() {
192 process::exit(status.code().unwrap_or(1));
193 }
194 Ok(())
195 }
196
197 fn process_linker_response_file(
198 &self,
199 arg: &str,
200 rustc_ver: &rustc_version::Version,
201 zig_version: &semver::Version,
202 target_info: &TargetInfo,
203 ) -> Result<String> {
204 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
208 let content = if target_info.is_windows_msvc {
209 if content_bytes[0..2] != [255, 254] {
210 bail!(
211 "linker response file `{}` didn't start with a utf16 BOM",
212 &arg
213 );
214 }
215 let content_utf16: Vec<u16> = content_bytes[2..]
216 .chunks_exact(2)
217 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
218 .collect();
219 String::from_utf16(&content_utf16).with_context(|| {
220 format!(
221 "linker response file `{}` didn't contain valid utf16 content",
222 &arg
223 )
224 })?
225 } else {
226 String::from_utf8(content_bytes).with_context(|| {
227 format!(
228 "linker response file `{}` didn't contain valid utf8 content",
229 &arg
230 )
231 })?
232 };
233 let mut link_args: Vec<_> = content
234 .split('\n')
235 .flat_map(|arg| self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info))
236 .collect();
237 if self.has_undefined_dynamic_lookup(&link_args) {
238 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
239 }
240 if target_info.is_macos && self.should_add_libcharset(&link_args, &zig_version) {
241 link_args.push("-lcharset".to_string());
242 }
243 if target_info.is_windows_msvc {
244 let new_content = link_args.join("\n");
245 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
246 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
248 out.push(c as u8);
250 out.push((c >> 8) as u8);
251 }
252 fs::write(arg.trim_start_matches('@'), out)?;
253 } else {
254 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
255 }
256 Ok(arg.to_string())
257 }
258
259 fn filter_linker_arg(
260 &self,
261 arg: &str,
262 rustc_ver: &rustc_version::Version,
263 zig_version: &semver::Version,
264 target_info: &TargetInfo,
265 ) -> Vec<String> {
266 if arg == "-lgcc_s" {
267 return vec!["-lunwind".to_string()];
269 } else if arg.starts_with("--target=") {
270 return vec![];
272 }
273 if (target_info.is_arm || target_info.is_windows_gnu)
274 && arg.ends_with(".rlib")
275 && arg.contains("libcompiler_builtins-")
276 {
277 return vec![];
279 }
280 if target_info.is_windows_gnu {
281 #[allow(clippy::if_same_then_else)]
282 if arg == "-lgcc_eh" {
283 return vec!["-lc++".to_string()];
286 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
287 return vec!["-Wl,-search_paths_first".to_owned()];
292 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
293 return vec![];
294 } else if arg == "-Wl,--disable-auto-image-base"
295 || arg == "-Wl,--dynamicbase"
296 || arg == "-Wl,--large-address-aware"
297 || (arg.starts_with("-Wl,")
298 && (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
299 {
300 return vec![];
305 } else if arg == "-lmsvcrt" {
306 return vec![];
307 }
308 } else if arg == "-Wl,--no-undefined-version" {
309 return vec![];
312 } else if arg == "-Wl,-znostart-stop-gc" {
313 return vec![];
316 }
317 if target_info.is_musl || target_info.is_ohos {
318 if arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt") {
320 return vec![];
321 } else if arg == "-Wl,-melf_i386" {
322 return vec![];
324 }
325 if rustc_ver.major == 1
326 && rustc_ver.minor < 59
327 && arg.ends_with(".rlib")
328 && arg.contains("liblibc-")
329 {
330 return vec![];
333 }
334 if arg == "-lc" {
335 return vec![];
336 }
337 }
338 if arg.starts_with("-march=") {
339 if target_info.is_arm || target_info.is_i386 {
341 return vec![];
342 } else if target_info.is_riscv64 {
343 return vec!["-march=generic_rv64".to_string()];
344 } else if arg.starts_with("-march=armv8-a") {
345 let mut args_march = if target_info
346 .target
347 .as_ref()
348 .map(|x| x.starts_with("aarch64-macos"))
349 .unwrap_or_default()
350 {
351 vec![arg.replace("armv8-a", "apple_m1")]
352 } else if target_info
353 .target
354 .as_ref()
355 .map(|x| x.starts_with("aarch64-linux"))
356 .unwrap_or_default()
357 {
358 vec![arg
359 .replace("armv8-a", "generic+v8a")
360 .replace("simd", "neon")]
361 } else {
362 vec![arg.to_string()]
363 };
364 if arg == "-march=armv8-a+crypto" {
365 args_march.append(&mut vec![
371 "-Xassembler".to_owned(),
372 "-march=armv8-a+crypto".to_owned(),
373 ]);
374 }
375 return args_march;
376 }
377 }
378 if target_info.is_macos {
379 if arg.starts_with("-Wl,-exported_symbols_list,") {
380 return vec![];
383 }
384 if arg == "-Wl,-dylib" {
385 return vec![];
387 }
388 }
389 if target_info.is_freebsd {
390 let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
391 if ignored_libs.contains(&arg) {
392 return vec![];
393 }
394 }
395 vec![arg.to_string()]
396 }
397
398 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
399 let undefined = args
400 .iter()
401 .position(|x| x == "-undefined")
402 .and_then(|i| args.get(i + 1));
403 matches!(undefined, Some(x) if x == "dynamic_lookup")
404 }
405
406 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
407 if (zig_version.major, zig_version.minor) >= (0, 12) {
409 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
410 } else {
411 false
412 }
413 }
414
415 fn add_macos_specific_args(
416 &self,
417 new_cmd_args: &mut Vec<String>,
418 zig_version: &semver::Version,
419 ) -> Result<()> {
420 let sdkroot = Self::macos_sdk_root();
421 if (zig_version.major, zig_version.minor) >= (0, 12) {
422 if let Some(ref sdkroot) = sdkroot {
426 if (zig_version.major, zig_version.minor) < (0, 15) {
427 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
428 }
429 }
431 }
432 if let Some(ref sdkroot) = sdkroot {
433 let include_prefix = if (zig_version.major, zig_version.minor) < (0, 15) {
434 sdkroot
435 } else {
436 Path::new("/")
437 };
438 new_cmd_args.extend_from_slice(&[
439 "-isystem".to_string(),
440 format!("{}", include_prefix.join("usr").join("include").display()),
441 format!("-L{}", include_prefix.join("usr").join("lib").display()),
442 format!(
443 "-F{}",
444 include_prefix
445 .join("System")
446 .join("Library")
447 .join("Frameworks")
448 .display()
449 ),
450 "-DTARGET_OS_IPHONE=0".to_string(),
451 ]);
452 }
453
454 let cache_dir = cache_dir();
456 let deps_dir = cache_dir.join("deps");
457 fs::create_dir_all(&deps_dir)?;
458 write_tbd_files(&deps_dir)?;
459 new_cmd_args.push("-L".to_string());
460 new_cmd_args.push(format!("{}", deps_dir.display()));
461 Ok(())
462 }
463
464 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
466 let mut child = Self::command()?
467 .arg(cmd)
468 .args(cmd_args)
469 .spawn()
470 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
471 let status = child.wait().expect("Failed to wait on zig child process");
472 if !status.success() {
473 process::exit(status.code().unwrap_or(1));
474 }
475 Ok(())
476 }
477
478 pub fn command() -> Result<Command> {
480 let (zig, zig_args) = Self::find_zig()?;
481 let mut cmd = Command::new(zig);
482 cmd.args(zig_args);
483 Ok(cmd)
484 }
485
486 fn zig_version() -> Result<semver::Version> {
487 static ZIG_VERSION: OnceLock<semver::Version> = OnceLock::new();
488
489 if let Some(version) = ZIG_VERSION.get() {
490 return Ok(version.clone());
491 }
492 let output = Self::command()?.arg("version").output()?;
493 let version_str =
494 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
495 let version = semver::Version::parse(version_str.trim())?;
496 Ok(ZIG_VERSION.get_or_init(|| version).clone())
497 }
498
499 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
501 static ZIG_PATH: OnceLock<(PathBuf, Vec<String>)> = OnceLock::new();
502
503 if let Some(cached) = ZIG_PATH.get() {
504 return Ok(cached.clone());
505 }
506 let result = Self::find_zig_python()
507 .or_else(|_| Self::find_zig_bin())
508 .context("Failed to find zig")?;
509 Ok(ZIG_PATH.get_or_init(|| result).clone())
510 }
511
512 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
514 let zig_path = zig_path()?;
515 let output = Command::new(&zig_path).arg("version").output()?;
516
517 let version_str = str::from_utf8(&output.stdout).with_context(|| {
518 format!("`{} version` didn't return utf8 output", zig_path.display())
519 })?;
520 Self::validate_zig_version(version_str)?;
521 Ok((zig_path, Vec::new()))
522 }
523
524 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
526 let python_path = python_path()?;
527 let output = Command::new(&python_path)
528 .args(["-m", "ziglang", "version"])
529 .output()?;
530
531 let version_str = str::from_utf8(&output.stdout).with_context(|| {
532 format!(
533 "`{} -m ziglang version` didn't return utf8 output",
534 python_path.display()
535 )
536 })?;
537 Self::validate_zig_version(version_str)?;
538 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
539 }
540
541 fn validate_zig_version(version: &str) -> Result<()> {
542 let min_ver = semver::Version::new(0, 9, 0);
543 let version = semver::Version::parse(version.trim())?;
544 if version >= min_ver {
545 Ok(())
546 } else {
547 bail!(
548 "zig version {} is too old, need at least {}",
549 version,
550 min_ver
551 )
552 }
553 }
554
555 pub fn lib_dir() -> Result<PathBuf> {
557 static LIB_DIR: OnceLock<PathBuf> = OnceLock::new();
558
559 if let Some(cached) = LIB_DIR.get() {
560 return Ok(cached.clone());
561 }
562 let (zig, zig_args) = Self::find_zig()?;
563 let output = Command::new(zig).args(zig_args).arg("env").output()?;
564 let zig_env: ZigEnv = serde_json::from_slice(&output.stdout)?;
565 Ok(LIB_DIR
566 .get_or_init(|| PathBuf::from(zig_env.lib_dir))
567 .clone())
568 }
569
570 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
571 where
572 K: AsRef<OsStr>,
573 V: AsRef<OsStr>,
574 {
575 let command_env_contains_no_key =
576 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
577
578 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
579 command.env(name, value);
580 }
581 }
582
583 pub(crate) fn apply_command_env(
584 manifest_path: Option<&Path>,
585 release: bool,
586 cargo: &cargo_options::CommonOptions,
587 cmd: &mut Command,
588 enable_zig_ar: bool,
589 ) -> Result<()> {
590 let rust_targets = cargo
592 .target
593 .iter()
594 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
595 .collect::<Vec<&str>>();
596 let rustc_meta = rustc_version::version_meta()?;
597 Self::add_env_if_missing(
598 cmd,
599 "CARGO_ZIGBUILD_RUSTC_VERSION",
600 rustc_meta.semver.to_string(),
601 );
602 let host_target = &rustc_meta.host;
603 for (parsed_target, raw_target) in rust_targets.iter().zip(&cargo.target) {
604 let env_target = parsed_target.replace('-', "_");
605 let zig_wrapper = prepare_zig_linker(raw_target)?;
606
607 if is_mingw_shell() {
608 let zig_cc = zig_wrapper.cc.to_slash_lossy();
609 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
610 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
611 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
612 if !parsed_target.contains("wasm") {
613 Self::add_env_if_missing(
614 cmd,
615 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
616 &*zig_cc,
617 );
618 }
619 } else {
620 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
621 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
622 if !parsed_target.contains("wasm") {
623 Self::add_env_if_missing(
624 cmd,
625 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
626 &zig_wrapper.cc,
627 );
628 }
629 }
630
631 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
632 if enable_zig_ar {
635 if parsed_target.contains("msvc") {
636 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
637 } else {
638 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
639 }
640 }
641
642 Self::setup_os_deps(manifest_path, release, cargo)?;
643
644 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
645 if env::var_os(&cmake_toolchain_file_env).is_none()
646 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
647 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
648 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
649 {
650 if let Ok(cmake_toolchain_file) =
651 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
652 {
653 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
654 }
655 }
656
657 if raw_target.contains("windows-gnu") {
658 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
659 let cache_dir = cache_dir();
662 let existing_path = env::var_os("PATH").unwrap_or_default();
663 let paths = std::iter::once(cache_dir).chain(env::split_paths(&existing_path));
664 if let Ok(new_path) = env::join_paths(paths) {
665 cmd.env("PATH", new_path);
666 }
667 }
668
669 if raw_target.contains("apple-darwin") {
670 if let Some(sdkroot) = Self::macos_sdk_root() {
671 if env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none() {
672 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
674 }
675 }
676 }
677
678 if host_target == parsed_target {
681 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
682 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
685 }
686 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
687 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
688 }
689
690 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
692 .context("Failed to collect `zig cc` options")?;
693 if raw_target.contains("apple-darwin") {
694 options.push("-DTARGET_OS_IPHONE=0".to_string());
696 }
697 let escaped_options = shlex::try_join(options.iter().map(|s| &s[..]))?;
698 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
699 let fallback_value = env::var(bindgen_env);
700 for target in [&env_target[..], parsed_target] {
701 let name = format!("{bindgen_env}_{target}");
702 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
703 if shlex::split(&value).is_none() {
704 value = shlex::try_quote(&value)?.into_owned();
706 }
707 if !value.is_empty() {
708 value.push(' ');
709 }
710 value.push_str(&escaped_options);
711 env::set_var(name, value);
712 } else {
713 env::set_var(name, escaped_options.clone());
714 }
715 }
716 }
717 Ok(())
718 }
719
720 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
724 #[derive(Debug, PartialEq, Eq)]
725 enum Kind {
726 Normal,
727 Framework,
728 }
729
730 #[derive(Debug)]
731 struct PerLanguageOptions {
732 glibc_minor_ver: Option<u32>,
733 include_paths: Vec<(Kind, String)>,
734 }
735
736 fn collect_per_language_options(
737 program: &Path,
738 ext: &str,
739 raw_target: &str,
740 ) -> Result<PerLanguageOptions> {
741 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
743 if !empty_file_path.exists() {
744 fs::write(&empty_file_path, "")?;
745 }
746
747 let output = Command::new(program)
748 .arg("-E")
749 .arg(&empty_file_path)
750 .arg("-v")
751 .output()?;
752 let stderr = String::from_utf8(output.stderr)?;
754 if !output.status.success() {
755 bail!(
756 "Failed to run `zig cc -v` with status {}: {}",
757 output.status,
758 stderr.trim(),
759 );
760 }
761
762 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
766 let stderr = &stderr[start + 16..];
767 let end = stderr
768 .find(|c: char| !c.is_ascii_digit())
769 .unwrap_or(stderr.len());
770 stderr[..end].parse().ok()
771 } else {
772 None
773 };
774
775 let start = stderr
776 .find("#include <...> search starts here:")
777 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
778 + 34;
779 let end = stderr
780 .find("End of search list.")
781 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
782
783 let mut include_paths = Vec::new();
784 for mut line in stderr[start..end].lines() {
785 line = line.trim();
786 let mut kind = Kind::Normal;
787 if line.ends_with(" (framework directory)") {
788 line = line[..line.len() - 22].trim();
789 kind = Kind::Framework;
790 } else if line.ends_with(" (headermap)") {
791 bail!("C/C++ search path includes header maps, which are not supported");
792 }
793 if !line.is_empty() {
794 include_paths.push((kind, line.to_owned()));
795 }
796 }
797
798 if raw_target.contains("ohos") {
800 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
801 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
802 }
803
804 Ok(PerLanguageOptions {
805 include_paths,
806 glibc_minor_ver,
807 })
808 }
809
810 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
811 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
812
813 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
815 bail!(
816 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
817 c_opts.glibc_minor_ver,
818 cpp_opts.glibc_minor_ver,
819 );
820 }
821 let c_paths = c_opts.include_paths;
822 let mut cpp_paths = cpp_opts.include_paths;
823 let cpp_pre_len = cpp_paths
824 .iter()
825 .position(|p| {
826 p == c_paths
827 .iter()
828 .filter(|(kind, _)| *kind == Kind::Normal)
829 .next()
830 .unwrap()
831 })
832 .unwrap_or_default();
833 let cpp_post_len = cpp_paths.len()
834 - cpp_paths
835 .iter()
836 .position(|p| p == c_paths.last().unwrap())
837 .unwrap_or_default()
838 - 1;
839
840 let mut args = Vec::new();
893
894 args.push("-nostdinc".to_owned());
897
898 if raw_target.contains("musl") || raw_target.contains("ohos") {
906 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
907 args.push("-D_LARGEFILE64_SOURCE".to_owned());
910 }
911 args.extend(
912 [
913 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
914 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
915 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
916 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
917 "-D_LIBCPP_ABI_VERSION=1",
918 "-D_LIBCPP_ABI_NAMESPACE=__1",
919 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
920 ]
921 .into_iter()
922 .map(ToString::to_string),
923 );
924 if let Some(ver) = c_opts.glibc_minor_ver {
925 args.push(format!("-D__GLIBC_MINOR__={ver}"));
927 }
928
929 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
930 if kind != Kind::Normal {
931 continue;
933 }
934 args.push("-cxx-isystem".to_owned());
940 args.push(path);
941 }
942
943 for (kind, path) in c_paths {
944 match kind {
945 Kind::Normal => {
946 args.push("-Xclang".to_owned());
948 args.push("-c-isystem".to_owned());
949 args.push("-Xclang".to_owned());
950 args.push(path.clone());
951 args.push("-cxx-isystem".to_owned());
952 args.push(path);
953 }
954 Kind::Framework => {
955 args.push("-iframework".to_owned());
956 args.push(path);
957 }
958 }
959 }
960
961 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
962 assert!(kind == Kind::Normal);
963 args.push("-cxx-isystem".to_owned());
964 args.push(path);
965 }
966
967 Ok(args)
968 }
969
970 fn setup_os_deps(
971 manifest_path: Option<&Path>,
972 release: bool,
973 cargo: &cargo_options::CommonOptions,
974 ) -> Result<()> {
975 for target in &cargo.target {
976 if target.contains("apple") {
977 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
978 target_dir.join(target)
979 } else {
980 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
981 if !manifest_path.exists() {
982 continue;
984 }
985 let metadata = cargo_metadata::MetadataCommand::new()
986 .manifest_path(manifest_path)
987 .no_deps()
988 .exec()?;
989 metadata.target_directory.into_std_path_buf().join(target)
990 };
991 let profile = match cargo.profile.as_deref() {
992 Some("dev" | "test") => "debug",
993 Some("release" | "bench") => "release",
994 Some(profile) => profile,
995 None => {
996 if release {
997 "release"
998 } else {
999 "debug"
1000 }
1001 }
1002 };
1003 let deps_dir = target_dir.join(profile).join("deps");
1004 fs::create_dir_all(&deps_dir)?;
1005 if !target_dir.join("CACHEDIR.TAG").is_file() {
1006 let _ = write_file(
1008 &target_dir.join("CACHEDIR.TAG"),
1009 "Signature: 8a477f597d28d172789f06886806bc55
1010# This file is a cache directory tag created by cargo.
1011# For information about cache directory tags see https://bford.info/cachedir/
1012",
1013 );
1014 }
1015 write_tbd_files(&deps_dir)?;
1016 } else if target.contains("arm") && target.contains("linux") {
1017 if let Ok(lib_dir) = Zig::lib_dir() {
1019 let arm_features_h = lib_dir
1020 .join("libc")
1021 .join("glibc")
1022 .join("sysdeps")
1023 .join("arm")
1024 .join("arm-features.h");
1025 if !arm_features_h.is_file() {
1026 fs::write(arm_features_h, ARM_FEATURES_H)?;
1027 }
1028 }
1029 } else if target.contains("windows-gnu") {
1030 if let Ok(lib_dir) = Zig::lib_dir() {
1031 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
1032 let synchronization_def = lib_common.join("synchronization.def");
1033 if !synchronization_def.is_file() {
1034 let api_ms_win_core_synch_l1_2_0_def =
1035 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
1036 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
1038 }
1039 }
1040 }
1041 }
1042 Ok(())
1043 }
1044
1045 fn setup_cmake_toolchain(
1046 target: &str,
1047 zig_wrapper: &ZigWrapper,
1048 enable_zig_ar: bool,
1049 ) -> Result<PathBuf> {
1050 let cmake = cache_dir().join("cmake");
1051 fs::create_dir_all(&cmake)?;
1052
1053 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
1054 let triple: Triple = target.parse()?;
1055 let os = triple.operating_system.to_string();
1056 let arch = triple.architecture.to_string();
1057 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1058 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1059 ("darwin", "aarch64") => ("Darwin", "arm64"),
1060 ("linux", arch) => {
1061 let cmake_arch = match arch {
1062 "powerpc" => "ppc",
1063 "powerpc64" => "ppc64",
1064 "powerpc64le" => "ppc64le",
1065 _ => arch,
1066 };
1067 ("Linux", cmake_arch)
1068 }
1069 ("windows", "x86_64") => ("Windows", "AMD64"),
1070 ("windows", "i686") => ("Windows", "X86"),
1071 ("windows", "aarch64") => ("Windows", "ARM64"),
1072 (os, arch) => (os, arch),
1073 };
1074 let mut content = format!(
1075 r#"
1076set(CMAKE_SYSTEM_NAME {system_name})
1077set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1078set(CMAKE_C_COMPILER {cc})
1079set(CMAKE_CXX_COMPILER {cxx})
1080set(CMAKE_RANLIB {ranlib})
1081set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1082set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1083 system_name = system_name,
1084 system_processor = system_processor,
1085 cc = zig_wrapper.cc.to_slash_lossy(),
1086 cxx = zig_wrapper.cxx.to_slash_lossy(),
1087 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1088 );
1089 if enable_zig_ar {
1090 content.push_str(&format!(
1091 "\nset(CMAKE_AR {})\n",
1092 zig_wrapper.ar.to_slash_lossy()
1093 ));
1094 }
1095 write_file(&toolchain_file, &content)?;
1096 Ok(toolchain_file)
1097 }
1098
1099 #[cfg(target_os = "macos")]
1100 fn macos_sdk_root() -> Option<PathBuf> {
1101 static SDK_ROOT: OnceLock<Option<PathBuf>> = OnceLock::new();
1102
1103 SDK_ROOT
1104 .get_or_init(|| match env::var_os("SDKROOT") {
1105 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1106 _ => {
1107 let output = Command::new("xcrun")
1108 .args(["--sdk", "macosx", "--show-sdk-path"])
1109 .output()
1110 .ok()?;
1111 if output.status.success() {
1112 let stdout = String::from_utf8(output.stdout).ok()?;
1113 let stdout = stdout.trim();
1114 if !stdout.is_empty() {
1115 return Some(stdout.into());
1116 }
1117 }
1118 None
1119 }
1120 })
1121 .clone()
1122 }
1123
1124 #[cfg(not(target_os = "macos"))]
1125 fn macos_sdk_root() -> Option<PathBuf> {
1126 match env::var_os("SDKROOT") {
1127 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1128 _ => None,
1129 }
1130 }
1131}
1132
1133fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1134 let existing_content = fs::read_to_string(path).unwrap_or_default();
1135 if existing_content != content {
1136 fs::write(path, content)?;
1137 }
1138 Ok(())
1139}
1140
1141fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1142 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1143 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1144 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1145 Ok(())
1146}
1147
1148fn cache_dir() -> PathBuf {
1149 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1150 .ok()
1151 .map(|s| s.into())
1152 .or_else(dirs::cache_dir)
1153 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1155 .join(env!("CARGO_PKG_NAME"))
1156 .join(env!("CARGO_PKG_VERSION"))
1157}
1158
1159#[derive(Debug, Deserialize)]
1160struct ZigEnv {
1161 lib_dir: String,
1162}
1163
1164#[derive(Debug, Clone)]
1166pub struct ZigWrapper {
1167 pub cc: PathBuf,
1168 pub cxx: PathBuf,
1169 pub ar: PathBuf,
1170 pub ranlib: PathBuf,
1171 pub lib: PathBuf,
1172}
1173
1174#[derive(Debug, Clone, Default, PartialEq)]
1175struct TargetFlags {
1176 pub target_cpu: String,
1177 pub target_feature: String,
1178}
1179
1180impl TargetFlags {
1181 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1182 let mut parsed = Self::default();
1183
1184 let f = rustflags::from_encoded(encoded);
1185 for flag in f {
1186 if let rustflags::Flag::Codegen { opt, value } = flag {
1187 let key = opt.replace('-', "_");
1188 match key.as_str() {
1189 "target_cpu" => {
1190 if let Some(value) = value {
1191 parsed.target_cpu = value;
1192 }
1193 }
1194 "target_feature" => {
1195 if let Some(value) = value {
1197 if !parsed.target_feature.is_empty() {
1198 parsed.target_feature.push(',');
1199 }
1200 parsed.target_feature.push_str(&value);
1201 }
1202 }
1203 _ => {}
1204 }
1205 }
1206 }
1207 Ok(parsed)
1208 }
1209}
1210
1211#[allow(clippy::blocks_in_conditions)]
1220pub fn prepare_zig_linker(target: &str) -> Result<ZigWrapper> {
1221 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1222 let abi_suffix = if abi_suffix.is_empty() {
1223 String::new()
1224 } else {
1225 if abi_suffix
1226 .split_once('.')
1227 .filter(|(x, y)| {
1228 !x.is_empty()
1229 && x.chars().all(|c| c.is_ascii_digit())
1230 && !y.is_empty()
1231 && y.chars().all(|c| c.is_ascii_digit())
1232 })
1233 .is_none()
1234 {
1235 bail!("Malformed zig target abi suffix.")
1236 }
1237 format!(".{abi_suffix}")
1238 };
1239 let triple: Triple = rust_target
1240 .parse()
1241 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1242 let arch = triple.architecture.to_string();
1243 let target_env = match (triple.architecture, triple.environment) {
1244 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1245 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1246 (_, Environment::GnuLlvm) => Environment::Gnu,
1247 (_, environment) => environment,
1248 };
1249 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1250 let file_target = target.trim_end_matches('.');
1251
1252 let mut cc_args = vec![
1253 "-g".to_owned(),
1255 "-fno-sanitize=all".to_owned(),
1257 ];
1258
1259 let zig_mcpu_default = match triple.operating_system {
1262 OperatingSystem::Linux => {
1263 match arch.as_str() {
1264 "arm" => match target_env {
1266 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1267 Environment::Gnueabihf | Environment::Musleabihf => {
1268 "generic+v6+strict_align+vfp2-d32"
1269 }
1270 _ => "",
1271 },
1272 "armv5te" => "generic+soft_float+strict_align",
1273 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1274 arch_str @ ("i586" | "i686") => {
1275 if arch_str == "i586" {
1276 "pentium"
1277 } else {
1278 "pentium4"
1279 }
1280 }
1281 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1282 "s390x" => "z10-vector",
1283 _ => "",
1284 }
1285 }
1286 _ => "",
1287 };
1288
1289 let zig_mcpu_override = {
1293 let cargo_config = cargo_config2::Config::load()?;
1294 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1295 let encoded_rust_flags = rust_flags.encode()?;
1296 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1297 target_flags.target_cpu.replace('-', "_")
1300 };
1301
1302 if !zig_mcpu_override.is_empty() {
1303 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1304 } else if !zig_mcpu_default.is_empty() {
1305 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1306 }
1307
1308 match triple.operating_system {
1309 OperatingSystem::Linux => {
1310 let zig_arch = match arch.as_str() {
1311 "arm" => "arm",
1313 "armv5te" => "arm",
1314 "armv7" => "arm",
1315 "i586" | "i686" => {
1316 let zig_version = Zig::zig_version()?;
1317 if zig_version.major == 0 && zig_version.minor >= 11 {
1318 "x86"
1319 } else {
1320 "i386"
1321 }
1322 }
1323 "riscv64gc" => "riscv64",
1324 "s390x" => "s390x",
1325 _ => arch.as_str(),
1326 };
1327 let mut zig_target_env = target_env.to_string();
1328
1329 let zig_version = Zig::zig_version()?;
1330
1331 if zig_version >= semver::Version::new(0, 15, 0)
1335 && arch.as_str() == "armv7"
1336 && target_env == Environment::Ohos
1337 {
1338 zig_target_env = "ohoseabi".to_string();
1339 }
1340
1341 cc_args.push(format!(
1342 "-target {zig_arch}-linux-{zig_target_env}{abi_suffix}"
1343 ));
1344 }
1345 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1346 let zig_version = Zig::zig_version()?;
1347 if zig_version > semver::Version::new(0, 9, 1) {
1350 cc_args.push(format!("-target {arch}-macos-none{abi_suffix}"));
1351 } else {
1352 cc_args.push(format!("-target {arch}-macos-gnu{abi_suffix}"));
1353 }
1354 }
1355 OperatingSystem::Windows { .. } => {
1356 let zig_arch = match arch.as_str() {
1357 "i686" => {
1358 let zig_version = Zig::zig_version()?;
1359 if zig_version.major == 0 && zig_version.minor >= 11 {
1360 "x86"
1361 } else {
1362 "i386"
1363 }
1364 }
1365 arch => arch,
1366 };
1367 cc_args.push(format!(
1368 "-target {zig_arch}-windows-{target_env}{abi_suffix}"
1369 ));
1370 }
1371 OperatingSystem::Emscripten => {
1372 cc_args.push(format!("-target {arch}-emscripten{abi_suffix}"));
1373 }
1374 OperatingSystem::Wasi => {
1375 cc_args.push(format!("-target {arch}-wasi{abi_suffix}"));
1376 }
1377 OperatingSystem::WasiP1 => {
1378 cc_args.push(format!("-target {arch}-wasi.0.1.0{abi_suffix}"));
1379 }
1380 OperatingSystem::Freebsd => {
1381 let zig_arch = match arch.as_str() {
1382 "i686" => {
1383 let zig_version = Zig::zig_version()?;
1384 if zig_version.major == 0 && zig_version.minor >= 11 {
1385 "x86"
1386 } else {
1387 "i386"
1388 }
1389 }
1390 arch => arch,
1391 };
1392 cc_args.push(format!("-target {zig_arch}-freebsd"));
1393 }
1394 OperatingSystem::Unknown => {
1395 if triple.architecture == Architecture::Wasm32
1396 || triple.architecture == Architecture::Wasm64
1397 {
1398 cc_args.push(format!("-target {arch}-freestanding{abi_suffix}"));
1399 } else {
1400 bail!("unsupported target '{rust_target}'")
1401 }
1402 }
1403 _ => bail!(format!("unsupported target '{rust_target}'")),
1404 };
1405
1406 let zig_linker_dir = cache_dir();
1407 fs::create_dir_all(&zig_linker_dir)?;
1408
1409 if triple.operating_system == OperatingSystem::Linux {
1410 if matches!(
1411 triple.environment,
1412 Environment::Gnu
1413 | Environment::Gnuspe
1414 | Environment::Gnux32
1415 | Environment::Gnueabi
1416 | Environment::Gnuabi64
1417 | Environment::GnuIlp32
1418 | Environment::Gnueabihf
1419 ) {
1420 let glibc_version = if abi_suffix.is_empty() {
1421 (2, 17)
1422 } else {
1423 let mut parts = abi_suffix[1..].split('.');
1424 let major: usize = parts.next().unwrap().parse()?;
1425 let minor: usize = parts.next().unwrap().parse()?;
1426 (major, minor)
1427 };
1428 if glibc_version < (2, 28) {
1430 use crate::linux::{FCNTL_H, FCNTL_MAP};
1431
1432 let zig_version = Zig::zig_version()?;
1433 if zig_version.major == 0 && zig_version.minor < 11 {
1434 let fcntl_map = zig_linker_dir.join("fcntl.map");
1435 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1436 if existing_content != FCNTL_MAP {
1437 fs::write(&fcntl_map, FCNTL_MAP)?;
1438 }
1439 let fcntl_h = zig_linker_dir.join("fcntl.h");
1440 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1441 if existing_content != FCNTL_H {
1442 fs::write(&fcntl_h, FCNTL_H)?;
1443 }
1444
1445 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1446 cc_args.push(format!("-include {}", fcntl_h.display()));
1447 }
1448 }
1449 } else if matches!(
1450 triple.environment,
1451 Environment::Musl
1452 | Environment::Muslabi64
1453 | Environment::Musleabi
1454 | Environment::Musleabihf
1455 ) {
1456 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1457
1458 let zig_version = Zig::zig_version()?;
1459 let rustc_version = rustc_version::version_meta()?.semver;
1460
1461 if (zig_version.major, zig_version.minor) >= (0, 11)
1466 && (rustc_version.major, rustc_version.minor) < (1, 72)
1467 {
1468 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1469 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1470
1471 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1472 }
1473 }
1474 }
1475
1476 let cc_args_str = cc_args.join(" ");
1477 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1478 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1479 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1480 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1481 write_linker_wrapper(&zig_cc, "cc", &cc_args_str)?;
1482 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str)?;
1483 write_linker_wrapper(&zig_ranlib, "ranlib", "")?;
1484
1485 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1486 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1487 symlink_wrapper(&zig_ar)?;
1488 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1489 symlink_wrapper(&zig_lib)?;
1490
1491 if matches!(triple.operating_system, OperatingSystem::Windows)
1497 && matches!(triple.environment, Environment::Gnu)
1498 {
1499 let dlltool_name: &str = if cfg!(windows) {
1500 "dlltool"
1501 } else {
1502 match triple.architecture {
1503 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
1504 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
1505 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
1506 _ => "dlltool",
1507 }
1508 };
1509 let zig_dlltool = zig_linker_dir.join(format!("{dlltool_name}{exe_ext}"));
1510 symlink_wrapper(&zig_dlltool)?;
1511 }
1512
1513 Ok(ZigWrapper {
1514 cc: zig_cc,
1515 cxx: zig_cxx,
1516 ar: zig_ar,
1517 ranlib: zig_ranlib,
1518 lib: zig_lib,
1519 })
1520}
1521
1522fn symlink_wrapper(target: &Path) -> Result<()> {
1523 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1524 PathBuf::from(exe)
1525 } else {
1526 env::current_exe()?
1527 };
1528 #[cfg(windows)]
1529 {
1530 if !target.exists() {
1531 if std::fs::hard_link(¤t_exe, target).is_err() {
1533 std::fs::copy(¤t_exe, target)?;
1535 }
1536 }
1537 }
1538
1539 #[cfg(unix)]
1540 {
1541 if !target.exists() {
1542 if fs::read_link(target).is_ok() {
1543 fs::remove_file(target)?;
1545 }
1546 std::os::unix::fs::symlink(current_exe, target)?;
1547 }
1548 }
1549 Ok(())
1550}
1551
1552#[cfg(target_family = "unix")]
1554fn write_linker_wrapper(path: &Path, command: &str, args: &str) -> Result<()> {
1555 let mut buf = Vec::<u8>::new();
1556 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1557 PathBuf::from(exe)
1558 } else {
1559 env::current_exe()?
1560 };
1561 writeln!(&mut buf, "#!/bin/sh")?;
1562
1563 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
1567
1568 writeln!(
1569 &mut buf,
1570 "exec \"{}\" zig {} -- {} \"$@\"",
1571 current_exe.display(),
1572 command,
1573 args
1574 )?;
1575
1576 let existing_content = fs::read(path).unwrap_or_default();
1580 if existing_content != buf {
1581 OpenOptions::new()
1582 .create(true)
1583 .write(true)
1584 .truncate(true)
1585 .mode(0o700)
1586 .open(path)?
1587 .write_all(&buf)?;
1588 }
1589 Ok(())
1590}
1591
1592#[cfg(not(target_family = "unix"))]
1594fn write_linker_wrapper(path: &Path, command: &str, args: &str) -> Result<()> {
1595 let mut buf = Vec::<u8>::new();
1596 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1597 PathBuf::from(exe)
1598 } else {
1599 env::current_exe()?
1600 };
1601 let current_exe = if is_mingw_shell() {
1602 current_exe.to_slash_lossy().to_string()
1603 } else {
1604 current_exe.display().to_string()
1605 };
1606 writeln!(
1607 &mut buf,
1608 "\"{}\" zig {} -- {} %*",
1609 adjust_canonicalization(current_exe),
1610 command,
1611 args
1612 )?;
1613
1614 let existing_content = fs::read(path).unwrap_or_default();
1615 if existing_content != buf {
1616 fs::write(path, buf)?;
1617 }
1618 Ok(())
1619}
1620
1621pub(crate) fn is_mingw_shell() -> bool {
1622 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
1623}
1624
1625#[cfg(target_os = "windows")]
1627pub fn adjust_canonicalization(p: String) -> String {
1628 const VERBATIM_PREFIX: &str = r#"\\?\"#;
1629 if p.starts_with(VERBATIM_PREFIX) {
1630 p[VERBATIM_PREFIX.len()..].to_string()
1631 } else {
1632 p
1633 }
1634}
1635
1636fn python_path() -> Result<PathBuf> {
1637 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
1638 Ok(which::which(python)?)
1639}
1640
1641fn zig_path() -> Result<PathBuf> {
1642 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
1643 Ok(which::which(zig)?)
1644}
1645
1646#[cfg(test)]
1647mod tests {
1648 use super::*;
1649
1650 #[test]
1651 fn test_target_flags() {
1652 let cases = [
1653 ("-C target-feature=-crt-static", "", "-crt-static"),
1655 ("-C target-cpu=native", "native", ""),
1656 (
1657 "--deny warnings --codegen target-feature=+crt-static",
1658 "",
1659 "+crt-static",
1660 ),
1661 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
1662 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
1663 (
1664 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
1665 "native",
1666 "-avx512bf16,-avx512bitalg",
1667 ),
1668 (
1669 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
1670 "x86-64",
1671 "",
1672 ),
1673 (
1674 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
1675 "",
1676 "+crt-static,+avx",
1677 ),
1678 ];
1679
1680 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
1681 let args = cargo_config2::Flags::from_space_separated(input);
1682 let encoded_rust_flags = args.encode().unwrap();
1683 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
1684 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
1685 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
1686 }
1687 }
1688}