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 = "watch")]
95pub use self::watch::Watch;
96
97#[cfg(feature = "include-target-specs")]
98pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH;
99
100#[derive(Debug, Error)]
101#[non_exhaustive]
102pub enum SpirvBuilderError {
103 #[error("`target` must be set, for example `spirv-unknown-vulkan1.2`")]
104 MissingTarget,
105 #[error("expected `{SPIRV_TARGET_PREFIX}...` target, found `{target}`")]
106 NonSpirvTarget { target: String },
107 #[error("SPIR-V target `{SPIRV_TARGET_PREFIX}-{target_env}` is not supported")]
108 UnsupportedSpirvTargetEnv { target_env: String },
109 #[error("`path_to_crate` must be set")]
110 MissingCratePath,
111 #[error("crate path '{0}' does not exist")]
112 CratePathDoesntExist(PathBuf),
113 #[error(
114 "Without feature `rustc_codegen_spirv`, you need to set the path of the dylib with `rustc_codegen_spirv_location`"
115 )]
116 MissingRustcCodegenSpirvDylib,
117 #[error("`rustc_codegen_spirv_location` path '{0}' is not a file")]
118 RustcCodegenSpirvDylibDoesNotExist(PathBuf),
119 #[error(
120 "Without feature `include-target-specs`, instead of setting a `target`, \
121 you need to set the path of the target spec file of your particular target with `path_to_target_spec`"
122 )]
123 MissingTargetSpec,
124 #[error("build failed")]
125 BuildFailed,
126 #[error("multi-module build cannot be used with print_metadata = MetadataPrintout::Full")]
127 MultiModuleWithPrintMetadata,
128 #[error("watching within build scripts will prevent build completion")]
129 WatchWithPrintMetadata,
130 #[error("multi-module metadata file missing")]
131 MetadataFileMissing(#[from] std::io::Error),
132 #[error("unable to parse multi-module metadata file")]
133 MetadataFileMalformed(#[from] serde_json::Error),
134 #[error("cargo metadata error")]
135 CargoMetadata(#[from] cargo_metadata::Error),
136}
137
138const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-";
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 MetadataPrintout {
144 #[default]
146 None,
147 DependencyOnly,
149 Full,
153}
154
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 SpirvMetadata {
159 #[default]
161 None,
162 NameVariables,
165 Full,
167}
168
169#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
171#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
172#[non_exhaustive]
173pub enum ShaderPanicStrategy {
174 #[default]
180 SilentExit,
181
182 #[cfg_attr(feature = "clap", clap(skip))]
252 DebugPrintfThenExit {
253 print_inputs: bool,
256
257 print_backtrace: bool,
264 },
265
266 #[allow(non_camel_case_types)]
277 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
278}
279
280#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
283#[cfg_attr(feature = "clap", derive(clap::Parser))]
284#[non_exhaustive]
285pub struct ValidatorOptions {
286 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
297 pub relax_struct_store: bool,
298 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
305 pub relax_logical_pointer: bool,
306 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
326 pub relax_block_layout: Option<bool>,
327 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
330 pub uniform_buffer_standard_layout: bool,
331 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
348 pub scalar_block_layout: bool,
349 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
352 pub skip_block_layout: bool,
353 }
356
357#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
360#[cfg_attr(feature = "clap", derive(clap::Parser))]
361#[non_exhaustive]
362pub struct OptimizerOptions {
363 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
370 pub preserve_bindings: bool,
371 }
375
376#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
378#[cfg_attr(feature = "clap", derive(clap::Parser))]
379#[non_exhaustive]
380pub struct ShaderCrateFeatures {
381 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
383 pub default_features: bool,
384 #[cfg_attr(feature = "clap", clap(long))]
386 pub features: Vec<String>,
387}
388
389impl Default for ShaderCrateFeatures {
390 fn default() -> Self {
391 Self {
392 default_features: true,
393 features: Vec::new(),
394 }
395 }
396}
397
398#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
399#[cfg_attr(feature = "clap", derive(clap::Parser))]
400#[non_exhaustive]
401pub struct SpirvBuilder {
402 #[cfg_attr(feature = "clap", clap(skip))]
403 pub path_to_crate: Option<PathBuf>,
404 #[cfg_attr(feature = "clap", clap(skip))]
407 pub print_metadata: MetadataPrintout,
408 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
410 pub release: bool,
411 #[cfg_attr(
413 feature = "clap",
414 clap(long, default_value = "spirv-unknown-vulkan1.2")
415 )]
416 pub target: Option<String>,
417 #[cfg_attr(feature = "clap", clap(flatten))]
419 #[serde(flatten)]
420 pub shader_crate_features: ShaderCrateFeatures,
421 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
423 pub deny_warnings: bool,
424 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
428 pub multimodule: bool,
429 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
432 pub spirv_metadata: SpirvMetadata,
433 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
436 pub capabilities: Vec<Capability>,
437 #[cfg_attr(feature = "clap", arg(long))]
440 pub extensions: Vec<String>,
441 #[cfg_attr(feature = "clap", clap(skip))]
444 pub extra_args: Vec<String>,
445 #[cfg_attr(feature = "clap", clap(skip))]
447 pub rustc_codegen_spirv_location: Option<PathBuf>,
448 #[cfg_attr(feature = "clap", clap(skip))]
450 pub toolchain_overwrite: Option<String>,
451 #[cfg_attr(feature = "clap", clap(skip))]
453 pub toolchain_rustc_version: Option<Version>,
454
455 #[cfg_attr(feature = "clap", clap(skip))]
460 pub path_to_target_spec: Option<PathBuf>,
461 #[cfg_attr(feature = "clap", clap(skip))]
465 pub target_dir_path: Option<PathBuf>,
466
467 #[cfg_attr(feature = "clap", clap(skip))]
470 pub shader_panic_strategy: ShaderPanicStrategy,
471
472 #[cfg_attr(feature = "clap", clap(flatten))]
474 #[serde(flatten)]
475 pub validator: ValidatorOptions,
476
477 #[cfg_attr(feature = "clap", clap(flatten))]
479 #[serde(flatten)]
480 pub optimizer: OptimizerOptions,
481}
482
483#[cfg(feature = "clap")]
484impl SpirvBuilder {
485 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
487 use core::str::FromStr;
488 Capability::from_str(capability).map_or_else(
489 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
490 Ok,
491 )
492 }
493}
494
495impl Default for SpirvBuilder {
496 fn default() -> Self {
497 Self {
498 path_to_crate: None,
499 print_metadata: MetadataPrintout::default(),
500 release: true,
501 target: None,
502 deny_warnings: false,
503 multimodule: false,
504 spirv_metadata: SpirvMetadata::default(),
505 capabilities: Vec::new(),
506 extensions: Vec::new(),
507 extra_args: Vec::new(),
508 rustc_codegen_spirv_location: None,
509 path_to_target_spec: None,
510 target_dir_path: None,
511 toolchain_overwrite: None,
512 toolchain_rustc_version: None,
513 shader_panic_strategy: ShaderPanicStrategy::default(),
514 validator: ValidatorOptions::default(),
515 optimizer: OptimizerOptions::default(),
516 shader_crate_features: ShaderCrateFeatures::default(),
517 }
518 }
519}
520
521impl SpirvBuilder {
522 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
523 Self {
524 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
525 target: Some(target.into()),
526 ..SpirvBuilder::default()
527 }
528 }
529
530 #[must_use]
535 pub fn target_spec(mut self, p: impl AsRef<Path>) -> Self {
536 self.path_to_target_spec = Some(p.as_ref().to_path_buf());
537 self
538 }
539
540 #[must_use]
542 pub fn print_metadata(mut self, v: MetadataPrintout) -> Self {
543 self.print_metadata = v;
544 self
545 }
546
547 #[must_use]
548 pub fn deny_warnings(mut self, v: bool) -> Self {
549 self.deny_warnings = v;
550 self
551 }
552
553 #[must_use]
555 pub fn release(mut self, v: bool) -> Self {
556 self.release = v;
557 self
558 }
559
560 #[must_use]
564 pub fn multimodule(mut self, v: bool) -> Self {
565 self.multimodule = v;
566 self
567 }
568
569 #[must_use]
572 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
573 self.spirv_metadata = v;
574 self
575 }
576
577 #[must_use]
580 pub fn capability(mut self, capability: Capability) -> Self {
581 self.capabilities.push(capability);
582 self
583 }
584
585 #[must_use]
588 pub fn extension(mut self, extension: impl Into<String>) -> Self {
589 self.extensions.push(extension.into());
590 self
591 }
592
593 #[must_use]
595 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
596 self.shader_panic_strategy = shader_panic_strategy;
597 self
598 }
599
600 #[must_use]
602 pub fn relax_struct_store(mut self, v: bool) -> Self {
603 self.validator.relax_struct_store = v;
604 self
605 }
606
607 #[must_use]
610 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
611 self.validator.relax_logical_pointer = v;
612 self
613 }
614
615 #[must_use]
618 pub fn relax_block_layout(mut self, v: bool) -> Self {
619 self.validator.relax_block_layout = Some(v);
620 self
621 }
622
623 #[must_use]
626 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
627 self.validator.uniform_buffer_standard_layout = v;
628 self
629 }
630
631 #[must_use]
635 pub fn scalar_block_layout(mut self, v: bool) -> Self {
636 self.validator.scalar_block_layout = v;
637 self
638 }
639
640 #[must_use]
643 pub fn skip_block_layout(mut self, v: bool) -> Self {
644 self.validator.skip_block_layout = v;
645 self
646 }
647
648 #[must_use]
650 pub fn preserve_bindings(mut self, v: bool) -> Self {
651 self.optimizer.preserve_bindings = v;
652 self
653 }
654
655 #[must_use]
658 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
659 self.extra_args.push(arg.into());
660 self
661 }
662
663 #[must_use]
665 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
666 self.shader_crate_features.default_features = default_features;
667 self
668 }
669
670 #[must_use]
672 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
673 self.shader_crate_features.features = features.into_iter().collect();
674 self
675 }
676
677 #[must_use]
678 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
679 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
680 self
681 }
682
683 #[must_use]
687 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
688 self.target_dir_path = Some(name.into());
689 self
690 }
691
692 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
695 let metadata_file = invoke_rustc(self)?;
696 match self.print_metadata {
697 MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
698 leaf_deps(&metadata_file, |artifact| {
699 println!("cargo:rerun-if-changed={artifact}");
700 })
701 .map_err(SpirvBuilderError::MetadataFileMissing)?;
703 }
704 MetadataPrintout::None => (),
705 }
706 let metadata = self.parse_metadata_file(&metadata_file)?;
707
708 Ok(metadata)
709 }
710
711 pub(crate) fn parse_metadata_file(
712 &self,
713 at: &Path,
714 ) -> Result<CompileResult, SpirvBuilderError> {
715 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
716 let metadata: CompileResult =
718 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
719 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
720 match &metadata.module {
721 ModuleResult::SingleModule(spirv_module) => {
722 assert!(!self.multimodule);
723 let env_var = format!(
724 "{}.spv",
725 at.file_name()
726 .unwrap()
727 .to_str()
728 .unwrap()
729 .strip_suffix(ARTIFACT_SUFFIX)
730 .unwrap()
731 );
732 if self.print_metadata == MetadataPrintout::Full {
733 println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
734 }
735 }
736 ModuleResult::MultiModule(_) => {
737 assert!(self.multimodule);
738 }
739 }
740 Ok(metadata)
741 }
742}
743
744fn dylib_path_envvar() -> &'static str {
746 if cfg!(windows) {
747 "PATH"
748 } else if cfg!(target_os = "macos") {
749 "DYLD_FALLBACK_LIBRARY_PATH"
750 } else {
751 "LD_LIBRARY_PATH"
752 }
753}
754fn dylib_path() -> Vec<PathBuf> {
755 match env::var_os(dylib_path_envvar()) {
756 Some(var) => env::split_paths(&var).collect(),
757 None => Vec::new(),
758 }
759}
760
761fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
762 if cfg!(feature = "rustc_codegen_spirv") {
763 let filename = format!(
764 "{}rustc_codegen_spirv{}",
765 env::consts::DLL_PREFIX,
766 env::consts::DLL_SUFFIX
767 );
768 for mut path in dylib_path() {
769 path.push(&filename);
770 if path.is_file() {
771 return Ok(path);
772 }
773 }
774 panic!("Could not find {filename} in library path");
775 } else {
776 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
777 }
778}
779
780fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
783 for s in &strings {
784 let s = s.borrow();
785 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
786 }
787 strings.join(sep)
788}
789
790fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
792 let target = builder
793 .target
794 .as_ref()
795 .ok_or(SpirvBuilderError::MissingTarget)?;
796 let path_to_crate = builder
797 .path_to_crate
798 .as_ref()
799 .ok_or(SpirvBuilderError::MissingCratePath)?;
800 {
801 let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| {
802 SpirvBuilderError::NonSpirvTarget {
803 target: target.clone(),
804 }
805 })?;
806 #[allow(clippy::match_same_arms)]
808 match target_env {
809 "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {}
815 "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {}
816 "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3"
817 | "vulkan1.4" => {}
818
819 _ => {
820 return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv {
821 target_env: target_env.into(),
822 });
823 }
824 }
825
826 if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule {
827 return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
828 }
829 if !path_to_crate.is_dir() {
830 return Err(SpirvBuilderError::CratePathDoesntExist(
831 path_to_crate.clone(),
832 ));
833 }
834 }
835
836 let toolchain_rustc_version =
837 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
838 toolchain_rustc_version.clone()
839 } else {
840 query_rustc_version(builder.toolchain_overwrite.as_deref())?
841 };
842
843 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
852 .transpose()
853 .unwrap_or_else(find_rustc_codegen_spirv)?;
854 if !rustc_codegen_spirv.is_file() {
855 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
856 rustc_codegen_spirv,
857 ));
858 }
859
860 let mut rustflags = vec![
861 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
862 "-Zbinary-dep-depinfo".to_string(),
866 "-Csymbol-mangling-version=v0".to_string(),
867 "-Zcrate-attr=feature(register_tool)".to_string(),
868 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
869 "-Coverflow-checks=off".to_string(),
872 "-Cdebug-assertions=off".to_string(),
873 "-Zinline-mir=off".to_string(),
876 "-Zmir-enable-passes=-GVN".to_string(),
881 "-Zshare-generics=off".to_string(),
888 ];
889
890 let tracked_env_var_get = |name| {
892 if let MetadataPrintout::Full | MetadataPrintout::DependencyOnly = builder.print_metadata {
893 println!("cargo:rerun-if-env-changed={name}");
894 }
895 env::var(name)
896 };
897
898 let mut llvm_args = vec![];
899 if builder.multimodule {
900 llvm_args.push("--module-output=multiple".to_string());
901 }
902 match builder.spirv_metadata {
903 SpirvMetadata::None => (),
904 SpirvMetadata::NameVariables => {
905 llvm_args.push("--spirv-metadata=name-variables".to_string());
906 }
907 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
908 }
909 if builder.validator.relax_struct_store {
910 llvm_args.push("--relax-struct-store".to_string());
911 }
912 if builder.validator.relax_logical_pointer {
913 llvm_args.push("--relax-logical-pointer".to_string());
914 }
915 if builder.validator.relax_block_layout.unwrap_or(false) {
916 llvm_args.push("--relax-block-layout".to_string());
917 }
918 if builder.validator.uniform_buffer_standard_layout {
919 llvm_args.push("--uniform-buffer-standard-layout".to_string());
920 }
921 if builder.validator.scalar_block_layout {
922 llvm_args.push("--scalar-block-layout".to_string());
923 }
924 if builder.validator.skip_block_layout {
925 llvm_args.push("--skip-block-layout".to_string());
926 }
927 if builder.optimizer.preserve_bindings {
928 llvm_args.push("--preserve-bindings".to_string());
929 }
930 let mut target_features = vec![];
931 let abort_strategy = match builder.shader_panic_strategy {
932 ShaderPanicStrategy::SilentExit => None,
933 ShaderPanicStrategy::DebugPrintfThenExit {
934 print_inputs,
935 print_backtrace,
936 } => {
937 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
938 Some(format!(
939 "debug-printf{}{}",
940 if print_inputs { "+inputs" } else { "" },
941 if print_backtrace { "+backtrace" } else { "" }
942 ))
943 }
944 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
945 Some("unreachable".into())
946 }
947 };
948 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
949
950 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
951 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
952 } else {
953 llvm_args.extend(builder.extra_args.iter().cloned());
954 }
955
956 let llvm_args = join_checking_for_separators(llvm_args, " ");
957 if !llvm_args.is_empty() {
958 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
959 }
960
961 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
962 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
963 let target_features = join_checking_for_separators(target_features, ",");
964 if !target_features.is_empty() {
965 rustflags.push(["-Ctarget-feature=", &target_features].concat());
966 }
967
968 if builder.deny_warnings {
969 rustflags.push("-Dwarnings".to_string());
970 }
971
972 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
973 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
974 }
975
976 let target_dir_path = builder
977 .target_dir_path
978 .clone()
979 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
980 let target_dir = if target_dir_path.is_absolute() {
981 target_dir_path
982 } else {
983 let metadata = cargo_metadata::MetadataCommand::new()
984 .current_dir(path_to_crate)
985 .exec()?;
986 metadata
987 .target_directory
988 .into_std_path_buf()
989 .join(target_dir_path)
990 };
991
992 let profile = if builder.release { "release" } else { "dev" };
993
994 let mut cargo = cargo_cmd::CargoCmd::new();
995 if let Some(toolchain) = &builder.toolchain_overwrite {
996 cargo.arg(format!("+{toolchain}"));
997 }
998 cargo.args([
999 "build",
1000 "--lib",
1001 "--message-format=json-render-diagnostics",
1002 "-Zbuild-std=core",
1003 "-Zbuild-std-features=compiler-builtins-mem",
1004 "--profile",
1005 profile,
1006 ]);
1007
1008 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
1009 cargo.args(extra_cargoflags.split_whitespace());
1010 }
1011
1012 if toolchain_rustc_version >= Version::new(1, 76, 0) {
1019 let path_opt = builder.path_to_target_spec.clone();
1020 let path;
1021 #[cfg(feature = "include-target-specs")]
1022 {
1023 path = path_opt
1024 .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json")));
1025 }
1026 #[cfg(not(feature = "include-target-specs"))]
1027 {
1028 path = path_opt.ok_or(SpirvBuilderError::MissingTargetSpec)?;
1029 }
1030 cargo.arg("--target").arg(path);
1031 } else {
1032 cargo.arg("--target").arg(target);
1033 }
1034
1035 if !builder.shader_crate_features.default_features {
1036 cargo.arg("--no-default-features");
1037 }
1038
1039 if !builder.shader_crate_features.features.is_empty() {
1040 cargo
1041 .arg("--features")
1042 .arg(builder.shader_crate_features.features.join(","));
1043 }
1044
1045 cargo.arg("--target-dir").arg(target_dir);
1046
1047 cargo.env(
1051 "CARGO_ENCODED_RUSTFLAGS",
1052 join_checking_for_separators(rustflags, "\x1f"),
1053 );
1054
1055 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1058 let num_cgus = 1;
1059 cargo.env(
1060 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1061 num_cgus.to_string(),
1062 );
1063
1064 cargo.stderr(Stdio::inherit()).current_dir(path_to_crate);
1065 log::debug!("building shaders with `{cargo:?}`");
1066 let build = cargo.output().expect("failed to execute cargo build");
1067
1068 let stdout = String::from_utf8(build.stdout).unwrap();
1072 if build.status.success() {
1073 get_sole_artifact(&stdout).ok_or_else(|| {
1074 eprintln!("--- build output ---\n{stdout}");
1075 panic!(
1076 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output (see above). Verify that `crate-type` is set correctly"
1077 );
1078 })
1079 } else {
1080 Err(SpirvBuilderError::BuildFailed)
1081 }
1082}
1083
1084#[derive(Deserialize)]
1085struct RustcOutput {
1086 reason: String,
1087 filenames: Option<Vec<String>>,
1088}
1089
1090const ARTIFACT_SUFFIX: &str = ".spv.json";
1091
1092fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1093 let mut last_compiler_artifact = None;
1094 for line in out.lines() {
1095 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1096 println!("{line}");
1098 continue;
1099 };
1100 if msg.reason == "compiler-artifact" {
1101 last_compiler_artifact = Some(msg);
1102 }
1103 }
1104 let last_compiler_artifact =
1105 last_compiler_artifact.expect("Did not find output file in rustc output");
1106
1107 let mut filenames = last_compiler_artifact
1108 .filenames
1109 .unwrap()
1110 .into_iter()
1111 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1112 let filename = filenames.next()?;
1113 assert_eq!(
1114 filenames.next(),
1115 None,
1116 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1117 );
1118 Some(filename.into())
1119}
1120
1121fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1123 let deps_file = artifact.with_extension("d");
1124 let mut deps_map = HashMap::new();
1125 depfile::read_deps_file(&deps_file, |item, deps| {
1126 deps_map.insert(item, deps);
1127 Ok(())
1128 })?;
1129 fn recurse(
1130 map: &HashMap<RawString, Vec<RawString>>,
1131 artifact: &RawStr,
1132 handle: &mut impl FnMut(&RawStr),
1133 ) {
1134 match map.get(artifact) {
1135 Some(entries) => {
1136 for entry in entries {
1137 recurse(map, entry, handle);
1138 }
1139 }
1140 None => handle(artifact),
1141 }
1142 }
1143 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1144 Ok(())
1145}
1146
1147pub fn query_rustc_version(toolchain: Option<&str>) -> std::io::Result<Version> {
1148 let mut cmd = Command::new("rustc");
1149 if let Some(toolchain) = toolchain {
1150 cmd.arg(format!("+{toolchain}"));
1151 }
1152 cmd.arg("--version");
1153 let output = cmd.output()?;
1154
1155 let stdout = String::from_utf8(output.stdout).expect("stdout must be utf-8");
1156 let parse = |output: &str| {
1157 let output = output.strip_prefix("rustc ")?;
1158 let version = &output[..output.find(|c| !"0123456789.".contains(c))?];
1159 Version::parse(version).ok()
1160 };
1161 Ok(parse(&stdout)
1162 .unwrap_or_else(|| panic!("failed parsing `rustc --version` output `{stdout}`")))
1163}