spirv_builder/
cargo_cmd.rs

1use std::collections::HashSet;
2use std::env;
3use std::ffi::{OsStr, OsString};
4use std::fmt::{Display, Formatter};
5use std::ops::{Deref, DerefMut};
6use std::process::Command;
7
8/// Filters the various env vars that a `cargo` child process would receive and reports back
9/// what was inherited and what was removed. By default, removes all env vars that influences
10/// the cargo invocations of a spirv-builder's build or cargo-gpu's install action.
11pub struct CargoCmd {
12    cargo: Command,
13    vars_os: Vec<(OsString, OsString)>,
14    removed: HashSet<OsString>,
15}
16
17impl CargoCmd {
18    pub fn new() -> Self {
19        let mut cargo = CargoCmd::new_no_filtering();
20
21        // Clear Cargo environment variables that we don't want to leak into the
22        // inner invocation of Cargo (because e.g. build scripts might read them),
23        // before we set any of our own below.
24        cargo.retain_vars_os(|(key, _)| {
25            !key.to_str()
26                .is_some_and(|s| s.starts_with("CARGO_FEATURES_") || s.starts_with("CARGO_CFG_"))
27        });
28
29        // NOTE(eddyb) Cargo caches some information it got from `rustc` in
30        // `.rustc_info.json`, and assumes it only depends on the `rustc` binary,
31        // but in our case, `rustc_codegen_spirv` changes are also relevant,
32        // so we turn off that caching with an env var, just to avoid any issues.
33        cargo.env("CARGO_CACHE_RUSTC_INFO", "0");
34
35        // NOTE(firestar99) If you call SpirvBuilder in a build script, it will
36        // set `RUSTC` before calling it. And if we were to propagate it to our
37        // cargo invocation, it will take precedence over the `+toolchain` we
38        // previously set.
39        cargo.env_remove("RUSTC");
40
41        // NOTE(tuguzT) Used by Cargo to call executables of Clippy, Miri
42        // (and maybe other Cargo subcommands) instead of `rustc`
43        // which could affect its functionality and break the build process.
44        cargo.env_remove("RUSTC_WRAPPER");
45
46        // NOTE(firestar99, tuguzT) Other environment variables that we don't want to
47        // leak into the inner invocation of Cargo & break the build process.
48        cargo
49            .env_remove("RUSTC_WORKSPACE_WRAPPER")
50            .env_remove("RUSTFLAGS")
51            .env_remove("CARGO")
52            .env_remove("RUSTUP_TOOLCHAIN");
53
54        // NOTE(firestar99) Overwritten by spirv-builder anyway
55        cargo.env_remove("CARGO_ENCODED_RUSTFLAGS");
56
57        // NOTE(firestar99) Ignore any externally supplied target dir:
58        // - spirv-builder: we overwrite it with `SpirvBuilder.target_dir_path` anyway
59        // - cargo-gpu: we want to build it in our cache dir
60        cargo.env_remove("CARGO_TARGET_DIR");
61
62        cargo
63    }
64
65    pub fn new_no_filtering() -> Self {
66        Self {
67            cargo: Command::new("cargo"),
68            vars_os: env::vars_os().collect(),
69            removed: HashSet::default(),
70        }
71    }
72
73    pub fn retain_vars_os(&mut self, mut f: impl FnMut((&OsString, &OsString)) -> bool) {
74        for (key, value) in &self.vars_os {
75            if !f((key, value)) {
76                self.removed.insert(key.clone());
77                self.cargo.env_remove(key);
78            }
79        }
80    }
81
82    pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
83        self.removed.insert(key.as_ref().to_os_string());
84        self.cargo.env_remove(key);
85        self
86    }
87
88    pub fn env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> &mut Self {
89        self.removed.remove(key.as_ref());
90        self.cargo.env(key, val);
91        self
92    }
93
94    pub fn env_var_report(&self) -> EnvVarReport {
95        let mut inherited = self.vars_os.clone();
96        inherited.retain(|(key, _)| !self.removed.contains(key));
97        EnvVarReport {
98            inherited,
99            removed: self.removed.clone(),
100        }
101    }
102}
103
104impl Default for CargoCmd {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl Display for CargoCmd {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("CargoCmd")
113            .field("cargo", &self.cargo)
114            .field("env_vars", &self.env_var_report())
115            .finish()
116    }
117}
118
119impl Deref for CargoCmd {
120    type Target = Command;
121
122    fn deref(&self) -> &Self::Target {
123        &self.cargo
124    }
125}
126
127impl DerefMut for CargoCmd {
128    fn deref_mut(&mut self) -> &mut Self::Target {
129        &mut self.cargo
130    }
131}
132
133#[derive(Clone, Debug, Default)]
134pub struct EnvVarReport {
135    pub inherited: Vec<(OsString, OsString)>,
136    pub removed: HashSet<OsString>,
137}
138
139impl Display for EnvVarReport {
140    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141        let removed = self
142            .removed
143            .iter()
144            .map(|key| format!("{}", key.to_string_lossy()))
145            .collect::<Vec<_>>();
146        let inherited = self
147            .inherited
148            .iter()
149            .map(|(key, value)| format!("{}: {}", key.to_string_lossy(), value.to_string_lossy()))
150            .collect::<Vec<_>>();
151
152        f.debug_struct("EnvVarReport")
153            .field("removed", &removed)
154            .field("inherited", &inherited)
155            .finish()
156    }
157}