use std::env;
use std::process::Command;
use std::{
fs,
io::ErrorKind::NotFound,
path::{Path, PathBuf},
};
fn main() {
println!("cargo:rerun-if-changed=msvc_version.c");
println!("cargo:rerun-if-changed=mach_dxc.h");
#[cfg(all(feature = "msvc_version_validation", target_env = "msvc"))]
validate_msvc_version();
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("Failed to get OUT_DIR environment"));
let target_url = get_target_url(static_crt());
let file_path = out_dir.join("machdxcompiler.tar.gz");
download_released_library(&target_url, &file_path);
extract_tar_gz(&file_path, &out_dir);
#[cfg(feature = "cbindings")]
generate_bindings();
println!("cargo:rustc-link-lib=static=machdxcompiler");
println!("cargo:rustc-link-search=native={}", out_dir.display());
}
#[cfg(feature = "cbindings")]
fn generate_bindings() {
let bindings = bindgen::Builder::default()
.rust_target(bindgen::RustTarget::Stable_1_73)
.header("mach_dxc.h")
.generate()
.expect("Unable to generate bindings");
let out_path =
PathBuf::from(env::var("OUT_DIR").expect("Failed to get OUT_DIR environment variable"));
bindings
.write_to_file(out_path.join("cbindings.rs"))
.expect("Couldn't write bindings!");
}
#[cfg(all(feature = "msvc_version_validation", target_env = "msvc"))]
fn validate_msvc_version() {
let target = std::env::var("TARGET").expect("Faild to get TARGET environment");
const CL_ARGS: &[&str] = &["/nologo", "/EP", "msvc_version.c"];
const MINIMUM_MSVC_VERSION: usize = 1941;
let status = cc::windows_registry::find(&target, "cl.exe")
.expect("Failed to locate cl.exe.\nPlease ensure it is installed and included in your system's PATH environment variable.")
.args(CL_ARGS)
.output()
.expect(&format!("Failed to run cl.exe with args: {CL_ARGS:?}"));
if status.stdout.is_empty() {
panic!("Output from cl.exe is empty");
}
let output_str = match std::str::from_utf8(&status.stdout) {
Ok(s) => s,
Err(e) => panic!(
"Failed to parse output of cl.exe as utf-8:\nsrc{:?}\nerror:\n{e:?}",
status.stdout
),
};
let version_str = output_str.trim();
let current_version = version_str.parse::<usize>().expect(&format!(
"Failed to parse version from output of cl.exe: {version_str}"
));
if current_version < MINIMUM_MSVC_VERSION {
panic!("Please upgrade the Visual Studio, the minimum supported version of MSVC is {MINIMUM_MSVC_VERSION}, which is correspond to [Visual Studio 2022 version 17.11], but the current version of MSVC is {current_version}\nCheck versions on https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170#version-macros");
}
}
fn get_target_url(static_crt: bool) -> String {
const BASE_URL: &str = "https://github.com/DouglasDwyer/mach-dxcompiler/releases";
const LATEST_RELEASE: &str = "2024.11.22+284d956.1";
const AVAILABLE_TARGETS: &[&str] = &[
"x86_64-linux-gnu",
"x86_64-linux-musl",
"aarch64-linux-gnu",
"aarch64-linux-musl",
"x86_64-windows-gnu",
"x86_64-windows-msvc",
"aarch64-windows-gnu",
"x86_64-macos-none",
"aarch64-macos-none",
];
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Failed to get architecture");
let mut os = env::var("CARGO_CFG_TARGET_OS").expect("Failed to get os");
if env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or_default() == "apple" && os == "darwin" {
os = "macos".to_owned();
}
let mut abi = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if abi.is_empty() {
abi = "none".to_owned();
}
let target = format!("{arch}-{os}-{abi}");
if !AVAILABLE_TARGETS.contains(&target.as_str()) {
panic!("Unsupported target: {target}\nCheck supported targets on {BASE_URL}");
}
let crt = if abi == "msvc" && !static_crt {
"Dynamic_lib"
} else {
"lib"
};
format!("{BASE_URL}/download/{LATEST_RELEASE}/{target}_ReleaseFast_{crt}.tar.gz")
}
fn download_released_library(url: &str, file_path: &Path) {
match Command::new("curl")
.arg("--location")
.arg("-o")
.arg(file_path)
.arg(url)
.spawn()
.expect("Failed to start Curl to download DXC binary")
.wait()
{
Ok(result) => {
if !result.success() {
if let Err(e) = fs::remove_file(file_path) {
if e.kind() != NotFound {
panic!("Failed to remove incomplete file");
}
}
panic!("{result}");
}
}
Err(_) => {
if let Err(e) = fs::remove_file(file_path) {
if e.kind() != NotFound {
panic!("Failed to remove incomplete file");
}
}
panic!("Failed to download DXC binary");
}
}
}
fn extract_tar_gz(path: &Path, output_dir: &Path) {
let result = Command::new("tar")
.current_dir(output_dir)
.arg("-xzf")
.arg(path)
.spawn()
.expect("Failed to start Tar to extract DXC binary")
.wait()
.expect("Failed to extract DXC binary");
if !result.success() {
panic!("{result}");
}
}
fn static_crt() -> bool {
env::var("CARGO_ENCODED_RUSTFLAGS")
.map(|flags| flags.contains("target-feature=+crt-static"))
.unwrap_or(false)
}