use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=csrc/ferrompi.c");
println!("cargo:rerun-if-changed=csrc/ferrompi.h");
let mpi_config = find_mpi_config();
let mut build = cc::Build::new();
build
.file("csrc/ferrompi.c")
.include("csrc")
.warnings(true)
.extra_warnings(true);
for path in &mpi_config.include_paths {
build.include(path);
}
if env::var("PROFILE").unwrap_or_default() == "release" {
build.opt_level(3);
}
build.compile("ferrompi");
for path in &mpi_config.link_paths {
println!("cargo:rustc-link-search=native={}", path.display());
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path.display());
}
const SKIP_LIBS: &[&str] = &["hwloc", "pmix", "ucp", "ucs", "ucx", "slurm", "amdhip64"];
for lib in &mpi_config.libs {
if !SKIP_LIBS.contains(&lib.as_str()) {
println!("cargo:rustc-link-lib={lib}");
}
}
if let Some(version) = mpi_config.version {
println!("cargo:rustc-env=MPI_VERSION={version}");
}
}
struct MpiConfig {
include_paths: Vec<PathBuf>,
link_paths: Vec<PathBuf>,
libs: Vec<String>,
version: Option<String>,
}
fn find_mpi_config() -> MpiConfig {
if let Ok(pkg_name) = env::var("MPI_PKG_CONFIG") {
if let Ok(config) = try_pkg_config(&pkg_name) {
eprintln!("Found MPI via MPI_PKG_CONFIG={pkg_name}");
return config;
}
}
for pkg_name in &["mpich", "ompi", "mpi"] {
if let Ok(config) = try_pkg_config(pkg_name) {
eprintln!("Found MPI via pkg-config: {pkg_name}");
return config;
}
}
if let Ok(config) = try_mpicc() {
eprintln!("Found MPI via mpicc");
return config;
}
if let Ok(mpich_dir) = env::var("CRAY_MPICH_DIR") {
eprintln!("Found Cray MPI at {mpich_dir}");
return MpiConfig {
include_paths: vec![PathBuf::from(format!("{mpich_dir}/include"))],
link_paths: vec![PathBuf::from(format!("{mpich_dir}/lib"))],
libs: vec!["mpi".to_string()],
version: None,
};
}
for prefix in &["/usr", "/usr/local", "/opt/mpich", "/opt/openmpi"] {
let include = PathBuf::from(format!("{prefix}/include"));
let lib = PathBuf::from(format!("{prefix}/lib"));
if include.join("mpi.h").exists() {
eprintln!("Found MPI at {prefix}");
return MpiConfig {
include_paths: vec![include],
link_paths: vec![lib],
libs: vec!["mpi".to_string()],
version: None,
};
}
}
panic!(
"Could not find MPI installation. Please ensure MPICH or OpenMPI is installed and either:\n\
- Set MPI_PKG_CONFIG to the pkg-config name (e.g., 'mpich')\n\
- Ensure 'mpicc' is in PATH\n\
- Set CRAY_MPICH_DIR for Cray systems"
);
}
fn try_pkg_config(name: &str) -> Result<MpiConfig, pkg_config::Error> {
let lib = pkg_config::Config::new()
.cargo_metadata(false) .probe(name)?;
Ok(MpiConfig {
include_paths: lib.include_paths,
link_paths: lib.link_paths,
libs: lib.libs,
version: Some(lib.version),
})
}
fn try_mpicc() -> Result<MpiConfig, String> {
let mpicc = env::var("MPICC").unwrap_or_else(|_| "mpicc".to_string());
let output = Command::new(&mpicc)
.arg("-show")
.output()
.map_err(|e| format!("Failed to run '{mpicc}': {e}"))?;
if !output.status.success() {
return Err("mpicc -show failed".to_string());
}
let show_output = String::from_utf8_lossy(&output.stdout);
parse_mpicc_show(&show_output)
}
#[allow(clippy::unnecessary_wraps)]
fn parse_mpicc_show(output: &str) -> Result<MpiConfig, String> {
let mut include_paths = Vec::new();
let mut link_paths = Vec::new();
let mut libs = Vec::new();
for part in output.split_whitespace() {
if let Some(path) = part.strip_prefix("-I") {
include_paths.push(PathBuf::from(path));
} else if let Some(path) = part.strip_prefix("-L") {
link_paths.push(PathBuf::from(path));
} else if let Some(lib) = part.strip_prefix("-l") {
libs.push(lib.to_string());
}
}
if libs.is_empty() {
libs.push("mpi".to_string());
}
Ok(MpiConfig {
include_paths,
link_paths,
libs,
version: None,
})
}