Skip to content

Commit 3590ceb

Browse files
committed
Make build script API
1 parent 09b6857 commit 3590ceb

File tree

12 files changed

+638
-165
lines changed

12 files changed

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

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)