Skip to content

Commit 19d6e05

Browse files
committed
make cargo gpu a library
1 parent a048eb5 commit 19d6e05

File tree

2 files changed

+215
-211
lines changed

2 files changed

+215
-211
lines changed

crates/cargo-gpu/src/lib.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//! Rust GPU shader crate builder.
2+
//!
3+
//! This program and library allows you to easily compile your rust-gpu shaders,
4+
//! without requiring you to fix your entire project to a specific toolchain.
5+
//!
6+
//! # How it works
7+
//!
8+
//! This program primarily manages installations of `rustc_codegen_spirv`, the
9+
//! codegen backend of rust-gpu to generate SPIR-V shader binaries. The codegen
10+
//! backend builds on internal, ever-changing interfaces of rustc, which requires
11+
//! fixing a version of rust-gpu to a specific version of the rustc compiler.
12+
//! Usually, this would require you to fix your entire project to that specific
13+
//! toolchain, but this project loosens that requirement by managing installations
14+
//! of `rustc_codegen_spirv` and their associated toolchains for you.
15+
//!
16+
//! We continue to use rust-gpu's `spirv_builder` crate to pass the many additional
17+
//! parameters required to configure rustc and our codegen backend, but provide you
18+
//! with a toolchain agnostic version that you may use from stable rustc. And a
19+
//! `cargo gpu` cmdline utility to simplify shader building even more.
20+
//!
21+
//! ## Where the binaries are
22+
//!
23+
//! We store our prebuild `rustc_spirv_builder` binaries in the default cache
24+
//! directory of your OS:
25+
//! * Windows: `C:/users/<user>/AppData/Local/rust-gpu`
26+
//! * Mac: `~/Library/Caches/rust-gpu`
27+
//! * Linux: `~/.cache/rust-gpu`
28+
//!
29+
//! ## How we build the backend
30+
//!
31+
//! * retrieve the version of rust-gpu you want to use based on the version of the
32+
//! `spirv-std` dependency in your shader crate.
33+
//! * create a dummy project at `<cache_dir>/codegen/<version>/` that depends on
34+
//! `rustc_codegen_spirv`
35+
//! * use `cargo metadata` to `cargo update` the dummy project, which downloads the
36+
//! `rustc_codegen_spirv` crate into cargo's cache, and retrieve the path to the
37+
//! download location.
38+
//! * search for the required toolchain in `build.rs` of `rustc_codegen_spirv`
39+
//! * build it with the required toolchain version
40+
//! * copy out the binary and clean the target dir
41+
//!
42+
//! ## Building shader crates
43+
//!
44+
//! `cargo-gpu` takes a path to a shader crate to build, as well as a path to a directory
45+
//! to put the compiled `spv` source files. It also takes a path to an output manifest
46+
//! file where all shader entry points will be mapped to their `spv` source files. This
47+
//! manifest file can be used by build scripts (`build.rs` files) to generate linkage or
48+
//! conduct other post-processing, like converting the `spv` files into `wgsl` files,
49+
//! for example.
50+
51+
use anyhow::Context as _;
52+
53+
use build::Build;
54+
use install::Install;
55+
use show::Show;
56+
57+
pub mod args;
58+
pub mod build;
59+
pub mod config;
60+
pub mod install;
61+
pub mod install_toolchain;
62+
pub mod linkage;
63+
pub mod lockfile;
64+
pub mod metadata;
65+
pub mod show;
66+
pub mod spirv_source;
67+
68+
/// Central function to write to the user.
69+
#[macro_export]
70+
macro_rules! user_output {
71+
($($args: tt)*) => {
72+
#[allow(
73+
clippy::allow_attributes,
74+
clippy::useless_attribute,
75+
unused_imports,
76+
reason = "`std::io::Write` is only sometimes called??"
77+
)]
78+
use std::io::Write as _;
79+
80+
#[expect(
81+
clippy::non_ascii_literal,
82+
reason = "CRAB GOOD. CRAB IMPORTANT."
83+
)]
84+
{
85+
print!("🦀 ");
86+
}
87+
print!($($args)*);
88+
std::io::stdout().flush().unwrap();
89+
}
90+
}
91+
92+
/// All of the available subcommands for `cargo gpu`
93+
#[derive(clap::Subcommand)]
94+
pub enum Command {
95+
/// Install rust-gpu compiler artifacts.
96+
Install(Box<Install>),
97+
98+
/// Compile a shader crate to SPIR-V.
99+
Build(Box<Build>),
100+
101+
/// Show some useful values.
102+
Show(Show),
103+
104+
/// A hidden command that can be used to recursively print out all the subcommand help messages:
105+
/// `cargo gpu dump-usage`
106+
/// Useful for updating the README.
107+
#[clap(hide(true))]
108+
DumpUsage,
109+
}
110+
111+
/// The main cli args of `cargo gpu`
112+
#[derive(clap::Parser)]
113+
#[clap(author, version, about, subcommand_required = true)]
114+
pub struct Cli {
115+
/// The command to run.
116+
#[clap(subcommand)]
117+
pub command: Command,
118+
}
119+
120+
/// returns the path to the cache dir of cargo gpu
121+
pub fn cache_dir() -> anyhow::Result<std::path::PathBuf> {
122+
let dir = directories::BaseDirs::new()
123+
.with_context(|| "could not find the user home directory")?
124+
.cache_dir()
125+
.join("rust-gpu");
126+
127+
Ok(if cfg!(test) {
128+
let thread_id = std::thread::current().id();
129+
let id = format!("{thread_id:?}").replace('(', "-").replace(')', "");
130+
dir.join("tests").join(id)
131+
} else {
132+
dir
133+
})
134+
}
135+
136+
/// Location of the target spec metadata files
137+
pub fn target_spec_dir() -> anyhow::Result<std::path::PathBuf> {
138+
let dir = cache_dir()?.join("target-specs");
139+
std::fs::create_dir_all(&dir)?;
140+
Ok(dir)
141+
}
142+
143+
/// Returns a string suitable to use as a directory.
144+
///
145+
/// Created from the spirv-builder source dep and the rustc channel.
146+
fn to_dirname(text: &str) -> String {
147+
text.replace(
148+
[std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='],
149+
"_",
150+
)
151+
.split(['{', '}', ' ', '\n', '"', '\''])
152+
.collect::<Vec<_>>()
153+
.concat()
154+
}
155+
156+
#[cfg(test)]
157+
mod test {
158+
use crate::cache_dir;
159+
use std::io::Write as _;
160+
161+
fn copy_dir_all(
162+
src: impl AsRef<std::path::Path>,
163+
dst: impl AsRef<std::path::Path>,
164+
) -> anyhow::Result<()> {
165+
std::fs::create_dir_all(&dst)?;
166+
for maybe_entry in std::fs::read_dir(src)? {
167+
let entry = maybe_entry?;
168+
let ty = entry.file_type()?;
169+
if ty.is_dir() {
170+
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
171+
} else {
172+
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
173+
}
174+
}
175+
Ok(())
176+
}
177+
178+
pub fn shader_crate_template_path() -> std::path::PathBuf {
179+
let project_base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
180+
project_base.join("../shader-crate-template")
181+
}
182+
183+
pub fn shader_crate_test_path() -> std::path::PathBuf {
184+
let shader_crate_path = crate::cache_dir().unwrap().join("shader_crate");
185+
copy_dir_all(shader_crate_template_path(), shader_crate_path.clone()).unwrap();
186+
shader_crate_path
187+
}
188+
189+
pub fn overwrite_shader_cargo_toml(shader_crate_path: &std::path::Path) -> std::fs::File {
190+
let cargo_toml = shader_crate_path.join("Cargo.toml");
191+
let mut file = std::fs::OpenOptions::new()
192+
.write(true)
193+
.truncate(true)
194+
.open(cargo_toml)
195+
.unwrap();
196+
writeln!(file, "[package]").unwrap();
197+
writeln!(file, "name = \"test\"").unwrap();
198+
file
199+
}
200+
201+
pub fn tests_teardown() {
202+
let cache_dir = cache_dir().unwrap();
203+
if !cache_dir.exists() {
204+
return;
205+
}
206+
std::fs::remove_dir_all(cache_dir).unwrap();
207+
}
208+
}

0 commit comments

Comments
 (0)