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" || arg == "-Wl,--dynamic-list" {
153 skip_next_arg = true;
157 } else {
158 new_cmd_args.push(arg);
159 }
160 }
161 }
162
163 if target_info.is_mips32 {
164 new_cmd_args.push("-Wl,-z,notext".to_string());
166 }
167
168 if self.has_undefined_dynamic_lookup(cmd_args) {
169 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
170 }
171 if target_info.is_macos {
172 if self.should_add_libcharset(cmd_args, &zig_version) {
173 new_cmd_args.push("-lcharset".to_string());
174 }
175 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
176 }
177
178 let mut command = Self::command()?;
181 if (zig_version.major, zig_version.minor) >= (0, 15) {
182 if let Some(sdkroot) = Self::macos_sdk_root() {
183 command.env("SDKROOT", sdkroot);
184 }
185 }
186
187 let mut child = command
188 .arg(cmd)
189 .args(new_cmd_args)
190 .spawn()
191 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
192 let status = child.wait().expect("Failed to wait on zig child process");
193 if !status.success() {
194 process::exit(status.code().unwrap_or(1));
195 }
196 Ok(())
197 }
198
199 fn process_linker_response_file(
200 &self,
201 arg: &str,
202 rustc_ver: &rustc_version::Version,
203 zig_version: &semver::Version,
204 target_info: &TargetInfo,
205 ) -> Result<String> {
206 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
210 let content = if target_info.is_windows_msvc {
211 if content_bytes[0..2] != [255, 254] {
212 bail!(
213 "linker response file `{}` didn't start with a utf16 BOM",
214 &arg
215 );
216 }
217 let content_utf16: Vec<u16> = content_bytes[2..]
218 .chunks_exact(2)
219 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
220 .collect();
221 String::from_utf16(&content_utf16).with_context(|| {
222 format!(
223 "linker response file `{}` didn't contain valid utf16 content",
224 &arg
225 )
226 })?
227 } else {
228 String::from_utf8(content_bytes).with_context(|| {
229 format!(
230 "linker response file `{}` didn't contain valid utf8 content",
231 &arg
232 )
233 })?
234 };
235 let mut link_args: Vec<_> = content
236 .split('\n')
237 .flat_map(|arg| self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info))
238 .collect();
239 if self.has_undefined_dynamic_lookup(&link_args) {
240 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
241 }
242 if target_info.is_macos && self.should_add_libcharset(&link_args, &zig_version) {
243 link_args.push("-lcharset".to_string());
244 }
245 if target_info.is_windows_msvc {
246 let new_content = link_args.join("\n");
247 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
248 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
250 out.push(c as u8);
252 out.push((c >> 8) as u8);
253 }
254 fs::write(arg.trim_start_matches('@'), out)?;
255 } else {
256 fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
257 }
258 Ok(arg.to_string())
259 }
260
261 fn filter_linker_arg(
262 &self,
263 arg: &str,
264 rustc_ver: &rustc_version::Version,
265 zig_version: &semver::Version,
266 target_info: &TargetInfo,
267 ) -> Vec<String> {
268 if arg == "-lgcc_s" {
269 return vec!["-lunwind".to_string()];
271 } else if arg.starts_with("--target=") {
272 return vec![];
274 } else if arg.starts_with("-e") && arg.len() > 2 && !arg.starts_with("-export") {
275 let entry = &arg[2..];
279 return vec![format!("-Wl,--entry={}", entry)];
280 }
281 if (target_info.is_arm || target_info.is_windows_gnu)
282 && arg.ends_with(".rlib")
283 && arg.contains("libcompiler_builtins-")
284 {
285 return vec![];
287 }
288 if target_info.is_windows_gnu {
289 #[allow(clippy::if_same_then_else)]
290 if arg == "-lgcc_eh" {
291 return vec!["-lc++".to_string()];
294 } else if arg == "-Wl,-Bdynamic" && (zig_version.major, zig_version.minor) >= (0, 11) {
295 return vec!["-Wl,-search_paths_first".to_owned()];
300 } else if arg == "-lwindows" || arg == "-l:libpthread.a" || arg == "-lgcc" {
301 return vec![];
302 } else if arg == "-Wl,--disable-auto-image-base"
303 || arg == "-Wl,--dynamicbase"
304 || arg == "-Wl,--large-address-aware"
305 || (arg.starts_with("-Wl,")
306 && (arg.ends_with("/list.def") || arg.ends_with("\\list.def")))
307 {
308 return vec![];
313 } else if arg == "-lmsvcrt" {
314 return vec![];
315 }
316 } else if arg == "-Wl,--no-undefined-version" {
317 return vec![];
320 } else if arg == "-Wl,-znostart-stop-gc" {
321 return vec![];
324 } else if arg.starts_with("-Wl,-plugin-opt") {
325 return vec![];
329 }
330 if target_info.is_musl || target_info.is_ohos {
331 if arg.ends_with(".o") && arg.contains("self-contained") && arg.contains("crt") {
333 return vec![];
334 } else if arg == "-Wl,-melf_i386" {
335 return vec![];
337 }
338 if rustc_ver.major == 1
339 && rustc_ver.minor < 59
340 && arg.ends_with(".rlib")
341 && arg.contains("liblibc-")
342 {
343 return vec![];
346 }
347 if arg == "-lc" {
348 return vec![];
349 }
350 }
351 if arg.starts_with("-march=") {
352 if target_info.is_arm || target_info.is_i386 {
354 return vec![];
355 } else if target_info.is_riscv64 {
356 return vec!["-march=generic_rv64".to_string()];
357 } else if arg.starts_with("-march=armv8-a") {
358 let mut args_march = if target_info
359 .target
360 .as_ref()
361 .map(|x| x.starts_with("aarch64-macos"))
362 .unwrap_or_default()
363 {
364 vec![arg.replace("armv8-a", "apple_m1")]
365 } else if target_info
366 .target
367 .as_ref()
368 .map(|x| x.starts_with("aarch64-linux"))
369 .unwrap_or_default()
370 {
371 vec![arg
372 .replace("armv8-a", "generic+v8a")
373 .replace("simd", "neon")]
374 } else {
375 vec![arg.to_string()]
376 };
377 if arg == "-march=armv8-a+crypto" {
378 args_march.append(&mut vec![
384 "-Xassembler".to_owned(),
385 "-march=armv8-a+crypto".to_owned(),
386 ]);
387 }
388 return args_march;
389 }
390 }
391 if target_info.is_macos {
392 if arg.starts_with("-Wl,-exported_symbols_list,") {
393 return vec![];
396 }
397 if arg == "-Wl,-dylib" {
398 return vec![];
400 }
401 }
402 if target_info.is_freebsd {
403 let ignored_libs = ["-lkvm", "-lmemstat", "-lprocstat", "-ldevstat"];
404 if ignored_libs.contains(&arg) {
405 return vec![];
406 }
407 }
408 vec![arg.to_string()]
409 }
410
411 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
412 let undefined = args
413 .iter()
414 .position(|x| x == "-undefined")
415 .and_then(|i| args.get(i + 1));
416 matches!(undefined, Some(x) if x == "dynamic_lookup")
417 }
418
419 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
420 if (zig_version.major, zig_version.minor) >= (0, 12) {
422 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
423 } else {
424 false
425 }
426 }
427
428 fn add_macos_specific_args(
429 &self,
430 new_cmd_args: &mut Vec<String>,
431 zig_version: &semver::Version,
432 ) -> Result<()> {
433 let sdkroot = Self::macos_sdk_root();
434 if (zig_version.major, zig_version.minor) >= (0, 12) {
435 if let Some(ref sdkroot) = sdkroot {
439 if (zig_version.major, zig_version.minor) < (0, 15) {
440 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
441 }
442 }
444 }
445 if let Some(ref sdkroot) = sdkroot {
446 let include_prefix = if (zig_version.major, zig_version.minor) < (0, 15) {
447 sdkroot
448 } else {
449 Path::new("/")
450 };
451 new_cmd_args.extend_from_slice(&[
452 "-isystem".to_string(),
453 format!("{}", include_prefix.join("usr").join("include").display()),
454 format!("-L{}", include_prefix.join("usr").join("lib").display()),
455 format!(
456 "-F{}",
457 include_prefix
458 .join("System")
459 .join("Library")
460 .join("Frameworks")
461 .display()
462 ),
463 "-DTARGET_OS_IPHONE=0".to_string(),
464 ]);
465 }
466
467 let cache_dir = cache_dir();
469 let deps_dir = cache_dir.join("deps");
470 fs::create_dir_all(&deps_dir)?;
471 write_tbd_files(&deps_dir)?;
472 new_cmd_args.push("-L".to_string());
473 new_cmd_args.push(format!("{}", deps_dir.display()));
474 Ok(())
475 }
476
477 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
479 let mut child = Self::command()?
480 .arg(cmd)
481 .args(cmd_args)
482 .spawn()
483 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
484 let status = child.wait().expect("Failed to wait on zig child process");
485 if !status.success() {
486 process::exit(status.code().unwrap_or(1));
487 }
488 Ok(())
489 }
490
491 pub fn command() -> Result<Command> {
493 let (zig, zig_args) = Self::find_zig()?;
494 let mut cmd = Command::new(zig);
495 cmd.args(zig_args);
496 Ok(cmd)
497 }
498
499 fn zig_version() -> Result<semver::Version> {
500 static ZIG_VERSION: OnceLock<semver::Version> = OnceLock::new();
501
502 if let Some(version) = ZIG_VERSION.get() {
503 return Ok(version.clone());
504 }
505 if let Ok(version_str) = env::var("CARGO_ZIGBUILD_ZIG_VERSION") {
507 if let Ok(version) = semver::Version::parse(&version_str) {
508 return Ok(ZIG_VERSION.get_or_init(|| version).clone());
509 }
510 }
511 let output = Self::command()?.arg("version").output()?;
512 let version_str =
513 str::from_utf8(&output.stdout).context("`zig version` didn't return utf8 output")?;
514 let version = semver::Version::parse(version_str.trim())?;
515 Ok(ZIG_VERSION.get_or_init(|| version).clone())
516 }
517
518 pub fn find_zig() -> Result<(PathBuf, Vec<String>)> {
520 static ZIG_PATH: OnceLock<(PathBuf, Vec<String>)> = OnceLock::new();
521
522 if let Some(cached) = ZIG_PATH.get() {
523 return Ok(cached.clone());
524 }
525 let result = Self::find_zig_python()
526 .or_else(|_| Self::find_zig_bin())
527 .context("Failed to find zig")?;
528 Ok(ZIG_PATH.get_or_init(|| result).clone())
529 }
530
531 fn find_zig_bin() -> Result<(PathBuf, Vec<String>)> {
533 let zig_path = zig_path()?;
534 let output = Command::new(&zig_path).arg("version").output()?;
535
536 let version_str = str::from_utf8(&output.stdout).with_context(|| {
537 format!("`{} version` didn't return utf8 output", zig_path.display())
538 })?;
539 Self::validate_zig_version(version_str)?;
540 Ok((zig_path, Vec::new()))
541 }
542
543 fn find_zig_python() -> Result<(PathBuf, Vec<String>)> {
545 let python_path = python_path()?;
546 let output = Command::new(&python_path)
547 .args(["-m", "ziglang", "version"])
548 .output()?;
549
550 let version_str = str::from_utf8(&output.stdout).with_context(|| {
551 format!(
552 "`{} -m ziglang version` didn't return utf8 output",
553 python_path.display()
554 )
555 })?;
556 Self::validate_zig_version(version_str)?;
557 Ok((python_path, vec!["-m".to_string(), "ziglang".to_string()]))
558 }
559
560 fn validate_zig_version(version: &str) -> Result<()> {
561 let min_ver = semver::Version::new(0, 9, 0);
562 let version = semver::Version::parse(version.trim())?;
563 if version >= min_ver {
564 Ok(())
565 } else {
566 bail!(
567 "zig version {} is too old, need at least {}",
568 version,
569 min_ver
570 )
571 }
572 }
573
574 pub fn lib_dir() -> Result<PathBuf> {
576 static LIB_DIR: OnceLock<PathBuf> = OnceLock::new();
577
578 if let Some(cached) = LIB_DIR.get() {
579 return Ok(cached.clone());
580 }
581 let (zig, zig_args) = Self::find_zig()?;
582 let output = Command::new(zig).args(zig_args).arg("env").output()?;
583 let zig_env: ZigEnv = serde_json::from_slice(&output.stdout)?;
584 Ok(LIB_DIR
585 .get_or_init(|| PathBuf::from(zig_env.lib_dir))
586 .clone())
587 }
588
589 fn add_env_if_missing<K, V>(command: &mut Command, name: K, value: V)
590 where
591 K: AsRef<OsStr>,
592 V: AsRef<OsStr>,
593 {
594 let command_env_contains_no_key =
595 |name: &K| !command.get_envs().any(|(key, _)| name.as_ref() == key);
596
597 if command_env_contains_no_key(&name) && env::var_os(&name).is_none() {
598 command.env(name, value);
599 }
600 }
601
602 pub(crate) fn apply_command_env(
603 manifest_path: Option<&Path>,
604 release: bool,
605 cargo: &cargo_options::CommonOptions,
606 cmd: &mut Command,
607 enable_zig_ar: bool,
608 ) -> Result<()> {
609 let rust_targets = cargo
611 .target
612 .iter()
613 .map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target))
614 .collect::<Vec<&str>>();
615 let rustc_meta = rustc_version::version_meta()?;
616 Self::add_env_if_missing(
617 cmd,
618 "CARGO_ZIGBUILD_RUSTC_VERSION",
619 rustc_meta.semver.to_string(),
620 );
621 let host_target = &rustc_meta.host;
622 for (parsed_target, raw_target) in rust_targets.iter().zip(&cargo.target) {
623 let env_target = parsed_target.replace('-', "_");
624 let zig_wrapper = prepare_zig_linker(raw_target)?;
625
626 if is_mingw_shell() {
627 let zig_cc = zig_wrapper.cc.to_slash_lossy();
628 let zig_cxx = zig_wrapper.cxx.to_slash_lossy();
629 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &*zig_cc);
630 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &*zig_cxx);
631 if !parsed_target.contains("wasm") {
632 Self::add_env_if_missing(
633 cmd,
634 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
635 &*zig_cc,
636 );
637 }
638 } else {
639 Self::add_env_if_missing(cmd, format!("CC_{env_target}"), &zig_wrapper.cc);
640 Self::add_env_if_missing(cmd, format!("CXX_{env_target}"), &zig_wrapper.cxx);
641 if !parsed_target.contains("wasm") {
642 Self::add_env_if_missing(
643 cmd,
644 format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
645 &zig_wrapper.cc,
646 );
647 }
648 }
649
650 Self::add_env_if_missing(cmd, format!("RANLIB_{env_target}"), &zig_wrapper.ranlib);
651 if enable_zig_ar {
654 if parsed_target.contains("msvc") {
655 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.lib);
656 } else {
657 Self::add_env_if_missing(cmd, format!("AR_{env_target}"), &zig_wrapper.ar);
658 }
659 }
660
661 Self::setup_os_deps(manifest_path, release, cargo)?;
662
663 let cmake_toolchain_file_env = format!("CMAKE_TOOLCHAIN_FILE_{env_target}");
664 if env::var_os(&cmake_toolchain_file_env).is_none()
665 && env::var_os(format!("CMAKE_TOOLCHAIN_FILE_{parsed_target}")).is_none()
666 && env::var_os("TARGET_CMAKE_TOOLCHAIN_FILE").is_none()
667 && env::var_os("CMAKE_TOOLCHAIN_FILE").is_none()
668 {
669 if let Ok(cmake_toolchain_file) =
670 Self::setup_cmake_toolchain(parsed_target, &zig_wrapper, enable_zig_ar)
671 {
672 cmd.env(cmake_toolchain_file_env, cmake_toolchain_file);
673 }
674 }
675
676 if raw_target.contains("windows-gnu") {
677 cmd.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
678 let cache_dir = cache_dir();
681 let existing_path = env::var_os("PATH").unwrap_or_default();
682 let paths = std::iter::once(cache_dir).chain(env::split_paths(&existing_path));
683 if let Ok(new_path) = env::join_paths(paths) {
684 cmd.env("PATH", new_path);
685 }
686 }
687
688 if raw_target.contains("apple-darwin") {
689 if let Some(sdkroot) = Self::macos_sdk_root() {
690 if env::var_os("PKG_CONFIG_SYSROOT_DIR").is_none() {
691 cmd.env("PKG_CONFIG_SYSROOT_DIR", sdkroot);
693 }
694 }
695 }
696
697 if host_target == parsed_target {
700 if !matches!(rustc_meta.channel, rustc_version::Channel::Nightly) {
701 cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
704 }
705 cmd.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
706 cmd.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
707 }
708
709 let mut options = Self::collect_zig_cc_options(&zig_wrapper, raw_target)
711 .context("Failed to collect `zig cc` options")?;
712 if raw_target.contains("apple-darwin") {
713 options.push("-DTARGET_OS_IPHONE=0".to_string());
715 }
716 let escaped_options = shell_words::join(options.iter().map(|s| &s[..]));
717 let bindgen_env = "BINDGEN_EXTRA_CLANG_ARGS";
718 let fallback_value = env::var(bindgen_env);
719 for target in [&env_target[..], parsed_target] {
720 let name = format!("{bindgen_env}_{target}");
721 if let Ok(mut value) = env::var(&name).or(fallback_value.clone()) {
722 if shell_words::split(&value).is_err() {
723 value = shell_words::quote(&value).into_owned();
725 }
726 if !value.is_empty() {
727 value.push(' ');
728 }
729 value.push_str(&escaped_options);
730 env::set_var(name, value);
731 } else {
732 env::set_var(name, escaped_options.clone());
733 }
734 }
735 }
736 Ok(())
737 }
738
739 fn collect_zig_cc_options(zig_wrapper: &ZigWrapper, raw_target: &str) -> Result<Vec<String>> {
743 #[derive(Debug, PartialEq, Eq)]
744 enum Kind {
745 Normal,
746 Framework,
747 }
748
749 #[derive(Debug)]
750 struct PerLanguageOptions {
751 glibc_minor_ver: Option<u32>,
752 include_paths: Vec<(Kind, String)>,
753 }
754
755 fn collect_per_language_options(
756 program: &Path,
757 ext: &str,
758 raw_target: &str,
759 ) -> Result<PerLanguageOptions> {
760 let empty_file_path = cache_dir().join(format!(".intentionally-empty-file.{ext}"));
762 if !empty_file_path.exists() {
763 fs::write(&empty_file_path, "")?;
764 }
765
766 let output = Command::new(program)
767 .arg("-E")
768 .arg(&empty_file_path)
769 .arg("-v")
770 .output()?;
771 let stderr = String::from_utf8(output.stderr)?;
773 if !output.status.success() {
774 bail!(
775 "Failed to run `zig cc -v` with status {}: {}",
776 output.status,
777 stderr.trim(),
778 );
779 }
780
781 let glibc_minor_ver = if let Some(start) = stderr.find("__GLIBC_MINOR__=") {
785 let stderr = &stderr[start + 16..];
786 let end = stderr
787 .find(|c: char| !c.is_ascii_digit())
788 .unwrap_or(stderr.len());
789 stderr[..end].parse().ok()
790 } else {
791 None
792 };
793
794 let start = stderr
795 .find("#include <...> search starts here:")
796 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?
797 + 34;
798 let end = stderr
799 .find("End of search list.")
800 .ok_or_else(|| anyhow!("Failed to parse `zig cc -v` output"))?;
801
802 let mut include_paths = Vec::new();
803 for mut line in stderr[start..end].lines() {
804 line = line.trim();
805 let mut kind = Kind::Normal;
806 if line.ends_with(" (framework directory)") {
807 line = line[..line.len() - 22].trim();
808 kind = Kind::Framework;
809 } else if line.ends_with(" (headermap)") {
810 bail!("C/C++ search path includes header maps, which are not supported");
811 }
812 if !line.is_empty() {
813 include_paths.push((kind, line.to_owned()));
814 }
815 }
816
817 if raw_target.contains("ohos") {
819 let ndk = env::var("OHOS_NDK_HOME").expect("Can't get NDK path");
820 include_paths.push((Kind::Normal, format!("{}/native/sysroot/usr/include", ndk)));
821 }
822
823 Ok(PerLanguageOptions {
824 include_paths,
825 glibc_minor_ver,
826 })
827 }
828
829 let c_opts = collect_per_language_options(&zig_wrapper.cc, "c", raw_target)?;
830 let cpp_opts = collect_per_language_options(&zig_wrapper.cxx, "cpp", raw_target)?;
831
832 if c_opts.glibc_minor_ver != cpp_opts.glibc_minor_ver {
834 bail!(
835 "`zig cc` gives a different glibc minor version for C ({:?}) and C++ ({:?})",
836 c_opts.glibc_minor_ver,
837 cpp_opts.glibc_minor_ver,
838 );
839 }
840 let c_paths = c_opts.include_paths;
841 let mut cpp_paths = cpp_opts.include_paths;
842 let cpp_pre_len = cpp_paths
843 .iter()
844 .position(|p| {
845 p == c_paths
846 .iter()
847 .filter(|(kind, _)| *kind == Kind::Normal)
848 .next()
849 .unwrap()
850 })
851 .unwrap_or_default();
852 let cpp_post_len = cpp_paths.len()
853 - cpp_paths
854 .iter()
855 .position(|p| p == c_paths.last().unwrap())
856 .unwrap_or_default()
857 - 1;
858
859 let mut args = Vec::new();
912
913 args.push("-nostdinc".to_owned());
916
917 if raw_target.contains("musl") || raw_target.contains("ohos") {
925 args.push("-D_LIBCPP_HAS_MUSL_LIBC".to_owned());
926 args.push("-D_LARGEFILE64_SOURCE".to_owned());
929 }
930 args.extend(
931 [
932 "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
933 "-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS",
934 "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
935 "-D_LIBCPP_PSTL_CPU_BACKEND_SERIAL",
936 "-D_LIBCPP_ABI_VERSION=1",
937 "-D_LIBCPP_ABI_NAMESPACE=__1",
938 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
939 ]
940 .into_iter()
941 .map(ToString::to_string),
942 );
943 if let Some(ver) = c_opts.glibc_minor_ver {
944 args.push(format!("-D__GLIBC_MINOR__={ver}"));
946 }
947
948 for (kind, path) in cpp_paths.drain(..cpp_pre_len) {
949 if kind != Kind::Normal {
950 continue;
952 }
953 args.push("-cxx-isystem".to_owned());
959 args.push(path);
960 }
961
962 for (kind, path) in c_paths {
963 match kind {
964 Kind::Normal => {
965 args.push("-Xclang".to_owned());
967 args.push("-c-isystem".to_owned());
968 args.push("-Xclang".to_owned());
969 args.push(path.clone());
970 args.push("-cxx-isystem".to_owned());
971 args.push(path);
972 }
973 Kind::Framework => {
974 args.push("-iframework".to_owned());
975 args.push(path);
976 }
977 }
978 }
979
980 for (kind, path) in cpp_paths.drain(cpp_paths.len() - cpp_post_len..) {
981 assert!(kind == Kind::Normal);
982 args.push("-cxx-isystem".to_owned());
983 args.push(path);
984 }
985
986 Ok(args)
987 }
988
989 fn setup_os_deps(
990 manifest_path: Option<&Path>,
991 release: bool,
992 cargo: &cargo_options::CommonOptions,
993 ) -> Result<()> {
994 for target in &cargo.target {
995 if target.contains("apple") {
996 let target_dir = if let Some(target_dir) = cargo.target_dir.clone() {
997 target_dir.join(target)
998 } else {
999 let manifest_path = manifest_path.unwrap_or_else(|| Path::new("Cargo.toml"));
1000 if !manifest_path.exists() {
1001 continue;
1003 }
1004 let metadata = cargo_metadata::MetadataCommand::new()
1005 .manifest_path(manifest_path)
1006 .no_deps()
1007 .exec()?;
1008 metadata.target_directory.into_std_path_buf().join(target)
1009 };
1010 let profile = match cargo.profile.as_deref() {
1011 Some("dev" | "test") => "debug",
1012 Some("release" | "bench") => "release",
1013 Some(profile) => profile,
1014 None => {
1015 if release {
1016 "release"
1017 } else {
1018 "debug"
1019 }
1020 }
1021 };
1022 let deps_dir = target_dir.join(profile).join("deps");
1023 fs::create_dir_all(&deps_dir)?;
1024 if !target_dir.join("CACHEDIR.TAG").is_file() {
1025 let _ = write_file(
1027 &target_dir.join("CACHEDIR.TAG"),
1028 "Signature: 8a477f597d28d172789f06886806bc55
1029# This file is a cache directory tag created by cargo.
1030# For information about cache directory tags see https://bford.info/cachedir/
1031",
1032 );
1033 }
1034 write_tbd_files(&deps_dir)?;
1035 } else if target.contains("arm") && target.contains("linux") {
1036 if let Ok(lib_dir) = Zig::lib_dir() {
1038 let arm_features_h = lib_dir
1039 .join("libc")
1040 .join("glibc")
1041 .join("sysdeps")
1042 .join("arm")
1043 .join("arm-features.h");
1044 if !arm_features_h.is_file() {
1045 fs::write(arm_features_h, ARM_FEATURES_H)?;
1046 }
1047 }
1048 } else if target.contains("windows-gnu") {
1049 if let Ok(lib_dir) = Zig::lib_dir() {
1050 let lib_common = lib_dir.join("libc").join("mingw").join("lib-common");
1051 let synchronization_def = lib_common.join("synchronization.def");
1052 if !synchronization_def.is_file() {
1053 let api_ms_win_core_synch_l1_2_0_def =
1054 lib_common.join("api-ms-win-core-synch-l1-2-0.def");
1055 fs::copy(api_ms_win_core_synch_l1_2_0_def, synchronization_def).ok();
1057 }
1058 }
1059 }
1060 }
1061 Ok(())
1062 }
1063
1064 fn setup_cmake_toolchain(
1065 target: &str,
1066 zig_wrapper: &ZigWrapper,
1067 enable_zig_ar: bool,
1068 ) -> Result<PathBuf> {
1069 let cmake = cache_dir().join("cmake");
1070 fs::create_dir_all(&cmake)?;
1071
1072 let toolchain_file = cmake.join(format!("{target}-toolchain.cmake"));
1073 let triple: Triple = target.parse()?;
1074 let os = triple.operating_system.to_string();
1075 let arch = triple.architecture.to_string();
1076 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
1077 ("darwin", "x86_64") => ("Darwin", "x86_64"),
1078 ("darwin", "aarch64") => ("Darwin", "arm64"),
1079 ("linux", arch) => {
1080 let cmake_arch = match arch {
1081 "powerpc" => "ppc",
1082 "powerpc64" => "ppc64",
1083 "powerpc64le" => "ppc64le",
1084 _ => arch,
1085 };
1086 ("Linux", cmake_arch)
1087 }
1088 ("windows", "x86_64") => ("Windows", "AMD64"),
1089 ("windows", "i686") => ("Windows", "X86"),
1090 ("windows", "aarch64") => ("Windows", "ARM64"),
1091 (os, arch) => (os, arch),
1092 };
1093 let mut content = format!(
1094 r#"
1095set(CMAKE_SYSTEM_NAME {system_name})
1096set(CMAKE_SYSTEM_PROCESSOR {system_processor})
1097set(CMAKE_C_COMPILER {cc})
1098set(CMAKE_CXX_COMPILER {cxx})
1099set(CMAKE_RANLIB {ranlib})
1100set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
1101set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)"#,
1102 system_name = system_name,
1103 system_processor = system_processor,
1104 cc = zig_wrapper.cc.to_slash_lossy(),
1105 cxx = zig_wrapper.cxx.to_slash_lossy(),
1106 ranlib = zig_wrapper.ranlib.to_slash_lossy(),
1107 );
1108 if enable_zig_ar {
1109 content.push_str(&format!(
1110 "\nset(CMAKE_AR {})\n",
1111 zig_wrapper.ar.to_slash_lossy()
1112 ));
1113 }
1114 write_file(&toolchain_file, &content)?;
1115 Ok(toolchain_file)
1116 }
1117
1118 #[cfg(target_os = "macos")]
1119 fn macos_sdk_root() -> Option<PathBuf> {
1120 static SDK_ROOT: OnceLock<Option<PathBuf>> = OnceLock::new();
1121
1122 SDK_ROOT
1123 .get_or_init(|| match env::var_os("SDKROOT") {
1124 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1125 _ => {
1126 let output = Command::new("xcrun")
1127 .args(["--sdk", "macosx", "--show-sdk-path"])
1128 .output()
1129 .ok()?;
1130 if output.status.success() {
1131 let stdout = String::from_utf8(output.stdout).ok()?;
1132 let stdout = stdout.trim();
1133 if !stdout.is_empty() {
1134 return Some(stdout.into());
1135 }
1136 }
1137 None
1138 }
1139 })
1140 .clone()
1141 }
1142
1143 #[cfg(not(target_os = "macos"))]
1144 fn macos_sdk_root() -> Option<PathBuf> {
1145 match env::var_os("SDKROOT") {
1146 Some(sdkroot) if !sdkroot.is_empty() => Some(sdkroot.into()),
1147 _ => None,
1148 }
1149 }
1150}
1151
1152fn write_file(path: &Path, content: &str) -> Result<(), anyhow::Error> {
1153 let existing_content = fs::read_to_string(path).unwrap_or_default();
1154 if existing_content != content {
1155 fs::write(path, content)?;
1156 }
1157 Ok(())
1158}
1159
1160fn write_tbd_files(deps_dir: &Path) -> Result<(), anyhow::Error> {
1161 write_file(&deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
1162 write_file(&deps_dir.join("libcharset.1.tbd"), LIBCHARSET_TBD)?;
1163 write_file(&deps_dir.join("libcharset.tbd"), LIBCHARSET_TBD)?;
1164 Ok(())
1165}
1166
1167fn cache_dir() -> PathBuf {
1168 env::var("CARGO_ZIGBUILD_CACHE_DIR")
1169 .ok()
1170 .map(|s| s.into())
1171 .or_else(dirs::cache_dir)
1172 .unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
1174 .join(env!("CARGO_PKG_NAME"))
1175 .join(env!("CARGO_PKG_VERSION"))
1176}
1177
1178#[derive(Debug, Deserialize)]
1179struct ZigEnv {
1180 lib_dir: String,
1181}
1182
1183#[derive(Debug, Clone)]
1185pub struct ZigWrapper {
1186 pub cc: PathBuf,
1187 pub cxx: PathBuf,
1188 pub ar: PathBuf,
1189 pub ranlib: PathBuf,
1190 pub lib: PathBuf,
1191}
1192
1193#[derive(Debug, Clone, Default, PartialEq)]
1194struct TargetFlags {
1195 pub target_cpu: String,
1196 pub target_feature: String,
1197}
1198
1199impl TargetFlags {
1200 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
1201 let mut parsed = Self::default();
1202
1203 let f = rustflags::from_encoded(encoded);
1204 for flag in f {
1205 if let rustflags::Flag::Codegen { opt, value } = flag {
1206 let key = opt.replace('-', "_");
1207 match key.as_str() {
1208 "target_cpu" => {
1209 if let Some(value) = value {
1210 parsed.target_cpu = value;
1211 }
1212 }
1213 "target_feature" => {
1214 if let Some(value) = value {
1216 if !parsed.target_feature.is_empty() {
1217 parsed.target_feature.push(',');
1218 }
1219 parsed.target_feature.push_str(&value);
1220 }
1221 }
1222 _ => {}
1223 }
1224 }
1225 }
1226 Ok(parsed)
1227 }
1228}
1229
1230#[allow(clippy::blocks_in_conditions)]
1239pub fn prepare_zig_linker(target: &str) -> Result<ZigWrapper> {
1240 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
1241 let abi_suffix = if abi_suffix.is_empty() {
1242 String::new()
1243 } else {
1244 if abi_suffix
1245 .split_once('.')
1246 .filter(|(x, y)| {
1247 !x.is_empty()
1248 && x.chars().all(|c| c.is_ascii_digit())
1249 && !y.is_empty()
1250 && y.chars().all(|c| c.is_ascii_digit())
1251 })
1252 .is_none()
1253 {
1254 bail!("Malformed zig target abi suffix.")
1255 }
1256 format!(".{abi_suffix}")
1257 };
1258 let triple: Triple = rust_target
1259 .parse()
1260 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
1261 let arch = triple.architecture.to_string();
1262 let target_env = match (triple.architecture, triple.environment) {
1263 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
1264 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
1265 (_, Environment::GnuLlvm) => Environment::Gnu,
1266 (_, environment) => environment,
1267 };
1268 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
1269 let file_target = target.trim_end_matches('.');
1270
1271 let mut cc_args = vec![
1272 "-g".to_owned(),
1274 "-fno-sanitize=all".to_owned(),
1276 ];
1277
1278 let zig_mcpu_default = match triple.operating_system {
1281 OperatingSystem::Linux => {
1282 match arch.as_str() {
1283 "arm" => match target_env {
1285 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
1286 Environment::Gnueabihf | Environment::Musleabihf => {
1287 "generic+v6+strict_align+vfp2-d32"
1288 }
1289 _ => "",
1290 },
1291 "armv5te" => "generic+soft_float+strict_align",
1292 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
1293 arch_str @ ("i586" | "i686") => {
1294 if arch_str == "i586" {
1295 "pentium"
1296 } else {
1297 "pentium4"
1298 }
1299 }
1300 "riscv64gc" => "generic_rv64+m+a+f+d+c",
1301 "s390x" => "z10-vector",
1302 _ => "",
1303 }
1304 }
1305 _ => "",
1306 };
1307
1308 let zig_mcpu_override = {
1312 let cargo_config = cargo_config2::Config::load()?;
1313 let rust_flags = cargo_config.rustflags(rust_target)?.unwrap_or_default();
1314 let encoded_rust_flags = rust_flags.encode()?;
1315 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
1316 target_flags.target_cpu.replace('-', "_")
1319 };
1320
1321 if !zig_mcpu_override.is_empty() {
1322 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
1323 } else if !zig_mcpu_default.is_empty() {
1324 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
1325 }
1326
1327 match triple.operating_system {
1328 OperatingSystem::Linux => {
1329 let zig_arch = match arch.as_str() {
1330 "arm" => "arm",
1332 "armv5te" => "arm",
1333 "armv7" => "arm",
1334 "i586" | "i686" => {
1335 let zig_version = Zig::zig_version()?;
1336 if zig_version.major == 0 && zig_version.minor >= 11 {
1337 "x86"
1338 } else {
1339 "i386"
1340 }
1341 }
1342 "riscv64gc" => "riscv64",
1343 "s390x" => "s390x",
1344 _ => arch.as_str(),
1345 };
1346 let mut zig_target_env = target_env.to_string();
1347
1348 let zig_version = Zig::zig_version()?;
1349
1350 if zig_version >= semver::Version::new(0, 15, 0)
1354 && arch.as_str() == "armv7"
1355 && target_env == Environment::Ohos
1356 {
1357 zig_target_env = "ohoseabi".to_string();
1358 }
1359
1360 cc_args.push("-target".to_string());
1361 cc_args.push(format!("{zig_arch}-linux-{zig_target_env}{abi_suffix}"));
1362 }
1363 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
1364 let zig_version = Zig::zig_version()?;
1365 if zig_version > semver::Version::new(0, 9, 1) {
1368 cc_args.push("-target".to_string());
1369 cc_args.push(format!("{arch}-macos-none{abi_suffix}"));
1370 } else {
1371 cc_args.push("-target".to_string());
1372 cc_args.push(format!("{arch}-macos-gnu{abi_suffix}"));
1373 }
1374 }
1375 OperatingSystem::Windows { .. } => {
1376 let zig_arch = match arch.as_str() {
1377 "i686" => {
1378 let zig_version = Zig::zig_version()?;
1379 if zig_version.major == 0 && zig_version.minor >= 11 {
1380 "x86"
1381 } else {
1382 "i386"
1383 }
1384 }
1385 arch => arch,
1386 };
1387 cc_args.push("-target".to_string());
1388 cc_args.push(format!("{zig_arch}-windows-{target_env}{abi_suffix}"));
1389 }
1390 OperatingSystem::Emscripten => {
1391 cc_args.push("-target".to_string());
1392 cc_args.push(format!("{arch}-emscripten{abi_suffix}"));
1393 }
1394 OperatingSystem::Wasi => {
1395 cc_args.push("-target".to_string());
1396 cc_args.push(format!("{arch}-wasi{abi_suffix}"));
1397 }
1398 OperatingSystem::WasiP1 => {
1399 cc_args.push("-target".to_string());
1400 cc_args.push(format!("{arch}-wasi.0.1.0{abi_suffix}"));
1401 }
1402 OperatingSystem::Freebsd => {
1403 let zig_arch = match arch.as_str() {
1404 "i686" => {
1405 let zig_version = Zig::zig_version()?;
1406 if zig_version.major == 0 && zig_version.minor >= 11 {
1407 "x86"
1408 } else {
1409 "i386"
1410 }
1411 }
1412 arch => arch,
1413 };
1414 cc_args.push("-target".to_string());
1415 cc_args.push(format!("{zig_arch}-freebsd"));
1416 }
1417 OperatingSystem::Unknown => {
1418 if triple.architecture == Architecture::Wasm32
1419 || triple.architecture == Architecture::Wasm64
1420 {
1421 cc_args.push("-target".to_string());
1422 cc_args.push(format!("{arch}-freestanding{abi_suffix}"));
1423 } else {
1424 bail!("unsupported target '{rust_target}'")
1425 }
1426 }
1427 _ => bail!(format!("unsupported target '{rust_target}'")),
1428 };
1429
1430 let zig_linker_dir = cache_dir();
1431 fs::create_dir_all(&zig_linker_dir)?;
1432
1433 if triple.operating_system == OperatingSystem::Linux {
1434 if matches!(
1435 triple.environment,
1436 Environment::Gnu
1437 | Environment::Gnuspe
1438 | Environment::Gnux32
1439 | Environment::Gnueabi
1440 | Environment::Gnuabi64
1441 | Environment::GnuIlp32
1442 | Environment::Gnueabihf
1443 ) {
1444 let glibc_version = if abi_suffix.is_empty() {
1445 (2, 17)
1446 } else {
1447 let mut parts = abi_suffix[1..].split('.');
1448 let major: usize = parts.next().unwrap().parse()?;
1449 let minor: usize = parts.next().unwrap().parse()?;
1450 (major, minor)
1451 };
1452 if glibc_version < (2, 28) {
1454 use crate::linux::{FCNTL_H, FCNTL_MAP};
1455
1456 let zig_version = Zig::zig_version()?;
1457 if zig_version.major == 0 && zig_version.minor < 11 {
1458 let fcntl_map = zig_linker_dir.join("fcntl.map");
1459 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
1460 if existing_content != FCNTL_MAP {
1461 fs::write(&fcntl_map, FCNTL_MAP)?;
1462 }
1463 let fcntl_h = zig_linker_dir.join("fcntl.h");
1464 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
1465 if existing_content != FCNTL_H {
1466 fs::write(&fcntl_h, FCNTL_H)?;
1467 }
1468
1469 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
1470 cc_args.push("-include".to_string());
1471 cc_args.push(fcntl_h.display().to_string());
1472 }
1473 }
1474 } else if matches!(
1475 triple.environment,
1476 Environment::Musl
1477 | Environment::Muslabi64
1478 | Environment::Musleabi
1479 | Environment::Musleabihf
1480 ) {
1481 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
1482
1483 let zig_version = Zig::zig_version()?;
1484 let rustc_version = rustc_version::version_meta()?.semver;
1485
1486 if (zig_version.major, zig_version.minor) >= (0, 11)
1491 && (rustc_version.major, rustc_version.minor) < (1, 72)
1492 {
1493 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
1494 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
1495
1496 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
1497 }
1498 }
1499 }
1500
1501 let cc_args_str = join_args_for_script(&cc_args);
1504 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
1505 let zig_cc = zig_linker_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
1506 let zig_cxx = zig_linker_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
1507 let zig_ranlib = zig_linker_dir.join(format!("zigranlib.{file_ext}"));
1508 let zig_version = Zig::zig_version()?;
1509 write_linker_wrapper(&zig_cc, "cc", &cc_args_str, &zig_version)?;
1510 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str, &zig_version)?;
1511 write_linker_wrapper(&zig_ranlib, "ranlib", "", &zig_version)?;
1512
1513 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
1514 let zig_ar = zig_linker_dir.join(format!("ar{exe_ext}"));
1515 symlink_wrapper(&zig_ar)?;
1516 let zig_lib = zig_linker_dir.join(format!("lib{exe_ext}"));
1517 symlink_wrapper(&zig_lib)?;
1518
1519 if matches!(triple.operating_system, OperatingSystem::Windows)
1525 && matches!(triple.environment, Environment::Gnu)
1526 {
1527 let dlltool_name: &str = if cfg!(windows) {
1528 "dlltool"
1529 } else {
1530 match triple.architecture {
1531 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
1532 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
1533 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
1534 _ => "dlltool",
1535 }
1536 };
1537 let zig_dlltool = zig_linker_dir.join(format!("{dlltool_name}{exe_ext}"));
1538 symlink_wrapper(&zig_dlltool)?;
1539 }
1540
1541 Ok(ZigWrapper {
1542 cc: zig_cc,
1543 cxx: zig_cxx,
1544 ar: zig_ar,
1545 ranlib: zig_ranlib,
1546 lib: zig_lib,
1547 })
1548}
1549
1550fn symlink_wrapper(target: &Path) -> Result<()> {
1551 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1552 PathBuf::from(exe)
1553 } else {
1554 env::current_exe()?
1555 };
1556 #[cfg(windows)]
1557 {
1558 if !target.exists() {
1559 if std::fs::hard_link(¤t_exe, target).is_err() {
1561 std::fs::copy(¤t_exe, target)?;
1563 }
1564 }
1565 }
1566
1567 #[cfg(unix)]
1568 {
1569 if !target.exists() {
1570 if fs::read_link(target).is_ok() {
1571 fs::remove_file(target)?;
1573 }
1574 std::os::unix::fs::symlink(current_exe, target)?;
1575 }
1576 }
1577 Ok(())
1578}
1579
1580#[cfg(target_family = "unix")]
1582fn join_args_for_script<I, S>(args: I) -> String
1583where
1584 I: IntoIterator<Item = S>,
1585 S: AsRef<str>,
1586{
1587 shell_words::join(args)
1588}
1589
1590#[cfg(not(target_family = "unix"))]
1596fn quote_for_batch(s: &str) -> String {
1597 let needs_quoting_or_escaping = s.is_empty()
1598 || s.contains(|c: char| {
1599 matches!(
1600 c,
1601 ' ' | '\t' | '"' | '&' | '|' | '<' | '>' | '^' | '%' | '(' | ')' | '!'
1602 )
1603 });
1604
1605 if !needs_quoting_or_escaping {
1606 return s.to_string();
1607 }
1608
1609 let mut out = String::with_capacity(s.len() + 8);
1610 out.push('"');
1611 for c in s.chars() {
1612 match c {
1613 '"' => out.push_str("\"\""),
1614 '%' => out.push_str("%%"),
1615 _ => out.push(c),
1616 }
1617 }
1618 out.push('"');
1619 out
1620}
1621
1622#[cfg(not(target_family = "unix"))]
1624fn join_args_for_script<I, S>(args: I) -> String
1625where
1626 I: IntoIterator<Item = S>,
1627 S: AsRef<str>,
1628{
1629 args.into_iter()
1630 .map(|s| quote_for_batch(s.as_ref()))
1631 .collect::<Vec<_>>()
1632 .join(" ")
1633}
1634
1635#[cfg(target_family = "unix")]
1637fn write_linker_wrapper(
1638 path: &Path,
1639 command: &str,
1640 args: &str,
1641 zig_version: &semver::Version,
1642) -> Result<()> {
1643 let mut buf = Vec::<u8>::new();
1644 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1645 PathBuf::from(exe)
1646 } else {
1647 env::current_exe()?
1648 };
1649 writeln!(&mut buf, "#!/bin/sh")?;
1650
1651 writeln!(
1653 &mut buf,
1654 "export CARGO_ZIGBUILD_ZIG_VERSION={}",
1655 zig_version
1656 )?;
1657
1658 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
1662
1663 writeln!(
1664 &mut buf,
1665 "exec \"{}\" zig {} -- {} \"$@\"",
1666 current_exe.display(),
1667 command,
1668 args
1669 )?;
1670
1671 let existing_content = fs::read(path).unwrap_or_default();
1675 if existing_content != buf {
1676 OpenOptions::new()
1677 .create(true)
1678 .write(true)
1679 .truncate(true)
1680 .mode(0o700)
1681 .open(path)?
1682 .write_all(&buf)?;
1683 }
1684 Ok(())
1685}
1686
1687#[cfg(not(target_family = "unix"))]
1689fn write_linker_wrapper(
1690 path: &Path,
1691 command: &str,
1692 args: &str,
1693 zig_version: &semver::Version,
1694) -> Result<()> {
1695 let mut buf = Vec::<u8>::new();
1696 let current_exe = if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
1697 PathBuf::from(exe)
1698 } else {
1699 env::current_exe()?
1700 };
1701 let current_exe = if is_mingw_shell() {
1702 current_exe.to_slash_lossy().to_string()
1703 } else {
1704 current_exe.display().to_string()
1705 };
1706 writeln!(&mut buf, "@echo off")?;
1707 writeln!(&mut buf, "setlocal DisableDelayedExpansion")?;
1709 writeln!(&mut buf, "set CARGO_ZIGBUILD_ZIG_VERSION={}", zig_version)?;
1711 writeln!(
1712 &mut buf,
1713 "\"{}\" zig {} -- {} %*",
1714 adjust_canonicalization(current_exe),
1715 command,
1716 args
1717 )?;
1718
1719 let existing_content = fs::read(path).unwrap_or_default();
1720 if existing_content != buf {
1721 fs::write(path, buf)?;
1722 }
1723 Ok(())
1724}
1725
1726pub(crate) fn is_mingw_shell() -> bool {
1727 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
1728}
1729
1730#[cfg(target_os = "windows")]
1732pub fn adjust_canonicalization(p: String) -> String {
1733 const VERBATIM_PREFIX: &str = r#"\\?\"#;
1734 if p.starts_with(VERBATIM_PREFIX) {
1735 p[VERBATIM_PREFIX.len()..].to_string()
1736 } else {
1737 p
1738 }
1739}
1740
1741fn python_path() -> Result<PathBuf> {
1742 let python = env::var("CARGO_ZIGBUILD_PYTHON_PATH").unwrap_or_else(|_| "python3".to_string());
1743 Ok(which::which(python)?)
1744}
1745
1746fn zig_path() -> Result<PathBuf> {
1747 let zig = env::var("CARGO_ZIGBUILD_ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
1748 Ok(which::which(zig)?)
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753 use super::*;
1754
1755 #[test]
1756 fn test_target_flags() {
1757 let cases = [
1758 ("-C target-feature=-crt-static", "", "-crt-static"),
1760 ("-C target-cpu=native", "native", ""),
1761 (
1762 "--deny warnings --codegen target-feature=+crt-static",
1763 "",
1764 "+crt-static",
1765 ),
1766 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
1767 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
1768 (
1769 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
1770 "native",
1771 "-avx512bf16,-avx512bitalg",
1772 ),
1773 (
1774 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
1775 "x86-64",
1776 "",
1777 ),
1778 (
1779 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
1780 "",
1781 "+crt-static,+avx",
1782 ),
1783 ];
1784
1785 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
1786 let args = cargo_config2::Flags::from_space_separated(input);
1787 let encoded_rust_flags = args.encode().unwrap();
1788 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
1789 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
1790 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
1791 }
1792 }
1793
1794 #[test]
1795 fn test_join_args_for_script() {
1796 let args = vec!["-target", "x86_64-linux-gnu"];
1798 let result = join_args_for_script(&args);
1799 assert!(result.contains("-target"));
1800 assert!(result.contains("x86_64-linux-gnu"));
1801 }
1802
1803 #[test]
1804 #[cfg(not(target_family = "unix"))]
1805 fn test_quote_for_batch() {
1806 assert_eq!(quote_for_batch("-target"), "-target");
1808 assert_eq!(quote_for_batch("x86_64-linux-gnu"), "x86_64-linux-gnu");
1809
1810 assert_eq!(
1812 quote_for_batch("C:\\Users\\John Doe\\path"),
1813 "\"C:\\Users\\John Doe\\path\""
1814 );
1815
1816 assert_eq!(quote_for_batch(""), "\"\"");
1818
1819 assert_eq!(quote_for_batch("foo&bar"), "\"foo&bar\"");
1821 assert_eq!(quote_for_batch("foo|bar"), "\"foo|bar\"");
1822 assert_eq!(quote_for_batch("foo<bar"), "\"foo<bar\"");
1823 assert_eq!(quote_for_batch("foo>bar"), "\"foo>bar\"");
1824 assert_eq!(quote_for_batch("foo^bar"), "\"foo^bar\"");
1825 assert_eq!(quote_for_batch("foo%bar"), "\"foo%bar\"");
1826
1827 assert_eq!(quote_for_batch("foo\"bar"), "\"foo\"\"bar\"");
1829 }
1830
1831 #[test]
1832 #[cfg(not(target_family = "unix"))]
1833 fn test_join_args_for_script_windows() {
1834 let args = vec![
1836 "-target",
1837 "x86_64-linux-gnu",
1838 "-L",
1839 "C:\\Users\\John Doe\\path",
1840 ];
1841 let result = join_args_for_script(&args);
1842 assert!(result.contains("\"C:\\Users\\John Doe\\path\""));
1844 assert!(result.contains("-target"));
1846 assert!(!result.contains("\"-target\""));
1847 }
1848}