1#![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#![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("multi-module metadata file missing")]
122 MetadataFileMissing(#[from] std::io::Error),
123 #[error("unable to parse multi-module metadata file")]
124 MetadataFileMalformed(#[from] serde_json::Error),
125 #[error(
126 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output.\n--- build output ---\n{stdout}"
127 )]
128 NoArtifactProduced { stdout: String },
129 #[error("cargo metadata error")]
130 CargoMetadata(#[from] cargo_metadata::Error),
131 #[cfg(feature = "watch")]
132 #[error(transparent)]
133 WatchFailed(#[from] SpirvWatcherError),
134}
135
136#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
137#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
138#[non_exhaustive]
139pub enum SpirvMetadata {
140 #[default]
142 None,
143 NameVariables,
146 Full,
148}
149
150#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
152#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
153#[non_exhaustive]
154pub enum ShaderPanicStrategy {
155 #[default]
161 SilentExit,
162
163 #[cfg_attr(feature = "clap", clap(skip))]
233 DebugPrintfThenExit {
234 print_inputs: bool,
237
238 print_backtrace: bool,
245 },
246
247 #[allow(non_camel_case_types)]
258 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
259}
260
261#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
264#[cfg_attr(feature = "clap", derive(clap::Parser))]
265#[non_exhaustive]
266pub struct ValidatorOptions {
267 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
278 pub relax_struct_store: bool,
279 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
286 pub relax_logical_pointer: bool,
287 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
307 pub relax_block_layout: Option<bool>,
308 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
311 pub uniform_buffer_standard_layout: bool,
312 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
329 pub scalar_block_layout: bool,
330 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
333 pub skip_block_layout: bool,
334 }
337
338#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
341#[cfg_attr(feature = "clap", derive(clap::Parser))]
342#[non_exhaustive]
343pub struct OptimizerOptions {
344 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
351 pub preserve_bindings: bool,
352 }
356
357#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
359#[cfg_attr(feature = "clap", derive(clap::Parser))]
360#[non_exhaustive]
361pub struct ShaderCrateFeatures {
362 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
364 pub default_features: bool,
365 #[cfg_attr(feature = "clap", clap(long))]
367 pub features: Vec<String>,
368}
369
370impl Default for ShaderCrateFeatures {
371 fn default() -> Self {
372 Self {
373 default_features: true,
374 features: Vec::new(),
375 }
376 }
377}
378
379#[derive(Clone, Debug, Default)]
381#[non_exhaustive]
382pub struct BuildScriptConfig {
383 pub defaults: bool,
386
387 pub dependency_info: Option<bool>,
393
394 pub env_shader_spv_path: Option<bool>,
411
412 pub forward_rustc_warnings: Option<bool>,
417
418 pub cargo_color_always: Option<bool>,
425}
426
427impl BuildScriptConfig {
429 fn get_dependency_info(&self) -> bool {
430 self.dependency_info.unwrap_or(self.defaults)
431 }
432 fn get_env_shader_spv_path(&self) -> bool {
433 self.env_shader_spv_path.unwrap_or(false)
434 }
435 fn get_forward_rustc_warnings(&self) -> bool {
436 self.forward_rustc_warnings.unwrap_or(self.defaults)
437 }
438 fn get_cargo_color_always(&self) -> bool {
439 self.cargo_color_always.unwrap_or(self.defaults)
440 }
441}
442
443#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
444#[cfg_attr(feature = "clap", derive(clap::Parser))]
445#[non_exhaustive]
446pub struct SpirvBuilder {
447 #[cfg_attr(feature = "clap", clap(skip))]
449 pub path_to_crate: Option<PathBuf>,
450 #[cfg_attr(feature = "clap", clap(skip))]
452 pub cargo_cmd: Option<String>,
453 #[cfg_attr(feature = "clap", clap(skip))]
456 pub cargo_cmd_like_rustc: Option<bool>,
457 #[cfg_attr(feature = "clap", clap(skip))]
459 #[serde(skip)]
460 pub build_script: BuildScriptConfig,
461 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
463 pub release: bool,
464 #[cfg_attr(
466 feature = "clap",
467 clap(long, default_value = "spirv-unknown-vulkan1.2")
468 )]
469 pub target: Option<String>,
470 #[cfg_attr(feature = "clap", clap(flatten))]
472 #[serde(flatten)]
473 pub shader_crate_features: ShaderCrateFeatures,
474 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
476 pub deny_warnings: bool,
477 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
481 pub multimodule: bool,
482 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
485 pub spirv_metadata: SpirvMetadata,
486 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
489 pub capabilities: Vec<Capability>,
490 #[cfg_attr(feature = "clap", arg(long))]
493 pub extensions: Vec<String>,
494 #[cfg_attr(feature = "clap", clap(skip))]
497 pub extra_args: Vec<String>,
498 #[cfg_attr(feature = "clap", clap(skip))]
500 pub rustc_codegen_spirv_location: Option<PathBuf>,
501 #[cfg_attr(feature = "clap", clap(skip))]
503 pub toolchain_overwrite: Option<String>,
504 #[cfg_attr(feature = "clap", clap(skip))]
506 pub toolchain_rustc_version: Option<Version>,
507
508 #[cfg_attr(feature = "clap", clap(skip))]
512 pub target_dir_path: Option<PathBuf>,
513
514 #[cfg_attr(feature = "clap", clap(skip))]
517 pub shader_panic_strategy: ShaderPanicStrategy,
518
519 #[cfg_attr(feature = "clap", clap(flatten))]
521 #[serde(flatten)]
522 pub validator: ValidatorOptions,
523
524 #[cfg_attr(feature = "clap", clap(flatten))]
526 #[serde(flatten)]
527 pub optimizer: OptimizerOptions,
528}
529
530#[cfg(feature = "clap")]
531impl SpirvBuilder {
532 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
534 use core::str::FromStr;
535 Capability::from_str(capability).map_or_else(
536 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
537 Ok,
538 )
539 }
540}
541
542impl Default for SpirvBuilder {
543 fn default() -> Self {
544 Self {
545 path_to_crate: None,
546 cargo_cmd: None,
547 cargo_cmd_like_rustc: None,
548 build_script: BuildScriptConfig::default(),
549 release: true,
550 target: None,
551 deny_warnings: false,
552 multimodule: false,
553 spirv_metadata: SpirvMetadata::default(),
554 capabilities: Vec::new(),
555 extensions: Vec::new(),
556 extra_args: Vec::new(),
557 rustc_codegen_spirv_location: None,
558 target_dir_path: None,
559 toolchain_overwrite: None,
560 toolchain_rustc_version: None,
561 shader_panic_strategy: ShaderPanicStrategy::default(),
562 validator: ValidatorOptions::default(),
563 optimizer: OptimizerOptions::default(),
564 shader_crate_features: ShaderCrateFeatures::default(),
565 }
566 }
567}
568
569impl SpirvBuilder {
570 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
571 Self {
572 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
573 target: Some(target.into()),
574 ..SpirvBuilder::default()
575 }
576 }
577
578 #[must_use]
579 pub fn deny_warnings(mut self, v: bool) -> Self {
580 self.deny_warnings = v;
581 self
582 }
583
584 #[must_use]
586 pub fn release(mut self, v: bool) -> Self {
587 self.release = v;
588 self
589 }
590
591 #[must_use]
595 pub fn multimodule(mut self, v: bool) -> Self {
596 self.multimodule = v;
597 self
598 }
599
600 #[must_use]
603 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
604 self.spirv_metadata = v;
605 self
606 }
607
608 #[must_use]
611 pub fn capability(mut self, capability: Capability) -> Self {
612 self.capabilities.push(capability);
613 self
614 }
615
616 #[must_use]
619 pub fn extension(mut self, extension: impl Into<String>) -> Self {
620 self.extensions.push(extension.into());
621 self
622 }
623
624 #[must_use]
626 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
627 self.shader_panic_strategy = shader_panic_strategy;
628 self
629 }
630
631 #[must_use]
633 pub fn relax_struct_store(mut self, v: bool) -> Self {
634 self.validator.relax_struct_store = v;
635 self
636 }
637
638 #[must_use]
641 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
642 self.validator.relax_logical_pointer = v;
643 self
644 }
645
646 #[must_use]
649 pub fn relax_block_layout(mut self, v: bool) -> Self {
650 self.validator.relax_block_layout = Some(v);
651 self
652 }
653
654 #[must_use]
657 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
658 self.validator.uniform_buffer_standard_layout = v;
659 self
660 }
661
662 #[must_use]
666 pub fn scalar_block_layout(mut self, v: bool) -> Self {
667 self.validator.scalar_block_layout = v;
668 self
669 }
670
671 #[must_use]
674 pub fn skip_block_layout(mut self, v: bool) -> Self {
675 self.validator.skip_block_layout = v;
676 self
677 }
678
679 #[must_use]
681 pub fn preserve_bindings(mut self, v: bool) -> Self {
682 self.optimizer.preserve_bindings = v;
683 self
684 }
685
686 #[must_use]
689 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
690 self.extra_args.push(arg.into());
691 self
692 }
693
694 #[must_use]
696 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
697 self.shader_crate_features.default_features = default_features;
698 self
699 }
700
701 #[must_use]
703 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
704 self.shader_crate_features.features = features.into_iter().collect();
705 self
706 }
707
708 #[must_use]
709 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
710 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
711 self
712 }
713
714 #[must_use]
718 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
719 self.target_dir_path = Some(name.into());
720 self
721 }
722
723 pub fn check(&mut self) -> Result<CompileResult, SpirvBuilderError> {
725 self.run_cargo_cmd("check")
726 }
727
728 pub fn clippy(&mut self) -> Result<CompileResult, SpirvBuilderError> {
730 self.run_cargo_cmd("clippy")
731 }
732
733 fn run_cargo_cmd(&mut self, cmd: &str) -> Result<CompileResult, SpirvBuilderError> {
735 let old = self.cargo_cmd.replace(cmd.into());
736 let result = self.build();
737 self.cargo_cmd = old;
738 result
739 }
740
741 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
743 let metadata_file = invoke_rustc(self)?;
744 if self.build_script.get_dependency_info() {
745 leaf_deps(&metadata_file, |artifact| {
746 println!("cargo:rerun-if-changed={artifact}");
747 })
748 .map_err(SpirvBuilderError::MetadataFileMissing)?;
750 }
751 let metadata = self.parse_metadata_file(&metadata_file)?;
752
753 Ok(metadata)
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 let metadata: CompileResult =
763 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
764 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
765 match &metadata.module {
766 ModuleResult::SingleModule(spirv_module) => {
767 assert!(!self.multimodule);
768 if self.build_script.get_env_shader_spv_path() {
769 let env_var = format!(
770 "{}.spv",
771 at.file_name()
772 .unwrap()
773 .to_str()
774 .unwrap()
775 .strip_suffix(ARTIFACT_SUFFIX)
776 .unwrap()
777 );
778 println!("cargo::rustc-env={}={}", env_var, spirv_module.display());
779 }
780 }
781 ModuleResult::MultiModule(_) => {
782 assert!(self.multimodule);
783 }
784 }
785 Ok(metadata)
786 }
787}
788
789fn dylib_path_envvar() -> &'static str {
791 if cfg!(windows) {
792 "PATH"
793 } else if cfg!(target_os = "macos") {
794 "DYLD_FALLBACK_LIBRARY_PATH"
795 } else {
796 "LD_LIBRARY_PATH"
797 }
798}
799fn dylib_path() -> Vec<PathBuf> {
800 let mut dylibs = match env::var_os(dylib_path_envvar()) {
801 Some(var) => env::split_paths(&var).collect(),
802 None => Vec::new(),
803 };
804 if let Ok(dir) = env::current_dir() {
805 dylibs.push(dir);
806 }
807 dylibs
808}
809
810fn rustc_codegen_spirv_dylib_name() -> String {
811 format!(
812 "{}rustc_codegen_spirv{}",
813 env::consts::DLL_PREFIX,
814 env::consts::DLL_SUFFIX
815 )
816}
817
818fn find_latest_hashed_rustc_codegen_spirv_in_dir(dir: &Path) -> Option<PathBuf> {
819 let prefix = format!("{}rustc_codegen_spirv-", env::consts::DLL_PREFIX);
820 let suffix = env::consts::DLL_SUFFIX;
821 let mut best_match: Option<(SystemTime, PathBuf)> = None;
822
823 for entry in std::fs::read_dir(dir).ok()?.flatten() {
824 let path = entry.path();
825 if !path.is_file() {
826 continue;
827 }
828
829 let Some(name) = path.file_name().and_then(OsStr::to_str) else {
830 continue;
831 };
832 if !name.starts_with(&prefix) || !name.ends_with(suffix) {
833 continue;
834 }
835
836 let modified = entry
837 .metadata()
838 .ok()
839 .and_then(|metadata| metadata.modified().ok())
840 .unwrap_or(SystemTime::UNIX_EPOCH);
841 match &mut best_match {
842 Some((best_modified, best_path)) if modified < *best_modified => {}
843 Some((best_modified, best_path)) => {
844 *best_modified = modified;
845 *best_path = path;
846 }
847 None => best_match = Some((modified, path)),
848 }
849 }
850
851 best_match.map(|(_, path)| path)
852}
853
854fn find_rustc_codegen_spirv_in_paths(dylib_paths: Vec<PathBuf>) -> Option<PathBuf> {
855 let exact_name = rustc_codegen_spirv_dylib_name();
856
857 for dir in &dylib_paths {
858 let path = dir.join(&exact_name);
859 if path.is_file() {
860 return Some(path);
861 }
862 }
863
864 dylib_paths
865 .into_iter()
866 .find_map(|dir| find_latest_hashed_rustc_codegen_spirv_in_dir(&dir))
867}
868
869fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
870 if cfg!(feature = "rustc_codegen_spirv") {
871 if let Some(path) = find_rustc_codegen_spirv_in_paths(dylib_path()) {
872 return Ok(path);
873 }
874 let filename = rustc_codegen_spirv_dylib_name();
875 panic!("Could not find {filename} in library path");
876 } else {
877 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
878 }
879}
880
881fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
884 for s in &strings {
885 let s = s.borrow();
886 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
887 }
888 strings.join(sep)
889}
890
891fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
893 let path_to_crate = builder
894 .path_to_crate
895 .as_ref()
896 .ok_or(SpirvBuilderError::MissingCratePath)?;
897 let target;
898 {
899 let target_str = builder
900 .target
901 .as_ref()
902 .ok_or(SpirvBuilderError::MissingTarget)?;
903 target = SpirvTarget::parse(target_str)?;
904
905 if builder.build_script.get_env_shader_spv_path() && builder.multimodule {
906 return Err(SpirvBuilderError::MultiModuleWithEnvShaderSpvPath);
907 }
908 if !path_to_crate.is_dir() {
909 return Err(SpirvBuilderError::CratePathDoesntExist(
910 path_to_crate.clone(),
911 ));
912 }
913 }
914
915 let toolchain_rustc_version =
916 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
917 toolchain_rustc_version.clone()
918 } else {
919 query_rustc_version(builder.toolchain_overwrite.as_deref())?
920 };
921
922 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
931 .transpose()
932 .unwrap_or_else(find_rustc_codegen_spirv)?;
933 if !rustc_codegen_spirv.is_file() {
934 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
935 rustc_codegen_spirv,
936 ));
937 }
938
939 let mut rustflags = vec![
940 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
941 "-Zbinary-dep-depinfo".to_string(),
945 "-Csymbol-mangling-version=v0".to_string(),
946 "-Zcrate-attr=feature(register_tool)".to_string(),
947 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
948 "-Coverflow-checks=off".to_string(),
951 "-Cdebug-assertions=off".to_string(),
952 "-Zinline-mir=off".to_string(),
955 "-Zmir-enable-passes=-GVN".to_string(),
960 "-Zshare-generics=off".to_string(),
967 ];
968
969 let tracked_env_var_get = |name| {
971 if builder.build_script.get_dependency_info() {
972 println!("cargo:rerun-if-env-changed={name}");
973 }
974 env::var(name)
975 };
976
977 let mut llvm_args = vec![];
978 if builder.multimodule {
979 llvm_args.push("--module-output=multiple".to_string());
980 }
981 match builder.spirv_metadata {
982 SpirvMetadata::None => (),
983 SpirvMetadata::NameVariables => {
984 llvm_args.push("--spirv-metadata=name-variables".to_string());
985 }
986 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
987 }
988 if builder.validator.relax_struct_store {
989 llvm_args.push("--relax-struct-store".to_string());
990 }
991 if builder.validator.relax_logical_pointer {
992 llvm_args.push("--relax-logical-pointer".to_string());
993 }
994 if builder.validator.relax_block_layout.unwrap_or(false) {
995 llvm_args.push("--relax-block-layout".to_string());
996 }
997 if builder.validator.uniform_buffer_standard_layout {
998 llvm_args.push("--uniform-buffer-standard-layout".to_string());
999 }
1000 if builder.validator.scalar_block_layout {
1001 llvm_args.push("--scalar-block-layout".to_string());
1002 }
1003 if builder.validator.skip_block_layout {
1004 llvm_args.push("--skip-block-layout".to_string());
1005 }
1006 if builder.optimizer.preserve_bindings {
1007 llvm_args.push("--preserve-bindings".to_string());
1008 }
1009 let mut target_features = vec![];
1010 let abort_strategy = match builder.shader_panic_strategy {
1011 ShaderPanicStrategy::SilentExit => None,
1012 ShaderPanicStrategy::DebugPrintfThenExit {
1013 print_inputs,
1014 print_backtrace,
1015 } => {
1016 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
1017 Some(format!(
1018 "debug-printf{}{}",
1019 if print_inputs { "+inputs" } else { "" },
1020 if print_backtrace { "+backtrace" } else { "" }
1021 ))
1022 }
1023 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
1024 Some("unreachable".into())
1025 }
1026 };
1027 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
1028
1029 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
1030 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
1031 } else {
1032 llvm_args.extend(builder.extra_args.iter().cloned());
1033 }
1034
1035 let llvm_args = join_checking_for_separators(llvm_args, " ");
1036 if !llvm_args.is_empty() {
1037 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
1038 }
1039
1040 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
1041 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
1042 let target_features = join_checking_for_separators(target_features, ",");
1043 if !target_features.is_empty() {
1044 rustflags.push(["-Ctarget-feature=", &target_features].concat());
1045 }
1046
1047 if builder.deny_warnings {
1048 rustflags.push("-Dwarnings".to_string());
1049 }
1050
1051 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
1052 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
1053 }
1054
1055 let target_dir_path = builder
1056 .target_dir_path
1057 .clone()
1058 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
1059 let target_dir = if target_dir_path.is_absolute() {
1060 target_dir_path
1061 } else {
1062 let metadata = cargo_metadata::MetadataCommand::new()
1063 .current_dir(path_to_crate)
1064 .exec()?;
1065 metadata
1066 .target_directory
1067 .into_std_path_buf()
1068 .join(target_dir_path)
1069 };
1070
1071 let mut cargo = cargo_cmd::CargoCmd::new();
1072 if let Some(toolchain) = &builder.toolchain_overwrite {
1073 cargo.arg(format!("+{toolchain}"));
1074 }
1075
1076 let cargo_cmd = builder.cargo_cmd.as_ref().map_or("rustc", |s| s.as_str());
1077 let cargo_cmd_like_rustc = builder.cargo_cmd_like_rustc.unwrap_or(cargo_cmd == "rustc");
1078 let profile = if builder.release { "release" } else { "dev" };
1079 cargo.args([
1080 cargo_cmd,
1081 "--lib",
1082 "--message-format=json-render-diagnostics",
1083 "-Zbuild-std=core",
1084 "-Zbuild-std-features=compiler-builtins-mem",
1085 "--profile",
1086 profile,
1087 ]);
1088 if cargo_cmd_like_rustc {
1089 cargo.args(["--crate-type", "dylib"]);
1095 }
1096
1097 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
1098 cargo.args(extra_cargoflags.split_whitespace());
1099 }
1100
1101 let target_spec_dir = target_dir.join("target-specs");
1102 let target_spec =
1103 TargetSpecVersion::target_arg(toolchain_rustc_version, &target, &target_spec_dir)?;
1104 target_spec.append_to_cmd(&mut cargo);
1105
1106 if !builder.shader_crate_features.default_features {
1107 cargo.arg("--no-default-features");
1108 }
1109
1110 if !builder.shader_crate_features.features.is_empty() {
1111 cargo
1112 .arg("--features")
1113 .arg(builder.shader_crate_features.features.join(","));
1114 }
1115
1116 cargo.arg("--target-dir").arg(target_dir);
1117
1118 if builder.build_script.get_forward_rustc_warnings() {
1120 cargo.args(["--quiet"]);
1122 }
1123 if builder.build_script.get_cargo_color_always() {
1124 cargo.args(["--color", "always"]);
1126 }
1127
1128 cargo.env(
1132 "CARGO_ENCODED_RUSTFLAGS",
1133 join_checking_for_separators(rustflags, "\x1f"),
1134 );
1135
1136 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1139 let num_cgus = 1;
1140 cargo.env(
1141 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1142 num_cgus.to_string(),
1143 );
1144
1145 if !builder.build_script.get_forward_rustc_warnings() {
1146 cargo.stderr(Stdio::inherit());
1147 }
1148 cargo.current_dir(path_to_crate);
1149 log::debug!("building shaders with `{cargo:?}`");
1150 let build = cargo.output().expect("failed to execute cargo build");
1151
1152 if builder.build_script.get_forward_rustc_warnings() {
1153 let stderr = String::from_utf8_lossy(&build.stderr);
1154 for line in stderr.lines() {
1155 println!("cargo::warning={line}");
1156 }
1157 }
1158
1159 let stdout = String::from_utf8(build.stdout).unwrap();
1163 if build.status.success() {
1164 get_sole_artifact(&stdout).ok_or(SpirvBuilderError::NoArtifactProduced { stdout })
1165 } else {
1166 Err(SpirvBuilderError::BuildFailed)
1167 }
1168}
1169
1170#[derive(Deserialize)]
1171struct RustcOutput {
1172 reason: String,
1173 filenames: Option<Vec<String>>,
1174}
1175
1176const ARTIFACT_SUFFIX: &str = ".spv.json";
1177
1178fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1179 let mut last_compiler_artifact = None;
1180 for line in out.lines() {
1181 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1182 println!("{line}");
1184 continue;
1185 };
1186 if msg.reason == "compiler-artifact" {
1187 last_compiler_artifact = Some(msg);
1188 }
1189 }
1190 let last_compiler_artifact =
1191 last_compiler_artifact.expect("Did not find output file in rustc output");
1192
1193 let mut filenames = last_compiler_artifact
1194 .filenames
1195 .unwrap()
1196 .into_iter()
1197 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1198 let filename = filenames.next()?;
1199 assert_eq!(
1200 filenames.next(),
1201 None,
1202 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1203 );
1204 Some(filename.into())
1205}
1206
1207fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1209 let deps_file = artifact.with_extension("d");
1210 let mut deps_map = HashMap::new();
1211 depfile::read_deps_file(&deps_file, |item, deps| {
1212 deps_map.insert(item, deps);
1213 Ok(())
1214 })?;
1215 fn recurse(
1216 map: &HashMap<RawString, Vec<RawString>>,
1217 artifact: &RawStr,
1218 handle: &mut impl FnMut(&RawStr),
1219 ) {
1220 match map.get(artifact) {
1221 Some(entries) => {
1222 for entry in entries {
1223 recurse(map, entry, handle);
1224 }
1225 }
1226 None => handle(artifact),
1227 }
1228 }
1229 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1230 Ok(())
1231}