use std::{
fs,
io::{Error, ErrorKind, Result},
path::Path,
process::Command,
str,
};
static MINIMUM_GIT_VERSION: [u8; 3] = [2, 40, 0]; static MINIMUM_CMAKE_VERSION: [u8; 3] = [3, 31, 0];
static BUILD_DIR: &str = "build/";
static OUTPUT_DIR: &str = "external/";
static ABSEIL_SRC: &str = "https://github.com/abseil/abseil-cpp.git";
#[allow(dead_code)]
#[cfg(all(target_os = "windows", target_env = "msvc"))]
static MINIMUM_MSVC_VERSION: [u8; 2] = [19, 22];
fn gather_output(command: &str, args: &[&str]) -> Result<String> {
println!("Running: {} {:?}", command, args);
let output = Command::new(command).args(args).output()?;
String::from_utf8(output.stdout)
.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid output!"))
}
fn is_version_valid(version_token: &str, req: [u8; 3]) -> bool {
let numbers: Vec<u32> = version_token
.split('.')
.filter_map(|s| s.parse().ok())
.collect();
let major = *numbers.get(0).unwrap_or(&0);
let minor = *numbers.get(1).unwrap_or(&0);
let patch = *numbers.get(2).unwrap_or(&0);
(major, minor, patch) >= (req[0] as u32, req[1] as u32, req[2] as u32)
}
fn is_valid_git_version(version_str: &str) -> bool {
let tokens: Vec<&str> = version_str.split_whitespace().collect();
if let Some(&version_token) = tokens.get(2) {
is_version_valid(version_token, MINIMUM_GIT_VERSION)
} else {
false
}
}
fn is_valid_cmake_version(version_str: &str) -> bool {
let tokens: Vec<&str> = version_str.split_whitespace().collect();
if let Some(&version_token) = tokens.get(2) {
is_version_valid(version_token, MINIMUM_CMAKE_VERSION)
} else {
false
}
}
fn run_command(command: &str, args: &[&str], dir: &Path) {
let status = Command::new(command)
.args(args)
.current_dir(dir)
.status()
.expect(&format!("Failed to run command: {} {:?}", command, args));
if !status.success() {
panic!("Command {:?} failed with status: {:?}", command, status);
}
}
#[cfg(all(target_os = "windows", target_env = "msvc"))]
fn basics_check() {
let git_version = gather_output("git", &["--version"]).expect("Failed to get git version");
println!("Git output: {}", git_version);
if !is_valid_git_version(&git_version) {
println!("Please install or update git to the latest version.");
std::process::exit(1);
} else {
println!("Git found. Proceeding.");
}
let cmake_version =
gather_output("CMake", &["--version"]).expect("Failed to get CMake version");
println!("CMake output: {}", cmake_version);
if !is_valid_cmake_version(&cmake_version) {
println!("Please install or update CMake to the latest version.");
std::process::exit(1);
} else {
println!("CMake found. Proceeding.");
}
let caps =
gather_output("CMake", &["-E", "capabilities"]).expect("Failed to run CMake capabilities");
if caps.contains("Visual Studio 16 2019") {
println!("Found Visual Studio 16 2019");
} else if caps.contains("Visual Studio 17 2022") {
println!("Found Visual Studio 17 2022");
} else {
println!(
"No suitable Visual Studio version found.\nRun 'CMake -E capabilities' to see what CMake sees."
);
panic!("CMake reports no suitable MSVC version. Needs 2019+.");
}
}
#[cfg(not(all(target_os = "windows", target_env = "msvc")))]
fn basics_check() {
println!("Architecture not yet supported.");
std::process::exit(1);
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
basics_check();
println!("1. Creating build directory");
fs::create_dir_all(BUILD_DIR).expect("Unable to create build directory");
println!("2. Cloning abseil-cpp repository if not present");
let abseil_dir = Path::new(BUILD_DIR).join("abseil-cpp");
if !abseil_dir.exists() {
run_command("git", &["clone", ABSEIL_SRC], Path::new(BUILD_DIR));
}
println!("3. Creating build directory in abseil-cpp");
let cmake_build_dir = abseil_dir.join(BUILD_DIR);
fs::create_dir_all(&cmake_build_dir).expect("Unable to create abseil build directory");
let common_flags = [
"-DABSL_USE_GOOGLETEST_HEAD=ON",
"-DCMAKE_CXX_STANDARD_REQUIRED=ON",
"-DCMAKE_CXX_STANDARD=20",
];
let build_type_flag = if cfg!(debug_assertions) {
"-DCMAKE_BUILD_TYPE=Debug"
} else {
"-DCMAKE_BUILD_TYPE=Release"
};
let cmake_config_args: Vec<&str> = common_flags
.iter()
.chain([build_type_flag, "../"].iter())
.copied()
.collect();
println!("4. Configuring the build using cmake");
run_command("cmake", &cmake_config_args, &cmake_build_dir);
println!("5. Building the project using cmake");
let cmake_build_args = if cfg!(debug_assertions) {
vec![
"--build",
".",
"--",
"/p:Configuration=Debug",
"/p:Platform=x64",
]
} else {
vec![
"--build",
".",
"--",
"/p:Configuration=Release",
"/p:Platform=x64",
]
};
run_command("cmake", &cmake_build_args, &cmake_build_dir);
println!("6. Moving the built DLL to the project root");
if cfg!(debug_assertions) {
let _source = cmake_build_dir.join("bin/Release/abseil_dll.dll");
let _destination = Path::new(OUTPUT_DIR).join("abseil.dll");
} else {
let _source = cmake_build_dir.join("bin/Debug/abseil_dll.dll");
let _destination = Path::new(OUTPUT_DIR).join("abseil_d.dll");
let _source = cmake_build_dir.join("bin/Debug/abseil_dll.pdb");
let _destination = Path::new(OUTPUT_DIR).join("abseil_d.pdb");
}
println!("Build script completed successfully.");
}