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))]
223 DebugPrintfThenExit {
224 print_inputs: bool,
227
228 print_backtrace: bool,
235 },
236
237 #[allow(non_camel_case_types)]
248 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
249}
250
251#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
254#[cfg_attr(feature = "clap", derive(clap::Parser))]
255#[non_exhaustive]
256pub struct ValidatorOptions {
257 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
268 pub relax_struct_store: bool,
269 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
276 pub relax_logical_pointer: bool,
277 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
297 pub relax_block_layout: Option<bool>,
298 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
301 pub uniform_buffer_standard_layout: bool,
302 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
319 pub scalar_block_layout: bool,
320 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
323 pub skip_block_layout: bool,
324 }
327
328#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
331#[cfg_attr(feature = "clap", derive(clap::Parser))]
332#[non_exhaustive]
333pub struct OptimizerOptions {
334 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
341 pub preserve_bindings: bool,
342 }
346
347#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
349#[cfg_attr(feature = "clap", derive(clap::Parser))]
350#[non_exhaustive]
351pub struct ShaderCrateFeatures {
352 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
354 pub default_features: bool,
355 #[cfg_attr(feature = "clap", clap(long))]
357 pub features: Vec<String>,
358}
359
360impl Default for ShaderCrateFeatures {
361 fn default() -> Self {
362 Self {
363 default_features: true,
364 features: Vec::new(),
365 }
366 }
367}
368
369#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
370#[cfg_attr(feature = "clap", derive(clap::Parser))]
371#[non_exhaustive]
372pub struct SpirvBuilder {
373 #[cfg_attr(feature = "clap", clap(skip))]
374 pub path_to_crate: Option<PathBuf>,
375 #[cfg_attr(feature = "clap", clap(skip))]
378 pub print_metadata: MetadataPrintout,
379 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
381 pub release: bool,
382 #[cfg_attr(
384 feature = "clap",
385 clap(long, default_value = "spirv-unknown-vulkan1.2")
386 )]
387 pub target: Option<String>,
388 #[cfg_attr(feature = "clap", clap(flatten))]
390 #[serde(flatten)]
391 pub shader_crate_features: ShaderCrateFeatures,
392 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
394 pub deny_warnings: bool,
395 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
399 pub multimodule: bool,
400 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
403 pub spirv_metadata: SpirvMetadata,
404 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
407 pub capabilities: Vec<Capability>,
408 #[cfg_attr(feature = "clap", arg(long))]
411 pub extensions: Vec<String>,
412 #[cfg_attr(feature = "clap", clap(skip))]
415 pub extra_args: Vec<String>,
416 #[cfg_attr(feature = "clap", clap(skip))]
418 pub rustc_codegen_spirv_location: Option<PathBuf>,
419 #[cfg_attr(feature = "clap", clap(skip))]
421 pub toolchain_overwrite: Option<String>,
422 #[cfg_attr(feature = "clap", clap(skip))]
424 pub toolchain_rustc_version: Option<Version>,
425
426 #[cfg_attr(feature = "clap", clap(skip))]
431 pub path_to_target_spec: Option<PathBuf>,
432 #[cfg_attr(feature = "clap", clap(skip))]
436 pub target_dir_path: Option<PathBuf>,
437
438 #[cfg_attr(feature = "clap", clap(skip))]
441 pub shader_panic_strategy: ShaderPanicStrategy,
442
443 #[cfg_attr(feature = "clap", clap(flatten))]
445 #[serde(flatten)]
446 pub validator: ValidatorOptions,
447
448 #[cfg_attr(feature = "clap", clap(flatten))]
450 #[serde(flatten)]
451 pub optimizer: OptimizerOptions,
452}
453
454#[cfg(feature = "clap")]
455impl SpirvBuilder {
456 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
458 use core::str::FromStr;
459 Capability::from_str(capability).map_or_else(
460 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
461 Ok,
462 )
463 }
464}
465
466impl Default for SpirvBuilder {
467 fn default() -> Self {
468 Self {
469 path_to_crate: None,
470 print_metadata: MetadataPrintout::default(),
471 release: true,
472 target: None,
473 deny_warnings: false,
474 multimodule: false,
475 spirv_metadata: SpirvMetadata::default(),
476 capabilities: Vec::new(),
477 extensions: Vec::new(),
478 extra_args: Vec::new(),
479 rustc_codegen_spirv_location: None,
480 path_to_target_spec: None,
481 target_dir_path: None,
482 toolchain_overwrite: None,
483 toolchain_rustc_version: None,
484 shader_panic_strategy: ShaderPanicStrategy::default(),
485 validator: ValidatorOptions::default(),
486 optimizer: OptimizerOptions::default(),
487 shader_crate_features: ShaderCrateFeatures::default(),
488 }
489 }
490}
491
492impl SpirvBuilder {
493 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
494 Self {
495 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
496 target: Some(target.into()),
497 ..SpirvBuilder::default()
498 }
499 }
500
501 #[must_use]
506 pub fn target_spec(mut self, p: impl AsRef<Path>) -> Self {
507 self.path_to_target_spec = Some(p.as_ref().to_path_buf());
508 self
509 }
510
511 #[must_use]
513 pub fn print_metadata(mut self, v: MetadataPrintout) -> Self {
514 self.print_metadata = v;
515 self
516 }
517
518 #[must_use]
519 pub fn deny_warnings(mut self, v: bool) -> Self {
520 self.deny_warnings = v;
521 self
522 }
523
524 #[must_use]
526 pub fn release(mut self, v: bool) -> Self {
527 self.release = v;
528 self
529 }
530
531 #[must_use]
535 pub fn multimodule(mut self, v: bool) -> Self {
536 self.multimodule = v;
537 self
538 }
539
540 #[must_use]
543 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
544 self.spirv_metadata = v;
545 self
546 }
547
548 #[must_use]
551 pub fn capability(mut self, capability: Capability) -> Self {
552 self.capabilities.push(capability);
553 self
554 }
555
556 #[must_use]
559 pub fn extension(mut self, extension: impl Into<String>) -> Self {
560 self.extensions.push(extension.into());
561 self
562 }
563
564 #[must_use]
566 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
567 self.shader_panic_strategy = shader_panic_strategy;
568 self
569 }
570
571 #[must_use]
573 pub fn relax_struct_store(mut self, v: bool) -> Self {
574 self.validator.relax_struct_store = v;
575 self
576 }
577
578 #[must_use]
581 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
582 self.validator.relax_logical_pointer = v;
583 self
584 }
585
586 #[must_use]
589 pub fn relax_block_layout(mut self, v: bool) -> Self {
590 self.validator.relax_block_layout = Some(v);
591 self
592 }
593
594 #[must_use]
597 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
598 self.validator.uniform_buffer_standard_layout = v;
599 self
600 }
601
602 #[must_use]
606 pub fn scalar_block_layout(mut self, v: bool) -> Self {
607 self.validator.scalar_block_layout = v;
608 self
609 }
610
611 #[must_use]
614 pub fn skip_block_layout(mut self, v: bool) -> Self {
615 self.validator.skip_block_layout = v;
616 self
617 }
618
619 #[must_use]
621 pub fn preserve_bindings(mut self, v: bool) -> Self {
622 self.optimizer.preserve_bindings = v;
623 self
624 }
625
626 #[must_use]
629 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
630 self.extra_args.push(arg.into());
631 self
632 }
633
634 #[must_use]
636 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
637 self.shader_crate_features.default_features = default_features;
638 self
639 }
640
641 #[must_use]
643 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
644 self.shader_crate_features.features = features.into_iter().collect();
645 self
646 }
647
648 #[must_use]
649 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
650 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
651 self
652 }
653
654 #[must_use]
658 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
659 self.target_dir_path = Some(name.into());
660 self
661 }
662
663 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
666 let metadata_file = invoke_rustc(self)?;
667 match self.print_metadata {
668 MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
669 leaf_deps(&metadata_file, |artifact| {
670 println!("cargo:rerun-if-changed={artifact}");
671 })
672 .map_err(SpirvBuilderError::MetadataFileMissing)?;
674 }
675 MetadataPrintout::None => (),
676 }
677 let metadata = self.parse_metadata_file(&metadata_file)?;
678
679 Ok(metadata)
680 }
681
682 pub(crate) fn parse_metadata_file(
683 &self,
684 at: &Path,
685 ) -> Result<CompileResult, SpirvBuilderError> {
686 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
687 let metadata: CompileResult =
689 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
690 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
691 match &metadata.module {
692 ModuleResult::SingleModule(spirv_module) => {
693 assert!(!self.multimodule);
694 let env_var = format!(
695 "{}.spv",
696 at.file_name()
697 .unwrap()
698 .to_str()
699 .unwrap()
700 .strip_suffix(ARTIFACT_SUFFIX)
701 .unwrap()
702 );
703 if self.print_metadata == MetadataPrintout::Full {
704 println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
705 }
706 }
707 ModuleResult::MultiModule(_) => {
708 assert!(self.multimodule);
709 }
710 }
711 Ok(metadata)
712 }
713}
714
715fn dylib_path_envvar() -> &'static str {
717 if cfg!(windows) {
718 "PATH"
719 } else if cfg!(target_os = "macos") {
720 "DYLD_FALLBACK_LIBRARY_PATH"
721 } else {
722 "LD_LIBRARY_PATH"
723 }
724}
725fn dylib_path() -> Vec<PathBuf> {
726 match env::var_os(dylib_path_envvar()) {
727 Some(var) => env::split_paths(&var).collect(),
728 None => Vec::new(),
729 }
730}
731
732fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
733 if cfg!(feature = "rustc_codegen_spirv") {
734 let filename = format!(
735 "{}rustc_codegen_spirv{}",
736 env::consts::DLL_PREFIX,
737 env::consts::DLL_SUFFIX
738 );
739 for mut path in dylib_path() {
740 path.push(&filename);
741 if path.is_file() {
742 return Ok(path);
743 }
744 }
745 panic!("Could not find {filename} in library path");
746 } else {
747 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
748 }
749}
750
751fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
754 for s in &strings {
755 let s = s.borrow();
756 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
757 }
758 strings.join(sep)
759}
760
761fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
763 let target = builder
764 .target
765 .as_ref()
766 .ok_or(SpirvBuilderError::MissingTarget)?;
767 let path_to_crate = builder
768 .path_to_crate
769 .as_ref()
770 .ok_or(SpirvBuilderError::MissingCratePath)?;
771 {
772 let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| {
773 SpirvBuilderError::NonSpirvTarget {
774 target: target.clone(),
775 }
776 })?;
777 #[allow(clippy::match_same_arms)]
779 match target_env {
780 "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {}
786 "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {}
787 "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3"
788 | "vulkan1.4" => {}
789
790 _ => {
791 return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv {
792 target_env: target_env.into(),
793 });
794 }
795 }
796
797 if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule {
798 return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
799 }
800 if !path_to_crate.is_dir() {
801 return Err(SpirvBuilderError::CratePathDoesntExist(
802 path_to_crate.clone(),
803 ));
804 }
805 }
806
807 let toolchain_rustc_version =
808 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
809 toolchain_rustc_version.clone()
810 } else {
811 query_rustc_version(builder.toolchain_overwrite.as_deref())?
812 };
813
814 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
823 .transpose()
824 .unwrap_or_else(find_rustc_codegen_spirv)?;
825 if !rustc_codegen_spirv.is_file() {
826 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
827 rustc_codegen_spirv,
828 ));
829 }
830
831 let mut rustflags = vec![
832 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
833 "-Zbinary-dep-depinfo".to_string(),
837 "-Csymbol-mangling-version=v0".to_string(),
838 "-Zcrate-attr=feature(register_tool)".to_string(),
839 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
840 "-Coverflow-checks=off".to_string(),
843 "-Cdebug-assertions=off".to_string(),
844 "-Zinline-mir=off".to_string(),
847 "-Zmir-enable-passes=-GVN".to_string(),
852 "-Zshare-generics=off".to_string(),
859 ];
860
861 let tracked_env_var_get = |name| {
863 if let MetadataPrintout::Full | MetadataPrintout::DependencyOnly = builder.print_metadata {
864 println!("cargo:rerun-if-env-changed={name}");
865 }
866 env::var(name)
867 };
868
869 let mut llvm_args = vec![];
870 if builder.multimodule {
871 llvm_args.push("--module-output=multiple".to_string());
872 }
873 match builder.spirv_metadata {
874 SpirvMetadata::None => (),
875 SpirvMetadata::NameVariables => {
876 llvm_args.push("--spirv-metadata=name-variables".to_string());
877 }
878 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
879 }
880 if builder.validator.relax_struct_store {
881 llvm_args.push("--relax-struct-store".to_string());
882 }
883 if builder.validator.relax_logical_pointer {
884 llvm_args.push("--relax-logical-pointer".to_string());
885 }
886 if builder.validator.relax_block_layout.unwrap_or(false) {
887 llvm_args.push("--relax-block-layout".to_string());
888 }
889 if builder.validator.uniform_buffer_standard_layout {
890 llvm_args.push("--uniform-buffer-standard-layout".to_string());
891 }
892 if builder.validator.scalar_block_layout {
893 llvm_args.push("--scalar-block-layout".to_string());
894 }
895 if builder.validator.skip_block_layout {
896 llvm_args.push("--skip-block-layout".to_string());
897 }
898 if builder.optimizer.preserve_bindings {
899 llvm_args.push("--preserve-bindings".to_string());
900 }
901 let mut target_features = vec![];
902 let abort_strategy = match builder.shader_panic_strategy {
903 ShaderPanicStrategy::SilentExit => None,
904 ShaderPanicStrategy::DebugPrintfThenExit {
905 print_inputs,
906 print_backtrace,
907 } => {
908 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
909 Some(format!(
910 "debug-printf{}{}",
911 if print_inputs { "+inputs" } else { "" },
912 if print_backtrace { "+backtrace" } else { "" }
913 ))
914 }
915 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
916 Some("unreachable".into())
917 }
918 };
919 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
920
921 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
922 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
923 } else {
924 llvm_args.extend(builder.extra_args.iter().cloned());
925 }
926
927 let llvm_args = join_checking_for_separators(llvm_args, " ");
928 if !llvm_args.is_empty() {
929 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
930 }
931
932 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
933 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
934 let target_features = join_checking_for_separators(target_features, ",");
935 if !target_features.is_empty() {
936 rustflags.push(["-Ctarget-feature=", &target_features].concat());
937 }
938
939 if builder.deny_warnings {
940 rustflags.push("-Dwarnings".to_string());
941 }
942
943 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
944 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
945 }
946
947 let target_dir_path = builder
948 .target_dir_path
949 .clone()
950 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
951 let target_dir = if target_dir_path.is_absolute() {
952 target_dir_path
953 } else {
954 let metadata = cargo_metadata::MetadataCommand::new()
955 .current_dir(path_to_crate)
956 .exec()?;
957 metadata
958 .target_directory
959 .into_std_path_buf()
960 .join(target_dir_path)
961 };
962
963 let profile = if builder.release { "release" } else { "dev" };
964
965 let mut cargo = cargo_cmd::CargoCmd::new();
966 if let Some(toolchain) = &builder.toolchain_overwrite {
967 cargo.arg(format!("+{toolchain}"));
968 }
969 cargo.args([
970 "build",
971 "--lib",
972 "--message-format=json-render-diagnostics",
973 "-Zbuild-std=core",
974 "-Zbuild-std-features=compiler-builtins-mem",
975 "--profile",
976 profile,
977 ]);
978
979 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
980 cargo.args(extra_cargoflags.split_whitespace());
981 }
982
983 if toolchain_rustc_version >= Version::new(1, 76, 0) {
990 let path_opt = builder.path_to_target_spec.clone();
991 let path;
992 #[cfg(feature = "include-target-specs")]
993 {
994 path = path_opt
995 .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json")));
996 }
997 #[cfg(not(feature = "include-target-specs"))]
998 {
999 path = path_opt.ok_or(SpirvBuilderError::MissingTargetSpec)?;
1000 }
1001 cargo.arg("--target").arg(path);
1002 } else {
1003 cargo.arg("--target").arg(target);
1004 }
1005
1006 if !builder.shader_crate_features.default_features {
1007 cargo.arg("--no-default-features");
1008 }
1009
1010 if !builder.shader_crate_features.features.is_empty() {
1011 cargo
1012 .arg("--features")
1013 .arg(builder.shader_crate_features.features.join(","));
1014 }
1015
1016 cargo.arg("--target-dir").arg(target_dir);
1017
1018 cargo.env(
1022 "CARGO_ENCODED_RUSTFLAGS",
1023 join_checking_for_separators(rustflags, "\x1f"),
1024 );
1025
1026 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1029 let num_cgus = 1;
1030 cargo.env(
1031 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1032 num_cgus.to_string(),
1033 );
1034
1035 cargo.stderr(Stdio::inherit()).current_dir(path_to_crate);
1036 log::debug!("building shaders with `{cargo}`");
1037 let build = cargo.output().expect("failed to execute cargo build");
1038
1039 let stdout = String::from_utf8(build.stdout).unwrap();
1043 if build.status.success() {
1044 get_sole_artifact(&stdout).ok_or_else(|| {
1045 eprintln!("--- build output ---\n{stdout}");
1046 panic!(
1047 "`{ARTIFACT_SUFFIX}` artifact not found in (supposedly successful) build output (see above). Verify that `crate-type` is set correctly"
1048 );
1049 })
1050 } else {
1051 Err(SpirvBuilderError::BuildFailed)
1052 }
1053}
1054
1055#[derive(Deserialize)]
1056struct RustcOutput {
1057 reason: String,
1058 filenames: Option<Vec<String>>,
1059}
1060
1061const ARTIFACT_SUFFIX: &str = ".spv.json";
1062
1063fn get_sole_artifact(out: &str) -> Option<PathBuf> {
1064 let mut last_compiler_artifact = None;
1065 for line in out.lines() {
1066 let Ok(msg) = serde_json::from_str::<RustcOutput>(line) else {
1067 println!("{line}");
1069 continue;
1070 };
1071 if msg.reason == "compiler-artifact" {
1072 last_compiler_artifact = Some(msg);
1073 }
1074 }
1075 let last_compiler_artifact =
1076 last_compiler_artifact.expect("Did not find output file in rustc output");
1077
1078 let mut filenames = last_compiler_artifact
1079 .filenames
1080 .unwrap()
1081 .into_iter()
1082 .filter(|v| v.ends_with(ARTIFACT_SUFFIX));
1083 let filename = filenames.next()?;
1084 assert_eq!(
1085 filenames.next(),
1086 None,
1087 "build had multiple `{ARTIFACT_SUFFIX}` artifacts"
1088 );
1089 Some(filename.into())
1090}
1091
1092fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
1094 let deps_file = artifact.with_extension("d");
1095 let mut deps_map = HashMap::new();
1096 depfile::read_deps_file(&deps_file, |item, deps| {
1097 deps_map.insert(item, deps);
1098 Ok(())
1099 })?;
1100 fn recurse(
1101 map: &HashMap<RawString, Vec<RawString>>,
1102 artifact: &RawStr,
1103 handle: &mut impl FnMut(&RawStr),
1104 ) {
1105 match map.get(artifact) {
1106 Some(entries) => {
1107 for entry in entries {
1108 recurse(map, entry, handle);
1109 }
1110 }
1111 None => handle(artifact),
1112 }
1113 }
1114 recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
1115 Ok(())
1116}
1117
1118pub fn query_rustc_version(toolchain: Option<&str>) -> std::io::Result<Version> {
1119 let mut cmd = Command::new("rustc");
1120 if let Some(toolchain) = toolchain {
1121 cmd.arg(format!("+{toolchain}"));
1122 }
1123 cmd.arg("--version");
1124 let output = cmd.output()?;
1125
1126 let stdout = String::from_utf8(output.stdout).expect("stdout must be utf-8");
1127 let parse = |output: &str| {
1128 let output = output.strip_prefix("rustc ")?;
1129 let version = &output[..output.find(|c| !"0123456789.".contains(c))?];
1130 Version::parse(version).ok()
1131 };
1132 Ok(parse(&stdout)
1133 .unwrap_or_else(|| panic!("failed parsing `rustc --version` output `{stdout}`")))
1134}