From 35c3ad65e2f6f2f421c3cedc77b7624f20b566e7 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Wed, 1 Apr 2026 21:13:16 -0700 Subject: [PATCH] spirv-builder: fall back to hashed codegen dylibs spirv-builder only looks for the exact librustc_codegen_spirv.{so,dylib,dll} filename in the dylib search path. That works in the rust-gpu workspace, but it misses external build.rs consumers where Cargo only exposes hashed deps artifacts such as librustc_codegen_spirv-.so. This showed up in a downstream workspace update to current rust-gpu main where the consumer path only had hashed backend artifacts available to default SpirvBuilder discovery. Keep preferring the exact filename when present, then fall back to the newest hashed rustc_codegen_spirv dylib in the same search paths. Add tests for exact matches, hashed fallback, and exact-over-hashed precedence. --- crates/spirv-builder/src/lib.rs | 75 +++++++++++++++++++++++---- crates/spirv-builder/src/tests.rs | 84 +++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 11 deletions(-) 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) + ); + } +}