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::{SpirvWatcher, SpirvWatcherError};
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("multi-module metadata file missing")]
129 MetadataFileMissing(#[from] std::io::Error),
130 #[error("unable to parse multi-module metadata file")]
131 MetadataFileMalformed(#[from] serde_json::Error),
132 #[error("cargo metadata error")]
133 CargoMetadata(#[from] cargo_metadata::Error),
134 #[cfg(feature = "watch")]
135 #[error(transparent)]
136 WatchFailed(#[from] SpirvWatcherError),
137}
138
139const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-";
140
141#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
142#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
143#[non_exhaustive]
144pub enum MetadataPrintout {
145 #[default]
147 None,
148 DependencyOnly,
150 Full,
154}
155
156#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
157#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
158#[non_exhaustive]
159pub enum SpirvMetadata {
160 #[default]
162 None,
163 NameVariables,
166 Full,
168}
169
170#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
172#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
173#[non_exhaustive]
174pub enum ShaderPanicStrategy {
175 #[default]
181 SilentExit,
182
183 #[cfg_attr(feature = "clap", clap(skip))]
253 DebugPrintfThenExit {
254 print_inputs: bool,
257
258 print_backtrace: bool,
265 },
266
267 #[allow(non_camel_case_types)]
278 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
279}
280
281#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
284#[cfg_attr(feature = "clap", derive(clap::Parser))]
285#[non_exhaustive]
286pub struct ValidatorOptions {
287 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
298 pub relax_struct_store: bool,
299 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
306 pub relax_logical_pointer: bool,
307 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
327 pub relax_block_layout: Option<bool>,
328 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
331 pub uniform_buffer_standard_layout: bool,
332 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
349 pub scalar_block_layout: bool,
350 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
353 pub skip_block_layout: bool,
354 }
357
358#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
361#[cfg_attr(feature = "clap", derive(clap::Parser))]
362#[non_exhaustive]
363pub struct OptimizerOptions {
364 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
371 pub preserve_bindings: bool,
372 }
376
377#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
379#[cfg_attr(feature = "clap", derive(clap::Parser))]
380#[non_exhaustive]
381pub struct ShaderCrateFeatures {
382 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
384 pub default_features: bool,
385 #[cfg_attr(feature = "clap", clap(long))]
387 pub features: Vec<String>,
388}
389
390impl Default for ShaderCrateFeatures {
391 fn default() -> Self {
392 Self {
393 default_features: true,
394 features: Vec::new(),
395 }
396 }
397}
398
399#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
400#[cfg_attr(feature = "clap", derive(clap::Parser))]
401#[non_exhaustive]
402pub struct SpirvBuilder {
403 #[cfg_attr(feature = "clap", clap(skip))]
405 pub path_to_crate: Option<PathBuf>,
406 #[cfg_attr(feature = "clap", clap(skip))]
408 pub cargo_cmd: Option<String>,
409 #[cfg_attr(feature = "clap", clap(skip))]
412 pub cargo_cmd_like_rustc: Option<bool>,
413 #[cfg_attr(feature = "clap", clap(skip))]
416 pub print_metadata: MetadataPrintout,
417 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
419 pub release: bool,
420 #[cfg_attr(
422 feature = "clap",
423 clap(long, default_value = "spirv-unknown-vulkan1.2")
424 )]
425 pub target: Option<String>,
426 #[cfg_attr(feature = "clap", clap(flatten))]
428 #[serde(flatten)]
429 pub shader_crate_features: ShaderCrateFeatures,
430 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
432 pub deny_warnings: bool,
433 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
437 pub multimodule: bool,
438 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
441 pub spirv_metadata: SpirvMetadata,
442 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
445 pub capabilities: Vec<Capability>,
446 #[cfg_attr(feature = "clap", arg(long))]
449 pub extensions: Vec<String>,
450 #[cfg_attr(feature = "clap", clap(skip))]
453 pub extra_args: Vec<String>,
454 #[cfg_attr(feature = "clap", clap(skip))]
456 pub rustc_codegen_spirv_location: Option<PathBuf>,
457 #[cfg_attr(feature = "clap", clap(skip))]
459 pub toolchain_overwrite: Option<String>,
460 #[cfg_attr(feature = "clap", clap(skip))]
462 pub toolchain_rustc_version: Option<Version>,
463
464 #[cfg_attr(feature = "clap", clap(skip))]
469 pub path_to_target_spec: Option<PathBuf>,
470 #[cfg_attr(feature = "clap", clap(skip))]
474 pub target_dir_path: Option<PathBuf>,
475
476 #[cfg_attr(feature = "clap", clap(skip))]
479 pub shader_panic_strategy: ShaderPanicStrategy,
480
481 #[cfg_attr(feature = "clap", clap(flatten))]
483 #[serde(flatten)]
484 pub validator: ValidatorOptions,
485
486 #[cfg_attr(feature = "clap", clap(flatten))]
488 #[serde(flatten)]
489 pub optimizer: OptimizerOptions,
490}
491
492#[cfg(feature = "clap")]
493impl SpirvBuilder {
494 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
496 use core::str::FromStr;
497 Capability::from_str(capability).map_or_else(
498 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
499 Ok,
500 )
501 }
502}
503
504impl Default for SpirvBuilder {
505 fn default() -> Self {
506 Self {
507 path_to_crate: None,
508 cargo_cmd: None,
509 cargo_cmd_like_rustc: None,
510 print_metadata: MetadataPrintout::default(),
511 release: true,
512 target: None,
513 deny_warnings: false,
514 multimodule: false,
515 spirv_metadata: SpirvMetadata::default(),
516 capabilities: Vec::new(),
517 extensions: Vec::new(),
518 extra_args: Vec::new(),
519 rustc_codegen_spirv_location: None,
520 path_to_target_spec: None,
521 target_dir_path: None,
522 toolchain_overwrite: None,
523 toolchain_rustc_version: None,
524 shader_panic_strategy: ShaderPanicStrategy::default(),
525 validator: ValidatorOptions::default(),
526 optimizer: OptimizerOptions::default(),
527 shader_crate_features: ShaderCrateFeatures::default(),
528 }
529 }
530}
531
532impl SpirvBuilder {
533 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
534 Self {
535 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
536 target: Some(target.into()),
537 ..SpirvBuilder::default()
538 }
539 }
540
541 #[must_use]
546 pub fn target_spec(mut self, p: impl AsRef<Path>) -> Self {
547 self.path_to_target_spec = Some(p.as_ref().to_path_buf());
548 self
549 }
550
551 #[must_use]
553 pub fn print_metadata(mut self, v: MetadataPrintout) -> Self {
554 self.print_metadata = v;
555 self
556 }
557
558 #[must_use]
559 pub fn deny_warnings(mut self, v: bool) -> Self {
560 self.deny_warnings = v;
561 self
562 }
563
564 #[must_use]
566 pub fn release(mut self, v: bool) -> Self {
567 self.release = v;
568 self
569 }
570
571 #[must_use]
575 pub fn multimodule(mut self, v: bool) -> Self {
576 self.multimodule = v;
577 self
578 }
579
580 #[must_use]
583 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
584 self.spirv_metadata = v;
585 self
586 }
587
588 #[must_use]
591 pub fn capability(mut self, capability: Capability) -> Self {
592 self.capabilities.push(capability);
593 self
594 }
595
596 #[must_use]
599 pub fn extension(mut self, extension: impl Into<String>) -> Self {
600 self.extensions.push(extension.into());
601 self
602 }
603
604 #[must_use]
606 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
607 self.shader_panic_strategy = shader_panic_strategy;
608 self
609 }
610
611 #[must_use]
613 pub fn relax_struct_store(mut self, v: bool) -> Self {
614 self.validator.relax_struct_store = v;
615 self
616 }
617
618 #[must_use]
621 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
622 self.validator.relax_logical_pointer = v;
623 self
624 }
625
626 #[must_use]
629 pub fn relax_block_layout(mut self, v: bool) -> Self {
630 self.validator.relax_block_layout = Some(v);
631 self
632 }
633
634 #[must_use]
637 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
638 self.validator.uniform_buffer_standard_layout = v;
639 self
640 }
641
642 #[must_use]
646 pub fn scalar_block_layout(mut self, v: bool) -> Self {
647 self.validator.scalar_block_layout = v;
648 self
649 }
650
651 #[must_use]
654 pub fn skip_block_layout(mut self, v: bool) -> Self {
655 self.validator.skip_block_layout = v;
656 self
657 }
658
659 #[must_use]
661 pub fn preserve_bindings(mut self, v: bool) -> Self {
662 self.optimizer.preserve_bindings = v;
663 self
664 }
665
666 #[must_use]
669 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
670 self.extra_args.push(arg.into());
671 self
672 }
673
674 #[must_use]
676 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
677 self.shader_crate_features.default_features = default_features;
678 self
679 }
680
681 #[must_use]
683 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
684 self.shader_crate_features.features = features.into_iter().collect();
685 self
686 }
687
688 #[must_use]
689 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
690 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
691 self
692 }
693
694 #[must_use]
698 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
699 self.target_dir_path = Some(name.into());
700 self
701 }
702
703 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
706 let metadata_file = invoke_rustc(self)?;
707 match self.print_metadata {
708 MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
709 leaf_deps(&metadata_file, |artifact| {
710 println!("cargo:rerun-if-changed={artifact}");
711 })
712 .map_err(SpirvBuilderError::MetadataFileMissing)?;
714 }
715 MetadataPrintout::None => (),
716 }
717 let metadata = self.parse_metadata_file(&metadata_file)?;
718
719 Ok(metadata)
720 }
721
722 pub(crate) fn parse_metadata_file(
723 &self,
724 at: &Path,
725 ) -> Result<CompileResult, SpirvBuilderError> {
726 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
727 let metadata: CompileResult =
729 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
730 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
731 match &metadata.module {
732 ModuleResult::SingleModule(spirv_module) => {
733 assert!(!self.multimodule);
734 let env_var = format!(
735 "{}.spv",
736 at.file_name()
737 .unwrap()
738 .to_str()
739 .unwrap()
740 .strip_suffix(ARTIFACT_SUFFIX)
741 .unwrap()
742 );
743 if self.print_metadata == MetadataPrintout::Full {
744 println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
745 }
746 }
747 ModuleResult::MultiModule(_) => {
748 assert!(self.multimodule);
749 }
750 }
751 Ok(metadata)
752 }
753}
754
755fn dylib_path_envvar() -> &'static str {
757 if cfg!(windows) {
758 "PATH"
759 } else if cfg!(target_os = "macos") {
760 "DYLD_FALLBACK_LIBRARY_PATH"
761 } else {
762 "LD_LIBRARY_PATH"
763 }
764}
765fn dylib_path() -> Vec<PathBuf> {
766 let mut dylibs = match env::var_os(dylib_path_envvar()) {
767 Some(var) => env::split_paths(&var).collect(),
768 None => Vec::new(),
769 };
770 if let Ok(dir) = env::current_dir() {
771 dylibs.push(dir);
772 }
773 dylibs
774}
775
776fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
777 if cfg!(feature = "rustc_codegen_spirv") {
778 let filename = format!(
779 "{}rustc_codegen_spirv{}",
780 env::consts::DLL_PREFIX,
781 env::consts::DLL_SUFFIX
782 );
783 let dylib_paths = dylib_path();
784 for mut path in dylib_paths {
785 path.push(&filename);
786 if path.is_file() {
787 return Ok(path);
788 }
789 }
790 panic!("Could not find {filename} in library path");
791 } else {
792 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
793 }
794}
795
796fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
799 for s in &strings {
800 let s = s.borrow();
801 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
802 }
803 strings.join(sep)
804}
805
806fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
808 let target = builder
809 .target
810 .as_ref()
811 .ok_or(SpirvBuilderError::MissingTarget)?;
812 let path_to_crate = builder
813 .path_to_crate
814 .as_ref()
815 .ok_or(SpirvBuilderError::MissingCratePath)?;
816 {
817 let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| {
818 SpirvBuilderError::NonSpirvTarget {
819 target: target.clone(),
820 }
821 })?;
822 #[allow(clippy::match_same_arms)]
824 match target_env {
825 "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {}
831 "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {}
832 "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3"
833 | "vulkan1.4" => {}
834
835 _ => {
836 return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv {
837 target_env: target_env.into(),
838 });
839 }
840 }
841
842 if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule {
843 return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
844 }
845 if !path_to_crate.is_dir() {
846 return Err(SpirvBuilderError::CratePathDoesntExist(
847 path_to_crate.clone(),
848 ));
849 }
850 }
851
852 let toolchain_rustc_version =
853 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
854 toolchain_rustc_version.clone()
855 } else {
856 query_rustc_version(builder.toolchain_overwrite.as_deref())?
857 };
858
859 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
868 .transpose()
869 .unwrap_or_else(find_rustc_codegen_spirv)?;
870 if !rustc_codegen_spirv.is_file() {
871 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
872 rustc_codegen_spirv,
873 ));
874 }
875
876 let mut rustflags = vec![
877 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
878 "-Zbinary-dep-depinfo".to_string(),
882 "-Csymbol-mangling-version=v0".to_string(),
883 "-Zcrate-attr=feature(register_tool)".to_string(),
884 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
885 "-Coverflow-checks=off".to_string(),
888 "-Cdebug-assertions=off".to_string(),
889 "-Zinline-mir=off".to_string(),
892 "-Zmir-enable-passes=-GVN".to_string(),
897 "-Zshare-generics=off".to_string(),
904 ];
905
906 let tracked_env_var_get = |name| {
908 if let MetadataPrintout::Full | MetadataPrintout::DependencyOnly = builder.print_metadata {
909 println!("cargo:rerun-if-env-changed={name}");
910 }
911 env::var(name)
912 };
913
914 let mut llvm_args = vec![];
915 if builder.multimodule {
916 llvm_args.push("--module-output=multiple".to_string());
917 }
918 match builder.spirv_metadata {
919 SpirvMetadata::None => (),
920 SpirvMetadata::NameVariables => {
921 llvm_args.push("--spirv-metadata=name-variables".to_string());
922 }
923 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
924 }
925 if builder.validator.relax_struct_store {
926 llvm_args.push("--relax-struct-store".to_string());
927 }
928 if builder.validator.relax_logical_pointer {
929 llvm_args.push("--relax-logical-pointer".to_string());
930 }
931 if builder.validator.relax_block_layout.unwrap_or(false) {
932 llvm_args.push("--relax-block-layout".to_string());
933 }
934 if builder.validator.uniform_buffer_standard_layout {
935 llvm_args.push("--uniform-buffer-standard-layout".to_string());
936 }
937 if builder.validator.scalar_block_layout {
938 llvm_args.push("--scalar-block-layout".to_string());
939 }
940 if builder.validator.skip_block_layout {
941 llvm_args.push("--skip-block-layout".to_string());
942 }
943 if builder.optimizer.preserve_bindings {
944 llvm_args.push("--preserve-bindings".to_string());
945 }
946 let mut target_features = vec![];
947 let abort_strategy = match builder.shader_panic_strategy {
948 ShaderPanicStrategy::SilentExit => None,
949 ShaderPanicStrategy::DebugPrintfThenExit {
950 print_inputs,
951 print_backtrace,
952 } => {
953 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
954 Some(format!(
955 "debug-printf{}{}",
956 if print_inputs { "+inputs" } else { "" },
957 if print_backtrace { "+backtrace" } else { "" }
958 ))
959 }
960 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
961 Some("unreachable".into())
962 }
963 };
964 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
965
966 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
967 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
968 } else {
969 llvm_args.extend(builder.extra_args.iter().cloned());
970 }
971
972 let llvm_args = join_checking_for_separators(llvm_args, " ");
973 if !llvm_args.is_empty() {
974 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
975 }
976
977 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
978 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
979 let target_features = join_checking_for_separators(target_features, ",");
980 if !target_features.is_empty() {
981 rustflags.push(["-Ctarget-feature=", &target_features].concat());
982 }
983
984 if builder.deny_warnings {
985 rustflags.push("-Dwarnings".to_string());
986 }
987
988 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
989 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
990 }
991
992 let target_dir_path = builder
993 .target_dir_path
994 .clone()
995 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
996 let target_dir = if target_dir_path.is_absolute() {
997 target_dir_path
998 } else {
999 let metadata = cargo_metadata::MetadataCommand::new()
1000 .current_dir(path_to_crate)
1001 .exec()?;
1002 metadata
1003 .target_directory
1004 .into_std_path_buf()
1005 .join(target_dir_path)
1006 };
1007
1008 let mut cargo = cargo_cmd::CargoCmd::new();
1009 if let Some(toolchain) = &builder.toolchain_overwrite {
1010 cargo.arg(format!("+{toolchain}"));
1011 }
1012
1013 let cargo_cmd = builder.cargo_cmd.as_ref().map_or("rustc", |s| s.as_str());
1014 let cargo_cmd_like_rustc = builder.cargo_cmd_like_rustc.unwrap_or(cargo_cmd == "rustc");
1015 let profile = if builder.release { "release" } else { "dev" };
1016 cargo.args([
1017 cargo_cmd,
1018 "--lib",
1019 "--message-format=json-render-diagnostics",
1020 "-Zbuild-std=core",
1021 "-Zbuild-std-features=compiler-builtins-mem",
1022 "--profile",
1023 profile,
1024 ]);
1025 if cargo_cmd_like_rustc {
1026 cargo.args(["--crate-type", "dylib"]);
1032 }
1033
1034 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
1035 cargo.args(extra_cargoflags.split_whitespace());
1036 }
1037
1038 if toolchain_rustc_version >= Version::new(1, 76, 0) {
1045 let path_opt = builder.path_to_target_spec.clone();
1046 let path;
1047 #[cfg(feature = "include-target-specs")]
1048 {
1049 path = path_opt
1050 .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json")));
1051 }
1052 #[cfg(not(feature = "include-target-specs"))]
1053 {
1054 path = path_opt.ok_or(SpirvBuilderError::MissingTargetSpec)?;
1055 }
1056 cargo.arg("--target").arg(path);
1057 } else {
1058 cargo.arg("--target").arg(target);
1059 }
1060
1061 if !builder.shader_crate_features.default_features {
1062 cargo.arg("--no-default-features");
1063 }
1064
1065 if !builder.shader_crate_features.features.is_empty() {
1066 cargo
1067 .arg("--features")
1068 .arg(builder.shader_crate_features.features.join(","));
1069 }
1070
1071 cargo.arg("--target-dir").arg(target_dir);
1072
1073 cargo.env(
1077 "CARGO_ENCODED_RUSTFLAGS",
1078 join_checking_for_separators(rustflags, "\x1f"),
1079 );
1080
1081 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1084 let num_cgus = 1;
1085 cargo.env(
1086 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1087 num_cgus.to_string(),
1088 );
1089
1090 cargo.stderr(Stdio::inherit()).current_dir(path_to_crate);
1091 log::debug!("building shaders with `{cargo:?}`");
1092 let build = cargo.output().expect("failed to execute cargo build");
1093
1094 let stdout = String::from_utf8(build.stdout).unwrap();
1098 if build.status.success() {
1099 get_sole_artifact(&stdout).ok_or_else(|| {
1100 eprintln!("--- build output ---\n{stdout}");
1101 panic!(
1102 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output (see above). Verify that `crate-type` is set correctly"
1103 );
1104 })
1105 } else {
1106 Err(SpirvBuilderError::BuildFailed)
1107 }
1108}
1109
1110#[derive(Deserialize)]
1111struct RustcOutput {
1112 reason: String,
1113 filenames: Option<Vec<String>>,
1114}
1115
1116const ARTIFACT_SUFFIX: &str = ".spv.json";
1117
1118fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1119 let mut last_compiler_artifact = None;
1120 for line in out.lines() {
1121 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1122 println!("{line}");
1124 continue;
1125 };
1126 if msg.reason == "compiler-artifact" {
1127 last_compiler_artifact = Some(msg);
1128 }
1129 }
1130 let last_compiler_artifact =
1131 last_compiler_artifact.expect("Did not find output file in rustc output");
1132
1133 let mut filenames = last_compiler_artifact
1134 .filenames
1135 .unwrap()
1136 .into_iter()
1137 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1138 let filename = filenames.next()?;
1139 assert_eq!(
1140 filenames.next(),
1141 None,
1142 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1143 );
1144 Some(filename.into())
1145}
1146
1147fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1149 let deps_file = artifact.with_extension("d");
1150 let mut deps_map = HashMap::new();
1151 depfile::read_deps_file(&deps_file, |item, deps| {
1152 deps_map.insert(item, deps);
1153 Ok(())
1154 })?;
1155 fn recurse(
1156 map: &HashMap<RawString, Vec<RawString>>,
1157 artifact: &RawStr,
1158 handle: &mut impl FnMut(&RawStr),
1159 ) {
1160 match map.get(artifact) {
1161 Some(entries) => {
1162 for entry in entries {
1163 recurse(map, entry, handle);
1164 }
1165 }
1166 None => handle(artifact),
1167 }
1168 }
1169 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1170 Ok(())
1171}
1172
1173pub fn query_rustc_version(toolchain: Option<&str>) -> std::io::Result<Version> {
1174 let mut cmd = Command::new("rustc");
1175 if let Some(toolchain) = toolchain {
1176 cmd.arg(format!("+{toolchain}"));
1177 }
1178 cmd.arg("--version");
1179 let output = cmd.output()?;
1180
1181 let stdout = String::from_utf8(output.stdout).expect("stdout must be utf-8");
1182 let parse = |output: &str| {
1183 let output = output.strip_prefix("rustc ")?;
1184 let version = &output[..output.find(|c| !"0123456789.".contains(c))?];
1185 Version::parse(version).ok()
1186 };
1187 Ok(parse(&stdout)
1188 .unwrap_or_else(|| panic!("failed parsing `rustc --version` output `{stdout}`")))
1189}