diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index 50a34e0..d7a6894 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -1,12 +1,11 @@ //! Install a dedicated per-shader crate that has the `rust-gpu` compiler in it. -use crate::legacy_target_specs::write_legacy_target_specs; use crate::spirv_source::{ get_channel_from_rustc_codegen_spirv_build_script, query_metadata, FindPackage as _, }; +use crate::target_specs::update_target_specs_files; use crate::{cache_dir, spirv_source::SpirvSource}; use anyhow::Context as _; -use cargo_metadata::Metadata; use spirv_builder::SpirvBuilder; use std::path::{Path, PathBuf}; @@ -196,83 +195,6 @@ package = "rustc_codegen_spirv" Ok(()) } - /// Copy spec files from one dir to another, assuming no subdirectories - fn copy_spec_files(src: &Path, dst: &Path) -> anyhow::Result<()> { - std::fs::create_dir_all(dst)?; - let dir = std::fs::read_dir(src)?; - for dir_entry in dir { - let file = dir_entry?; - let file_path = file.path(); - if file_path.is_file() { - std::fs::copy(file_path, dst.join(file.file_name()))?; - } - } - Ok(()) - } - - /// Add the target spec files to the crate. - fn update_spec_files( - source: &SpirvSource, - install_dir: &Path, - dummy_metadata: &Metadata, - skip_rebuild: bool, - ) -> anyhow::Result { - let mut target_specs_dst = install_dir.join("target-specs"); - if !skip_rebuild { - if let Ok(target_specs) = - dummy_metadata.find_package("rustc_codegen_spirv-target-specs") - { - log::info!( - "target-specs: found crate `rustc_codegen_spirv-target-specs` with manifest at `{}`", - target_specs.manifest_path - ); - - let target_specs_src = target_specs - .manifest_path - .as_std_path() - .parent() - .and_then(|root| { - let src = root.join("target-specs"); - src.is_dir().then_some(src) - }) - .context("Could not find `target-specs` directory within `rustc_codegen_spirv-target-specs` dependency")?; - if source.is_path() { - // skip copy - log::info!( - "target-specs: source is local path, use target-specs from `{}`", - target_specs_src.display() - ); - target_specs_dst = target_specs_src; - } else { - // copy over the target-specs - log::info!( - "target-specs: Copy target specs from `{}`", - target_specs_src.display() - ); - Self::copy_spec_files(&target_specs_src, &target_specs_dst) - .context("copying target-specs json files")?; - } - } else { - // use legacy target specs bundled with cargo gpu - if source.is_path() { - // This is a stupid situation: - // * We can't be certain that there are `target-specs` in the local checkout (there may be some in `spirv-builder`) - // * We can't dump our legacy ones into the `install_dir`, as that would modify the local rust-gpu checkout - // -> do what the old cargo gpu did, one global dir for all target specs - // and hope parallel runs don't shred each other - target_specs_dst = cache_dir()?.join("legacy-target-specs-for-local-checkout"); - } - log::info!( - "target-specs: Writing legacy target specs to `{}`", - target_specs_dst.display() - ); - write_legacy_target_specs(&target_specs_dst)?; - } - } - - Ok(target_specs_dst) - } - /// Install the binary pair and return the [`InstalledBackend`], from which you can create [`SpirvBuilder`] instances. /// /// # Errors @@ -338,9 +260,8 @@ package = "rustc_codegen_spirv" log::info!("selected toolchain channel `{toolchain_channel:?}`"); log::debug!("update_spec_files"); - let target_spec_dir = - Self::update_spec_files(&source, &install_dir, &dummy_metadata, skip_rebuild) - .context("writing target spec files")?; + let target_spec_dir = update_target_specs_files(&source, &dummy_metadata, !skip_rebuild) + .context("writing target spec files")?; if !skip_rebuild { log::debug!("ensure_toolchain_and_components_exist"); diff --git a/crates/cargo-gpu/src/legacy_target_specs.rs b/crates/cargo-gpu/src/legacy_target_specs.rs deleted file mode 100644 index 796b0e3..0000000 --- a/crates/cargo-gpu/src/legacy_target_specs.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Legacy target specs are spec jsons for versions before `rustc_codegen_spirv-target-specs` -//! came bundled with them. Instead, cargo gpu needs to bundle these legacy spec files. Luckily, -//! they are the same for all versions, as bundling target specs with the codegen backend was -//! introduced before the first target spec update. - -use anyhow::Context as _; -use std::path::Path; - -/// Extract legacy target specs from our executable into some directory -pub fn write_legacy_target_specs(target_spec_dir: &Path) -> anyhow::Result<()> { - std::fs::create_dir_all(target_spec_dir)?; - for (filename, contents) in legacy_target_specs::TARGET_SPECS { - let path = target_spec_dir.join(filename); - std::fs::write(&path, contents.as_bytes()) - .with_context(|| format!("writing legacy target spec file at [{}]", path.display()))?; - } - Ok(()) -} diff --git a/crates/cargo-gpu/src/lib.rs b/crates/cargo-gpu/src/lib.rs index b3adabf..323743b 100644 --- a/crates/cargo-gpu/src/lib.rs +++ b/crates/cargo-gpu/src/lib.rs @@ -61,12 +61,12 @@ mod config; mod dump_usage; mod install; mod install_toolchain; -mod legacy_target_specs; mod linkage; mod lockfile; mod metadata; mod show; mod spirv_source; +mod target_specs; mod test; pub use install::*; diff --git a/crates/cargo-gpu/src/show.rs b/crates/cargo-gpu/src/show.rs index 265b126..b7eb19b 100644 --- a/crates/cargo-gpu/src/show.rs +++ b/crates/cargo-gpu/src/show.rs @@ -1,6 +1,11 @@ //! Display various information about `cargo gpu`, eg its cache directory. use crate::cache_dir; +use crate::spirv_source::{query_metadata, SpirvSource}; +use crate::target_specs::update_target_specs_files; +use anyhow::bail; +use std::fs; +use std::path::Path; /// Show the computed source of the spirv-std dependency. #[derive(Clone, Debug, clap::Parser)] @@ -21,6 +26,9 @@ pub enum Info { Commitsh, /// All the available SPIR-V capabilities that can be set with `--capabilities` Capabilities, + + /// All available SPIR-V targets + Targets(SpirvSourceDep), } /// `cargo gpu show` @@ -46,8 +54,7 @@ impl Show { println!("{}\n", cache_dir()?.display()); } Info::SpirvSource(SpirvSourceDep { shader_crate }) => { - let rust_gpu_source = - crate::spirv_source::SpirvSource::get_rust_gpu_deps_from_shader(shader_crate)?; + let rust_gpu_source = SpirvSource::get_rust_gpu_deps_from_shader(shader_crate)?; println!("{rust_gpu_source}\n"); } Info::Commitsh => { @@ -63,6 +70,13 @@ impl Show { println!(" {capability:?}"); } } + Info::Targets(SpirvSourceDep { shader_crate }) => { + let (source, targets) = Self::available_spirv_targets_iter(shader_crate)?; + println!("All available targets for rust-gpu version '{source}':"); + for target in targets { + println!("{target}"); + } + } } Ok(()) @@ -76,4 +90,32 @@ impl Show { let last_capability = spirv_builder::Capability::CacheControlsINTEL as u32; (0..=last_capability).filter_map(spirv_builder::Capability::from_u32) } + + /// List all available spirv targets, note: the targets from compile time of cargo-gpu and those + /// in the cache-directory will be picked up. + fn available_spirv_targets_iter( + shader_crate: &Path, + ) -> anyhow::Result<(SpirvSource, impl Iterator)> { + let source = SpirvSource::new(shader_crate, None, None)?; + let install_dir = source.install_dir()?; + if !install_dir.is_dir() { + bail!("rust-gpu version {} is not installed", source); + } + let dummy_metadata = query_metadata(&install_dir)?; + let target_specs_dir = update_target_specs_files(&source, &dummy_metadata, false)?; + + let mut targets = fs::read_dir(target_specs_dir)? + .filter_map(|entry| { + let file = entry.ok()?; + if file.path().is_file() { + if let Some(target) = file.file_name().to_string_lossy().strip_suffix(".json") { + return Some(target.to_owned()); + } + } + None + }) + .collect::>(); + targets.sort(); + Ok((source, targets.into_iter())) + } } diff --git a/crates/cargo-gpu/src/target_specs.rs b/crates/cargo-gpu/src/target_specs.rs new file mode 100644 index 0000000..00deeef --- /dev/null +++ b/crates/cargo-gpu/src/target_specs.rs @@ -0,0 +1,135 @@ +//! This module deals with target specs, which are json metadata files that need to be passed to +//! rustc to add foreign targets such as `spirv_unknown_vulkan1.2`. +//! +//! There are 4 version ranges of `rustc_codegen_spirv` and they all need different handling of +//! their target specs: +//! * "ancient" versions such as 0.9.0 or earlier do not need target specs, just passing the target +//! string (`spirv-unknown-vulkan1.2`) directly is sufficient. We still prep target-specs for them +//! like the "legacy" variant below, spirv-builder +//! [will just ignore it](https://github.com/Rust-GPU/rust-gpu/blob/369122e1703c0c32d3d46f46fa11ccf12667af03/crates/spirv-builder/src/lib.rs#L987) +//! * "legacy" versions require target specs to compile, which is a requirement introduced by some +//! rustc version. Back then it was decided that cargo gpu would ship them, as they'd probably +//! never change, right? So now we're stuck with having to ship these "legacy" target specs with +//! cargo gpu *forever*. These are the symbol `legacy_target_specs::TARGET_SPECS`, with +//! `legacy_target_specs` being a **fixed** version of `rustc_codegen_spirv-target-specs`, +//! which must **never** update. +//! * As of [PR 256](https://github.com/Rust-GPU/rust-gpu/pull/256), `rustc_codegen_spirv` now has +//! a direct dependency on `rustc_codegen_spirv-target-specs`, allowing cargo gpu to pull the +//! required target specs directly from that dependency. At this point, the target specs are +//! still the same as the legacy target specs. +//! * The [edition 2024 PR](https://github.com/Rust-GPU/rust-gpu/pull/249) must update the +//! target specs to comply with newly added validation within rustc. This is why the new system +//! was implemented, so we can support both old and new target specs without having to worry +//! which version of cargo gpu you are using. It'll "just work". + +use crate::cache_dir; +use crate::spirv_source::{FindPackage as _, SpirvSource}; +use anyhow::Context as _; +use cargo_metadata::Metadata; +use std::path::{Path, PathBuf}; + +/// Extract legacy target specs from our executable into some directory +pub fn write_legacy_target_specs(target_spec_dir: &Path) -> anyhow::Result<()> { + std::fs::create_dir_all(target_spec_dir)?; + for (filename, contents) in legacy_target_specs::TARGET_SPECS { + let path = target_spec_dir.join(filename); + std::fs::write(&path, contents.as_bytes()) + .with_context(|| format!("writing legacy target spec file at [{}]", path.display()))?; + } + Ok(()) +} + +/// Copy spec files from one dir to another, assuming no subdirectories +fn copy_spec_files(src: &Path, dst: &Path) -> anyhow::Result<()> { + std::fs::create_dir_all(dst)?; + let dir = std::fs::read_dir(src)?; + for dir_entry in dir { + let file = dir_entry?; + let file_path = file.path(); + if file_path.is_file() { + std::fs::copy(file_path, dst.join(file.file_name()))?; + } + } + Ok(()) +} + +/// Computes the `target-specs` directory to use and updates the target spec files, if enabled. +pub fn update_target_specs_files( + source: &SpirvSource, + dummy_metadata: &Metadata, + update_files: bool, +) -> anyhow::Result { + log::info!( + "target-specs: Resolving target specs `{}`", + if update_files { + "and update them" + } else { + "without updating" + } + ); + + let mut target_specs_dst = source.install_dir()?.join("target-specs"); + if let Ok(target_specs) = dummy_metadata.find_package("rustc_codegen_spirv-target-specs") { + log::info!( + "target-specs: found crate `rustc_codegen_spirv-target-specs` with manifest at `{}`", + target_specs.manifest_path + ); + + let target_specs_src = target_specs + .manifest_path + .as_std_path() + .parent() + .and_then(|root| { + let src = root.join("target-specs"); + src.is_dir().then_some(src) + }) + .context("Could not find `target-specs` directory within `rustc_codegen_spirv-target-specs` dependency")?; + log::info!( + "target-specs: found `rustc_codegen_spirv-target-specs` with `target-specs` directory `{}`", + target_specs_dst.display() + ); + + if source.is_path() { + // skip copy + log::info!( + "target-specs resolution: source is local path, use target-specs directly from `{}`", + target_specs_dst.display() + ); + target_specs_dst = target_specs_src; + } else { + // copy over the target-specs + log::info!( + "target-specs resolution: coping target-specs from `{}`{}", + target_specs_dst.display(), + if update_files { "" } else { " was skipped" } + ); + if update_files { + copy_spec_files(&target_specs_src, &target_specs_dst) + .context("copying target-specs json files")?; + } + } + } else { + // use legacy target specs bundled with cargo gpu + if source.is_path() { + // This is a stupid situation: + // * We can't be certain that there are `target-specs` in the local checkout (there may be some in `spirv-builder`) + // * We can't dump our legacy ones into the `install_dir`, as that would modify the local rust-gpu checkout + // -> do what the old cargo gpu did, one global dir for all target specs + // and hope parallel runs don't shred each other + target_specs_dst = cache_dir()?.join("legacy-target-specs-for-local-checkout"); + } + log::info!( + "target-specs resolution: legacy target specs in directory `{}`", + target_specs_dst.display() + ); + if update_files { + log::info!( + "target-specs: Writing legacy target specs into `{}`", + target_specs_dst.display() + ); + write_legacy_target_specs(&target_specs_dst)?; + } + } + + Ok(target_specs_dst) +}