mod cmd_finder;
use std::env;
use std::env::consts;
use std::fs;
use std::path::{Path, PathBuf};
static SHADERC_STATIC_LIB: &str = "shaderc_combined";
static SHADERC_SHARED_LIB0: &str = "shaderc";
static SHADERC_SHARED_LIB1: &str = "shaderc_shared";
static SHADERC_STATIC_LIB_FILE_UNIX: &str = "libshaderc_combined.a";
static SHADERC_STATIC_LIB_FILE_WIN: &str = "shaderc_combined.lib";
static MIN_VULKAN_SDK_VERSION: u32 = 182;
fn get_apple_sdk_path() -> Option<PathBuf> {
let target = std::env::var("TARGET").unwrap();
use std::process::Command;
let sdk = if target == "x86_64-apple-ios"
|| target == "i386-apple-ios"
|| target == "aarch64-apple-ios-sim"
{
"iphonesimulator"
} else if target == "aarch64-apple-ios"
|| target == "armv7-apple-ios"
|| target == "armv7s-apple-ios"
{
"iphoneos"
} else {
return None;
};
let output = if let Ok(out) = Command::new("xcrun")
.args(["--sdk", sdk, "--show-sdk-path"])
.output()
{
out.stdout
} else {
return None;
};
let prefix_str = std::str::from_utf8(&output).expect("invalid output from `xcrun`");
Some(PathBuf::from(prefix_str.trim_end().to_string()))
}
fn build_shaderc_unix(shaderc_dir: &PathBuf, use_ninja: bool, target_os: &str) -> PathBuf {
let mut config = cmake::Config::new(shaderc_dir);
config
.profile("Release")
.define("CMAKE_INSTALL_LIBDIR", "lib")
.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
.define("ENABLE_SPVREMAPPER", "OFF")
.define("ENABLE_GLSLANG_BINARIES", "OFF")
.define("SHADERC_SKIP_TESTS", "ON")
.define("SPIRV_SKIP_EXECUTABLES", "ON")
.define("SPIRV_WERROR", "OFF");
if use_ninja {
config.generator("Ninja");
}
if target_os == "ios" {
if let Some(path) = get_apple_sdk_path() {
config.define("CMAKE_OSX_SYSROOT", path);
}
}
config.build()
}
fn build_shaderc_msvc(shaderc_dir: &PathBuf) -> PathBuf {
let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
let mut config = cmake::Config::new(shaderc_dir);
config
.profile("Release")
.define("CMAKE_INSTALL_LIBDIR", "lib")
.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
.define("ENABLE_SPVREMAPPER", "OFF")
.define("ENABLE_GLSLANG_BINARIES", "OFF")
.define("SHADERC_SKIP_TESTS", "ON")
.define("SPIRV_SKIP_EXECUTABLES", "ON")
.define("SPIRV_WERROR", "OFF")
.generator("Ninja");
let config = if linkage.contains("crt-static") {
config
.define("CMAKE_C_FLAGS", " /nologo /EHsc /MT")
.define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MT")
.define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MT")
.define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MT")
} else {
config
.define("CMAKE_C_FLAGS", " /nologo /EHsc /MD")
.define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MD")
.define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MD")
.define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MD")
.define("SHADERC_ENABLE_SHARED_CRT", "ON")
};
config.build()
}
fn build_from_source(target_os: &str, target_env: &str) {
let mut finder = cmd_finder::CommandFinder::new();
finder.must_have("cmake");
finder.must_have("git");
finder
.maybe_have("python3")
.or(finder.maybe_have("python"))
.unwrap_or_else(|| {
panic!("Build requires one of `python3` or `python`");
});
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let shaderc_dir = Path::new(&manifest_dir).join("build");
let mut lib_path = if target_env == "msvc" {
finder.must_have("ninja");
build_shaderc_msvc(&shaderc_dir)
} else {
let has_ninja = finder.maybe_have("ninja").is_some();
build_shaderc_unix(&shaderc_dir, has_ninja, target_os)
};
lib_path.push("lib");
println!("cargo:rustc-link-search=native={}", lib_path.display());
println!("cargo:rustc-link-lib=static={SHADERC_STATIC_LIB}");
}
fn check_vulkan_sdk_version(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let xml = std::fs::read_to_string(
path.join("share")
.join("vulkan")
.join("registry")
.join("vk.xml"),
)
.map_err(|error| format!("could not read vk.xml in $VULKAN_SDK: {error}"))?;
let tree = roxmltree::Document::parse(&xml)
.map_err(|error| format!("vk.xml in $VULKAN_SDK is not a valid XML document: {error}"))?;
let version = tree
.root()
.descendants()
.find(|node| node.has_tag_name("types"))
.ok_or("invalid vk.xml in $VULKAN_SDK is invalid: missing <types> node")?
.descendants()
.find(|node| node.text() == Some("VK_HEADER_VERSION"))
.ok_or("invalid vk.xml in $VULKAN_SDK is invalid: missing VK_HEADER_VERSION node")?
.tail()
.ok_or("invalid vk.xml in $VULKAN_SDK: no vesion string")?
.trim()
.parse::<u32>()?;
if version < MIN_VULKAN_SDK_VERSION {
return Err(Box::from(format!(
"requires Vulkan SDK patch version to be at least {MIN_VULKAN_SDK_VERSION}"
)));
}
Ok(())
}
fn host_target() -> String {
let output = std::process::Command::new("rustc")
.arg("-vV")
.output()
.expect("failed to invoke rustc");
std::str::from_utf8(&output.stdout)
.expect("rustc didn't return valid UTF-8")
.lines()
.find_map(|l| l.strip_prefix("host: "))
.expect("failed to query rustc for the host target triple")
.to_owned()
}
fn get_search_dir(target_os: &str, build_from_source: bool) -> Option<String> {
if let Ok(lib_dir) = env::var("SHADERC_LIB_DIR") {
println!("cargo:warning=shaderc: searching native shaderc libraries in '{lib_dir}'");
return Some(lib_dir);
}
if let Ok(sdk_dir) = env::var("VULKAN_SDK") {
check_vulkan_sdk_version(Path::new(&sdk_dir)).unwrap();
println!("cargo:warning=shaderc: searching native shaderc libraries in Vulkan SDK '{sdk_dir}/lib'");
return Some(format!("{sdk_dir}/lib/"));
}
if let Ok(pkg_lib) = pkg_config::Config::new().probe(SHADERC_SHARED_LIB0) {
let pkg_dir = pkg_lib.link_paths[0].as_path().to_string_lossy();
println!("cargo:warning=shaderc: searching native shaderc libraries in '{pkg_dir}' from pkg-config");
return Some(pkg_dir.to_string());
}
if !build_from_source {
println!(
"cargo:warning=shaderc: searching for native shaderc libraries on system; \
use '--features build-from-source' to force building from source code"
);
if target_os == "macos" {
let macos_path = "/usr/local/lib/";
if Path::new(macos_path).exists() {
return Some(macos_path.to_owned());
}
} else if target_os == "linux" {
let debian_arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap() {
arch if arch == "x86" => "i386".to_owned(),
arch => arch,
};
let debian_triple_path = format!("/usr/lib/{debian_arch}-linux-gnu/");
return if Path::new(&debian_triple_path).exists() {
Some(debian_triple_path)
} else if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "x86_64"
&& Path::new("/usr/lib64/").exists()
{
Some("/usr/lib64/".to_owned())
} else {
Some("/usr/lib/".to_owned())
};
}
}
None
}
fn main() {
if env::var("DOCS_RS").is_ok() {
let is_cross_compiling = env::var("TARGET").unwrap() != host_target();
if is_cross_compiling {
println!(
"cargo:warning=shaderc: docs.rs cross-compilation detected, will not attempt to \
link against shaderc",
);
return;
}
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
let config_build_from_source = env::var("CARGO_FEATURE_BUILD_FROM_SOURCE").is_ok();
let config_prefer_static_linking = env::var("CARGO_FEATURE_PREFER_STATIC_LINKING").is_ok();
let has_explicit_set_search_dir = env::var("SHADERC_LIB_DIR").is_ok();
if config_build_from_source {
println!("cargo:warning=shaderc: requested to build from source");
build_from_source(&target_os, &target_env);
return;
}
let search_dir = get_search_dir(&target_os, config_build_from_source).and_then(|search_dir| {
let path = Path::new(&search_dir);
let cannonical = fs::canonicalize(path);
if path.is_relative() {
println!(
"cargo:warning=shaderc: the given search path '{path:?}' is relative; \
path must be relative to shaderc-sys crate, \
likely not your current working directory"
);
} else if !path.is_dir() {
println!("cargo:warning=shaderc: the given search path '{path:?}' is not a directory");
}
if (cannonical.is_err()) && has_explicit_set_search_dir {
println!("cargo:warning=shaderc: {:?}", cannonical.err().unwrap());
println!(
"cargo:warning=shaderc: failed to canonicalize the given search path '{path:?}'"
);
None
} else {
cannonical.ok()
}
});
if let Some(search_dir) = search_dir {
let search_dir_str = search_dir.to_string_lossy();
let static_lib_path = search_dir.join(if target_os == "windows" && target_env == "msvc" {
SHADERC_STATIC_LIB_FILE_WIN
} else {
SHADERC_STATIC_LIB_FILE_UNIX
});
let dylib0_name = format!(
"{}{}{}",
consts::DLL_PREFIX,
SHADERC_SHARED_LIB0,
consts::DLL_SUFFIX
);
let dylib1_name = format!(
"{}{}{}",
consts::DLL_PREFIX,
SHADERC_SHARED_LIB1,
consts::DLL_SUFFIX
);
let dylib0_path = search_dir.join(dylib0_name);
let dylib1_path = search_dir.join(dylib1_name);
if let Some((lib_name, lib_kind)) = {
match (
dylib0_path.exists(),
dylib1_path.exists(),
static_lib_path.exists(),
config_prefer_static_linking,
) {
(false, false, true, _) | (_, _, true, true) => {
Some((SHADERC_STATIC_LIB, "static"))
}
(true, _, _, _) => Some((SHADERC_SHARED_LIB0, "dylib")),
(_, true, _, _) => Some((SHADERC_SHARED_LIB1, "dylib")),
_ => None,
}
} {
match (target_os.as_str(), target_env.as_str()) {
("linux" | "android", _) => {
println!("cargo:rustc-link-search=native={search_dir_str}");
println!("cargo:rustc-link-lib={lib_kind}={lib_name}");
return;
}
("windows", "msvc") => {
println!("cargo:warning=shaderc: Windows MSVC static build is experimental");
println!("cargo:rustc-link-search=native={search_dir_str}");
println!("cargo:rustc-link-lib={lib_kind}={lib_name}");
return;
}
("windows", "gnu") => {
println!("cargo:warning=shaderc: Windows MinGW static build is experimental");
println!("cargo:rustc-link-search=native={search_dir_str}");
println!("cargo:rustc-link-lib={lib_kind}={lib_name}");
return;
}
("macos", _) => {
println!("cargo:warning=shaderc: macOS static build is experimental");
println!("cargo:rustc-link-search=native={search_dir_str}");
println!("cargo:rustc-link-lib={lib_kind}={lib_name}");
return;
}
("ios", _) => {
println!("cargo:warning=shaderc: macOS static build is experimental");
println!("cargo:rustc-link-search=native={search_dir_str}");
println!("cargo:rustc-link-lib={lib_kind}={lib_name}");
return;
}
(_, _) => {
println!(
"cargo:warning=shaderc: unsupported platform for linking against \
native shaderc libraries installed on system"
);
}
}
}
}
println!(
"cargo:warning=shaderc: cannot find native shaderc library on system; \
falling back to build from source"
);
build_from_source(&target_os, &target_env);
}