]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_codegen_ssa/src/back/command.rs
Rollup merge of #93753 - jeremyBanks:main-conflict, r=petrochenkov
[rust.git] / compiler / rustc_codegen_ssa / src / back / command.rs
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_span::symbol::Symbol;
11 use rustc_target::spec::LldFlavor;
12
13 #[derive(Clone)]
14 pub struct Command {
15     program: Program,
16     args: Vec<OsString>,
17     env: Vec<(OsString, OsString)>,
18     env_remove: Vec<OsString>,
19 }
20
21 #[derive(Clone)]
22 enum Program {
23     Normal(OsString),
24     CmdBatScript(OsString),
25     Lld(OsString, LldFlavor),
26 }
27
28 impl Command {
29     pub fn new<P: AsRef<OsStr>>(program: P) -> Command {
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()))
35     }
36
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
41     fn _new(program: Program) -> Command {
42         Command { program, args: Vec::new(), env: Vec::new(), env_remove: Vec::new() }
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
50     pub fn sym_arg(&mut self, arg: Symbol) -> &mut Command {
51         self.arg(arg.as_str());
52         self
53     }
54
55     pub fn args<I>(&mut self, args: I) -> &mut Command
56     where
57         I: IntoIterator<Item: AsRef<OsStr>>,
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
70     where
71         K: AsRef<OsStr>,
72         V: AsRef<OsStr>,
73     {
74         self._env(key.as_ref(), value.as_ref());
75         self
76     }
77
78     fn _env(&mut self, key: &OsStr, value: &OsStr) {
79         self.env.push((key.to_owned(), value.to_owned()));
80     }
81
82     pub fn env_remove<K>(&mut self, key: K) -> &mut Command
83     where
84         K: AsRef<OsStr>,
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
94     pub fn output(&mut self) -> io::Result<Output> {
95         self.command().output()
96     }
97
98     pub fn command(&self) -> process::Command {
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             }
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                 });
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                 }
120                 c
121             }
122         };
123         ret.args(&self.args);
124         ret.envs(self.env.clone());
125         for k in &self.env_remove {
126             ret.env_remove(k);
127         }
128         ret
129     }
130
131     // extensions
132
133     pub fn get_args(&self) -> &[OsString] {
134         &self.args
135     }
136
137     pub fn take_args(&mut self) -> Vec<OsString> {
138         mem::take(&mut self.args)
139     }
140
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) {
147             return false;
148         }
149
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 {
154             return false;
155         }
156
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         //
177         // [1]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
178         // [2]: https://devblogs.microsoft.com/oldnewthing/?p=41553
179
180         let estimated_command_line_len = self.args.iter().map(|a| a.len()).sum::<usize>();
181         estimated_command_line_len > 1024 * 6
182     }
183 }
184
185 impl fmt::Debug for Command {
186     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187         self.command().fmt(f)
188     }
189 }