1 | use std::{ |
2 | collections::HashMap, |
3 | ffi::OsString, |
4 | path::{Path, PathBuf}, |
5 | process::Command, |
6 | sync::Mutex, |
7 | }; |
8 | |
9 | use crate::command_helpers::{run_output, CargoOutput}; |
10 | |
11 | /// Configuration used to represent an invocation of a C compiler. |
12 | /// |
13 | /// This can be used to figure out what compiler is in use, what the arguments |
14 | /// to it are, and what the environment variables look like for the compiler. |
15 | /// This can be used to further configure other build systems (e.g. forward |
16 | /// along CC and/or CFLAGS) or the `to_command` method can be used to run the |
17 | /// compiler itself. |
18 | #[derive (Clone, Debug)] |
19 | #[allow (missing_docs)] |
20 | pub struct Tool { |
21 | pub(crate) path: PathBuf, |
22 | pub(crate) cc_wrapper_path: Option<PathBuf>, |
23 | pub(crate) cc_wrapper_args: Vec<OsString>, |
24 | pub(crate) args: Vec<OsString>, |
25 | pub(crate) env: Vec<(OsString, OsString)>, |
26 | pub(crate) family: ToolFamily, |
27 | pub(crate) cuda: bool, |
28 | pub(crate) removed_args: Vec<OsString>, |
29 | pub(crate) has_internal_target_arg: bool, |
30 | } |
31 | |
32 | impl Tool { |
33 | pub(crate) fn new( |
34 | path: PathBuf, |
35 | cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, |
36 | cargo_output: &CargoOutput, |
37 | ) -> Self { |
38 | Self::with_features(path, None, false, cached_compiler_family, cargo_output) |
39 | } |
40 | |
41 | pub(crate) fn with_clang_driver( |
42 | path: PathBuf, |
43 | clang_driver: Option<&str>, |
44 | cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, |
45 | cargo_output: &CargoOutput, |
46 | ) -> Self { |
47 | Self::with_features( |
48 | path, |
49 | clang_driver, |
50 | false, |
51 | cached_compiler_family, |
52 | cargo_output, |
53 | ) |
54 | } |
55 | |
56 | /// Explicitly set the `ToolFamily`, skipping name-based detection. |
57 | pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self { |
58 | Self { |
59 | path, |
60 | cc_wrapper_path: None, |
61 | cc_wrapper_args: Vec::new(), |
62 | args: Vec::new(), |
63 | env: Vec::new(), |
64 | family, |
65 | cuda: false, |
66 | removed_args: Vec::new(), |
67 | has_internal_target_arg: false, |
68 | } |
69 | } |
70 | |
71 | pub(crate) fn with_features( |
72 | path: PathBuf, |
73 | clang_driver: Option<&str>, |
74 | cuda: bool, |
75 | cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, |
76 | cargo_output: &CargoOutput, |
77 | ) -> Self { |
78 | fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily { |
79 | let mut cmd = Command::new(path); |
80 | cmd.arg("--version" ); |
81 | |
82 | let stdout = match run_output( |
83 | &mut cmd, |
84 | &path.to_string_lossy(), |
85 | // tool detection issues should always be shown as warnings |
86 | cargo_output, |
87 | ) |
88 | .ok() |
89 | .and_then(|o| String::from_utf8(o).ok()) |
90 | { |
91 | Some(s) => s, |
92 | None => { |
93 | // --version failed. fallback to gnu |
94 | cargo_output.print_warning(&format_args!("Failed to run: {:?}" , cmd)); |
95 | return ToolFamily::Gnu; |
96 | } |
97 | }; |
98 | if stdout.contains("clang" ) { |
99 | ToolFamily::Clang |
100 | } else if stdout.contains("GCC" ) { |
101 | ToolFamily::Gnu |
102 | } else { |
103 | // --version doesn't include clang for GCC |
104 | cargo_output.print_warning(&format_args!( |
105 | "Compiler version doesn't include clang or GCC: {:?}" , |
106 | cmd |
107 | )); |
108 | ToolFamily::Gnu |
109 | } |
110 | } |
111 | let detect_family = |path: &Path| -> ToolFamily { |
112 | if let Some(family) = cached_compiler_family.lock().unwrap().get(path) { |
113 | return *family; |
114 | } |
115 | |
116 | let family = detect_family_inner(path, cargo_output); |
117 | cached_compiler_family |
118 | .lock() |
119 | .unwrap() |
120 | .insert(path.into(), family); |
121 | family |
122 | }; |
123 | |
124 | // Try to detect family of the tool from its name, falling back to Gnu. |
125 | let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { |
126 | if fname.contains("clang-cl" ) { |
127 | ToolFamily::Msvc { clang_cl: true } |
128 | } else if fname.ends_with("cl" ) || fname == "cl.exe" { |
129 | ToolFamily::Msvc { clang_cl: false } |
130 | } else if fname.contains("clang" ) { |
131 | match clang_driver { |
132 | Some("cl" ) => ToolFamily::Msvc { clang_cl: true }, |
133 | _ => ToolFamily::Clang, |
134 | } |
135 | } else { |
136 | detect_family(&path) |
137 | } |
138 | } else { |
139 | detect_family(&path) |
140 | }; |
141 | |
142 | Tool { |
143 | path, |
144 | cc_wrapper_path: None, |
145 | cc_wrapper_args: Vec::new(), |
146 | args: Vec::new(), |
147 | env: Vec::new(), |
148 | family, |
149 | cuda, |
150 | removed_args: Vec::new(), |
151 | has_internal_target_arg: false, |
152 | } |
153 | } |
154 | |
155 | /// Add an argument to be stripped from the final command arguments. |
156 | pub(crate) fn remove_arg(&mut self, flag: OsString) { |
157 | self.removed_args.push(flag); |
158 | } |
159 | |
160 | /// Push an "exotic" flag to the end of the compiler's arguments list. |
161 | /// |
162 | /// Nvidia compiler accepts only the most common compiler flags like `-D`, |
163 | /// `-I`, `-c`, etc. Options meant specifically for the underlying |
164 | /// host C++ compiler have to be prefixed with `-Xcompiler`. |
165 | /// [Another possible future application for this function is passing |
166 | /// clang-specific flags to clang-cl, which otherwise accepts only |
167 | /// MSVC-specific options.] |
168 | pub(crate) fn push_cc_arg(&mut self, flag: OsString) { |
169 | if self.cuda { |
170 | self.args.push("-Xcompiler" .into()); |
171 | } |
172 | self.args.push(flag); |
173 | } |
174 | |
175 | /// Checks if an argument or flag has already been specified or conflicts. |
176 | /// |
177 | /// Currently only checks optimization flags. |
178 | pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool { |
179 | let flag = flag.to_str().unwrap(); |
180 | let mut chars = flag.chars(); |
181 | |
182 | // Only duplicate check compiler flags |
183 | if self.is_like_msvc() { |
184 | if chars.next() != Some('/' ) { |
185 | return false; |
186 | } |
187 | } else if self.is_like_gnu() || self.is_like_clang() { |
188 | if chars.next() != Some('-' ) { |
189 | return false; |
190 | } |
191 | } |
192 | |
193 | // Check for existing optimization flags (-O, /O) |
194 | if chars.next() == Some('O' ) { |
195 | return self |
196 | .args() |
197 | .iter() |
198 | .any(|a| a.to_str().unwrap_or("" ).chars().nth(1) == Some('O' )); |
199 | } |
200 | |
201 | // TODO Check for existing -m..., -m...=..., /arch:... flags |
202 | false |
203 | } |
204 | |
205 | /// Don't push optimization arg if it conflicts with existing args. |
206 | pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) { |
207 | if self.is_duplicate_opt_arg(&flag) { |
208 | println!("Info: Ignoring duplicate arg {:?}" , &flag); |
209 | } else { |
210 | self.push_cc_arg(flag); |
211 | } |
212 | } |
213 | |
214 | /// Converts this compiler into a `Command` that's ready to be run. |
215 | /// |
216 | /// This is useful for when the compiler needs to be executed and the |
217 | /// command returned will already have the initial arguments and environment |
218 | /// variables configured. |
219 | pub fn to_command(&self) -> Command { |
220 | let mut cmd = match self.cc_wrapper_path { |
221 | Some(ref cc_wrapper_path) => { |
222 | let mut cmd = Command::new(cc_wrapper_path); |
223 | cmd.arg(&self.path); |
224 | cmd |
225 | } |
226 | None => Command::new(&self.path), |
227 | }; |
228 | cmd.args(&self.cc_wrapper_args); |
229 | |
230 | let value = self |
231 | .args |
232 | .iter() |
233 | .filter(|a| !self.removed_args.contains(a)) |
234 | .collect::<Vec<_>>(); |
235 | cmd.args(&value); |
236 | |
237 | for (k, v) in self.env.iter() { |
238 | cmd.env(k, v); |
239 | } |
240 | cmd |
241 | } |
242 | |
243 | /// Returns the path for this compiler. |
244 | /// |
245 | /// Note that this may not be a path to a file on the filesystem, e.g. "cc", |
246 | /// but rather something which will be resolved when a process is spawned. |
247 | pub fn path(&self) -> &Path { |
248 | &self.path |
249 | } |
250 | |
251 | /// Returns the default set of arguments to the compiler needed to produce |
252 | /// executables for the target this compiler generates. |
253 | pub fn args(&self) -> &[OsString] { |
254 | &self.args |
255 | } |
256 | |
257 | /// Returns the set of environment variables needed for this compiler to |
258 | /// operate. |
259 | /// |
260 | /// This is typically only used for MSVC compilers currently. |
261 | pub fn env(&self) -> &[(OsString, OsString)] { |
262 | &self.env |
263 | } |
264 | |
265 | /// Returns the compiler command in format of CC environment variable. |
266 | /// Or empty string if CC env was not present |
267 | /// |
268 | /// This is typically used by configure script |
269 | pub fn cc_env(&self) -> OsString { |
270 | match self.cc_wrapper_path { |
271 | Some(ref cc_wrapper_path) => { |
272 | let mut cc_env = cc_wrapper_path.as_os_str().to_owned(); |
273 | cc_env.push(" " ); |
274 | cc_env.push(self.path.to_path_buf().into_os_string()); |
275 | for arg in self.cc_wrapper_args.iter() { |
276 | cc_env.push(" " ); |
277 | cc_env.push(arg); |
278 | } |
279 | cc_env |
280 | } |
281 | None => OsString::from("" ), |
282 | } |
283 | } |
284 | |
285 | /// Returns the compiler flags in format of CFLAGS environment variable. |
286 | /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS |
287 | /// This is typically used by configure script |
288 | pub fn cflags_env(&self) -> OsString { |
289 | let mut flags = OsString::new(); |
290 | for (i, arg) in self.args.iter().enumerate() { |
291 | if i > 0 { |
292 | flags.push(" " ); |
293 | } |
294 | flags.push(arg); |
295 | } |
296 | flags |
297 | } |
298 | |
299 | /// Whether the tool is GNU Compiler Collection-like. |
300 | pub fn is_like_gnu(&self) -> bool { |
301 | self.family == ToolFamily::Gnu |
302 | } |
303 | |
304 | /// Whether the tool is Clang-like. |
305 | pub fn is_like_clang(&self) -> bool { |
306 | self.family == ToolFamily::Clang |
307 | } |
308 | |
309 | /// Whether the tool is AppleClang under .xctoolchain |
310 | #[cfg (target_vendor = "apple" )] |
311 | pub(crate) fn is_xctoolchain_clang(&self) -> bool { |
312 | let path = self.path.to_string_lossy(); |
313 | path.contains(".xctoolchain/" ) |
314 | } |
315 | #[cfg (not(target_vendor = "apple" ))] |
316 | pub(crate) fn is_xctoolchain_clang(&self) -> bool { |
317 | false |
318 | } |
319 | |
320 | /// Whether the tool is MSVC-like. |
321 | pub fn is_like_msvc(&self) -> bool { |
322 | match self.family { |
323 | ToolFamily::Msvc { .. } => true, |
324 | _ => false, |
325 | } |
326 | } |
327 | } |
328 | |
329 | /// Represents the family of tools this tool belongs to. |
330 | /// |
331 | /// Each family of tools differs in how and what arguments they accept. |
332 | /// |
333 | /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. |
334 | #[derive (Copy, Clone, Debug, PartialEq)] |
335 | pub enum ToolFamily { |
336 | /// Tool is GNU Compiler Collection-like. |
337 | Gnu, |
338 | /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags |
339 | /// and its cross-compilation approach is different. |
340 | Clang, |
341 | /// Tool is the MSVC cl.exe. |
342 | Msvc { clang_cl: bool }, |
343 | } |
344 | |
345 | impl ToolFamily { |
346 | /// What the flag to request debug info for this family of tools look like |
347 | pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) { |
348 | match *self { |
349 | ToolFamily::Msvc { .. } => { |
350 | cmd.push_cc_arg("-Z7" .into()); |
351 | } |
352 | ToolFamily::Gnu | ToolFamily::Clang => { |
353 | cmd.push_cc_arg( |
354 | dwarf_version |
355 | .map_or_else(|| "-g" .into(), |v| format!("-gdwarf- {}" , v)) |
356 | .into(), |
357 | ); |
358 | } |
359 | } |
360 | } |
361 | |
362 | /// What the flag to force frame pointers. |
363 | pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) { |
364 | match *self { |
365 | ToolFamily::Gnu | ToolFamily::Clang => { |
366 | cmd.push_cc_arg("-fno-omit-frame-pointer" .into()); |
367 | } |
368 | _ => (), |
369 | } |
370 | } |
371 | |
372 | /// What the flags to enable all warnings |
373 | pub(crate) fn warnings_flags(&self) -> &'static str { |
374 | match *self { |
375 | ToolFamily::Msvc { .. } => "-W4" , |
376 | ToolFamily::Gnu | ToolFamily::Clang => "-Wall" , |
377 | } |
378 | } |
379 | |
380 | /// What the flags to enable extra warnings |
381 | pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> { |
382 | match *self { |
383 | ToolFamily::Msvc { .. } => None, |
384 | ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra" ), |
385 | } |
386 | } |
387 | |
388 | /// What the flag to turn warning into errors |
389 | pub(crate) fn warnings_to_errors_flag(&self) -> &'static str { |
390 | match *self { |
391 | ToolFamily::Msvc { .. } => "-WX" , |
392 | ToolFamily::Gnu | ToolFamily::Clang => "-Werror" , |
393 | } |
394 | } |
395 | |
396 | pub(crate) fn verbose_stderr(&self) -> bool { |
397 | *self == ToolFamily::Clang |
398 | } |
399 | } |
400 | |