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::{
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 pub fn rustc<P: Into<PathAndArgs>>(mut self, rustc: P) -> Self {
60 self.rustc = Some(rustc.into());
61 self
62 }
63 pub fn cargo<S: Into<OsString>>(mut self, cargo: S) -> Self {
69 self.cargo = Some(cargo.into());
70 self
71 }
72 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 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 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)] 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)] #[allow(unnameable_types)] #[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 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 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 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 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" => 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 "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 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#[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 if is_spec_path(&triple_or_spec_path) {
497 let triple = match &triple_or_spec_path {
498 &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 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 pub fn major_minor(&self) -> (u32, u32) {
634 (self.major, self.minor)
635 }
636}
637impl CargoVersion {
638 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 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 Some(v) if v == "-1" => false,
675 _ => 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
687fn 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 let mut rustc = PathBuf::from(cargo);
703 rustc.pop(); 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)] 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(); 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)] fn parse_cfg_list() {
872 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 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)] 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 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 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 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 }