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