Skip to content

Commit 592e58e

Browse files
committed
Make build script API
1 parent 09b6857 commit 592e58e

File tree

12 files changed

+647
-165
lines changed

12 files changed

+647
-165
lines changed

Cargo.lock

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ keywords = ["gpu", "compiler", "rust-gpu"]
2222
license = "MIT OR Apache-2.0"
2323

2424
[workspace.dependencies]
25-
spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "929112a325335c3acd520ceca10e54596c3be93e", default-features = false }
25+
spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "13a80b7ed9fbd5738746ef31ab8ddfee3f403551", default-features = false }
2626
anyhow = "1.0.98"
2727
thiserror = "2.0.12"
2828
clap = { version = "4.5.41", features = ["derive"] }

crates/cargo-gpu-build/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ license.workspace = true
99

1010
[dependencies]
1111
rustc_codegen_spirv-cache = { path = "../rustc_codegen_spirv-cache" }
12-
spirv-builder.workspace = true
12+
dunce.workspace = true
1313
thiserror.workspace = true
1414
semver.workspace = true
1515
log.workspace = true
1616

1717
[features]
1818
# Rebuilds target shader crate upon changes
19-
watch = ["spirv-builder/watch"]
19+
watch = ["rustc_codegen_spirv-cache/watch"]
2020
# Enables `clap` support for public structs
21-
clap = ["spirv-builder/clap", "rustc_codegen_spirv-cache/clap"]
21+
clap = ["rustc_codegen_spirv-cache/clap"]
2222

