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_enum,
16 clippy::enum_glob_use,
17 clippy::exit,
18 clippy::expl_impl_clone_on_copy,
19 clippy::explicit_deref_methods,
20 clippy::explicit_into_iter_loop,
21 clippy::fallible_impl_from,
22 clippy::filter_map_next,
23 clippy::float_cmp_const,
24 clippy::fn_params_excessive_bools,
25 clippy::if_let_mutex,
26 clippy::implicit_clone,
27 clippy::imprecise_flops,
28 clippy::inefficient_to_string,
29 clippy::invalid_upcast_comparisons,
30 clippy::large_types_passed_by_value,
31 clippy::let_unit_value,
32 clippy::linkedlist,
33 clippy::lossy_float_literal,
34 clippy::macro_use_imports,
35 clippy::manual_ok_or,
36 clippy::map_err_ignore,
37 clippy::map_flatten,
38 clippy::map_unwrap_or,
39 clippy::match_same_arms,
40 clippy::match_wildcard_for_single_variants,
41 clippy::mem_forget,
42 clippy::mut_mut,
43 clippy::mutex_integer,
44 clippy::needless_borrow,
45 clippy::needless_continue,
46 clippy::option_option,
47 clippy::path_buf_push_overwrite,
48 clippy::ptr_as_ptr,
49 clippy::ref_option_ref,
50 clippy::rest_pat_in_fully_bound_structs,
51 clippy::same_functions_in_if_condition,
52 clippy::semicolon_if_nothing_returned,
53 clippy::string_add_assign,
54 clippy::string_add,
55 clippy::string_lit_as_bytes,
56 clippy::string_to_string,
57 clippy::todo,
58 clippy::trait_duplication_in_bounds,
59 clippy::unimplemented,
60 clippy::unnested_or_patterns,
61 clippy::unused_self,
62 clippy::useless_transmute,
63 clippy::verbose_file_reads,
64 clippy::zero_sized_map_values,
65 future_incompatible,
66 nonstandard_style,
67 rust_2018_idioms
68)]
69#![doc = include_str!("../README.md")]
73
74pub mod cargo_cmd;
75mod depfile;
76#[cfg(feature = "watch")]
77mod watch;
78
79use raw_string::{RawStr, RawString};
80use semver::Version;
81use serde::Deserialize;
82use std::borrow::Borrow;
83use std::collections::HashMap;
84use std::env;
85use std::fs::File;
86use std::io::BufReader;
87use std::path::{Path, PathBuf};
88use std::process::{Command, Stdio};
89use thiserror::Error;
90
91pub use rustc_codegen_spirv_types::Capability;
92pub use rustc_codegen_spirv_types::{CompileResult, ModuleResult};
93
94#[cfg(feature = "include-target-specs")]
95pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH;
96
97#[derive(Debug, Error)]
98#[non_exhaustive]
99pub enum SpirvBuilderError {
100 #[error("`target` must be set, for example `spirv-unknown-vulkan1.2`")]
101 MissingTarget,
102 #[error("expected `{SPIRV_TARGET_PREFIX}...` target, found `{target}`")]
103 NonSpirvTarget { target: String },
104 #[error("SPIR-V target `{SPIRV_TARGET_PREFIX}-{target_env}` is not supported")]
105 UnsupportedSpirvTargetEnv { target_env: String },
106 #[error("`path_to_crate` must be set")]
107 MissingCratePath,
108 #[error("crate path '{0}' does not exist")]
109 CratePathDoesntExist(PathBuf),
110 #[error(
111 "Without feature `rustc_codegen_spirv`, you need to set the path of the dylib with `rustc_codegen_spirv_location`"
112 )]
113 MissingRustcCodegenSpirvDylib,
114 #[error("`rustc_codegen_spirv_location` path '{0}' is not a file")]
115 RustcCodegenSpirvDylibDoesNotExist(PathBuf),
116 #[error(
117 "Without feature `include-target-specs`, instead of setting a `target`, \
118 you need to set the path of the target spec file of your particular target with `path_to_target_spec`"
119 )]
120 MissingTargetSpec,
121 #[error("build failed")]
122 BuildFailed,
123 #[error("multi-module build cannot be used with print_metadata = MetadataPrintout::Full")]
124 MultiModuleWithPrintMetadata,
125 #[error("watching within build scripts will prevent build completion")]
126 WatchWithPrintMetadata,
127 #[error("multi-module metadata file missing")]
128 MetadataFileMissing(#[from] std::io::Error),
129 #[error("unable to parse multi-module metadata file")]
130 MetadataFileMalformed(#[from] serde_json::Error),
131 #[error("cargo metadata error")]
132 CargoMetadata(#[from] cargo_metadata::Error),
133}
134
135const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-";
136
137#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
138#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
139#[non_exhaustive]
140pub enum MetadataPrintout {
141 #[default]
143 None,
144 DependencyOnly,
146 Full,
150}
151
152#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
153#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
154#[non_exhaustive]
155pub enum SpirvMetadata {
156 #[default]
158 None,
159 NameVariables,
162 Full,
164}
165
166#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
168#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
169#[non_exhaustive]
170pub enum ShaderPanicStrategy {
171 #[default]
177 SilentExit,
178
179 #[cfg_attr(feature = "clap", clap(skip))]
249 DebugPrintfThenExit {
250 print_inputs: bool,
253
254 print_backtrace: bool,
261 },
262
263 #[allow(non_camel_case_types)]
274 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
275}
276
277#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
280#[cfg_attr(feature = "clap", derive(clap::Parser))]
281#[non_exhaustive]
282pub struct ValidatorOptions {
283 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
294 pub relax_struct_store: bool,
295 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
302 pub relax_logical_pointer: bool,
303 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
323 pub relax_block_layout: Option<bool>,
324 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
327 pub uniform_buffer_standard_layout: bool,
328 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
345 pub scalar_block_layout: bool,
346 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
349 pub skip_block_layout: bool,
350 }
353
354#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
357#[cfg_attr(feature = "clap", derive(clap::Parser))]
358#[non_exhaustive]
359pub struct OptimizerOptions {
360 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
367 pub preserve_bindings: bool,
368 }
372
373#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
375#[cfg_attr(feature = "clap", derive(clap::Parser))]
376#[non_exhaustive]
377pub struct ShaderCrateFeatures {
378 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
380 pub default_features: bool,
381 #[cfg_attr(feature = "clap", clap(long))]
383 pub features: Vec<String>,
384}
385
386impl Default for ShaderCrateFeatures {
387 fn default() -> Self {
388 Self {
389 default_features: true,
390 features: Vec::new(),
391 }
392 }
393}
394
395#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
396#[cfg_attr(feature = "clap", derive(clap::Parser))]
397#[non_exhaustive]
398pub struct SpirvBuilder {
399 #[cfg_attr(feature = "clap", clap(skip))]
400 pub path_to_crate: Option<PathBuf>,
401 #[cfg_attr(feature = "clap", clap(skip))]
404 pub print_metadata: MetadataPrintout,
405 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
407 pub release: bool,
408 #[cfg_attr(
410 feature = "clap",
411 clap(long, default_value = "spirv-unknown-vulkan1.2")
412 )]
413 pub target: Option<String>,
414 #[cfg_attr(feature = "clap", clap(flatten))]
416 #[serde(flatten)]
417 pub shader_crate_features: ShaderCrateFeatures,
418 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
420 pub deny_warnings: bool,
421 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
425 pub multimodule: bool,
426 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
429 pub spirv_metadata: SpirvMetadata,
430 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
433 pub capabilities: Vec<Capability>,
434 #[cfg_attr(feature = "clap", arg(long))]
437 pub extensions: Vec<String>,
438 #[cfg_attr(feature = "clap", clap(skip))]
441 pub extra_args: Vec<String>,
442 #[cfg_attr(feature = "clap", clap(skip))]
444 pub rustc_codegen_spirv_location: Option<PathBuf>,
445 #[cfg_attr(feature = "clap", clap(skip))]
447 pub toolchain_overwrite: Option<String>,
448 #[cfg_attr(feature = "clap", clap(skip))]
450 pub toolchain_rustc_version: Option<Version>,
451
452 #[cfg_attr(feature = "clap", clap(skip))]
457 pub path_to_target_spec: Option<PathBuf>,
458 #[cfg_attr(feature = "clap", clap(skip))]
462 pub target_dir_path: Option<PathBuf>,
463
464 #[cfg_attr(feature = "clap", clap(skip))]
467 pub shader_panic_strategy: ShaderPanicStrategy,
468
469 #[cfg_attr(feature = "clap", clap(flatten))]
471 #[serde(flatten)]
472 pub validator: ValidatorOptions,
473
474 #[cfg_attr(feature = "clap", clap(flatten))]
476 #[serde(flatten)]
477 pub optimizer: OptimizerOptions,
478}
479
480#[cfg(feature = "clap")]
481impl SpirvBuilder {
482 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
484 use core::str::FromStr;
485 Capability::from_str(capability).map_or_else(
486 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
487 Ok,
488 )
489 }
490}
491
492impl Default for SpirvBuilder {
493 fn default() -> Self {
494 Self {
495 path_to_crate: None,
496 print_metadata: MetadataPrintout::default(),
497 release: true,
498 target: None,
499 deny_warnings: false,
500 multimodule: false,
501 spirv_metadata: SpirvMetadata::default(),
502 capabilities: Vec::new(),
503 extensions: Vec::new(),
504 extra_args: Vec::new(),
505 rustc_codegen_spirv_location: None,
506 path_to_target_spec: None,
507 target_dir_path: None,
508 toolchain_overwrite: None,
509 toolchain_rustc_version: None,
510 shader_panic_strategy: ShaderPanicStrategy::default(),
511 validator: ValidatorOptions::default(),
512 optimizer: OptimizerOptions::default(),
513 shader_crate_features: ShaderCrateFeatures::default(),
514 }
515 }
516}
517
518impl SpirvBuilder {
519 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
520 Self {
521 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
522 target: Some(target.into()),
523 ..SpirvBuilder::default()
524 }
525 }
526
527 #[must_use]
532 pub fn target_spec(mut self, p: impl AsRef<Path>) -> Self {
533 self.path_to_target_spec = Some(p.as_ref().to_path_buf());
534 self
535 }
536
537 #[must_use]
539 pub fn print_metadata(mut self, v: MetadataPrintout) -> Self {
540 self.print_metadata = v;
541 self
542 }
543
544 #[must_use]
545 pub fn deny_warnings(mut self, v: bool) -> Self {
546 self.deny_warnings = v;
547 self
548 }
549
550 #[must_use]
552 pub fn release(mut self, v: bool) -> Self {
553 self.release = v;
554 self
555 }
556
557 #[must_use]
561 pub fn multimodule(mut self, v: bool) -> Self {
562 self.multimodule = v;
563 self
564 }
565
566 #[must_use]
569 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
570 self.spirv_metadata = v;
571 self
572 }
573
574 #[must_use]
577 pub fn capability(mut self, capability: Capability) -> Self {
578 self.capabilities.push(capability);
579 self
580 }
581
582 #[must_use]
585 pub fn extension(mut self, extension: impl Into<String>) -> Self {
586 self.extensions.push(extension.into());
587 self
588 }
589
590 #[must_use]
592 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
593 self.shader_panic_strategy = shader_panic_strategy;
594 self
595 }
596
597 #[must_use]
599 pub fn relax_struct_store(mut self, v: bool) -> Self {
600 self.validator.relax_struct_store = v;
601 self
602 }
603
604 #[must_use]
607 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
608 self.validator.relax_logical_pointer = v;
609 self
610 }
611
612 #[must_use]
615 pub fn relax_block_layout(mut self, v: bool) -> Self {
616 self.validator.relax_block_layout = Some(v);
617 self
618 }
619
620 #[must_use]
623 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
624 self.validator.uniform_buffer_standard_layout = v;
625 self
626 }
627
628 #[must_use]
632 pub fn scalar_block_layout(mut self, v: bool) -> Self {
633 self.validator.scalar_block_layout = v;
634 self
635 }
636
637 #[must_use]
640 pub fn skip_block_layout(mut self, v: bool) -> Self {
641 self.validator.skip_block_layout = v;
642 self
643 }
644
645 #[must_use]
647 pub fn preserve_bindings(mut self, v: bool) -> Self {
648 self.optimizer.preserve_bindings = v;
649 self
650 }
651
652 #[must_use]
655 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
656 self.extra_args.push(arg.into());
657 self
658 }
659
660 #[must_use]
662 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
663 self.shader_crate_features.default_features = default_features;
664 self
665 }
666
667 #[must_use]
669 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
670 self.shader_crate_features.features = features.into_iter().collect();
671 self
672 }
673
674 #[must_use]
675 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
676 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
677 self
678 }
679
680 #[must_use]
684 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
685 self.target_dir_path = Some(name.into());
686 self
687 }
688
689 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
692 let metadata_file = invoke_rustc(self)?;
693 match self.print_metadata {
694 MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
695 leaf_deps(&metadata_file, |artifact| {
696 println!("cargo:rerun-if-changed={artifact}");
697 })
698 .map_err(SpirvBuilderError::MetadataFileMissing)?;
700 }
701 MetadataPrintout::None => (),
702 }
703 let metadata = self.parse_metadata_file(&metadata_file)?;
704
705 Ok(metadata)
706 }
707
708 pub(crate) fn parse_metadata_file(
709 &self,
710 at: &Path,
711 ) -> Result<CompileResult, SpirvBuilderError> {
712 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
713 let metadata: CompileResult =
715 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
716 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
717 match &metadata.module {
718 ModuleResult::SingleModule(spirv_module) => {
719 assert!(!self.multimodule);
720 let env_var = format!(
721 "{}.spv",
722 at.file_name()
723 .unwrap()
724 .to_str()
725 .unwrap()
726 .strip_suffix(ARTIFACT_SUFFIX)
727 .unwrap()
728 );
729 if self.print_metadata == MetadataPrintout::Full {
730 println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
731 }
732 }
733 ModuleResult::MultiModule(_) => {
734 assert!(self.multimodule);
735 }
736 }
737 Ok(metadata)
738 }
739}
740
741fn dylib_path_envvar() -> &'static str {
743 if cfg!(windows) {
744 "PATH"
745 } else if cfg!(target_os = "macos") {
746 "DYLD_FALLBACK_LIBRARY_PATH"
747 } else {
748 "LD_LIBRARY_PATH"
749 }
750}
751fn dylib_path() -> Vec<PathBuf> {
752 match env::var_os(dylib_path_envvar()) {
753 Some(var) => env::split_paths(&var).collect(),
754 None => Vec::new(),
755 }
756}
757
758fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
759 if cfg!(feature = "rustc_codegen_spirv") {
760 let filename = format!(
761 "{}rustc_codegen_spirv{}",
762 env::consts::DLL_PREFIX,
763 env::consts::DLL_SUFFIX
764 );
765 for mut path in dylib_path() {
766 path.push(&filename);
767 if path.is_file() {
768 return Ok(path);
769 }
770 }
771 panic!("Could not find {filename} in library path");
772 } else {
773 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
774 }
775}
776
777fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
780 for s in &strings {
781 let s = s.borrow();
782 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
783 }
784 strings.join(sep)
785}
786
787fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
789 let target = builder
790 .target
791 .as_ref()
792 .ok_or(SpirvBuilderError::MissingTarget)?;
793 let path_to_crate = builder
794 .path_to_crate
795 .as_ref()
796 .ok_or(SpirvBuilderError::MissingCratePath)?;
797 {
798 let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| {
799 SpirvBuilderError::NonSpirvTarget {
800 target: target.clone(),
801 }
802 })?;
803 #[allow(clippy::match_same_arms)]
805 match target_env {
806 "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {}
812 "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {}
813 "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3"
814 | "vulkan1.4" => {}
815
816 _ => {
817 return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv {
818 target_env: target_env.into(),
819 });
820 }
821 }
822
823 if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule {
824 return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
825 }
826 if !path_to_crate.is_dir() {
827 return Err(SpirvBuilderError::CratePathDoesntExist(
828 path_to_crate.clone(),
829 ));
830 }
831 }
832
833 let toolchain_rustc_version =
834 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
835 toolchain_rustc_version.clone()
836 } else {
837 query_rustc_version(builder.toolchain_overwrite.as_deref())?
838 };
839
840 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
849 .transpose()
850 .unwrap_or_else(find_rustc_codegen_spirv)?;
851 if !rustc_codegen_spirv.is_file() {
852 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
853 rustc_codegen_spirv,
854 ));
855 }
856
857 let mut rustflags = vec![
858 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
859 "-Zbinary-dep-depinfo".to_string(),
863 "-Csymbol-mangling-version=v0".to_string(),
864 "-Zcrate-attr=feature(register_tool)".to_string(),
865 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
866 "-Coverflow-checks=off".to_string(),
869 "-Cdebug-assertions=off".to_string(),
870 "-Zinline-mir=off".to_string(),
873 "-Zmir-enable-passes=-GVN".to_string(),
878 "-Zshare-generics=off".to_string(),
885 ];
886
887 let tracked_env_var_get = |name| {
889 if let MetadataPrintout::Full | MetadataPrintout::DependencyOnly = builder.print_metadata {
890 println!("cargo:rerun-if-env-changed={name}");
891 }
892 env::var(name)
893 };
894
895 let mut llvm_args = vec![];
896 if builder.multimodule {
897 llvm_args.push("--module-output=multiple".to_string());
898 }
899 match builder.spirv_metadata {
900 SpirvMetadata::None => (),
901 SpirvMetadata::NameVariables => {
902 llvm_args.push("--spirv-metadata=name-variables".to_string());
903 }
904 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
905 }
906 if builder.validator.relax_struct_store {
907 llvm_args.push("--relax-struct-store".to_string());
908 }
909 if builder.validator.relax_logical_pointer {
910 llvm_args.push("--relax-logical-pointer".to_string());
911 }
912 if builder.validator.relax_block_layout.unwrap_or(false) {
913 llvm_args.push("--relax-block-layout".to_string());
914 }
915 if builder.validator.uniform_buffer_standard_layout {
916 llvm_args.push("--uniform-buffer-standard-layout".to_string());
917 }
918 if builder.validator.scalar_block_layout {
919 llvm_args.push("--scalar-block-layout".to_string());
920 }
921 if builder.validator.skip_block_layout {
922 llvm_args.push("--skip-block-layout".to_string());
923 }
924 if builder.optimizer.preserve_bindings {
925 llvm_args.push("--preserve-bindings".to_string());
926 }
927 let mut target_features = vec![];
928 let abort_strategy = match builder.shader_panic_strategy {
929 ShaderPanicStrategy::SilentExit => None,
930 ShaderPanicStrategy::DebugPrintfThenExit {
931 print_inputs,
932 print_backtrace,
933 } => {
934 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
935 Some(format!(
936 "debug-printf{}{}",
937 if print_inputs { "+inputs" } else { "" },
938 if print_backtrace { "+backtrace" } else { "" }
939 ))
940 }
941 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
942 Some("unreachable".into())
943 }
944 };
945 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
946
947 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
948 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
949 } else {
950 llvm_args.extend(builder.extra_args.iter().cloned());
951 }
952
953 let llvm_args = join_checking_for_separators(llvm_args, " ");
954 if !llvm_args.is_empty() {
955 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
956 }
957
958 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
959 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
960 let target_features = join_checking_for_separators(target_features, ",");
961 if !target_features.is_empty() {
962 rustflags.push(["-Ctarget-feature=", &target_features].concat());
963 }
964
965 if builder.deny_warnings {
966 rustflags.push("-Dwarnings".to_string());
967 }
968
969 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
970 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
971 }
972
973 let target_dir_path = builder
974 .target_dir_path
975 .clone()
976 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
977 let target_dir = if target_dir_path.is_absolute() {
978 target_dir_path
979 } else {
980 let metadata = cargo_metadata::MetadataCommand::new()
981 .current_dir(path_to_crate)
982 .exec()?;
983 metadata
984 .target_directory
985 .into_std_path_buf()
986 .join(target_dir_path)
987 };
988
989 let profile = if builder.release { "release" } else { "dev" };
990
991 let mut cargo = cargo_cmd::CargoCmd::new();
992 if let Some(toolchain) = &builder.toolchain_overwrite {
993 cargo.arg(format!("+{toolchain}"));
994 }
995 cargo.args([
996 "build",
997 "--lib",
998 "--message-format=json-render-diagnostics",
999 "-Zbuild-std=core",
1000 "-Zbuild-std-features=compiler-builtins-mem",
1001 "--profile",
1002 profile,
1003 ]);
1004
1005 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
1006 cargo.args(extra_cargoflags.split_whitespace());
1007 }
1008
1009 if toolchain_rustc_version >= Version::new(1, 76, 0) {
1016 let path_opt = builder.path_to_target_spec.clone();
1017 let path;
1018 #[cfg(feature = "include-target-specs")]
1019 {
1020 path = path_opt
1021 .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json")));
1022 }
1023 #[cfg(not(feature = "include-target-specs"))]
1024 {
1025 path = path_opt.ok_or(SpirvBuilderError::MissingTargetSpec)?;
1026 }
1027 cargo.arg("--target").arg(path);
1028 } else {
1029 cargo.arg("--target").arg(target);
1030 }
1031
1032 if !builder.shader_crate_features.default_features {
1033 cargo.arg("--no-default-features");
1034 }
1035
1036 if !builder.shader_crate_features.features.is_empty() {
1037 cargo
1038 .arg("--features")
1039 .arg(builder.shader_crate_features.features.join(","));
1040 }
1041
1042 cargo.arg("--target-dir").arg(target_dir);
1043
1044 cargo.env(
1048 "CARGO_ENCODED_RUSTFLAGS",
1049 join_checking_for_separators(rustflags, "\x1f"),
1050 );
1051
1052 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1055 let num_cgus = 1;
1056 cargo.env(
1057 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1058 num_cgus.to_string(),
1059 );
1060
1061 cargo.stderr(Stdio::inherit()).current_dir(path_to_crate);
1062 log::debug!("building shaders with `{cargo}`");
1063 let build = cargo.output().expect("failed to execute cargo build");
1064
1065 let stdout = String::from_utf8(build.stdout).unwrap();
1069 if build.status.success() {
1070 get_sole_artifact(&stdout).ok_or_else(|| {
1071 eprintln!("--- build output ---\n{stdout}");
1072 panic!(
1073 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output (see above). Verify that `crate-type` is set correctly"
1074 );
1075 })
1076 } else {
1077 Err(SpirvBuilderError::BuildFailed)
1078 }
1079}
1080
1081#[derive(Deserialize)]
1082struct RustcOutput {
1083 reason: String,
1084 filenames: Option<Vec<String>>,
1085}
1086
1087const ARTIFACT_SUFFIX: &str = ".spv.json";
1088
1089fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1090 let mut last_compiler_artifact = None;
1091 for line in out.lines() {
1092 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1093 println!("{line}");
1095 continue;
1096 };
1097 if msg.reason == "compiler-artifact" {
1098 last_compiler_artifact = Some(msg);
1099 }
1100 }
1101 let last_compiler_artifact =
1102 last_compiler_artifact.expect("Did not find output file in rustc output");
1103
1104 let mut filenames = last_compiler_artifact
1105 .filenames
1106 .unwrap()
1107 .into_iter()
1108 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1109 let filename = filenames.next()?;
1110 assert_eq!(
1111 filenames.next(),
1112 None,
1113 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1114 );
1115 Some(filename.into())
1116}
1117
1118fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1120 let deps_file = artifact.with_extension("d");
1121 let mut deps_map = HashMap::new();
1122 depfile::read_deps_file(&deps_file, |item, deps| {
1123 deps_map.insert(item, deps);
1124 Ok(())
1125 })?;
1126 fn recurse(
1127 map: &HashMap<RawString, Vec<RawString>>,
1128 artifact: &RawStr,
1129 handle: &mut impl FnMut(&RawStr),
1130 ) {
1131 match map.get(artifact) {
1132 Some(entries) => {
1133 for entry in entries {
1134 recurse(map, entry, handle);
1135 }
1136 }
1137 None => handle(artifact),
1138 }
1139 }
1140 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1141 Ok(())
1142}
1143
1144pub fn query_rustc_version(toolchain: Option<&str>) -> std::io::Result<Version> {
1145 let mut cmd = Command::new("rustc");
1146 if let Some(toolchain) = toolchain {
1147 cmd.arg(format!("+{toolchain}"));
1148 }
1149 cmd.arg("--version");
1150 let output = cmd.output()?;
1151
1152 let stdout = String::from_utf8(output.stdout).expect("stdout must be utf-8");
1153 let parse = |output: &str| {
1154 let output = output.strip_prefix("rustc ")?;
1155 let version = &output[..output.find(|c| !"0123456789.".contains(c))?];
1156 Version::parse(version).ok()
1157 };
1158 Ok(parse(&stdout)
1159 .unwrap_or_else(|| panic!("failed parsing `rustc --version` output `{stdout}`")))
1160}