1use std::collections::BTreeSet;
5use std::ffi::OsString;
6use std::path::PathBuf;
7
8use serde::{Deserialize, Serialize};
9use target_lexicon::{BinaryFormat, Triple};
10
11use crate::platform::{PanicStrategy, Profile, StripLevel};
12
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15pub struct ParsedExternCrate {
16 pub crate_name: String,
18 pub path: PathBuf,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ParsedRustcArgs {
29 pub crate_name: String,
31 pub crate_types: Vec<String>,
33 pub features: BTreeSet<String>,
35 pub cfgs: BTreeSet<String>,
40 pub emit: BTreeSet<String>,
42 pub json: BTreeSet<String>,
44 pub input_path: Option<PathBuf>,
46 pub target: Option<String>,
48 pub c_metadata: Option<String>,
50 pub out_dir: Option<PathBuf>,
52 pub extra_filename: String,
54 pub opt_level: Option<String>,
56 pub debuginfo: Option<String>,
58 pub panic_strategy: Option<String>,
60 pub debug_assertions: Option<bool>,
62 pub overflow_checks: Option<bool>,
64 pub strip: Option<String>,
66 pub native_search_paths: Vec<PathBuf>,
68 pub extern_crates: Vec<ParsedExternCrate>,
70 pub embed_metadata: Option<bool>,
74 pub embed_bitcode: bool,
80 pub has_custom_codegen: bool,
84 pub link_options: BTreeSet<String>,
91}
92
93impl ParsedRustcArgs {
94 pub fn parse(args: &[OsString]) -> Result<Self, String> {
105 let env_effect = env_rustflags_effect();
106 let mut parsed = Self {
107 crate_name: String::new(),
108 crate_types: Vec::new(),
109 features: BTreeSet::new(),
110 cfgs: BTreeSet::new(),
111 emit: BTreeSet::new(),
112 json: BTreeSet::new(),
113 input_path: None,
114 target: None,
115 c_metadata: None,
116 out_dir: None,
117 extra_filename: String::new(),
118 opt_level: None,
119 debuginfo: None,
120 panic_strategy: None,
121 debug_assertions: None,
122 overflow_checks: None,
123 strip: None,
124 native_search_paths: Vec::new(),
125 extern_crates: Vec::new(),
126 embed_metadata: None,
127 embed_bitcode: true,
128 has_custom_codegen: env_effect.custom_codegen,
129 link_options: env_effect.link_options,
130 };
131
132 let mut iter = args.iter();
133 while let Some(arg) = iter.next() {
134 let Some(arg) = arg.to_str() else {
135 return Err(format!(
136 "rustc argument is not valid UTF-8: {}",
137 arg.display()
138 ));
139 };
140 apply_rustc_arg(arg, &mut iter, &mut parsed)?;
141 }
142
143 if parsed.crate_name.is_empty() {
144 return Err("missing --crate-name in rustc arguments".to_owned());
145 }
146
147 Ok(parsed)
148 }
149
150 #[must_use]
159 pub fn is_cacheable(&self) -> bool {
160 if !self.is_restorable_artifact() {
161 return false;
162 }
163 if self.has_custom_codegen {
164 return false;
165 }
166 std::env::var_os("CARGO_PRIMARY_PACKAGE").is_none()
167 }
168
169 #[must_use]
188 pub fn link_options_reaching_the_linker(&self) -> Vec<String> {
189 if self.invokes_the_linker() {
190 self.link_options.iter().cloned().collect()
191 } else {
192 Vec::new()
193 }
194 }
195
196 #[must_use]
204 fn invokes_the_linker(&self) -> bool {
205 self.crate_types
206 .iter()
207 .any(|kind| matches!(kind.as_str(), "proc-macro" | "dylib" | "cdylib" | "bin"))
208 }
209
210 #[must_use]
221 pub fn is_locally_cacheable(&self) -> bool {
222 self.is_restorable_artifact()
223 && !self.has_custom_codegen
224 && std::env::var_os("CARGO_PRIMARY_PACKAGE").is_none()
225 }
226
227 #[must_use]
230 pub fn is_restorable_artifact(&self) -> bool {
231 (self.produces_rlib() || self.produces_dynamic_library())
232 && self.c_metadata.is_some()
233 && self.out_dir.is_some()
234 }
235
236 #[must_use]
238 pub fn is_proc_macro(&self) -> bool {
239 self.crate_types.iter().any(|kind| kind == "proc-macro")
240 }
241
242 #[must_use]
244 pub fn produces_rlib(&self) -> bool {
245 self.crate_types
246 .iter()
247 .any(|kind| kind == "lib" || kind == "rlib")
248 }
249
250 #[must_use]
252 pub fn produces_dynamic_library(&self) -> bool {
253 self.crate_types
254 .iter()
255 .any(|kind| kind == "proc-macro" || kind == "dylib")
256 }
257
258 #[must_use]
260 pub fn is_binary(&self) -> bool {
261 self.crate_types.iter().any(|kind| kind == "bin")
262 }
263
264 #[must_use]
267 pub fn is_build_script(&self) -> bool {
268 self.is_binary() && self.crate_name == "build_script_build"
269 }
270
271 #[must_use]
274 pub fn requests_json_artifact_notifications(&self) -> bool {
275 self.json.contains("artifacts")
276 }
277
278 #[must_use]
282 pub fn output_rlib_path(&self) -> Option<PathBuf> {
283 let out_dir = self.out_dir.as_ref()?;
284 if !self.produces_rlib() {
285 return None;
286 }
287 Some(out_dir.join(format!(
288 "lib{}{}.rlib",
289 self.crate_name, self.extra_filename
290 )))
291 }
292
293 #[must_use]
296 pub fn output_rmeta_path(&self) -> Option<PathBuf> {
297 let out_dir = self.out_dir.as_ref()?;
298 Some(out_dir.join(format!(
299 "lib{}{}.rmeta",
300 self.crate_name, self.extra_filename
301 )))
302 }
303
304 pub fn output_dynamic_library_path(&self) -> Result<Option<PathBuf>, String> {
314 if !self.produces_dynamic_library() {
315 return Ok(None);
316 }
317 let out_dir = self
318 .out_dir
319 .as_ref()
320 .ok_or_else(|| "dynamic library output requires --out-dir".to_owned())?;
321 let (prefix, extension) = match self.target.as_deref() {
322 Some(target) => dynamic_library_naming(target)?,
323 None => (
324 std::env::consts::DLL_PREFIX,
325 std::env::consts::DLL_SUFFIX
326 .strip_prefix('.')
327 .unwrap_or(std::env::consts::DLL_SUFFIX),
328 ),
329 };
330
331 Ok(Some(out_dir.join(format!(
332 "{prefix}{}{}.{}",
333 self.crate_name, self.extra_filename, extension
334 ))))
335 }
336
337 #[must_use]
340 pub fn output_dep_info_path(&self) -> Option<PathBuf> {
341 let out_dir = self.out_dir.as_ref()?;
342 Some(out_dir.join(format!("{}{}.d", self.crate_name, self.extra_filename)))
343 }
344
345 #[must_use]
349 pub fn output_binary_path(&self) -> Option<PathBuf> {
350 let out_dir = self.out_dir.as_ref()?;
351 if !self.is_binary() {
352 return None;
353 }
354 Some(out_dir.join(format!(
355 "{}{}{}",
356 self.crate_name,
357 self.extra_filename,
358 std::env::consts::EXE_SUFFIX
359 )))
360 }
361
362 pub fn output_link_path(&self) -> Result<Option<PathBuf>, String> {
370 if let Some(path) = self.output_rlib_path() {
371 return Ok(Some(path));
372 }
373 if let Some(path) = self.output_binary_path() {
374 return Ok(Some(path));
375 }
376 self.output_dynamic_library_path()
377 }
378
379 #[must_use]
383 pub fn build_script_alias_path(&self) -> Option<PathBuf> {
384 let out_dir = self.out_dir.as_ref()?;
385 if !self.is_build_script() {
386 return None;
387 }
388 Some(out_dir.join(format!(
389 "build-script-build{}",
390 std::env::consts::EXE_SUFFIX
391 )))
392 }
393
394 pub fn profile(&self) -> Result<Profile, String> {
402 Ok(Profile {
403 opt_level: self.opt_level.clone().unwrap_or_else(|| "0".to_owned()),
404 debuginfo: parse_debuginfo_level(self.debuginfo.as_deref())?,
405 debug_assertions: self.debug_assertions.unwrap_or(true),
406 overflow_checks: self.overflow_checks.unwrap_or(true),
407 panic: parse_panic_strategy(self.panic_strategy.as_deref())?,
408 strip: parse_strip_level(self.strip.as_deref())?,
409 })
410 }
411}
412
413fn apply_rustc_arg<'a>(
414 arg: &str,
415 iter: &mut impl Iterator<Item = &'a OsString>,
416 parsed: &mut ParsedRustcArgs,
417) -> Result<(), String> {
418 match arg {
419 "--crate-name" => apply_crate_name(next_str(iter, "--crate-name")?, parsed),
420 "--crate-type" => apply_crate_types(next_str(iter, "--crate-type")?, parsed),
421 "--target" => apply_target(next_str(iter, "--target")?, parsed),
422 "--cfg" => apply_cfg(next_str(iter, "--cfg")?, parsed),
423 "--out-dir" => parsed.out_dir = Some(PathBuf::from(next_os(iter, "--out-dir")?)),
424 "--extern" => {
425 parse_extern_crate(next_os(iter, "--extern")?.clone(), parsed)?;
426 }
427 "--emit" => {
428 parse_emit_kinds(next_str(iter, "--emit")?, parsed);
429 }
430 "--json" => {
431 parse_json_kinds(next_str(iter, "--json")?, parsed);
432 }
433 "-C" => {
434 parse_codegen_option(next_str(iter, "-C")?, parsed)?;
435 }
436 "-L" => {
437 parse_library_search(next_str(iter, "-L")?, parsed);
438 }
439 _ => apply_attached_arg(arg, iter, parsed)?,
440 }
441 Ok(())
442}
443
444fn apply_attached_arg<'a>(
451 arg: &str,
452 iter: &mut impl Iterator<Item = &'a OsString>,
453 parsed: &mut ParsedRustcArgs,
454) -> Result<(), String> {
455 if let Some(value) = arg.strip_prefix("--crate-name=") {
456 apply_crate_name(value, parsed);
457 return Ok(());
458 }
459 if let Some(value) = arg.strip_prefix("--crate-type=") {
460 apply_crate_types(value, parsed);
461 return Ok(());
462 }
463 if let Some(value) = arg.strip_prefix("--target=") {
464 apply_target(value, parsed);
465 return Ok(());
466 }
467 if let Some(value) = arg.strip_prefix("--cfg=") {
468 apply_cfg(value, parsed);
469 return Ok(());
470 }
471 if let Some(value) = arg.strip_prefix("--out-dir=") {
472 parsed.out_dir = Some(PathBuf::from(value));
473 return Ok(());
474 }
475 if let Some(value) = arg.strip_prefix("--extern=") {
476 return parse_extern_crate(OsString::from(value), parsed);
477 }
478 if let Some(value) = arg.strip_prefix("--emit=") {
479 parse_emit_kinds(value, parsed);
480 return Ok(());
481 }
482 if let Some(value) = arg.strip_prefix("--json=") {
483 parse_json_kinds(value, parsed);
484 return Ok(());
485 }
486 if let Some(option) = arg.strip_prefix("-C") {
487 return parse_codegen_option(option, parsed);
488 }
489 if let Some(option) = arg.strip_prefix("-L") {
490 parse_library_search(option, parsed);
491 return Ok(());
492 }
493 if arg == "-Z" {
494 return parse_unstable_option(next_str(iter, "-Z")?, parsed);
495 }
496 if let Some(option) = arg.strip_prefix("-Z") {
497 return parse_unstable_option(option, parsed);
498 }
499 if !arg.starts_with('-')
500 && parsed.input_path.is_none()
501 && std::path::Path::new(arg)
502 .extension()
503 .and_then(|extension| extension.to_str())
504 == Some("rs")
505 {
506 parsed.input_path = Some(PathBuf::from(arg));
507 }
508 Ok(())
509}
510
511fn apply_crate_name(value: &str, parsed: &mut ParsedRustcArgs) {
512 value.clone_into(&mut parsed.crate_name);
513}
514
515fn apply_crate_types(value: &str, parsed: &mut ParsedRustcArgs) {
521 for crate_type in value.split(',') {
522 if !parsed.crate_types.iter().any(|seen| seen == crate_type) {
523 parsed.crate_types.push(crate_type.to_owned());
524 }
525 }
526}
527
528fn apply_target(value: &str, parsed: &mut ParsedRustcArgs) {
529 parsed.target = Some(value.to_owned());
530}
531
532fn apply_cfg(value: &str, parsed: &mut ParsedRustcArgs) {
533 match parse_feature_cfg(value) {
534 Some(feature) => {
535 parsed.features.insert(feature);
536 }
537 None => {
538 parsed.cfgs.insert(value.to_owned());
539 }
540 }
541}
542
543fn dynamic_library_naming(target: &str) -> Result<(&'static str, &'static str), String> {
544 let triple: Triple = target
545 .parse()
546 .map_err(|error| format!("parse target triple `{target}`: {error}"))?;
547 match triple.binary_format {
548 BinaryFormat::Elf => Ok(("lib", "so")),
549 BinaryFormat::Macho => Ok(("lib", "dylib")),
550 BinaryFormat::Coff => Ok(("", "dll")),
551 other => Err(format!(
552 "unsupported binary format `{}` for dynamic library output",
553 other.into_str()
554 )),
555 }
556}
557
558fn parse_codegen_option(option: &str, parsed: &mut ParsedRustcArgs) -> Result<(), String> {
559 let Some((key, value)) = option.split_once('=') else {
560 match option {
561 "prefer-dynamic" => {}
562 _ => parsed.has_custom_codegen = true,
563 }
564 return Ok(());
565 };
566
567 match key {
568 "metadata" => parsed.c_metadata = Some(value.to_owned()),
569 "extra-filename" => value.clone_into(&mut parsed.extra_filename),
570 "opt-level" => parsed.opt_level = Some(value.to_owned()),
571 "debuginfo" => parsed.debuginfo = Some(value.to_owned()),
572 "panic" => parsed.panic_strategy = Some(value.to_owned()),
573 "debug-assertions" => parsed.debug_assertions = Some(parse_bool(value)?),
574 "overflow-checks" => parsed.overflow_checks = Some(parse_bool(value)?),
575 "strip" => parsed.strip = Some(value.to_owned()),
576 "embed-bitcode" => parsed.embed_bitcode = parse_bool(value)?,
577 "codegen-units" | "split-debuginfo" => {}
578 _ if is_link_only_codegen_option(option) => {
579 parsed.link_options.insert(option.to_owned());
580 }
581 _ => parsed.has_custom_codegen = true,
582 }
583
584 Ok(())
585}
586
587fn is_link_only_codegen_option(option: &str) -> bool {
600 let key = option.split_once('=').map_or(option, |(key, _)| key);
601 matches!(
602 key,
603 "linker" | "linker-flavor" | "link-arg" | "link-args" | "link-self-contained"
604 )
605}
606
607fn parse_unstable_option(option: &str, parsed: &mut ParsedRustcArgs) -> Result<(), String> {
611 match option.split_once('=') {
612 Some(("embed-metadata", value)) => {
613 parsed.embed_metadata = Some(parse_bool(value)?);
614 }
615 _ => parsed.has_custom_codegen = true,
616 }
617 Ok(())
618}
619
620fn parse_library_search(option: &str, parsed: &mut ParsedRustcArgs) {
621 if let Some(path) = option.strip_prefix("native=") {
622 parsed.native_search_paths.push(PathBuf::from(path));
623 }
624}
625
626fn parse_extern_crate(arg: OsString, parsed: &mut ParsedRustcArgs) -> Result<(), String> {
627 let arg = arg.into_string().map_err(|value| {
628 format!(
629 "rustc --extern argument is not valid UTF-8: {}",
630 value.display()
631 )
632 })?;
633 let Some((crate_name, path)) = arg.split_once('=') else {
634 return Ok(());
635 };
636 if crate_name.is_empty() || path.is_empty() {
637 return Err(format!("invalid rustc --extern argument: {arg}"));
638 }
639 parsed.extern_crates.push(ParsedExternCrate {
640 crate_name: crate_name.to_owned(),
641 path: PathBuf::from(path),
642 });
643 parsed.extern_crates.sort_by(|left, right| {
644 left.crate_name
645 .cmp(&right.crate_name)
646 .then(left.path.cmp(&right.path))
647 });
648 Ok(())
649}
650
651fn parse_emit_kinds(value: &str, parsed: &mut ParsedRustcArgs) {
652 parsed.emit.extend(
653 value
654 .split(',')
655 .filter(|emit| !emit.is_empty())
656 .map(str::to_owned),
657 );
658}
659
660fn parse_json_kinds(value: &str, parsed: &mut ParsedRustcArgs) {
661 parsed.json.extend(
662 value
663 .split(',')
664 .filter(|json| !json.is_empty())
665 .map(str::to_owned),
666 );
667}
668
669fn parse_bool(value: &str) -> Result<bool, String> {
670 match value {
671 "yes" | "true" | "on" => Ok(true),
672 "no" | "false" | "off" => Ok(false),
673 _ => Err(format!("invalid boolean rustc codegen value: {value}")),
674 }
675}
676
677fn parse_feature_cfg(cfg: &str) -> Option<String> {
678 let feature = cfg.strip_prefix("feature=\"")?;
679 let feature = feature.strip_suffix('"')?;
680 Some(feature.to_owned())
681}
682
683fn parse_debuginfo_level(value: Option<&str>) -> Result<u32, String> {
684 match value {
685 None | Some("none") => Ok(0),
686 Some("line-directives-only" | "line-tables-only" | "limited") => Ok(1),
687 Some("full") => Ok(2),
688 Some(raw) => raw
689 .parse::<u32>()
690 .map_err(|error| format!("invalid debuginfo rustc codegen value `{raw}`: {error}")),
691 }
692}
693
694fn parse_strip_level(value: Option<&str>) -> Result<StripLevel, String> {
695 match value.unwrap_or("none") {
696 "none" => Ok(StripLevel::None),
697 "debuginfo" => Ok(StripLevel::Debuginfo),
698 "symbols" => Ok(StripLevel::Symbols),
699 other => Err(format!("invalid strip rustc codegen value `{other}`")),
700 }
701}
702
703fn parse_panic_strategy(value: Option<&str>) -> Result<PanicStrategy, String> {
704 match value.unwrap_or("unwind") {
705 "unwind" => Ok(PanicStrategy::Unwind),
706 "abort" => Ok(PanicStrategy::Abort),
707 raw => Err(format!("invalid panic rustc codegen value: {raw}")),
708 }
709}
710
711fn next_os<'a>(
712 iter: &mut impl Iterator<Item = &'a OsString>,
713 flag: &str,
714) -> Result<&'a OsString, String> {
715 iter.next()
716 .ok_or_else(|| format!("missing value after {flag}"))
717}
718
719fn next_str<'a>(
720 iter: &mut impl Iterator<Item = &'a OsString>,
721 flag: &str,
722) -> Result<&'a str, String> {
723 next_os(iter, flag)?
724 .to_str()
725 .ok_or_else(|| format!("value after {flag} is not valid UTF-8"))
726}
727
728#[derive(Debug, Default, Clone)]
730struct EnvRustflagsEffect {
731 custom_codegen: bool,
733 link_options: BTreeSet<String>,
736}
737
738fn env_rustflags_effect() -> EnvRustflagsEffect {
744 if std::env::var_os("CARGO_ENCODED_RUSTFLAGS").is_none()
745 && std::env::var_os("RUSTFLAGS").is_none()
746 {
747 return EnvRustflagsEffect::default();
748 }
749
750 let mut flags = encoded_rustflags();
751 match split_rustflags_env() {
752 Ok(extra) => flags.extend(extra),
753 Err(error) => {
754 tracing::warn!(
755 %error,
756 "RUSTFLAGS could not be parsed — treating as custom codegen (cache disabled)"
757 );
758 return EnvRustflagsEffect {
759 custom_codegen: true,
760 link_options: BTreeSet::new(),
761 };
762 }
763 }
764
765 let custom = EnvRustflagsEffect {
766 custom_codegen: true,
767 link_options: BTreeSet::new(),
768 };
769 let mut effect = EnvRustflagsEffect::default();
770 let mut iter = flags.into_iter();
771 while let Some(flag) = iter.next() {
772 match flag.as_str() {
773 "--remap-path-prefix" => {
774 if iter.next().is_none() {
775 return custom;
776 }
777 }
778 _ if flag.starts_with("--remap-path-prefix=") => {}
779 "-C" | "--codegen" => match iter.next() {
780 Some(option) if is_link_only_codegen_option(&option) => {
781 effect.link_options.insert(option);
782 }
783 _ => return custom,
784 },
785 _ => {
786 let Some(option) = flag
787 .strip_prefix("-C")
788 .or_else(|| flag.strip_prefix("--codegen="))
789 .filter(|option| is_link_only_codegen_option(option))
790 else {
791 return custom;
792 };
793 effect.link_options.insert(option.to_owned());
794 }
795 }
796 }
797
798 effect
799}
800
801fn encoded_rustflags() -> Vec<String> {
802 std::env::var("CARGO_ENCODED_RUSTFLAGS")
803 .ok()
804 .into_iter()
805 .flat_map(|value| value.split('\x1f').map(str::to_owned).collect::<Vec<_>>())
806 .filter(|value| !value.is_empty())
807 .collect()
808}
809
810fn split_rustflags_env() -> Result<Vec<String>, shell_words::ParseError> {
811 std::env::var("RUSTFLAGS").map_or_else(|_| Ok(Vec::new()), |value| shell_words::split(&value))
812}
813
814#[cfg(test)]
815mod tests {
816 use std::collections::BTreeSet;
817 use std::path::{Path, PathBuf};
818
819 use super::ParsedRustcArgs;
820 use crate::platform::StripLevel;
821
822 fn args(parts: &[&str]) -> Vec<std::ffi::OsString> {
823 parts.iter().map(std::ffi::OsString::from).collect()
824 }
825
826 fn with_clean_rustc_env<T>(f: impl FnOnce() -> T) -> T {
827 let encoded = std::env::var_os("CARGO_ENCODED_RUSTFLAGS");
828 let rustflags = std::env::var_os("RUSTFLAGS");
829 let primary = std::env::var_os("CARGO_PRIMARY_PACKAGE");
830 unsafe {
831 std::env::remove_var("CARGO_ENCODED_RUSTFLAGS");
832 std::env::remove_var("RUSTFLAGS");
833 std::env::remove_var("CARGO_PRIMARY_PACKAGE");
834 }
835 let result = f();
836 unsafe {
837 match encoded {
838 Some(value) => std::env::set_var("CARGO_ENCODED_RUSTFLAGS", value),
839 None => std::env::remove_var("CARGO_ENCODED_RUSTFLAGS"),
840 }
841 match rustflags {
842 Some(value) => std::env::set_var("RUSTFLAGS", value),
843 None => std::env::remove_var("RUSTFLAGS"),
844 }
845 match primary {
846 Some(value) => std::env::set_var("CARGO_PRIMARY_PACKAGE", value),
847 None => std::env::remove_var("CARGO_PRIMARY_PACKAGE"),
848 }
849 }
850 result
851 }
852
853 #[test]
854 #[serial_test::serial]
855 fn parses_debug_rlib_invocation() {
856 with_clean_rustc_env(|| {
857 let parsed = ParsedRustcArgs::parse(&args(&[
858 "--crate-name",
859 "itoa",
860 "--crate-type",
861 "rlib",
862 "--target",
863 "aarch64-apple-darwin",
864 "--cfg",
865 "feature=\"default\"",
866 "--out-dir",
867 "/tmp/out",
868 "-C",
869 "metadata=abc123",
870 "-C",
871 "extra-filename=-abc123",
872 "-C",
873 "opt-level=0",
874 "-C",
875 "debug-assertions=yes",
876 ]))
877 .expect("parser should succeed");
878
879 assert_eq!(parsed.crate_name, "itoa");
880 assert_eq!(parsed.target.as_deref(), Some("aarch64-apple-darwin"));
881 assert_eq!(parsed.c_metadata.as_deref(), Some("abc123"));
882 assert!(parsed.is_cacheable());
883 assert!(parsed.features.contains("default"));
884 assert!(
885 parsed
886 .output_rlib_path()
887 .expect("rlib path")
888 .ends_with("libitoa-abc123.rlib")
889 );
890 });
891 }
892
893 #[test]
894 #[serial_test::serial]
895 fn parses_the_equals_spellings_of_every_flag_cargo_can_emit() {
896 with_clean_rustc_env(|| {
897 let parsed = ParsedRustcArgs::parse(&args(&[
898 "--crate-name=itoa",
899 "--crate-type=rlib",
900 "--target=aarch64-apple-darwin",
901 "--cfg=feature=\"default\"",
902 "--out-dir=/tmp/out",
903 "-C",
904 "metadata=abc123",
905 "-C",
906 "extra-filename=-abc123",
907 "-C",
908 "opt-level=0",
909 "-C",
910 "debug-assertions=yes",
911 ]))
912 .expect("parser should succeed");
913
914 assert_eq!(parsed.crate_name, "itoa");
915 assert_eq!(parsed.crate_types, vec!["rlib"]);
916 assert_eq!(parsed.target.as_deref(), Some("aarch64-apple-darwin"));
917 assert_eq!(parsed.c_metadata.as_deref(), Some("abc123"));
918 assert_eq!(
919 parsed.out_dir.as_deref(),
920 Some(std::path::Path::new("/tmp/out"))
921 );
922 assert!(parsed.is_cacheable());
923 assert!(parsed.features.contains("default"));
924 assert!(
925 parsed
926 .output_rlib_path()
927 .expect("rlib path")
928 .ends_with("libitoa-abc123.rlib")
929 );
930 });
931 }
932
933 #[test]
934 #[serial_test::serial]
935 fn equals_and_space_spellings_produce_the_same_parse() {
936 with_clean_rustc_env(|| {
937 let space = ParsedRustcArgs::parse(&args(&[
938 "--crate-name",
939 "itoa",
940 "--crate-type",
941 "rlib",
942 "--crate-type",
943 "cdylib",
944 "--target",
945 "aarch64-apple-darwin",
946 "--cfg",
947 "feature=\"serde\"",
948 "--cfg",
949 "unix",
950 "--out-dir",
951 "/tmp/out",
952 "-C",
953 "metadata=abc123",
954 "-C",
955 "extra-filename=-abc123",
956 ]))
957 .expect("space spelling parses");
958 let equals = ParsedRustcArgs::parse(&args(&[
959 "--crate-name=itoa",
960 "--crate-type=rlib",
961 "--crate-type=cdylib",
962 "--target=aarch64-apple-darwin",
963 "--cfg=feature=\"serde\"",
964 "--cfg=unix",
965 "--out-dir=/tmp/out",
966 "-C",
967 "metadata=abc123",
968 "-C",
969 "extra-filename=-abc123",
970 ]))
971 .expect("equals spelling parses");
972
973 assert_eq!(space.crate_name, equals.crate_name);
974 assert_eq!(space.crate_types, equals.crate_types);
975 assert_eq!(space.target, equals.target);
976 assert_eq!(space.features, equals.features);
977 assert_eq!(space.cfgs, equals.cfgs);
978 assert_eq!(space.cfgs, BTreeSet::from(["unix".to_owned()]));
979 assert_eq!(space.out_dir, equals.out_dir);
980 assert_eq!(space.c_metadata, equals.c_metadata);
981 assert_eq!(space.extra_filename, equals.extra_filename);
982 });
983 }
984
985 #[test]
986 #[serial_test::serial]
987 fn rejects_custom_codegen() {
988 with_clean_rustc_env(|| {
989 let parsed = ParsedRustcArgs::parse(&args(&[
990 "--crate-name",
991 "itoa",
992 "--crate-type",
993 "rlib",
994 "--target",
995 "aarch64-apple-darwin",
996 "--out-dir",
997 "/tmp/out",
998 "-C",
999 "metadata=abc123",
1000 "-C",
1001 "target-cpu=native",
1002 ]))
1003 .expect("parser should succeed");
1004
1005 assert!(!parsed.is_cacheable());
1006 });
1007 }
1008
1009 #[test]
1010 #[serial_test::serial]
1011 fn proc_macro_is_cacheable_without_debug_profile() {
1012 with_clean_rustc_env(|| {
1013 let parsed = ParsedRustcArgs::parse(&args(&[
1014 "--crate-name",
1015 "serde_derive",
1016 "--crate-type",
1017 "proc-macro",
1018 "--target",
1019 "aarch64-apple-darwin",
1020 "--out-dir",
1021 "/tmp/out",
1022 "-C",
1023 "metadata=pm123",
1024 "-C",
1025 "opt-level=3",
1026 ]))
1027 .expect("parser should succeed");
1028
1029 assert!(parsed.is_cacheable());
1030 assert!(parsed.is_proc_macro());
1031 });
1032 }
1033
1034 #[test]
1035 #[serial_test::serial]
1036 fn release_profile_is_cacheable_because_the_profile_is_identity() {
1037 with_clean_rustc_env(|| {
1038 let parsed = ParsedRustcArgs::parse(&args(&[
1039 "--crate-name",
1040 "itoa",
1041 "--crate-type",
1042 "rlib",
1043 "--target",
1044 "aarch64-apple-darwin",
1045 "--out-dir",
1046 "/tmp/out",
1047 "-C",
1048 "metadata=abc123",
1049 "-C",
1050 "opt-level=3",
1051 "-C",
1052 "debug-assertions=no",
1053 ]))
1054 .expect("parser should succeed");
1055
1056 assert!(parsed.is_cacheable());
1057 assert!(parsed.is_locally_cacheable());
1058 let profile = parsed.profile().expect("profile");
1059 assert_eq!(profile.opt_level, "3");
1060 assert!(!profile.debug_assertions);
1061 });
1062 }
1063
1064 #[test]
1065 #[serial_test::serial]
1066 fn primary_package_is_never_locally_cacheable() {
1067 with_clean_rustc_env(|| {
1068 unsafe { std::env::set_var("CARGO_PRIMARY_PACKAGE", "1") };
1069 let parsed = ParsedRustcArgs::parse(&args(&[
1070 "--crate-name",
1071 "itoa",
1072 "--crate-type",
1073 "rlib",
1074 "--target",
1075 "aarch64-apple-darwin",
1076 "--out-dir",
1077 "/tmp/out",
1078 "-C",
1079 "metadata=abc123",
1080 ]))
1081 .expect("parser should succeed");
1082
1083 assert!(!parsed.is_locally_cacheable());
1084 });
1085 }
1086
1087 #[test]
1088 #[serial_test::serial]
1089 fn custom_codegen_is_never_locally_cacheable() {
1090 with_clean_rustc_env(|| {
1091 let parsed = ParsedRustcArgs::parse(&args(&[
1092 "--crate-name",
1093 "itoa",
1094 "--crate-type",
1095 "rlib",
1096 "--target",
1097 "aarch64-apple-darwin",
1098 "--out-dir",
1099 "/tmp/out",
1100 "-C",
1101 "metadata=abc123",
1102 "-C",
1103 "target-cpu=native",
1104 ]))
1105 .expect("parser should succeed");
1106
1107 assert!(!parsed.is_locally_cacheable());
1108 });
1109 }
1110
1111 #[test]
1112 #[serial_test::serial]
1113 fn link_only_codegen_options_stay_cacheable_for_an_rlib() {
1114 with_clean_rustc_env(|| {
1115 for option in [
1116 "link-arg=-fuse-ld=mold",
1117 "link-args=-fuse-ld=mold -Wl,--as-needed",
1118 "linker=clang",
1119 "linker-flavor=gcc",
1120 "link-self-contained=y",
1121 ] {
1122 let parsed = ParsedRustcArgs::parse(&args(&[
1123 "--crate-name",
1124 "itoa",
1125 "--crate-type",
1126 "rlib",
1127 "--target",
1128 "x86_64-unknown-linux-gnu",
1129 "--out-dir",
1130 "/tmp/out",
1131 "-C",
1132 "metadata=abc123",
1133 "-C",
1134 option,
1135 ]))
1136 .expect("parser should succeed");
1137
1138 assert!(!parsed.has_custom_codegen, "{option} marked custom");
1139 assert!(parsed.is_cacheable(), "{option} made unit uncacheable");
1140 }
1141 });
1142 }
1143
1144 #[test]
1145 #[serial_test::serial]
1146 fn link_options_key_a_unit_that_links_instead_of_disqualifying_it() {
1147 with_clean_rustc_env(|| {
1153 for crate_type in ["proc-macro", "dylib"] {
1154 let parsed = ParsedRustcArgs::parse(&args(&[
1155 "--crate-name",
1156 "serde_derive",
1157 "--crate-type",
1158 crate_type,
1159 "--target",
1160 "x86_64-unknown-linux-gnu",
1161 "--out-dir",
1162 "/tmp/out",
1163 "-C",
1164 "metadata=abc123",
1165 "-C",
1166 "link-arg=-L/opt/custom/lib",
1167 ]))
1168 .expect("parser should succeed");
1169
1170 assert_eq!(
1171 parsed.link_options_reaching_the_linker(),
1172 vec!["link-arg=-L/opt/custom/lib".to_owned()],
1173 "{crate_type} lost the link option the key needs"
1174 );
1175 assert!(
1176 !parsed.has_custom_codegen,
1177 "{crate_type} was misfiled as custom codegen"
1178 );
1179 assert!(
1180 parsed.is_cacheable(),
1181 "{crate_type} with a link option was refused instead of keyed"
1182 );
1183 assert!(
1184 parsed.is_locally_cacheable(),
1185 "{crate_type} with a link option was refused by the local cache"
1186 );
1187 }
1188 });
1189 }
1190
1191 #[test]
1192 #[serial_test::serial]
1193 fn an_rlib_never_carries_link_options_into_its_key() {
1194 with_clean_rustc_env(|| {
1198 let parsed = ParsedRustcArgs::parse(&args(&[
1199 "--crate-name",
1200 "serde",
1201 "--crate-type",
1202 "lib",
1203 "--target",
1204 "x86_64-unknown-linux-gnu",
1205 "--out-dir",
1206 "/tmp/out",
1207 "-C",
1208 "metadata=abc123",
1209 "-C",
1210 "link-arg=-fuse-ld=mold",
1211 ]))
1212 .expect("parser should succeed");
1213
1214 assert_eq!(
1215 parsed.link_options,
1216 BTreeSet::from(["link-arg=-fuse-ld=mold".to_owned()]),
1217 "the option was not parsed"
1218 );
1219 assert!(
1220 parsed.link_options_reaching_the_linker().is_empty(),
1221 "an rlib reported a link option as reaching the linker"
1222 );
1223 assert!(parsed.is_cacheable(), "a mold-built rlib stopped serving");
1224 });
1225 }
1226 #[test]
1227 #[serial_test::serial]
1228 fn repeated_crate_type_flags_union_into_one_identity() {
1229 with_clean_rustc_env(|| {
1234 let parsed = ParsedRustcArgs::parse(&args(&[
1235 "--crate-name",
1236 "ffi_thing",
1237 "--crate-type",
1238 "lib",
1239 "--crate-type",
1240 "cdylib",
1241 "--target",
1242 "x86_64-unknown-linux-gnu",
1243 "--out-dir",
1244 "/tmp/out",
1245 "-C",
1246 "metadata=abc123",
1247 ]))
1248 .expect("parser should succeed");
1249
1250 assert_eq!(parsed.crate_types, vec!["lib", "cdylib"]);
1251 });
1252 }
1253
1254 #[test]
1255 #[serial_test::serial]
1256 fn a_lib_that_also_produces_a_cdylib_is_a_unit_that_links() {
1257 with_clean_rustc_env(|| {
1264 let parsed = ParsedRustcArgs::parse(&args(&[
1265 "--crate-name",
1266 "ffi_thing",
1267 "--crate-type",
1268 "lib",
1269 "--crate-type",
1270 "cdylib",
1271 "--target",
1272 "x86_64-unknown-linux-gnu",
1273 "--out-dir",
1274 "/tmp/out",
1275 "-C",
1276 "metadata=abc123",
1277 "-C",
1278 "link-arg=-L/opt/custom/lib",
1279 ]))
1280 .expect("parser should succeed");
1281
1282 assert!(parsed.produces_rlib(), "the rlib output is what is cached");
1283 assert_eq!(
1284 parsed.link_options_reaching_the_linker(),
1285 vec!["link-arg=-L/opt/custom/lib".to_owned()],
1286 "a lib+cdylib unit dropped the link option from its key"
1287 );
1288 assert!(parsed.is_cacheable(), "a lib+cdylib unit stopped serving");
1289 });
1290 }
1291
1292 #[test]
1293 #[serial_test::serial]
1294 fn link_only_env_rustflags_key_a_unit_that_links() {
1295 unsafe {
1300 std::env::set_var("RUSTFLAGS", "-C link-arg=-fuse-ld=mold");
1301 }
1302 let linked = ParsedRustcArgs::parse(&args(&[
1303 "--crate-name",
1304 "serde_derive",
1305 "--crate-type",
1306 "proc-macro",
1307 "--target",
1308 "x86_64-unknown-linux-gnu",
1309 "--out-dir",
1310 "/tmp/out",
1311 "-C",
1312 "metadata=abc123",
1313 ]))
1314 .expect("parser should succeed");
1315 let rlib = ParsedRustcArgs::parse(&args(&[
1316 "--crate-name",
1317 "serde",
1318 "--crate-type",
1319 "rlib",
1320 "--target",
1321 "x86_64-unknown-linux-gnu",
1322 "--out-dir",
1323 "/tmp/out",
1324 "-C",
1325 "metadata=abc123",
1326 ]))
1327 .expect("parser should succeed");
1328 unsafe {
1329 std::env::remove_var("RUSTFLAGS");
1330 }
1331
1332 assert_eq!(
1333 linked.link_options_reaching_the_linker(),
1334 vec!["link-arg=-fuse-ld=mold".to_owned()],
1335 "a linked unit lost the rustflags link option from its key"
1336 );
1337 assert!(
1338 linked.is_cacheable(),
1339 "a mold-linked proc-macro stopped serving"
1340 );
1341 assert!(
1342 rlib.link_options_reaching_the_linker().is_empty(),
1343 "an rlib took a rustflags link option into its key"
1344 );
1345 assert!(rlib.is_cacheable(), "the rlib stopped being cacheable");
1346 }
1347
1348 #[test]
1349 #[serial_test::serial]
1350 fn codegen_flags_that_change_objects_stay_custom() {
1351 with_clean_rustc_env(|| {
1352 for option in ["linker-plugin-lto", "link-dead-code=y", "target-cpu=native"] {
1353 let parsed = ParsedRustcArgs::parse(&args(&[
1354 "--crate-name",
1355 "itoa",
1356 "--crate-type",
1357 "rlib",
1358 "--target",
1359 "x86_64-unknown-linux-gnu",
1360 "--out-dir",
1361 "/tmp/out",
1362 "-C",
1363 "metadata=abc123",
1364 "-C",
1365 option,
1366 ]))
1367 .expect("parser should succeed");
1368
1369 assert!(parsed.has_custom_codegen, "{option} lost its marking");
1370 assert!(!parsed.is_locally_cacheable());
1371 }
1372 });
1373 }
1374
1375 #[test]
1376 #[serial_test::serial]
1377 fn nightly_cargo_embed_metadata_is_not_custom_codegen() {
1378 with_clean_rustc_env(|| {
1379 for spelling in ["-Z embed-metadata=no", "-Zembed-metadata=no"] {
1380 let mut invocation = vec![
1381 "--crate-name",
1382 "itoa",
1383 "--crate-type",
1384 "rlib",
1385 "--target",
1386 "aarch64-apple-darwin",
1387 "--out-dir",
1388 "/tmp/out",
1389 "-C",
1390 "metadata=abc123",
1391 ];
1392 invocation.extend(spelling.split(' '));
1393 let parsed =
1394 ParsedRustcArgs::parse(&args(&invocation)).expect("parser should succeed");
1395
1396 assert_eq!(parsed.embed_metadata, Some(false));
1397 assert!(!parsed.has_custom_codegen);
1398 assert!(parsed.is_locally_cacheable());
1399 assert!(parsed.is_cacheable());
1400 }
1401 });
1402 }
1403
1404 #[test]
1405 #[serial_test::serial]
1406 fn parses_embed_metadata_yes() {
1407 with_clean_rustc_env(|| {
1408 let parsed = ParsedRustcArgs::parse(&args(&[
1409 "--crate-name",
1410 "itoa",
1411 "--crate-type",
1412 "rlib",
1413 "--target",
1414 "aarch64-apple-darwin",
1415 "--out-dir",
1416 "/tmp/out",
1417 "-C",
1418 "metadata=abc123",
1419 "-Z",
1420 "embed-metadata=yes",
1421 ]))
1422 .expect("parser should succeed");
1423
1424 assert_eq!(parsed.embed_metadata, Some(true));
1425 assert!(!parsed.has_custom_codegen);
1426 });
1427 }
1428
1429 #[test]
1430 #[serial_test::serial]
1431 fn other_unstable_options_still_count_as_custom_codegen() {
1432 with_clean_rustc_env(|| {
1433 for spelling in ["-Z some-other-flag", "-Zsome-other-flag"] {
1434 let mut invocation = vec![
1435 "--crate-name",
1436 "itoa",
1437 "--crate-type",
1438 "rlib",
1439 "--target",
1440 "aarch64-apple-darwin",
1441 "--out-dir",
1442 "/tmp/out",
1443 "-C",
1444 "metadata=abc123",
1445 ];
1446 invocation.extend(spelling.split(' '));
1447 let parsed =
1448 ParsedRustcArgs::parse(&args(&invocation)).expect("parser should succeed");
1449
1450 assert_eq!(parsed.embed_metadata, None);
1451 assert!(parsed.has_custom_codegen);
1452 assert!(!parsed.is_locally_cacheable());
1453 }
1454 });
1455 }
1456
1457 #[test]
1458 #[serial_test::serial]
1459 fn malformed_embed_metadata_value_is_a_parse_error() {
1460 with_clean_rustc_env(|| {
1461 let error = ParsedRustcArgs::parse(&args(&[
1462 "--crate-name",
1463 "itoa",
1464 "--crate-type",
1465 "rlib",
1466 "-Z",
1467 "embed-metadata=banana",
1468 ]))
1469 .expect_err("malformed embed-metadata value must fail parsing");
1470
1471 assert!(error.contains("invalid boolean rustc codegen value"));
1472 });
1473 }
1474
1475 #[test]
1476 #[serial_test::serial]
1477 fn accepts_common_codegen_flag_without_value() {
1478 with_clean_rustc_env(|| {
1479 let parsed = ParsedRustcArgs::parse(&args(&[
1480 "--crate-name",
1481 "zerocopy_derive",
1482 "--crate-type",
1483 "proc-macro",
1484 "--target",
1485 "aarch64-apple-darwin",
1486 "--out-dir",
1487 "/tmp/out",
1488 "-C",
1489 "metadata=pm123",
1490 "-C",
1491 "prefer-dynamic",
1492 ]))
1493 .expect("parser should succeed");
1494
1495 assert!(parsed.is_cacheable());
1496 assert!(parsed.is_proc_macro());
1497 assert!(!parsed.has_custom_codegen);
1498 });
1499 }
1500
1501 #[test]
1502 #[serial_test::serial]
1503 fn unknown_codegen_flag_without_value_is_not_cacheable() {
1504 with_clean_rustc_env(|| {
1505 let parsed = ParsedRustcArgs::parse(&args(&[
1506 "--crate-name",
1507 "itoa",
1508 "--crate-type",
1509 "rlib",
1510 "--target",
1511 "aarch64-apple-darwin",
1512 "--out-dir",
1513 "/tmp/out",
1514 "-C",
1515 "metadata=abc123",
1516 "-C",
1517 "mystery-flag",
1518 ]))
1519 .expect("parser should succeed");
1520
1521 assert!(!parsed.is_cacheable());
1522 assert!(parsed.has_custom_codegen);
1523 });
1524 }
1525
1526 #[test]
1527 #[serial_test::serial]
1528 fn debug_profile_defaults_are_cacheable() {
1529 with_clean_rustc_env(|| {
1530 let parsed = ParsedRustcArgs::parse(&args(&[
1531 "--crate-name",
1532 "unicode_ident",
1533 "--crate-type",
1534 "lib",
1535 "--target",
1536 "aarch64-apple-darwin",
1537 "--out-dir",
1538 "/tmp/out",
1539 "-C",
1540 "metadata=fd828ed38da2eccd",
1541 "-C",
1542 "extra-filename=-d1c4311f6644f7fa",
1543 "-C",
1544 "split-debuginfo=unpacked",
1545 ]))
1546 .expect("parser should succeed");
1547
1548 assert!(parsed.is_cacheable());
1549 assert!(!parsed.has_custom_codegen);
1550 });
1551 }
1552
1553 #[test]
1554 #[serial_test::serial]
1555 fn implicit_host_target_is_cacheable() {
1556 with_clean_rustc_env(|| {
1557 let parsed = ParsedRustcArgs::parse(&args(&[
1558 "--crate-name",
1559 "byteorder",
1560 "--crate-type",
1561 "lib",
1562 "--out-dir",
1563 "/tmp/out",
1564 "-C",
1565 "metadata=979b12f2cebb9d65",
1566 "-C",
1567 "extra-filename=-9c7162ae9201c5e4",
1568 ]))
1569 .expect("parser should succeed");
1570
1571 assert!(parsed.is_cacheable());
1572 });
1573 }
1574
1575 #[test]
1576 #[serial_test::serial]
1577 fn optimized_dependency_is_still_restorable_for_ci_capture() {
1578 with_clean_rustc_env(|| {
1579 let parsed = ParsedRustcArgs::parse(&args(&[
1580 "--crate-name",
1581 "regex",
1582 "--crate-type",
1583 "lib",
1584 "--target",
1585 "aarch64-apple-darwin",
1586 "--out-dir",
1587 "/tmp/out",
1588 "--json",
1589 "diagnostic-rendered-ansi,artifacts,future-incompat",
1590 "-C",
1591 "metadata=regex123",
1592 "-C",
1593 "extra-filename=-regex123",
1594 "-C",
1595 "opt-level=3",
1596 "-C",
1597 "debug-assertions=yes",
1598 ]))
1599 .expect("parser should succeed");
1600
1601 assert!(parsed.is_restorable_artifact());
1602 assert!(parsed.is_cacheable());
1603 assert!(parsed.requests_json_artifact_notifications());
1604 });
1605 }
1606
1607 #[test]
1608 #[serial_test::serial]
1609 fn dylib_invocation_is_cacheable_in_debug_profile() {
1610 with_clean_rustc_env(|| {
1611 let parsed = ParsedRustcArgs::parse(&args(&[
1612 "--crate-name",
1613 "bevy_dylib",
1614 "--crate-type",
1615 "dylib",
1616 "--target",
1617 "aarch64-apple-darwin",
1618 "--out-dir",
1619 "/tmp/out",
1620 "-C",
1621 "metadata=dylib123",
1622 "-C",
1623 "extra-filename=-dylib123",
1624 "-C",
1625 "opt-level=0",
1626 "-C",
1627 "debug-assertions=yes",
1628 ]))
1629 .expect("parser should succeed");
1630
1631 assert!(parsed.is_cacheable());
1632 assert_eq!(
1633 parsed
1634 .output_dynamic_library_path()
1635 .expect("dylib path")
1636 .expect("dynamic library output"),
1637 PathBuf::from("/tmp/out/libbevy_dylib-dylib123.dylib")
1638 );
1639 });
1640 }
1641
1642 #[test]
1643 #[serial_test::serial]
1644 fn proc_macro_metadata_output_uses_rmeta_path() {
1645 with_clean_rustc_env(|| {
1646 let parsed = ParsedRustcArgs::parse(&args(&[
1647 "--crate-name",
1648 "pest_derive",
1649 "--crate-type",
1650 "proc-macro",
1651 "--target",
1652 "aarch64-apple-darwin",
1653 "--out-dir",
1654 "/tmp/out",
1655 "--emit",
1656 "dep-info,metadata",
1657 "-C",
1658 "metadata=pm123",
1659 "-C",
1660 "extra-filename=-pm123",
1661 ]))
1662 .expect("parser should succeed");
1663
1664 assert_eq!(
1665 parsed.output_rmeta_path().expect("proc-macro rmeta path"),
1666 PathBuf::from("/tmp/out/libpest_derive-pm123.rmeta")
1667 );
1668 });
1669 }
1670
1671 #[test]
1672 #[serial_test::serial]
1673 fn static_lib_crate_types_do_not_report_dynamic_library_output() {
1674 with_clean_rustc_env(|| {
1675 let parsed = ParsedRustcArgs::parse(&args(&[
1676 "--crate-name",
1677 "unicode_ident",
1678 "--crate-type",
1679 "lib",
1680 "--target",
1681 "aarch64-apple-darwin",
1682 "--out-dir",
1683 "/tmp/out",
1684 "--emit",
1685 "dep-info,metadata,link",
1686 "-C",
1687 "metadata=lib123",
1688 "-C",
1689 "extra-filename=-lib123",
1690 ]))
1691 .expect("parser should succeed");
1692
1693 assert!(parsed.produces_rlib());
1694 assert!(!parsed.produces_dynamic_library());
1695 assert_eq!(
1696 parsed
1697 .output_dynamic_library_path()
1698 .expect("dynamic library path"),
1699 None
1700 );
1701 assert_eq!(
1702 parsed.output_link_path().expect("link path"),
1703 Some(PathBuf::from("/tmp/out/libunicode_ident-lib123.rlib"))
1704 );
1705 });
1706 }
1707
1708 #[test]
1709 #[serial_test::serial]
1710 fn build_script_paths_match_cargo_naming() {
1711 with_clean_rustc_env(|| {
1712 let parsed = ParsedRustcArgs::parse(&args(&[
1713 "--crate-name",
1714 "build_script_build",
1715 "--crate-type",
1716 "bin",
1717 "--out-dir",
1718 "/tmp/out",
1719 "-C",
1720 "metadata=e8a2e5854cc9f44b",
1721 "-C",
1722 "extra-filename=-fbf81822541b190b",
1723 ]))
1724 .expect("parser should succeed");
1725
1726 assert!(parsed.is_build_script());
1727 assert_eq!(
1728 parsed.output_binary_path().expect("build script output"),
1729 Path::new("/tmp/out").join(format!(
1730 "build_script_build-fbf81822541b190b{}",
1731 std::env::consts::EXE_SUFFIX
1732 ))
1733 );
1734 assert_eq!(
1735 parsed
1736 .build_script_alias_path()
1737 .expect("build script alias"),
1738 Path::new("/tmp/out").join(format!(
1739 "build-script-build{}",
1740 std::env::consts::EXE_SUFFIX
1741 ))
1742 );
1743 });
1744 }
1745
1746 #[test]
1747 #[serial_test::serial]
1748 fn remap_rustflags_do_not_disable_cacheability() {
1749 unsafe {
1750 std::env::set_var(
1751 "RUSTFLAGS",
1752 "--remap-path-prefix=/tmp/work=stow-ci://workspace",
1753 );
1754 }
1755 let parsed = ParsedRustcArgs::parse(&args(&[
1756 "--crate-name",
1757 "cfg_if",
1758 "--crate-type",
1759 "lib",
1760 "--target",
1761 "aarch64-apple-darwin",
1762 "--out-dir",
1763 "/tmp/out",
1764 "-C",
1765 "metadata=cfgif123",
1766 ]))
1767 .expect("parser should succeed");
1768 unsafe {
1769 std::env::remove_var("RUSTFLAGS");
1770 }
1771
1772 assert!(parsed.is_cacheable());
1773 assert!(!parsed.has_custom_codegen);
1774 }
1775
1776 #[test]
1777 #[serial_test::serial]
1778 fn link_flag_env_rustflags_do_not_disable_cacheability() {
1779 for value in [
1780 "-C link-arg=-fuse-ld=mold",
1781 "-Clink-arg=-fuse-ld=mold",
1782 "--codegen=link-arg=-fuse-ld=mold",
1783 "--remap-path-prefix=/tmp/work=stow-ci://workspace -C linker=clang",
1784 ] {
1785 unsafe {
1786 std::env::set_var("RUSTFLAGS", value);
1787 }
1788 let parsed = ParsedRustcArgs::parse(&args(&[
1789 "--crate-name",
1790 "cfg_if",
1791 "--crate-type",
1792 "lib",
1793 "--target",
1794 "x86_64-unknown-linux-gnu",
1795 "--out-dir",
1796 "/tmp/out",
1797 "-C",
1798 "metadata=cfgif123",
1799 ]))
1800 .expect("parser should succeed");
1801 unsafe {
1802 std::env::remove_var("RUSTFLAGS");
1803 }
1804
1805 assert!(parsed.is_cacheable(), "{value} made unit uncacheable");
1806 assert!(!parsed.has_custom_codegen, "{value} marked custom");
1807 }
1808 }
1809
1810 #[test]
1811 #[serial_test::serial]
1812 fn mixed_env_rustflags_still_disable_cacheability() {
1813 unsafe {
1814 std::env::set_var(
1815 "RUSTFLAGS",
1816 "-C link-arg=-fuse-ld=mold -C target-cpu=native",
1817 );
1818 }
1819 let parsed = ParsedRustcArgs::parse(&args(&[
1820 "--crate-name",
1821 "cfg_if",
1822 "--crate-type",
1823 "lib",
1824 "--target",
1825 "x86_64-unknown-linux-gnu",
1826 "--out-dir",
1827 "/tmp/out",
1828 "-C",
1829 "metadata=cfgif123",
1830 ]))
1831 .expect("parser should succeed");
1832 unsafe {
1833 std::env::remove_var("RUSTFLAGS");
1834 }
1835
1836 assert!(!parsed.is_cacheable());
1837 assert!(parsed.has_custom_codegen);
1838 }
1839
1840 #[test]
1841 #[serial_test::serial]
1842 fn custom_env_rustflags_disable_cacheability() {
1843 unsafe {
1844 std::env::set_var("RUSTFLAGS", "-C target-cpu=native");
1845 }
1846 let parsed = ParsedRustcArgs::parse(&args(&[
1847 "--crate-name",
1848 "cfg_if",
1849 "--crate-type",
1850 "lib",
1851 "--target",
1852 "aarch64-apple-darwin",
1853 "--out-dir",
1854 "/tmp/out",
1855 "-C",
1856 "metadata=cfgif123",
1857 ]))
1858 .expect("parser should succeed");
1859 unsafe {
1860 std::env::remove_var("RUSTFLAGS");
1861 }
1862
1863 assert!(!parsed.is_cacheable());
1864 assert!(parsed.has_custom_codegen);
1865 }
1866
1867 #[test]
1868 #[serial_test::serial]
1869 fn parses_json_artifact_requests() {
1870 with_clean_rustc_env(|| {
1871 let parsed = ParsedRustcArgs::parse(&args(&[
1872 "--crate-name",
1873 "itoa",
1874 "--crate-type",
1875 "lib",
1876 "--out-dir",
1877 "/tmp/out",
1878 "--json=diagnostic-rendered-ansi,artifacts,future-incompat",
1879 "-C",
1880 "metadata=json123",
1881 ]))
1882 .expect("parser should succeed");
1883
1884 assert!(parsed.requests_json_artifact_notifications());
1885 assert!(parsed.json.contains("diagnostic-rendered-ansi"));
1886 assert!(parsed.json.contains("future-incompat"));
1887 });
1888 }
1889
1890 #[test]
1891 #[serial_test::serial]
1892 fn parses_extern_crates() {
1893 with_clean_rustc_env(|| {
1894 let parsed = ParsedRustcArgs::parse(&args(&[
1895 "--crate-name",
1896 "grep_searcher",
1897 "--crate-type",
1898 "lib",
1899 "--out-dir",
1900 "/tmp/out",
1901 "--extern",
1902 "memchr=/tmp/deps/libmemchr-aaaaaaaaaaaaaaaa.rmeta",
1903 "--extern=regex_automata=/tmp/deps/libregex_automata-bbbbbbbbbbbbbbbb.rmeta",
1904 "-C",
1905 "metadata=grepsearcher123",
1906 ]))
1907 .expect("parser should succeed");
1908
1909 assert_eq!(parsed.extern_crates.len(), 2);
1910 assert_eq!(parsed.extern_crates[0].crate_name, "memchr");
1911 assert_eq!(
1912 parsed.extern_crates[0].path,
1913 PathBuf::from("/tmp/deps/libmemchr-aaaaaaaaaaaaaaaa.rmeta")
1914 );
1915 assert_eq!(parsed.extern_crates[1].crate_name, "regex_automata");
1916 });
1917 }
1918
1919 #[test]
1920 fn strip_level_is_part_of_the_profile() {
1921 let parsed = ParsedRustcArgs::parse(&args(&[
1922 "--crate-name",
1923 "itoa",
1924 "--crate-type",
1925 "rlib",
1926 "--out-dir",
1927 "/tmp/out",
1928 "-C",
1929 "metadata=abc123",
1930 "-C",
1931 "strip=debuginfo",
1932 ]))
1933 .expect("parser should succeed");
1934 assert!(!parsed.has_custom_codegen);
1935 assert_eq!(parsed.strip.as_deref(), Some("debuginfo"));
1936 assert_eq!(
1937 parsed.profile().expect("profile").strip,
1938 StripLevel::Debuginfo
1939 );
1940
1941 let error = ParsedRustcArgs::parse(&args(&["--crate-name", "itoa", "-C", "strip=all"]))
1942 .expect("parser should succeed")
1943 .profile()
1944 .expect_err("unknown strip level must fail");
1945 assert!(error.contains("strip"), "{error}");
1946 }
1947}