1use 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 pub fn rustc<P: Into<PathAndArgs>>(mut self, rustc: P) -> Self {
57 self.rustc = Some(rustc.into());
58 self
59 }
60 pub fn cargo<S: Into<OsString>>(mut self, cargo: S) -> Self {
66 self.cargo = Some(cargo.into());
67 self
68 }
69 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 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 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)] 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)] #[allow(unknown_lints, unnameable_types)] #[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 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 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 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 "test" | "debug_assertions" | "proc_macro" => false,
332 flag => cfg.flags.contains(flag),
333 }
334 }
335 Predicate::KeyValue { key, val } => {
336 match *key {
337 "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 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#[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 if is_spec_path(&triple_or_spec_path) {
482 let triple = match &triple_or_spec_path {
483 &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 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 pub fn major_minor(&self) -> (u32, u32) {
619 (self.major, self.minor)
620 }
621}
622impl CargoVersion {
623 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 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 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
671fn 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 let mut rustc = PathBuf::from(cargo);
687 rustc.pop(); 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)] 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(); 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)] fn parse_cfg_list() {
856 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 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)] 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 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 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 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 }