use std::env;
#[cfg(feature = "fetch-spec")]
use std::io::Read;
use std::path::PathBuf;
#[cfg(feature = "fetch-spec")]
const KHRONOS_RAW_BASE: &str = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs";
#[cfg(feature = "fetch-spec")]
const VK_XML_REPO_PATHS: &[&str] = &[
"xml/vk.xml", "src/spec/vk.xml", ];
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=VK_XML_PATH");
println!("cargo:rerun-if-env-changed=VK_VERSION");
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
println!("cargo:rustc-env=BUILD_TIMESTAMP={}", timestamp);
let out_dir = env::var("OUT_DIR")?;
let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
let output_path = PathBuf::from(&out_dir).join("vulkan_bindings.rs");
let xml_path = resolve_vk_xml(&manifest_dir, &out_dir)?;
println!("cargo:rerun-if-changed={}", xml_path.display());
vulkan_gen::generate_bindings(&xml_path, &output_path)?;
println!(
"cargo:rustc-env=SPOCK_GENERATED_BINDINGS={}",
output_path.display()
);
println!("Generated Vulkan bindings from {}", xml_path.display());
Ok(())
}
fn resolve_vk_xml(
manifest_dir: &str,
#[cfg_attr(not(feature = "fetch-spec"), allow(unused))] out_dir: &str,
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
if let Ok(env_path) = env::var("VK_XML_PATH") {
let path = PathBuf::from(&env_path);
if path.exists() {
println!("Using vk.xml from VK_XML_PATH: {}", path.display());
return Ok(path);
}
let workspace_relative = PathBuf::from(manifest_dir).join("..").join(&env_path);
if workspace_relative.exists() {
println!(
"Using vk.xml from VK_XML_PATH (workspace-relative): {}",
workspace_relative.display()
);
return Ok(workspace_relative);
}
return Err(format!(
"VK_XML_PATH is set to '{}' but the file does not exist \
(tried absolute and relative to workspace root)",
env_path
)
.into());
}
let local_path = PathBuf::from(manifest_dir).join("../spec/registry/Vulkan-Docs/xml/vk.xml");
if local_path.exists() {
println!("Using local vk.xml: {}", local_path.display());
return Ok(local_path);
}
#[cfg(feature = "fetch-spec")]
{
let version = env::var("VK_VERSION").ok();
download_vk_xml(out_dir, version.as_deref())
}
#[cfg(not(feature = "fetch-spec"))]
{
Err("vk.xml not found. Provide it via one of:\n\
\x20 1. Set VK_XML_PATH environment variable to the path of your vk.xml\n\
\x20 2. Place the Vulkan-Docs repo at ../spec/registry/Vulkan-Docs/\n\
\x20 3. Enable the `fetch-spec` feature to auto-download:\n\
\x20 cargo build -p vulkane --features fetch-spec\n\
\x20 VK_VERSION=1.3.250 cargo build -p vulkane --features fetch-spec"
.into())
}
}
#[cfg(feature = "fetch-spec")]
fn download_vk_xml(
out_dir: &str,
version: Option<&str>,
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
let cache_filename = match version {
Some(v) => format!("vk-{}.xml", v),
None => "vk.xml".to_string(),
};
let cached_path = PathBuf::from(out_dir).join(&cache_filename);
if cached_path.exists() {
match version {
Some(v) => {
println!(
"Using cached vk.xml for version {}: {}",
v,
cached_path.display()
);
return Ok(cached_path);
}
None => {
if let Ok(metadata) = std::fs::metadata(&cached_path) {
if let Ok(modified) = metadata.modified() {
let age = std::time::SystemTime::now()
.duration_since(modified)
.unwrap_or_default();
if age.as_secs() < 86400 {
println!(
"Using cached vk.xml ({}h old): {}",
age.as_secs() / 3600,
cached_path.display()
);
return Ok(cached_path);
}
}
}
}
}
}
let git_ref = match version {
Some(v) => format!("refs/tags/v{}", v),
None => "refs/heads/main".to_string(),
};
let label = version.unwrap_or("latest");
println!("Downloading vk.xml ({})...", label);
let mut last_error = String::new();
for repo_path in VK_XML_REPO_PATHS {
let url = format!("{}/{}/{}", KHRONOS_RAW_BASE, git_ref, repo_path);
println!(" Trying {}...", url);
match ureq::get(&url).call() {
Ok(response) => {
let mut content = Vec::new();
response.into_body().as_reader().read_to_end(&mut content)?;
let content_str = std::str::from_utf8(&content)?;
if !content_str.contains("<registry>") {
last_error = format!("{} returned non-XML content", url);
continue;
}
std::fs::write(&cached_path, &content)?;
if let Some(ver_line) = content_str.lines().find(|l| {
l.contains("VK_HEADER_VERSION")
&& !l.contains("COMPLETE")
&& l.contains("#define")
}) {
println!(" {}", ver_line.trim());
}
println!(" Cached to {}", cached_path.display());
return Ok(cached_path);
}
Err(e) => {
last_error = format!("{}: {}", url, e);
continue;
}
}
}
Err(format!(
"Failed to download vk.xml for version '{}'. \
Tried all known repository paths.\n Last error: {}",
label, last_error
)
.into())
}