1use core::{
4 cell::{OnceCell, RefCell},
5 cmp,
6 hash::Hash,
7 iter,
8 str::FromStr,
9};
10use std::{
11 borrow::Cow,
12 collections::{HashMap, HashSet},
13 ffi::{OsStr, OsString},
14 path::{Path, PathBuf},
15};
16
17use serde::{
18 de::{Deserialize, Deserializer},
19 ser::{Serialize, Serializer},
20};
21use serde_derive::{Deserialize, Serialize};
22
23use crate::{
24 PathAndArgs,
25 cfg_expr::expr::{Expression, Predicate},
26 easy,
27 error::{Context as _, Error, Result},
28 process::ProcessBuilder,
29 value::{Definition, Value},
30 walk,
31};
32
33#[derive(Debug, Clone, Default)]
34#[must_use]
35pub struct ResolveOptions {
36 env: Option<HashMap<String, OsString>>,
37 rustc: Option<PathAndArgs>,
38 cargo: Option<OsString>,
39 #[allow(clippy::option_option)]
40 cargo_home: Option<Option<PathBuf>>,
41 host_triple: Option<String>,
42}
43
44impl ResolveOptions {
45 pub fn rustc<P: Into<PathAndArgs>>(mut self, rustc: P) -> Self {
51 self.rustc = Some(rustc.into());
52 self
53 }
54 pub fn cargo<S: Into<OsString>>(mut self, cargo: S) -> Self {
60 self.cargo = Some(cargo.into());
61 self
62 }
63 pub fn cargo_home<P: Into<Option<PathBuf>>>(mut self, cargo_home: P) -> Self {
73 self.cargo_home = Some(cargo_home.into());
74 self
75 }
76 pub fn host_triple<S: Into<String>>(mut self, triple: S) -> Self {
82 self.host_triple = Some(triple.into());
83 self
84 }
85 pub fn env<I: IntoIterator<Item = (K, V)>, K: Into<OsString>, V: Into<OsString>>(
95 mut self,
96 vars: I,
97 ) -> Self {
98 let mut env = HashMap::default();
99 for (k, v) in vars {
100 if let Ok(k) = k.into().into_string() {
101 if k.starts_with("CARGO") || k.starts_with("RUST") || k == "BROWSER" {
102 env.insert(k, v.into());
103 }
104 }
105 }
106 self.env = Some(env);
107 self
108 }
109
110 #[doc(hidden)] pub fn into_context(mut self, current_dir: PathBuf) -> ResolveContext {
112 if self.env.is_none() {
113 self = self.env(std::env::vars_os());
114 }
115 let env = self.env.unwrap();
116 let rustc = match self.rustc {
117 Some(rustc) => OnceCell::from(rustc),
118 None => OnceCell::new(),
119 };
120 let cargo = match self.cargo {
121 Some(cargo) => cargo,
122 None => env.get("CARGO").cloned().unwrap_or_else(|| "cargo".into()),
123 };
124 let cargo_home = match self.cargo_home {
125 Some(cargo_home) => OnceCell::from(cargo_home),
126 None => OnceCell::new(),
127 };
128 let host_triple = match self.host_triple {
129 Some(host_triple) => OnceCell::from(host_triple),
130 None => OnceCell::new(),
131 };
132
133 ResolveContext {
134 env,
135 rustc,
136 cargo,
137 cargo_home,
138 host_triple,
139 rustc_version: OnceCell::new(),
140 cargo_version: OnceCell::new(),
141 cfg: RefCell::default(),
142 current_dir,
143 }
144 }
145}
146
147#[doc(hidden)] #[allow(unknown_lints, unnameable_types)] #[derive(Debug, Clone)]
150#[must_use]
151pub struct ResolveContext {
152 pub(crate) env: HashMap<String, OsString>,
153 rustc: OnceCell<easy::PathAndArgs>,
154 pub(crate) cargo: OsString,
155 cargo_home: OnceCell<Option<PathBuf>>,
156 host_triple: OnceCell<String>,
157 rustc_version: OnceCell<RustcVersion>,
158 cargo_version: OnceCell<CargoVersion>,
159 cfg: RefCell<CfgMap>,
160 pub(crate) current_dir: PathBuf,
161}
162
163impl ResolveContext {
164 pub(crate) fn rustc(&self, build_config: &easy::BuildConfig) -> &PathAndArgs {
165 self.rustc.get_or_init(|| {
166 let rustc =
169 build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from);
170 let rustc_wrapper = build_config.rustc_wrapper.clone();
171 let rustc_workspace_wrapper = build_config.rustc_workspace_wrapper.clone();
172 let mut rustc =
173 rustc_wrapper.into_iter().chain(rustc_workspace_wrapper).chain(iter::once(rustc));
174 PathAndArgs {
175 path: rustc.next().unwrap(),
176 args: rustc.map(PathBuf::into_os_string).collect(),
177 }
178 })
179 }
180 pub(crate) fn rustc_for_version(&self, build_config: &easy::BuildConfig) -> PathAndArgs {
181 let rustc =
183 build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from);
184 let rustc_wrapper = build_config.rustc_wrapper.clone();
185 let mut rustc = rustc_wrapper.into_iter().chain(iter::once(rustc));
186 PathAndArgs {
187 path: rustc.next().unwrap(),
188 args: rustc.map(PathBuf::into_os_string).collect(),
189 }
190 }
191 pub(crate) fn cargo_home(&self, cwd: &Path) -> Option<&Path> {
192 self.cargo_home.get_or_init(|| walk::cargo_home_with_cwd(cwd)).as_deref()
193 }
194 pub(crate) fn host_triple(&self, build_config: &easy::BuildConfig) -> Result<&str> {
195 if let Some(host) = self.host_triple.get() {
196 return Ok(host);
197 }
198 let cargo_host = verbose_version(cmd!(&self.cargo)).and_then(|ref vv| {
199 let r = self.cargo_version.set(cargo_version(vv)?);
200 debug_assert!(r.is_ok());
201 host_triple(vv)
202 });
203 let host = match cargo_host {
204 Ok(host) => host,
205 Err(_) => {
206 let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?;
207 let r = self.rustc_version.set(rustc_version(vv)?);
208 debug_assert!(r.is_ok());
209 host_triple(vv)?
210 }
211 };
212 Ok(self.host_triple.get_or_init(|| host))
213 }
214 pub(crate) fn rustc_version(&self, build_config: &easy::BuildConfig) -> Result<RustcVersion> {
215 if let Some(&rustc_version) = self.rustc_version.get() {
216 return Ok(rustc_version);
217 }
218 let _ = self.host_triple(build_config);
219 if let Some(&rustc_version) = self.rustc_version.get() {
220 return Ok(rustc_version);
221 }
222 let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?;
223 let rustc_version = rustc_version(vv)?;
224 Ok(*self.rustc_version.get_or_init(|| rustc_version))
225 }
226 pub(crate) fn cargo_version(&self, build_config: &easy::BuildConfig) -> Result<CargoVersion> {
227 if let Some(&cargo_version) = self.cargo_version.get() {
228 return Ok(cargo_version);
229 }
230 let _ = self.host_triple(build_config);
231 if let Some(&cargo_version) = self.cargo_version.get() {
232 return Ok(cargo_version);
233 }
234 let vv = &verbose_version(cmd!(&self.cargo))?;
235 let cargo_version = cargo_version(vv)?;
236 Ok(*self.cargo_version.get_or_init(|| cargo_version))
237 }
238
239 pub(crate) fn env(&self, name: &'static str) -> Result<Option<Value<String>>> {
242 match self.env.get(name) {
243 None => Ok(None),
244 Some(v) => Ok(Some(Value {
245 val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?,
246 definition: Some(Definition::Environment(name.into())),
247 })),
248 }
249 }
250 pub(crate) fn env_redacted(&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
255 .clone()
256 .into_string()
257 .map_err(|_var| Error::env_not_unicode_redacted(name))?,
258 definition: Some(Definition::Environment(name.into())),
259 })),
260 }
261 }
262 pub(crate) fn env_parse<T>(&self, name: &'static str) -> Result<Option<Value<T>>>
263 where
264 T: FromStr,
265 T::Err: std::error::Error + Send + Sync + 'static,
266 {
267 match self.env(name)? {
268 Some(v) => Ok(Some(
269 v.parse()
270 .with_context(|| format!("failed to parse environment variable `{name}`"))?,
271 )),
272 None => Ok(None),
273 }
274 }
275 pub(crate) fn env_dyn(&self, name: &str) -> Result<Option<Value<String>>> {
276 match self.env.get(name) {
277 None => Ok(None),
278 Some(v) => Ok(Some(Value {
279 val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?,
280 definition: Some(Definition::Environment(name.to_owned().into())),
281 })),
282 }
283 }
284
285 pub(crate) fn eval_cfg(
286 &self,
287 expr: &str,
288 target: &TargetTripleRef<'_>,
289 build_config: &easy::BuildConfig,
290 ) -> Result<bool> {
291 let expr = Expression::parse(expr).map_err(Error::new)?;
292 let mut cfg_map = self.cfg.borrow_mut();
293 cfg_map.eval_cfg(&expr, target, || self.rustc(build_config).into())
294 }
295}
296
297#[derive(Debug, Clone, Default)]
298pub(crate) struct CfgMap {
299 map: HashMap<TargetTripleBorrow<'static>, Cfg>,
300}
301
302impl CfgMap {
303 pub(crate) fn eval_cfg(
304 &mut self,
305 expr: &Expression,
306 target: &TargetTripleRef<'_>,
307 rustc: impl FnOnce() -> ProcessBuilder,
308 ) -> Result<bool> {
309 let cfg = match self.map.get(target.cli_target()) {
310 Some(cfg) => cfg,
311 None => {
312 let cfg = Cfg::from_rustc(rustc(), target)?;
313 self.map.insert(TargetTripleBorrow(target.clone().into_owned()), cfg);
314 &self.map[target.cli_target()]
315 }
316 };
317 Ok(expr.eval(|pred| match pred {
318 Predicate::Flag(flag) => {
319 match *flag {
320 "test" | "debug_assertions" | "proc_macro" => false,
322 flag => cfg.flags.contains(flag),
323 }
324 }
325 Predicate::KeyValue { key, val } => {
326 match *key {
327 "feature" => false,
329 key => cfg.key_values.get(key).is_some_and(|values| values.contains(*val)),
330 }
331 }
332 }))
333 }
334}
335
336#[derive(Debug, Clone)]
337struct Cfg {
338 flags: HashSet<String>,
339 key_values: HashMap<String, HashSet<String>>,
340}
341
342impl Cfg {
343 fn from_rustc(mut rustc: ProcessBuilder, target: &TargetTripleRef<'_>) -> Result<Self> {
344 let list =
345 rustc.args(["--print", "cfg", "--target", &*target.cli_target_string()]).read()?;
346 Ok(Self::parse(&list))
347 }
348
349 fn parse(list: &str) -> Self {
350 let mut flags = HashSet::default();
351 let mut key_values = HashMap::<String, HashSet<String>>::default();
352
353 for line in list.lines() {
354 let line = line.trim();
355 if line.is_empty() {
356 continue;
357 }
358 match line.split_once('=') {
359 None => {
360 flags.insert(line.to_owned());
361 }
362 Some((name, value)) => {
363 if value.len() < 2 || !value.starts_with('"') || !value.ends_with('"') {
364 if cfg!(test) {
365 panic!("invalid value '{value}'");
366 }
367 continue;
368 }
369 let value = &value[1..value.len() - 1];
370 if value.is_empty() {
371 continue;
372 }
373 if let Some(values) = key_values.get_mut(name) {
374 values.insert(value.to_owned());
375 } else {
376 let mut values = HashSet::default();
377 values.insert(value.to_owned());
378 key_values.insert(name.to_owned(), values);
379 }
380 }
381 }
382 }
383
384 Self { flags, key_values }
385 }
386}
387
388#[derive(Debug, Clone)]
389pub struct TargetTripleRef<'a> {
390 triple: Cow<'a, str>,
391 spec_path: Option<Cow<'a, Path>>,
392}
393
394pub type TargetTriple = TargetTripleRef<'static>;
395
396impl PartialEq for TargetTripleRef<'_> {
397 fn eq(&self, other: &Self) -> bool {
398 self.cli_target() == other.cli_target()
399 }
400}
401impl Eq for TargetTripleRef<'_> {}
402impl PartialOrd for TargetTripleRef<'_> {
403 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
404 Some(self.cmp(other))
405 }
406}
407impl Ord for TargetTripleRef<'_> {
408 fn cmp(&self, other: &Self) -> cmp::Ordering {
409 self.cli_target().cmp(other.cli_target())
410 }
411}
412impl Hash for TargetTripleRef<'_> {
413 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
414 self.cli_target().hash(state);
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
422#[serde(transparent)]
423pub(crate) struct TargetTripleBorrow<'a>(pub(crate) TargetTripleRef<'a>);
424impl core::borrow::Borrow<OsStr> for TargetTripleBorrow<'_> {
425 fn borrow(&self) -> &OsStr {
426 self.0.cli_target()
427 }
428}
429
430fn is_spec_path(triple_or_spec_path: &str) -> bool {
431 Path::new(triple_or_spec_path).extension() == Some(OsStr::new("json"))
432 || triple_or_spec_path.contains('/')
433 || triple_or_spec_path.contains('\\')
434}
435fn resolve_spec_path(
436 spec_path: &str,
437 def: Option<&Definition>,
438 current_dir: Option<&Path>,
439) -> Option<PathBuf> {
440 if let Some(def) = def {
441 if let Some(root) = def.root_opt(current_dir) {
442 return Some(root.join(spec_path));
443 }
444 }
445 None
446}
447
448impl<'a> TargetTripleRef<'a> {
449 pub(crate) fn new(
450 triple_or_spec_path: Cow<'a, str>,
451 def: Option<&Definition>,
452 current_dir: Option<&Path>,
453 ) -> Self {
454 if is_spec_path(&triple_or_spec_path) {
456 let triple = match &triple_or_spec_path {
457 &Cow::Borrowed(v) => Path::new(v).file_stem().unwrap().to_str().unwrap().into(),
459 Cow::Owned(v) => {
460 Path::new(v).file_stem().unwrap().to_str().unwrap().to_owned().into()
461 }
462 };
463 Self {
464 triple,
465 spec_path: Some(match resolve_spec_path(&triple_or_spec_path, def, current_dir) {
466 Some(v) => v.into(),
467 None => match triple_or_spec_path {
468 Cow::Borrowed(v) => Path::new(v).into(),
469 Cow::Owned(v) => PathBuf::from(v).into(),
470 },
471 }),
472 }
473 } else {
474 Self { triple: triple_or_spec_path, spec_path: None }
475 }
476 }
477
478 pub fn into_owned(self) -> TargetTriple {
479 TargetTripleRef {
480 triple: self.triple.into_owned().into(),
481 spec_path: self.spec_path.map(|v| v.into_owned().into()),
482 }
483 }
484
485 pub fn triple(&self) -> &str {
486 &self.triple
487 }
488 pub fn spec_path(&self) -> Option<&Path> {
489 self.spec_path.as_deref()
490 }
491 pub(crate) fn cli_target_string(&self) -> Cow<'_, str> {
492 self.cli_target().to_string_lossy()
510 }
511 pub(crate) fn cli_target(&self) -> &OsStr {
512 match self.spec_path() {
513 Some(v) => v.as_os_str(),
514 None => OsStr::new(self.triple()),
515 }
516 }
517}
518
519impl<'a> From<&'a TargetTripleRef<'_>> for TargetTripleRef<'a> {
520 fn from(value: &'a TargetTripleRef<'_>) -> Self {
521 TargetTripleRef {
522 triple: value.triple().into(),
523 spec_path: value.spec_path().map(Into::into),
524 }
525 }
526}
527impl From<String> for TargetTripleRef<'static> {
528 fn from(value: String) -> Self {
529 Self::new(value.into(), None, None)
530 }
531}
532impl<'a> From<&'a String> for TargetTripleRef<'a> {
533 fn from(value: &'a String) -> Self {
534 Self::new(value.into(), None, None)
535 }
536}
537impl<'a> From<&'a str> for TargetTripleRef<'a> {
538 fn from(value: &'a str) -> Self {
539 Self::new(value.into(), None, None)
540 }
541}
542
543impl Serialize for TargetTripleRef<'_> {
544 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
545 where
546 S: Serializer,
547 {
548 self.cli_target_string().serialize(serializer)
549 }
550}
551impl<'de> Deserialize<'de> for TargetTripleRef<'static> {
552 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
553 where
554 D: Deserializer<'de>,
555 {
556 Ok(Self::new(String::deserialize(deserializer)?.into(), None, None))
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561#[non_exhaustive]
562pub struct RustcVersion {
563 pub major: u32,
564 pub minor: u32,
565 pub patch: Option<u32>,
566 pub nightly: bool,
567}
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569#[non_exhaustive]
570pub struct CargoVersion {
571 pub major: u32,
572 pub minor: u32,
573 pub patch: u32,
574 pub nightly: bool,
575}
576
577impl RustcVersion {
578 pub fn major_minor(&self) -> (u32, u32) {
582 (self.major, self.minor)
583 }
584}
585impl CargoVersion {
586 pub fn major_minor(&self) -> (u32, u32) {
590 (self.major, self.minor)
591 }
592}
593
594fn verbose_version(mut rustc_or_cargo: ProcessBuilder) -> Result<(String, ProcessBuilder)> {
595 rustc_or_cargo.arg("-vV");
599 let verbose_version = rustc_or_cargo.read()?;
600 Ok((verbose_version, rustc_or_cargo))
601}
602
603fn parse_version(verbose_version: &str) -> Option<(u32, u32, Option<u32>, bool)> {
604 let release = verbose_version.lines().find_map(|line| line.strip_prefix("release: "))?;
605 let (version, channel) = release.split_once('-').unwrap_or((release, ""));
606 let mut digits = version.splitn(3, '.');
607 let major = digits.next()?.parse::<u32>().ok()?;
608 let minor = digits.next()?.parse::<u32>().ok()?;
609 let patch = match digits.next() {
610 Some(p) => Some(p.parse::<u32>().ok()?),
611 None => None,
612 };
613 let nightly = channel == "nightly" || channel == "dev";
614 Some((major, minor, patch, nightly))
615}
616
617fn rustc_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<RustcVersion> {
618 let (major, minor, patch, nightly) = parse_version(verbose_version)
619 .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?;
620 let nightly = match std::env::var_os("RUSTC_BOOTSTRAP") {
621 Some(v) if v == "-1" => false,
623 _ => nightly,
624 };
625 Ok(RustcVersion { major, minor, patch, nightly })
626}
627fn cargo_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<CargoVersion> {
628 let (major, minor, patch, nightly) = parse_version(verbose_version)
629 .and_then(|(major, minor, patch, nightly)| Some((major, minor, patch?, nightly)))
630 .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?;
631 Ok(CargoVersion { major, minor, patch, nightly })
632}
633
634fn host_triple((verbose_version, cmd): &(String, ProcessBuilder)) -> Result<String> {
636 let host = verbose_version
637 .lines()
638 .find_map(|line| line.strip_prefix("host: "))
639 .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?
640 .to_owned();
641 Ok(host)
642}
643
644fn rustc_path(cargo: &OsStr) -> PathBuf {
645 let mut rustc = PathBuf::from(cargo);
650 rustc.pop(); rustc.push(format!("rustc{}", std::env::consts::EXE_SUFFIX));
652 if rustc.exists() { rustc } else { "rustc".into() }
653}
654
655#[allow(clippy::std_instead_of_alloc, clippy::std_instead_of_core)]
656#[cfg(test)]
657mod tests {
658 use std::{
659 fmt::Write as _,
660 io::{self, Write as _},
661 };
662
663 use fs_err as fs;
664
665 use super::*;
666
667 fn fixtures_dir() -> &'static Path {
668 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures"))
669 }
670
671 #[test]
672 #[cfg_attr(miri, ignore)] fn version_and_host() {
674 let rustc_vv = &verbose_version(cmd!("rustc")).unwrap();
675 let cargo_vv = &verbose_version(cmd!("cargo")).unwrap();
676 let rustc_version = rustc_version(rustc_vv).unwrap();
677 let cargo_version = cargo_version(cargo_vv).unwrap();
678 {
679 let mut out = String::new();
680 let _ = writeln!(out, "rustc version: {rustc_version:?}");
681 let _ = writeln!(out, "rustc host: {:?}", host_triple(rustc_vv).unwrap());
682 let _ = writeln!(out, "cargo version: {cargo_version:?}");
683 let _ = writeln!(out, "cargo host: {:?}", host_triple(cargo_vv).unwrap());
684 let mut stderr = io::stderr().lock(); let _ = stderr.write_all(out.as_bytes());
686 let _ = stderr.flush();
687 }
688
689 assert_eq!(rustc_version.major_minor(), (rustc_version.major, rustc_version.minor));
690 assert!(rustc_version.major_minor() < (2, 0));
691 assert!(rustc_version.major_minor() < (1, u32::MAX));
692 assert!(rustc_version.major_minor() >= (1, 70));
693 assert!(rustc_version.major_minor() > (1, 0));
694 assert!(rustc_version.major_minor() > (0, u32::MAX));
695
696 assert_eq!(cargo_version.major_minor(), (cargo_version.major, cargo_version.minor));
697 assert!(cargo_version.major_minor() < (2, 0));
698 assert!(cargo_version.major_minor() < (1, u32::MAX));
699 assert!(cargo_version.major_minor() >= (1, 70));
700 assert!(cargo_version.major_minor() > (1, 0));
701 assert!(cargo_version.major_minor() > (0, u32::MAX));
702 }
703
704 #[test]
705 fn target_triple() {
706 let t = TargetTripleRef::from("x86_64-unknown-linux-gnu");
707 assert_eq!(t.triple, "x86_64-unknown-linux-gnu");
708 assert!(matches!(t.triple, Cow::Borrowed(..)));
709 assert!(t.spec_path.is_none());
710 }
711
712 #[rustversion::attr(not(nightly), ignore)]
713 #[test]
714 #[cfg_attr(miri, ignore)] fn parse_cfg_list() {
716 for target in cmd!("rustc", "--print", "target-list").read().unwrap().lines() {
718 let _cfg = Cfg::from_rustc(cmd!("rustc"), &target.into()).unwrap();
719 }
720 for spec_path in
722 fs::read_dir(fixtures_dir().join("target-specs")).unwrap().map(|e| e.unwrap().path())
723 {
724 let _cfg = Cfg::from_rustc(cmd!("rustc"), &spec_path.to_str().unwrap().into()).unwrap();
725 }
726 }
727
728 #[test]
729 fn env_filter() {
730 let env_list = [
732 ("CARGO_BUILD_JOBS", "-1"),
733 ("RUSTC", "rustc"),
734 ("CARGO_BUILD_RUSTC", "rustc"),
735 ("RUSTC_WRAPPER", "rustc_wrapper"),
736 ("CARGO_BUILD_RUSTC_WRAPPER", "rustc_wrapper"),
737 ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
738 ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
739 ("RUSTDOC", "rustdoc"),
740 ("CARGO_BUILD_RUSTDOC", "rustdoc"),
741 ("CARGO_BUILD_TARGET", "triple"),
742 ("CARGO_TARGET_DIR", "target"),
743 ("CARGO_BUILD_TARGET_DIR", "target"),
744 ("CARGO_ENCODED_RUSTFLAGS", "1"),
745 ("RUSTFLAGS", "1"),
746 ("CARGO_BUILD_RUSTFLAGS", "1"),
747 ("CARGO_ENCODED_RUSTDOCFLAGS", "1"),
748 ("RUSTDOCFLAGS", "1"),
749 ("CARGO_BUILD_RUSTDOCFLAGS", "1"),
750 ("CARGO_INCREMENTAL", "false"),
751 ("CARGO_BUILD_INCREMENTAL", "1"),
752 ("CARGO_BUILD_DEP_INFO_BASEDIR", "1"),
753 ("BROWSER", "1"),
754 ("CARGO_FUTURE_INCOMPAT_REPORT_FREQUENCY", "always"),
755 ("CARGO_CARGO_NEW_VCS", "git"),
756 ("CARGO_HTTP_DEBUG", "true"),
757 ("CARGO_HTTP_PROXY", "-"),
758 ("CARGO_HTTP_TIMEOUT", "1"),
759 ("CARGO_HTTP_CAINFO", "-"),
760 ("CARGO_HTTP_CHECK_REVOKE", "true"),
761 ("CARGO_HTTP_LOW_SPEED_LIMIT", "1"),
762 ("CARGO_HTTP_MULTIPLEXING", "true"),
763 ("CARGO_HTTP_USER_AGENT", "-"),
764 ("CARGO_NET_RETRY", "1"),
765 ("CARGO_NET_GIT_FETCH_WITH_CLI", "false"),
766 ("CARGO_NET_OFFLINE", "false"),
767 ("CARGO_REGISTRIES_crates-io_INDEX", "https://github.com/rust-lang/crates.io-index"),
768 ("CARGO_REGISTRIES_crates-io_TOKEN", "00000000000000000000000000000000000"),
769 ("CARGO_REGISTRY_DEFAULT", "crates-io"),
770 ("CARGO_REGISTRY_TOKEN", "00000000000000000000000000000000000"),
771 ("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "git"),
772 ("CARGO_TERM_QUIET", "false"),
773 ("CARGO_TERM_VERBOSE", "false"),
774 ("CARGO_TERM_COLOR", "auto"),
775 ("CARGO_TERM_PROGRESS_WHEN", "auto"),
776 ("CARGO_TERM_PROGRESS_WIDTH", "100"),
777 ];
778 let mut config = crate::de::Config::default();
779 let cx =
780 &ResolveOptions::default().env(env_list).into_context(std::env::current_dir().unwrap());
781 config.apply_env(cx).unwrap();
782
783 let mut env_list = env_list.to_vec();
785 env_list.push(("A", "B"));
786 let cx = &ResolveOptions::default()
787 .env(env_list.iter().copied())
788 .into_context(std::env::current_dir().unwrap());
789 for (k, v) in env_list {
790 if k == "A" {
791 assert!(!cx.env.contains_key(k));
792 } else {
793 assert_eq!(cx.env[k], v, "key={k},value={v}");
794 }
795 }
796 }
797
798 #[test]
799 fn rustc_wrapper() {
800 for (env_list, expected) in [
801 (
802 &[
803 ("RUSTC", "rustc"),
804 ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
805 ("RUSTC_WRAPPER", "rustc_wrapper"),
806 ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
807 ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
808 ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
809 ][..],
810 PathAndArgs {
811 path: "rustc_wrapper".into(),
812 args: vec!["rustc_workspace_wrapper".into(), "rustc".into()],
813 },
814 ),
815 (
816 &[
817 ("RUSTC", "rustc"),
818 ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
819 ("RUSTC_WRAPPER", ""),
820 ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
821 ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
822 ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
823 ][..],
824 PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
825 ),
826 (
827 &[
828 ("RUSTC", "rustc"),
829 ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
830 ("RUSTC_WRAPPER", "rustc_wrapper"),
831 ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
832 ("RUSTC_WORKSPACE_WRAPPER", ""),
833 ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
834 ][..],
835 PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] },
836 ),
837 (
838 &[
839 ("CARGO_BUILD_RUSTC", "cargo_build_rustc"),
840 ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"),
841 ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"),
842 ],
843 PathAndArgs {
844 path: "cargo_build_rustc_wrapper".into(),
845 args: vec![
846 "cargo_build_rustc_workspace_wrapper".into(),
847 "cargo_build_rustc".into(),
848 ],
849 },
850 ),
851 (
852 &[
853 ("RUSTC", "rustc"),
854 ("RUSTC_WRAPPER", "rustc_wrapper"),
855 ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
856 ],
857 PathAndArgs {
858 path: "rustc_wrapper".into(),
859 args: vec!["rustc_workspace_wrapper".into(), "rustc".into()],
860 },
861 ),
862 (
863 &[
864 ("RUSTC", "rustc"),
865 ("RUSTC_WRAPPER", "rustc_wrapper"),
866 ("RUSTC_WORKSPACE_WRAPPER", ""),
867 ],
868 PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] },
869 ),
870 (
871 &[
872 ("RUSTC", "rustc"),
873 ("RUSTC_WRAPPER", ""),
874 ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"),
875 ],
876 PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
877 ),
878 (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper")], PathAndArgs {
879 path: "rustc_wrapper".into(),
880 args: vec!["rustc".into()],
881 }),
882 (
883 &[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper")],
884 PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] },
885 ),
886 (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "")], PathAndArgs {
887 path: "rustc".into(),
888 args: vec![],
889 }),
890 (&[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "")], PathAndArgs {
891 path: "rustc".into(),
892 args: vec![],
893 }),
894 ] {
895 let mut config = crate::de::Config::default();
896 let cx = &ResolveOptions::default()
897 .env(env_list.iter().copied())
898 .into_context(std::env::current_dir().unwrap());
899 config.apply_env(cx).unwrap();
900 let build = crate::easy::BuildConfig::from_unresolved(config.build, &cx.current_dir);
901 assert_eq!(*cx.rustc(&build), expected);
902 }
903 }
904
905 #[cfg(unix)]
906 #[test]
907 fn env_non_utf8() {
908 use std::{ffi::OsStr, os::unix::prelude::OsStrExt as _};
909
910 let cx = &ResolveOptions::default()
911 .env([("CARGO_ALIAS_a", OsStr::from_bytes(&[b'f', b'o', 0x80, b'o']))])
912 .cargo_home(None)
913 .rustc(PathAndArgs::new("rustc"))
914 .into_context(std::env::current_dir().unwrap());
915 assert_eq!(
916 cx.env("CARGO_ALIAS_a").unwrap_err().to_string(),
917 "failed to parse environment variable `CARGO_ALIAS_a`"
918 );
919 assert_eq!(
920 format!("{:#}", anyhow::Error::from(cx.env("CARGO_ALIAS_a").unwrap_err())),
921 "failed to parse environment variable `CARGO_ALIAS_a`: environment variable was not valid unicode: \"fo\\x80o\""
922 );
923 }
924
925 }