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