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};
9
10use anyhow::{Context, Result, bail};
11use fs_err as fs;
12#[cfg(not(target_family = "unix"))]
13use path_slash::PathBufExt;
14use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
15
16use super::cli_config::CliConfig;
17use super::locate::cache_dir;
18use super::{Zig, get_dlltool_name, has_system_dlltool};
19
20#[derive(Debug, Clone)]
22pub struct ZigWrapper {
23 pub cc: PathBuf,
24 pub cxx: PathBuf,
25 pub ar: PathBuf,
26 pub ranlib: PathBuf,
27 pub lib: PathBuf,
28 pub target: String,
29}
30
31#[derive(Debug, Clone, Default, PartialEq)]
32struct TargetFlags {
33 pub target_cpu: String,
34 pub target_feature: String,
35}
36
37impl TargetFlags {
38 pub fn parse_from_encoded(encoded: &OsStr) -> Result<Self> {
39 let mut parsed = Self::default();
40
41 let f = rustflags::from_encoded(encoded);
42 for flag in f {
43 if let rustflags::Flag::Codegen { opt, value } = flag {
44 let key = opt.replace('-', "_");
45 match key.as_str() {
46 "target_cpu" => {
47 if let Some(value) = value {
48 parsed.target_cpu = value;
49 }
50 }
51 "target_feature" => {
52 if let Some(value) = value {
54 if !parsed.target_feature.is_empty() {
55 parsed.target_feature.push(',');
56 }
57 parsed.target_feature.push_str(&value);
58 }
59 }
60 _ => {}
61 }
62 }
63 }
64 Ok(parsed)
65 }
66}
67
68#[allow(clippy::blocks_in_conditions)]
77pub fn prepare_zig_linker(
78 target: &str,
79 cargo_config: &cargo_config2::Config,
80) -> Result<ZigWrapper> {
81 prepare_zig_linker_with_cli_config(target, cargo_config, &[])
82}
83
84pub fn prepare_zig_linker_with_cli_config(
88 target: &str,
89 cargo_config: &cargo_config2::Config,
90 config_args: &[String],
91) -> Result<ZigWrapper> {
92 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
93 let abi_suffix = if abi_suffix.is_empty() {
94 String::new()
95 } else {
96 if abi_suffix
97 .split_once('.')
98 .filter(|(x, y)| {
99 !x.is_empty()
100 && x.chars().all(|c| c.is_ascii_digit())
101 && !y.is_empty()
102 && y.chars().all(|c| c.is_ascii_digit())
103 })
104 .is_none()
105 {
106 bail!("Malformed zig target abi suffix.")
107 }
108 format!(".{abi_suffix}")
109 };
110 let triple: Triple = rust_target
111 .parse()
112 .with_context(|| format!("Unsupported Rust target '{rust_target}'"))?;
113 let arch = triple.architecture.to_string();
114 let target_env = zig_target_env(&triple);
115 let file_ext = if cfg!(windows) { "bat" } else { "sh" };
116 let file_target = target.trim_end_matches('.');
117
118 let mut cc_args = vec![
119 "-g".to_owned(),
121 "-fno-sanitize=all".to_owned(),
123 ];
124
125 let zig_mcpu_default = match triple.operating_system {
128 OperatingSystem::Linux => {
129 match arch.as_str() {
130 "arm" => match target_env {
132 Environment::Gnueabi | Environment::Musleabi => "generic+v6+strict_align",
133 Environment::Gnueabihf | Environment::Musleabihf => {
134 "generic+v6+strict_align+vfp2-d32"
135 }
136 _ => "",
137 },
138 "armv5te" => "generic+soft_float+strict_align",
139 "armv7" => "generic+v7a+vfp3-d32+thumb2-neon",
140 arch_str @ ("i586" | "i686") => {
141 if arch_str == "i586" {
142 "pentium"
143 } else {
144 "pentium4"
145 }
146 }
147 "riscv64gc" => "generic_rv64+m+a+f+d+c",
148 "s390x" => "z10-vector",
149 _ => "",
150 }
151 }
152 _ => "",
153 };
154
155 let zig_mcpu_override = {
159 let cli_config = CliConfig::parse(config_args)?;
160 let rust_flags = cli_config
161 .rustflags(cargo_config, rust_target)?
162 .unwrap_or_default();
163 let encoded_rust_flags = rust_flags.encode()?;
164 let target_flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags))?;
165 target_flags.target_cpu.replace('-', "_")
168 };
169
170 if !zig_mcpu_override.is_empty() {
171 cc_args.push(format!("-mcpu={zig_mcpu_override}"));
172 } else if !zig_mcpu_default.is_empty() {
173 cc_args.push(format!("-mcpu={zig_mcpu_default}"));
174 }
175
176 let zig_target = zig_target_triple(
177 rust_target,
178 &triple,
179 target_env,
180 &abi_suffix,
181 &Zig::zig_version()?,
182 )?;
183 cc_args.push("-target".to_string());
184 cc_args.push(zig_target.clone());
185
186 let zig_linker_dir = cache_dir();
187 fs::create_dir_all(&zig_linker_dir)?;
188
189 if triple.operating_system == OperatingSystem::Linux {
190 if matches!(
191 triple.environment,
192 Environment::Gnu
193 | Environment::Gnuspe
194 | Environment::Gnux32
195 | Environment::Gnueabi
196 | Environment::Gnuabi64
197 | Environment::GnuIlp32
198 | Environment::Gnueabihf
199 ) {
200 let glibc_version = if abi_suffix.is_empty() {
201 (2, 17)
202 } else {
203 let mut parts = abi_suffix[1..].split('.');
204 let major: usize = parts.next().unwrap().parse()?;
205 let minor: usize = parts.next().unwrap().parse()?;
206 (major, minor)
207 };
208 if glibc_version < (2, 28) {
210 use crate::linux::{FCNTL_H, FCNTL_MAP};
211
212 let zig_version = Zig::zig_version()?;
213 if zig_version.major == 0 && zig_version.minor < 11 {
214 let fcntl_map = zig_linker_dir.join("fcntl.map");
215 let existing_content = fs::read_to_string(&fcntl_map).unwrap_or_default();
216 if existing_content != FCNTL_MAP {
217 fs::write(&fcntl_map, FCNTL_MAP)?;
218 }
219 let fcntl_h = zig_linker_dir.join("fcntl.h");
220 let existing_content = fs::read_to_string(&fcntl_h).unwrap_or_default();
221 if existing_content != FCNTL_H {
222 fs::write(&fcntl_h, FCNTL_H)?;
223 }
224
225 cc_args.push(format!("-Wl,--version-script={}", fcntl_map.display()));
226 cc_args.push("-include".to_string());
227 cc_args.push(fcntl_h.display().to_string());
228 }
229 }
230 } else if matches!(
231 triple.environment,
232 Environment::Musl
233 | Environment::Muslabi64
234 | Environment::Musleabi
235 | Environment::Musleabihf
236 ) {
237 use crate::linux::MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT;
238
239 let zig_version = Zig::zig_version()?;
240 let rustc_version = rustc_version::version_meta()?.semver;
241
242 if (zig_version.major, zig_version.minor) >= (0, 11)
247 && (rustc_version.major, rustc_version.minor) < (1, 72)
248 {
249 let weak_symbols_map = zig_linker_dir.join("musl_weak_symbols_map.ld");
250 fs::write(&weak_symbols_map, MUSL_WEAK_SYMBOLS_MAPPING_SCRIPT)?;
251
252 cc_args.push(format!("-Wl,-T,{}", weak_symbols_map.display()));
253 }
254 }
255 }
256
257 let cc_args_str = join_args_for_script(&cc_args);
260
261 let current_exe = resolve_current_exe()?;
266 let exe_hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC)
267 .checksum(current_exe.as_os_str().as_encoded_bytes());
268 let wrapper_dir = zig_linker_dir
269 .join("wrappers")
270 .join(format!("{:x}", exe_hash));
271 fs::create_dir_all(&wrapper_dir)?;
272
273 let hash = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC).checksum(cc_args_str.as_bytes());
274 let zig_cc = wrapper_dir.join(format!("zigcc-{file_target}-{:x}.{file_ext}", hash));
275 let zig_cxx = wrapper_dir.join(format!("zigcxx-{file_target}-{:x}.{file_ext}", hash));
276 let zig_ranlib = wrapper_dir.join(format!("zigranlib.{file_ext}"));
277 let zig_version = Zig::zig_version()?;
278 let zig_command = Zig::find_zig()?;
279 write_linker_wrapper(&zig_cc, "cc", &cc_args_str, &zig_version, &zig_command)?;
280 write_linker_wrapper(&zig_cxx, "c++", &cc_args_str, &zig_version, &zig_command)?;
281 write_linker_wrapper(&zig_ranlib, "ranlib", "", &zig_version, &zig_command)?;
282
283 let exe_ext = if cfg!(windows) { ".exe" } else { "" };
284 let zig_ar = wrapper_dir.join(format!("ar{exe_ext}"));
285 symlink_wrapper(&zig_ar)?;
286 let zig_lib = wrapper_dir.join(format!("lib{exe_ext}"));
287 symlink_wrapper(&zig_lib)?;
288
289 if matches!(triple.operating_system, OperatingSystem::Windows)
295 && matches!(triple.environment, Environment::Gnu)
296 {
297 if !has_system_dlltool(&triple.architecture) {
300 let dlltool_name = get_dlltool_name(&triple.architecture);
301 let zig_dlltool = wrapper_dir.join(format!("{dlltool_name}{exe_ext}"));
302 symlink_wrapper(&zig_dlltool)?;
303 }
304 }
305
306 Ok(ZigWrapper {
307 cc: zig_cc,
308 cxx: zig_cxx,
309 ar: zig_ar,
310 ranlib: zig_ranlib,
311 lib: zig_lib,
312 target: zig_target,
313 })
314}
315
316fn resolve_current_exe() -> Result<PathBuf> {
318 if let Ok(exe) = env::var("CARGO_BIN_EXE_cargo-zigbuild") {
319 Ok(PathBuf::from(exe))
320 } else {
321 Ok(env::current_exe()?)
322 }
323}
324
325pub(crate) fn symlink_wrapper(target: &Path) -> Result<()> {
326 let current_exe = resolve_current_exe()?;
327 #[cfg(windows)]
328 {
329 if !target.exists() {
330 if std::fs::hard_link(¤t_exe, target).is_err() {
332 std::fs::copy(¤t_exe, target)?;
334 }
335 }
336 }
337
338 #[cfg(unix)]
339 {
340 if !target.exists() {
341 if fs::read_link(target).is_ok() {
342 fs::remove_file(target)?;
344 }
345 std::os::unix::fs::symlink(current_exe, target)?;
346 }
347 }
348 Ok(())
349}
350
351#[cfg(target_family = "unix")]
353fn join_args_for_script<I, S>(args: I) -> String
354where
355 I: IntoIterator<Item = S>,
356 S: AsRef<str>,
357{
358 shell_words::join(args)
359}
360
361#[cfg(not(target_family = "unix"))]
367fn quote_for_batch(s: &str) -> String {
368 let needs_quoting_or_escaping = s.is_empty()
369 || s.contains(|c: char| {
370 matches!(
371 c,
372 ' ' | '\t' | '"' | '&' | '|' | '<' | '>' | '^' | '%' | '(' | ')' | '!'
373 )
374 });
375
376 if !needs_quoting_or_escaping {
377 return s.to_string();
378 }
379
380 let mut out = String::with_capacity(s.len() + 8);
381 out.push('"');
382 for c in s.chars() {
383 match c {
384 '"' => out.push_str("\"\""),
385 '%' => out.push_str("%%"),
386 _ => out.push(c),
387 }
388 }
389 out.push('"');
390 out
391}
392
393#[cfg(not(target_family = "unix"))]
395fn join_args_for_script<I, S>(args: I) -> String
396where
397 I: IntoIterator<Item = S>,
398 S: AsRef<str>,
399{
400 args.into_iter()
401 .map(|s| quote_for_batch(s.as_ref()))
402 .collect::<Vec<_>>()
403 .join(" ")
404}
405
406#[cfg(target_family = "unix")]
408fn write_linker_wrapper(
409 path: &Path,
410 command: &str,
411 args: &str,
412 zig_version: &semver::Version,
413 zig_command: &(PathBuf, Vec<String>),
414) -> Result<()> {
415 let mut buf = Vec::<u8>::new();
416 let current_exe = resolve_current_exe()?;
417 writeln!(&mut buf, "#!/bin/sh")?;
418
419 writeln!(
421 &mut buf,
422 "export CARGO_ZIGBUILD_ZIG_VERSION={}",
423 zig_version
424 )?;
425 writeln!(
428 &mut buf,
429 "export CARGO_ZIGBUILD_ZIG_COMMAND={}",
430 shell_words::quote(&zig_command.0.to_string_lossy())
431 )?;
432 if !zig_command.1.is_empty() {
433 writeln!(
434 &mut buf,
435 "export CARGO_ZIGBUILD_ZIG_COMMAND_ARGS={}",
436 shell_words::quote(&zig_command.1.join(" "))
437 )?;
438 }
439
440 writeln!(&mut buf, "if [ -n \"$SDKROOT\" ]; then export SDKROOT; fi")?;
442
443 writeln!(
444 &mut buf,
445 "exec \"{}\" zig {} -- {} \"$@\"",
446 current_exe.display(),
447 command,
448 args
449 )?;
450
451 let existing_content = fs::read(path).unwrap_or_default();
455 if existing_content != buf {
456 OpenOptions::new()
457 .create(true)
458 .write(true)
459 .truncate(true)
460 .mode(0o700)
461 .open(path)?
462 .write_all(&buf)?;
463 }
464 Ok(())
465}
466
467#[cfg(not(target_family = "unix"))]
469fn write_linker_wrapper(
470 path: &Path,
471 command: &str,
472 args: &str,
473 zig_version: &semver::Version,
474 zig_command: &(PathBuf, Vec<String>),
475) -> Result<()> {
476 let mut buf = Vec::<u8>::new();
477 let current_exe = resolve_current_exe()?;
478 let current_exe = if is_mingw_shell() {
479 current_exe.to_slash_lossy().to_string()
480 } else {
481 current_exe.display().to_string()
482 };
483 writeln!(&mut buf, "@echo off")?;
484 writeln!(&mut buf, "setlocal DisableDelayedExpansion")?;
486 writeln!(&mut buf, "set CARGO_ZIGBUILD_ZIG_VERSION={}", zig_version)?;
488 writeln!(
491 &mut buf,
492 "set \"CARGO_ZIGBUILD_ZIG_COMMAND={}\"",
493 zig_command.0.display()
494 )?;
495 if !zig_command.1.is_empty() {
496 writeln!(
497 &mut buf,
498 "set \"CARGO_ZIGBUILD_ZIG_COMMAND_ARGS={}\"",
499 zig_command.1.join(" ")
500 )?;
501 }
502 writeln!(
503 &mut buf,
504 "\"{}\" zig {} -- {} %*",
505 adjust_canonicalization(current_exe),
506 command,
507 args
508 )?;
509
510 let existing_content = fs::read(path).unwrap_or_default();
511 if existing_content != buf {
512 fs::write(path, buf)?;
513 }
514 Ok(())
515}
516
517pub(crate) fn is_mingw_shell() -> bool {
518 env::var_os("MSYSTEM").is_some() && env::var_os("SHELL").is_some()
519}
520
521#[cfg(target_os = "windows")]
523pub fn adjust_canonicalization(p: String) -> String {
524 const VERBATIM_PREFIX: &str = r#"\\?\"#;
525 if p.starts_with(VERBATIM_PREFIX) {
526 p[VERBATIM_PREFIX.len()..].to_string()
527 } else {
528 p
529 }
530}
531
532fn zig_target_env(triple: &Triple) -> Environment {
534 match (triple.architecture, triple.environment) {
535 (Architecture::Mips32(..), Environment::Gnu) => Environment::Gnueabihf,
536 (Architecture::Mips32(..), Environment::Musl) => Environment::Musleabi,
537 (Architecture::Powerpc, Environment::Gnu) => Environment::Gnueabihf,
538 (_, Environment::GnuLlvm) => Environment::Gnu,
539 (_, environment) => environment,
540 }
541}
542
543fn zig_target_triple(
546 rust_target: &str,
547 triple: &Triple,
548 target_env: Environment,
549 abi_suffix: &str,
550 zig_version: &semver::Version,
551) -> Result<String> {
552 let arch = triple.architecture.to_string();
553 let zig_target = match triple.operating_system {
554 OperatingSystem::Linux => {
555 let zig_arch = match arch.as_str() {
556 "arm" => "arm",
558 "armv5te" => "arm",
559 "armv7" => "arm",
560 "i586" | "i686" => {
561 if zig_version.major == 0 && zig_version.minor >= 11 {
562 "x86"
563 } else {
564 "i386"
565 }
566 }
567 "riscv64gc" => "riscv64",
568 "s390x" => "s390x",
569 _ => arch.as_str(),
570 };
571 let mut zig_target_env = target_env.to_string();
572
573 if *zig_version >= semver::Version::new(0, 15, 0)
577 && arch.as_str() == "armv7"
578 && target_env == Environment::Ohos
579 {
580 zig_target_env = "ohoseabi".to_string();
581 }
582
583 format!("{zig_arch}-linux-{zig_target_env}{abi_suffix}")
584 }
585 OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => {
586 if *zig_version > semver::Version::new(0, 9, 1) {
589 format!("{arch}-macos-none{abi_suffix}")
590 } else {
591 format!("{arch}-macos-gnu{abi_suffix}")
592 }
593 }
594 OperatingSystem::Windows => {
595 let zig_arch = match arch.as_str() {
596 "i686" => {
597 if zig_version.major == 0 && zig_version.minor >= 11 {
598 "x86"
599 } else {
600 "i386"
601 }
602 }
603 arch => arch,
604 };
605 format!("{zig_arch}-windows-{target_env}{abi_suffix}")
606 }
607 OperatingSystem::Emscripten => {
608 format!("{arch}-emscripten{abi_suffix}")
609 }
610 OperatingSystem::Wasi => {
611 format!("{arch}-wasi{abi_suffix}")
612 }
613 OperatingSystem::WasiP1 => {
614 format!("{arch}-wasi.0.1.0{abi_suffix}")
615 }
616 OperatingSystem::IOS(_) if triple.environment == Environment::Macabi => {
617 format!("{arch}-maccatalyst-none{abi_suffix}")
620 }
621 OperatingSystem::Freebsd => {
622 let zig_arch = match arch.as_str() {
623 "i686" => {
624 if zig_version.major == 0 && zig_version.minor >= 11 {
625 "x86"
626 } else {
627 "i386"
628 }
629 }
630 arch => arch,
631 };
632 format!("{zig_arch}-freebsd")
633 }
634 OperatingSystem::Openbsd => {
635 format!("{arch}-openbsd")
636 }
637 OperatingSystem::Unknown => {
638 if triple.architecture == Architecture::Wasm32
639 || triple.architecture == Architecture::Wasm64
640 {
641 format!("{arch}-freestanding{abi_suffix}")
642 } else {
643 bail!("unsupported target '{rust_target}'")
644 }
645 }
646 _ => bail!(format!("unsupported target '{rust_target}'")),
647 };
648 Ok(zig_target)
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654
655 #[test]
656 fn test_target_flags() {
657 let cases = [
658 ("-C target-feature=-crt-static", "", "-crt-static"),
660 ("-C target-cpu=native", "native", ""),
661 (
662 "--deny warnings --codegen target-feature=+crt-static",
663 "",
664 "+crt-static",
665 ),
666 ("-C target_cpu=skylake-avx512", "skylake-avx512", ""),
667 ("-Ctarget_cpu=x86-64-v3", "x86-64-v3", ""),
668 (
669 "-C target-cpu=native --cfg foo -C target-feature=-avx512bf16,-avx512bitalg",
670 "native",
671 "-avx512bf16,-avx512bitalg",
672 ),
673 (
674 "--target x86_64-unknown-linux-gnu --codegen=target-cpu=x --codegen=target-cpu=x86-64",
675 "x86-64",
676 "",
677 ),
678 (
679 "-Ctarget-feature=+crt-static -Ctarget-feature=+avx",
680 "",
681 "+crt-static,+avx",
682 ),
683 ];
684
685 for (input, expected_target_cpu, expected_target_feature) in cases.iter() {
686 let args = cargo_config2::Flags::from_space_separated(input);
687 let encoded_rust_flags = args.encode().unwrap();
688 let flags = TargetFlags::parse_from_encoded(OsStr::new(&encoded_rust_flags)).unwrap();
689 assert_eq!(flags.target_cpu, *expected_target_cpu, "{}", input);
690 assert_eq!(flags.target_feature, *expected_target_feature, "{}", input);
691 }
692 }
693
694 #[test]
695 fn test_join_args_for_script() {
696 let args = vec!["-target", "x86_64-linux-gnu"];
698 let result = join_args_for_script(&args);
699 assert!(result.contains("-target"));
700 assert!(result.contains("x86_64-linux-gnu"));
701 }
702
703 #[test]
704 #[cfg(not(target_family = "unix"))]
705 fn test_quote_for_batch() {
706 assert_eq!(quote_for_batch("-target"), "-target");
708 assert_eq!(quote_for_batch("x86_64-linux-gnu"), "x86_64-linux-gnu");
709
710 assert_eq!(
712 quote_for_batch("C:\\Users\\John Doe\\path"),
713 "\"C:\\Users\\John Doe\\path\""
714 );
715
716 assert_eq!(quote_for_batch(""), "\"\"");
718
719 assert_eq!(quote_for_batch("foo&bar"), "\"foo&bar\"");
721 assert_eq!(quote_for_batch("foo|bar"), "\"foo|bar\"");
722 assert_eq!(quote_for_batch("foo<bar"), "\"foo<bar\"");
723 assert_eq!(quote_for_batch("foo>bar"), "\"foo>bar\"");
724 assert_eq!(quote_for_batch("foo^bar"), "\"foo^bar\"");
725 assert_eq!(quote_for_batch("foo%bar"), "\"foo%bar\"");
726
727 assert_eq!(quote_for_batch("foo\"bar"), "\"foo\"\"bar\"");
729 }
730
731 #[test]
732 #[cfg(not(target_family = "unix"))]
733 fn test_join_args_for_script_windows() {
734 let args = vec![
736 "-target",
737 "x86_64-linux-gnu",
738 "-L",
739 "C:\\Users\\John Doe\\path",
740 ];
741 let result = join_args_for_script(&args);
742 assert!(result.contains("\"C:\\Users\\John Doe\\path\""));
744 assert!(result.contains("-target"));
746 assert!(!result.contains("\"-target\""));
747 }
748
749 fn zig_target(target: &str, zig_version: &str) -> Result<String> {
751 let (rust_target, abi_suffix) = target.split_once('.').unwrap_or((target, ""));
752 let abi_suffix = if abi_suffix.is_empty() {
753 String::new()
754 } else {
755 format!(".{abi_suffix}")
756 };
757 let triple: Triple = rust_target.parse()?;
758 let target_env = zig_target_env(&triple);
759 zig_target_triple(
760 rust_target,
761 &triple,
762 target_env,
763 &abi_suffix,
764 &semver::Version::parse(zig_version)?,
765 )
766 }
767
768 #[test]
769 fn test_zig_target_triple() {
770 let cases = [
771 ("x86_64-unknown-linux-gnu", "0.15.2", "x86_64-linux-gnu"),
773 (
774 "x86_64-unknown-linux-gnu.2.36",
775 "0.15.2",
776 "x86_64-linux-gnu.2.36",
777 ),
778 ("aarch64-unknown-linux-musl", "0.15.2", "aarch64-linux-musl"),
779 (
780 "armv7-unknown-linux-gnueabihf",
781 "0.15.2",
782 "arm-linux-gnueabihf",
783 ),
784 (
785 "arm-unknown-linux-gnueabihf",
786 "0.15.2",
787 "arm-linux-gnueabihf",
788 ),
789 ("riscv64gc-unknown-linux-gnu", "0.15.2", "riscv64-linux-gnu"),
790 ("s390x-unknown-linux-gnu", "0.15.2", "s390x-linux-gnu"),
791 ("i686-unknown-linux-gnu", "0.15.2", "x86-linux-gnu"),
793 ("i686-unknown-linux-gnu", "0.10.1", "i386-linux-gnu"),
794 ("armv7-unknown-linux-ohos", "0.15.2", "arm-linux-ohoseabi"),
796 ("armv7-unknown-linux-ohos", "0.14.1", "arm-linux-ohos"),
797 (
798 "powerpc-unknown-linux-gnu",
799 "0.15.2",
800 "powerpc-linux-gnueabihf",
801 ),
802 ("aarch64-apple-darwin", "0.15.2", "aarch64-macos-none"),
804 ("aarch64-apple-darwin", "0.9.1", "aarch64-macos-gnu"),
805 (
806 "aarch64-apple-ios-macabi",
807 "0.15.2",
808 "aarch64-maccatalyst-none",
809 ),
810 ("x86_64-pc-windows-gnu", "0.15.2", "x86_64-windows-gnu"),
811 ("i686-pc-windows-gnu", "0.15.2", "x86-windows-gnu"),
812 ("i686-pc-windows-gnu", "0.10.1", "i386-windows-gnu"),
813 ("wasm32-wasip1", "0.15.2", "wasm32-wasi.0.1.0"),
814 ("wasm32-wasi", "0.15.2", "wasm32-wasi"),
815 ("wasm32-unknown-unknown", "0.15.2", "wasm32-freestanding"),
816 ("wasm32-unknown-emscripten", "0.15.2", "wasm32-emscripten"),
817 ("x86_64-unknown-freebsd", "0.15.2", "x86_64-freebsd"),
818 ("x86_64-unknown-openbsd", "0.15.2", "x86_64-openbsd"),
819 ];
820
821 for (target, zig_version, expected) in cases {
822 assert_eq!(
823 zig_target(target, zig_version).unwrap(),
824 expected,
825 "{target} with zig {zig_version}"
826 );
827 }
828 }
829
830 #[test]
831 fn test_zig_target_triple_unsupported() {
832 let err = zig_target("x86_64-unknown-redox", "0.15.2").unwrap_err();
833 assert!(
834 err.to_string().contains("unsupported target"),
835 "unexpected error: {err}"
836 );
837 }
838}