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 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_GDAL", "OFF")
.define("ENABLE_SINGLE_FILES_WERROR", "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"), "config.cc");
let dst = dst.display().to_string();
println!("cargo:rustc-link-search={dst}/build/src/");
pkg_config::Config::new()
.arg("--with-path")
.arg(format!("{dst}/build"))
.probe("libvalhalla")
.unwrap();
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");
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
}