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
74mod depfile;
75#[cfg(feature = "watch")]
76mod watch;
77
78use raw_string::{RawStr, RawString};
79use semver::Version;
80use serde::Deserialize;
81use std::borrow::Borrow;
82use std::collections::HashMap;
83use std::env;
84use std::fs::File;
85use std::io::BufReader;
86use std::path::{Path, PathBuf};
87use std::process::{Command, Stdio};
88use thiserror::Error;
89
90pub use rustc_codegen_spirv_types::Capability;
91pub use rustc_codegen_spirv_types::{CompileResult, ModuleResult};
92
93#[cfg(feature = "include-target-specs")]
94pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH;
95
96#[derive(Debug, Error)]
97#[non_exhaustive]
98pub enum SpirvBuilderError {
99 #[error("`target` must be set, for example `spirv-unknown-vulkan1.2`")]
100 MissingTarget,
101 #[error("expected `{SPIRV_TARGET_PREFIX}...` target, found `{target}`")]
102 NonSpirvTarget { target: String },
103 #[error("SPIR-V target `{SPIRV_TARGET_PREFIX}-{target_env}` is not supported")]
104 UnsupportedSpirvTargetEnv { target_env: String },
105 #[error("`path_to_crate` must be set")]
106 MissingCratePath,
107 #[error("crate path '{0}' does not exist")]
108 CratePathDoesntExist(PathBuf),
109 #[error(
110 "Without feature `rustc_codegen_spirv`, you need to set the path of the dylib with `rustc_codegen_spirv_location`"
111 )]
112 MissingRustcCodegenSpirvDylib,
113 #[error("`rustc_codegen_spirv_location` path '{0}' is not a file")]
114 RustcCodegenSpirvDylibDoesNotExist(PathBuf),
115 #[error(
116 "Without feature `include-target-specs`, instead of setting a `target`, \
117 you need to set the path of the target spec file of your particular target with `path_to_target_spec`"
118 )]
119 MissingTargetSpec,
120 #[error("build failed")]
121 BuildFailed,
122 #[error("multi-module build cannot be used with print_metadata = MetadataPrintout::Full")]
123 MultiModuleWithPrintMetadata,
124 #[error("watching within build scripts will prevent build completion")]
125 WatchWithPrintMetadata,
126 #[error("multi-module metadata file missing")]
127 MetadataFileMissing(#[from] std::io::Error),
128 #[error("unable to parse multi-module metadata file")]
129 MetadataFileMalformed(#[from] serde_json::Error),
130 #[error("cargo metadata error")]
131 CargoMetadata(#[from] cargo_metadata::Error),
132}
133
134const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-";
135
136#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
137#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
138#[non_exhaustive]
139pub enum MetadataPrintout {
140 #[default]
142 None,
143 DependencyOnly,
145 Full,
149}
150
151#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
152#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
153#[non_exhaustive]
154pub enum SpirvMetadata {
155 #[default]
157 None,
158 NameVariables,
161 Full,
163}
164
165#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, serde::Deserialize, serde::Serialize)]
167#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
168#[non_exhaustive]
169pub enum ShaderPanicStrategy {
170 #[default]
176 SilentExit,
177
178 #[cfg_attr(feature = "clap", clap(skip))]
222 DebugPrintfThenExit {
223 print_inputs: bool,
226
227 print_backtrace: bool,
234 },
235
236 #[allow(non_camel_case_types)]
247 UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable,
248}
249
250#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
253#[cfg_attr(feature = "clap", derive(clap::Parser))]
254#[non_exhaustive]
255pub struct ValidatorOptions {
256 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
267 pub relax_struct_store: bool,
268 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
275 pub relax_logical_pointer: bool,
276 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
296 pub relax_block_layout: Option<bool>,
297 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
300 pub uniform_buffer_standard_layout: bool,
301 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
318 pub scalar_block_layout: bool,
319 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
322 pub skip_block_layout: bool,
323 }
326
327#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
330#[cfg_attr(feature = "clap", derive(clap::Parser))]
331#[non_exhaustive]
332pub struct OptimizerOptions {
333 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
340 pub preserve_bindings: bool,
341 }
345
346#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
348#[cfg_attr(feature = "clap", derive(clap::Parser))]
349#[non_exhaustive]
350pub struct ShaderCrateFeatures {
351 #[cfg_attr(feature = "clap", clap(long = "no-default-features", default_value = "true", action = clap::ArgAction::SetFalse))]
353 pub default_features: bool,
354 #[cfg_attr(feature = "clap", clap(long))]
356 pub features: Vec<String>,
357}
358
359impl Default for ShaderCrateFeatures {
360 fn default() -> Self {
361 Self {
362 default_features: true,
363 features: Vec::new(),
364 }
365 }
366}
367
368#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
369#[cfg_attr(feature = "clap", derive(clap::Parser))]
370#[non_exhaustive]
371pub struct SpirvBuilder {
372 #[cfg_attr(feature = "clap", clap(skip))]
373 pub path_to_crate: Option<PathBuf>,
374 #[cfg_attr(feature = "clap", clap(skip))]
377 pub print_metadata: MetadataPrintout,
378 #[cfg_attr(feature = "clap", clap(long = "debug", default_value = "true", action = clap::ArgAction::SetFalse))]
380 pub release: bool,
381 #[cfg_attr(
383 feature = "clap",
384 clap(long, default_value = "spirv-unknown-vulkan1.2")
385 )]
386 pub target: Option<String>,
387 #[cfg_attr(feature = "clap", clap(flatten))]
389 #[serde(flatten)]
390 pub shader_crate_features: ShaderCrateFeatures,
391 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
393 pub deny_warnings: bool,
394 #[cfg_attr(feature = "clap", arg(long, default_value = "false"))]
398 pub multimodule: bool,
399 #[cfg_attr(feature = "clap", arg(long, default_value = "none"))]
402 pub spirv_metadata: SpirvMetadata,
403 #[cfg_attr(feature = "clap", arg(long, value_parser=Self::parse_spirv_capability))]
406 pub capabilities: Vec<Capability>,
407 #[cfg_attr(feature = "clap", arg(long))]
410 pub extensions: Vec<String>,
411 #[cfg_attr(feature = "clap", clap(skip))]
414 pub extra_args: Vec<String>,
415 #[cfg_attr(feature = "clap", clap(skip))]
417 pub rustc_codegen_spirv_location: Option<PathBuf>,
418 #[cfg_attr(feature = "clap", clap(skip))]
420 pub toolchain_overwrite: Option<String>,
421 #[cfg_attr(feature = "clap", clap(skip))]
423 pub toolchain_rustc_version: Option<Version>,
424
425 #[cfg_attr(feature = "clap", clap(skip))]
430 pub path_to_target_spec: Option<PathBuf>,
431 #[cfg_attr(feature = "clap", clap(skip))]
435 pub target_dir_path: Option<PathBuf>,
436
437 #[cfg_attr(feature = "clap", clap(skip))]
440 pub shader_panic_strategy: ShaderPanicStrategy,
441
442 #[cfg_attr(feature = "clap", clap(flatten))]
444 #[serde(flatten)]
445 pub validator: ValidatorOptions,
446
447 #[cfg_attr(feature = "clap", clap(flatten))]
449 #[serde(flatten)]
450 pub optimizer: OptimizerOptions,
451}
452
453#[cfg(feature = "clap")]
454impl SpirvBuilder {
455 fn parse_spirv_capability(capability: &str) -> Result<Capability, clap::Error> {
457 use core::str::FromStr;
458 Capability::from_str(capability).map_or_else(
459 |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
460 Ok,
461 )
462 }
463}
464
465impl Default for SpirvBuilder {
466 fn default() -> Self {
467 Self {
468 path_to_crate: None,
469 print_metadata: MetadataPrintout::default(),
470 release: true,
471 target: None,
472 deny_warnings: false,
473 multimodule: false,
474 spirv_metadata: SpirvMetadata::default(),
475 capabilities: Vec::new(),
476 extensions: Vec::new(),
477 extra_args: Vec::new(),
478 rustc_codegen_spirv_location: None,
479 path_to_target_spec: None,
480 target_dir_path: None,
481 toolchain_overwrite: None,
482 toolchain_rustc_version: None,
483 shader_panic_strategy: ShaderPanicStrategy::default(),
484 validator: ValidatorOptions::default(),
485 optimizer: OptimizerOptions::default(),
486 shader_crate_features: ShaderCrateFeatures::default(),
487 }
488 }
489}
490
491impl SpirvBuilder {
492 pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
493 Self {
494 path_to_crate: Some(path_to_crate.as_ref().to_owned()),
495 target: Some(target.into()),
496 ..SpirvBuilder::default()
497 }
498 }
499
500 #[must_use]
505 pub fn target_spec(mut self, p: impl AsRef<Path>) -> Self {
506 self.path_to_target_spec = Some(p.as_ref().to_path_buf());
507 self
508 }
509
510 #[must_use]
512 pub fn print_metadata(mut self, v: MetadataPrintout) -> Self {
513 self.print_metadata = v;
514 self
515 }
516
517 #[must_use]
518 pub fn deny_warnings(mut self, v: bool) -> Self {
519 self.deny_warnings = v;
520 self
521 }
522
523 #[must_use]
525 pub fn release(mut self, v: bool) -> Self {
526 self.release = v;
527 self
528 }
529
530 #[must_use]
534 pub fn multimodule(mut self, v: bool) -> Self {
535 self.multimodule = v;
536 self
537 }
538
539 #[must_use]
542 pub fn spirv_metadata(mut self, v: SpirvMetadata) -> Self {
543 self.spirv_metadata = v;
544 self
545 }
546
547 #[must_use]
550 pub fn capability(mut self, capability: Capability) -> Self {
551 self.capabilities.push(capability);
552 self
553 }
554
555 #[must_use]
558 pub fn extension(mut self, extension: impl Into<String>) -> Self {
559 self.extensions.push(extension.into());
560 self
561 }
562
563 #[must_use]
565 pub fn shader_panic_strategy(mut self, shader_panic_strategy: ShaderPanicStrategy) -> Self {
566 self.shader_panic_strategy = shader_panic_strategy;
567 self
568 }
569
570 #[must_use]
572 pub fn relax_struct_store(mut self, v: bool) -> Self {
573 self.validator.relax_struct_store = v;
574 self
575 }
576
577 #[must_use]
580 pub fn relax_logical_pointer(mut self, v: bool) -> Self {
581 self.validator.relax_logical_pointer = v;
582 self
583 }
584
585 #[must_use]
588 pub fn relax_block_layout(mut self, v: bool) -> Self {
589 self.validator.relax_block_layout = Some(v);
590 self
591 }
592
593 #[must_use]
596 pub fn uniform_buffer_standard_layout(mut self, v: bool) -> Self {
597 self.validator.uniform_buffer_standard_layout = v;
598 self
599 }
600
601 #[must_use]
605 pub fn scalar_block_layout(mut self, v: bool) -> Self {
606 self.validator.scalar_block_layout = v;
607 self
608 }
609
610 #[must_use]
613 pub fn skip_block_layout(mut self, v: bool) -> Self {
614 self.validator.skip_block_layout = v;
615 self
616 }
617
618 #[must_use]
620 pub fn preserve_bindings(mut self, v: bool) -> Self {
621 self.optimizer.preserve_bindings = v;
622 self
623 }
624
625 #[must_use]
628 pub fn extra_arg(mut self, arg: impl Into<String>) -> Self {
629 self.extra_args.push(arg.into());
630 self
631 }
632
633 #[must_use]
635 pub fn shader_crate_default_features(mut self, default_features: bool) -> Self {
636 self.shader_crate_features.default_features = default_features;
637 self
638 }
639
640 #[must_use]
642 pub fn shader_crate_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
643 self.shader_crate_features.features = features.into_iter().collect();
644 self
645 }
646
647 #[must_use]
648 pub fn rustc_codegen_spirv_location(mut self, path_to_dylib: impl AsRef<Path>) -> Self {
649 self.rustc_codegen_spirv_location = Some(path_to_dylib.as_ref().to_path_buf());
650 self
651 }
652
653 #[must_use]
657 pub fn target_dir_path(mut self, name: impl Into<PathBuf>) -> Self {
658 self.target_dir_path = Some(name.into());
659 self
660 }
661
662 pub fn build(&self) -> Result<CompileResult, SpirvBuilderError> {
665 let metadata_file = invoke_rustc(self)?;
666 match self.print_metadata {
667 MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
668 leaf_deps(&metadata_file, |artifact| {
669 println!("cargo:rerun-if-changed={artifact}");
670 })
671 .map_err(SpirvBuilderError::MetadataFileMissing)?;
673 }
674 MetadataPrintout::None => (),
675 }
676 let metadata = self.parse_metadata_file(&metadata_file)?;
677
678 Ok(metadata)
679 }
680
681 pub(crate) fn parse_metadata_file(
682 &self,
683 at: &Path,
684 ) -> Result<CompileResult, SpirvBuilderError> {
685 let metadata_contents = File::open(at).map_err(SpirvBuilderError::MetadataFileMissing)?;
686 let metadata: CompileResult =
688 rustc_codegen_spirv_types::serde_json::from_reader(BufReader::new(metadata_contents))
689 .map_err(SpirvBuilderError::MetadataFileMalformed)?;
690 match &metadata.module {
691 ModuleResult::SingleModule(spirv_module) => {
692 assert!(!self.multimodule);
693 let env_var = format!(
694 "{}.spv",
695 at.file_name()
696 .unwrap()
697 .to_str()
698 .unwrap()
699 .strip_suffix(ARTIFACT_SUFFIX)
700 .unwrap()
701 );
702 if self.print_metadata == MetadataPrintout::Full {
703 println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
704 }
705 }
706 ModuleResult::MultiModule(_) => {
707 assert!(self.multimodule);
708 }
709 }
710 Ok(metadata)
711 }
712}
713
714fn dylib_path_envvar() -> &'static str {
716 if cfg!(windows) {
717 "PATH"
718 } else if cfg!(target_os = "macos") {
719 "DYLD_FALLBACK_LIBRARY_PATH"
720 } else {
721 "LD_LIBRARY_PATH"
722 }
723}
724fn dylib_path() -> Vec<PathBuf> {
725 match env::var_os(dylib_path_envvar()) {
726 Some(var) => env::split_paths(&var).collect(),
727 None => Vec::new(),
728 }
729}
730
731fn find_rustc_codegen_spirv() -> Result<PathBuf, SpirvBuilderError> {
732 if cfg!(feature = "rustc_codegen_spirv") {
733 let filename = format!(
734 "{}rustc_codegen_spirv{}",
735 env::consts::DLL_PREFIX,
736 env::consts::DLL_SUFFIX
737 );
738 for mut path in dylib_path() {
739 path.push(&filename);
740 if path.is_file() {
741 return Ok(path);
742 }
743 }
744 panic!("Could not find {filename} in library path");
745 } else {
746 Err(SpirvBuilderError::MissingRustcCodegenSpirvDylib)
747 }
748}
749
750fn join_checking_for_separators(strings: Vec<impl Borrow<str>>, sep: &str) -> String {
753 for s in &strings {
754 let s = s.borrow();
755 assert!(!s.contains(sep), "{s:?} may not contain separator {sep:?}");
756 }
757 strings.join(sep)
758}
759
760fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
762 let target = builder
763 .target
764 .as_ref()
765 .ok_or(SpirvBuilderError::MissingTarget)?;
766 let path_to_crate = builder
767 .path_to_crate
768 .as_ref()
769 .ok_or(SpirvBuilderError::MissingCratePath)?;
770 {
771 let target_env = target.strip_prefix(SPIRV_TARGET_PREFIX).ok_or_else(|| {
772 SpirvBuilderError::NonSpirvTarget {
773 target: target.clone(),
774 }
775 })?;
776 #[allow(clippy::match_same_arms)]
778 match target_env {
779 "spv1.0" | "spv1.1" | "spv1.2" | "spv1.3" | "spv1.4" | "spv1.5" | "spv1.6" => {}
785 "opengl4.0" | "opengl4.1" | "opengl4.2" | "opengl4.3" | "opengl4.5" => {}
786 "vulkan1.0" | "vulkan1.1" | "vulkan1.1spv1.4" | "vulkan1.2" | "vulkan1.3"
787 | "vulkan1.4" => {}
788
789 _ => {
790 return Err(SpirvBuilderError::UnsupportedSpirvTargetEnv {
791 target_env: target_env.into(),
792 });
793 }
794 }
795
796 if (builder.print_metadata == MetadataPrintout::Full) && builder.multimodule {
797 return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
798 }
799 if !path_to_crate.is_dir() {
800 return Err(SpirvBuilderError::CratePathDoesntExist(
801 path_to_crate.clone(),
802 ));
803 }
804 }
805
806 let toolchain_rustc_version =
807 if let Some(toolchain_rustc_version) = &builder.toolchain_rustc_version {
808 toolchain_rustc_version.clone()
809 } else {
810 query_rustc_version(builder.toolchain_overwrite.as_deref())?
811 };
812
813 let rustc_codegen_spirv = Ok(builder.rustc_codegen_spirv_location.clone())
822 .transpose()
823 .unwrap_or_else(find_rustc_codegen_spirv)?;
824 if !rustc_codegen_spirv.is_file() {
825 return Err(SpirvBuilderError::RustcCodegenSpirvDylibDoesNotExist(
826 rustc_codegen_spirv,
827 ));
828 }
829
830 let mut rustflags = vec![
831 format!("-Zcodegen-backend={}", rustc_codegen_spirv.display()),
832 "-Zbinary-dep-depinfo".to_string(),
836 "-Csymbol-mangling-version=v0".to_string(),
837 "-Zcrate-attr=feature(register_tool)".to_string(),
838 "-Zcrate-attr=register_tool(rust_gpu)".to_string(),
839 "-Coverflow-checks=off".to_string(),
842 "-Cdebug-assertions=off".to_string(),
843 "-Zinline-mir=off".to_string(),
846 "-Zmir-enable-passes=-GVN".to_string(),
851 "-Zshare-generics=off".to_string(),
858 ];
859
860 let tracked_env_var_get = |name| {
862 if let MetadataPrintout::Full | MetadataPrintout::DependencyOnly = builder.print_metadata {
863 println!("cargo:rerun-if-env-changed={name}");
864 }
865 env::var(name)
866 };
867
868 let mut llvm_args = vec![];
869 if builder.multimodule {
870 llvm_args.push("--module-output=multiple".to_string());
871 }
872 match builder.spirv_metadata {
873 SpirvMetadata::None => (),
874 SpirvMetadata::NameVariables => {
875 llvm_args.push("--spirv-metadata=name-variables".to_string());
876 }
877 SpirvMetadata::Full => llvm_args.push("--spirv-metadata=full".to_string()),
878 }
879 if builder.validator.relax_struct_store {
880 llvm_args.push("--relax-struct-store".to_string());
881 }
882 if builder.validator.relax_logical_pointer {
883 llvm_args.push("--relax-logical-pointer".to_string());
884 }
885 if builder.validator.relax_block_layout.unwrap_or(false) {
886 llvm_args.push("--relax-block-layout".to_string());
887 }
888 if builder.validator.uniform_buffer_standard_layout {
889 llvm_args.push("--uniform-buffer-standard-layout".to_string());
890 }
891 if builder.validator.scalar_block_layout {
892 llvm_args.push("--scalar-block-layout".to_string());
893 }
894 if builder.validator.skip_block_layout {
895 llvm_args.push("--skip-block-layout".to_string());
896 }
897 if builder.optimizer.preserve_bindings {
898 llvm_args.push("--preserve-bindings".to_string());
899 }
900 let mut target_features = vec![];
901 let abort_strategy = match builder.shader_panic_strategy {
902 ShaderPanicStrategy::SilentExit => None,
903 ShaderPanicStrategy::DebugPrintfThenExit {
904 print_inputs,
905 print_backtrace,
906 } => {
907 target_features.push("+ext:SPV_KHR_non_semantic_info".into());
908 Some(format!(
909 "debug-printf{}{}",
910 if print_inputs { "+inputs" } else { "" },
911 if print_backtrace { "+backtrace" } else { "" }
912 ))
913 }
914 ShaderPanicStrategy::UNSOUND_DO_NOT_USE_UndefinedBehaviorViaUnreachable => {
915 Some("unreachable".into())
916 }
917 };
918 llvm_args.extend(abort_strategy.map(|strategy| format!("--abort-strategy={strategy}")));
919
920 if let Ok(extra_codegen_args) = tracked_env_var_get("RUSTGPU_CODEGEN_ARGS") {
921 llvm_args.extend(extra_codegen_args.split_whitespace().map(|s| s.to_string()));
922 } else {
923 llvm_args.extend(builder.extra_args.iter().cloned());
924 }
925
926 let llvm_args = join_checking_for_separators(llvm_args, " ");
927 if !llvm_args.is_empty() {
928 rustflags.push(["-Cllvm-args=", &llvm_args].concat());
929 }
930
931 target_features.extend(builder.capabilities.iter().map(|cap| format!("+{cap:?}")));
932 target_features.extend(builder.extensions.iter().map(|ext| format!("+ext:{ext}")));
933 let target_features = join_checking_for_separators(target_features, ",");
934 if !target_features.is_empty() {
935 rustflags.push(["-Ctarget-feature=", &target_features].concat());
936 }
937
938 if builder.deny_warnings {
939 rustflags.push("-Dwarnings".to_string());
940 }
941
942 if let Ok(extra_rustflags) = tracked_env_var_get("RUSTGPU_RUSTFLAGS") {
943 rustflags.extend(extra_rustflags.split_whitespace().map(|s| s.to_string()));
944 }
945
946 let target_dir_path = builder
947 .target_dir_path
948 .clone()
949 .unwrap_or_else(|| PathBuf::from("spirv-builder"));
950 let target_dir = if target_dir_path.is_absolute() {
951 target_dir_path
952 } else {
953 let metadata = cargo_metadata::MetadataCommand::new()
954 .current_dir(path_to_crate)
955 .exec()?;
956 metadata
957 .target_directory
958 .into_std_path_buf()
959 .join(target_dir_path)
960 };
961
962 let profile = if builder.release { "release" } else { "dev" };
963
964 let mut cargo = Command::new("cargo");
965 if let Some(toolchain) = &builder.toolchain_overwrite {
966 cargo.arg(format!("+{toolchain}"));
967 }
968 cargo.args([
969 "build",
970 "--lib",
971 "--message-format=json-render-diagnostics",
972 "-Zbuild-std=core",
973 "-Zbuild-std-features=compiler-builtins-mem",
974 "--profile",
975 profile,
976 ]);
977
978 if let Ok(extra_cargoflags) = tracked_env_var_get("RUSTGPU_CARGOFLAGS") {
979 cargo.args(extra_cargoflags.split_whitespace());
980 }
981
982 if toolchain_rustc_version >= Version::new(1, 76, 0) {
989 let path_opt = builder.path_to_target_spec.clone();
990 let path;
991 #[cfg(feature = "include-target-specs")]
992 {
993 path = path_opt
994 .unwrap_or_else(|| PathBuf::from(format!("{TARGET_SPEC_DIR_PATH}/{target}.json")));
995 }
996 #[cfg(not(feature = "include-target-specs"))]
997 {
998 path = path_opt.ok_or(SpirvBuilderError::MissingTargetSpec)?;
999 }
1000 cargo.arg("--target").arg(path);
1001 } else {
1002 cargo.arg("--target").arg(target);
1003 }
1004
1005 if !builder.shader_crate_features.default_features {
1006 cargo.arg("--no-default-features");
1007 }
1008
1009 if !builder.shader_crate_features.features.is_empty() {
1010 cargo
1011 .arg("--features")
1012 .arg(builder.shader_crate_features.features.join(","));
1013 }
1014
1015 cargo.arg("--target-dir").arg(target_dir);
1016
1017 for (key, _) in env::vars_os() {
1021 let remove = key
1022 .to_str()
1023 .is_some_and(|s| s.starts_with("CARGO_FEATURES_") || s.starts_with("CARGO_CFG_"));
1024 if remove {
1025 cargo.env_remove(key);
1026 }
1027 }
1028
1029 cargo.env("CARGO_CACHE_RUSTC_INFO", "0");
1034
1035 cargo.env_remove("RUSTC");
1040
1041 cargo.env(
1045 "CARGO_ENCODED_RUSTFLAGS",
1046 join_checking_for_separators(rustflags, "\x1f"),
1047 );
1048
1049 let profile_in_env_var = profile.replace('-', "_").to_ascii_uppercase();
1050
1051 let num_cgus = 1;
1054 cargo.env(
1055 format!("CARGO_PROFILE_{profile_in_env_var}_CODEGEN_UNITS"),
1056 num_cgus.to_string(),
1057 );
1058
1059 let build = cargo
1060 .stderr(Stdio::inherit())
1061 .current_dir(path_to_crate)
1062 .output()
1063 .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}