nextest_runner/
rustc_cli.rs1use crate::cargo_config::TargetTriple;
5use camino::Utf8PathBuf;
6use std::{borrow::Cow, path::PathBuf};
7use tracing::{debug, trace};
8
9#[derive(Clone, Debug)]
11pub struct RustcCli<'a> {
12 rustc_path: Utf8PathBuf,
13 args: Vec<Cow<'a, str>>,
14}
15
16impl<'a> RustcCli<'a> {
17 pub fn version_verbose() -> Self {
19 let mut cli = Self::default();
20 cli.add_arg("--version").add_arg("--verbose");
21 cli
22 }
23
24 pub fn print_host_libdir() -> Self {
26 let mut cli = Self::default();
27 cli.add_arg("--print").add_arg("target-libdir");
28 cli
29 }
30
31 pub fn print_target_libdir(triple: &'a TargetTriple) -> Self {
33 let mut cli = Self::default();
34 cli.add_arg("--print")
35 .add_arg("target-libdir")
36 .add_arg("--target")
37 .add_arg(triple.platform.triple_str());
38 cli
39 }
40
41 fn add_arg(&mut self, arg: impl Into<Cow<'a, str>>) -> &mut Self {
42 self.args.push(arg.into());
43 self
44 }
45
46 pub fn to_expression(&self) -> duct::Expression {
48 duct::cmd(
49 self.rustc_path.as_str(),
50 self.args.iter().map(|arg| arg.as_ref()),
51 )
52 }
53
54 pub fn read(&self) -> Option<Vec<u8>> {
57 let expression = self.to_expression();
58 trace!("Executing command: {:?}", expression);
59 let output = match expression
60 .stdout_capture()
61 .stderr_capture()
62 .unchecked()
63 .run()
64 {
65 Ok(output) => output,
66 Err(e) => {
67 debug!("Failed to spawn the child process: {}", e);
68 return None;
69 }
70 };
71 if !output.status.success() {
72 debug!("execution failed with {}", output.status);
73 debug!("stdout:");
74 debug!("{}", String::from_utf8_lossy(&output.stdout));
75 debug!("stderr:");
76 debug!("{}", String::from_utf8_lossy(&output.stderr));
77 return None;
78 }
79 Some(output.stdout)
80 }
81}
82
83impl Default for RustcCli<'_> {
84 fn default() -> Self {
85 Self {
86 rustc_path: rustc_path(),
87 args: vec![],
88 }
89 }
90}
91
92fn rustc_path() -> Utf8PathBuf {
93 match std::env::var_os("RUSTC") {
94 Some(rustc_path) => PathBuf::from(rustc_path)
95 .try_into()
96 .expect("RUSTC env var is not valid UTF-8"),
97 None => Utf8PathBuf::from("rustc"),
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use camino_tempfile::Utf8TempDir;
105 use std::env;
106
107 #[test]
108 fn test_should_run_rustc_version() {
109 let mut cli = RustcCli::default();
110 cli.add_arg("--version");
111 let output = cli.read().expect("rustc --version should run successfully");
112 let output = String::from_utf8(output).expect("the output should be valid utf-8");
113 assert!(
114 output.starts_with("rustc"),
115 "The output should start with rustc, but the actual output is: {output}"
116 );
117 }
118
119 #[test]
120 fn test_should_respect_rustc_env() {
121 env::set_var("RUSTC", "cargo");
122 let mut cli = RustcCli::default();
123 cli.add_arg("--version");
124 let output = cli.read().expect("cargo --version should run successfully");
125 let output = String::from_utf8(output).expect("the output should be valid utf-8");
126 assert!(
127 output.starts_with("cargo"),
128 "The output should start with cargo, but the actual output is: {output}"
129 );
130 }
131
132 #[test]
133 fn test_fail_to_spawn() {
134 let fake_dir = Utf8TempDir::new().expect("should create the temp dir successfully");
135 env::set_var("RUSTC", fake_dir.path());
137 let mut cli = RustcCli::default();
138 cli.add_arg("--version");
139 let output = cli.read();
140 assert_eq!(output, None);
141 }
142
143 #[test]
144 fn test_execute_with_failure() {
145 let mut cli = RustcCli::default();
146 cli.add_arg("--print");
148 cli.add_arg("Y7uDG1HrrY");
149 let output = cli.read();
150 assert_eq!(output, None);
151 }
152}