spirv_builder/lib.rs
1// FIXME(eddyb) update/review these lints.
2//
3// BEGIN - Embark standard lints v0.4
4// do not change or add/remove here, but one can add exceptions after this section
5// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
6#![deny(unsafe_code)]
7#![warn(
8 clippy::all,
9 clippy::await_holding_lock,
10 clippy::char_lit_as_u8,
11 clippy::checked_conversions,
12 clippy::dbg_macro,
13 clippy::debug_assert_with_mut_call,
14 clippy::doc_markdown,
15 clippy::empty_enum,
16 clippy::enum_glob_use,
17 clippy::exit,
18 clippy::expl_impl_clone_on_copy,
19 clippy::explicit_deref_methods,
20 clippy::explicit_into_iter_loop,
21 clippy::fallible_impl_from,
22 clippy::filter_map_next,
23 clippy::float_cmp_const,
24 clippy::fn_params_excessive_bools,
25 clippy::if_let_mutex,
26 clippy::implicit_clone,
27 clippy::imprecise_flops,
28 clippy::inefficient_to_string,
29 clippy::invalid_upcast_comparisons,
30 clippy::large_types_passed_by_value,
31 clippy::let_unit_value,
32 clippy::linkedlist,
33 clippy::lossy_float_literal,
34 clippy::macro_use_imports,
35 clippy::manual_ok_or,
36 clippy::map_err_ignore,
37 clippy::map_flatten,
38 clippy::map_unwrap_or,
39 clippy::match_same_arms,
40 clippy::match_wildcard_for_single_variants,
41 clippy::mem_forget,
42 clippy::mut_mut,
43 clippy::mutex_integer,
44 clippy::needless_borrow,
45 clippy::needless_continue,
46 clippy::option_option,
47 clippy::path_buf_push_overwrite,
48 clippy::ptr_as_ptr,
49 clippy::ref_option_ref,
50 clippy::rest_pat_in_fully_bound_structs,
51 clippy::same_functions_in_if_condition,
52 clippy::semicolon_if_nothing_returned,
53 clippy::string_add_assign,
54 clippy::string_add,
55 clippy::string_lit_as_bytes,
56 clippy::string_to_string,
57 clippy::todo,
58 clippy::trait_duplication_in_bounds,
59 clippy::unimplemented,
60 clippy::unnested_or_patterns,
61 clippy::unused_self,
62 clippy::useless_transmute,
63 clippy::verbose_file_reads,
64 clippy::zero_sized_map_values,
65 future_incompatible,
66 nonstandard_style,
67 rust_2018_idioms
68)]
69// END - Embark standard lints v0.4
70// crate-specific exceptions:
71// #![allow()]
72#![doc = include_str!("../README.md")]
73
74pub mod cargo_cmd;
75mod depfile;
76#[cfg(test)]
77mod tests;
78#[cfg(feature = "watch")]
79mod watch;
80
81use raw_string::{RawStr, RawString};
82use semver::Version;
83use serde::Deserialize;
84use std::borrow::Borrow;
85use std::collections::HashMap;
86use std::env;
87use std::fs::File;
88use std::io::BufReader;
89use std::path::{Path, PathBuf};
90use std::process::Stdio;
91use thiserror::Error;
92
93#[cfg(feature = "watch")]
94pub use self::watch::{SpirvWatcher, SpirvWatcherError};
95pub use rustc_codegen_spirv_types::*;
96
97#[derive(Debug, Error)]
98#[non_exhaustive]
99pub enum SpirvBuilderError {
100 #[error("`target` must be set, for example `spirv-unknown-vulkan1.2`")]
101 MissingTarget,
102 #[error("TargetError: {0}")]
103 TargetError(#[from] TargetError),
104 #[error("`path_to_crate` must be set")]
105 MissingCratePath,
106 #[error("crate path '{0}' does not exist")]
107 CratePathDoesntExist(PathBuf),
108 #[error(
109 "Without feature `rustc_codegen_spirv`, you need to set the path of the dylib with `rustc_codegen_spirv_location`"
110 )]
111 MissingRustcCodegenSpirvDylib,
112 #[error("`rustc_codegen_spirv_location` path '{0}' is not a file")]
113 RustcCodegenSpirvDylibDoesNotExist(PathBuf),
114 #[error("build failed")]
115 BuildFailed,
116 #[error(
117 "`multimodule: true` build cannot be used together with `build_script.env_shader_spv_path: true`"
118 )]
119 MultiModuleWithEnvShaderSpvPath,
120 #[error("multi-module metadata file missing")]
121 MetadataFileMissing(#[from] std::io::Error),
122 #[error("unable to parse multi-module metadata file")]
123 MetadataFileMalformed(#[from] serde_json::Error),
124 #[error(
125 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output.\n--- build output ---\n{stdout}"
126 )]
127 NoArtifactProduced { stdout: String },
128 #[error("cargo metadata error")]
129 CargoMetadata(#[from] cargo_metadata::Error),
130 #[cfg(feature = "watch")]
131 #[error(transparent)]
132 WatchFailed(#[from] SpirvWatcherError),
133}
134
135#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
136#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
137#[non_exhaustive]
138pub enum SpirvMetadata {
139 /// Strip all names and other debug information from SPIR-V output.
140 #[default]
141 None,
142 /// Only include `OpName`s for public interface variables (uniforms and the like), to allow
143 /// shader reflection.
144 NameVariables,
145 /// Include all `OpName`s for everything, and `OpLine`s. Significantly increases binary size.
146 Full,
147}
148
149/// Strategy used to handle Rust `panic!`s in shaders compiled to SPIR-V.
150#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
151#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
152#[non_exhaustive]
153pub enum ShaderPanicStrategy {
154 /// Return from shader entry-point with no side-effects **(default)**.
155 ///
156 /// While similar to the standard SPIR-V `OpTerminateInvocation`, this is
157 /// *not* limited to fragment shaders, and instead supports all shaders
158 /// (as it's handled via control-flow rewriting, instead of SPIR-V features).
159 #[default]
160 SilentExit,
161
162 /// Like `SilentExit`, but also using `debugPrintf` to report the panic in
163 /// a way that can reach the user, before returning from the entry-point.
164 ///
165 /// Quick setup for enabling `debugPrintf` output (to stdout) at runtime:
166 /// - **set these environment variables**:
167 /// - `VK_LOADER_LAYERS_ENABLE=VK_LAYER_KHRONOS_validation`
168 /// - `VK_LAYER_PRINTF_ONLY_PRESET=1`
169 /// - `VK_LAYER_PRINTF_TO_STDOUT=1` (not always needed, but can help)
170 /// - if using `wgpu`, enable `wgpu::Features::SPIRV_SHADER_PASSTHROUGH`,
171 /// and use `create_shader_module_passthrough` instead of `create_shader_module`
172 /// - in case of errors, or no output (from a `panic!()`/`debug_printf!()`),
173 /// keep reading below for additional information and alternatives
174 ///
175 /// ---
176 ///
177 /// **Note**: enabling this automatically adds the `SPV_KHR_non_semantic_info`
178 /// extension, as `debugPrintf` is from a "non-semantic extended instruction set".
179 ///
180 /// **Note**: `debugPrintf` output reaching the user involves:
181 /// - being able to load the shader in the first place:
182 /// - for `wgpu`, use "SPIR-V shader passthrough" (Naga lacks `debugPrintf`):
183 /// - enable `wgpu::Features::SPIRV_SHADER_PASSTHROUGH`
184 /// - replace `create_shader_module` calls with `create_shader_module_passthrough`
185 /// - *in theory*, the `VK_KHR_shader_non_semantic_info` Vulkan *Device* extension
186 /// (or requiring at least Vulkan 1.3, which incorporated it)
187 /// - *however*, Validation Layers don't actually check this anymore,
188 /// since Vulkan SDK version 1.4.313.0 (and drivers shouldn't care either)
189 /// - **general configurability** of [Vulkan SDK](https://vulkan.lunarg.com/sdk/home)
190 /// and/or [Vulkan Loader](https://github.com/KhronosGroup/Vulkan-Loader)
191 /// - *(this list doubles as a legend for shorthands used later below)*
192 /// - **env**: setting environment variables on the fly
193 /// - easiest for quick testing, no code changes/rebuilding needed
194 /// - e.g. `FOO=1 cargo run ...` (in UNIX-style shells)
195 /// - **instance**: programmatic control via `vkCreateInstance()` params
196 /// - best for integration with app-specific debugging functionality
197 /// - limited to direct Vulkan usage (e.g. `ash`, not `wgpu`)
198 /// - `VK_EXT_layer_settings` as a `VK_LAYER_*` environment variables
199 /// analogue, e.g. `VK_LAYER_FOO` controlled by a `VkLayerSettingEXT`
200 /// with `"foo"` as `pSettingName` (and an appropriate `type`/value),
201 /// included in `VkLayerSettingsCreateInfoEXT`'s `pSettings`
202 /// - on-disk configuration and interactive tooling, e.g.:
203 /// - `vk_layer_settings.txt` files, either hand-written, or generated by
204 /// the "Vulkan Configurator" GUI tool (included with the Vulkan SDK)
205 /// - third-party Vulkan debuggers like `RenderDoc`
206 /// - [Vulkan Validation Layers](https://github.com/KhronosGroup/Vulkan-ValidationLayers)
207 /// - (they contain the `debugPrintf` implementation, a SPIR-V -> SPIR-V translation)
208 /// - enabled by one of (as per "**general configurability**" above):
209 /// - **env**: `VK_LOADER_LAYERS_ENABLE=VK_LAYER_KHRONOS_validation`
210 /// - **instance**: `"VK_LAYER_KHRONOS_validation"` in the list of layers
211 /// - via `wgpu`: `wgpu::InstanceFlags::VALIDATION`
212 /// - Validation Layers' `debugPrintf` support
213 /// ([official docs](https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md)):
214 /// - enabled by one of (as per "**general configurability**" above):
215 /// - **env**: `VK_LAYER_PRINTF_ENABLE=1` (validation + `debugPrintf`)
216 /// - **env**: `VK_LAYER_PRINTF_ONLY_PRESET=1` (*only* `debugPrintf`, no validation)
217 /// - **instance**: `"printf_enable"` / `"printf_only_preset"` via `VkLayerSettingEXT`
218 /// (i.e. analogues for the two environment variables)
219 /// - **instance**: `VkValidationFeaturesEXT` with `pEnabledValidationFeatures`
220 /// containing `VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT`
221 /// - outputting the `debugPrintf` messages sent back from the GPU:
222 /// - defaults to common validation logging (itself defaulting to stdout)
223 /// - **env**: `VK_LAYER_PRINTF_TO_STDOUT=1` (and its **instance** analogue)
224 /// forces direct printing to stdout, bypassing `VK_EXT_debug_utils` etc.
225 /// - validation logging can itself be controlled via `VK_EXT_debug_utils`
226 /// - `wgpu` built in debug mode (and/or with debug-assertions enabled):
227 /// - it uses `VK_EXT_debug_utils` internally, exposing it via `log`
228 /// - with e.g. `env_logger`, `RUST_LOG=info` suffices for `debugPrintf`
229 /// messages (as they specifically have the "info" level)
230 /// - other `log`/`tracing` subscribers should be configured similarly
231 #[cfg_attr(feature = "clap", clap(skip))]
232 DebugPrintfThenExit {
233 /// Whether to also print the entry-point inputs (excluding buffers/resources),
234 /// which should uniquely identify the panicking shader invocation.
235 print_inputs: bool,
236
237 /// Whether to also print a "backtrace" (i.e. the chain of function calls
238 /// that led to the `panic!`).
239 ///
240 /// As there is no way to dynamically compute this information, the string
241 /// containing the full backtrace of each `panic!` is statically generated,
242 /// meaning this option could significantly increase binary size.
243 print_backtrace: bool,
244 },
245
246 /// **Warning**: this is _**unsound**_ (i.e. adds Undefined Behavior to *safe* Rust code)
247 ///
248 /// This option only exists for testing (hence the unfriendly name it has),
249 /// and more specifically testing whether conditional panics are responsible
250 /// for performance differences when upgrading from older Rust-GPU versions
251 /// (which used infinite loops for panics, that `spirv-opt`/drivers could've
252 /// sometimes treated as UB, and optimized as if they were impossible to reach).
253 ///
254 /// Unlike those infinite loops, however, this uses `OpUnreachable`, so it
255 /// forces the old worst-case (all `panic!`s become UB and are optimized out).
256 #[allow(non_camel_case_types)]
257 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
258}
259
260/// Options for specifying the behavior of the validator
261/// Copied from `spirv-tools/src/val.rs` struct `ValidatorOptions`, with some fields disabled.
262#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
263#[cfg_attr(feature = "clap", derive(clap::Parser))]
264#[non_exhaustive]
265pub struct ValidatorOptions {
266 /// Record whether or not the validator should relax the rules on types for
267 /// stores to structs. When relaxed, it will allow a type mismatch as long as
268 /// the types are structs with the same layout. Two structs have the same layout
269 /// if
270 ///
271 /// 1) the members of the structs are either the same type or are structs with
272 /// same layout, and
273 ///
274 /// 2) the decorations that affect the memory layout are identical for both
275 /// types. Other decorations are not relevant.
276 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
277 pub relax_struct_store: bool,
278 /// Records whether or not the validator should relax the rules on pointer usage
279 /// in logical addressing mode.
280 ///
281 /// When relaxed, it will allow the following usage cases of pointers:
282 /// 1) `OpVariable` allocating an object whose type is a pointer type
283 /// 2) `OpReturnValue` returning a pointer value
284 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
285 pub relax_logical_pointer: bool,
286 // /// Records whether or not the validator should relax the rules because it is
287 // /// expected that the optimizations will make the code legal.
288 // ///
289 // /// When relaxed, it will allow the following:
290 // /// 1) It will allow relaxed logical pointers. Setting this option will also
291 // /// set that option.
292 // /// 2) Pointers that are pass as parameters to function calls do not have to
293 // /// match the storage class of the formal parameter.
294 // /// 3) Pointers that are actaul parameters on function calls do not have to point
295 // /// to the same type pointed as the formal parameter. The types just need to
296 // /// logically match.
297 // pub before_legalization: bool,
298 /// Records whether the validator should use "relaxed" block layout rules.
299 /// Relaxed layout rules are described by Vulkan extension
300 /// `VK_KHR_relaxed_block_layout`, and they affect uniform blocks, storage blocks,
301 /// and push constants.
302 ///
303 /// This is enabled by default when targeting Vulkan 1.1 or later.
304 /// Relaxed layout is more permissive than the default rules in Vulkan 1.0.
305 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
306 pub relax_block_layout: Option<bool>,
307 /// Records whether the validator should use standard block layout rules for
308 /// uniform blocks.
309 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
310 pub uniform_buffer_standard_layout: bool,
311 /// Records whether the validator should use "scalar" block layout rules.
312 /// Scalar layout rules are more permissive than relaxed block layout.
313 ///
314 /// See Vulkan extnesion `VK_EXT_scalar_block_layout`. The scalar alignment is
315 /// defined as follows:
316 /// - scalar alignment of a scalar is the scalar size
317 /// - scalar alignment of a vector is the scalar alignment of its component
318 /// - scalar alignment of a matrix is the scalar alignment of its component
319 /// - scalar alignment of an array is the scalar alignment of its element
320 /// - scalar alignment of a struct is the max scalar alignment among its
321 /// members
322 ///
323 /// For a struct in Uniform, `StorageClass`, or `PushConstant`:
324 /// - a member Offset must be a multiple of the member's scalar alignment
325 /// - `ArrayStride` or `MatrixStride` must be a multiple of the array or matrix
326 /// scalar alignment
327 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
328 pub scalar_block_layout: bool,
329 /// Records whether or not the validator should skip validating standard
330 /// uniform/storage block layout.
331 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
332 pub skip_block_layout: bool,
333 // /// Applies a maximum to one or more Universal limits
334 // pub max_limits: Vec<(ValidatorLimits, u32)>,
335}
336
337/// Options for specifying the behavior of the optimizer
338/// Copied from `spirv-tools/src/opt.rs` struct `Options`, with some fields disabled.
339#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
340#[cfg_attr(feature = "clap", derive(clap::Parser))]
341#[non_exhaustive]
342pub struct OptimizerOptions {
343 // /// Records the validator options that should be passed to the validator,
344 // /// the validator will run with the options before optimizer.
345 // pub validator_options: Option<crate::val::ValidatorOptions>,
346 // /// Records the maximum possible value for the id bound.
347 // pub max_id_bound: Option<u32>,
348 /// Records whether all bindings within the module should be preserved.
349 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
350 pub preserve_bindings: bool,
351 // /// Records whether all specialization constants within the module
352 // /// should be preserved.
353 // pub preserve_spec_constants: bool,
354}
355
356/// Cargo features specification for building the shader crate.
357#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
358#[cfg_attr(feature = "clap", derive(clap::Parser))]
359#[non_exhaustive]
360pub struct ShaderCrateFeatures {
361 /// Set --default-features for the target shader crate.
362 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
363 pub default_features: bool,
364 /// Set --features for the target shader crate.
365 #[cfg_attr(feature = "clap", clap(long))]
366 pub features: Vec<String>,
367}
368
369impl Default for ShaderCrateFeatures {
370 fn default() -> Self {
371 Self {
372 default_features: true,
373 features: Vec::new(),
374 }
375 }
376}
377
378/// Configuration for build scripts
379#[derive(Clone, Debug, Default)]
380#[non_exhaustive]
381pub struct BuildScriptConfig {
382 /// Enable this if you are using `spirv-builder` from a build script to apply some recommended default options, such
383 /// as [`Self::dependency_info`], [`Self::forward_rustc_warnings`] and [`Self::cargo_color_always`].
384 pub defaults: bool,
385
386 /// Print dependency information for cargo build scripts (with `cargo::rerun-if-changed={}` and such).
387 /// Dependency information makes cargo rerun the build script is rerun when shader source files change, thus
388 /// rebuilding the shader.
389 ///
390 /// Default: [`Self::defaults`]
391 pub dependency_info: Option<bool>,
392
393 /// Whether to emit an env var pointing to the shader module file (via `cargo::rustc-env={}`). The name of the env
394 /// var is the crate name with `.spv` appended, e.g. `sky_shader.spv`.
395 /// Not supported together with `multimodule=true`.
396 ///
397 /// Some examples on how to include the shader module in the source code:
398 /// * wgpu:
399 /// ```rust,ignore
400 /// let shader: ShaderModuleDescriptorPassthrough = include_spirv_raw!(env!("my_shader.spv"));
401 /// ```
402 /// * ash
403 /// ```rust,ignore
404 /// let bytes: &[u8] = include_bytes!(env!("my_shader.spv"))
405 /// let words = ash::util::read_spv(&mut std::io::Cursor::new(bytes)).unwrap();
406 /// ```
407 ///
408 /// Default: `false`
409 pub env_shader_spv_path: Option<bool>,
410
411 /// Forwards any warnings or errors by rustc as build script warnings (via `cargo::warning=`). Not enabling this
412 /// option may hide warnings if the build succeeds.
413 ///
414 /// Default: [`Self::defaults`]
415 pub forward_rustc_warnings: Option<bool>,
416
417 /// Pass `--color always` to cargo to force enable colorful error messages. Particularly in build scripts, these
418 /// are disabled by default, even though we'll forward them to your console. Should your console not support colors,
419 /// then the outer cargo executing the build script will filter out all ansi escape sequences anyway, so we're free
420 /// to always emit them.
421 ///
422 /// Default: [`Self::defaults`]
423 pub cargo_color_always: Option<bool>,
424}
425
426/// these all have the prefix `get` so the doc items link to the members, not these private fns
427impl BuildScriptConfig {
428 fn get_dependency_info(&self) -> bool {
429 self.dependency_info.unwrap_or(self.defaults)
430 }
431 fn get_env_shader_spv_path(&self) -> bool {
432 self.env_shader_spv_path.unwrap_or(false)
433 }
434 fn get_forward_rustc_warnings(&self) -> bool {
435 self.forward_rustc_warnings.unwrap_or(self.defaults)
436 }
437 fn get_cargo_color_always(&self) -> bool {
438 self.cargo_color_always.unwrap_or(self.defaults)
439 }
440}
441
442#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
443#[cfg_attr(feature = "clap", derive(clap::Parser))]
444#[non_exhaustive]
445pub struct SpirvBuilder {
446 /// The path to the shader crate to compile
447 #[cfg_attr(feature = "clap", clap(skip))]
448 pub path_to_crate: Option<PathBuf>,
449 /// The cargo command to run, formatted like `cargo {cargo_cmd} ...`. Defaults to `rustc`.
450 #[cfg_attr(feature = "clap", clap(skip))]
451 pub cargo_cmd: Option<String>,
452 /// Whether the cargo command set in `cargo_cmd` behaves like `cargo rustc` and allows passing args such as
453 /// `--crate-type dylib`. Defaults to true if `cargo_cmd` is `None` or `Some("rustc")`.
454 #[cfg_attr(feature = "clap", clap(skip))]
455 pub cargo_cmd_like_rustc: Option<bool>,
456 /// Configuration for build scripts
457 #[cfg_attr(feature = "clap", clap(skip))]
458 #[serde(skip)]
459 pub build_script: BuildScriptConfig,
460 /// Build in release. Defaults to true.
461 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
462 pub release: bool,
463 /// The target triple, eg. `spirv-unknown-vulkan1.2`
464 #[cfg_attr(
465 feature = "clap",
466 clap(long, default_value = "spirv-unknown-vulkan1.2")
467 )]
468 pub target: Option<String>,
469 /// Cargo features specification for building the shader crate.
470 #[cfg_attr(feature = "clap", clap(flatten))]
471 #[serde(flatten)]
472 pub shader_crate_features: ShaderCrateFeatures,
473 /// Deny any warnings, as they may never be printed when building within a build script. Defaults to false.
474 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
475 pub deny_warnings: bool,
476 /// Splits the resulting SPIR-V file into one module per entry point. This is useful in cases
477 /// where ecosystem tooling has bugs around multiple entry points per module - having all entry
478 /// points bundled into a single file is the preferred system.
479 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
480 pub multimodule: bool,
481 /// Sets the level of metadata (primarily `OpName` and `OpLine`) included in the SPIR-V binary.
482 /// Including metadata significantly increases binary size.
483 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
484 pub spirv_metadata: SpirvMetadata,
485 /// Adds a capability to the SPIR-V module. Checking if a capability is enabled in code can be
486 /// done via `#[cfg(target_feature = "TheCapability")]`.
487 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
488 pub capabilities: Vec<Capability>,
489 /// Adds an extension to the SPIR-V module. Checking if an extension is enabled in code can be
490 /// done via `#[cfg(target_feature = "ext:the_extension")]`.
491 #[cfg_attr(feature = "clap", arg(long))]
492 pub extensions: Vec<String>,
493 /// Set additional "codegen arg". Note: the `RUSTGPU_CODEGEN_ARGS` environment variable
494 /// takes precedence over any set arguments using this function.
495 #[cfg_attr(feature = "clap", clap(skip))]
496 pub extra_args: Vec<String>,
497 // Location of a known `rustc_codegen_spirv` dylib, only required without feature `rustc_codegen_spirv`.
498 #[cfg_attr(feature = "clap", clap(skip))]
499 pub rustc_codegen_spirv_location: Option<PathBuf>,
500 // Overwrite the toolchain like `cargo +nightly`
501 #[cfg_attr(feature = "clap", clap(skip))]
502 pub toolchain_overwrite: Option<String>,
503 // Set the rustc version of the toolchain, used to adjust params to support older toolchains
504 #[cfg_attr(feature = "clap", clap(skip))]
505 pub toolchain_rustc_version: Option<Version>,
506
507 /// Set the target dir path to use for building shaders. Relative paths will be resolved
508 /// relative to the `target` dir of the shader crate, absolute paths are used as is.
509 /// Defaults to `spirv-builder`, resulting in the path `./target/spirv-builder`.
510 #[cfg_attr(feature = "clap", clap(skip))]
511 pub target_dir_path: Option<PathBuf>,
512
513 // `rustc_codegen_spirv::linker` codegen args
514 /// Change the shader `panic!` handling strategy (see [`ShaderPanicStrategy`]).
515 #[cfg_attr(feature = "clap", clap(skip))]
516 pub shader_panic_strategy: ShaderPanicStrategy,
517
518 /// spirv-val flags
519 #[cfg_attr(feature = "clap", clap(flatten))]
520 #[serde(flatten)]
521 pub validator: ValidatorOptions,
522
523 /// spirv-opt flags
524 #[cfg_attr(feature = "clap", clap(flatten))]
525 #[serde(flatten)]
526 pub optimizer: OptimizerOptions,
527}
528
529#[cfg(feature = "clap")]
530impl SpirvBuilder {
531 /// Clap value parser for `Capability`.
532 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
533 use core::str::FromStr;
534 Capability::from_str(capability).map_or_else(
535 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
536 Ok,
537 )
538 }
539}
540
541impl Default for SpirvBuilder {
542 fn default() -> Self {
543 Self {
544 path_to_crate: None,
545 cargo_cmd: None,
546 cargo_cmd_like_rustc: None,
547 build_script: BuildScriptConfig::default(),
548 release: true,
549 target: None,
550 deny_warnings: false,
551 multimodule: false,
552 spirv_metadata: SpirvMetadata::default(),
553 capabilities: Vec::new(),
554 extensions: Vec::new(),
555 extra_args: Vec::new(),
556 rustc_codegen_spirv_location: None,
557 target_dir_path: None,
558 toolchain_overwrite: None,
559 toolchain_rustc_version: None,
560 shader_panic_strategy: ShaderPanicStrategy::default(),
561 validator: ValidatorOptions::default(),
562 optimizer: OptimizerOptions::default(),
563 shader_crate_features: ShaderCrateFeatures::default(),
564 }
565 }
566}
567
568impl SpirvBuilder {
569 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
570 Self {
571 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
572 target: Some(target.into()),
573 ..SpirvBuilder::default()
574 }
575 }
576
577 #[must_use]
578 pub fn deny_warnings(mut self, v: bool) -> Self {
579 self.deny_warnings = v;
580 self
581 }
582
583 /// Build in release. Defaults to true.
584 #[must_use]
585 pub fn release(mut self, v: bool) -> Self {
586 self.release = v;
587 self
588 }
589
590 /// Splits the resulting SPIR-V file into one module per entry point. This is useful in cases
591 /// where ecosystem tooling has bugs around multiple entry points per module - having all entry
592 /// points bundled into a single file is the preferred system.
593 #[must_use]
594 pub fn multimodule(mut self, v: bool) -> Self {
595 self.multimodule = v;
596 self
597 }
598
599 /// Sets the level of metadata (primarily `OpName` and `OpLine`) included in the SPIR-V binary.
600 /// Including metadata significantly increases binary size.
601 #[must_use]
602 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
603 self.spirv_metadata = v;
604 self
605 }
606
607 /// Adds a capability to the SPIR-V module. Checking if a capability is enabled in code can be
608 /// done via `#[cfg(target_feature = "TheCapability")]`.
609 #[must_use]
610 pub fn capability(mut self, capability: Capability) -> Self {
611 self.capabilities.push(capability);
612 self
613 }
614
615 /// Adds an extension to the SPIR-V module. Checking if an extension is enabled in code can be
616 /// done via `#[cfg(target_feature = "ext:the_extension")]`.
617 #[must_use]
618 pub fn extension(mut self, extension: impl Into<String>) -> Self {
619 self.extensions.push(extension.into());
620 self
621 }
622
623 /// Change the shader `panic!` handling strategy (see [`ShaderPanicStrategy`]).
624 #[must_use]
625 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
626 self.shader_panic_strategy = shader_panic_strategy;
627 self
628 }
629
630 /// Allow store from one struct type to a different type with compatible layout and members.
631 #[must_use]
632 pub fn relax_struct_store(mut self, v: bool) -> Self {
633 self.validator.relax_struct_store = v;
634 self
635 }
636
637 /// Allow allocating an object of a pointer type and returning a pointer value from a function
638 /// in logical addressing mode
639 #[must_use]
640 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
641 self.validator.relax_logical_pointer = v;
642 self
643 }
644
645 /// Enable `VK_KHR_relaxed_block_layout` when checking standard uniform, storage buffer, and
646 /// push constant layouts. This is the default when targeting Vulkan 1.1 or later.
647 #[must_use]
648 pub fn relax_block_layout(mut self, v: bool) -> Self {
649 self.validator.relax_block_layout = Some(v);
650 self
651 }
652
653 /// Enable `VK_KHR_uniform_buffer_standard_layout` when checking standard uniform buffer
654 /// layouts.
655 #[must_use]
656 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
657 self.validator.uniform_buffer_standard_layout = v;
658 self
659 }
660
661 /// Enable `VK_EXT_scalar_block_layout` when checking standard uniform, storage buffer, and
662 /// push constant layouts. Scalar layout rules are more permissive than relaxed block layout so
663 /// in effect this will override the --relax-block-layout option.
664 #[must_use]
665 pub fn scalar_block_layout(mut self, v: bool) -> Self {
666 self.validator.scalar_block_layout = v;
667 self
668 }
669
670 /// Skip checking standard uniform/storage buffer layout. Overrides any --relax-block-layout or
671 /// --scalar-block-layout option.
672 #[must_use]
673 pub fn skip_block_layout(mut self, v: bool) -> Self {
674 self.validator.skip_block_layout = v;
675 self
676 }
677
678 /// Preserve unused descriptor bindings. Useful for reflection.
679 #[must_use]
680 pub fn preserve_bindings(mut self, v: bool) -> Self {
681 self.optimizer.preserve_bindings = v;
682 self
683 }
684
685 /// Set additional "codegen arg". Note: the `RUSTGPU_CODEGEN_ARGS` environment variable
686 /// takes precedence over any set arguments using this function.
687 #[must_use]
688 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
689 self.extra_args.push(arg.into());
690 self
691 }
692
693 /// Set --default-features for the target shader crate.
694 #[must_use]
695 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
696 self.shader_crate_features.default_features = default_features;
697 self
698 }
699
700 /// Set --features for the target shader crate.
701 #[must_use]
702 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
703 self.shader_crate_features.features = features.into_iter().collect();
704 self
705 }
706
707 #[must_use]
708 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
709 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
710 self
711 }
712
713 /// Set the target dir path to use for building shaders. Relative paths will be resolved
714 /// relative to the `target` dir of the shader crate, absolute paths are used as is.
715 /// Defaults to `spirv-builder`, resulting in the path `./target/spirv-builder`.
716 #[must_use]
717 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
718 self.target_dir_path = Some(name.into());
719 self
720 }
721
722 /// Shortcut for `cargo check`
723 pub fn check(&mut self) -> Result<CompileResult, SpirvBuilderError> {
724 self.run_cargo_cmd("check")
725 }
726
727 /// Shortcut for `cargo clippy`
728 pub fn clippy(&mut self) -> Result<CompileResult, SpirvBuilderError> {
729 self.run_cargo_cmd("clippy")
730 }
731
732 /// Run the supplied cargo cmd, and ensure to reset the state so [`Self::build`] still works as normal
733 fn run_cargo_cmd(&mut self, cmd: &str) -> Result<CompileResult, SpirvBuilderError> {
734 let old = self.cargo_cmd.replace(cmd.into());
735 let result = self.build();
736 self.cargo_cmd = old;
737 result
738 }
739
740 /// Builds the module
741 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
742 let metadata_file = invoke_rustc(self)?;
743 if self.build_script.get_dependency_info() {
744 leaf_deps(&metadata_file, |artifact| {
745 println!("cargo:rerun-if-changed={artifact}");
746 })
747 // Close enough
748 .map_err(SpirvBuilderError::MetadataFileMissing)?;
749 }
750 let metadata = self.parse_metadata_file(&metadata_file)?;
751
752 Ok(metadata)
753 }
754
755 pub(crate) fn parse_metadata_file(
756 &self,
757 at: &Path,
758 ) -> Result<CompileResult, SpirvBuilderError> {
759 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
760 // FIXME(eddyb) move this functionality into `rustc_codegen_spirv_types`.
761 let metadata: CompileResult =
762 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
763 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
764 match &metadata.module {
765 ModuleResult::SingleModule(spirv_module) => {
766 assert!(!self.multimodule);
767 if self.build_script.get_env_shader_spv_path() {
768 let env_var = format!(
769 "{}.spv",
770 at.file_name()
771 .unwrap()
772 .to_str()
773 .unwrap()
774 .strip_suffix(ARTIFACT_SUFFIX)
775 .unwrap()
776 );
777 println!("cargo::rustc-env={}={}", env_var, spirv_module.display());
778 }
779 }
780 ModuleResult::MultiModule(_) => {
781 assert!(self.multimodule);
782 }
783 }
784 Ok(metadata)
785 }
786}
787
788// https://github.com/rust-lang/cargo/blob/1857880b5124580c4aeb4e8bc5f1198f491d61b1/src/cargo/util/paths.rs#L29-L52
789fn dylib_path_envvar() -> &'static str {
790 if cfg!(windows) {
791 "PATH"
792 } else if cfg!(target_os = "macos") {
793 "DYLD_FALLBACK_LIBRARY_PATH"
794 } else {
795 "LD_LIBRARY_PATH"
796 }
797}
798fn dylib_path() -> Vec<PathBuf> {
799 let mut dylibs = match env::var_os(dylib_path_envvar()) {
800 Some(var) => env::split_paths(&var).collect(),
801 None => Vec::new(),
802 };
803 if let Ok(dir) = env::current_dir() {
804 dylibs.push(dir);
805 }
806 dylibs
807}
808
809fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
810 if cfg!(feature = "rustc_codegen_spirv") {
811 let filename = format!(
812 "{}rustc_codegen_spirv{}",
813 env::consts::DLL_PREFIX,
814 env::consts::DLL_SUFFIX
815 );
816 let dylib_paths = dylib_path();
817 for mut path in dylib_paths {
818 path.push(&filename);
819 if path.is_file() {
820 return Ok(path);
821 }
822 }
823 panic!("Could not find {filename} in library path");
824 } else {
825 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
826 }
827}
828
829/// Joins strings together while ensuring none of the strings contain the separator.
830// NOTE(eddyb) this intentionally consumes the `Vec` to limit accidental misuse.
831fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
832 for s in &strings {
833 let s = s.borrow();
834 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
835 }
836 strings.join(sep)
837}
838
839// Returns path to the metadata json.
840fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
841 let path_to_crate = builder
842 .path_to_crate
843 .as_ref()
844 .ok_or(SpirvBuilderError::MissingCratePath)?;
845 let target;
846 {
847 let target_str = builder
848 .target
849 .as_ref()
850 .ok_or(SpirvBuilderError::MissingTarget)?;
851 target = SpirvTarget::parse(target_str)?;
852
853 if builder.build_script.get_env_shader_spv_path() && builder.multimodule {
854 return Err(SpirvBuilderError::MultiModuleWithEnvShaderSpvPath);
855 }
856 if !path_to_crate.is_dir() {
857 return Err(SpirvBuilderError::CratePathDoesntExist(
858 path_to_crate.clone(),
859 ));
860 }
861 }
862
863 let toolchain_rustc_version =
864 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
865 toolchain_rustc_version.clone()
866 } else {
867 query_rustc_version(builder.toolchain_overwrite.as_deref())?
868 };
869
870 // Okay, this is a little bonkers: in a normal world, we'd have the user clone
871 // rustc_codegen_spirv and pass in the path to it, and then we'd invoke cargo to build it, grab
872 // the resulting .so, and pass it into -Z codegen-backend. But that's really gross: the user
873 // needs to clone rustc_codegen_spirv and tell us its path! So instead, we *directly reference
874 // rustc_codegen_spirv in spirv-builder's Cargo.toml*, which means that it will get built
875 // alongside build.rs, and cargo will helpfully add it to LD_LIBRARY_PATH for us! However,
876 // rustc expects a full path, instead of a filename looked up via LD_LIBRARY_PATH, so we need
877 // to copy cargo's understanding of library lookup and find the library and its full path.
878 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
879 .transpose()
880 .unwrap_or_else(find_rustc_codegen_spirv)?;
881 if !rustc_codegen_spirv.is_file() {
882 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
883 rustc_codegen_spirv,
884 ));
885 }
886
887 let mut rustflags = vec![
888 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
889 // Ensure the codegen backend is emitted in `.d` files to force Cargo
890 // to rebuild crates compiled with it when it changes (this used to be
891 // the default until https://github.com/rust-lang/rust/pull/93969).
892 "-Zbinary-dep-depinfo".to_string(),
893 "-Csymbol-mangling-version=v0".to_string(),
894 "-Zcrate-attr=feature(register_tool)".to_string(),
895 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
896 // HACK(eddyb) this is the same configuration that we test with, and
897 // ensures no unwanted surprises from e.g. `core` debug assertions.
898 "-Coverflow-checks=off".to_string(),
899 "-Cdebug-assertions=off".to_string(),
900 // HACK(eddyb) we need this for `core::fmt::rt::Argument::new_*` calls
901 // to *never* be inlined, so we can pattern-match the calls themselves.
902 "-Zinline-mir=off".to_string(),
903 // HACK(eddyb) similar to turning MIR inlining off, we also can't allow
904 // optimizations that drastically impact (the quality of) codegen, and
905 // GVN currently can lead to the memcpy-out-of-const-alloc-global-var
906 // pattern, even for `ScalarPair` (e.g. `return None::<u32>;`).
907 "-Zmir-enable-passes=-GVN".to_string(),
908 // HACK(eddyb) avoid ever reusing instantiations from `compiler_builtins`
909 // which is special-cased to turn calls to functions that never return,
910 // into aborts, and this applies to the panics of UB-checking helpers
911 // (https://github.com/rust-lang/rust/pull/122580#issuecomment-3033026194)
912 // but while upstream that only loses the panic message, for us it's even
913 // worse, as we lose the chance to remove otherwise-dead `fmt::Arguments`.
914 "-Zshare-generics=off".to_string(),
915 ];
916
917 // Wrapper for `env::var` that appropriately informs Cargo of the dependency.
918 let tracked_env_var_get = |name| {
919 if builder.build_script.get_dependency_info() {
920 println!("cargo:rerun-if-env-changed={name}");
921 }
922 env::var(name)
923 };
924
925 let mut llvm_args = vec![];
926 if builder.multimodule {
927 llvm_args.push("--module-output=multiple".to_string());
928 }
929 match builder.spirv_metadata {
930 SpirvMetadata::None => (),
931 SpirvMetadata::NameVariables => {
932 llvm_args.push("--spirv-metadata=name-variables".to_string());
933 }
934 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
935 }
936 if builder.validator.relax_struct_store {
937 llvm_args.push("--relax-struct-store".to_string());
938 }
939 if builder.validator.relax_logical_pointer {
940 llvm_args.push("--relax-logical-pointer".to_string());
941 }
942 if builder.validator.relax_block_layout.unwrap_or(false) {
943 llvm_args.push("--relax-block-layout".to_string());
944 }
945 if builder.validator.uniform_buffer_standard_layout {
946 llvm_args.push("--uniform-buffer-standard-layout".to_string());
947 }
948 if builder.validator.scalar_block_layout {
949 llvm_args.push("--scalar-block-layout".to_string());
950 }
951 if builder.validator.skip_block_layout {
952 llvm_args.push("--skip-block-layout".to_string());
953 }
954 if builder.optimizer.preserve_bindings {
955 llvm_args.push("--preserve-bindings".to_string());
956 }
957 let mut target_features = vec![];
958 let abort_strategy = match builder.shader_panic_strategy {
959 ShaderPanicStrategy::SilentExit => None,
960 ShaderPanicStrategy::DebugPrintfThenExit {
961 print_inputs,
962 print_backtrace,
963 } => {
964 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
965 Some(format!(
966 "debug-printf{}{}",
967 if print_inputs { "+inputs" } else { "" },
968 if print_backtrace { "+backtrace" } else { "" }
969 ))
970 }
971 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
972 Some("unreachable".into())
973 }
974 };
975 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
976
977 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
978 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
979 } else {
980 llvm_args.extend(builder.extra_args.iter().cloned());
981 }
982
983 let llvm_args = join_checking_for_separators(llvm_args, " ");
984 if !llvm_args.is_empty() {
985 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
986 }
987
988 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
989 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
990 let target_features = join_checking_for_separators(target_features, ",");
991 if !target_features.is_empty() {
992 rustflags.push(["-Ctarget-feature=", &target_features].concat());
993 }
994
995 if builder.deny_warnings {
996 rustflags.push("-Dwarnings".to_string());
997 }
998
999 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
1000 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
1001 }
1002
1003 let target_dir_path = builder
1004 .target_dir_path
1005 .clone()
1006 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
1007 let target_dir = if target_dir_path.is_absolute() {
1008 target_dir_path
1009 } else {
1010 let metadata = cargo_metadata::MetadataCommand::new()
1011 .current_dir(path_to_crate)
1012 .exec()?;
1013 metadata
1014 .target_directory
1015 .into_std_path_buf()
1016 .join(target_dir_path)
1017 };
1018
1019 let mut cargo = cargo_cmd::CargoCmd::new();
1020 if let Some(toolchain) = &builder.toolchain_overwrite {
1021 cargo.arg(format!("+{toolchain}"));
1022 }
1023
1024 let cargo_cmd = builder.cargo_cmd.as_ref().map_or("rustc", |s| s.as_str());
1025 let cargo_cmd_like_rustc = builder.cargo_cmd_like_rustc.unwrap_or(cargo_cmd == "rustc");
1026 let profile = if builder.release { "release" } else { "dev" };
1027 cargo.args([
1028 cargo_cmd,
1029 "--lib",
1030 "--message-format=json-render-diagnostics",
1031 "-Zbuild-std=core",
1032 "-Zbuild-std-features=compiler-builtins-mem",
1033 "--profile",
1034 profile,
1035 ]);
1036 if cargo_cmd_like_rustc {
1037 // About `crate-type`: We use it to determine whether the crate needs to be linked into shaders. For `rlib`,
1038 // we're emitting regular rust libraries as is expected. For `dylib` or `cdylib`, we're linking all `rlib`s
1039 // together, legalize them in many passes and emit a final `*.spv` file. Quirk: If you depend on a crate
1040 // that has crate-type `dylib`, we also link it, and it will fail if it has no shaders, which may not be
1041 // desired. (Gathered from reading source code and experimenting, @firestar99)
1042 cargo.args(["--crate-type", "dylib"]);
1043 }
1044
1045 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
1046 cargo.args(extra_cargoflags.split_whitespace());
1047 }
1048
1049 let target_spec_dir = target_dir.join("target-specs");
1050 let target = TargetSpecVersion::target_arg(toolchain_rustc_version, &target, &target_spec_dir)?;
1051 cargo.arg("--target").arg(target);
1052
1053 if !builder.shader_crate_features.default_features {
1054 cargo.arg("--no-default-features");
1055 }
1056
1057 if !builder.shader_crate_features.features.is_empty() {
1058 cargo
1059 .arg("--features")
1060 .arg(builder.shader_crate_features.features.join(","));
1061 }
1062
1063 cargo.arg("--target-dir").arg(target_dir);
1064
1065 // Args for warning and error forwarding
1066 if builder.build_script.get_forward_rustc_warnings() {
1067 // Quiet to remove all the status messages and only emit errors and warnings
1068 cargo.args(["--quiet"]);
1069 }
1070 if builder.build_script.get_cargo_color_always() {
1071 // Always emit color, since the outer cargo will remove ascii escape sequences if color is turned off
1072 cargo.args(["--color", "always"]);
1073 }
1074
1075 // NOTE(eddyb) this used to be just `RUSTFLAGS` but at some point Cargo
1076 // added a separate environment variable using `\x1f` instead of spaces,
1077 // which allows us to have spaces within individual `rustc` flags.
1078 cargo.env(
1079 "CARGO_ENCODED_RUSTFLAGS",
1080 join_checking_for_separators(rustflags, "\x1f"),
1081 );
1082
1083 // NOTE(eddyb) there's no parallelism to take advantage of multiple CGUs,
1084 // and inter-CGU duplication can be wasteful, so this forces 1 CGU for now.
1085 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1086 let num_cgus = 1;
1087 cargo.env(
1088 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1089 num_cgus.to_string(),
1090 );
1091
1092 if !builder.build_script.get_forward_rustc_warnings() {
1093 cargo.stderr(Stdio::inherit());
1094 }
1095 cargo.current_dir(path_to_crate);
1096 log::debug!("building shaders with `{cargo:?}`");
1097 let build = cargo.output().expect("failed to execute cargo build");
1098
1099 if builder.build_script.get_forward_rustc_warnings() {
1100 let stderr = String::from_utf8_lossy(&build.stderr);
1101 for line in stderr.lines() {
1102 println!("cargo::warning={line}");
1103 }
1104 }
1105
1106 // `get_last_artifact` has the side-effect of printing invalid lines, so
1107 // we do that even in case of an error, to let through any useful messages
1108 // that ended up on stdout instead of stderr.
1109 let stdout = String::from_utf8(build.stdout).unwrap();
1110 if build.status.success() {
1111 get_sole_artifact(&stdout).ok_or(SpirvBuilderError::NoArtifactProduced { stdout })
1112 } else {
1113 Err(SpirvBuilderError::BuildFailed)
1114 }
1115}
1116
1117#[derive(Deserialize)]
1118struct RustcOutput {
1119 reason: String,
1120 filenames: Option<Vec<String>>,
1121}
1122
1123const ARTIFACT_SUFFIX: &str = ".spv.json";
1124
1125fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1126 let mut last_compiler_artifact = None;
1127 for line in out.lines() {
1128 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1129 // Pass through invalid lines
1130 println!("{line}");
1131 continue;
1132 };
1133 if msg.reason == "compiler-artifact" {
1134 last_compiler_artifact = Some(msg);
1135 }
1136 }
1137 let last_compiler_artifact =
1138 last_compiler_artifact.expect("Did not find output file in rustc output");
1139
1140 let mut filenames = last_compiler_artifact
1141 .filenames
1142 .unwrap()
1143 .into_iter()
1144 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1145 let filename = filenames.next()?;
1146 assert_eq!(
1147 filenames.next(),
1148 None,
1149 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1150 );
1151 Some(filename.into())
1152}
1153
1154/// Internally iterate through the leaf dependencies of the artifact at `artifact`
1155fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1156 let deps_file = artifact.with_extension("d");
1157 let mut deps_map = HashMap::new();
1158 depfile::read_deps_file(&deps_file, |item, deps| {
1159 deps_map.insert(item, deps);
1160 Ok(())
1161 })?;
1162 fn recurse(
1163 map: &HashMap<RawString, Vec<RawString>>,
1164 artifact: &RawStr,
1165 handle: &mut impl FnMut(&RawStr),
1166 ) {
1167 match map.get(artifact) {
1168 Some(entries) => {
1169 for entry in entries {
1170 recurse(map, entry, handle);
1171 }
1172 }
1173 None => handle(artifact),
1174 }
1175 }
1176 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1177 Ok(())
1178}