1mod cargo_env;
2mod cli_config;
3mod linker_args;
4mod locate;
5mod target_info;
6mod wrapper;
7
8use std::env;
9use std::process;
10
11use anyhow::{Context, Result, bail};
12use fs_err as fs;
13use target_lexicon::Architecture;
14
15use cargo_env::{write_file, write_tbd_files};
16use linker_args::{
17 LinkerArgFilter, dedup_apple_link_libs, quote_linker_response_arg,
18 unescape_linker_response_line,
19};
20use locate::cache_dir;
21use target_info::TargetInfo;
22
23pub use cli_config::CliConfig;
24#[cfg(target_os = "windows")]
25pub use wrapper::adjust_canonicalization;
26pub use wrapper::{ZigWrapper, prepare_zig_linker, prepare_zig_linker_with_cli_config};
27
28#[derive(Clone, Debug, clap::Subcommand)]
30pub enum Zig {
31 #[command(name = "cc")]
33 Cc {
34 #[arg(num_args = 1.., trailing_var_arg = true)]
36 args: Vec<String>,
37 },
38 #[command(name = "c++")]
40 Cxx {
41 #[arg(num_args = 1.., trailing_var_arg = true)]
43 args: Vec<String>,
44 },
45 #[command(name = "ar")]
47 Ar {
48 #[arg(num_args = 1.., trailing_var_arg = true)]
50 args: Vec<String>,
51 },
52 #[command(name = "ranlib")]
54 Ranlib {
55 #[arg(num_args = 1.., trailing_var_arg = true)]
57 args: Vec<String>,
58 },
59 #[command(name = "lib")]
61 Lib {
62 #[arg(num_args = 1.., trailing_var_arg = true)]
64 args: Vec<String>,
65 },
66 #[command(name = "dlltool")]
68 Dlltool {
69 #[arg(num_args = 1.., trailing_var_arg = true)]
71 args: Vec<String>,
72 },
73}
74
75impl Zig {
76 pub fn execute(&self) -> Result<()> {
78 match self {
79 Zig::Cc { args } => self.execute_compiler("cc", args),
80 Zig::Cxx { args } => self.execute_compiler("c++", args),
81 Zig::Ar { args } => self.execute_tool("ar", args),
82 Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
83 Zig::Lib { args } => self.execute_compiler("lib", args),
84 Zig::Dlltool { args } => self.execute_dlltool(args),
85 }
86 }
87
88 pub fn execute_dlltool(&self, cmd_args: &[String]) -> Result<()> {
91 let zig_version = Zig::zig_version()?;
92 let needs_filtering = zig_version.major == 0 && zig_version.minor < 12;
93
94 if !needs_filtering {
95 return self.execute_tool("dlltool", cmd_args);
96 }
97
98 let mut filtered_args = Vec::with_capacity(cmd_args.len());
101 let mut skip_next = false;
102 for arg in cmd_args {
103 if skip_next {
104 skip_next = false;
105 continue;
106 }
107 if arg == "--no-leading-underscore" {
108 continue;
109 }
110 if arg == "--temp-prefix" || arg == "-t" {
111 skip_next = true;
113 continue;
114 }
115 if arg.starts_with("--temp-prefix=") || arg.starts_with("-t=") {
117 continue;
118 }
119 filtered_args.push(arg.clone());
120 }
121
122 self.execute_tool("dlltool", &filtered_args)
123 }
124
125 pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
127 let target = cmd_args
128 .iter()
129 .position(|x| x == "-target")
130 .and_then(|index| cmd_args.get(index + 1));
131 let target_info = TargetInfo::new(target);
132
133 let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
134 Ok(version) => version.parse()?,
135 Err(_) => rustc_version::version()?,
136 };
137 let zig_version = Zig::zig_version()?;
138
139 let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
140 let mut skip_next_arg = false;
141 let mut linker_filter = LinkerArgFilter::default();
142 let mut seen_target = false;
143 for arg in cmd_args {
144 if skip_next_arg {
145 skip_next_arg = false;
146 continue;
147 }
148 if arg == "-target" {
152 if seen_target {
153 skip_next_arg = true;
154 continue;
155 }
156 seen_target = true;
157 }
158 let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
159 vec![self.process_linker_response_file(
160 arg,
161 &rustc_ver,
162 &zig_version,
163 &target_info,
164 &mut linker_filter,
165 )?]
166 } else {
167 linker_filter.filter_arg(arg, &rustc_ver, &zig_version, &target_info)
168 };
169 new_cmd_args.extend(args);
170 }
171
172 if target_info.is_apple_platform() {
173 new_cmd_args = dedup_apple_link_libs(new_cmd_args);
174 }
175
176 if target_info.is_mips32() {
177 new_cmd_args.push("-Wl,-z,notext".to_string());
179 }
180
181 if target_info.is_arm()
186 && !cmd_args
187 .iter()
188 .any(|x| matches!(x.as_str(), "-c" | "-E" | "-S" | "-M" | "-MM"))
189 {
190 let cache_dir = cache_dir();
191 fs::create_dir_all(&cache_dir)?;
192 let shim_path = cache_dir.join("aeabi_unaligned.c");
193 write_file(&shim_path, AEABI_UNALIGNED_C)?;
194 new_cmd_args.push("-Wno-unused-command-line-argument".to_string());
198 new_cmd_args.push(shim_path.display().to_string());
199 }
200
201 if target_info.is_windows_gnu() && (zig_version.major, zig_version.minor) >= (0, 16) {
202 new_cmd_args.push("-lcompiler_rt".to_string());
203 }
204
205 if self.has_undefined_dynamic_lookup(cmd_args) {
206 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
207 }
208 if target_info.is_macos() {
209 if self.should_add_libcharset(cmd_args, &zig_version) {
210 new_cmd_args.push("-lcharset".to_string());
211 }
212 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
213 }
214
215 let mut command = Self::command()?;
218 if (zig_version.major, zig_version.minor) >= (0, 15)
219 && let Some(sdkroot) = Self::macos_sdk_root()
220 {
221 command.env("SDKROOT", sdkroot);
222 }
223
224 let mut child = command
225 .arg(cmd)
226 .args(new_cmd_args)
227 .spawn()
228 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
229 let status = child.wait().expect("Failed to wait on zig child process");
230 if !status.success() {
231 process::exit(status.code().unwrap_or(1));
232 }
233 Ok(())
234 }
235
236 fn process_linker_response_file(
237 &self,
238 arg: &str,
239 rustc_ver: &rustc_version::Version,
240 zig_version: &semver::Version,
241 target_info: &TargetInfo,
242 linker_filter: &mut LinkerArgFilter,
243 ) -> Result<String> {
244 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
248 let content = if target_info.is_windows_msvc() {
249 if content_bytes[0..2] != [255, 254] {
250 bail!(
251 "linker response file `{}` didn't start with a utf16 BOM",
252 &arg
253 );
254 }
255 let content_utf16: Vec<u16> = content_bytes[2..]
256 .chunks_exact(2)
257 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
258 .collect();
259 String::from_utf16(&content_utf16).with_context(|| {
260 format!(
261 "linker response file `{}` didn't contain valid utf16 content",
262 &arg
263 )
264 })?
265 } else {
266 String::from_utf8(content_bytes).with_context(|| {
267 format!(
268 "linker response file `{}` didn't contain valid utf8 content",
269 &arg
270 )
271 })?
272 };
273 let unescape = !target_info.is_windows_msvc();
278 let mut link_args = linker_filter.filter_args(
279 content.split_terminator('\n').map(|line| {
281 if unescape {
282 unescape_linker_response_line(line)
283 } else {
284 line.to_string()
285 }
286 }),
287 rustc_ver,
288 zig_version,
289 target_info,
290 );
291 if self.has_undefined_dynamic_lookup(&link_args) {
292 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
293 }
294 if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
295 link_args.push("-lcharset".to_string());
296 }
297 if target_info.is_windows_msvc() {
298 let new_content = link_args.join("\n");
299 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
300 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
302 out.push(c as u8);
304 out.push((c >> 8) as u8);
305 }
306 fs::write(arg.trim_start_matches('@'), out)?;
307 } else {
308 let new_content = link_args
312 .iter()
313 .map(|arg| quote_linker_response_arg(arg))
314 .collect::<Vec<_>>()
315 .join("\n");
316 fs::write(arg.trim_start_matches('@'), new_content.as_bytes())?;
317 }
318 Ok(arg.to_string())
319 }
320
321 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
322 let undefined = args
323 .iter()
324 .position(|x| x == "-undefined")
325 .and_then(|i| args.get(i + 1));
326 matches!(undefined, Some(x) if x == "dynamic_lookup")
327 }
328
329 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
330 if (zig_version.major, zig_version.minor) >= (0, 12) {
332 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
333 } else {
334 false
335 }
336 }
337
338 fn add_macos_specific_args(
339 &self,
340 new_cmd_args: &mut Vec<String>,
341 zig_version: &semver::Version,
342 ) -> Result<()> {
343 let sdkroot = Self::macos_sdk_root();
344 if (zig_version.major, zig_version.minor) >= (0, 12) {
345 if let Some(ref sdkroot) = sdkroot
349 && (zig_version.major, zig_version.minor) < (0, 15)
350 {
351 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
352 }
353 }
355 if let Some(ref sdkroot) = sdkroot {
356 if (zig_version.major, zig_version.minor) < (0, 15) {
357 new_cmd_args.extend_from_slice(&[
359 "-isystem".to_string(),
360 format!("{}", sdkroot.join("usr").join("include").display()),
361 format!("-L{}", sdkroot.join("usr").join("lib").display()),
362 format!(
363 "-F{}",
364 sdkroot
365 .join("System")
366 .join("Library")
367 .join("Frameworks")
368 .display()
369 ),
370 "-DTARGET_OS_IPHONE=0".to_string(),
371 ]);
372 } else {
373 new_cmd_args.extend_from_slice(&[
376 "-isystem".to_string(),
377 format!("{}", sdkroot.join("usr").join("include").display()),
378 format!("-L{}", sdkroot.join("usr").join("lib").display()),
379 format!(
380 "-F{}",
381 sdkroot
382 .join("System")
383 .join("Library")
384 .join("Frameworks")
385 .display()
386 ),
387 "-iframework".to_string(),
389 format!(
390 "{}",
391 sdkroot
392 .join("System")
393 .join("Library")
394 .join("Frameworks")
395 .display()
396 ),
397 "-DTARGET_OS_IPHONE=0".to_string(),
398 ]);
399 }
400 }
401
402 let cache_dir = cache_dir();
404 let deps_dir = cache_dir.join("deps");
405 fs::create_dir_all(&deps_dir)?;
406 write_tbd_files(&deps_dir)?;
407 new_cmd_args.push("-L".to_string());
408 new_cmd_args.push(format!("{}", deps_dir.display()));
409 Ok(())
410 }
411
412 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
414 let mut child = Self::command()?
415 .arg(cmd)
416 .args(cmd_args)
417 .spawn()
418 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
419 let status = child.wait().expect("Failed to wait on zig child process");
420 if !status.success() {
421 process::exit(status.code().unwrap_or(1));
422 }
423 Ok(())
424 }
425}
426
427const AEABI_UNALIGNED_C: &str = r#"
432#ifdef __cplusplus
433extern "C" {
434#endif
435__attribute__((weak)) int __aeabi_uread4(void *address) {
436 int value;
437 __builtin_memcpy(&value, address, 4);
438 return value;
439}
440__attribute__((weak)) int __aeabi_uwrite4(int value, void *address) {
441 __builtin_memcpy(address, &value, 4);
442 return value;
443}
444__attribute__((weak)) long long __aeabi_uread8(void *address) {
445 long long value;
446 __builtin_memcpy(&value, address, 8);
447 return value;
448}
449__attribute__((weak)) long long __aeabi_uwrite8(long long value, void *address) {
450 __builtin_memcpy(address, &value, 8);
451 return value;
452}
453#ifdef __cplusplus
454}
455#endif
456"#;
457
458pub(crate) fn get_dlltool_name(arch: &Architecture) -> &'static str {
462 if cfg!(windows) {
463 "dlltool"
464 } else {
465 match arch {
466 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
467 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
468 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
469 _ => "dlltool",
470 }
471 }
472}
473
474pub(crate) fn has_system_dlltool(arch: &Architecture) -> bool {
477 which::which(get_dlltool_name(arch)).is_ok()
478}