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;
11
12use anyhow::{anyhow, bail, Context, Result};
13use fs_err as fs;
14use path_slash::PathBufExt;
15use serde::Deserialize;
16use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
17
18use crate::linux::ARM_FEATURES_H;
19use crate::macos::{LIBCHARSET_TBD, LIBICONV_TBD};
20
21#[derive(Clone, Debug, clap::Subcommand)]
23pub enum Zig {
24 #[command(name = "cc")]
26 Cc {
27 #[arg(num_args = 1.., trailing_var_arg = true)]
29 args: Vec<String>,
30 },
31 #[command(name = "c++")]
33 Cxx {
34 #[arg(num_args = 1.., trailing_var_arg = true)]
36 args: Vec<String>,
37 },
38 #[command(name = "ar")]
40 Ar {
41 #[arg(num_args = 1.., trailing_var_arg = true)]
43 args: Vec<String>,
44 },
45 #[command(name = "ranlib")]
47 Ranlib {
48 #[arg(num_args = 1.., trailing_var_arg = true)]
50 args: Vec<String>,
51 },
52 #[command(name = "lib")]
54 Lib {
55 #[arg(num_args = 1.., trailing_var_arg = true)]
57 args: Vec<String>,
58 },
59}
60
61struct TargetInfo {
62 target: Option<String>,
63 is_musl: bool,
64 is_windows_gnu: bool,
65 is_windows_msvc: bool,
66 is_arm: bool,
67 is_i386: bool,
68 is_riscv64: bool,
69 is_mips32: bool,
70 is_macos: bool,
71 is_ohos: bool,
72}
73
74impl TargetInfo {
75 fn new(target: Option<&String>) -> Self {
76 Self {
77 target: target.cloned(),
78 is_musl: target.map(|x| x.contains("musl")).unwrap_or_default(),
79 is_windows_gnu: target
80 .map(|x| x.contains("windows-gnu"))
81 .unwrap_or_default(),
82 is_windows_msvc: target
83 .map(|x| x.contains("windows-msvc"))
84 .unwrap_or_default(),
85 is_arm: target.map(|x| x.starts_with("arm")).unwrap_or_default(),
86 is_i386: target.map(|x| x.starts_with("i386")).unwrap_or_default(),
87 is_riscv64: target.map(|x| x.starts_with("riscv64")).unwrap_or_default(),
88 is_mips32: target
89 .map(|x| x.starts_with("mips") && !x.starts_with("mips64"))
90 .unwrap_or_default(),
91 is_macos: target.map(|x| x.contains("macos")).unwrap_or_default(),
92 is_ohos: target.map(|x| x.contains("ohos")).unwrap_or_default(),
93 }
94 }
95}
96
97impl Zig {
98 pub fn execute(&self) -> Result<()> {
100 match self {
101 Zig::Cc { args } => self.execute_compiler("cc", args),
102 Zig::Cxx { args } => self.execute_compiler("c++", args),
103 Zig::Ar { args } => self.execute_tool("ar", args),
104 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
105 Zig::Lib { args } => self.execute_compiler("lib", args),
106 }
107 }
108
109 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
111 let target = cmd_args
112 .iter()
113 .position(|x| x == "-target")
114 .and_then(|index| cmd_args.get(index + 1));
115 let target_info = TargetInfo::new(target);
116
117 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
118 Ok(version) => version.parse()?,
119 Err(_) => rustc_version::version()?,
120 };
121 let zig_version = Zig::zig_version()?;
122
123 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
124 let mut skip_next_arg = false;
125 for arg in cmd_args {
126 if skip_next_arg {
127 skip_next_arg = false;
128 continue;
129 }
130 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
131 vec![self.process_linker_response_file(
132 arg,
133 &rustc_ver,
134 &zig_version,
135 &target_info,
136 )?]
137 } else {
138 self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info)
139 };
140 for arg in args {
141 if arg == "-Wl,-exported_symbols_list" {
142 skip_next_arg = true;
144 } else {
145 new_cmd_args.push(arg);
146 }
147 }
148 }
149
150 if target_info.is_mips32 {
151 new_cmd_args.push("-Wl,-z,notext".to_string());
153 }
154
155 if self.has_undefined_dynamic_lookup(cmd_args) {
156 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
157 }
158 if target_info.is_macos {
159 if self.should_add_libcharset(cmd_args, &zig_version) {
160 new_cmd_args.push("-lcharset".to_string());
161 }
162 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
163 }
164
165 let mut child = Self::command()?
166 .arg(cmd)
167 .args(new_cmd_args)
168 .spawn()
169 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
170 let status = child.wait().expect("Failed to wait on zig child process");
171 if !status.success() {
172 process::exit(status.code().unwrap_or(1));
173 }
174 Ok(())
175 }
176
177 fn process_linker_response_file(
178 &self,
179 arg: &str,
180 rustc_ver: &rustc_version::Version,
181 zig_version: &semver::Version,
182 target_info: &TargetInfo,
183 ) -> Result<String> {
184 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
188 let content = if target_info.is_windows_msvc {
189 if content_bytes[0..2] != [255, 254] {
190 bail!(
191 "linker response file `{}` didn't start with a utf16 BOM",
192 &arg
193 );
194 }
195 let content_utf16: Vec<u16> = content_bytes[2..]
196 .chunks_exact(2)
197 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
198 .collect();
199 String::from_utf16(&content_utf16).with_context(|| {
200 format!(
201 "linker response file `{}` didn't contain valid utf16 content",
202 &arg
203 )
204 })?
205 } else {
206 String::from_utf8(content_bytes).with_context(|| {
207 format!(
208 "linker response file `{}` didn't contain valid utf8 content",
209 &arg
210 )
211 })?
212 };
213 let mut link_args: Vec<_> = content
214 .split('\n')
215 .flat_map(|arg| self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info))
216 .collect();
217 if self.has_undefined_dynamic_lookup(&link_args) {
218 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
219 }
220 if target_info.is_macos && self.should_add_libcharset(&link_args, &zig_version) {
221 link_args.push("-lcharset".to_string());
222 }
223 if target_info.is_windows_msvc {
224 let new_content = link_args.join("\n");
225 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
226 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
228 out.push(c as u8);
230 out.push((c >> 8) as u8);
231 }
232 fs::write(arg.trim_start_matches('@'), out)?;
233 } else {
234 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
235 }
236 Ok(arg.to_string())
237 }
238
239 fn filter_linker_arg(
240 &self,
241 arg: &str,
242 rustc_ver: &rustc_version::Version,
243 zig_version: &semver::Version,
244 target_info: &TargetInfo,
245 ) -> Vec<String> {
246 if arg == "-lgcc_s" {
247 return vec!["-lunwind".to_string()];
249 } else if arg.starts_with("--target=") {
250 return vec![];
252 }
253 if (target_info.is_arm || target_info.is_windows_gnu)
254 && arg.ends_with(".rlib")
255 && arg.contains("libcompiler_builtins-")
256 {
257 return vec![];
259 }
260 if target_info.is_windows_gnu {
261 #[allow(clippy::if_same_then_else)]
262 if arg == "-lgcc_eh" {
263 return vec!["-lc++".to_string()];
266 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
267 return vec!["-Wl,-search_paths_first".to_owned()];
272 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
273 return vec![];
274 } else if arg == "-Wl,--disable-auto-image-base"
275 || arg == "-Wl,--dynamicbase"
276 || arg == "-Wl,--large-address-aware"
277 || (arg.starts_with("-Wl,")
278 && (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
279 {
280 return vec![];
285 } else if arg == "-lmsvcrt" {
286 return vec![];
287 }
288 } else if arg == "-Wl,--no-undefined-version" {
289 return vec![];
292 } else if arg == "-Wl,-znostart-stop-gc" {
293 return vec![];
296 }
297 if target_info.is_musl || target_info.is_ohos {
298 if arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt") {
300 return vec![];
301 } else if arg == "-Wl,-melf_i386" {
302 return vec![];
304 }
305 if rustc_ver.major == 1
306 && rustc_ver.minor < 59
307 && arg.ends_with(".rlib")
308 && arg.contains("liblibc-")
309 {
310 return vec![];
313 }
314 if arg == "-lc" {
315 return vec![];
316 }
317 }
318 if arg.starts_with("-march=") {
319 if target_info.is_arm || target_info.is_i386 {
321 return vec![];
322 } else if target_info.is_riscv64 {
323 return vec!["-march=generic_rv64".to_string()];
324 } else if arg.starts_with("-march=armv8-a") {
325 let mut args_march = if target_info
326 .target
327 .as_ref()
328 .map(|x| x.starts_with("aarch64-macos"))
329 .unwrap_or_default()
330 {
331 vec![arg.replace("armv8-a", "apple_m1")]
332 } else if target_info
333 .target
334 .as_ref()
335 .map(|x| x.starts_with("aarch64-linux"))
336 .unwrap_or_default()
337 {
338 vec![arg
339 .replace("armv8-a", "generic+v8a")
340 .replace("simd", "neon")]
341 } else {
342 vec![arg.to_string()]
343 };
344 if arg == "-march=armv8-a+crypto" {
345 args_march.append(&mut vec![
351 "-Xassembler".to_owned(),
352 "-march=armv8-a+crypto".to_owned(),
353 ]);
354 }
355 return args_march;
356 }
357 }
358 if target_info.is_macos {
359 if arg.starts_with("-Wl,-exported_symbols_list,") {
360 return vec![];
363 }
364 if arg == "-Wl,-dylib" {
365 return vec![];
367 }
368 }
369 vec![arg.to_string()]
370 }
371
372 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
373 let undefined = args
374 .iter()
375 .position(|x| x == "-undefined")
376 .and_then(|i| args.get(i + 1));
377 matches!(undefined, Some(x) if x == "dynamic_lookup")
378 }
379
380 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
381 if (zig_version.major, zig_version.minor) >= (0, 12) {
383 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
384 } else {
385 false
386 }
387 }
388
389 fn add_macos_specific_args(
390 &self,
391 new_cmd_args: &mut Vec<String>,
392 zig_version: &semver::Version,
393 ) -> Result<()> {
394 let sdkroot = Self::macos_sdk_root();
395 if (zig_version.major, zig_version.minor) >= (0, 12) {
396 if let Some(ref sdkroot) = sdkroot {
398 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
399 }
400 }
401 if let Some(ref sdkroot) = sdkroot {
402 let include_prefix = if (zig_version.major, zig_version.minor) < (0, 14) {
403 sdkroot
404 } else {
405 Path::new("/")
406 };
407 new_cmd_args.extend_from_slice(&[
408 "-isystem".to_string(),
409 format!("{}", include_prefix.join("usr").join("include").display()),
410 format!("-L{}", include_prefix.join("usr").join("lib").display()),
411 format!(
412 "-F{}",
413 include_prefix
414 .join("System")
415 .join("Library")
416 .join("Frameworks")
417 .display()
418 ),
419 "-DTARGET_OS_IPHONE=0".to_string(),
420 ]);
421 }
422
423 let cache_dir = cache_dir();
425 let deps_dir = cache_dir.join("deps");
426 fs::create_dir_all(&deps_dir)?;
427 write_tbd_files(&deps_dir)?;
428 new_cmd_args.push("-L".to_string());
429 new_cmd_args.push(format!("{}", deps_dir.display()));
430 Ok(())
431 }
432
433 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
435 let mut child = Self::command()?
436 .arg(cmd)
437 .args(cmd_args)
438 .spawn()
439 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
440 let status = child.wait().expect("Failed to wait on zig child process");
441 if !status.success() {
442 process::exit(status.code().unwrap_or(1));
443 }
444 Ok(())
445 }
446
447 pub fn command() -> Result<Command> {
449 let (zig, zig_args) = Self::find_zig()?;
450 let mut cmd = Command::new(zig);
451 cmd.args(zig_args);
452 Ok(cmd)
453 }
454
455 fn zig_version() -> Result<semver::Version> {
456 let output = Self::command()?.arg("version").output()?;
457 let version_str =
458 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
459 let version = semver::Version::parse(version_str.trim())?;
460 Ok(version)
461 }
462
463 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
465 Self::find_zig_python()
466 .or_else(|_| Self::find_zig_bin())
467 .context("Failed to find zig")
468 }
469
470 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
472 let zig_path = zig_path()?;
473 let output = Command::new(&zig_path).arg("version").output()?;
474
475 let version_str = str::from_utf8(&output.stdout).with_context(|| {
476 format!("`{} version` didn't return utf8 output", zig_path.display())
477 })?;
478 Self::validate_zig_version(version_str)?;
479 Ok((zig_path, Vec::new()))
480 }
481
482 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
484 let python_path = python_path()?;
485 let output = Command::new(&python_path)
486 .args(["-m", "ziglang", "version"])
487 .output()?;
488
489 let version_str = str::from_utf8(&output.stdout).with_context(|| {
490 format!(
491 "`{} -m ziglang version` didn't return utf8 output",
492 python_path.display()
493 )
494 })?;
495 Self::validate_zig_version(version_str)?;
496 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
497 }
498
499 fn validate_zig_version(version: &str) -> Result<()> {
500 let min_ver = semver::Version::new(0, 9, 0);
501 let version = semver::Version::parse(version.trim())?;
502 if version >= min_ver {
503 Ok(())
504 } else {
505 bail!(
506 "zig version {} is too old, need at least {}",
507 version,
508 min_ver
509 )
510 }
511 }
512
513 pub fn lib_dir() -> Result<PathBuf> {
515 let (zig, zig_args) = Self::find_zig()?;
516 let output = Command::new(zig).args(zig_args).arg("env").output()?;
517 let zig_env: ZigEnv = serde_json::from_slice(&output.stdout)?;
518 Ok(PathBuf::from(zig_env.lib_dir))
519 }
520
521 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
522 where
523 K: AsRef<OsStr>,
524 V: AsRef<OsStr>,
525 {
526 let command_env_contains_no_key =
527 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
528
529 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
530 command.env(name, value);
531 }
532 }
533
534 pub(crate) fn apply_command_env(
535 manifest_path: Option<&Path>,
536 release: bool,
537 cargo: &cargo_options::CommonOptions,
538 cmd: &mut Command,
539 enable_zig_ar: bool,
540 ) -> Result<()> {
541 let rust_targets = cargo
543 .target
544 .iter()
545 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
546 .collect::<Vec<&str>>();
547 let rustc_meta = rustc_version::version_meta()?;
548 Self::add_env_if_missing(
549 cmd,
550 "CARGO_ZIGBUILD_RUSTC_VERSION",
551 rustc_meta.semver.to_string(),
552 );
553 let host_target = &rustc_meta.host;
554 for (parsed_target, raw_target) in rust_targets.iter().zip(&cargo.target) {
555 let env_target = parsed_target.replace('-', "_");
556 let zig_wrapper = prepare_zig_linker(raw_target)?;
557
558 if is_mingw_shell() {
559 let zig_cc = zig_wrapper.cc.to_slash_lossy();
560 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
561 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
562 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
563 if !parsed_target.contains("wasm") {
564 Self::add_env_if_missing(
565 cmd,
566 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
567 &*zig_cc,
568 );
569 }
570 } else {
571 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
572 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
573 if !parsed_target.contains("wasm") {
574 Self::add_env_if_missing(
575 cmd,
576 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
577 &zig_wrapper.cc,
578 );
579 }
580 }
581
582 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
583 if enable_zig_ar {
586 if parsed_target.contains("msvc") {
587 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
588 } else {
589 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
590 }
591 }
592
593 Self::setup_os_deps(manifest_path, release, cargo)?;
594
595 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
596 if env::var_os(&cmake_toolchain_file_env).is_none()
597 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
598 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
599 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
600 {
601 if let Ok(cmake_toolchain_file) =
602 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
603 {
604 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
605 }
606 }
607
608 if raw_target.contains("windows-gnu") {
609 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
610 }
611
612 if raw_target.contains("apple-darwin") {
613 if let Some(sdkroot) = Self::macos_sdk_root() {
614 if env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none() {
615 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
617 }
618 }
619 }
620
621 if host_target == parsed_target {
624 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
625 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
628 }
629 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
630 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
631 }
632
633 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
635 .context("Failed to collect `zig cc` options")?;
636 if raw_target.contains("apple-darwin") {
637 options.push("-DTARGET_OS_IPHONE=0".to_string());
639 }
640 let escaped_options = shlex::try_join(options.iter().map(|s| &s[..]))?;
641 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
642 let fallback_value = env::var(bindgen_env);
643 for target in [&env_target[..], parsed_target] {
644 let name = format!("{bindgen_env}_{target}");
645 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
646 if shlex::split(&value).is_none() {
647 value = shlex::try_quote(&value)?.into_owned();
649 }
650 if !value.is_empty() {
651 value.push(' ');
652 }
653 value.push_str(&escaped_options);
654 env::set_var(name, value);
655 } else {
656 env::set_var(name, escaped_options.clone());
657 }
658 }
659 }
660 Ok(())
661 }
662
663 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
667 #[derive(Debug, PartialEq, Eq)]
668 enum Kind {
669 Normal,
670 Framework,
671 }
672
673 #[derive(Debug)]
674 struct PerLanguageOptions {
675 glibc_minor_ver: Option<u32>,
676 include_paths: Vec<(Kind, String)>,
677 }
678
679 fn collect_per_language_options(
680 program: &Path,
681 ext: &str,
682 raw_target: &str,
683 ) -> Result<PerLanguageOptions> {
684 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
686 if !empty_file_path.exists() {
687 fs::write(&empty_file_path, "")?;
688 }
689
690 let output = Command::new(program)
691 .arg("-E")
692 .arg(&empty_file_path)
693 .arg("-v")
694 .output()?;
695 let stderr = String::from_utf8(output.stderr)?;
697 if !output.status.success() {
698 bail!(
699 "Failed to run `zig cc -v` with status {}: {}",
700 output.status,
701 stderr.trim(),
702 );
703 }
704
705 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
709 let stderr = &stderr[start + 16..];
710 let end = stderr
711 .find(|c: char| !c.is_ascii_digit())
712 .unwrap_or(stderr.len());
713 stderr[..end].parse().ok()
714 } else {
715 None
716 };
717
718 let start = stderr
719 .find("#include <...> search starts here:")
720 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
721 + 34;
722 let end = stderr
723 .find("End of search list.")
724 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
725
726 let mut include_paths = Vec::new();
727 for mut line in stderr[start..end].lines() {
728 line = line.trim();
729 let mut kind = Kind::Normal;
730 if line.ends_with(" (framework directory)") {
731 line = line[..line.len() - 22].trim();
732 kind = Kind::Framework;
733 } else if line.ends_with(" (headermap)") {
734 bail!("C/C++ search path includes header maps, which are not supported");
735 }
736 if !line.is_empty() {
737 include_paths.push((kind, line.to_owned()));
738 }
739 }
740
741 if raw_target.contains("ohos") {
743 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
744 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
745 }
746
747 Ok(PerLanguageOptions {
748 include_paths,
749 glibc_minor_ver,
750 })
751 }
752
753 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
754 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
755
756 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
758 bail!(
759 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
760 c_opts.glibc_minor_ver,
761 cpp_opts.glibc_minor_ver,
762 );
763 }
764 let c_paths = c_opts.include_paths;
765 let mut cpp_paths = cpp_opts.include_paths;
766 let cpp_pre_len = cpp_paths
767 .iter()
768 .position(|p| {
769 p == c_paths
770 .iter()
771 .filter(|(kind, _)| *kind == Kind::Normal)
772 .next()
773 .unwrap()
774 })
775 .unwrap_or_default();
776 let cpp_post_len = cpp_paths.len()
777 - cpp_paths
778 .iter()
779 .position(|p| p == c_paths.last().unwrap())
780 .unwrap_or_default()
781 - 1;
782
783 let mut args = Vec::new();
836
837 args.push("-nostdinc".to_owned());
840
841 if raw_target.contains("musl") || raw_target.contains("ohos") {
849 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
850 args.push("-D_LARGEFILE64_SOURCE".to_owned());
853 }
854 args.extend(
855 [
856 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
857 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
858 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
859 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
860 "-D_LIBCPP_ABI_VERSION=1",
861 "-D_LIBCPP_ABI_NAMESPACE=__1",
862 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
863 ]
864 .into_iter()
865 .map(ToString::to_string),
866 );
867 if let Some(ver) = c_opts.glibc_minor_ver {
868 args.push(format!("-D__GLIBC_MINOR__={ver}"));
870 }
871
872 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
873 if kind != Kind::Normal {
874 continue;
876 }
877 args.push("-cxx-isystem".to_owned());
883 args.push(path);
884 }
885
886 for (kind, path) in c_paths {
887 match kind {
888 Kind::Normal => {
889 args.push("-Xclang".to_owned());
891 args.push("-c-isystem".to_owned());
892 args.push("-Xclang".to_owned());
893 args.push(path.clone());
894 args.push("-cxx-isystem".to_owned());
895 args.push(path);
896 }
897 Kind::Framework => {
898 args.push("-iframework".to_owned());
899 args.push(path);
900 }
901 }
902 }
903
904 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
905 assert!(kind == Kind::Normal);
906 args.push("-cxx-isystem".to_owned());
907 args.push(path);
908 }
909
910 Ok(args)
911 }
912
913 fn setup_os_deps(
914 manifest_path: Option<&Path>,
915 release: bool,
916 cargo: &cargo_options::CommonOptions,
917 ) -> Result<()> {
918 for target in &cargo.target {
919 if target.contains("apple") {
920 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
921 target_dir.join(target)
922 } else {
923 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
924 if !manifest_path.exists() {
925 continue;
927 }
928 let metadata = cargo_metadata::MetadataCommand::new()
929 .manifest_path(manifest_path)
930 .no_deps()
931 .exec()?;
932 metadata.target_directory.into_std_path_buf().join(target)
933 };
934 let profile = match cargo.profile.as_deref() {
935 Some("dev" | "test") => "debug",
936 Some("release" | "bench") => "release",
937 Some(profile) => profile,
938 None => {
939 if release {
940 "release"
941 } else {
942 "debug"
943 }
944 }
945 };
946 let deps_dir = target_dir.join(profile).join("deps");
947 fs::create_dir_all(&deps_dir)?;
948 if !target_dir.join("CACHEDIR.TAG").is_file() {
949 let _ = write_file(
951 &target_dir.join("CACHEDIR.TAG"),
952 "Signature: 8a477f597d28d172789f06886806bc55
953# This file is a cache directory tag created by cargo.
954# For information about cache directory tags see https://bford.info/cachedir/
955",
956 );
957 }
958 write_tbd_files(&deps_dir)?;
959 } else if target.contains("arm") && target.contains("linux") {
960 if let Ok(lib_dir) = Zig::lib_dir() {
962 let arm_features_h = lib_dir
963 .join("libc")
964 .join("glibc")
965 .join("sysdeps")
966 .join("arm")
967 .join("arm-features.h");
968 if !arm_features_h.is_file() {
969 fs::write(arm_features_h, ARM_FEATURES_H)?;
970 }
971 }
972 } else if target.contains("windows-gnu") {
973 if let Ok(lib_dir) = Zig::lib_dir() {
974 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
975 let synchronization_def = lib_common.join("synchronization.def");
976 if !synchronization_def.is_file() {
977 let api_ms_win_core_synch_l1_2_0_def =
978 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
979 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
981 }
982 }
983 }
984 }
985 Ok(())
986 }
987
988 fn setup_cmake_toolchain(
989 target: &str,
990 zig_wrapper: &ZigWrapper,
991 enable_zig_ar: bool,
992 ) -> Result<PathBuf> {
993 let cmake = cache_dir().join("cmake");
994 fs::create_dir_all(&cmake)?;
995
996 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
997 let triple: Triple = target.parse()?;
998 let os = triple.operating_system.to_string();
999 let arch = triple.architecture.to_string();
1000 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1001 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1002 ("darwin", "aarch64") => ("Darwin", "arm64"),
1003 ("linux", arch) => {
1004 let cmake_arch = match arch {
1005 "powerpc" => "ppc",
1006 "powerpc64" => "ppc64",
1007 "powerpc64le" => "ppc64le",
1008 _ => arch,
1009 };
1010 ("Linux", cmake_arch)
1011 }
1012 ("windows", "x86_64") => ("Windows", "AMD64"),
1013 ("windows", "i686") => ("Windows", "X86"),
1014 ("windows", "aarch64") => ("Windows", "ARM64"),
1015 (os, arch) => (os, arch),
1016 };
1017 let mut content = format!(
1018 r#"
1019set(CMAKE_SYSTEM_NAME {system_name})
1020set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1021set(CMAKE_C_COMPILER {cc})
1022set(CMAKE_CXX_COMPILER {cxx})
1023set(CMAKE_RANLIB {ranlib})
1024set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1025set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1026 system_name = system_name,
1027 system_processor = system_processor,
1028 cc = zig_wrapper.cc.to_slash_lossy(),
1029 cxx = zig_wrapper.cxx.to_slash_lossy(),
1030 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1031 );
1032 if enable_zig_ar {
1033 content.push_str(&format!(
1034 "\nset(CMAKE_AR {})\n",
1035 zig_wrapper.ar.to_slash_lossy()
1036 ));
1037 }
1038 write_file(&toolchain_file, &content)?;
1039 Ok(toolchain_file)
1040 }
1041
1042 #[cfg(target_os = "macos")]
1043 fn macos_sdk_root() -> Option<PathBuf> {
1044 match env::var_os("SDKROOT") {
1045 Some(sdkroot) => {
1046 if !sdkroot.is_empty() {
1047 Some(sdkroot.into())
1048 } else {
1049 None
1050 }
1051 }
1052 None => {
1053 let output = Command::new("xcrun")
1054 .args(["--sdk", "macosx", "--show-sdk-path"])
1055 .output();
1056 if let Ok(output) = output {
1057 if output.status.success() {
1058 if let Ok(stdout) = String::from_utf8(output.stdout) {
1059 let stdout = stdout.trim();
1060 if !stdout.is_empty() {
1061 return Some(stdout.into());
1062 }
1063 }
1064 }
1065 }
1066 None
1067 }
1068 }
1069 }
1070
1071 #[cfg(not(target_os = "macos"))]
1072 fn macos_sdk_root() -> Option<PathBuf> {
1073 match env::var_os("SDKROOT") {
1074 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1075 _ => None,
1076 }
1077 }
1078}
1079
1080fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1081 let existing_content = fs::read_to_string(path).unwrap_or_default();
1082 if existing_content != content {
1083 fs::write(path, content)?;
1084 }
1085 Ok(())
1086}
1087
1088fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1089 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1090 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1091 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1092 Ok(())
1093}
1094
1095fn cache_dir() -> PathBuf {
1096 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1097 .ok()
1098 .map(|s| s.into())
1099 .or_else(dirs::cache_dir)
1100 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1102 .join(env!("CARGO_PKG_NAME"))
1103 .join(env!("CARGO_PKG_VERSION"))
1104}
1105
1106#[derive(Debug, Deserialize)]
1107struct ZigEnv {
1108 lib_dir: String,
1109}
1110
1111#[derive(Debug, Clone)]
1113pub struct ZigWrapper {
1114 pub cc: PathBuf,
1115 pub cxx: PathBuf,
1116 pub ar: PathBuf,
1117 pub ranlib: PathBuf,
1118 pub lib: PathBuf,
1119}
1120
1121#[derive(Debug, Clone, Default, PartialEq)]
1122struct TargetFlags {
1123 pub target_cpu: String,
1124 pub target_feature: String,
1125}
1126
1127impl TargetFlags {
1128 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1129 let mut parsed = Self::default();
1130
1131 let f = rustflags::from_encoded(encoded);
1132 for flag in f {
1133 if let rustflags::Flag::Codegen { opt, value } = flag {
1134 let key = opt.replace('-', "_");
1135 match key.as_str() {
1136 "target_cpu" => {
1137 if let Some(value) = value {
1138 parsed.target_cpu = value;
1139 }
1140 }
1141 "target_feature" => {
1142 if let Some(value) = value {
1144 if !parsed.target_feature.is_empty() {
1145 parsed.target_feature.push(',');
1146 }
1147 parsed.target_feature.push_str(&value);
1148 }
1149 }
1150 _ => {}
1151 }
1152 }
1153 }
1154 Ok(parsed)
1155 }
1156}
1157
1158#[allow(clippy::blocks_in_conditions)]
1167pub fn prepare_zig_linker(target: &str) -> Result<ZigWrapper> {
1168 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1169 let abi_suffix = if abi_suffix.is_empty() {
1170 String::new()
1171 } else {
1172 if abi_suffix
1173 .split_once('.')
1174 .filter(|(x, y)| {
1175 !x.is_empty()
1176 && x.chars().all(|c| c.is_ascii_digit())
1177 && !y.is_empty()
1178 && y.chars().all(|c| c.is_ascii_digit())
1179 })
1180 .is_none()
1181 {
1182 bail!("Malformed zig target abi suffix.")
1183 }
1184 format!(".{abi_suffix}")
1185 };
1186 let triple: Triple = rust_target
1187 .parse()
1188 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1189 let arch = triple.architecture.to_string();
1190 let target_env = match (triple.architecture, triple.environment) {
1191 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1192 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1193 (_, Environment::GnuLlvm) => Environment::Gnu,
1194 (_, environment) => environment,
1195 };
1196 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1197 let file_target = target.trim_end_matches('.');
1198
1199 let mut cc_args = vec![
1200 "-g".to_owned(),
1202 "-fno-sanitize=all".to_owned(),
1204 ];
1205
1206 let zig_mcpu_default = match triple.operating_system {
1209 OperatingSystem::Linux => {
1210 match arch.as_str() {
1211 "arm" => match target_env {
1213 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1214 Environment::Gnueabihf | Environment::Musleabihf => {
1215 "generic+v6+strict_align+vfp2-d32"
1216 }
1217 _ => "",
1218 },
1219 "armv5te" => "generic+soft_float+strict_align",
1220 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1221 arch_str @ ("i586" | "i686") => {
1222 if arch_str == "i586" {
1223 "pentium"
1224 } else {
1225 "pentium4"
1226 }
1227 }
1228 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1229 "s390x" => "z10-vector",
1230 _ => "",
1231 }
1232 }
1233 _ => "",
1234 };
1235
1236 let zig_mcpu_override = {
1240 let cargo_config = cargo_config2::Config::load()?;
1241 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1242 let encoded_rust_flags = rust_flags.encode()?;
1243 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1244 target_flags.target_cpu.replace('-', "_")
1247 };
1248
1249 if !zig_mcpu_override.is_empty() {
1250 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1251 } else if !zig_mcpu_default.is_empty() {
1252 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1253 }
1254
1255 match triple.operating_system {
1256 OperatingSystem::Linux => {
1257 let zig_arch = match arch.as_str() {
1258 "arm" => "arm",
1260 "armv5te" => "arm",
1261 "armv7" => "arm",
1262 "i586" | "i686" => {
1263 let zig_version = Zig::zig_version()?;
1264 if zig_version.major == 0 && zig_version.minor >= 11 {
1265 "x86"
1266 } else {
1267 "i386"
1268 }
1269 }
1270 "riscv64gc" => "riscv64",
1271 "s390x" => "s390x",
1272 _ => arch.as_str(),
1273 };
1274 cc_args.push(format!("-target {zig_arch}-linux-{target_env}{abi_suffix}"));
1275 }
1276 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1277 let zig_version = Zig::zig_version()?;
1278 if zig_version > semver::Version::new(0, 9, 1) {
1281 cc_args.push(format!("-target {arch}-macos-none{abi_suffix}"));
1282 } else {
1283 cc_args.push(format!("-target {arch}-macos-gnu{abi_suffix}"));
1284 }
1285 }
1286 OperatingSystem::Windows { .. } => {
1287 let zig_arch = match arch.as_str() {
1288 "i686" => {
1289 let zig_version = Zig::zig_version()?;
1290 if zig_version.major == 0 && zig_version.minor >= 11 {
1291 "x86"
1292 } else {
1293 "i386"
1294 }
1295 }
1296 arch => arch,
1297 };
1298 cc_args.push(format!(
1299 "-target {zig_arch}-windows-{target_env}{abi_suffix}"
1300 ));
1301 }
1302 OperatingSystem::Emscripten => {
1303 cc_args.push(format!("-target {arch}-emscripten{abi_suffix}"));
1304 }
1305 OperatingSystem::Wasi => {
1306 cc_args.push(format!("-target {arch}-wasi{abi_suffix}"));
1307 }
1308 OperatingSystem::WasiP1 => {
1309 cc_args.push(format!("-target {arch}-wasi.0.1.0{abi_suffix}"));
1310 }
1311 OperatingSystem::Unknown => {
1312 if triple.architecture == Architecture::Wasm32
1313 || triple.architecture == Architecture::Wasm64
1314 {
1315 cc_args.push(format!("-target {arch}-freestanding{abi_suffix}"));
1316 } else {
1317 bail!("unsupported target '{rust_target}'")
1318 }
1319 }
1320 _ => bail!(format!("unsupported target '{rust_target}'")),
1321 };
1322
1323 let zig_linker_dir = cache_dir();
1324 fs::create_dir_all(&zig_linker_dir)?;
1325
1326 if triple.operating_system == OperatingSystem::Linux {
1327 if matches!(
1328 triple.environment,
1329 Environment::Gnu
1330 | Environment::Gnuspe
1331 | Environment::Gnux32
1332 | Environment::Gnueabi
1333 | Environment::Gnuabi64
1334 | Environment::GnuIlp32
1335 | Environment::Gnueabihf
1336 ) {
1337 let glibc_version = if abi_suffix.is_empty() {
1338 (2, 17)
1339 } else {
1340 let mut parts = abi_suffix[1..].split('.');
1341 let major: usize = parts.next().unwrap().parse()?;
1342 let minor: usize = parts.next().unwrap().parse()?;
1343 (major, minor)
1344 };
1345 if glibc_version < (2, 28) {
1347 use crate::linux::{FCNTL_H, FCNTL_MAP};
1348
1349 let zig_version = Zig::zig_version()?;
1350 if zig_version.major == 0 && zig_version.minor < 11 {
1351 let fcntl_map = zig_linker_dir.join("fcntl.map");
1352 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1353 if existing_content != FCNTL_MAP {
1354 fs::write(&fcntl_map, FCNTL_MAP)?;
1355 }
1356 let fcntl_h = zig_linker_dir.join("fcntl.h");
1357 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1358 if existing_content != FCNTL_H {
1359 fs::write(&fcntl_h, FCNTL_H)?;
1360 }
1361
1362 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1363 cc_args.push(format!("-include {}", fcntl_h.display()));
1364 }
1365 }
1366 } else if matches!(
1367 triple.environment,
1368 Environment::Musl
1369 | Environment::Muslabi64
1370 | Environment::Musleabi
1371 | Environment::Musleabihf
1372 ) {
1373 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1374
1375 let zig_version = Zig::zig_version()?;
1376 let rustc_version = rustc_version::version_meta()?.semver;
1377
1378 if (zig_version.major, zig_version.minor) >= (0, 11)
1383 && (rustc_version.major, rustc_version.minor) < (1, 72)
1384 {
1385 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1386 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1387
1388 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1389 }
1390 }
1391 }
1392
1393 let cc_args_str = cc_args.join(" ");
1394 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1395 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1396 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1397 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1398 write_linker_wrapper(&zig_cc, "cc", &cc_args_str)?;
1399 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str)?;
1400 write_linker_wrapper(&zig_ranlib, "ranlib", "")?;
1401
1402 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1403 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1404 symlink_wrapper(&zig_ar)?;
1405 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1406 symlink_wrapper(&zig_lib)?;
1407
1408 Ok(ZigWrapper {
1409 cc: zig_cc,
1410 cxx: zig_cxx,
1411 ar: zig_ar,
1412 ranlib: zig_ranlib,
1413 lib: zig_lib,
1414 })
1415}
1416
1417fn symlink_wrapper(target: &Path) -> Result<()> {
1418 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1419 PathBuf::from(exe)
1420 } else {
1421 env::current_exe()?
1422 };
1423 #[cfg(windows)]
1424 {
1425 if !target.exists() {
1426 if std::fs::hard_link(¤t_exe, target).is_err() {
1428 std::fs::copy(¤t_exe, target)?;
1430 }
1431 }
1432 }
1433
1434 #[cfg(unix)]
1435 {
1436 if !target.exists() {
1437 if fs::read_link(target).is_ok() {
1438 fs::remove_file(target)?;
1440 }
1441 std::os::unix::fs::symlink(current_exe, target)?;
1442 }
1443 }
1444 Ok(())
1445}
1446
1447#[cfg(target_family = "unix")]
1449fn write_linker_wrapper(path: &Path, command: &str, args: &str) -> Result<()> {
1450 let mut buf = Vec::<u8>::new();
1451 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1452 PathBuf::from(exe)
1453 } else {
1454 env::current_exe()?
1455 };
1456 writeln!(&mut buf, "#!/bin/sh")?;
1457 writeln!(
1458 &mut buf,
1459 "exec \"{}\" zig {} -- {} \"$@\"",
1460 current_exe.display(),
1461 command,
1462 args
1463 )?;
1464
1465 let existing_content = fs::read(path).unwrap_or_default();
1469 if existing_content != buf {
1470 OpenOptions::new()
1471 .create(true)
1472 .write(true)
1473 .truncate(true)
1474 .mode(0o700)
1475 .open(path)?
1476 .write_all(&buf)?;
1477 }
1478 Ok(())
1479}
1480
1481#[cfg(not(target_family = "unix"))]
1483fn write_linker_wrapper(path: &Path, command: &str, args: &str) -> Result<()> {
1484 let mut buf = Vec::<u8>::new();
1485 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1486 PathBuf::from(exe)
1487 } else {
1488 env::current_exe()?
1489 };
1490 let current_exe = if is_mingw_shell() {
1491 current_exe.to_slash_lossy().to_string()
1492 } else {
1493 current_exe.display().to_string()
1494 };
1495 writeln!(
1496 &mut buf,
1497 "\"{}\" zig {} -- {} %*",
1498 adjust_canonicalization(current_exe),
1499 command,
1500 args
1501 )?;
1502
1503 let existing_content = fs::read(path).unwrap_or_default();
1504 if existing_content != buf {
1505 fs::write(path, buf)?;
1506 }
1507 Ok(())
1508}
1509
1510pub(crate) fn is_mingw_shell() -> bool {
1511 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
1512}
1513
1514#[cfg(target_os = "windows")]
1516pub fn adjust_canonicalization(p: String) -> String {
1517 const VERBATIM_PREFIX: &str = r#"\\?\"#;
1518 if p.starts_with(VERBATIM_PREFIX) {
1519 p[VERBATIM_PREFIX.len()..].to_string()
1520 } else {
1521 p
1522 }
1523}
1524
1525fn python_path() -> Result<PathBuf> {
1526 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
1527 Ok(which::which(python)?)
1528}
1529
1530fn zig_path() -> Result<PathBuf> {
1531 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
1532 Ok(which::which(zig)?)
1533}
1534
1535#[cfg(test)]
1536mod tests {
1537 use super::*;
1538
1539 #[test]
1540 fn test_target_flags() {
1541 let cases = [
1542 ("-C target-feature=-crt-static", "", "-crt-static"),
1544 ("-C target-cpu=native", "native", ""),
1545 (
1546 "--deny warnings --codegen target-feature=+crt-static",
1547 "",
1548 "+crt-static",
1549 ),
1550 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
1551 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
1552 (
1553 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
1554 "native",
1555 "-avx512bf16,-avx512bitalg",
1556 ),
1557 (
1558 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
1559 "x86-64",
1560 "",
1561 ),
1562 (
1563 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
1564 "",
1565 "+crt-static,+avx",
1566 ),
1567 ];
1568
1569 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
1570 let args = cargo_config2::Flags::from_space_separated(input);
1571 let encoded_rust_flags = args.encode().unwrap();
1572 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
1573 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
1574 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
1575 }
1576 }
1577}