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}