1use std::{
2 collections::HashMap,
3 ffi::OsString,
4 path::{Path, PathBuf},
5 process::Command,
6 sync::Mutex,
7};
8
9use 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)]
20pub 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
32impl 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)]
335pub 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
345impl 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