blob: 2d84d67e3c85bd097a4dc8924005593711fac2bd [file] [log] [blame]
//! 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;
use syntax::symbol::Symbol;
#[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 sym_arg(&mut self, arg: Symbol) -> &mut Command {
self.arg(&arg.as_str());
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(match flavor {
LldFlavor::Wasm => "wasm",
LldFlavor::Ld => "gnu",
LldFlavor::Link => "link",
LldFlavor::Ld64 => "darwin",
});
c
}
};
ret.args(&self.args);
ret.envs(self.env.clone());
for k in &self.env_remove {
ret.env_remove(k);
}
return 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://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
// [2]: https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?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)
}
}