extern crate bindgen;
use goblin::error::Error;
use goblin::mach::cputype::{CPU_TYPE_ARM64, CPU_TYPE_X86_64};
use goblin::mach::load_command::CommandVariant;
use goblin::mach::{Mach, MachO, SingleArch};
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::env::{self, VarError};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fmt, fs};
fn building_docs() -> bool {
env::var("DOCS_RS").is_ok_and(|v| v == "1")
}
fn building_for_darwin() -> bool {
env::var("CARGO_CFG_TARGET_VENDOR").is_ok_and(|v| v == "apple")
}
fn target_arch() -> Result<String, VarError> {
env::var("CARGO_CFG_TARGET_ARCH")
}
fn cross_compiling() -> bool {
cfg!(target_vendor = "apple") ^ building_for_darwin()
}
fn homebrew_prefix(path: &str, package: &str) -> PathBuf {
let arch = target_arch();
let hb_path = match arch.as_deref() {
Ok("x86_64") => PathBuf::from("/usr/local"),
Ok("aarch64") => PathBuf::from("/opt/homebrew"),
_ => panic!("unknown arch {}", arch.unwrap()),
};
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 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 !building_for_darwin() {
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";
let var = env::var_os(key).unwrap_or_else(|| package_prefix("bin/llvm-config", "llvm").into_os_string());
unsafe { env::set_var(key, var); }
}
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 = building_docs();
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));
println!("cargo::rerun-if-env-changed={}", key);
println!("cargo::rustc-env={}={}", key, version);
println!("cargo::metadata={}={}", key, version);
println!(
"cargo::metadata=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!");
}