• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::collections::HashMap;
2 use std::env;
3 use std::ffi::OsString;
4 use std::fmt::Write as _;
5 use std::fs::File;
6 use std::io::{self, BufWriter, Read, Write};
7 use std::ops::Not;
8 use std::path::{Path, PathBuf};
9 use std::process::Command;
10 
11 use cargo_metadata::{Metadata, MetadataCommand};
12 use serde::{Deserialize, Serialize};
13 
14 pub use crate::arg::*;
15 
show_error(msg: &impl std::fmt::Display) -> !16 pub fn show_error(msg: &impl std::fmt::Display) -> ! {
17     eprintln!("fatal error: {msg}");
18     std::process::exit(1)
19 }
20 
21 macro_rules! show_error {
22     ($($tt:tt)*) => { crate::util::show_error(&format_args!($($tt)*)) };
23 }
24 
25 /// The information to run a crate with the given environment.
26 #[derive(Clone, Serialize, Deserialize)]
27 pub struct CrateRunEnv {
28     /// The command-line arguments.
29     pub args: Vec<String>,
30     /// The environment.
31     pub env: Vec<(OsString, OsString)>,
32     /// The current working directory.
33     pub current_dir: OsString,
34     /// The contents passed via standard input.
35     pub stdin: Vec<u8>,
36 }
37 
38 impl CrateRunEnv {
39     /// Gather all the information we need.
collect(args: impl Iterator<Item = String>, capture_stdin: bool) -> Self40     pub fn collect(args: impl Iterator<Item = String>, capture_stdin: bool) -> Self {
41         let args = args.collect();
42         let env = env::vars_os().collect();
43         let current_dir = env::current_dir().unwrap().into_os_string();
44 
45         let mut stdin = Vec::new();
46         if capture_stdin {
47             std::io::stdin().lock().read_to_end(&mut stdin).expect("cannot read stdin");
48         }
49 
50         CrateRunEnv { args, env, current_dir, stdin }
51     }
52 }
53 
54 /// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
55 #[derive(Serialize, Deserialize)]
56 pub enum CrateRunInfo {
57     /// Run it with the given environment.
58     RunWith(CrateRunEnv),
59     /// Skip it as Miri does not support interpreting such kind of crates.
60     SkipProcMacroTest,
61 }
62 
63 impl CrateRunInfo {
store(&self, filename: &Path)64     pub fn store(&self, filename: &Path) {
65         let file = File::create(filename)
66             .unwrap_or_else(|_| show_error!("cannot create `{}`", filename.display()));
67         let file = BufWriter::new(file);
68         serde_json::ser::to_writer(file, self)
69             .unwrap_or_else(|_| show_error!("cannot write to `{}`", filename.display()));
70     }
71 }
72 
73 #[derive(Clone, Debug)]
74 pub enum MiriCommand {
75     /// Our own special 'setup' command.
76     Setup,
77     /// A command to be forwarded to cargo.
78     Forward(String),
79 }
80 
81 /// Escapes `s` in a way that is suitable for using it as a string literal in TOML syntax.
escape_for_toml(s: &str) -> String82 pub fn escape_for_toml(s: &str) -> String {
83     // We want to surround this string in quotes `"`. So we first escape all quotes,
84     // and also all backslashes (that are used to escape quotes).
85     let s = s.replace('\\', r#"\\"#).replace('"', r#"\""#);
86     format!("\"{s}\"")
87 }
88 
89 /// Returns the path to the `miri` binary
find_miri() -> PathBuf90 pub fn find_miri() -> PathBuf {
91     if let Some(path) = env::var_os("MIRI") {
92         return path.into();
93     }
94     let mut path = std::env::current_exe().expect("current executable path invalid");
95     if cfg!(windows) {
96         path.set_file_name("miri.exe");
97     } else {
98         path.set_file_name("miri");
99     }
100     path
101 }
102 
miri() -> Command103 pub fn miri() -> Command {
104     Command::new(find_miri())
105 }
106 
miri_for_host() -> Command107 pub fn miri_for_host() -> Command {
108     let mut cmd = miri();
109     cmd.env("MIRI_BE_RUSTC", "host");
110     cmd
111 }
112 
cargo() -> Command113 pub fn cargo() -> Command {
114     Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
115 }
116 
117 /// Execute the `Command`, where possible by replacing the current process with a new process
118 /// described by the `Command`. Then exit this process with the exit code of the new process.
exec(mut cmd: Command) -> !119 pub fn exec(mut cmd: Command) -> ! {
120     // On non-Unix imitate POSIX exec as closely as we can
121     #[cfg(not(unix))]
122     {
123         let exit_status = cmd.status().expect("failed to run command");
124         std::process::exit(exit_status.code().unwrap_or(-1))
125     }
126     // On Unix targets, actually exec.
127     // If exec returns, process setup has failed. This is the same error condition as the expect in
128     // the non-Unix case.
129     #[cfg(unix)]
130     {
131         use std::os::unix::process::CommandExt;
132         let error = cmd.exec();
133         Err(error).expect("failed to run command")
134     }
135 }
136 
137 /// Execute the `Command`, where possible by replacing the current process with a new process
138 /// described by the `Command`. Then exit this process with the exit code of the new process.
139 /// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its
140 /// contents to `path` first, then setting stdin to that file.
exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> ! where P: AsRef<Path>,141 pub fn exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> !
142 where
143     P: AsRef<Path>,
144 {
145     #[cfg(unix)]
146     {
147         // Write the bytes we want to send to stdin out to a file
148         std::fs::write(&path, input).unwrap();
149         // Open the file for reading, and set our new stdin to it
150         let stdin = File::open(&path).unwrap();
151         cmd.stdin(stdin);
152         // Unlink the file so that it is fully cleaned up as soon as the new process exits
153         std::fs::remove_file(&path).unwrap();
154         // Finally, we can hand off control.
155         exec(cmd)
156     }
157     #[cfg(not(unix))]
158     {
159         drop(path); // We don't need the path, we can pipe the bytes directly
160         cmd.stdin(std::process::Stdio::piped());
161         let mut child = cmd.spawn().expect("failed to spawn process");
162         {
163             let stdin = child.stdin.as_mut().expect("failed to open stdin");
164             stdin.write_all(input).expect("failed to write out test source");
165         }
166         let exit_status = child.wait().expect("failed to run command");
167         std::process::exit(exit_status.code().unwrap_or(-1))
168     }
169 }
170 
ask_to_run(mut cmd: Command, ask: bool, text: &str)171 pub fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
172     // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
173     // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
174     // so we also check their `TF_BUILD`.
175     let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
176     if ask && !is_ci {
177         let mut buf = String::new();
178         print!("I will run `{cmd:?}` to {text}. Proceed? [Y/n] ");
179         io::stdout().flush().unwrap();
180         io::stdin().read_line(&mut buf).unwrap();
181         match buf.trim().to_lowercase().as_ref() {
182             // Proceed.
183             "" | "y" | "yes" => {}
184             "n" | "no" => show_error!("aborting as per your request"),
185             a => show_error!("invalid answer `{}`", a),
186         };
187     } else {
188         eprintln!("Running `{cmd:?}` to {text}.");
189     }
190 
191     if cmd.status().unwrap_or_else(|_| panic!("failed to execute {cmd:?}")).success().not() {
192         show_error!("failed to {}", text);
193     }
194 }
195 
196 // Computes the extra flags that need to be passed to cargo to make it behave like the current
197 // cargo invocation.
cargo_extra_flags() -> Vec<String>198 fn cargo_extra_flags() -> Vec<String> {
199     let mut flags = Vec::new();
200     // `-Zunstable-options` is required by `--config`.
201     flags.push("-Zunstable-options".to_string());
202 
203     // Forward `--config` flags.
204     let config_flag = "--config";
205     for arg in get_arg_flag_values(config_flag) {
206         flags.push(config_flag.to_string());
207         flags.push(arg);
208     }
209 
210     // Forward `--manifest-path`.
211     let manifest_flag = "--manifest-path";
212     if let Some(manifest) = get_arg_flag_value(manifest_flag) {
213         flags.push(manifest_flag.to_string());
214         flags.push(manifest);
215     }
216 
217     // Forwarding `--target-dir` would make sense, but `cargo metadata` does not support that flag.
218 
219     flags
220 }
221 
get_cargo_metadata() -> Metadata222 pub fn get_cargo_metadata() -> Metadata {
223     // This will honor the `CARGO` env var the same way our `cargo()` does.
224     MetadataCommand::new().no_deps().other_options(cargo_extra_flags()).exec().unwrap()
225 }
226 
227 /// Pulls all the crates in this workspace from the cargo metadata.
228 /// Workspace members are emitted like "miri 0.1.0 (path+file:///path/to/miri)"
229 /// Additionally, somewhere between cargo metadata and TyCtxt, '-' gets replaced with '_' so we
230 /// make that same transformation here.
local_crates(metadata: &Metadata) -> String231 pub fn local_crates(metadata: &Metadata) -> String {
232     assert!(!metadata.workspace_members.is_empty());
233     let mut local_crates = String::new();
234     for member in &metadata.workspace_members {
235         let name = member.repr.split(' ').next().unwrap();
236         let name = name.replace('-', "_");
237         local_crates.push_str(&name);
238         local_crates.push(',');
239     }
240     local_crates.pop(); // Remove the trailing ','
241 
242     local_crates
243 }
244 
env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)>245 fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> {
246     let mut envs = HashMap::new();
247     for (key, value) in std::env::vars() {
248         envs.insert(key, value);
249     }
250     for (key, value) in cmd.get_envs() {
251         if let Some(value) = value {
252             envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string());
253         } else {
254             envs.remove(&key.to_string_lossy().to_string());
255         }
256     }
257     let mut envs: Vec<_> = envs.into_iter().collect();
258     envs.sort();
259     envs
260 }
261 
262 /// Debug-print a command that is going to be run.
debug_cmd(prefix: &str, verbose: usize, cmd: &Command)263 pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
264     if verbose == 0 {
265         return;
266     }
267     // We only do a single `eprintln!` call to minimize concurrency interactions.
268     let mut out = prefix.to_string();
269     writeln!(out, " running command: env \\").unwrap();
270     if verbose > 1 {
271         // Print the full environment this will be called in.
272         for (key, value) in env_vars_from_cmd(cmd) {
273             writeln!(out, "{key}={value:?} \\").unwrap();
274         }
275     } else {
276         // Print only what has been changed for this `cmd`.
277         for (var, val) in cmd.get_envs() {
278             if let Some(val) = val {
279                 writeln!(out, "{}={val:?} \\", var.to_string_lossy()).unwrap();
280             } else {
281                 writeln!(out, "--unset={}", var.to_string_lossy()).unwrap();
282             }
283         }
284     }
285     write!(out, "{cmd:?}").unwrap();
286     eprintln!("{out}");
287 }
288