cp2k-libint-sys 0.3.2

Static Libint2 build for cp2k-rs
//! Builds static Libint2 2.13.1 from the CP2K-specific pre-generated tarball
//! (lmax = 5, matching the CP2K toolchain) and exports its install prefix via
//! `cargo:root` (DEP_CP2K_LIBINT_ROOT).
//!
//! Libint2 provides the two-electron integrals needed for Hartree-Fock
//! exchange and hybrid functionals. It is C++ (with a Fortran interface
//! module); the worker links libstdc++ statically, so this adds no dynamic
//! dependency. The compile of the pre-generated sources is long (~10+ min).
//!
//! CP2K 2026.2 discovers Libint2 via `find_package(Libint2 CONFIG)` and
//! requires the Fortran module (`libint_f.mod`), both of which only the CMake
//! build of libint >= 2.9 installs — hence CMake here, not autotools.

use cp2k_build_utils as bu;
use std::process::Command;

fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-env-changed=CP2K_TARGET_ARCH");

    let arch = bu::target_arch();
    // Version-suffixed stable dir: CI caches persist across version bumps and
    // the skip check below only tests for the installed archive, so the old
    // libint-stable (2.6.0) tree must not be picked up for 2.13.1.
    let stable_dir = bu::stable_dir("libint-v2.13");
    let src_dir = stable_dir.join("src");
    let build_dir = stable_dir.join("build");
    let install_dir = stable_dir.join("install");

    // The CMake config is installed last, so its presence (not just the
    // archive's) marks a complete install — a failed `cmake --install` leaves
    // the archive behind.
    if install_dir.join("lib").join("libint2.a").exists()
        && install_dir
            .join("lib/cmake/libint2/libint2-config.cmake")
            .exists()
    {
        println!(
            "cargo:warning=Libint2 already installed at {}",
            install_dir.display()
        );
        bu::emit_root(&install_dir);
        return;
    }

    if !src_dir.exists() {
        println!("cargo:warning=Downloading Libint2 2.13.1 (CP2K lmax-5 tarball)...");
        let tarball = stable_dir.join("libint-v2.13.1-cp2k-lmax-5.tar.xz");
        // CP2K-specific pre-generated Libint tarball (matches the toolchain).
        bu::download(
            "https://www.cp2k.org/static/downloads/libint-v2.13.1-cp2k-lmax-5.tar.xz",
            &tarball,
        );
        bu::verify_sha256(
            &tarball,
            "527000f915bea9879391273b96689a8c2b98beeec37cb64637e3c11dd421a5e8",
        );
        std::fs::create_dir_all(&src_dir).expect("Failed to create libint src dir");
        // The tarball prefixes every entry with "./<name>/", so two path
        // components must be stripped.
        bu::run(
            Command::new("tar").args([
                "-xJf",
                tarball.to_str().unwrap(),
                "--strip-components=2",
                "-C",
                src_dir.to_str().unwrap(),
            ]),
            "Libint2 extract",
        );
    }

    // Eigen3 (header-only), installed into the libint prefix: libint's
    // installed CMake config does find_dependency(Eigen3) even with the C++
    // API off, so Eigen3Config.cmake must be discoverable both here and later
    // at CP2K configure time. Installing it into the libint prefix (already on
    // CP2K's CMAKE_PREFIX_PATH) solves both without a separate crate — same
    // role as install_eigen.sh in the CP2K 2026.2 toolchain.
    let eigen_src = stable_dir.join("eigen-src");
    if !eigen_src.exists() {
        println!("cargo:warning=Downloading Eigen 3.4.0 (libint CMake dependency)...");
        let tarball = stable_dir.join("eigen-3.4.0.tar.gz");
        bu::download(
            "https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz",
            &tarball,
        );
        bu::verify_sha256(
            &tarball,
            "8586084f71f9bde545ee7fa6d00288b264a2b7ac3607b974e54d13e7162c1c72",
        );
        std::fs::create_dir_all(&eigen_src).expect("Failed to create eigen src dir");
        bu::run(
            Command::new("tar").args([
                "-xzf",
                tarball.to_str().unwrap(),
                "--strip-components=1",
                "-C",
                eigen_src.to_str().unwrap(),
            ]),
            "Eigen extract",
        );
    }
    if !install_dir
        .join("share/eigen3/cmake/Eigen3Config.cmake")
        .exists()
    {
        let eigen_build = stable_dir.join("eigen-build");
        std::fs::create_dir_all(&eigen_build).expect("Failed to create eigen build dir");
        bu::run(
            Command::new("cmake").args([
                "-S",
                eigen_src.to_str().unwrap(),
                "-B",
                eigen_build.to_str().unwrap(),
                "-DBUILD_TESTING=OFF",
                "-DEIGEN_BUILD_DOC=OFF",
                &format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()),
            ]),
            "Eigen configure",
        );
        bu::run(
            Command::new("cmake").args(["--install", eigen_build.to_str().unwrap()]),
            "Eigen install",
        );
    }

    std::fs::create_dir_all(&build_dir).expect("Failed to create libint build dir");
    let num_jobs = bu::num_jobs();
    let march = bu::march_flag(&arch);
    let mpi_bin = bu::mpi_bin_dir();
    let mpi_cxx = mpi_bin.join("mpicxx").display().to_string();
    let mpi_fc = mpi_bin.join("mpifort").display().to_string();

    // CMake build per the CP2K toolchain (install_libint.sh): Fortran module
    // on, Boost lookup disabled (libint bundles a stripped Boost.Preprocessor).
    println!("cargo:warning=Configuring Libint2 with CMake...");
    bu::run(
        Command::new("cmake")
            .args([
                "-S",
                src_dir.to_str().unwrap(),
                "-B",
                build_dir.to_str().unwrap(),
                "-DCMAKE_BUILD_TYPE=Release",
                "-DBUILD_SHARED_LIBS=OFF",
                "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
                "-DCMAKE_INSTALL_LIBDIR=lib",
                "-DBUILD_TESTING=OFF",
                "-DLIBINT2_ENABLE_FORTRAN=ON",
                // The (header-only) C++ API stays on: libint's install rules
                // unconditionally install the bundled Boost headers that are
                // only unpacked when the C++ API is enabled, and its installed
                // CMake config does find_dependency(Eigen3) regardless — the
                // Eigen install above satisfies both.
                "-DCMAKE_DISABLE_FIND_PACKAGE_Boost=ON",
                &format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()),
                // Eigen3 was installed into the same prefix above.
                &format!("-DCMAKE_PREFIX_PATH={}", install_dir.display()),
                &format!("-DCMAKE_CXX_COMPILER={mpi_cxx}"),
                &format!("-DCMAKE_Fortran_COMPILER={mpi_fc}"),
                &format!("-DCMAKE_CXX_FLAGS={march}"),
                &format!("-DCMAKE_Fortran_FLAGS={march}"),
            ])
            .current_dir(&src_dir),
        "Libint2 configure",
    );

    println!("cargo:warning=Building Libint2 (pre-generated C++, takes 10+ minutes)...");
    bu::run(
        Command::new("cmake").args(["--build", build_dir.to_str().unwrap(), "-j", &num_jobs]),
        "Libint2 build",
    );

    println!("cargo:warning=Installing Libint2...");
    bu::run(
        Command::new("cmake").args(["--install", build_dir.to_str().unwrap()]),
        "Libint2 install",
    );

    println!(
        "cargo:warning=Libint2 installed to {}",
        install_dir.display()
    );
    bu::emit_root(&install_dir);
}