diff --git a/crates/spirv-builder/src/lib.rs b/crates/spirv-builder/src/lib.rs index 56995fb488a..1cc3449d6cf 100644 --- a/crates/spirv-builder/src/lib.rs +++ b/crates/spirv-builder/src/lib.rs @@ -83,10 +83,12 @@ use serde::Deserialize; use std::borrow::Borrow; use std::collections::HashMap; use std::env; +use std::ffi::OsStr; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::process::Stdio; +use std::time::SystemTime; use thiserror::Error; #[cfg(feature = "watch")] @@ -805,20 +807,71 @@ fn dylib_path() -> Vec { dylibs } +fn rustc_codegen_spirv_dylib_name() -> String { + format!( + "{}rustc_codegen_spirv{}", + env::consts::DLL_PREFIX, + env::consts::DLL_SUFFIX + ) +} + +fn find_latest_hashed_rustc_codegen_spirv_in_dir(dir: &Path) -> Option { + let prefix = format!("{}rustc_codegen_spirv-", env::consts::DLL_PREFIX); + let suffix = env::consts::DLL_SUFFIX; + let mut best_match: Option<(SystemTime, PathBuf)> = None; + + for entry in std::fs::read_dir(dir).ok()?.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + + let Some(name) = path.file_name().and_then(OsStr::to_str) else { + continue; + }; + if !name.starts_with(&prefix) || !name.ends_with(suffix) { + continue; + } + + let modified = entry + .metadata() + .ok() + .and_then(|metadata| metadata.modified().ok()) + .unwrap_or(SystemTime::UNIX_EPOCH); + match &mut best_match { + Some((best_modified, best_path)) if modified < *best_modified => {} + Some((best_modified, best_path)) => { + *best_modified = modified; + *best_path = path; + } + None => best_match = Some((modified, path)), + } + } + + best_match.map(|(_, path)| path) +} + +fn find_rustc_codegen_spirv_in_paths(dylib_paths: Vec) -> Option { + let exact_name = rustc_codegen_spirv_dylib_name(); + + for dir in &dylib_paths { + let path = dir.join(&exact_name); + if path.is_file() { + return Some(path); + } + } + + dylib_paths + .into_iter() + .find_map(|dir| find_latest_hashed_rustc_codegen_spirv_in_dir(&dir)) +} + fn find_rustc_codegen_spirv() -> Result { if cfg!(feature = "rustc_codegen_spirv") { - let filename = format!( - "{}rustc_codegen_spirv{}", - env::consts::DLL_PREFIX, - env::consts::DLL_SUFFIX - ); - let dylib_paths = dylib_path(); - for mut path in dylib_paths { - path.push(&filename); - if path.is_file() { - return Ok(path); - } + if let Some(path) = find_rustc_codegen_spirv_in_paths(dylib_path()) { + return Ok(path); } + let filename = rustc_codegen_spirv_dylib_name(); panic!("Could not find {filename} in library path"); } else { Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib) diff --git a/crates/spirv-builder/src/tests.rs b/crates/spirv-builder/src/tests.rs index 1550a357ba2..6b37072be07 100644 --- a/crates/spirv-builder/src/tests.rs +++ b/crates/spirv-builder/src/tests.rs @@ -18,3 +18,87 @@ mod clap { }; } } + +mod dylib_lookup { + use crate::{find_rustc_codegen_spirv_in_paths, rustc_codegen_spirv_dylib_name}; + use std::fs; + use std::path::{Path, PathBuf}; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + struct TempDir(PathBuf); + + impl TempDir { + fn new(test_name: &str) -> Self { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::ZERO) + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "spirv-builder-{test_name}-{}-{unique}", + std::process::id() + )); + fs::create_dir_all(&path).unwrap(); + Self(path) + } + + fn path(&self) -> &Path { + &self.0 + } + } + + impl Drop for TempDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + + #[test] + fn finds_exact_backend_dylib() { + let dir = TempDir::new("exact"); + let exact = dir.path().join(rustc_codegen_spirv_dylib_name()); + fs::File::create(&exact).expect("failed to create exact backend dylib file"); + + assert_eq!( + find_rustc_codegen_spirv_in_paths(vec![dir.path().to_path_buf()]), + Some(exact) + ); + } + + #[test] + fn falls_back_to_hashed_backend_dylib() { + let dir = TempDir::new("hashed"); + let hashed = dir.path().join(format!( + "{}rustc_codegen_spirv-deadbeef{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + )); + fs::File::create(&hashed).expect("failed to create hashed backend dylib file"); + + assert_eq!( + find_rustc_codegen_spirv_in_paths(vec![dir.path().to_path_buf()]), + Some(hashed) + ); + } + + #[test] + fn prefers_exact_match_over_hashed_match_in_earlier_dir() { + let hashed_dir = TempDir::new("hashed-first"); + let exact_dir = TempDir::new("exact-second"); + let hashed = hashed_dir.path().join(format!( + "{}rustc_codegen_spirv-deadbeef{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + )); + let exact = exact_dir.path().join(rustc_codegen_spirv_dylib_name()); + fs::File::create(&hashed).expect("failed to create hashed backend dylib file"); + fs::File::create(&exact).expect("failed to create exact backend dylib file"); + + assert_eq!( + find_rustc_codegen_spirv_in_paths(vec![ + hashed_dir.path().to_path_buf(), + exact_dir.path().to_path_buf() + ]), + Some(exact) + ); + } +}