extern crate bindgen;
use goblin::mach::{SingleArch,Mach,MachO};
use goblin::mach::cputype::{CPU_TYPE_X86_64,CPU_TYPE_ARM64};
use goblin::mach::load_command::CommandVariant;
use goblin::error::Error;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::ffi::OsString;
use std::{env,fmt,fs,str};
use std::path::{Path,PathBuf};
use std::str::FromStr;
fn cross_compiling() -> bool {
#[cfg(feature = "is_some_and")]{ cfg!(target_vendor = "apple") ^ env::var("CARGO_CFG_TARGET_VENDOR").is_ok_and(|v| v == "apple")}
#[cfg(not(feature = "is_some_and"))]{ cfg!(target_vendor = "apple") ^ (env::var("CARGO_CFG_TARGET_VENDOR").unwrap() == "apple")}
}
fn homebrew_prefix(path: &str, package: &str) -> PathBuf {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let hb_path = match arch.as_str() {
"x86_64" => PathBuf::from("/usr/local"),
"aarch64" => PathBuf::from("/opt/homebrew"),
_ => panic!("unknown arch {}", arch),
};
if hb_path.join(path).exists() {
hb_path.join(path)
} else {
hb_path.join("opt").join(package).join(path)
}
}
fn macports_prefix(path: &str) -> PathBuf {
PathBuf::from("/opt/local/").join(path)
}
fn package_prefix(path: &str, package: &str) -> PathBuf {
if Path::new("/opt/local/bin/port").exists() {
macports_prefix(path)
} else if Path::new("/opt/homebrew/bin/brew").exists()
|| Path::new("/usr/local/bin/brew").exists()
{
homebrew_prefix(path, package)
} else {
PathBuf::from(env::var_os("PREFIX").unwrap_or_else(|| OsString::from("/"))).join(path)
}
}
#[derive(Eq)]
struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let mao = self.major.cmp(&other.major);
let mio = self.minor.cmp(&other.minor);
let pao = self.patch.cmp(&other.patch);
if mao == Ordering::Equal && mio == Ordering::Equal { pao }
else if mao == Ordering::Equal { mio }
else { mao }
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.major == other.major &&
self.minor == other.minor &&
self.patch == other.patch
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s
.trim()
.split('.')
.map(|p|p.parse::<u32>().unwrap())
.take(3)
.collect::<VecDeque<u32>>();
Ok(Self { major: parts.pop_front().unwrap(), minor: parts.pop_front().unwrap_or(0), patch: parts.pop_front().unwrap_or(0) })
}
}
impl From<u32> for Version {
fn from(packed: u32) -> Self {
let major = (packed & 0b1111_1111_1111_1111_0000_0000_0000_0000u32) >> 16;
let minor = (packed & 0b0000_0000_0000_0000_1111_1111_0000_0000u32) >> 8;
let patch = (packed & 0b0000_0000_0000_0000_0000_0000_1111_1111u32) >> 0;
Self {major, minor, patch}
}
}
impl From<MachO<'_>> for Version {
fn from(b: MachO) -> Self {
let packed = b.load_commands.iter().find_map(|c|match c.command {
CommandVariant::VersionMinMacosx(v) => Some(v.version),
CommandVariant::BuildVersion(v) => Some(v.minos),
_ => None
}).unwrap();
Self::from(packed)
}
}
fn find_version(lib: &Path) -> Version {
match Mach::parse(&fs::read(lib).map_err(goblin::error::Error::IO).unwrap()).unwrap() {
Mach::Binary(b)=>Version::from(b),
Mach::Fat(f)=>{
match f.find(|r| r.unwrap().cputype == match env::var("CARGO_CFG_TARGET_ARCH").as_deref() {
Ok("x86_64") => CPU_TYPE_X86_64,
Ok("aarch64") => CPU_TYPE_ARM64,
_ => panic!("unknown arch"),
}).unwrap().ok().unwrap() {
SingleArch::MachO(b)=>Version::from(b),
SingleArch::Archive(_)=>panic!("lib is an archive?"),
}
},
}
}
fn ensure_apple() {
if env::var("CARGO_CFG_TARGET_VENDOR").unwrap() != "apple" {
panic!("The KERN_PROCARGS2 sysctl only exists in xnu kernels, BSD or Linux users should just read /proc/$PID/cmdline which is much easier and faster, Solaris users should use pargs.\nIf you are writing a cross platform program, you can depend on this crate only on macOS by specifying the dependency as:\n[target.'cfg(target_vendor = \"apple\")'.dependencies]\n{} = \"{}\"",env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION"))
}
}
fn debug_env() {
env::vars().for_each(|(key, value)| println!("cargo:warning={}={}", key, value));
}
fn locate_llvm_config() {
let key = "LLVM_CONFIG_PATH";
env::set_var(
key,
env::var_os(key)
.unwrap_or_else(|| package_prefix("bin/llvm-config", "llvm").into_os_string()),
);
}
fn main() {
ensure_apple();
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
if env::var_os("DEBUG_CARGO_ENV").is_some() {
debug_env();
}
let header = "wrapper.h";
let building_docs = env::var("DOCS_RS").unwrap_or_else(|_| "0".to_string()) == "1";
if !building_docs {
let lib_env = "LIBGETARGV_LIB_DIR";
let lib_path = env::var(lib_env)
.map(PathBuf::from)
.unwrap_or_else(|_| package_prefix("lib", "getargv"));
let lib_name = "libgetargv.dylib";
let lib = lib_path.join(lib_name);
if !lib.exists() && env::var_os(lib_env).is_some() {
panic!(
"Couldn't locate {1} in {0}, check if version is in file name.",
env::var(lib_env).unwrap(),
lib_name
);
} else if !lib.exists() {
panic!("Couldn't locate {1}, try setting the {0} env var to the path to the directory in which {1} is located.", lib_env, lib_name);
}
println!(
"cargo:rustc-link-search={}",
lib_path
.canonicalize()
.expect("cannot canonicalize path")
.display()
);
println!("cargo:rustc-link-lib=getargv");
println!("cargo:rerun-if-env-changed={}", lib_env);
println!("cargo:rerun-if-changed={}", header);
let key = "MACOSX_DEPLOYMENT_TARGET";
let version = env::var(key).map(|s|s.parse::<Version>().unwrap()).unwrap_or_else(|_| find_version(&lib));
let version_s = format!("{}", version);
env::set_var(key, version_s);
println!("cargo:{}={}", key, version);
println!(
"cargo:PID_MAX={}",
if version >= (Version{major:10,minor:6,patch:0}) { 99_999 } else { 30_000 }
);
}
locate_llvm_config();
let builder = if building_docs {
bindgen::Builder::default().clang_args(["-I", "docs_shim"])
} else {
bindgen::Builder::default()
}
.header(header)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_function(".*_of_pid|free_Argv.*")
.allowlist_type(".*Argv.*")
.no_copy(".*Result");
match builder.generate() {
Ok(bindings) => bindings,
Err(e) => match e {
bindgen::BindgenError::ClangDiagnostic(s) if cross_compiling() && s.contains("file not found") && s.split('\'').nth(1).is_some() => {
panic!("Clang could not find '{}', perhaps you need to set the 'BINDGEN_EXTRA_CLANG_ARGS' env var to something like: '--sysroot=/path/to/macos/sysroot'",
s.split('\'').nth(1).unwrap()
)
}
_ => {
panic!("Unable to generate bindings: {}", e)
}
},
}
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}