use miniserde::{Deserialize, json};
use std::fs;
use std::path::Path;
fn main() {
let build_type = match (
std::env::var("PROFILE").as_deref(),
std::env::var("DEBUG").as_deref(),
) {
(Ok("debug"), _) => "Debug",
(Ok("release"), Ok("true")) => "RelWithDebInfo",
_ => "Release",
};
let unity_build = !matches!(std::env::var("UNITY_BUILD").as_deref(), Ok("OFF"));
let dst = cmake::Config::new("valhalla")
.define("CMAKE_BUILD_TYPE", build_type)
.define("CMAKE_EXPORT_COMPILE_COMMANDS", "ON") .define("ENABLE_TOOLS", "OFF")
.define("ENABLE_DATA_TOOLS", "OFF")
.define("ENABLE_SERVICES", "OFF")
.define("ENABLE_HTTP", "OFF")
.define("ENABLE_PYTHON_BINDINGS", "OFF")
.define("ENABLE_TESTS", "OFF")
.define("ENABLE_GEOTIFF", "OFF")
.define("ENABLE_SINGLE_FILES_WERROR", "OFF")
.define("CMAKE_UNITY_BUILD", if unity_build { "ON" } else { "OFF" })
.define("ENABLE_THREAD_SAFE_TILE_REF_COUNT", "ON")
.define("LOGGING_LEVEL", "WARN") .build_target("valhalla")
.build();
let _ = fs::remove_file("valhalla/third_party/tz/leapseconds");
let valhalla_includes = extract_includes(
&dst.join("build/compile_commands.json"),
if unity_build {
"/valhalla.dir/Unity/unity_0_cxx.cxx"
} else {
"config.cc"
},
);
cxx_build::bridges(["src/actor.rs", "src/config.rs", "src/lib.rs"])
.file("src/libvalhalla.cpp")
.std("c++20")
.includes(valhalla_includes)
.define("ENABLE_THREAD_SAFE_TILE_REF_COUNT", None)
.compile("libvalhalla-cxxbridge");
let dst = dst.display().to_string();
println!("cargo:rustc-link-search=native={dst}/build/src/");
if let Err(err) = pkg_config::Config::new()
.arg("--with-path")
.arg(format!("{dst}/build"))
.probe("libvalhalla")
{
let pc_file = format!("{}/build/libvalhalla.pc", dst);
let pc_content = fs::read_to_string(&pc_file)
.unwrap_or_else(|_| "Could not read libvalhalla.pc file".to_string());
panic!("Failed to link libvalhalla: {err}\nlibvalhalla.pc:\n{pc_content}");
}
println!("cargo:rerun-if-changed=src/actor.hpp");
println!("cargo:rerun-if-changed=src/config.hpp");
println!("cargo:rerun-if-changed=src/costing.hpp");
println!("cargo:rerun-if-changed=src/libvalhalla.cpp");
println!("cargo:rerun-if-changed=src/libvalhalla.hpp");
println!("cargo:rerun-if-changed=valhalla");
let proto_files: Vec<_> = fs::read_dir("valhalla/proto")
.expect("Failed to read valhalla/proto directory")
.map(|entry| entry.expect("Bad fs entry").path())
.filter(|path| path.extension().is_some_and(|ext| ext == "proto"))
.collect();
prost_build::compile_protos(&proto_files, &["valhalla/proto/"])
.expect("Failed to compile proto files");
}
#[derive(Deserialize)]
struct CompileCommand {
command: String,
file: String,
}
fn extract_includes(compile_commands: &Path, cpp_source: &str) -> Vec<String> {
assert!(compile_commands.exists(), "compile_commands.json not found");
let content =
fs::read_to_string(compile_commands).expect("Failed to read compile_commands.json");
let commands: Vec<CompileCommand> =
json::from_str(&content).expect("Failed to parse compile_commands.json");
let command = commands
.into_iter()
.find(|cmd| cmd.file.ends_with(cpp_source))
.expect("Failed to find reference cpp source file");
let args: Vec<&str> = command.command.split_whitespace().collect();
let mut includes = Vec::new();
for i in 0..args.len() {
if args[i].starts_with("-I") {
includes.push(args[i][2..].to_string());
} else if args[i] == "-isystem" && i + 1 < args.len() {
includes.push(args[i + 1].to_string());
}
}
includes
}