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