use std::sync::OnceLock;
#[cfg(feature = "vendored")]
mod vendored_deps {
pub use flate2::read::GzDecoder;
pub use sha3::{Digest, Sha3_256};
pub use std::{
env,
path::{Path, PathBuf},
};
pub use tar::Archive;
}
#[cfg(feature = "vendored")]
use vendored_deps::*;
fn main() {
if std::env::var("DOCS_RS").is_err() {
setup_hwloc();
}
}
fn setup_hwloc() {
let required_version = if cfg!(feature = "hwloc-2_12_1") {
"2.12.1"
} else if cfg!(feature = "hwloc-2_12_0") {
"2.12.0"
} else if cfg!(feature = "hwloc-2_11_0") {
"2.11.0"
} else if cfg!(feature = "hwloc-2_10_0") {
"2.10.0"
} else if cfg!(feature = "hwloc-2_8_0") {
"2.8.0"
} else if cfg!(feature = "hwloc-2_5_0") {
"2.5.0"
} else if cfg!(feature = "hwloc-2_4_0") {
"2.4.0"
} else if cfg!(feature = "hwloc-2_3_0") {
"2.3.0"
} else if cfg!(feature = "hwloc-2_2_0") {
"2.2.0"
} else if cfg!(feature = "hwloc-2_1_0") {
"2.1.0"
} else if cfg!(feature = "hwloc-2_0_4") {
"2.0.4"
} else {
"2.0.0"
};
#[cfg(feature = "vendored")]
setup_vendored_hwloc(required_version);
#[cfg(not(feature = "vendored"))]
find_hwloc(Some(required_version));
}
fn find_hwloc(required_version: Option<&str>) -> pkg_config::Library {
let mut config = pkg_config::Config::new();
if let Some(required_version) = required_version {
let first_unsupported_version = match required_version
.split('.')
.next()
.expect("No major version in required_version")
{
"2" => "3.0.0",
other => panic!("Please add support for hwloc v{other}.x"),
};
config.range_version(required_version..first_unsupported_version);
}
let lib = config
.statik(target_os() != "macos")
.probe("hwloc")
.expect("Could not find a suitable version of hwloc");
if target_family().split(',').any(|family| family == "unix") {
for link_path in &lib.link_paths {
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{}",
link_path
.to_str()
.expect("Link path is not an UTF-8 string")
);
}
}
lib
}
#[cfg(feature = "vendored")]
fn setup_vendored_hwloc(required_version: &str) {
let (source_version, sha3_digest) = match required_version
.split('.')
.next()
.expect("No major version in required_version")
{
"2" => (
"2.12.1",
hex("ece646396730d57109298aac80dc226cd05c7d9e7f5b48976a43e9c365a73417"),
),
other => panic!("Please add support for bundling hwloc v{other}.x"),
};
let out_path = env::var("OUT_DIR").expect("No output directory given");
let source_path = fetch_hwloc(out_path, source_version, sha3_digest);
if target_os() == "windows" {
install_hwloc_cmake(source_path);
} else {
install_hwloc_autotools(source_path);
}
}
#[cfg(feature = "vendored")]
fn hex(hex: &'static str) -> Box<[u8]> {
assert_eq!(hex.len() % 2, 0, "Digest string {hex:?} should contain full bytes, i.e. pairs of hex digits, but it contains an odd number of bytes");
hex.as_bytes()
.chunks_exact(2)
.map(|hexdigit_pair| {
hexdigit_pair.iter().copied().fold(0, |acc, mut hexdigit| {
hexdigit = match hexdigit.to_ascii_lowercase() {
b'0'..=b'9' => hexdigit - b'0',
b'a'..=b'f' => hexdigit - b'a' + 10,
_ => {
panic!("Encountered byte that is not a hexadecimal digit in digest string {hex:?}")
}
};
acc * 16 + hexdigit
})
})
.collect()
}
#[cfg(feature = "vendored")]
fn fetch_hwloc(
parent_path: impl AsRef<Path>,
version: &str,
sha3_digest: impl AsRef<[u8]>,
) -> PathBuf {
let parent_path = parent_path.as_ref();
let extracted_path = parent_path.join(format!("hwloc-{version}"));
if extracted_path.exists() {
eprintln!("Reusing previous hwloc v{version} download");
return extracted_path;
}
let mut version_components = version.split('.');
let major = version_components.next().expect("no major hwloc version");
let minor = version_components.next().expect("no minor hwloc version");
let url = format!(
"https://download.open-mpi.org/release/hwloc/v{major}.{minor}/hwloc-{version}.tar.gz"
);
eprintln!("Downloading hwloc v{version} from URL {url}...");
let tar_gz = ureq::get(url)
.call()
.expect("failed to GET hwloc source")
.into_body()
.read_to_vec()
.expect("failed to parse hwloc source HTTP body");
eprintln!("Verifying hwloc source integrity...");
let mut hasher = Sha3_256::new();
hasher.update(&tar_gz[..]);
assert_eq!(
&hasher.finalize()[..],
sha3_digest.as_ref(),
"downloaded hwloc source failed integrity check"
);
eprintln!("Extracting hwloc source...");
let tar = GzDecoder::new(&tar_gz[..]);
let mut archive = Archive::new(tar);
archive
.unpack(parent_path)
.expect("failed to extract hwloc source");
extracted_path
}
#[cfg(feature = "vendored")]
fn install_hwloc_cmake(source_path: impl AsRef<Path>) {
let cmake_path = source_path.as_ref().join("contrib").join("windows-cmake");
assert!(
cmake_path.join("CMakeLists.txt").exists(),
"Need hwloc's CMake support to build on Windows (with MSVC)"
);
let mut config = cmake::Config::new(cmake_path);
if let Ok(profile) = env::var("HWLOC_BUILD_PROFILE") {
config.profile(&profile);
}
if let Ok(toolchain) = env::var("HWLOC_TOOLCHAIN") {
config.define("CMAKE_TOOLCHAIN_FILE", &toolchain);
}
config.define("HWLOC_ENABLE_TESTING", "OFF");
config.define("HWLOC_SKIP_LSTOPO", "1");
config.define("HWLOC_SKIP_TOOLS", "1");
config.profile("Release");
let install_path = config.always_configure(false).build();
println!("cargo:rustc-link-lib=static=hwloc");
println!(
"cargo:rustc-link-search={}",
install_path.join("lib").display()
);
}
#[cfg(feature = "vendored")]
fn install_hwloc_autotools(source_path: impl AsRef<Path>) {
let mut config = autotools::Config::new(source_path);
config.enable_static().disable_shared();
if target_os() == "macos" {
config.ldflag("-F/System/Library/Frameworks -framework CoreFoundation");
if cfg!(feature = "vendored-extra") {
println!("cargo:rustc-link-lib=xml2");
}
}
if cfg!(not(feature = "vendored-extra")) {
config
.disable("cairo", None)
.disable("io", None)
.disable("libxml2", None);
}
let install_path = config.fast_build(true).reconf("-ivf").build();
let new_path = |lib_dir: &str| install_path.join(lib_dir).join("pkgconfig");
let new_path = format!(
"{}:{}",
new_path("lib").display(),
new_path("lib64").display()
);
match env::var("PKG_CONFIG_PATH") {
Ok(old_path) if !old_path.is_empty() => {
env::set_var("PKG_CONFIG_PATH", format!("{new_path}:{old_path}"))
}
Ok(_) | Err(env::VarError::NotPresent) => env::set_var("PKG_CONFIG_PATH", new_path),
Err(other_err) => panic!("Failed to check PKG_CONFIG_PATH: {other_err}"),
}
find_hwloc(None);
}
fn target_os() -> &'static str {
static TARGET_OS: OnceLock<Box<str>> = OnceLock::new();
TARGET_OS
.get_or_init(|| {
std::env::var("CARGO_CFG_TARGET_OS")
.expect("Cargo should tell us what the target OS is")
.into_boxed_str()
})
.as_ref()
}
fn target_family() -> &'static str {
static TARGET_FAMILY: OnceLock<Box<str>> = OnceLock::new();
TARGET_FAMILY
.get_or_init(|| {
std::env::var("CARGO_CFG_TARGET_FAMILY")
.expect("Cargo should tell us what the target family is")
.into_boxed_str()
})
.as_ref()
}