Skip to main content

cargo_config2/
env.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3// Environment variables are prefer over config values.
4// https://doc.rust-lang.org/nightly/cargo/reference/config.html#environment-variables
5
6use alloc::borrow::ToOwned as _;
7
8use crate::{
9    de::{
10        BuildConfig, CargoNewConfig, Config, CredentialProvider, DocConfig, Flags,
11        FutureIncompatReportConfig, GlobalCredentialProviders, HttpConfig, NetConfig, PathAndArgs,
12        RegistriesConfigValue, RegistryConfig, StringList, StringOrArray, TermConfig, TermProgress,
13        split_space_separated,
14    },
15    error::{Context as _, Error, Result},
16    resolve::ResolveContext,
17    value::{Definition, Value},
18};
19
20pub(crate) trait ApplyEnv {
21    /// Applies configuration environment variables.
22    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()>;
23}
24
25impl Config {
26    /// Applies configuration environment variables.
27    ///
28    /// **Note:** This ignores environment variables for target-specific
29    /// configurations ([`self.target`](Self::target). This is because it is
30    /// difficult to determine exactly which target the target-specific
31    /// configuration defined in the environment variables are for.
32    /// (e.g., In environment variables, `-` and `.` in the target triple are replaced by `_`)
33    #[doc(hidden)] // Not public API.
34    pub fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
35        for (k, v) in &cx.env {
36            let definition = || Some(Definition::Environment(k.clone().into()));
37            let error_env_not_unicode = || Error::env_not_unicode(k, v.clone());
38            let error_env_not_unicode_redacted = || Error::env_not_unicode_redacted(k);
39
40            // https://doc.rust-lang.org/nightly/cargo/reference/config.html#alias
41            if let Some(k) = k.strip_prefix("CARGO_ALIAS_") {
42                self.alias.insert(
43                    k.to_owned(),
44                    StringList::from_string(
45                        v.to_str().ok_or_else(error_env_not_unicode)?,
46                        definition().as_ref(),
47                    ),
48                );
49            }
50            // https://doc.rust-lang.org/nightly/cargo/reference/config.html#credential-alias
51            else if let Some(k) = k.strip_prefix("CARGO_CREDENTIAL_ALIAS_") {
52                self.credential_alias.insert(
53                    k.to_owned(),
54                    PathAndArgs::from_string(
55                        v.to_str().ok_or_else(error_env_not_unicode)?,
56                        definition().as_ref(),
57                    )
58                    .context("invalid length 0, expected at least one element")?,
59                );
60            }
61            // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries
62            else if let Some(k) = k.strip_prefix("CARGO_REGISTRIES_") {
63                if let Some(k) = k.strip_suffix("_INDEX") {
64                    let v = v.to_str().ok_or_else(error_env_not_unicode)?;
65                    let index = Some(Value { val: v.to_owned(), definition: definition() });
66                    if let Some(registries_config_value) = self.registries.get_mut(k) {
67                        registries_config_value.index = index;
68                    } else {
69                        self.registries.insert(k.to_owned(), RegistriesConfigValue {
70                            index,
71                            token: None,
72                            credential_provider: None,
73                            protocol: None,
74                        });
75                    }
76                } else if let Some(k) = k.strip_suffix("_TOKEN") {
77                    let v = v.to_str().ok_or_else(error_env_not_unicode_redacted)?;
78                    let token = Some(Value { val: v.to_owned(), definition: definition() });
79                    if let Some(registries_config_value) = self.registries.get_mut(k) {
80                        registries_config_value.token = token;
81                    } else {
82                        self.registries.insert(k.to_owned(), RegistriesConfigValue {
83                            index: None,
84                            token,
85                            credential_provider: None,
86                            protocol: None,
87                        });
88                    }
89                } else if let Some(k) = k.strip_suffix("_CREDENTIAL_PROVIDER") {
90                    let credential_provider = Some(
91                        CredentialProvider::from_string(k, definition().as_ref())
92                            .context("invalid length 0, expected at least one element")?,
93                    );
94                    if let Some(registries_config_value) = self.registries.get_mut(k) {
95                        registries_config_value.credential_provider = credential_provider;
96                    } else {
97                        self.registries.insert(k.to_owned(), RegistriesConfigValue {
98                            index: None,
99                            token: None,
100                            credential_provider,
101                            protocol: None,
102                        });
103                    }
104                } else if k == "CRATES_IO_PROTOCOL" {
105                    let k = "crates-io";
106                    let v = v.to_str().ok_or_else(error_env_not_unicode)?;
107                    let protocol =
108                        Some(Value { val: v.to_owned(), definition: definition() }.parse()?);
109                    if let Some(registries_config_value) = self.registries.get_mut(k) {
110                        registries_config_value.protocol = protocol;
111                    } else {
112                        self.registries.insert(k.to_owned(), RegistriesConfigValue {
113                            index: None,
114                            token: None,
115                            credential_provider: None,
116                            protocol,
117                        });
118                    }
119                }
120            }
121        }
122
123        // For self.target, we handle it in de::Config::resolve_target.
124
125        self.build.apply_env(cx)?;
126        self.doc.apply_env(cx)?;
127        self.future_incompat_report.apply_env(cx)?;
128        self.http.apply_env(cx)?;
129        self.net.apply_env(cx)?;
130        self.registry.apply_env(cx)?;
131        self.term.apply_env(cx)?;
132        Ok(())
133    }
134}
135
136impl ApplyEnv for BuildConfig {
137    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
138        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildjobs
139        if let Some(jobs) = cx.env_parse("CARGO_BUILD_JOBS")? {
140            self.jobs = Some(jobs);
141        }
142
143        // The following priorities are not documented, but at as of cargo
144        // 1.63.0-nightly (2022-05-31), `RUSTC*` are preferred over `CARGO_BUILD_RUSTC*`.
145        // 1. RUSTC
146        // 2. build.rustc (CARGO_BUILD_RUSTC)
147        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc
148        // See also https://github.com/taiki-e/cargo-llvm-cov/pull/180#discussion_r887904341.
149        if let Some(rustc) = cx.env("RUSTC")? {
150            self.rustc = Some(rustc);
151        } else if let Some(rustc) = cx.env("CARGO_BUILD_RUSTC")? {
152            self.rustc = Some(rustc);
153        }
154        // 1. RUSTC_WRAPPER
155        // 2. build.rustc-wrapper (CARGO_BUILD_RUSTC_WRAPPER)
156        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-wrapper
157        // Setting this to an empty string instructs cargo to not use a wrapper.
158        // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads
159        if let Some(rustc_wrapper) = cx.env("RUSTC_WRAPPER")? {
160            if rustc_wrapper.val.is_empty() {
161                self.rustc_wrapper = None;
162            } else {
163                self.rustc_wrapper = Some(rustc_wrapper);
164            }
165        } else if let Some(rustc_wrapper) = cx.env("CARGO_BUILD_RUSTC_WRAPPER")? {
166            if rustc_wrapper.val.is_empty() {
167                self.rustc_wrapper = None;
168            } else {
169                self.rustc_wrapper = Some(rustc_wrapper);
170            }
171        }
172        // 1. RUSTC_WORKSPACE_WRAPPER
173        // 2. build.rustc-workspace-wrapper (CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER)
174        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-workspace-wrapper
175        // Setting this to an empty string instructs cargo to not use a wrapper.
176        // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads
177        if let Some(rustc_workspace_wrapper) = cx.env("RUSTC_WORKSPACE_WRAPPER")? {
178            if rustc_workspace_wrapper.val.is_empty() {
179                self.rustc_workspace_wrapper = None;
180            } else {
181                self.rustc_workspace_wrapper = Some(rustc_workspace_wrapper);
182            }
183        } else if let Some(rustc_workspace_wrapper) =
184            cx.env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER")?
185        {
186            if rustc_workspace_wrapper.val.is_empty() {
187                self.rustc_workspace_wrapper = None;
188            } else {
189                self.rustc_workspace_wrapper = Some(rustc_workspace_wrapper);
190            }
191        }
192        // 1. RUSTDOC
193        // 2. build.rustdoc (CARGO_BUILD_RUSTDOC)
194        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdoc
195        if let Some(rustdoc) = cx.env("RUSTDOC")? {
196            self.rustdoc = Some(rustdoc);
197        } else if let Some(rustdoc) = cx.env("CARGO_BUILD_RUSTDOC")? {
198            self.rustdoc = Some(rustdoc);
199        }
200
201        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget
202        if let Some(target) = cx.env("CARGO_BUILD_TARGET")? {
203            self.target = Some(StringOrArray::String(target));
204        }
205
206        // The following priorities are not documented, but at as of cargo
207        // 1.68.0-nightly (2022-12-23), `CARGO_*` are preferred over `CARGO_BUILD_*`.
208        // 1. CARGO_TARGET_DIR
209        // 2. CARGO_BUILD_TARGET_DIR
210        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget
211        if let Some(target_dir) = cx.env("CARGO_TARGET_DIR")? {
212            self.target_dir = Some(target_dir);
213        } else if let Some(target_dir) = cx.env("CARGO_BUILD_TARGET_DIR")? {
214            self.target_dir = Some(target_dir);
215        }
216
217        // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-dir
218        if let Some(build_dir) = cx.env("CARGO_BUILD_BUILD_DIR")? {
219            self.build_dir = Some(build_dir);
220        }
221
222        // 1. CARGO_ENCODED_RUSTFLAGS
223        // 2. RUSTFLAGS
224        // 3. target.<triple>.rustflags (CARGO_TARGET_<triple>_RUSTFLAGS) and target.<cfg>.rustflags
225        // 4. build.rustflags (CARGO_BUILD_RUSTFLAGS)
226        // For 3, we handle it in de::Config::resolve_target.
227        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustflags
228        self.override_target_rustflags = false;
229        if let Some(rustflags) = cx.env("CARGO_ENCODED_RUSTFLAGS")? {
230            self.rustflags = Some(Flags::from_encoded(&rustflags));
231            self.override_target_rustflags = true;
232        } else if let Some(rustflags) = cx.env("RUSTFLAGS")? {
233            self.rustflags =
234                Some(Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref()));
235            self.override_target_rustflags = true;
236        } else if let Some(rustflags) = cx.env("CARGO_BUILD_RUSTFLAGS")? {
237            self.rustflags =
238                Some(Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref()));
239        }
240        // 1. CARGO_ENCODED_RUSTDOCFLAGS
241        // 2. RUSTDOCFLAGS
242        // 3. target.<triple>.rustdocflags (CARGO_TARGET_<triple>_RUSTDOCFLAGS)
243        // 4. build.rustdocflags (CARGO_BUILD_RUSTDOCFLAGS)
244        // For 3, we handle it in de::Config::resolve_target.
245        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags
246        self.override_target_rustdocflags = false;
247        if let Some(rustdocflags) = cx.env("CARGO_ENCODED_RUSTDOCFLAGS")? {
248            self.rustdocflags = Some(Flags::from_encoded(&rustdocflags));
249            self.override_target_rustdocflags = true;
250        } else if let Some(rustdocflags) = cx.env("RUSTDOCFLAGS")? {
251            self.rustdocflags = Some(Flags::from_space_separated(
252                &rustdocflags.val,
253                rustdocflags.definition.as_ref(),
254            ));
255            self.override_target_rustdocflags = true;
256        } else if let Some(rustdocflags) = cx.env("CARGO_BUILD_RUSTDOCFLAGS")? {
257            self.rustdocflags = Some(Flags::from_space_separated(
258                &rustdocflags.val,
259                rustdocflags.definition.as_ref(),
260            ));
261        }
262
263        // The following priorities are not documented, but at as of cargo
264        // 1.68.0-nightly (2022-12-23), `CARGO_*` are preferred over `CARGO_BUILD_*`.
265        // 1. CARGO_INCREMENTAL
266        // 2. CARGO_BUILD_INCREMENTAL
267        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildincremental
268        if let Some(incremental) = cx.env("CARGO_INCREMENTAL")? {
269            // As of cargo 1.68.0-nightly (2022-12-23), cargo handles invalid value like 0.
270            self.incremental =
271                Some(Value { val: incremental.val == "1", definition: incremental.definition });
272        } else if let Some(incremental) = cx.env_parse("CARGO_BUILD_INCREMENTAL")? {
273            self.incremental = Some(incremental);
274        }
275
276        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#builddep-info-basedir
277        if let Some(dep_info_basedir) = cx.env("CARGO_BUILD_DEP_INFO_BASEDIR")? {
278            self.dep_info_basedir = Some(dep_info_basedir);
279        }
280
281        Ok(())
282    }
283}
284
285impl ApplyEnv for DocConfig {
286    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
287        // doc.browser config value is prefer over BROWSER environment variable.
288        // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/ops/cargo_doc.rs#L143-L144
289        if self.browser.is_none() {
290            if let Some(browser) = cx.env("BROWSER")? {
291                self.browser = Some(
292                    PathAndArgs::from_string(&browser.val, browser.definition.as_ref())
293                        .context("invalid length 0, expected at least one element")?,
294                );
295            }
296        }
297        Ok(())
298    }
299}
300
301impl ApplyEnv for FutureIncompatReportConfig {
302    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
303        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-reportfrequency
304        if let Some(frequency) = cx.env_parse("CARGO_FUTURE_INCOMPAT_REPORT_FREQUENCY")? {
305            self.frequency = Some(frequency);
306        }
307        Ok(())
308    }
309}
310
311impl ApplyEnv for CargoNewConfig {
312    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
313        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-newvcs
314        if let Some(vcs) = cx.env_parse("CARGO_CARGO_NEW_VCS")? {
315            self.vcs = Some(vcs);
316        }
317        Ok(())
318    }
319}
320
321impl ApplyEnv for HttpConfig {
322    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
323        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpdebug
324        if let Some(debug) = cx.env_parse("CARGO_HTTP_DEBUG")? {
325            self.debug = Some(debug);
326        }
327        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpproxy
328        // TODO:
329        // > CARGO_HTTP_PROXY or HTTPS_PROXY or https_proxy or http_proxy
330        if let Some(proxy) = cx.env("CARGO_HTTP_PROXY")? {
331            self.proxy = Some(proxy);
332        }
333        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
334        // TODO:
335        // > CARGO_HTTP_TIMEOUT or HTTP_TIMEOUT
336        if let Some(timeout) = cx.env_parse("CARGO_HTTP_TIMEOUT")? {
337            self.timeout = Some(timeout);
338        }
339        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcainfo
340        if let Some(cainfo) = cx.env("CARGO_HTTP_CAINFO")? {
341            self.cainfo = Some(cainfo);
342        }
343        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcheck-revoke
344        if let Some(check_revoke) = cx.env_parse("CARGO_HTTP_CHECK_REVOKE")? {
345            self.check_revoke = Some(check_revoke);
346        }
347        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httplow-speed-limit
348        if let Some(low_speed_limit) = cx.env_parse("CARGO_HTTP_LOW_SPEED_LIMIT")? {
349            self.low_speed_limit = Some(low_speed_limit);
350        }
351        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpmultiplexing
352        if let Some(multiplexing) = cx.env_parse("CARGO_HTTP_MULTIPLEXING")? {
353            self.multiplexing = Some(multiplexing);
354        }
355        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpuser-agent
356        if let Some(user_agent) = cx.env("CARGO_HTTP_USER_AGENT")? {
357            self.user_agent = Some(user_agent);
358        }
359        Ok(())
360    }
361}
362
363impl ApplyEnv for NetConfig {
364    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
365        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netretry
366        if let Some(retry) = cx.env_parse("CARGO_NET_RETRY")? {
367            self.retry = Some(retry);
368        }
369        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netgit-fetch-with-cli
370        if let Some(git_fetch_with_cli) = cx.env_parse("CARGO_NET_GIT_FETCH_WITH_CLI")? {
371            self.git_fetch_with_cli = Some(git_fetch_with_cli);
372        }
373        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netoffline
374        if let Some(offline) = cx.env_parse("CARGO_NET_OFFLINE")? {
375            self.offline = Some(offline);
376        }
377        Ok(())
378    }
379}
380
381impl ApplyEnv for RegistryConfig {
382    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
383        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrydefault
384        if let Some(default) = cx.env("CARGO_REGISTRY_DEFAULT")? {
385            self.default = Some(default);
386        }
387        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrycredential-provider
388        if let Some(credential_provider) = cx.env("CARGO_REGISTRY_CREDENTIAL_PROVIDER")? {
389            self.credential_provider = Some(CredentialProvider::from_string(
390                &credential_provider.val,
391                credential_provider.definition.as_ref(),
392            )?);
393        }
394        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrytoken
395        if let Some(token) = cx.env_redacted("CARGO_REGISTRY_TOKEN")? {
396            self.token = Some(token);
397        }
398        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registryglobal-credential-providers
399        if let Some(global_credential_providers) =
400            cx.env("CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS")?
401        {
402            self.global_credential_providers = GlobalCredentialProviders::from_list(
403                split_space_separated(&global_credential_providers.val),
404                global_credential_providers.definition.as_ref(),
405            )?;
406        }
407        Ok(())
408    }
409}
410
411impl ApplyEnv for TermConfig {
412    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
413        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termquiet
414        if let Some(quiet) = cx.env_parse("CARGO_TERM_QUIET")? {
415            self.quiet = Some(quiet);
416        }
417        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termverbose
418        if let Some(verbose) = cx.env_parse("CARGO_TERM_VERBOSE")? {
419            self.verbose = Some(verbose);
420        }
421        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termcolor
422        if let Some(color) = cx.env_parse("CARGO_TERM_COLOR")? {
423            self.color = Some(color);
424        }
425        self.progress.apply_env(cx)?;
426        Ok(())
427    }
428}
429
430impl ApplyEnv for TermProgress {
431    fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> {
432        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswhen
433        if let Some(when) = cx.env_parse("CARGO_TERM_PROGRESS_WHEN")? {
434            self.when = Some(when);
435        }
436        // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswidth
437        if let Some(width) = cx.env_parse("CARGO_TERM_PROGRESS_WIDTH")? {
438            self.width = Some(width);
439        }
440        Ok(())
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use alloc::borrow::ToOwned as _;
447
448    use super::ApplyEnv as _;
449    use crate::{ResolveOptions, value::Value};
450
451    #[test]
452    fn empty_string_wrapper_envs() {
453        let env_list = [("RUSTC_WRAPPER", ""), ("RUSTC_WORKSPACE_WRAPPER", "")];
454        let mut config = crate::de::BuildConfig::default();
455        let cx =
456            &ResolveOptions::default().env(env_list).into_context(std::env::current_dir().unwrap());
457        config.rustc_wrapper = Some(Value { val: "rustc_wrapper".to_owned(), definition: None });
458        config.rustc_workspace_wrapper =
459            Some(Value { val: "rustc_workspace_wrapper".to_owned(), definition: None });
460        config.apply_env(cx).unwrap();
461        assert!(config.rustc_wrapper.is_none());
462        assert!(config.rustc_workspace_wrapper.is_none());
463    }
464}