Skip to content

show-targets #36 #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 3 additions & 82 deletions crates/cargo-gpu/src/install.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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<PathBuf> {
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
Expand Down Expand Up @@ -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");
Expand Down
18 changes: 0 additions & 18 deletions crates/cargo-gpu/src/legacy_target_specs.rs

This file was deleted.

2 changes: 1 addition & 1 deletion crates/cargo-gpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
46 changes: 44 additions & 2 deletions crates/cargo-gpu/src/show.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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`
Expand All @@ -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 => {
Expand All @@ -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(())
Expand All @@ -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<Item = String>)> {
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::<Vec<_>>();
targets.sort();
Ok((source, targets.into_iter()))
}
}
135 changes: 135 additions & 0 deletions crates/cargo-gpu/src/target_specs.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf> {
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)
}