Skip to main content

cargo_config2/
resolve.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use alloc::{
4    borrow::{Cow, ToOwned as _},
5    boxed::Box,
6    collections::BTreeSet,
7    format,
8    string::String,
9};
10use core::{
11    cell::{OnceCell, RefCell},
12    cmp,
13    hash::Hash,
14    iter,
15    str::{self, FromStr},
16};
17use std::{
18    collections::{HashMap, HashSet},
19    ffi::{OsStr, OsString},
20    path::{Path, PathBuf},
21};
22
23use serde::{
24    de::{Deserialize, Deserializer},
25    ser::{Serialize, Serializer},
26};
27use serde_derive::{Deserialize, Serialize};
28
29use crate::{
30    PathAndArgs, cfg,
31    cfg_expr::expr::{Expression, Predicate},
32    easy,
33    error::{Context as _, Error, Result},
34    process::ProcessBuilder,
35    value::{Definition, Value},
36    walk,
37};
38
39#[derive(Debug, Clone, Default)]
40#[must_use]
41pub struct ResolveOptions {
42    env: Option<HashMap<String, OsString>>,
43    rustc: Option<PathAndArgs>,
44    cargo: Option<OsString>,
45    #[allow(clippy::option_option)]
46    cargo_home: Option<Option<PathBuf>>,
47    host_triple: Option<Box<str>>,
48}
49
50impl ResolveOptions {
51    /// Sets `rustc` path and args.
52    ///
53    /// # Default value
54    ///
55    /// [`Config::rustc`](crate::Config::rustc)
56    pub fn rustc<P: Into<PathAndArgs>>(mut self, rustc: P) -> Self {
57        self.rustc = Some(rustc.into());
58        self
59    }
60    /// Sets `cargo` path.
61    ///
62    /// # Default value
63    ///
64    /// The value of the `CARGO` environment variable if it is set. Otherwise, "cargo".
65    pub fn cargo<S: Into<OsString>>(mut self, cargo: S) -> Self {
66        self.cargo = Some(cargo.into());
67        self
68    }
69    /// Sets `CARGO_HOME` path.
70    ///
71    /// # Default value
72    ///
73    /// [`home::cargo_home_with_cwd`] if the current directory was specified when
74    /// loading config. Otherwise, [`home::cargo_home`].
75    ///
76    /// [`home::cargo_home_with_cwd`]: https://docs.rs/home/latest/home/fn.cargo_home_with_cwd.html
77    /// [`home::cargo_home`]: https://docs.rs/home/latest/home/fn.cargo_home.html
78    pub fn cargo_home<P: Into<Option<PathBuf>>>(mut self, cargo_home: P) -> Self {
79        self.cargo_home = Some(cargo_home.into());
80        self
81    }
82    /// Sets host target triple.
83    ///
84    /// # Default value
85    ///
86    /// Parse the version output of `cargo` specified by [`Self::cargo`].
87    pub fn host_triple<S: Into<String>>(mut self, triple: S) -> Self {
88        self.host_triple = Some(triple.into().into_boxed_str());
89        self
90    }
91    /// Sets the specified key-values as environment variables to be read during
92    /// config resolution.
93    ///
94    /// This is mainly intended for use in tests where it is necessary to adjust
95    /// the kinds of environment variables that are referenced.
96    ///
97    /// # Default value
98    ///
99    /// [`std::env::vars_os`]
100    pub fn env<I: IntoIterator<Item = (K, V)>, K: Into<OsString>, V: Into<OsString>>(
101        mut self,
102        vars: I,
103    ) -> Self {
104        let mut env = HashMap::default();
105        for (k, v) in vars {
106            if let Ok(k) = k.into().into_string() {
107                if k.starts_with("CARGO") || k.starts_with("RUST") || k == "BROWSER" {
108                    env.insert(k, v.into());
109                }
110            }
111        }
112        self.env = Some(env);
113        self
114    }
115
116    #[doc(hidden)] // Not public API.
117    pub fn into_context(mut self, current_dir: PathBuf) -> ResolveContext {
118        if self.env.is_none() {
119            self = self.env(std::env::vars_os());
120        }
121        let env = self.env.unwrap();
122        let rustc = match self.rustc {
123            Some(rustc) => OnceCell::from(rustc),
124            None => OnceCell::new(),
125        };
126        let cargo = match self.cargo {
127            Some(cargo) => cargo,
128            None => env.get("CARGO").cloned().unwrap_or_else(|| "cargo".into()),
129        };
130        let cargo_home = match self.cargo_home {
131            Some(cargo_home) => OnceCell::from(cargo_home),
132            None => OnceCell::new(),
133        };
134        let host_triple = match self.host_triple {
135            Some(host_triple) => OnceCell::from(host_triple),
136            None => OnceCell::new(),
137        };
138
139        ResolveContext {
140            env,
141            rustc,
142            cargo,
143            cargo_home,
144            host_triple,
145            rustc_version: OnceCell::new(),
146            cargo_version: OnceCell::new(),
147            cfg: RefCell::default(),
148            current_dir,
149        }
150    }
151}
152
153#[doc(hidden)] // Not public API.
154#[allow(unknown_lints, unnameable_types)] // Not public API. unnameable_types is available on Rust 1.79+
155#[derive(Debug, Clone)]
156#[must_use]
157pub struct ResolveContext {
158    pub(crate) env: HashMap<String, OsString>,
159    rustc: OnceCell<easy::PathAndArgs>,
160    pub(crate) cargo: OsString,
161    cargo_home: OnceCell<Option<PathBuf>>,
162    host_triple: OnceCell<Box<str>>,
163    rustc_version: OnceCell<RustcVersion>,
164    cargo_version: OnceCell<CargoVersion>,
165    pub(crate) cfg: RefCell<CfgMap>,
166    pub(crate) current_dir: PathBuf,
167}
168
169impl ResolveContext {
170    pub(crate) fn rustc(&self, build_config: &easy::BuildConfig) -> &PathAndArgs {
171        self.rustc.get_or_init(|| {
172            // https://github.com/rust-lang/cargo/pull/10896
173            // https://github.com/rust-lang/cargo/pull/13648
174            let rustc =
175                build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from);
176            let rustc_wrapper = build_config.rustc_wrapper.clone();
177            let rustc_workspace_wrapper = build_config.rustc_workspace_wrapper.clone();
178            let mut rustc =
179                rustc_wrapper.into_iter().chain(rustc_workspace_wrapper).chain(iter::once(rustc));
180            PathAndArgs {
181                path: rustc.next().unwrap(),
182                args: rustc.map(PathBuf::into_os_string).collect(),
183            }
184        })
185    }
186    pub(crate) fn rustc_for_version(&self, build_config: &easy::BuildConfig) -> PathAndArgs {
187        // Do not apply RUSTC_WORKSPACE_WRAPPER: https://github.com/cuviper/autocfg/issues/58#issuecomment-2067625980
188        let rustc =
189            build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from);
190        let rustc_wrapper = build_config.rustc_wrapper.clone();
191        let mut rustc = rustc_wrapper.into_iter().chain(iter::once(rustc));
192        PathAndArgs {
193            path: rustc.next().unwrap(),
194            args: rustc.map(PathBuf::into_os_string).collect(),
195        }
196    }
197    pub(crate) fn cargo_home(&self, cwd: &Path) -> Option<&Path> {
198        self.cargo_home.get_or_init(|| walk::cargo_home_with_cwd(cwd)).as_deref()
199    }
200    pub(crate) fn host_triple(&self, build_config: &easy::BuildConfig) -> Result<&str> {
201        if let Some(host) = self.host_triple.get() {
202            return Ok(host);
203        }
204        let cargo_host = verbose_version(cmd!(&self.cargo)).and_then(|ref vv| {
205            let r = self.cargo_version.set(cargo_version(vv)?);
206            debug_assert!(r.is_ok());
207            host_triple(vv)
208        });
209        let host = match cargo_host {
210            Ok(host) => host,
211            Err(_) => {
212                let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?;
213                let r = self.rustc_version.set(rustc_version(vv)?);
214                debug_assert!(r.is_ok());
215                host_triple(vv)?
216            }
217        };
218        Ok(self.host_triple.get_or_init(|| host))
219    }
220    pub(crate) fn rustc_version(&self, build_config: &easy::BuildConfig) -> Result<RustcVersion> {
221        if let Some(&rustc_version) = self.rustc_version.get() {
222            return Ok(rustc_version);
223        }
224        let _ = self.host_triple(build_config);
225        if let Some(&rustc_version) = self.rustc_version.get() {
226            return Ok(rustc_version);
227        }
228        let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?;
229        let rustc_version = rustc_version(vv)?;
230        Ok(*self.rustc_version.get_or_init(|| rustc_version))
231    }
232    pub(crate) fn cargo_version(&self, build_config: &easy::BuildConfig) -> Result<CargoVersion> {
233        if let Some(&cargo_version) = self.cargo_version.get() {
234            return Ok(cargo_version);
235        }
236        let _ = self.host_triple(build_config);
237        if let Some(&cargo_version) = self.cargo_version.get() {
238            return Ok(cargo_version);
239        }
240        let vv = &verbose_version(cmd!(&self.cargo))?;
241        let cargo_version = cargo_version(vv)?;
242        Ok(*self.cargo_version.get_or_init(|| cargo_version))
243    }
244
245    // micro-optimization for static name -- avoiding name allocation can speed up
246    // de::Config::apply_env by up to 40% because most env var names we fetch are static.
247    pub(crate) fn env(&self, name: &'static str) -> Result<Option<Value<String>>> {
248        match self.env.get(name) {
249            None => Ok(None),
250            Some(v) => Ok(Some(Value {
251                val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?,
252                definition: Some(Definition::Environment(name.into())),
253            })),
254        }
255    }
256    pub(crate) fn env_redacted(&self, name: &'static str) -> Result<Option<Value<String>>> {
257        match self.env.get(name) {
258            None => Ok(None),
259            Some(v) => Ok(Some(Value {
260                val: v
261                    .clone()
262                    .into_string()
263                    .map_err(|_var| Error::env_not_unicode_redacted(name))?,
264                definition: Some(Definition::Environment(name.into())),
265            })),
266        }
267    }
268    pub(crate) fn env_parse<T>(&self, name: &'static str) -> Result<Option<Value<T>>>
269    where
270        T: FromStr,
271        T::Err: std::error::Error + Send + Sync + 'static,
272    {
273        match self.env(name)? {
274            Some(v) => Ok(Some(
275                v.parse()
276                    .with_context(|| format!("failed to parse environment variable `{name}`"))?,
277            )),
278            None => Ok(None),
279        }
280    }
281    pub(crate) fn env_dyn(&self, name: &str) -> Result<Option<Value<String>>> {
282        match self.env.get(name) {
283            None => Ok(None),
284            Some(v) => Ok(Some(Value {
285                val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?,
286                definition: Some(Definition::Environment(name.to_owned().into())),
287            })),
288        }
289    }
290
291    pub(crate) fn eval_cfg(
292        &self,
293        expr: &str,
294        target: &TargetTripleRef<'_>,
295        build_config: &easy::BuildConfig,
296    ) -> Result<bool> {
297        let expr = Expression::parse(expr).map_err(Error::new)?;
298        let mut cfg_map = self.cfg.borrow_mut();
299        cfg_map.eval_cfg(&expr, target, &|| self.rustc(build_config).into())
300    }
301}
302
303#[derive(Debug, Clone, Default)]
304pub(crate) struct CfgMap {
305    map: HashMap<TargetTripleBorrow<'static>, Cfg>,
306}
307
308impl CfgMap {
309    pub(crate) fn get_or_init<'a>(
310        &'a mut self,
311        target: &TargetTripleRef<'_>,
312        rustc: &dyn Fn() -> ProcessBuilder,
313    ) -> Result<&'a Cfg> {
314        if !self.map.contains_key(target.cli_target()) {
315            let cfg = Cfg::from_rustc(rustc(), target)?;
316            self.map.insert(TargetTripleBorrow(target.clone().into_owned()), cfg);
317        }
318        Ok(&self.map[target.cli_target()])
319    }
320    pub(crate) fn eval_cfg(
321        &mut self,
322        expr: &Expression,
323        target: &TargetTripleRef<'_>,
324        rustc: &dyn Fn() -> ProcessBuilder,
325    ) -> Result<bool> {
326        let cfg = self.get_or_init(target, rustc)?;
327        Ok(expr.eval(|pred| match pred {
328            Predicate::Flag(flag) => {
329                match *flag {
330                    // https://github.com/rust-lang/cargo/pull/7660
331                    "test" | "debug_assertions" | "proc_macro" => false,
332                    flag => cfg.flags.contains(flag),
333                }
334            }
335            Predicate::KeyValue { key, val } => {
336                match *key {
337                    // https://github.com/rust-lang/cargo/pull/7660
338                    "feature" => false,
339                    key => cfg.key_values.get(key).is_some_and(|values| values.contains(*val)),
340                }
341            }
342        }))
343    }
344}
345
346#[derive(Debug, Clone)]
347pub(crate) struct Cfg {
348    flags: HashSet<Box<str>>,
349    pub(crate) key_values: HashMap<Box<str>, BTreeSet<Box<str>>>,
350}
351
352impl Cfg {
353    pub(crate) fn get<C: cfg::Cfg>(&self) -> Result<C::Output> {
354        let Some(values) = self.key_values.get(C::KEY) else {
355            return C::default_output().with_context(|| {
356                format!(
357                    "{} cfg should be always available in cfg list from rustc --print cfg",
358                    C::KEY
359                )
360            });
361        };
362        if values.len() > C::MAX {
363            bail!("too many {} cfg", C::KEY)
364        }
365        C::from_values(values.iter())
366            .with_context(|| format!("failed to parse value of {} cfg", C::KEY))
367    }
368
369    fn from_rustc(mut rustc: ProcessBuilder, target: &TargetTripleRef<'_>) -> Result<Self> {
370        let target = &*target.cli_target_string();
371        if is_spec_path(target) {
372            rustc.args(["-Z", "unstable-options"]);
373        }
374        // TODO: pass rustflags?
375        let list = rustc.args(["--print", "cfg", "--target", target]).read()?;
376        Ok(Self::parse(&list))
377    }
378
379    fn parse(list: &str) -> Self {
380        let mut flags = HashSet::default();
381        let mut key_values = HashMap::<Box<str>, BTreeSet<Box<str>>>::default();
382
383        for line in list.lines() {
384            let line = line.trim();
385            if line.is_empty() {
386                continue;
387            }
388            match line.split_once('=') {
389                None => {
390                    flags.insert(line.into());
391                }
392                Some((name, value)) => {
393                    if value.len() < 2 || !value.starts_with('"') || !value.ends_with('"') {
394                        if cfg!(test) {
395                            panic!("invalid value '{value}'");
396                        }
397                        continue;
398                    }
399                    let value = &value[1..value.len() - 1];
400                    if let Some(values) = key_values.get_mut(name) {
401                        values.insert(value.into());
402                    } else {
403                        let mut values = BTreeSet::default();
404                        values.insert(value.into());
405                        key_values.insert(name.into(), values);
406                    }
407                }
408            }
409        }
410
411        Self { flags, key_values }
412    }
413}
414
415#[derive(Debug, Clone)]
416pub struct TargetTripleRef<'a> {
417    triple: Cow<'a, str>,
418    spec_path: Option<Cow<'a, Path>>,
419}
420
421pub type TargetTriple = TargetTripleRef<'static>;
422
423impl PartialEq for TargetTripleRef<'_> {
424    fn eq(&self, other: &Self) -> bool {
425        self.cli_target() == other.cli_target()
426    }
427}
428impl Eq for TargetTripleRef<'_> {}
429impl PartialOrd for TargetTripleRef<'_> {
430    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
431        Some(self.cmp(other))
432    }
433}
434impl Ord for TargetTripleRef<'_> {
435    fn cmp(&self, other: &Self) -> cmp::Ordering {
436        self.cli_target().cmp(other.cli_target())
437    }
438}
439impl Hash for TargetTripleRef<'_> {
440    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
441        self.cli_target().hash(state);
442    }
443}
444
445// This wrapper is needed to support pre-1.63 Rust.
446// In pre-1.63 Rust we cannot use TargetTripleRef<'non_static> as an index of
447// HashMap<TargetTripleRef<'static>, _> without this trick.
448#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
449#[serde(transparent)]
450pub(crate) struct TargetTripleBorrow<'a>(pub(crate) TargetTripleRef<'a>);
451impl core::borrow::Borrow<OsStr> for TargetTripleBorrow<'_> {
452    fn borrow(&self) -> &OsStr {
453        self.0.cli_target()
454    }
455}
456
457fn is_spec_path(triple_or_spec_path: &str) -> bool {
458    Path::new(triple_or_spec_path).extension().is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
459        || triple_or_spec_path.contains(['/', '\\'])
460}
461fn resolve_spec_path(
462    spec_path: &str,
463    def: Option<&Definition>,
464    current_dir: Option<&Path>,
465) -> Option<PathBuf> {
466    if let Some(def) = def {
467        if let Some(root) = def.root_opt(current_dir) {
468            return Some(root.join(spec_path));
469        }
470    }
471    None
472}
473
474impl<'a> TargetTripleRef<'a> {
475    pub(crate) fn new(
476        triple_or_spec_path: Cow<'a, str>,
477        def: Option<&Definition>,
478        current_dir: Option<&Path>,
479    ) -> Self {
480        // Handles custom target
481        if is_spec_path(&triple_or_spec_path) {
482            let triple = match &triple_or_spec_path {
483                // `triple_or_spec_path` is valid UTF-8, so unwrap here will never panic.
484                &Cow::Borrowed(v) => Path::new(v).file_stem().unwrap().to_str().unwrap().into(),
485                Cow::Owned(v) => {
486                    Path::new(v).file_stem().unwrap().to_str().unwrap().to_owned().into()
487                }
488            };
489            Self {
490                triple,
491                spec_path: Some(match resolve_spec_path(&triple_or_spec_path, def, current_dir) {
492                    Some(v) => v.into(),
493                    None => match triple_or_spec_path {
494                        Cow::Borrowed(v) => Path::new(v).into(),
495                        Cow::Owned(v) => PathBuf::from(v).into(),
496                    },
497                }),
498            }
499        } else {
500            Self { triple: triple_or_spec_path, spec_path: None }
501        }
502    }
503
504    pub fn into_owned(self) -> TargetTriple {
505        TargetTripleRef {
506            triple: self.triple.into_owned().into(),
507            spec_path: self.spec_path.map(|v| v.into_owned().into()),
508        }
509    }
510
511    pub fn triple(&self) -> &str {
512        &self.triple
513    }
514    pub fn spec_path(&self) -> Option<&Path> {
515        self.spec_path.as_deref()
516    }
517    pub(crate) fn cli_target_string(&self) -> Cow<'_, str> {
518        // Cargo converts spec path containing non-UTF8 byte to string with
519        // to_string_lossy before passing it to rustc.
520        // This is not good behavior but we just follow the behavior of cargo for now.
521        //
522        // ```
523        // $ pwd
524        // /tmp/��/a
525        // $ cat .cargo/config.toml
526        // [build]
527        // target = "avr-unknown-gnu-atmega2560.json"
528        // ```
529        // $ cargo build
530        // error: target path "/tmp/��/a/avr-unknown-gnu-atmega2560.json" is not a valid file
531        //
532        // Caused by:
533        //   No such file or directory (os error 2)
534        // ```
535        self.cli_target().to_string_lossy()
536    }
537    pub(crate) fn cli_target(&self) -> &OsStr {
538        match self.spec_path() {
539            Some(v) => v.as_os_str(),
540            None => OsStr::new(self.triple()),
541        }
542    }
543}
544
545impl<'a> From<&'a TargetTripleRef<'_>> for TargetTripleRef<'a> {
546    fn from(value: &'a TargetTripleRef<'_>) -> Self {
547        TargetTripleRef {
548            triple: value.triple().into(),
549            spec_path: value.spec_path().map(Into::into),
550        }
551    }
552}
553impl From<String> for TargetTripleRef<'static> {
554    fn from(value: String) -> Self {
555        Self::new(value.into(), None, None)
556    }
557}
558impl<'a> From<&'a String> for TargetTripleRef<'a> {
559    fn from(value: &'a String) -> Self {
560        Self::new(value.into(), None, None)
561    }
562}
563impl From<Box<str>> for TargetTripleRef<'static> {
564    fn from(value: Box<str>) -> Self {
565        Self::new(value.into_string().into(), None, None)
566    }
567}
568impl<'a> From<&'a Box<str>> for TargetTripleRef<'a> {
569    fn from(value: &'a Box<str>) -> Self {
570        let value: &str = value;
571        Self::new(value.into(), None, None)
572    }
573}
574impl<'a> From<&'a str> for TargetTripleRef<'a> {
575    fn from(value: &'a str) -> Self {
576        Self::new(value.into(), None, None)
577    }
578}
579
580impl Serialize for TargetTripleRef<'_> {
581    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
582    where
583        S: Serializer,
584    {
585        self.cli_target_string().serialize(serializer)
586    }
587}
588impl<'de> Deserialize<'de> for TargetTripleRef<'static> {
589    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
590    where
591        D: Deserializer<'de>,
592    {
593        Ok(Self::new(String::deserialize(deserializer)?.into(), None, None))
594    }
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
598#[non_exhaustive]
599pub struct RustcVersion {
600    pub major: u32,
601    pub minor: u32,
602    pub patch: Option<u32>,
603    pub nightly: bool,
604}
605#[derive(Debug, Clone, Copy, PartialEq, Eq)]
606#[non_exhaustive]
607pub struct CargoVersion {
608    pub major: u32,
609    pub minor: u32,
610    pub patch: u32,
611    pub nightly: bool,
612}
613
614impl RustcVersion {
615    /// Returns the pair of the major and minor versions.
616    ///
617    /// This is useful for comparing versions: `version.major_minor() < (1, 70)`
618    pub fn major_minor(&self) -> (u32, u32) {
619        (self.major, self.minor)
620    }
621}
622impl CargoVersion {
623    /// Returns the pair of the major and minor versions.
624    ///
625    /// This is useful for comparing versions: `version.major_minor() < (1, 70)`
626    pub fn major_minor(&self) -> (u32, u32) {
627        (self.major, self.minor)
628    }
629}
630
631fn verbose_version(mut rustc_or_cargo: ProcessBuilder) -> Result<(String, ProcessBuilder)> {
632    // Use verbose version output because the packagers add extra strings to the normal version output.
633    // Do not use long flags (--version --verbose) because clippy-deriver doesn't handle them properly.
634    // -vV is also matched with that cargo internally uses: https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/rustc.rs#L65
635    rustc_or_cargo.arg("-vV");
636    let verbose_version = rustc_or_cargo.read()?;
637    Ok((verbose_version, rustc_or_cargo))
638}
639
640fn parse_version(verbose_version: &str) -> Option<(u32, u32, Option<u32>, bool)> {
641    let release = verbose_version.lines().find_map(|line| line.strip_prefix("release: "))?;
642    let (version, channel) = release.split_once('-').unwrap_or((release, ""));
643    let mut digits = version.splitn(3, '.');
644    let major = digits.next()?.parse::<u32>().ok()?;
645    let minor = digits.next()?.parse::<u32>().ok()?;
646    let patch = match digits.next() {
647        Some(p) => Some(p.parse::<u32>().ok()?),
648        None => None,
649    };
650    let nightly = channel == "nightly" || channel == "dev";
651    Some((major, minor, patch, nightly))
652}
653
654fn rustc_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<RustcVersion> {
655    let (major, minor, patch, nightly) = parse_version(verbose_version)
656        .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?;
657    let nightly = match std::env::var_os("RUSTC_BOOTSTRAP") {
658        // When -1 is passed rustc works like stable, e.g., cfg(target_feature = "unstable_target_feature") will never be set. https://github.com/rust-lang/rust/pull/132993
659        Some(v) if v == "-1" => false,
660        _ => nightly,
661    };
662    Ok(RustcVersion { major, minor, patch, nightly })
663}
664fn cargo_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<CargoVersion> {
665    let (major, minor, patch, nightly) = parse_version(verbose_version)
666        .and_then(|(major, minor, patch, nightly)| Some((major, minor, patch?, nightly)))
667        .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?;
668    Ok(CargoVersion { major, minor, patch, nightly })
669}
670
671/// Gets host triple of the given `rustc` or `cargo`.
672fn host_triple((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<Box<str>> {
673    let host = verbose_version
674        .lines()
675        .find_map(|line| line.strip_prefix("host: "))
676        .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?
677        .into();
678    Ok(host)
679}
680
681fn rustc_path(cargo: &OsStr) -> PathBuf {
682    // When toolchain override shorthand (`+toolchain`) is used, `rustc` in
683    // PATH and `CARGO` environment variable may be different toolchains.
684    // When Rust was installed using rustup, the same toolchain's rustc
685    // binary is in the same directory as the cargo binary, so we use it.
686    let mut rustc = PathBuf::from(cargo);
687    rustc.pop(); // cargo
688    rustc.push(format!("rustc{}", std::env::consts::EXE_SUFFIX));
689    if rustc.exists() { rustc } else { "rustc".into() }
690}
691
692#[allow(clippy::std_instead_of_alloc, clippy::std_instead_of_core)]
693#[cfg(test)]
694mod tests {
695    use std::{
696        eprintln,
697        fmt::Write as _,
698        io::{self, Write as _},
699        vec,
700        vec::Vec,
701    };
702
703    use fs_err as fs;
704
705    use super::*;
706    use crate::cfg;
707
708    fn fixtures_dir() -> &'static Path {
709        Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures"))
710    }
711
712    #[test]
713    #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output)
714    fn version_and_host() {
715        let rustc_vv = &verbose_version(cmd!("rustc")).unwrap();
716        let cargo_vv = &verbose_version(cmd!("cargo")).unwrap();
717        let rustc_version = rustc_version(rustc_vv).unwrap();
718        let cargo_version = cargo_version(cargo_vv).unwrap();
719        {
720            let mut out = String::new();
721            let _ = writeln!(out, "rustc version: {rustc_version:?}");
722            let _ = writeln!(out, "rustc host: {:?}", host_triple(rustc_vv).unwrap());
723            let _ = writeln!(out, "cargo version: {cargo_version:?}");
724            let _ = writeln!(out, "cargo host: {:?}", host_triple(cargo_vv).unwrap());
725            let mut stderr = io::stderr().lock(); // Not buffered because it is written at once.
726            let _ = stderr.write_all(out.as_bytes());
727            let _ = stderr.flush();
728        }
729
730        assert_eq!(rustc_version.major_minor(), (rustc_version.major, rustc_version.minor));
731        assert!(rustc_version.major_minor() < (2, 0));
732        assert!(rustc_version.major_minor() < (1, u32::MAX));
733        assert!(rustc_version.major_minor() >= (1, 70));
734        assert!(rustc_version.major_minor() > (1, 0));
735        assert!(rustc_version.major_minor() > (0, u32::MAX));
736
737        assert_eq!(cargo_version.major_minor(), (cargo_version.major, cargo_version.minor));
738        assert!(cargo_version.major_minor() < (2, 0));
739        assert!(cargo_version.major_minor() < (1, u32::MAX));
740        assert!(cargo_version.major_minor() >= (1, 70));
741        assert!(cargo_version.major_minor() > (1, 0));
742        assert!(cargo_version.major_minor() > (0, u32::MAX));
743    }
744
745    #[test]
746    fn target_triple() {
747        let t = TargetTripleRef::from("x86_64-unknown-linux-gnu");
748        assert_eq!(t.triple, "x86_64-unknown-linux-gnu");
749        assert!(matches!(t.triple, Cow::Borrowed(..)));
750        assert!(t.spec_path.is_none());
751    }
752
753    fn check_cfg(cfg: &Cfg) {
754        let target_abi: Option<cfg::TargetAbi> = cfg.get::<cfg::TargetAbi>().unwrap();
755        if let Some(target_abi) = target_abi {
756            assert_eq!(target_abi, target_abi.as_str());
757            assert_eq!(target_abi.as_str(), target_abi);
758            assert_eq!(target_abi, target_abi.as_str().parse::<cfg::TargetAbi>().unwrap());
759            assert_eq!(target_abi, cfg::TargetAbi::from(target_abi.as_str()));
760            #[allow(deprecated)]
761            let other = cfg::TargetAbi::__Other(target_abi.as_str().into());
762            assert_eq!(target_abi, other);
763            assert_eq!(other, target_abi);
764        }
765        let target_arch: cfg::TargetArch = cfg.get::<cfg::TargetArch>().unwrap();
766        assert_eq!(target_arch, target_arch.as_str());
767        assert_eq!(target_arch.as_str(), target_arch);
768        assert_eq!(target_arch, target_arch.as_str().parse::<cfg::TargetArch>().unwrap());
769        assert_eq!(target_arch, cfg::TargetArch::from(target_arch.as_str()));
770        #[allow(deprecated)]
771        let other = cfg::TargetArch::__Other(target_arch.as_str().into());
772        assert_eq!(target_arch, other);
773        assert_eq!(other, target_arch);
774        let target_endian: cfg::TargetEndian = cfg.get::<cfg::TargetEndian>().unwrap();
775        assert_eq!(target_endian, target_endian.as_str());
776        assert_eq!(target_endian.as_str(), target_endian);
777        assert_eq!(target_endian, target_endian.as_str().parse::<cfg::TargetEndian>().unwrap());
778        let target_env: Option<cfg::TargetEnv> = cfg.get::<cfg::TargetEnv>().unwrap();
779        if let Some(target_env) = target_env {
780            assert_eq!(target_env, target_env.as_str());
781            assert_eq!(target_env.as_str(), target_env);
782            assert_eq!(target_env, target_env.as_str().parse::<cfg::TargetEnv>().unwrap());
783            assert_eq!(target_env, cfg::TargetEnv::from(target_env.as_str()));
784            #[allow(deprecated)]
785            let other = cfg::TargetEnv::__Other(target_env.as_str().into());
786            assert_eq!(target_env, other);
787            assert_eq!(other, target_env);
788        }
789        let target_family: Vec<cfg::TargetFamily> = cfg.get::<cfg::TargetFamily>().unwrap();
790        for target_family in target_family {
791            assert_eq!(target_family, target_family.as_str());
792            assert_eq!(target_family.as_str(), target_family);
793            assert_eq!(target_family, target_family.as_str().parse::<cfg::TargetFamily>().unwrap());
794            assert_eq!(target_family, cfg::TargetFamily::from(target_family.as_str()));
795            #[allow(deprecated)]
796            let other = cfg::TargetFamily::__Other(target_family.as_str().into());
797            assert_eq!(target_family, other);
798            assert_eq!(other, target_family);
799        }
800        let target_has_atomic: Vec<cfg::TargetHasAtomic> =
801            cfg.get::<cfg::TargetHasAtomic>().unwrap();
802        for target_has_atomic in target_has_atomic {
803            assert_eq!(target_has_atomic, target_has_atomic.as_str());
804            assert_eq!(target_has_atomic.as_str(), target_has_atomic);
805            assert_eq!(
806                target_has_atomic,
807                target_has_atomic.as_str().parse::<cfg::TargetHasAtomic>().unwrap()
808            );
809            assert!(
810                target_has_atomic == 8
811                    || target_has_atomic == 16
812                    || target_has_atomic == 32
813                    || target_has_atomic == 64
814                    || target_has_atomic == 128
815                    || target_has_atomic == "ptr",
816                "{target_has_atomic:?}"
817            );
818        }
819        let target_os: cfg::TargetOs = cfg.get::<cfg::TargetOs>().unwrap();
820        assert_eq!(target_os, target_os.as_str());
821        assert_eq!(target_os.as_str(), target_os);
822        assert_eq!(target_os, target_os.as_str().parse::<cfg::TargetOs>().unwrap());
823        assert_eq!(target_os, cfg::TargetOs::from(target_os.as_str()));
824        #[allow(deprecated)]
825        let other = cfg::TargetOs::__Other(target_os.as_str().into());
826        assert_eq!(target_os, other);
827        assert_eq!(other, target_os);
828        let target_pointer_width: cfg::TargetPointerWidth =
829            cfg.get::<cfg::TargetPointerWidth>().unwrap();
830        assert_eq!(target_pointer_width, target_pointer_width.as_str());
831        assert_eq!(target_pointer_width.as_str(), target_pointer_width);
832        assert_eq!(
833            target_pointer_width,
834            target_pointer_width.as_str().parse::<cfg::TargetPointerWidth>().unwrap()
835        );
836        assert!(
837            target_pointer_width == 16 || target_pointer_width == 32 || target_pointer_width == 64,
838            "{target_pointer_width:?}"
839        );
840        let target_vendor: Option<cfg::TargetVendor> = cfg.get::<cfg::TargetVendor>().unwrap();
841        if let Some(target_vendor) = target_vendor {
842            assert_eq!(target_vendor, target_vendor.as_str());
843            assert_eq!(target_vendor.as_str(), target_vendor);
844            assert_eq!(target_vendor, target_vendor.as_str().parse::<cfg::TargetVendor>().unwrap());
845            assert_eq!(target_vendor, cfg::TargetVendor::from(target_vendor.as_str()));
846            #[allow(deprecated)]
847            let other = cfg::TargetVendor::__Other(target_vendor.as_str().into());
848            assert_eq!(target_vendor, other);
849            assert_eq!(other, target_vendor);
850        }
851    }
852
853    #[test]
854    #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output)
855    fn parse_cfg_list() {
856        // builtin targets
857        for target in cmd!("rustc", "--print", "target-list").read().unwrap().lines() {
858            let cfg = Cfg::from_rustc(cmd!("rustc"), &target.into()).unwrap();
859            check_cfg(&cfg);
860        }
861        // custom targets
862        for spec_path in
863            fs::read_dir(fixtures_dir().join("target-specs")).unwrap().map(|e| e.unwrap().path())
864        {
865            let res = Cfg::from_rustc(cmd!("rustc"), &spec_path.to_str().unwrap().into());
866            if rustversion::cfg!(nightly) {
867                let _cfg = res.unwrap();
868            } else {
869                let _e = res.unwrap_err();
870            }
871        }
872    }
873
874    #[test]
875    #[cfg_attr(miri, ignore)] // Miri is too slow
876    fn parse_cfg_list_all() {
877        let mut list = String::new();
878        for e in fs::read_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("tools/gen/cfg")).unwrap()
879        {
880            let p = e.unwrap().path();
881            eprintln!("{}:", p.display());
882            let text = fs::read_to_string(p).unwrap();
883            let mut lines = text.lines();
884            while let Some(line) = lines.next() {
885                if line.starts_with("1.") {
886                    // alias
887                    let line = lines.next();
888                    assert!(matches!(line, Some("") | None), "{line:?}");
889                    assert!(lines.next().is_none());
890                    break;
891                }
892                let _target = line.strip_suffix(":").context(line).unwrap();
893                for line in lines.by_ref() {
894                    if line.is_empty() {
895                        break;
896                    }
897                    list.push_str(line);
898                    list.push('\n');
899                }
900                let cfg = Cfg::parse(&list);
901                check_cfg(&cfg);
902                list.clear();
903            }
904        }
905    }
906
907    #[test]
908    fn env_filter() {
909        // NB: sync with bench in bench/bench.rs
910        let env_list = [
911            ("CARGO_BUILD_JOBS", "-1"),
912            ("RUSTC", "rustc"),
913            ("CARGO_BUILD_RUSTC", "rustc"),
914            ("RUSTC_WRAPPER", "rustc_wrapper"),
915            ("CARGO_BUILD_RUSTC_WRAPPER", "rustc_wrapper"),
916            ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
917            ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
918            ("RUSTDOC", "rustdoc"),
919            ("CARGO_BUILD_RUSTDOC", "rustdoc"),
920            ("CARGO_BUILD_TARGET", "triple"),
921            ("CARGO_TARGET_DIR", "target"),
922            ("CARGO_BUILD_TARGET_DIR", "target"),
923            ("CARGO_ENCODED_RUSTFLAGS", "1"),
924            ("RUSTFLAGS", "1"),
925            ("CARGO_BUILD_RUSTFLAGS", "1"),
926            ("CARGO_ENCODED_RUSTDOCFLAGS", "1"),
927            ("RUSTDOCFLAGS", "1"),
928            ("CARGO_BUILD_RUSTDOCFLAGS", "1"),
929            ("CARGO_INCREMENTAL", "false"),
930            ("CARGO_BUILD_INCREMENTAL", "1"),
931            ("CARGO_BUILD_DEP_INFO_BASEDIR", "1"),
932            ("BROWSER", "1"),
933            ("CARGO_FUTURE_INCOMPAT_REPORT_FREQUENCY", "always"),
934            ("CARGO_CARGO_NEW_VCS", "git"),
935            ("CARGO_HTTP_DEBUG", "true"),
936            ("CARGO_HTTP_PROXY", "-"),
937            ("CARGO_HTTP_TIMEOUT", "1"),
938            ("CARGO_HTTP_CAINFO", "-"),
939            ("CARGO_HTTP_CHECK_REVOKE", "true"),
940            ("CARGO_HTTP_LOW_SPEED_LIMIT", "1"),
941            ("CARGO_HTTP_MULTIPLEXING", "true"),
942            ("CARGO_HTTP_USER_AGENT", "-"),
943            ("CARGO_NET_RETRY", "1"),
944            ("CARGO_NET_GIT_FETCH_WITH_CLI", "false"),
945            ("CARGO_NET_OFFLINE", "false"),
946            ("CARGO_REGISTRIES_crates-io_INDEX", "https://github.com/rust-lang/crates.io-index"),
947            ("CARGO_REGISTRIES_crates-io_TOKEN", "00000000000000000000000000000000000"),
948            ("CARGO_REGISTRY_DEFAULT", "crates-io"),
949            ("CARGO_REGISTRY_TOKEN", "00000000000000000000000000000000000"),
950            ("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "git"),
951            ("CARGO_TERM_QUIET", "false"),
952            ("CARGO_TERM_VERBOSE", "false"),
953            ("CARGO_TERM_COLOR", "auto"),
954            ("CARGO_TERM_PROGRESS_WHEN", "auto"),
955            ("CARGO_TERM_PROGRESS_WIDTH", "100"),
956        ];
957        let mut config = crate::de::Config::default();
958        let cx =
959            &ResolveOptions::default().env(env_list).into_context(std::env::current_dir().unwrap());
960        config.apply_env(cx).unwrap();
961
962        // ResolveOptions::env attempts to avoid pushing unrelated envs.
963        let mut env_list = env_list.to_vec();
964        env_list.push(("A", "B"));
965        let cx = &ResolveOptions::default()
966            .env(env_list.iter().copied())
967            .into_context(std::env::current_dir().unwrap());
968        for (k, v) in env_list {
969            if k == "A" {
970                assert!(!cx.env.contains_key(k));
971            } else {
972                assert_eq!(cx.env[k], v, "key={k},value={v}");
973            }
974        }
975    }
976
977    #[test]
978    fn rustc_wrapper() {
979        for (env_list, expected) in [
980            (
981                &[
982                    ("RUSTC", "rustc"),
983                    ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
984                    ("RUSTC_WRAPPER", "rustc_wrapper"),
985                    ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
986                    ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
987                    ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
988                ][..],
989                PathAndArgs {
990                    path: "rustc_wrapper".into(),
991                    args: vec!["rustc_workspace_wrapper".into(), "rustc".into()],
992                },
993            ),
994            (
995                &[
996                    ("RUSTC", "rustc"),
997                    ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
998                    ("RUSTC_WRAPPER", ""),
999                    ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
1000                    ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
1001                    ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
1002                ][..],
1003                PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
1004            ),
1005            (
1006                &[
1007                    ("RUSTC", "rustc"),
1008                    ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
1009                    ("RUSTC_WRAPPER", "rustc_wrapper"),
1010                    ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
1011                    ("RUSTC_WORKSPACE_WRAPPER", ""),
1012                    ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
1013                ][..],
1014                PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] },
1015            ),
1016            (
1017                &[
1018                    ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
1019                    ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
1020                    ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
1021                ],
1022                PathAndArgs {
1023                    path: "cargo_build_rustc_wrapper".into(),
1024                    args: vec![
1025                        "cargo_build_rustc_workspace_wrapper".into(),
1026                        "cargo_build_rustc".into(),
1027                    ],
1028                },
1029            ),
1030            (
1031                &[
1032                    ("RUSTC", "rustc"),
1033                    ("RUSTC_WRAPPER", "rustc_wrapper"),
1034                    ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
1035                ],
1036                PathAndArgs {
1037                    path: "rustc_wrapper".into(),
1038                    args: vec!["rustc_workspace_wrapper".into(), "rustc".into()],
1039                },
1040            ),
1041            (
1042                &[
1043                    ("RUSTC", "rustc"),
1044                    ("RUSTC_WRAPPER", "rustc_wrapper"),
1045                    ("RUSTC_WORKSPACE_WRAPPER", ""),
1046                ],
1047                PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] },
1048            ),
1049            (
1050                &[
1051                    ("RUSTC", "rustc"),
1052                    ("RUSTC_WRAPPER", ""),
1053                    ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
1054                ],
1055                PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
1056            ),
1057            (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper")], PathAndArgs {
1058                path: "rustc_wrapper".into(),
1059                args: vec!["rustc".into()],
1060            }),
1061            (
1062                &[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper")],
1063                PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
1064            ),
1065            (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "")], PathAndArgs {
1066                path: "rustc".into(),
1067                args: vec![],
1068            }),
1069            (&[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "")], PathAndArgs {
1070                path: "rustc".into(),
1071                args: vec![],
1072            }),
1073        ] {
1074            let mut config = crate::de::Config::default();
1075            let cx = &ResolveOptions::default()
1076                .env(env_list.iter().copied())
1077                .into_context(std::env::current_dir().unwrap());
1078            config.apply_env(cx).unwrap();
1079            let build = crate::easy::BuildConfig::from_unresolved(config.build, &cx.current_dir);
1080            assert_eq!(*cx.rustc(&build), expected);
1081        }
1082    }
1083
1084    #[cfg(unix)]
1085    #[test]
1086    fn env_non_utf8() {
1087        use std::{ffi::OsStr, os::unix::prelude::OsStrExt as _, string::ToString as _};
1088
1089        let cx = &ResolveOptions::default()
1090            .env([("CARGO_ALIAS_a", OsStr::from_bytes(&[b'f', b'o', 0x80, b'o']))])
1091            .cargo_home(None)
1092            .rustc(PathAndArgs::new("rustc"))
1093            .into_context(std::env::current_dir().unwrap());
1094        assert_eq!(
1095            cx.env("CARGO_ALIAS_a").unwrap_err().to_string(),
1096            "failed to parse environment variable `CARGO_ALIAS_a`"
1097        );
1098        assert_eq!(
1099            format!("{:#}", anyhow::Error::from(cx.env("CARGO_ALIAS_a").unwrap_err())),
1100            "failed to parse environment variable `CARGO_ALIAS_a`: environment variable was not valid unicode: \"fo\\x80o\""
1101        );
1102    }
1103
1104    // #[test]
1105    // fn dump_all_env() {
1106    //     let mut config = crate::de::Config::default();
1107    //     let cx = &mut ResolveContext::no_env();
1108    //     config.apply_env(cx).unwrap();
1109    // }
1110}