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(shim_path.display().to_string());
195 }
196
197 if target_info.is_windows_gnu() && (zig_version.major, zig_version.minor) >= (0, 16) {
198 new_cmd_args.push("-lcompiler_rt".to_string());
199 }
200
201 if self.has_undefined_dynamic_lookup(cmd_args) {
202 new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
203 }
204 if target_info.is_macos() {
205 if self.should_add_libcharset(cmd_args, &zig_version) {
206 new_cmd_args.push("-lcharset".to_string());
207 }
208 self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
209 }
210
211 let mut command = Self::command()?;
214 if (zig_version.major, zig_version.minor) >= (0, 15)
215 && let Some(sdkroot) = Self::macos_sdk_root()
216 {
217 command.env("SDKROOT", sdkroot);
218 }
219
220 let mut child = command
221 .arg(cmd)
222 .args(new_cmd_args)
223 .spawn()
224 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
225 let status = child.wait().expect("Failed to wait on zig child process");
226 if !status.success() {
227 process::exit(status.code().unwrap_or(1));
228 }
229 Ok(())
230 }
231
232 fn process_linker_response_file(
233 &self,
234 arg: &str,
235 rustc_ver: &rustc_version::Version,
236 zig_version: &semver::Version,
237 target_info: &TargetInfo,
238 linker_filter: &mut LinkerArgFilter,
239 ) -> Result<String> {
240 let content_bytes = fs::read(arg.trim_start_matches('@'))?;
244 let content = if target_info.is_windows_msvc() {
245 if content_bytes[0..2] != [255, 254] {
246 bail!(
247 "linker response file `{}` didn't start with a utf16 BOM",
248 &arg
249 );
250 }
251 let content_utf16: Vec<u16> = content_bytes[2..]
252 .chunks_exact(2)
253 .map(|a| u16::from_ne_bytes([a[0], a[1]]))
254 .collect();
255 String::from_utf16(&content_utf16).with_context(|| {
256 format!(
257 "linker response file `{}` didn't contain valid utf16 content",
258 &arg
259 )
260 })?
261 } else {
262 String::from_utf8(content_bytes).with_context(|| {
263 format!(
264 "linker response file `{}` didn't contain valid utf8 content",
265 &arg
266 )
267 })?
268 };
269 let unescape = !target_info.is_windows_msvc();
274 let mut link_args = linker_filter.filter_args(
275 content.split_terminator('\n').map(|line| {
277 if unescape {
278 unescape_linker_response_line(line)
279 } else {
280 line.to_string()
281 }
282 }),
283 rustc_ver,
284 zig_version,
285 target_info,
286 );
287 if self.has_undefined_dynamic_lookup(&link_args) {
288 link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
289 }
290 if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
291 link_args.push("-lcharset".to_string());
292 }
293 if target_info.is_windows_msvc() {
294 let new_content = link_args.join("\n");
295 let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
296 for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
298 out.push(c as u8);
300 out.push((c >> 8) as u8);
301 }
302 fs::write(arg.trim_start_matches('@'), out)?;
303 } else {
304 let new_content = link_args
308 .iter()
309 .map(|arg| quote_linker_response_arg(arg))
310 .collect::<Vec<_>>()
311 .join("\n");
312 fs::write(arg.trim_start_matches('@'), new_content.as_bytes())?;
313 }
314 Ok(arg.to_string())
315 }
316
317 fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
318 let undefined = args
319 .iter()
320 .position(|x| x == "-undefined")
321 .and_then(|i| args.get(i + 1));
322 matches!(undefined, Some(x) if x == "dynamic_lookup")
323 }
324
325 fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
326 if (zig_version.major, zig_version.minor) >= (0, 12) {
328 args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
329 } else {
330 false
331 }
332 }
333
334 fn add_macos_specific_args(
335 &self,
336 new_cmd_args: &mut Vec<String>,
337 zig_version: &semver::Version,
338 ) -> Result<()> {
339 let sdkroot = Self::macos_sdk_root();
340 if (zig_version.major, zig_version.minor) >= (0, 12) {
341 if let Some(ref sdkroot) = sdkroot
345 && (zig_version.major, zig_version.minor) < (0, 15)
346 {
347 new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
348 }
349 }
351 if let Some(ref sdkroot) = sdkroot {
352 if (zig_version.major, zig_version.minor) < (0, 15) {
353 new_cmd_args.extend_from_slice(&[
355 "-isystem".to_string(),
356 format!("{}", sdkroot.join("usr").join("include").display()),
357 format!("-L{}", sdkroot.join("usr").join("lib").display()),
358 format!(
359 "-F{}",
360 sdkroot
361 .join("System")
362 .join("Library")
363 .join("Frameworks")
364 .display()
365 ),
366 "-DTARGET_OS_IPHONE=0".to_string(),
367 ]);
368 } else {
369 new_cmd_args.extend_from_slice(&[
372 "-isystem".to_string(),
373 format!("{}", sdkroot.join("usr").join("include").display()),
374 format!("-L{}", sdkroot.join("usr").join("lib").display()),
375 format!(
376 "-F{}",
377 sdkroot
378 .join("System")
379 .join("Library")
380 .join("Frameworks")
381 .display()
382 ),
383 "-iframework".to_string(),
385 format!(
386 "{}",
387 sdkroot
388 .join("System")
389 .join("Library")
390 .join("Frameworks")
391 .display()
392 ),
393 "-DTARGET_OS_IPHONE=0".to_string(),
394 ]);
395 }
396 }
397
398 let cache_dir = cache_dir();
400 let deps_dir = cache_dir.join("deps");
401 fs::create_dir_all(&deps_dir)?;
402 write_tbd_files(&deps_dir)?;
403 new_cmd_args.push("-L".to_string());
404 new_cmd_args.push(format!("{}", deps_dir.display()));
405 Ok(())
406 }
407
408 pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
410 let mut child = Self::command()?
411 .arg(cmd)
412 .args(cmd_args)
413 .spawn()
414 .with_context(|| format!("Failed to run `zig {cmd}`"))?;
415 let status = child.wait().expect("Failed to wait on zig child process");
416 if !status.success() {
417 process::exit(status.code().unwrap_or(1));
418 }
419 Ok(())
420 }
421}
422
423const AEABI_UNALIGNED_C: &str = r#"
428#ifdef __cplusplus
429extern "C" {
430#endif
431__attribute__((weak)) int __aeabi_uread4(void *address) {
432 int value;
433 __builtin_memcpy(&value, address, 4);
434 return value;
435}
436__attribute__((weak)) int __aeabi_uwrite4(int value, void *address) {
437 __builtin_memcpy(address, &value, 4);
438 return value;
439}
440__attribute__((weak)) long long __aeabi_uread8(void *address) {
441 long long value;
442 __builtin_memcpy(&value, address, 8);
443 return value;
444}
445__attribute__((weak)) long long __aeabi_uwrite8(long long value, void *address) {
446 __builtin_memcpy(address, &value, 8);
447 return value;
448}
449#ifdef __cplusplus
450}
451#endif
452"#;
453
454pub(crate) fn get_dlltool_name(arch: &Architecture) -> &'static str {
458 if cfg!(windows) {
459 "dlltool"
460 } else {
461 match arch {
462 Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
463 Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
464 Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
465 _ => "dlltool",
466 }
467 }
468}
469
470pub(crate) fn has_system_dlltool(arch: &Architecture) -> bool {
473 which::which(get_dlltool_name(arch)).is_ok()
474}