use std::{env, path::PathBuf};
fn regex_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if "\\.+*?()|[]{}^$".contains(c) {
out.push('\\');
}
out.push(c);
}
out
}
const SOURCES: &[&str] = &[
"2D.cpp",
"AIMultiplayer.cpp",
"Aircraft.cpp",
"CSLCopy.cpp",
"CSLModels.cpp",
"Contrail.cpp",
"Map.cpp",
"Network.cpp",
"RelatedDoc8643.cpp",
"Remote.cpp",
"Sound.cpp",
"Utilities.cpp",
"XPMPMultiplayer.cpp",
];
fn xpmp2_dir(manifest_dir: &std::path::Path) -> PathBuf {
if let Some(dir) = env::var_os("XPMP2_SRC_DIR") {
return PathBuf::from(dir);
}
manifest_dir.join("vendor/XPMP2")
}
fn xplm_headers_dir(manifest_dir: &std::path::Path) -> PathBuf {
if let Some(dir) = env::var_os("XPLM_SDK_DIR") {
return PathBuf::from(dir).join("CHeaders/XPLM");
}
manifest_dir.join("vendor/xplm-sdk/CHeaders/XPLM")
}
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let xpmp2_dir = xpmp2_dir(&manifest_dir);
let xpmp2_inc = xpmp2_dir.join("inc");
let xpmp2_src = xpmp2_dir.join("src");
let xplm_headers = xplm_headers_dir(&manifest_dir);
let shim_dir = manifest_dir.join("shim");
if !xpmp2_inc.join("XPMPMultiplayer.h").is_file() {
panic!(
"XPMP2 source not found/initialized (expected {}). Run \
`git submodule update --init xpmp2-sys/vendor/XPMP2`, or set \
XPMP2_SRC_DIR to point at your own checkout.",
xpmp2_inc.display()
);
}
if !xplm_headers.join("XPLMDefs.h").is_file() {
panic!(
"XPLM headers not found (expected {}) — see README.md's \
\"Getting the SDK\" section; xpmp2-sys needs a copy for the \
same reason xplm-sys does (XPMP2's own headers include \
XPLM's), or set XPLM_SDK_DIR to point at your own SDK checkout.",
xplm_headers.display()
);
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mut build = cc::Build::new();
build
.cpp(true)
.std("c++17")
.include(&xpmp2_inc)
.include(&xpmp2_src)
.include(&xplm_headers)
.include(&shim_dir)
.define("XPLM200", "1")
.define("XPLM210", "1")
.define("XPLM300", "1")
.define("XPLM301", "1")
.define("XPLM303", "1")
.define("XPLM400", "1")
.define("XPMP2_VERSION", "\"3.6.1\"")
.define("XPMP2_VER_MAJOR", "3")
.define("XPMP2_VER_MINOR", "6")
.define("XPMP2_VER_PATCH", "1");
match target_os.as_str() {
"windows" => {
build
.define("IBM", "1")
.define("APL", "0")
.define("LIN", "0")
.define("_WIN32_WINNT", "0x0601")
.define("_CRT_SECURE_NO_WARNINGS", None)
.flag_if_supported("/EHsc");
}
"linux" => {
build
.define("LIN", "1")
.define("IBM", "0")
.define("APL", "0")
.flag_if_supported("-fexceptions")
.flag_if_supported("-Wno-deprecated-declarations");
}
"macos" => {
build
.define("APL", "1")
.define("IBM", "0")
.define("LIN", "0")
.flag_if_supported("-fexceptions")
.flag_if_supported("-Wno-deprecated-declarations");
}
other => panic!("unsupported target_os for XPMP2: {other}"),
}
for source in SOURCES {
build.file(xpmp2_src.join(source));
}
build.file(shim_dir.join("shim.cpp"));
build.compile("xpmp2");
if target_os == "windows" {
println!("cargo:rustc-link-lib=dylib=Ws2_32");
println!("cargo:rustc-link-lib=dylib=Iphlpapi");
}
let mut builder = bindgen::Builder::default()
.header(xpmp2_inc.join("XPMPMultiplayer.h").to_str().unwrap())
.header(shim_dir.join("shim.h").to_str().unwrap())
.clang_arg("-x")
.clang_arg("c++")
.clang_arg("-std=c++17")
.clang_arg(format!("-I{}", xpmp2_inc.display()))
.clang_arg(format!("-I{}", xplm_headers.display()))
.clang_arg(format!("-I{}", shim_dir.display()))
.clang_arg("-DXPLM200=1")
.clang_arg("-DXPLM210=1")
.clang_arg("-DXPLM300=1")
.clang_arg("-DXPLM301=1")
.clang_arg("-DXPLM303=1")
.clang_arg("-DXPLM400=1")
.allowlist_file(format!(
"{}.*",
regex_escape(&xpmp2_inc.join("XPMPMultiplayer.h").to_string_lossy())
))
.allowlist_file(format!(
"{}.*",
regex_escape(&shim_dir.join("shim.h").to_string_lossy())
))
.blocklist_function("XPMPSoundGetAudioDeviceName")
.blocklist_function("XPMPSoundSetAudioDeviceName")
.blocklist_function("XPMPGetModelInfo2")
.blocklist_function("XPMPAddModelDataRef")
.blocklist_function("XPMPGetAircraft")
.blocklist_function("XPMPSoundGetActiveAudioDevice")
.blocklist_type("std::.*")
.opaque_type("std::.*")
.layout_tests(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
builder = match target_os.as_str() {
"windows" => {
let tool = cc::Build::new().get_compiler();
for (key, val) in tool.env() {
if key == "INCLUDE" {
for path in env::split_paths(val).filter(|p| !p.as_os_str().is_empty()) {
builder = builder.clang_arg(format!("-I{}", path.display()));
}
}
}
builder
.clang_arg("-DIBM=1")
.clang_arg("-DAPL=0")
.clang_arg("-DLIN=0")
}
"linux" => builder
.clang_arg("-DLIN=1")
.clang_arg("-DIBM=0")
.clang_arg("-DAPL=0"),
"macos" => builder
.clang_arg("-DAPL=1")
.clang_arg("-DIBM=0")
.clang_arg("-DLIN=0"),
_ => unreachable!("checked above"),
};
let bindings = builder
.generate()
.expect("unable to generate xpmp2-sys bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("failed to write bindings.rs");
println!("cargo:rerun-if-changed={}", xpmp2_inc.display());
println!("cargo:rerun-if-changed={}", xpmp2_src.display());
println!("cargo:rerun-if-changed={}", shim_dir.display());
}