use std::{
collections::BTreeSet,
path::{Path, PathBuf},
process::Command,
};
const TCL_LIBS: [&str; 2] = ["tcl9.0", "tcl9tk9.0"];
fn main() {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
println!("cargo::rerun-if-env-changed=PYO3_CONFIG_FILE");
let config = std::env::var_os("PYO3_CONFIG_FILE")
.and_then(|v| PathBuf::from(v).canonicalize().ok())
.expect(
"PYO3_CONFIG_FILE must point at the pyo3-config.txt generated by omp-py's \
scripts/fetch-python.sh; set it before cargo runs — e.g. in the consumer's \
.cargo/config.toml: [env] PYO3_CONFIG_FILE = \"<vendor>/python/pyo3-config.txt\"",
);
let vendor = config.parent().expect("PYO3_CONFIG_FILE has no parent directory").to_path_buf();
assert!(
vendor.join("PYTHON.json").is_file(),
"{} does not sit in a python-build-standalone vendor tree — run omp-py's \
scripts/fetch-python.sh first",
config.display()
);
println!("cargo::rerun-if-changed={}", vendor.join("PYTHON.json").display());
println!("cargo::rerun-if-changed={}", vendor.join("stdlib.bin").display());
let json: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(vendor.join("PYTHON.json")).unwrap()).unwrap();
let mut static_libs = BTreeSet::from(["clang_rt.osx".to_owned()]);
let mut frameworks = BTreeSet::new();
let mut system_libs = BTreeSet::new();
let extensions = json["build_info"]["extensions"].as_object().unwrap();
for variant in extensions.values().flat_map(|v| v.as_array().unwrap()) {
for link in variant["links"].as_array().unwrap() {
let name = link["name"].as_str().unwrap();
if link["path_static"].is_string() {
static_libs.insert(name.to_owned());
} else if link["framework"].as_bool() == Some(true) {
frameworks.insert(name);
} else if !TCL_LIBS.contains(&name) {
system_libs.insert(name);
}
}
}
if std::env::var("TARGET").as_deref() == Ok("aarch64-apple-darwin") {
println!(
"cargo::rustc-link-arg=--ld-path={}",
manifest.join("scripts/ld64.lld").display()
);
}
println!("cargo::rustc-link-arg=-Wl,-export_dynamic");
println!("cargo::rustc-link-search=native={}", vendor.join("build/lib").display());
for lib in &static_libs {
println!("cargo::rustc-link-lib=static={lib}");
}
for framework in &frameworks {
println!("cargo::rustc-link-lib=framework={framework}");
}
for lib in &system_libs {
println!("cargo::rustc-link-lib={lib}");
}
let py_src = manifest.join("python");
let requirements = manifest.join("requirements.txt");
let packer = manifest.join("scripts/pack-pymodules.py");
println!("cargo::rerun-if-changed={}", py_src.display());
println!("cargo::rerun-if-changed={}", requirements.display());
println!("cargo::rerun-if-changed={}", packer.display());
let interpreter = ["python3.14td", "python3.14t"]
.iter()
.map(|name| vendor.join("install/bin").join(name))
.find(|p| p.is_file())
.expect("vendored interpreter missing — run omp-py's scripts/fetch-python.sh first");
let bundled = bundled_packages(&requirements, &vendor);
let modules_blob = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("omp_modules.bin");
let mut pack = Command::new(interpreter);
pack.arg(&packer).arg(&py_src);
if let Some(dir) = &bundled {
pack.arg(dir);
}
let status = pack
.arg(&modules_blob)
.args(["--prefix", "<omp-py>"])
.args(["--exclude", "*.dist-info", "__pycache__", "bin"])
.status()
.expect("failed to run pack-pymodules.py");
assert!(status.success(), "pack-pymodules.py failed");
println!("cargo::rustc-env=OMP_STDLIB_BLOB={}", vendor.join("stdlib.bin").display());
println!("cargo::rustc-env=OMP_PY_MODULES_BLOB={}", modules_blob.display());
}
fn bundled_packages(requirements: &Path, vendor: &Path) -> Option<PathBuf> {
let spec = std::fs::read_to_string(requirements).unwrap_or_default();
let listed = spec
.lines()
.map(str::trim)
.any(|l| !l.is_empty() && !l.starts_with('#'));
if !listed {
return None;
}
let dir = vendor.join("bundled");
let stamp = dir.join(".requirements.stamp");
println!("cargo::rerun-if-changed={}", stamp.display());
assert!(
std::fs::read_to_string(&stamp).ok().as_deref() == Some(&spec),
"bundled packages under {} missing or stale vs {} — run omp-py's scripts/fetch-python.sh",
dir.display(),
requirements.display()
);
Some(dir)
}