use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
const CP2K_GIT_BRANCH: &str = "support/v2026.2";
const CP2K_GIT_COMMIT: &str = "67b5da876dd6a76b8b021d5a04d1c81ba79a4c50";
fn cp2k_git_ref_override() -> Option<String> {
env::var("CP2K_GIT_REF")
.ok()
.filter(|r| !r.trim().is_empty())
}
fn upstream_dir_suffix() -> String {
match cp2k_git_ref_override() {
Some(r) => format!(
"-upstream-{}",
r.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect::<String>()
),
None => String::new(),
}
}
fn cp2k_target_arch() -> String {
env::var("CP2K_TARGET_ARCH").unwrap_or_else(|_| "v3".to_string())
}
fn march_flag(arch: &str) -> String {
match arch {
"v4" => "-march=x86-64-v4".to_string(),
_ => "-march=x86-64-v3".to_string(),
}
}
fn arch_dir_suffix(arch: &str) -> &'static str {
match arch {
"v4" => "-v4",
_ => "",
}
}
fn rocm_cmake_prefix_suffix() -> String {
if std::env::var("CARGO_FEATURE_HIP").is_ok() {
format!(
";{}",
std::env::var("ROCM_PATH").unwrap_or_else(|_| "/opt/rocm".into())
)
} else {
String::new()
}
}
fn gpu_dir_suffix() -> &'static str {
if std::env::var("CARGO_FEATURE_CUDA").is_ok() {
"-cuda"
} else if std::env::var("CARGO_FEATURE_HIP").is_ok() {
"-hip"
} else {
""
}
}
fn skala_dir_suffix() -> &'static str {
if std::env::var("CARGO_FEATURE_SKALA").is_ok() {
"-skala"
} else {
""
}
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CP2K_TARGET_ARCH");
println!("cargo:rerun-if-env-changed=CP2K_GIT_REF");
println!("cargo:rerun-if-env-changed=CP2K_CUDA_ARCH");
println!("cargo:rerun-if-env-changed=CP2K_HIP_ARCH");
println!("cargo:rerun-if-env-changed=CUDA_PATH");
println!("cargo:rerun-if-env-changed=ROCM_PATH");
#[cfg(all(feature = "cuda", feature = "hip"))]
compile_error!("The 'cuda' and 'hip' features are mutually exclusive. Enable only one.");
if cfg!(feature = "extended") {
println!("cargo:rerun-if-changed=extensions/fortran/libcp2k_extended.F90");
println!("cargo:rerun-if-changed=extensions/fortran/libcp2k_mpi.F90");
println!("cargo:rerun-if-changed=extensions/include/libcp2k_extended.h");
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let mpi_base_dir = detect_mpi_installation();
println!("cargo:warning=Building CP2K from source - this may take 30+ minutes");
let profile_dir = out_dir.ancestors().nth(3).unwrap_or(&out_dir).to_path_buf();
let arch = cp2k_target_arch();
let cp2k_stable_dir = profile_dir.join(format!(
"cp2k-stable{}{}{}{}",
arch_dir_suffix(&arch),
gpu_dir_suffix(),
skala_dir_suffix(),
upstream_dir_suffix()
));
std::fs::create_dir_all(&cp2k_stable_dir).expect("Failed to create cp2k-stable dir");
let cp2k_src_dir = clone_cp2k_repository(&cp2k_stable_dir);
let openblas_lib_dir = PathBuf::from(env::var("DEP_OPENBLAS_LIBRARY").expect(
"DEP_OPENBLAS_LIBRARY not set – is openblas-src a dependency with the 'static' feature?",
));
let openblas_static_lib = openblas_lib_dir.join("libopenblas.a");
let openblas_include_dir = PathBuf::from(
env::var("DEP_OPENBLAS_INCLUDE")
.unwrap_or_else(|_| openblas_lib_dir.to_string_lossy().into_owned()),
);
println!(
"cargo:warning=Using static OpenBLAS: {}",
openblas_static_lib.display()
);
let dep_root = |k: &str| {
PathBuf::from(env::var(k).unwrap_or_else(|_| {
panic!("{k} not set – the corresponding -sys crate must be a dependency")
}))
};
let fftw3_install = dep_root("DEP_CP2K_FFTW3_ROOT");
let dbcsr_install_dir = dep_root("DEP_CP2K_DBCSR_ROOT");
let scalapack_install = dep_root("DEP_CP2K_SCALAPACK_ROOT");
let libxc_install = dep_root("DEP_CP2K_LIBXC_ROOT");
let dftd4_install = dep_root("DEP_CP2K_DFTD4_ROOT");
let libint_install = dep_root("DEP_CP2K_LIBINT_ROOT");
let gauxc_install = dep_root("DEP_CP2K_GAUXC_ROOT");
let libvori_install = dep_root("DEP_CP2K_LIBVORI_ROOT");
let elpa_install = dep_root("DEP_CP2K_ELPA_ROOT");
let cosma_install = dep_root("DEP_CP2K_COSMA_ROOT");
let libsmeagol_install = dep_root("DEP_CP2K_LIBSMEAGOL_ROOT");
let trexio_install = dep_root("DEP_CP2K_TREXIO_ROOT");
let tblite_install = dep_root("DEP_CP2K_TBLITE_ROOT");
let greenx_install = dep_root("DEP_CP2K_GREENX_ROOT");
let gmp_install = dep_root("DEP_CP2K_GMP_ROOT");
let libvdwxc_install = dep_root("DEP_CP2K_LIBVDWXC_ROOT");
let plumed_install = dep_root("DEP_CP2K_PLUMED_ROOT");
let zlib_install = dep_root("DEP_CP2K_ZLIB_ROOT");
let pexsi_enabled = env::var("CARGO_FEATURE_PEXSI").is_ok();
let pexsi_install = pexsi_enabled.then(|| dep_root("DEP_CP2K_PEXSI_ROOT"));
let superlu_dist_install = pexsi_enabled.then(|| dep_root("DEP_CP2K_SUPERLU_DIST_ROOT"));
let parmetis_install = pexsi_enabled.then(|| dep_root("DEP_CP2K_PARMETIS_ROOT"));
let libxsmm_install = env::var("DEP_CP2K_LIBXSMM_ROOT").ok().map(PathBuf::from);
let sirius_deps = SiriusDeps {
sirius: dep_root("DEP_CP2K_SIRIUS_ROOT"),
gsl: dep_root("DEP_CP2K_GSL_ROOT"),
spglib: dep_root("DEP_CP2K_SPGLIB_ROOT"),
pugixml: dep_root("DEP_CP2K_PUGIXML_ROOT"),
spfft: dep_root("DEP_CP2K_SPFFT_ROOT"),
spla: dep_root("DEP_CP2K_SPLA_ROOT"),
costa: dep_root("DEP_CP2K_COSTA_ROOT"),
hdf5: dep_root("DEP_CP2K_HDF5_ROOT"),
fmt: dep_root("DEP_CP2K_FMT_ROOT"),
};
let cp2k_build_dir = cp2k_stable_dir.join("build");
let deps_fingerprint = {
let mut roots: Vec<(String, String)> = env::vars()
.filter(|(k, _)| k.starts_with("DEP_CP2K_") && k.ends_with("_ROOT"))
.map(|(k, v)| {
let marker = PathBuf::from(&v)
.parent()
.map(|p| p.join(".version"))
.and_then(|m| std::fs::read_to_string(m).ok())
.unwrap_or_default();
(k, format!("{v}={}", marker.trim()))
})
.collect();
roots.sort();
roots
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>()
.join("\n")
};
let fp_file = cp2k_stable_dir.join(".deps-fingerprint");
let fp_stale = std::fs::read_to_string(&fp_file)
.map(|prev| prev != deps_fingerprint)
.unwrap_or(true);
if fp_stale && cp2k_build_dir.exists() {
println!(
"cargo:warning=A CP2K dependency changed version – wiping the CP2K build dir for a clean reconfigure..."
);
std::fs::remove_dir_all(&cp2k_build_dir).expect("Failed to remove stale CP2K build dir");
}
std::fs::create_dir_all(&cp2k_build_dir).expect("Failed to create CP2K build directory");
configure_cp2k_cmake(
&cp2k_src_dir,
&cp2k_build_dir,
&dbcsr_install_dir,
&openblas_static_lib,
&openblas_include_dir,
&fftw3_install,
&scalapack_install,
&libxc_install,
libxsmm_install.as_deref(),
&dftd4_install,
&libint_install,
&gauxc_install,
&libvori_install,
&elpa_install,
&cosma_install,
&libsmeagol_install,
&sirius_deps,
&trexio_install,
&tblite_install,
&greenx_install,
&gmp_install,
&libvdwxc_install,
&plumed_install,
&zlib_install,
pexsi_install.as_deref(),
superlu_dist_install.as_deref(),
parmetis_install.as_deref(),
);
std::fs::write(&fp_file, &deps_fingerprint).ok();
build_cp2k_cmake(&cp2k_build_dir);
if cfg!(feature = "extended") {
println!("cargo:warning=Building Fortran extensions for extended interface...");
build_fortran_extensions(&cp2k_src_dir, &cp2k_build_dir, &out_dir);
}
let libcp2k_path = find_libcp2k(&cp2k_build_dir);
let cp2k_lib_dir = libcp2k_path.parent().unwrap();
let emit = |k: &str, v: &Path| println!("cargo:{k}={}", v.display());
emit("cp2k_lib_dir", cp2k_lib_dir);
let built_commit = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&cp2k_src_dir)
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
let built_ref = cp2k_git_ref_override().unwrap_or_else(|| CP2K_GIT_BRANCH.to_string());
println!("cargo:cp2k_source_ref={built_ref}");
println!("cargo:cp2k_source_commit={built_commit}");
println!("cargo:warning=CP2K source: {built_ref} @ {built_commit}");
emit("dbcsr_lib_dir", &dbcsr_install_dir.join("lib"));
emit("fftw3_lib_dir", &fftw3_install.join("lib"));
emit("scalapack_lib_dir", &scalapack_install.join("lib"));
emit("libxc_lib_dir", &libxc_install.join("lib"));
emit("dftd4_lib_dir", &dftd4_install.join("lib"));
emit("libint_lib_dir", &libint_install.join("lib"));
emit("gauxc_lib_dir", &gauxc_install.join("lib"));
if let Ok(libtorch_root) = std::env::var("DEP_CP2K_LIBTORCH_ROOT") {
emit(
"libtorch_lib_dir",
&PathBuf::from(libtorch_root).join("lib"),
);
}
emit("libvori_lib_dir", &libvori_install.join("lib"));
emit("elpa_lib_dir", &elpa_install.join("lib"));
emit("cosma_lib_dir", &cosma_install.join("lib"));
emit("libsmeagol_lib_dir", &libsmeagol_install.join("lib"));
emit("trexio_lib_dir", &trexio_install.join("lib"));
emit("tblite_lib_dir", &tblite_install.join("lib"));
emit("greenx_lib_dir", &greenx_install.join("lib"));
emit("gmp_lib_dir", &gmp_install.join("lib"));
emit("libvdwxc_lib_dir", &libvdwxc_install.join("lib"));
emit("plumed_lib_dir", &plumed_install.join("lib"));
emit("zlib_lib_dir", &zlib_install.join("lib"));
if let (Some(pexsi), Some(superlu_dist), Some(parmetis)) =
(&pexsi_install, &superlu_dist_install, &parmetis_install)
{
emit("pexsi_lib_dir", &pexsi.join("lib"));
emit("superlu_dist_lib_dir", &superlu_dist.join("lib"));
emit("parmetis_lib_dir", &parmetis.join("lib"));
}
if let Some(libxsmm) = &libxsmm_install {
emit("libxsmm_lib_dir", &libxsmm.join("lib"));
}
emit("sirius_lib_dir", &sirius_deps.sirius.join("lib"));
emit("gsl_lib_dir", &sirius_deps.gsl.join("lib"));
emit("spglib_lib_dir", &sirius_deps.spglib.join("lib"));
emit("pugixml_lib_dir", &sirius_deps.pugixml.join("lib"));
emit("spfft_lib_dir", &sirius_deps.spfft.join("lib"));
emit("spla_lib_dir", &sirius_deps.spla.join("lib"));
emit("costa_lib_dir", &sirius_deps.costa.join("lib"));
emit("hdf5_lib_dir", &sirius_deps.hdf5.join("lib"));
emit("fmt_lib_dir", &sirius_deps.fmt.join("lib"));
emit("openblas_lib", &openblas_static_lib);
if let Some(mpi_lib_dir) = find_mpi_lib_dir(&mpi_base_dir) {
emit("mpi_lib_dir", &mpi_lib_dir);
}
if cfg!(feature = "extended") {
let extended_lib_dir = out_dir.join("extensions_lib");
if extended_lib_dir.join("libcp2k_extended.a").exists() {
emit("extended_lib_dir", &extended_lib_dir);
}
}
}
fn detect_mpi_installation() -> PathBuf {
if let Ok(output) = Command::new("which").arg("mpicc").output()
&& output.status.success()
{
let mpicc = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
if let Some(base) = mpicc.parent().and_then(Path::parent) {
return base.to_path_buf();
}
}
let common_paths = [
"/usr/lib64/openmpi",
"/usr/lib/x86_64-linux-gnu/openmpi",
"/usr/local",
"/opt/openmpi",
];
for path in &common_paths {
let path_buf = PathBuf::from(path);
if path_buf.join("bin").join("mpicc").exists()
|| path_buf.join("include").join("mpi.h").exists()
{
return path_buf;
}
}
panic!(
"Could not find MPI installation. Ensure OpenMPI is installed (checked PATH and common locations like /usr/lib64/openmpi)."
);
}
fn find_mpi_lib_dir(mpi_base_dir: &Path) -> Option<PathBuf> {
let lib_candidates = ["lib64", "lib"];
for lib_subdir in &lib_candidates {
let lib_dir = mpi_base_dir.join(lib_subdir);
if lib_dir.exists() {
return Some(lib_dir);
}
}
None
}
fn mpi_pkgconfig_dir() -> String {
if let Some(lib) = find_mpi_lib_dir(&detect_mpi_installation()) {
let pc = lib.join("pkgconfig");
if pc.is_dir() {
return pc.to_string_lossy().into_owned();
}
}
"/usr/lib64/openmpi/lib/pkgconfig".to_string()
}
struct SiriusDeps {
sirius: PathBuf,
gsl: PathBuf,
spglib: PathBuf,
pugixml: PathBuf,
spfft: PathBuf,
spla: PathBuf,
costa: PathBuf,
hdf5: PathBuf,
fmt: PathBuf,
}
fn configure_cp2k_cmake(
cp2k_src_dir: &Path,
cp2k_build_dir: &Path,
dbcsr_install_dir: &Path,
openblas_static_lib: &Path,
openblas_include_dir: &Path,
fftw3_install: &Path,
scalapack_install: &Path,
libxc_install: &Path,
libxsmm_install: Option<&Path>,
dftd4_install: &Path,
libint_install: &Path,
gauxc_install: &Path,
libvori_install: &Path,
elpa_install: &Path,
cosma_install: &Path,
libsmeagol_install: &Path,
sirius: &SiriusDeps,
trexio_install: &Path,
tblite_install: &Path,
greenx_install: &Path,
gmp_install: &Path,
libvdwxc_install: &Path,
plumed_install: &Path,
zlib_install: &Path,
pexsi_install: Option<&Path>,
superlu_dist_install: Option<&Path>,
parmetis_install: Option<&Path>,
) {
println!("cargo:warning=Configuring CP2K with CMake...");
let num_jobs = env::var("NUM_JOBS").unwrap_or_else(|_| num_cpus::get().to_string());
let mpi_bin = detect_mpi_installation().join("bin");
let openmpi_pkgconfig = mpi_pkgconfig_dir();
let mut pkg_config_dirs = vec![openmpi_pkgconfig.clone()];
let _ = libxsmm_install; pkg_config_dirs.push(
elpa_install
.join("lib")
.join("pkgconfig")
.display()
.to_string(),
);
pkg_config_dirs.push(
libvdwxc_install
.join("lib")
.join("pkgconfig")
.display()
.to_string(),
);
pkg_config_dirs.push(
plumed_install
.join("lib")
.join("pkgconfig")
.display()
.to_string(),
);
let pkg_config_path = match env::var("PKG_CONFIG_PATH") {
Ok(existing) => format!("{}:{existing}", pkg_config_dirs.join(":")),
Err(_) => pkg_config_dirs.join(":"),
};
let mut prefix_entries = vec![
fftw3_install.display().to_string(),
dbcsr_install_dir.display().to_string(),
scalapack_install.display().to_string(),
libxc_install.display().to_string(),
dftd4_install.display().to_string(),
libint_install.display().to_string(),
gauxc_install.display().to_string(),
];
if let Ok(libtorch_root) = std::env::var("DEP_CP2K_LIBTORCH_ROOT") {
prefix_entries.push(libtorch_root);
}
prefix_entries.push(tblite_install.display().to_string());
prefix_entries.push(greenx_install.display().to_string());
prefix_entries.push(plumed_install.display().to_string());
if let (Some(pexsi), Some(superlu_dist), Some(parmetis)) =
(pexsi_install, superlu_dist_install, parmetis_install)
{
prefix_entries.push(pexsi.display().to_string());
prefix_entries.push(superlu_dist.display().to_string());
prefix_entries.push(parmetis.display().to_string());
}
prefix_entries.push(sirius.sirius.join("lib/cmake/sirius").display().to_string());
prefix_entries.push(
sirius
.sirius
.join("lib/cmake/sirius_cxx")
.display()
.to_string(),
);
prefix_entries.push(sirius.gsl.display().to_string());
prefix_entries.push(sirius.hdf5.display().to_string());
prefix_entries.push(sirius.pugixml.display().to_string());
prefix_entries.push(sirius.fmt.display().to_string());
prefix_entries.push(sirius.spfft.join("lib/cmake/SpFFT").display().to_string());
prefix_entries.push(sirius.spla.join("lib/cmake/SPLA").display().to_string());
prefix_entries.push(sirius.costa.join("lib/cmake/costa").display().to_string());
prefix_entries.push(cosma_install.join("lib/cmake/cosma").display().to_string());
#[cfg(feature = "hip")]
{
let rocm_path = env::var("ROCM_PATH").unwrap_or_else(|_| "/opt/rocm".into());
prefix_entries.push(rocm_path.clone());
prefix_entries.push(format!("{rocm_path}/lib/cmake"));
}
#[cfg(feature = "cuda")]
{
let cuda_path = env::var("CUDA_PATH").unwrap_or_else(|_| "/usr/local/cuda".into());
prefix_entries.push(cuda_path);
}
let cmake_prefix_path = prefix_entries.join(";");
let mut args: Vec<String> = vec![
"-S".into(),
cp2k_src_dir.to_string_lossy().into_owned(),
"-B".into(),
cp2k_build_dir.to_string_lossy().into_owned(),
"-DCMAKE_BUILD_TYPE=Release".into(),
"-DBUILD_SHARED_LIBS=OFF".into(),
"-DCP2K_USE_MPI=ON".into(),
"-DCP2K_USE_FFTW3=ON".into(),
"-DCP2K_USE_BLAS=ON".into(),
"-DCP2K_USE_LAPACK=ON".into(),
"-DCP2K_USE_SCALAPACK=ON".into(),
"-DCP2K_USE_LIBXC=ON".into(),
"-DCP2K_USE_DFTD4=ON".into(),
"-DCP2K_USE_SPGLIB=OFF".into(),
"-DCP2K_USE_LIBINT2=ON".into(),
"-DCP2K_USE_GAUXC=ON".into(),
"-DCP2K_USE_VORI=ON".into(),
format!("-DCP2K_LIBVORI_ROOT={}", libvori_install.display()),
"-DCP2K_USE_ELPA=ON".into(),
"-DCP2K_ENABLE_ELPA_OPENMP_SUPPORT=ON".into(),
"-DCP2K_USE_COSMA=ON".into(),
"-DCP2K_USE_LIBSMEAGOL=ON".into(),
format!("-DCP2K_LIBSMEAGOL_ROOT={}", libsmeagol_install.display()),
"-DCP2K_USE_TREXIO=ON".into(),
format!("-DTREXIO_ROOT={}", trexio_install.display()),
"-DCP2K_USE_TBLITE=ON".into(),
"-DCP2K_USE_GREENX=ON".into(),
"-DCP2K_USE_LIBVDWXC=ON".into(),
"-DCP2K_USE_PLUMED=ON".into(),
format!(
"-DGMP_LIBRARY={}",
gmp_install.join("lib/libgmp.a").display()
),
format!(
"-DBLAS_LIBRARIES={};gfortran;gomp;pthread;m",
openblas_static_lib.display()
),
format!(
"-DLAPACK_LIBRARIES={};gfortran;gomp;pthread;m",
openblas_static_lib.display()
),
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON".into(),
"-DCP2K_BLAS_VENDOR=CUSTOM".into(),
format!(
"-DCP2K_BLAS_LINK_LIBRARIES={}",
openblas_static_lib.display()
),
format!(
"-DCP2K_LAPACK_LINK_LIBRARIES={}",
openblas_static_lib.display()
),
format!(
"-DCP2K_BLAS_INCLUDE_DIRS={}",
openblas_include_dir.display()
),
format!("-DCP2K_FFTW3_ROOT={}", fftw3_install.display()),
"-DCP2K_ENABLE_FFTW3_OPENMP_SUPPORT=ON".into(),
format!(
"-DCMAKE_PREFIX_PATH={cmake_prefix_path}{}",
rocm_cmake_prefix_suffix()
),
format!(
"-DCMAKE_Fortran_COMPILER={}",
mpi_bin.join("mpifort").display()
),
format!("-DCMAKE_C_COMPILER={}", mpi_bin.join("mpicc").display()),
format!("-DCMAKE_CXX_COMPILER={}", mpi_bin.join("mpicxx").display()),
format!("-DCMAKE_C_FLAGS={}", march_flag(&cp2k_target_arch())),
format!("-DCMAKE_CXX_FLAGS={}", march_flag(&cp2k_target_arch())),
format!("-DCMAKE_Fortran_FLAGS={}", march_flag(&cp2k_target_arch())),
format!("-DCMAKE_BUILD_PARALLEL_LEVEL={num_jobs}"),
];
if pexsi_install.is_some() {
args.push("-DCP2K_USE_PEXSI=ON".into());
}
#[cfg(feature = "cuda")]
{
println!("cargo:warning=Enabling CUDA GPU acceleration...");
let cuda_arch = env::var("CP2K_CUDA_ARCH").unwrap_or_else(|_| "80".into());
args.push("-DCP2K_USE_ACCEL=CUDA".into());
args.push(format!("-DCMAKE_CUDA_ARCHITECTURES={cuda_arch}"));
args.push("-DCP2K_ENABLE_GRID_GPU=ON".into());
args.push("-DCP2K_ENABLE_DBM_GPU=ON".into());
args.push("-DCP2K_ENABLE_PW_GPU=ON".into());
}
#[cfg(feature = "hip")]
{
println!("cargo:warning=Enabling HIP/ROCm GPU acceleration...");
let hip_arch = env::var("CP2K_HIP_ARCH").unwrap_or_else(|_| "gfx90a".into());
args.push("-DCP2K_USE_ACCEL=HIP".into());
args.push(format!("-DCMAKE_HIP_ARCHITECTURES={hip_arch}"));
args.push("-DCP2K_ENABLE_GRID_GPU=ON".into());
args.push("-DCP2K_ENABLE_DBM_GPU=ON".into());
args.push("-DCP2K_ENABLE_PW_GPU=ON".into());
}
#[cfg(not(any(feature = "cuda", feature = "hip")))]
{
args.push("-DCP2K_USE_ACCEL=NONE".into());
}
println!("cargo:warning=Enabling SIRIUS support...");
let openblas_lib_dir = openblas_static_lib.parent().unwrap();
args.push("-DCP2K_USE_SIRIUS=ON".into());
args.push(format!("-DHDF5_ROOT={}", sirius.hdf5.display()));
args.push("-DHDF5_USE_STATIC_LIBRARIES=ON".into());
args.push("-DHDF5_PREFER_PARALLEL=OFF".into());
args.push(format!("-DGSL_ROOT_DIR={}", sirius.gsl.display()));
args.push("-DBLA_VENDOR=OpenBLAS".into());
args.push("-DBLA_STATIC=ON".into());
args.push(format!(
"-DCMAKE_LIBRARY_PATH={}",
openblas_lib_dir.display()
));
let mut cmd = Command::new("cmake");
cmd.args(&args)
.env("PKG_CONFIG_PATH", &pkg_config_path)
.env("SPG_DIR", sirius.spglib.display().to_string())
.env("LIBXCROOT", libxc_install.display().to_string())
.env("GSL_ROOT_DIR", sirius.gsl.display().to_string())
.env("SCALAPACKROOT", scalapack_install.display().to_string())
.env("LIBVORI_ROOT", libvori_install.display().to_string())
.env("LIBSMEAGOL_ROOT", libsmeagol_install.display().to_string())
.env("TREXIO_ROOT", trexio_install.display().to_string())
.env(
"OPENBLAS_ROOT",
openblas_static_lib.parent().unwrap().display().to_string(),
)
.env("OPENBLAS_DIR", openblas_include_dir.display().to_string());
let output = cmd
.output()
.expect("Failed to run cmake configure. Make sure cmake is installed.");
if !output.status.success() {
eprintln!("CMake configure failed!");
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
panic!("CP2K CMake configuration failed");
}
println!("cargo:warning=CP2K CMake configuration completed");
}
fn build_cp2k_cmake(cp2k_build_dir: &Path) {
println!("cargo:warning=Building CP2K with CMake - this may take 30+ minutes...");
let num_jobs = env::var("NUM_JOBS").unwrap_or_else(|_| num_cpus::get().to_string());
let home = env::var("HOME").ok();
let extended_path = {
let base = env::var("PATH").unwrap_or_else(|_| "/usr/local/bin:/usr/bin:/bin".to_string());
match home.as_ref() {
Some(h) => format!("{h}/.local/bin:{base}"),
None => {
println!(
"cargo:warning=HOME not set; cannot extend PATH with ~/.local/bin for fypp"
);
base
}
}
};
let extended_pythonpath = {
let base = env::var("PYTHONPATH").unwrap_or_default();
let extra: String = match home.as_ref() {
Some(h) => {
let candidates = [
format!("{h}/.local/lib/python3.12/site-packages"),
format!("{h}/.local/lib/python3.11/site-packages"),
format!("{h}/.local/lib/python3.10/site-packages"),
format!("{h}/.local/lib/python3.9/site-packages"),
];
candidates.join(":")
}
None => {
println!(
"cargo:warning=HOME not set; cannot extend PYTHONPATH with ~/.local site-packages for fypp"
);
String::new()
}
};
if extra.is_empty() {
base
} else if base.is_empty() {
extra
} else {
format!("{extra}:{base}")
}
};
let output = Command::new("cmake")
.args([
"--build",
cp2k_build_dir.to_str().unwrap(),
"--target",
"cp2k",
"-j",
&num_jobs,
])
.env("PATH", &extended_path)
.env("PYTHONPATH", &extended_pythonpath)
.output()
.expect("Failed to execute CP2K CMake build");
if !output.status.success() {
eprintln!("CP2K build failed!");
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
panic!("CP2K compilation failed");
}
println!("cargo:warning=CP2K build completed successfully");
}
fn find_libcp2k(cp2k_build_dir: &Path) -> PathBuf {
let lib_path = cp2k_build_dir.join("lib").join("libcp2k.a");
if lib_path.exists() {
return lib_path;
}
panic!("Could not find libcp2k.a at {lib_path:?} after building");
}
fn clone_cp2k_repository(out_dir: &Path) -> PathBuf {
let cp2k_src_dir = out_dir.join("cp2k");
if cp2k_src_dir.exists() {
let current_sha = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&cp2k_src_dir)
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string());
let matches = if cp2k_git_ref_override().is_some() {
true
} else {
let pinned = CP2K_GIT_COMMIT.to_string();
current_sha
.as_deref()
.map(|s| s == pinned || s.starts_with(&pinned) || pinned.starts_with(s))
.unwrap_or(false)
};
if !matches {
println!(
"cargo:warning=Existing CP2K checkout is at {:?}, expected {CP2K_GIT_COMMIT} – wiping and re-cloning...",
current_sha.as_deref().unwrap_or("<unknown>")
);
std::fs::remove_dir_all(&cp2k_src_dir).expect("Failed to remove stale CP2K checkout");
let stale_build_dir = out_dir.join("build");
if stale_build_dir.exists() {
std::fs::remove_dir_all(&stale_build_dir)
.expect("Failed to remove stale CP2K build dir");
}
}
}
let ref_override = cp2k_git_ref_override();
let clone_ref = ref_override
.clone()
.unwrap_or_else(|| CP2K_GIT_BRANCH.to_string());
if !cp2k_src_dir.exists() {
println!("cargo:warning=Cloning CP2K repository at {clone_ref:?}...");
let output = Command::new("git")
.args([
"clone",
"--depth",
"1",
"--branch",
&clone_ref, "--recursive", "https://github.com/cp2k/cp2k.git",
cp2k_src_dir.to_str().unwrap(),
])
.output()
.expect("Failed to clone CP2K repository. Make sure git is installed.");
if !output.status.success() {
panic!(
"Failed to clone CP2K repository: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let cloned_sha = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&cp2k_src_dir)
.output()
.expect("Failed to read cloned CP2K HEAD");
if !cloned_sha.status.success() {
panic!(
"Failed to read cloned CP2K HEAD: {}",
String::from_utf8_lossy(&cloned_sha.stderr)
);
}
let cloned = String::from_utf8_lossy(&cloned_sha.stdout)
.trim()
.to_string();
if ref_override.is_none() && cloned != CP2K_GIT_COMMIT {
panic!(
"CP2K branch {CP2K_GIT_BRANCH:?} HEAD is {cloned}, but build is pinned to \
{CP2K_GIT_COMMIT}. The branch has advanced upstream — re-pin \
CP2K_GIT_COMMIT in crates/cp2k-sys/build.rs after verifying the new commit."
);
}
println!("cargo:warning=CP2K repository cloned successfully at {cloned}");
} else {
println!("cargo:warning=CP2K repository already exists, updating submodules...");
if let Some(git_ref) = &ref_override {
let fetched = Command::new("git")
.args(["fetch", "--depth", "1", "origin", git_ref])
.current_dir(&cp2k_src_dir)
.output()
.expect("Failed to fetch the CP2K override ref");
if !fetched.status.success() {
panic!(
"Failed to fetch CP2K ref {git_ref:?}: {}",
String::from_utf8_lossy(&fetched.stderr)
);
}
let reset = Command::new("git")
.args(["reset", "--hard", "FETCH_HEAD"])
.current_dir(&cp2k_src_dir)
.output()
.expect("Failed to reset the CP2K checkout to the fetched ref");
if !reset.status.success() {
panic!(
"Failed to reset CP2K to {git_ref:?}: {}",
String::from_utf8_lossy(&reset.stderr)
);
}
}
let output = Command::new("git")
.args(["submodule", "update", "--init", "--recursive"])
.current_dir(&cp2k_src_dir)
.output()
.expect("Failed to update submodules");
if !output.status.success() {
panic!(
"Failed to update submodules: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}
cp2k_src_dir
}
fn build_fortran_extensions(cp2k_src_dir: &Path, cp2k_build_dir: &Path, out_dir: &Path) {
let extensions_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("extensions/fortran");
let obj_dir = out_dir.join("extensions_obj");
let lib_dir = out_dir.join("extensions_lib");
std::fs::create_dir_all(&obj_dir).expect("Failed to create obj dir");
std::fs::create_dir_all(&lib_dir).expect("Failed to create lib dir");
println!("cargo:warning=Compiling Fortran extensions...");
let cp2k_src = cp2k_src_dir.join("src");
let mod_files_dir = cp2k_build_dir.join("src").join("mod_files");
if !mod_files_dir.exists() {
panic!(
"CP2K mod files directory not found: {}. Make sure CP2K is built before compiling extensions.",
mod_files_dir.display()
);
}
let fortran_files = vec!["libcp2k_extended.F90", "libcp2k_mpi.F90"];
let mut object_files = Vec::new();
for fortran_file in &fortran_files {
let src_path = extensions_dir.join(fortran_file);
if !src_path.exists() {
println!("cargo:warning=Skipping {} - file not found", fortran_file);
continue;
}
let obj_name = fortran_file.replace(".F90", ".o").replace(".f90", ".o");
let obj_path = obj_dir.join(&obj_name);
println!("cargo:warning=Compiling {}...", fortran_file);
let output = Command::new("mpifort")
.args([
"-c",
"-fPIC",
"-O2",
"-g",
"-ffree-form",
"-ffree-line-length-none",
"-fallow-argument-mismatch",
"-D__parallel", &format!("-I{}", cp2k_src.display()),
&format!("-I{}/base", cp2k_src.display()),
&format!("-I{}", mod_files_dir.display()), "-o",
obj_path.to_str().unwrap(),
src_path.to_str().unwrap(),
])
.output()
.unwrap_or_else(|_| panic!("Failed to compile {}", fortran_file));
if !output.status.success() {
eprintln!("Failed to compile {}", fortran_file);
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
panic!("Fortran compilation failed for {}", fortran_file);
}
object_files.push(obj_path);
}
if object_files.is_empty() {
println!("cargo:warning=No Fortran extension files compiled");
return;
}
let lib_path = lib_dir.join("libcp2k_extended.a");
println!("cargo:warning=Creating libcp2k_extended.a...");
let output = Command::new("ar")
.arg("rcs")
.arg(&lib_path)
.args(&object_files)
.output()
.expect("Failed to create archive");
if !output.status.success() {
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
panic!("Failed to create libcp2k_extended.a");
}
println!(
"cargo:warning=Fortran extensions built successfully at {}",
lib_path.display()
);
}