use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
pub fn patch_lib(
static_lib: &Path,
out_dir: &Path,
lib_name: &str,
lib_name_patched: &str,
_global_symbols_wildcard: &str,
final_lib: &Path,
) {
let target_arch = get_macos_arch();
let intermediate_obj = out_dir.join(format!("lib{}_intermediate.o", lib_name));
let ld_status = Command::new("ld")
.arg("-arch")
.arg(&target_arch)
.arg("-r")
.arg("-o")
.arg(&intermediate_obj)
.arg("-all_load")
.arg(static_lib)
.status()
.expect("Failed to execute ld command on macOS");
if !ld_status.success() {
panic!("ld -r command failed for {} on macOS", static_lib.display());
}
if !intermediate_obj.exists() {
panic!(
"Intermediate object file was not created: {}",
intermediate_obj.display()
);
}
let symbols_file = out_dir.join("symbols_to_keep.txt");
match create_macos_symbols_file(&intermediate_obj, &symbols_file) {
Ok(symbol_count) => {
if symbol_count == 0 {
fs::copy(static_lib, final_lib).expect("Failed to copy library file");
let _ = fs::remove_file(&intermediate_obj);
return;
}
}
Err(_) => {
fs::copy(static_lib, final_lib).expect("Failed to copy library file");
let _ = fs::remove_file(&intermediate_obj);
return;
}
}
let final_obj = out_dir.join(format!("lib{}_filtered.o", lib_name_patched));
let ld_filter_status = Command::new("ld")
.arg("-arch")
.arg(&target_arch)
.arg("-r")
.arg("-o")
.arg(&final_obj)
.arg("-exported_symbols_list")
.arg(&symbols_file)
.arg(&intermediate_obj)
.status()
.expect("Failed to execute ld filtering command on macOS");
if !ld_filter_status.success() {
panic!(
"ld filtering command failed for {} on macOS",
intermediate_obj.display()
);
}
let ar_status = Command::new("ar")
.arg("rcs")
.arg(final_lib)
.arg(&final_obj)
.status()
.expect("Failed to execute ar command on macOS");
if !ar_status.success() {
panic!("ar command failed for {} on macOS", final_obj.display());
}
let _ = fs::remove_file(&intermediate_obj);
let _ = fs::remove_file(&final_obj);
let _ = fs::remove_file(&symbols_file);
}
fn get_macos_arch() -> String {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set");
match target_arch.as_str() {
"aarch64" => "arm64".to_string(),
"x86_64" => "x86_64".to_string(),
arch => {
println!(
"cargo:warning=Unknown target architecture for macOS: {}",
arch
);
arch.to_string()
}
}
}
fn create_macos_symbols_file(
obj_file: &Path,
symbols_file: &Path,
) -> Result<usize, Box<dyn std::error::Error>> {
let nm_variations = [
vec!["-g", "-defined-only"], vec!["-g"], vec!["-a"], vec!["--defined-only"], ];
let mut nm_output = None;
let mut last_error = String::new();
for args in &nm_variations {
let mut cmd = Command::new("nm");
for arg in args {
cmd.arg(arg);
}
cmd.arg(obj_file);
match cmd.output() {
Ok(output) if output.status.success() => {
nm_output = Some(output);
break;
}
Ok(output) => {
last_error = format!(
"nm failed with args {:?}: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
Err(e) => {
last_error = format!("nm command error with args {:?}: {}", args, e);
}
}
}
let nm_output = nm_output.ok_or_else(|| {
format!(
"All nm command variations failed. Last error: {}",
last_error
)
})?;
let nm_stdout = String::from_utf8_lossy(&nm_output.stdout);
let mut symbols_to_keep = Vec::new();
for line in nm_stdout.lines() {
if let Some(symbol) = parse_nm_symbol_macos(line) {
if symbol.starts_with("aic_") || symbol.starts_with("_aic_") {
symbols_to_keep.push(symbol);
}
}
}
if symbols_to_keep.is_empty() {
return Ok(0);
}
let symbols_content = symbols_to_keep.join("\n");
fs::write(symbols_file, symbols_content)
.map_err(|e| format!("Failed to write symbols file: {}", e))?;
Ok(symbols_to_keep.len())
}
fn parse_nm_symbol_macos(line: &str) -> Option<String> {
let line = line.trim();
if line.is_empty() {
return None;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let symbol_type = parts[1];
let symbol_name = parts[2];
if symbol_type.chars().any(|c| c.is_uppercase()) {
Some(symbol_name.to_string())
} else {
None
}
} else if parts.len() == 2 {
let first = parts[0];
let second = parts[1];
if first.len() == 1 && first.chars().any(|c| c.is_uppercase()) {
Some(second.to_string())
} else {
None
}
} else if parts.len() == 1 {
let symbol = parts[0];
if !symbol.is_empty() && (symbol.starts_with("aic_") || symbol.starts_with("_aic_")) {
Some(symbol.to_string())
} else {
None
}
} else {
None
}
}