mod cargo_env;
mod cli_config;
mod linker_args;
mod locate;
mod target_info;
mod wrapper;
use std::env;
use std::process;
use anyhow::{Context, Result, bail};
use fs_err as fs;
use target_lexicon::Architecture;
use cargo_env::{write_file, write_tbd_files};
use linker_args::{FilteredArg, dedup_apple_link_libs, filter_linker_arg, filter_linker_args};
use locate::cache_dir;
use target_info::TargetInfo;
pub use cli_config::CliConfig;
#[cfg(target_os = "windows")]
pub use wrapper::adjust_canonicalization;
pub use wrapper::{ZigWrapper, prepare_zig_linker, prepare_zig_linker_with_cli_config};
#[derive(Clone, Debug, clap::Subcommand)]
pub enum Zig {
#[command(name = "cc")]
Cc {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
#[command(name = "c++")]
Cxx {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
#[command(name = "ar")]
Ar {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
#[command(name = "ranlib")]
Ranlib {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
#[command(name = "lib")]
Lib {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
#[command(name = "dlltool")]
Dlltool {
#[arg(num_args = 1.., trailing_var_arg = true)]
args: Vec<String>,
},
}
impl Zig {
pub fn execute(&self) -> Result<()> {
match self {
Zig::Cc { args } => self.execute_compiler("cc", args),
Zig::Cxx { args } => self.execute_compiler("c++", args),
Zig::Ar { args } => self.execute_tool("ar", args),
Zig::Ranlib { args } => self.execute_compiler("ranlib", args),
Zig::Lib { args } => self.execute_compiler("lib", args),
Zig::Dlltool { args } => self.execute_dlltool(args),
}
}
pub fn execute_dlltool(&self, cmd_args: &[String]) -> Result<()> {
let zig_version = Zig::zig_version()?;
let needs_filtering = zig_version.major == 0 && zig_version.minor < 12;
if !needs_filtering {
return self.execute_tool("dlltool", cmd_args);
}
let mut filtered_args = Vec::with_capacity(cmd_args.len());
let mut skip_next = false;
for arg in cmd_args {
if skip_next {
skip_next = false;
continue;
}
if arg == "--no-leading-underscore" {
continue;
}
if arg == "--temp-prefix" || arg == "-t" {
skip_next = true;
continue;
}
if arg.starts_with("--temp-prefix=") || arg.starts_with("-t=") {
continue;
}
filtered_args.push(arg.clone());
}
self.execute_tool("dlltool", &filtered_args)
}
pub fn execute_compiler(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
let target = cmd_args
.iter()
.position(|x| x == "-target")
.and_then(|index| cmd_args.get(index + 1));
let target_info = TargetInfo::new(target);
let rustc_ver = match env::var("CARGO_ZIGBUILD_RUSTC_VERSION") {
Ok(version) => version.parse()?,
Err(_) => rustc_version::version()?,
};
let zig_version = Zig::zig_version()?;
let mut new_cmd_args = Vec::with_capacity(cmd_args.len());
let mut skip_next_arg = false;
let mut seen_target = false;
for arg in cmd_args {
if skip_next_arg {
skip_next_arg = false;
continue;
}
if arg == "-target" {
if seen_target {
skip_next_arg = true;
continue;
}
seen_target = true;
}
let args = if arg.starts_with('@') && arg.ends_with("linker-arguments") {
vec![self.process_linker_response_file(
arg,
&rustc_ver,
&zig_version,
&target_info,
)?]
} else {
match self.filter_linker_arg(arg, &rustc_ver, &zig_version, &target_info) {
FilteredArg::Keep(filtered) => filtered,
FilteredArg::Skip => continue,
FilteredArg::SkipWithNext => {
skip_next_arg = true;
continue;
}
}
};
new_cmd_args.extend(args);
}
if target_info.is_apple_platform() {
new_cmd_args = dedup_apple_link_libs(new_cmd_args);
}
if target_info.is_mips32() {
new_cmd_args.push("-Wl,-z,notext".to_string());
}
if target_info.is_arm() && !cmd_args.iter().any(|x| x == "-c" || x == "-E" || x == "-S") {
let cache_dir = cache_dir();
fs::create_dir_all(&cache_dir)?;
let shim_path = cache_dir.join("aeabi_unaligned.c");
write_file(&shim_path, AEABI_UNALIGNED_C)?;
new_cmd_args.push(shim_path.display().to_string());
}
if target_info.is_windows_gnu() && (zig_version.major, zig_version.minor) >= (0, 16) {
new_cmd_args.push("-lcompiler_rt".to_string());
}
if self.has_undefined_dynamic_lookup(cmd_args) {
new_cmd_args.push("-Wl,-undefined=dynamic_lookup".to_string());
}
if target_info.is_macos() {
if self.should_add_libcharset(cmd_args, &zig_version) {
new_cmd_args.push("-lcharset".to_string());
}
self.add_macos_specific_args(&mut new_cmd_args, &zig_version)?;
}
let mut command = Self::command()?;
if (zig_version.major, zig_version.minor) >= (0, 15)
&& let Some(sdkroot) = Self::macos_sdk_root()
{
command.env("SDKROOT", sdkroot);
}
let mut child = command
.arg(cmd)
.args(new_cmd_args)
.spawn()
.with_context(|| format!("Failed to run `zig {cmd}`"))?;
let status = child.wait().expect("Failed to wait on zig child process");
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
Ok(())
}
fn process_linker_response_file(
&self,
arg: &str,
rustc_ver: &rustc_version::Version,
zig_version: &semver::Version,
target_info: &TargetInfo,
) -> Result<String> {
let content_bytes = fs::read(arg.trim_start_matches('@'))?;
let content = if target_info.is_windows_msvc() {
if content_bytes[0..2] != [255, 254] {
bail!(
"linker response file `{}` didn't start with a utf16 BOM",
&arg
);
}
let content_utf16: Vec<u16> = content_bytes[2..]
.chunks_exact(2)
.map(|a| u16::from_ne_bytes([a[0], a[1]]))
.collect();
String::from_utf16(&content_utf16).with_context(|| {
format!(
"linker response file `{}` didn't contain valid utf16 content",
&arg
)
})?
} else {
String::from_utf8(content_bytes).with_context(|| {
format!(
"linker response file `{}` didn't contain valid utf8 content",
&arg
)
})?
};
let mut link_args: Vec<_> = filter_linker_args(
content.split('\n').map(|s| s.to_string()),
rustc_ver,
zig_version,
target_info,
);
if self.has_undefined_dynamic_lookup(&link_args) {
link_args.push("-Wl,-undefined=dynamic_lookup".to_string());
}
if target_info.is_macos() && self.should_add_libcharset(&link_args, zig_version) {
link_args.push("-lcharset".to_string());
}
if target_info.is_windows_msvc() {
let new_content = link_args.join("\n");
let mut out = Vec::with_capacity((1 + new_content.len()) * 2);
for c in std::iter::once(0xFEFF).chain(new_content.encode_utf16()) {
out.push(c as u8);
out.push((c >> 8) as u8);
}
fs::write(arg.trim_start_matches('@'), out)?;
} else {
fs::write(arg.trim_start_matches('@'), link_args.join("\n").as_bytes())?;
}
Ok(arg.to_string())
}
fn filter_linker_arg(
&self,
arg: &str,
rustc_ver: &rustc_version::Version,
zig_version: &semver::Version,
target_info: &TargetInfo,
) -> FilteredArg {
filter_linker_arg(arg, rustc_ver, zig_version, target_info)
}
fn has_undefined_dynamic_lookup(&self, args: &[String]) -> bool {
let undefined = args
.iter()
.position(|x| x == "-undefined")
.and_then(|i| args.get(i + 1));
matches!(undefined, Some(x) if x == "dynamic_lookup")
}
fn should_add_libcharset(&self, args: &[String], zig_version: &semver::Version) -> bool {
if (zig_version.major, zig_version.minor) >= (0, 12) {
args.iter().any(|x| x == "-liconv") && !args.iter().any(|x| x == "-lcharset")
} else {
false
}
}
fn add_macos_specific_args(
&self,
new_cmd_args: &mut Vec<String>,
zig_version: &semver::Version,
) -> Result<()> {
let sdkroot = Self::macos_sdk_root();
if (zig_version.major, zig_version.minor) >= (0, 12) {
if let Some(ref sdkroot) = sdkroot
&& (zig_version.major, zig_version.minor) < (0, 15)
{
new_cmd_args.push(format!("--sysroot={}", sdkroot.display()));
}
}
if let Some(ref sdkroot) = sdkroot {
if (zig_version.major, zig_version.minor) < (0, 15) {
new_cmd_args.extend_from_slice(&[
"-isystem".to_string(),
format!("{}", sdkroot.join("usr").join("include").display()),
format!("-L{}", sdkroot.join("usr").join("lib").display()),
format!(
"-F{}",
sdkroot
.join("System")
.join("Library")
.join("Frameworks")
.display()
),
"-DTARGET_OS_IPHONE=0".to_string(),
]);
} else {
new_cmd_args.extend_from_slice(&[
"-isystem".to_string(),
format!("{}", sdkroot.join("usr").join("include").display()),
format!("-L{}", sdkroot.join("usr").join("lib").display()),
format!(
"-F{}",
sdkroot
.join("System")
.join("Library")
.join("Frameworks")
.display()
),
"-iframework".to_string(),
format!(
"{}",
sdkroot
.join("System")
.join("Library")
.join("Frameworks")
.display()
),
"-DTARGET_OS_IPHONE=0".to_string(),
]);
}
}
let cache_dir = cache_dir();
let deps_dir = cache_dir.join("deps");
fs::create_dir_all(&deps_dir)?;
write_tbd_files(&deps_dir)?;
new_cmd_args.push("-L".to_string());
new_cmd_args.push(format!("{}", deps_dir.display()));
Ok(())
}
pub fn execute_tool(&self, cmd: &str, cmd_args: &[String]) -> Result<()> {
let mut child = Self::command()?
.arg(cmd)
.args(cmd_args)
.spawn()
.with_context(|| format!("Failed to run `zig {cmd}`"))?;
let status = child.wait().expect("Failed to wait on zig child process");
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
Ok(())
}
}
const AEABI_UNALIGNED_C: &str = r#"
#ifdef __cplusplus
extern "C" {
#endif
__attribute__((weak)) int __aeabi_uread4(void *address) {
int value;
__builtin_memcpy(&value, address, 4);
return value;
}
__attribute__((weak)) int __aeabi_uwrite4(int value, void *address) {
__builtin_memcpy(address, &value, 4);
return value;
}
__attribute__((weak)) long long __aeabi_uread8(void *address) {
long long value;
__builtin_memcpy(&value, address, 8);
return value;
}
__attribute__((weak)) long long __aeabi_uwrite8(long long value, void *address) {
__builtin_memcpy(address, &value, 8);
return value;
}
#ifdef __cplusplus
}
#endif
"#;
pub(crate) fn get_dlltool_name(arch: &Architecture) -> &'static str {
if cfg!(windows) {
"dlltool"
} else {
match arch {
Architecture::X86_64 => "x86_64-w64-mingw32-dlltool",
Architecture::X86_32(_) => "i686-w64-mingw32-dlltool",
Architecture::Aarch64(_) => "aarch64-w64-mingw32-dlltool",
_ => "dlltool",
}
}
}
pub(crate) fn has_system_dlltool(arch: &Architecture) -> bool {
which::which(get_dlltool_name(arch)).is_ok()
}