Skip to main content

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