1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
//! A thin wrapper around `Command` in the standard library which allows us to
//! read the arguments that are built up.
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::io;
use std::mem;
use std::process::{self, Output};
use rustc_target::spec::LldFlavor;
#[derive(Clone)]
pub struct Command {
program: Program,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
env_remove: Vec<OsString>,
}
#[derive(Clone)]
enum Program {
Normal(OsString),
CmdBatScript(OsString),
Lld(OsString, LldFlavor),
}
impl Command {
pub fn new<P: AsRef<OsStr>>(program: P) -> Command {
Command::_new(Program::Normal(program.as_ref().to_owned()))
}
pub fn bat_script<P: AsRef<OsStr>>(program: P) -> Command {
Command::_new(Program::CmdBatScript(program.as_ref().to_owned()))
}
pub fn lld<P: AsRef<OsStr>>(program: P, flavor: LldFlavor) -> Command {
Command::_new(Program::Lld(program.as_ref().to_owned(), flavor))
}
fn _new(program: Program) -> Command {
Command { program, args: Vec::new(), env: Vec::new(), env_remove: Vec::new() }
}
pub fn arg<P: AsRef<OsStr>>(&mut self, arg: P) -> &mut Command {
self._arg(arg.as_ref());
self
}
pub fn args<I>(&mut self, args: I) -> &mut Command
where
I: IntoIterator<Item: AsRef<OsStr>>,
{
for arg in args {
self._arg(arg.as_ref());
}
self
}
fn _arg(&mut self, arg: &OsStr) {
self.args.push(arg.to_owned());
}
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Command
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self._env(key.as_ref(), value.as_ref());
self
}
fn _env(&mut self, key: &OsStr, value: &OsStr) {
self.env.push((key.to_owned(), value.to_owned()));
}
pub fn env_remove<K>(&mut self, key: K) -> &mut Command
where
K: AsRef<OsStr>,
{
self._env_remove(key.as_ref());
self
}
fn _env_remove(&mut self, key: &OsStr) {
self.env_remove.push(key.to_owned());
}
pub fn output(&mut self) -> io::Result<Output> {
self.command().output()
}
pub fn command(&self) -> process::Command {
let mut ret = match self.program {
Program::Normal(ref p) => process::Command::new(p),
Program::CmdBatScript(ref p) => {
let mut c = process::Command::new("cmd");
c.arg("/c").arg(p);
c
}
Program::Lld(ref p, flavor) => {
let mut c = process::Command::new(p);
c.arg("-flavor").arg(flavor.as_str());
if let LldFlavor::Wasm = flavor {
// LLVM expects host-specific formatting for @file
// arguments, but we always generate posix formatted files
// at this time. Indicate as such.
c.arg("--rsp-quoting=posix");
}
c
}
};
ret.args(&self.args);
ret.envs(self.env.clone());
for k in &self.env_remove {
ret.env_remove(k);
}
ret
}
// extensions
pub fn get_args(&self) -> &[OsString] {
&self.args
}
pub fn take_args(&mut self) -> Vec<OsString> {
mem::take(&mut self.args)
}
/// Returns a `true` if we're pretty sure that this'll blow OS spawn limits,
/// or `false` if we should attempt to spawn and see what the OS says.
pub fn very_likely_to_exceed_some_spawn_limit(&self) -> bool {
// We mostly only care about Windows in this method, on Unix the limits
// can be gargantuan anyway so we're pretty unlikely to hit them
if cfg!(unix) {
return false;
}
// Right now LLD doesn't support the `@` syntax of passing an argument
// through files, so regardless of the platform we try to go to the OS
// on this one.
if let Program::Lld(..) = self.program {
return false;
}
// Ok so on Windows to spawn a process is 32,768 characters in its
// command line [1]. Unfortunately we don't actually have access to that
// as it's calculated just before spawning. Instead we perform a
// poor-man's guess as to how long our command line will be. We're
// assuming here that we don't have to escape every character...
//
// Turns out though that `cmd.exe` has even smaller limits, 8192
// characters [2]. Linkers can often be batch scripts (for example
// Emscripten, Gecko's current build system) which means that we're
// running through batch scripts. These linkers often just forward
// arguments elsewhere (and maybe tack on more), so if we blow 8192
// bytes we'll typically cause them to blow as well.
//
// Basically as a result just perform an inflated estimate of what our
// command line will look like and test if it's > 8192 (we actually
// test against 6k to artificially inflate our estimate). If all else
// fails we'll fall back to the normal unix logic of testing the OS
// error code if we fail to spawn and automatically re-spawning the
// linker with smaller arguments.
//
// [1]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
// [2]: https://devblogs.microsoft.com/oldnewthing/?p=41553
let estimated_command_line_len = self.args.iter().map(|a| a.len()).sum::<usize>();
estimated_command_line_len > 1024 * 6
}
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.command().fmt(f)
}
}