2323
[lints]
2424
workspace = true
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
//! This module provides a `rust-gpu` shader crate builder
2+
//! usable inside of build scripts or as a part of CLI.
3+
4+
use std::{io, process::Stdio};
5+
6+
use crate::{
7+
lockfile::{LockfileMismatchError, LockfileMismatchHandler},
8+
spirv_builder::{CompileResult, SpirvBuilder, SpirvBuilderError},
9+
spirv_cache::{
10+
backend::{Install, InstallError, InstallParams, InstallRunParams, InstalledBackend},
11+
command::CommandExecError,
12+
toolchain::{
13+
HaltToolchainInstallation, InheritStderr, InheritStdout, NoopOnComponentsInstall,
14+
NoopOnToolchainInstall, StdioCfg,
15+
},
16+
user_output,
17+
},
18+
};
19+
20+
#[cfg(feature = "watch")]
21+
use crate::spirv_builder::SpirvWatcher;
22+
23+
/// Parameters for [`ShaderCrateBuilder::new()`].
24+
#[derive(Debug, Clone)]
25+
#[non_exhaustive]
26+
pub struct ShaderCrateBuilderParams<W, T, C, O, E> {
27+
/// Parameters of the shader crate build.
28+
pub build: SpirvBuilder,
29+
/// Parameters of the codegen backend installation for the shader crate.
30+
pub install: InstallParams,
31+
/// Writer of user output.
32+
pub writer: W,
33+
/// Callbacks to halt toolchain installation.
34+
pub halt: HaltToolchainInstallation<T, C>,
35+
/// Configuration of [`Stdio`] for commands run during installation.
36+
pub stdio_cfg: StdioCfg<O, E>,
37+
}
38+
39+
impl<W, T, C, O, E> ShaderCrateBuilderParams<W, T, C, O, E> {
40+
/// Replaces build parameters of the shader crate.
41+
#[inline]
42+
#[must_use]
43+
pub fn build(self, build: SpirvBuilder) -> Self {
44+
Self {
45+
build,
46+
install: self.install,
47+
writer: self.writer,
48+
halt: self.halt,
49+
stdio_cfg: self.stdio_cfg,
50+
}
51+
}
52+
53+
/// Replaces codegen backend installation parameters of the shader crate.
54+
#[inline]
55+
#[must_use]
56+
pub fn install(self, install: InstallParams) -> Self {
57+
Self {
58+
build: self.build,
59+
install,
60+
writer: self.writer,
61+
halt: self.halt,
62+
stdio_cfg: self.stdio_cfg,
63+
}
64+
}
65+
66+
/// Replaces the writer of user output.
67+
#[inline]
68+
#[must_use]
69+
pub fn writer<NW>(self, writer: NW) -> ShaderCrateBuilderParams<NW, T, C, O, E> {
70+
ShaderCrateBuilderParams {
71+
build: self.build,
72+
install: self.install,
73+
writer,
74+
halt: self.halt,
75+
stdio_cfg: self.stdio_cfg,
76+
}
77+
}
78+
79+
/// Replaces the callbacks to halt toolchain installation.
80+
#[inline]
81+
#[must_use]
82+
pub fn halt<NT, NC>(
83+
self,
84+
halt: HaltToolchainInstallation<NT, NC>,
85+
) -> ShaderCrateBuilderParams<W, NT, NC, O, E> {
86+
ShaderCrateBuilderParams {
87+
build: self.build,
88+
install: self.install,
89+
writer: self.writer,
90+
halt,
91+
stdio_cfg: self.stdio_cfg,
92+
}
93+
}
94+
95+
/// Replaces the [`Stdio`] configuration for commands run during installation.
96+
#[inline]
97+
#[must_use]
98+
pub fn stdio_cfg<NO, NE>(
99+
self,
100+
stdio_cfg: StdioCfg<NO, NE>,
101+
) -> ShaderCrateBuilderParams<W, T, C, NO, NE> {
102+
ShaderCrateBuilderParams {
103+
build: self.build,
104+
install: self.install,
105+
writer: self.writer,
106+
halt: self.halt,
107+
stdio_cfg,
108+
}
109+
}
110+
}
111+
112+
/// [`Default`] parameters for [`ShaderCrateBuilder::new()`].
113+
pub type DefaultShaderCrateBuilderParams = ShaderCrateBuilderParams<
114+
io::Stdout,
115+
NoopOnToolchainInstall,
116+
NoopOnComponentsInstall,
117+
InheritStdout,
118+
InheritStderr,
119+
>;
120+
121+
impl DefaultShaderCrateBuilderParams {
122+
/// Creates default parameters with the given build parameters.
123+
#[inline]
124+
#[must_use]
125+
pub fn from_build(build: SpirvBuilder) -> Self {
126+
Self {
127+
build,
128+
..Default::default()
129+
}
130+
}
131+
}
132+
133+
impl Default for DefaultShaderCrateBuilderParams {
134+
#[inline]
135+
fn default() -> Self {
136+
Self {
137+
build: SpirvBuilder::default(),
138+
install: InstallParams::default(),
139+
writer: io::stdout(),
140+
halt: HaltToolchainInstallation::noop(),
141+
stdio_cfg: StdioCfg::inherit(),
142+
}
143+
}
144+
}
145+
146+
/// A builder for compiling a `rust-gpu` shader crate.
147+
#[derive(Debug, Clone)]
148+
#[non_exhaustive]
149+
pub struct ShaderCrateBuilder<W = io::Stdout> {
150+
/// The underlying builder for compiling the shader crate.
151+
pub builder: SpirvBuilder,
152+
/// The arguments used to install the backend.
153+
pub installed_backend_args: Install,
154+
/// The installed backend.
155+
pub installed_backend: InstalledBackend,
156+
/// The lockfile mismatch handler.
157+
pub lockfile_mismatch_handler: LockfileMismatchHandler,
158+
/// Writer of user output.
159+
pub writer: W,
160+
}
161+
162+
impl<W> ShaderCrateBuilder<W>
163+
where
164+
W: io::Write,
165+
{
166+
/// Creates shader crate builder, allowing to modify install and build parameters separately.
167+
///
168+
/// # Errors
169+
///
170+
/// Returns an error if:
171+
///
172+
/// * the shader crate path / target was not set,
173+
/// * the shader crate path is not valid,
174+
/// * the backend installation fails,
175+
/// * there is a lockfile version mismatch that cannot be resolved automatically.
176+
#[inline]
177+
pub fn new<R, T, C, O, E>(
178+
mut params: ShaderCrateBuilderParams<W, T, C, O, E>,
179+
) -> Result<Self, NewShaderCrateBuilderError<R>>
180+
where
181+
R: From<CommandExecError>,
182+
T: FnOnce(&str) -> Result<(), R>,
183+
C: FnOnce(&str) -> Result<(), R>,
184+
O: FnMut() -> Stdio,
185+
E: FnMut() -> Stdio,
186+
{
187+
if params.build.target.is_none() {
188+
return Err(NewShaderCrateBuilderError::MissingTarget);
189+
}
190+
let shader_crate = params
191+
.build
192+
.path_to_crate
193+
.as_ref()
194+
.ok_or(NewShaderCrateBuilderError::MissingCratePath)?;
195+
let shader_crate_path = dunce::canonicalize(shader_crate)?;
196+
197+
let backend_to_install = Install::new(shader_crate_path, params.install);
198+
let backend_install_params = InstallRunParams::default()
199+
.writer(&mut params.writer)
200+
.halt(HaltToolchainInstallation {
201+
on_toolchain_install: |channel: &str| (params.halt.on_toolchain_install)(channel),
202+
on_components_install: |channel: &str| (params.halt.on_components_install)(channel),
203+
})
204+
.stdio_cfg(StdioCfg {
205+
stdout: || (params.stdio_cfg.stdout)(),
206+
stderr: || (params.stdio_cfg.stderr)(),
207+
});
208+
let backend = backend_to_install.run(backend_install_params)?;
209+
210+
let lockfile_mismatch_handler = LockfileMismatchHandler::new(
211+
&backend_to_install.shader_crate,
212+
&backend.toolchain_channel,
213+
backend_to_install.params.force_overwrite_lockfiles_v4_to_v3,
214+
)?;
215+
216+
#[expect(clippy::unreachable, reason = "target was already set")]
217+
backend
218+
.configure_spirv_builder(&mut params.build)
219+
.unwrap_or_else(|_| unreachable!("target was checked before calling this function"));
220+
221+
Ok(Self {
222+
builder: params.build,
223+
installed_backend_args: backend_to_install,
224+
installed_backend: backend,
225+
lockfile_mismatch_handler,
226+
writer: params.writer,
227+
})
228+
}
229+
230+
/// Builds the shader crate using the configured [`SpirvBuilder`].
231+
///
232+
/// # Errors
233+
///
234+
/// Returns an error if building the shader crate failed.
235+
#[inline]
236+
pub fn build(&mut self) -> Result<CompileResult, ShaderCrateBuildError> {
237+
let shader_crate = self.installed_backend_args.shader_crate.display();
238+
user_output!(&mut self.writer, "Compiling shaders at {shader_crate}...\n")?;
239+
240+
let result = self.builder.build()?;
241+
Ok(result)
242+
}
243+
244+
/// Watches the shader crate for changes using the configured [`SpirvBuilder`].
245+
///
246+
/// # Errors
247+
///
248+
/// Returns an error if watching shader crate for changes failed.
249+
#[cfg(feature = "watch")]
250+
#[inline]
251+
pub fn watch(&mut self) -> Result<SpirvWatcher, ShaderCrateBuildError> {
252+
let shader_crate = self.installed_backend_args.shader_crate.display();
253+
user_output!(
254+
&mut self.writer,
255+
"Watching shaders for changes at {shader_crate}...\n"
256+
)?;
257+
258+
let watcher = self.builder.clone().watch()?;
259+
Ok(watcher)
260+
}
261+
}
262+
263+
/// An error indicating what went wrong when creating a [`ShaderCrateBuilder`].
264+
#[derive(Debug, thiserror::Error)]
265+
#[non_exhaustive]
266+
pub enum NewShaderCrateBuilderError<E = CommandExecError> {
267+
/// Shader crate target is missing from parameters of the build.
268+
#[error("shader crate target must be set, for example `spirv-unknown-vulkan1.2`")]
269+
MissingTarget,
270+
/// Shader path is missing from parameters of the build.
271+
#[error("path to shader crate must be set")]
272+
MissingCratePath,
273+
/// The given shader crate path is not valid.
274+
#[error("shader crate path is not valid: {0}")]
275+
InvalidCratePath(#[from] io::Error),
276+
/// The backend installation failed.
277+
#[error("could not install backend: {0}")]
278+
Install(#[from] InstallError<E>),
279+
/// There is a lockfile version mismatch that cannot be resolved automatically.
280+
#[error(transparent)]
281+
LockfileMismatch(#[from] LockfileMismatchError),
282+
}
283+
284+
/// An error indicating what went wrong when building the shader crate.
285+
#[derive(Debug, thiserror::Error)]
286+
#[non_exhaustive]
287+
pub enum ShaderCrateBuildError {
288+
/// Failed to write user output.
289+
#[error("failed to write user output: {0}")]
290+
IoWrite(#[from] io::Error),
291+
/// Failed to build shader crate.
292+
#[error("failed to build shader crate: {0}")]
293+
Build(#[from] SpirvBuilderError),
294+
}

crates/cargo-gpu-build/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
//! to pass the many additional parameters required to configure rustc and our codegen backend,
1313
//! but provide you with a toolchain-agnostic version that you may use from stable rustc.
1414
15-
#![expect(clippy::pub_use, reason = "part of public API")]
15+
#![expect(clippy::pub_use, reason = "pub use for build scripts")]
1616

1717
pub use rustc_codegen_spirv_cache as spirv_cache;
1818
pub use rustc_codegen_spirv_cache::spirv_builder;
1919

20+
pub mod build;
2021
pub mod lockfile;

crates/cargo-gpu/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,17 @@ default-run = "cargo-gpu"
1111

1212
[dependencies]
1313
cargo-gpu-build = { path = "../cargo-gpu-build", features = ["clap", "watch"] }
14+
spirv-builder = { workspace = true, features = ["clap", "watch"] }
1415
cargo_metadata.workspace = true
1516
anyhow.workspace = true
1617
thiserror.workspace = true
17-
spirv-builder = { workspace = true, features = ["clap", "watch"] }
1818
clap.workspace = true
1919
env_logger.workspace = true
2020
log.workspace = true
2121
relative-path.workspace = true
2222
serde.workspace = true
2323
serde_json.workspace = true
2424
crossterm.workspace = true
25-
semver.workspace = true
2625
dunce.workspace = true
2726

2827
[dev-dependencies]

0 commit comments

Comments
 (0)