1 | //! A library for build scripts to compile custom C code |
2 | //! |
3 | //! This library is intended to be used as a `build-dependencies` entry in |
4 | //! `Cargo.toml`: |
5 | //! |
6 | //! ```toml |
7 | //! [build-dependencies] |
8 | //! cc = "1.0" |
9 | //! ``` |
10 | //! |
11 | //! The purpose of this crate is to provide the utility functions necessary to |
12 | //! compile C code into a static archive which is then linked into a Rust crate. |
13 | //! Configuration is available through the `Build` struct. |
14 | //! |
15 | //! This crate will automatically detect situations such as cross compilation or |
16 | //! other environment variables set by Cargo and will build code appropriately. |
17 | //! |
18 | //! The crate is not limited to C code, it can accept any source code that can |
19 | //! be passed to a C or C++ compiler. As such, assembly files with extensions |
20 | //! `.s` (gcc/clang) and `.asm` (MSVC) can also be compiled. |
21 | //! |
22 | //! [`Build`]: struct.Build.html |
23 | //! |
24 | //! # Parallelism |
25 | //! |
26 | //! To parallelize computation, enable the `parallel` feature for the crate. |
27 | //! |
28 | //! ```toml |
29 | //! [build-dependencies] |
30 | //! cc = { version = "1.0", features = ["parallel"] } |
31 | //! ``` |
32 | //! To specify the max number of concurrent compilation jobs, set the `NUM_JOBS` |
33 | //! environment variable to the desired amount. |
34 | //! |
35 | //! Cargo will also set this environment variable when executed with the `-jN` flag. |
36 | //! |
37 | //! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can |
38 | //! also specify the build parallelism. |
39 | //! |
40 | //! # Examples |
41 | //! |
42 | //! Use the `Build` struct to compile `src/foo.c`: |
43 | //! |
44 | //! ```no_run |
45 | //! fn main() { |
46 | //! cc::Build::new() |
47 | //! .file("src/foo.c" ) |
48 | //! .define("FOO" , Some("bar" )) |
49 | //! .include("src" ) |
50 | //! .compile("foo" ); |
51 | //! } |
52 | //! ``` |
53 | |
54 | #![doc (html_root_url = "https://docs.rs/cc/1.0" )] |
55 | #![cfg_attr (test, deny(warnings))] |
56 | #![allow (deprecated)] |
57 | #![deny (missing_docs)] |
58 | |
59 | use std::collections::{hash_map, HashMap}; |
60 | use std::env; |
61 | use std::ffi::{OsStr, OsString}; |
62 | use std::fmt::{self, Display, Formatter}; |
63 | use std::fs; |
64 | use std::hash::Hasher; |
65 | use std::io::{self, BufRead, BufReader, Read, Write}; |
66 | use std::path::{Component, Path, PathBuf}; |
67 | use std::process::{Child, Command, Stdio}; |
68 | use std::sync::{Arc, Mutex}; |
69 | use std::thread::{self, JoinHandle}; |
70 | |
71 | // These modules are all glue to support reading the MSVC version from |
72 | // the registry and from COM interfaces |
73 | #[cfg (windows)] |
74 | mod registry; |
75 | #[cfg (windows)] |
76 | #[macro_use ] |
77 | mod winapi; |
78 | #[cfg (windows)] |
79 | mod com; |
80 | #[cfg (windows)] |
81 | mod setup_config; |
82 | #[cfg (windows)] |
83 | mod vs_instances; |
84 | |
85 | pub mod windows_registry; |
86 | |
87 | /// A builder for compilation of a native library. |
88 | /// |
89 | /// A `Build` is the main type of the `cc` crate and is used to control all the |
90 | /// various configuration options and such of a compile. You'll find more |
91 | /// documentation on each method itself. |
92 | #[derive (Clone, Debug)] |
93 | pub struct Build { |
94 | include_directories: Vec<PathBuf>, |
95 | definitions: Vec<(String, Option<String>)>, |
96 | objects: Vec<PathBuf>, |
97 | flags: Vec<String>, |
98 | flags_supported: Vec<String>, |
99 | known_flag_support_status: Arc<Mutex<HashMap<String, bool>>>, |
100 | ar_flags: Vec<String>, |
101 | asm_flags: Vec<String>, |
102 | no_default_flags: bool, |
103 | files: Vec<PathBuf>, |
104 | cpp: bool, |
105 | cpp_link_stdlib: Option<Option<String>>, |
106 | cpp_set_stdlib: Option<String>, |
107 | cuda: bool, |
108 | cudart: Option<String>, |
109 | target: Option<String>, |
110 | host: Option<String>, |
111 | out_dir: Option<PathBuf>, |
112 | opt_level: Option<String>, |
113 | debug: Option<bool>, |
114 | force_frame_pointer: Option<bool>, |
115 | env: Vec<(OsString, OsString)>, |
116 | compiler: Option<PathBuf>, |
117 | archiver: Option<PathBuf>, |
118 | ranlib: Option<PathBuf>, |
119 | cargo_metadata: bool, |
120 | link_lib_modifiers: Vec<String>, |
121 | pic: Option<bool>, |
122 | use_plt: Option<bool>, |
123 | static_crt: Option<bool>, |
124 | shared_flag: Option<bool>, |
125 | static_flag: Option<bool>, |
126 | warnings_into_errors: bool, |
127 | warnings: Option<bool>, |
128 | extra_warnings: Option<bool>, |
129 | env_cache: Arc<Mutex<HashMap<String, Option<String>>>>, |
130 | apple_sdk_root_cache: Arc<Mutex<HashMap<String, OsString>>>, |
131 | emit_rerun_if_env_changed: bool, |
132 | } |
133 | |
134 | /// Represents the types of errors that may occur while using cc-rs. |
135 | #[derive (Clone, Debug)] |
136 | enum ErrorKind { |
137 | /// Error occurred while performing I/O. |
138 | IOError, |
139 | /// Invalid architecture supplied. |
140 | ArchitectureInvalid, |
141 | /// Environment variable not found, with the var in question as extra info. |
142 | EnvVarNotFound, |
143 | /// Error occurred while using external tools (ie: invocation of compiler). |
144 | ToolExecError, |
145 | /// Error occurred due to missing external tools. |
146 | ToolNotFound, |
147 | /// One of the function arguments failed validation. |
148 | InvalidArgument, |
149 | } |
150 | |
151 | /// Represents an internal error that occurred, with an explanation. |
152 | #[derive (Clone, Debug)] |
153 | pub struct Error { |
154 | /// Describes the kind of error that occurred. |
155 | kind: ErrorKind, |
156 | /// More explanation of error that occurred. |
157 | message: String, |
158 | } |
159 | |
160 | impl Error { |
161 | fn new(kind: ErrorKind, message: &str) -> Error { |
162 | Error { |
163 | kind: kind, |
164 | message: message.to_owned(), |
165 | } |
166 | } |
167 | } |
168 | |
169 | impl From<io::Error> for Error { |
170 | fn from(e: io::Error) -> Error { |
171 | Error::new(kind:ErrorKind::IOError, &format!(" {}" , e)) |
172 | } |
173 | } |
174 | |
175 | impl Display for Error { |
176 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
177 | write!(f, " {:?}: {}" , self.kind, self.message) |
178 | } |
179 | } |
180 | |
181 | impl std::error::Error for Error {} |
182 | |
183 | /// Configuration used to represent an invocation of a C compiler. |
184 | /// |
185 | /// This can be used to figure out what compiler is in use, what the arguments |
186 | /// to it are, and what the environment variables look like for the compiler. |
187 | /// This can be used to further configure other build systems (e.g. forward |
188 | /// along CC and/or CFLAGS) or the `to_command` method can be used to run the |
189 | /// compiler itself. |
190 | #[derive (Clone, Debug)] |
191 | pub struct Tool { |
192 | path: PathBuf, |
193 | cc_wrapper_path: Option<PathBuf>, |
194 | cc_wrapper_args: Vec<OsString>, |
195 | args: Vec<OsString>, |
196 | env: Vec<(OsString, OsString)>, |
197 | family: ToolFamily, |
198 | cuda: bool, |
199 | removed_args: Vec<OsString>, |
200 | } |
201 | |
202 | /// Represents the family of tools this tool belongs to. |
203 | /// |
204 | /// Each family of tools differs in how and what arguments they accept. |
205 | /// |
206 | /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. |
207 | #[derive (Copy, Clone, Debug, PartialEq)] |
208 | enum ToolFamily { |
209 | /// Tool is GNU Compiler Collection-like. |
210 | Gnu, |
211 | /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags |
212 | /// and its cross-compilation approach is different. |
213 | Clang, |
214 | /// Tool is the MSVC cl.exe. |
215 | Msvc { clang_cl: bool }, |
216 | } |
217 | |
218 | impl ToolFamily { |
219 | /// What the flag to request debug info for this family of tools look like |
220 | fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) { |
221 | match *self { |
222 | ToolFamily::Msvc { .. } => { |
223 | cmd.push_cc_arg("-Z7" .into()); |
224 | } |
225 | ToolFamily::Gnu | ToolFamily::Clang => { |
226 | cmd.push_cc_arg( |
227 | dwarf_version |
228 | .map_or_else(|| "-g" .into(), |v| format!("-gdwarf- {}" , v)) |
229 | .into(), |
230 | ); |
231 | } |
232 | } |
233 | } |
234 | |
235 | /// What the flag to force frame pointers. |
236 | fn add_force_frame_pointer(&self, cmd: &mut Tool) { |
237 | match *self { |
238 | ToolFamily::Gnu | ToolFamily::Clang => { |
239 | cmd.push_cc_arg("-fno-omit-frame-pointer" .into()); |
240 | } |
241 | _ => (), |
242 | } |
243 | } |
244 | |
245 | /// What the flags to enable all warnings |
246 | fn warnings_flags(&self) -> &'static str { |
247 | match *self { |
248 | ToolFamily::Msvc { .. } => "-W4" , |
249 | ToolFamily::Gnu | ToolFamily::Clang => "-Wall" , |
250 | } |
251 | } |
252 | |
253 | /// What the flags to enable extra warnings |
254 | fn extra_warnings_flags(&self) -> Option<&'static str> { |
255 | match *self { |
256 | ToolFamily::Msvc { .. } => None, |
257 | ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra" ), |
258 | } |
259 | } |
260 | |
261 | /// What the flag to turn warning into errors |
262 | fn warnings_to_errors_flag(&self) -> &'static str { |
263 | match *self { |
264 | ToolFamily::Msvc { .. } => "-WX" , |
265 | ToolFamily::Gnu | ToolFamily::Clang => "-Werror" , |
266 | } |
267 | } |
268 | |
269 | fn verbose_stderr(&self) -> bool { |
270 | *self == ToolFamily::Clang |
271 | } |
272 | } |
273 | |
274 | /// Represents an object. |
275 | /// |
276 | /// This is a source file -> object file pair. |
277 | #[derive (Clone, Debug)] |
278 | struct Object { |
279 | src: PathBuf, |
280 | dst: PathBuf, |
281 | } |
282 | |
283 | impl Object { |
284 | /// Create a new source file -> object file pair. |
285 | fn new(src: PathBuf, dst: PathBuf) -> Object { |
286 | Object { src: src, dst: dst } |
287 | } |
288 | } |
289 | |
290 | impl Build { |
291 | /// Construct a new instance of a blank set of configuration. |
292 | /// |
293 | /// This builder is finished with the [`compile`] function. |
294 | /// |
295 | /// [`compile`]: struct.Build.html#method.compile |
296 | pub fn new() -> Build { |
297 | Build { |
298 | include_directories: Vec::new(), |
299 | definitions: Vec::new(), |
300 | objects: Vec::new(), |
301 | flags: Vec::new(), |
302 | flags_supported: Vec::new(), |
303 | known_flag_support_status: Arc::new(Mutex::new(HashMap::new())), |
304 | ar_flags: Vec::new(), |
305 | asm_flags: Vec::new(), |
306 | no_default_flags: false, |
307 | files: Vec::new(), |
308 | shared_flag: None, |
309 | static_flag: None, |
310 | cpp: false, |
311 | cpp_link_stdlib: None, |
312 | cpp_set_stdlib: None, |
313 | cuda: false, |
314 | cudart: None, |
315 | target: None, |
316 | host: None, |
317 | out_dir: None, |
318 | opt_level: None, |
319 | debug: None, |
320 | force_frame_pointer: None, |
321 | env: Vec::new(), |
322 | compiler: None, |
323 | archiver: None, |
324 | ranlib: None, |
325 | cargo_metadata: true, |
326 | link_lib_modifiers: Vec::new(), |
327 | pic: None, |
328 | use_plt: None, |
329 | static_crt: None, |
330 | warnings: None, |
331 | extra_warnings: None, |
332 | warnings_into_errors: false, |
333 | env_cache: Arc::new(Mutex::new(HashMap::new())), |
334 | apple_sdk_root_cache: Arc::new(Mutex::new(HashMap::new())), |
335 | emit_rerun_if_env_changed: true, |
336 | } |
337 | } |
338 | |
339 | /// Add a directory to the `-I` or include path for headers |
340 | /// |
341 | /// # Example |
342 | /// |
343 | /// ```no_run |
344 | /// use std::path::Path; |
345 | /// |
346 | /// let library_path = Path::new("/path/to/library" ); |
347 | /// |
348 | /// cc::Build::new() |
349 | /// .file("src/foo.c" ) |
350 | /// .include(library_path) |
351 | /// .include("src" ) |
352 | /// .compile("foo" ); |
353 | /// ``` |
354 | pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Build { |
355 | self.include_directories.push(dir.as_ref().to_path_buf()); |
356 | self |
357 | } |
358 | |
359 | /// Add multiple directories to the `-I` include path. |
360 | /// |
361 | /// # Example |
362 | /// |
363 | /// ```no_run |
364 | /// # use std::path::Path; |
365 | /// # let condition = true; |
366 | /// # |
367 | /// let mut extra_dir = None; |
368 | /// if condition { |
369 | /// extra_dir = Some(Path::new("/path/to" )); |
370 | /// } |
371 | /// |
372 | /// cc::Build::new() |
373 | /// .file("src/foo.c" ) |
374 | /// .includes(extra_dir) |
375 | /// .compile("foo" ); |
376 | /// ``` |
377 | pub fn includes<P>(&mut self, dirs: P) -> &mut Build |
378 | where |
379 | P: IntoIterator, |
380 | P::Item: AsRef<Path>, |
381 | { |
382 | for dir in dirs { |
383 | self.include(dir); |
384 | } |
385 | self |
386 | } |
387 | |
388 | /// Specify a `-D` variable with an optional value. |
389 | /// |
390 | /// # Example |
391 | /// |
392 | /// ```no_run |
393 | /// cc::Build::new() |
394 | /// .file("src/foo.c" ) |
395 | /// .define("FOO" , "BAR" ) |
396 | /// .define("BAZ" , None) |
397 | /// .compile("foo" ); |
398 | /// ``` |
399 | pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build { |
400 | self.definitions |
401 | .push((var.to_string(), val.into().map(|s| s.to_string()))); |
402 | self |
403 | } |
404 | |
405 | /// Add an arbitrary object file to link in |
406 | pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Build { |
407 | self.objects.push(obj.as_ref().to_path_buf()); |
408 | self |
409 | } |
410 | |
411 | /// Add an arbitrary flag to the invocation of the compiler |
412 | /// |
413 | /// # Example |
414 | /// |
415 | /// ```no_run |
416 | /// cc::Build::new() |
417 | /// .file("src/foo.c" ) |
418 | /// .flag("-ffunction-sections" ) |
419 | /// .compile("foo" ); |
420 | /// ``` |
421 | pub fn flag(&mut self, flag: &str) -> &mut Build { |
422 | self.flags.push(flag.to_string()); |
423 | self |
424 | } |
425 | |
426 | /// Add a flag to the invocation of the ar |
427 | /// |
428 | /// # Example |
429 | /// |
430 | /// ```no_run |
431 | /// cc::Build::new() |
432 | /// .file("src/foo.c" ) |
433 | /// .file("src/bar.c" ) |
434 | /// .ar_flag("/NODEFAULTLIB:libc.dll" ) |
435 | /// .compile("foo" ); |
436 | /// ``` |
437 | pub fn ar_flag(&mut self, flag: &str) -> &mut Build { |
438 | self.ar_flags.push(flag.to_string()); |
439 | self |
440 | } |
441 | |
442 | /// Add a flag that will only be used with assembly files. |
443 | /// |
444 | /// The flag will be applied to input files with either a `.s` or |
445 | /// `.asm` extension (case insensitive). |
446 | /// |
447 | /// # Example |
448 | /// |
449 | /// ```no_run |
450 | /// cc::Build::new() |
451 | /// .asm_flag("-Wa,-defsym,abc=1" ) |
452 | /// .file("src/foo.S" ) // The asm flag will be applied here |
453 | /// .file("src/bar.c" ) // The asm flag will not be applied here |
454 | /// .compile("foo" ); |
455 | /// ``` |
456 | pub fn asm_flag(&mut self, flag: &str) -> &mut Build { |
457 | self.asm_flags.push(flag.to_string()); |
458 | self |
459 | } |
460 | |
461 | fn ensure_check_file(&self) -> Result<PathBuf, Error> { |
462 | let out_dir = self.get_out_dir()?; |
463 | let src = if self.cuda { |
464 | assert!(self.cpp); |
465 | out_dir.join("flag_check.cu" ) |
466 | } else if self.cpp { |
467 | out_dir.join("flag_check.cpp" ) |
468 | } else { |
469 | out_dir.join("flag_check.c" ) |
470 | }; |
471 | |
472 | if !src.exists() { |
473 | let mut f = fs::File::create(&src)?; |
474 | write!(f, "int main(void) {{ return 0; }}" )?; |
475 | } |
476 | |
477 | Ok(src) |
478 | } |
479 | |
480 | /// Run the compiler to test if it accepts the given flag. |
481 | /// |
482 | /// For a convenience method for setting flags conditionally, |
483 | /// see `flag_if_supported()`. |
484 | /// |
485 | /// It may return error if it's unable to run the compiler with a test file |
486 | /// (e.g. the compiler is missing or a write to the `out_dir` failed). |
487 | /// |
488 | /// Note: Once computed, the result of this call is stored in the |
489 | /// `known_flag_support` field. If `is_flag_supported(flag)` |
490 | /// is called again, the result will be read from the hash table. |
491 | pub fn is_flag_supported(&self, flag: &str) -> Result<bool, Error> { |
492 | let mut known_status = self.known_flag_support_status.lock().unwrap(); |
493 | if let Some(is_supported) = known_status.get(flag).cloned() { |
494 | return Ok(is_supported); |
495 | } |
496 | |
497 | let out_dir = self.get_out_dir()?; |
498 | let src = self.ensure_check_file()?; |
499 | let obj = out_dir.join("flag_check" ); |
500 | let target = self.get_target()?; |
501 | let host = self.get_host()?; |
502 | let mut cfg = Build::new(); |
503 | cfg.flag(flag) |
504 | .target(&target) |
505 | .opt_level(0) |
506 | .host(&host) |
507 | .debug(false) |
508 | .cpp(self.cpp) |
509 | .cuda(self.cuda); |
510 | if let Some(ref c) = self.compiler { |
511 | cfg.compiler(c.clone()); |
512 | } |
513 | let mut compiler = cfg.try_get_compiler()?; |
514 | |
515 | // Clang uses stderr for verbose output, which yields a false positive |
516 | // result if the CFLAGS/CXXFLAGS include -v to aid in debugging. |
517 | if compiler.family.verbose_stderr() { |
518 | compiler.remove_arg("-v" .into()); |
519 | } |
520 | |
521 | let mut cmd = compiler.to_command(); |
522 | let is_arm = target.contains("aarch64" ) || target.contains("arm" ); |
523 | let clang = compiler.family == ToolFamily::Clang; |
524 | command_add_output_file( |
525 | &mut cmd, |
526 | &obj, |
527 | self.cuda, |
528 | target.contains("msvc" ), |
529 | clang, |
530 | false, |
531 | is_arm, |
532 | ); |
533 | |
534 | // We need to explicitly tell msvc not to link and create an exe |
535 | // in the root directory of the crate |
536 | if target.contains("msvc" ) && !self.cuda { |
537 | cmd.arg("-c" ); |
538 | } |
539 | |
540 | cmd.arg(&src); |
541 | |
542 | let output = cmd.output()?; |
543 | let is_supported = output.status.success() && output.stderr.is_empty(); |
544 | |
545 | known_status.insert(flag.to_owned(), is_supported); |
546 | Ok(is_supported) |
547 | } |
548 | |
549 | /// Add an arbitrary flag to the invocation of the compiler if it supports it |
550 | /// |
551 | /// # Example |
552 | /// |
553 | /// ```no_run |
554 | /// cc::Build::new() |
555 | /// .file("src/foo.c" ) |
556 | /// .flag_if_supported("-Wlogical-op" ) // only supported by GCC |
557 | /// .flag_if_supported("-Wunreachable-code" ) // only supported by clang |
558 | /// .compile("foo" ); |
559 | /// ``` |
560 | pub fn flag_if_supported(&mut self, flag: &str) -> &mut Build { |
561 | self.flags_supported.push(flag.to_string()); |
562 | self |
563 | } |
564 | |
565 | /// Set the `-shared` flag. |
566 | /// |
567 | /// When enabled, the compiler will produce a shared object which can |
568 | /// then be linked with other objects to form an executable. |
569 | /// |
570 | /// # Example |
571 | /// |
572 | /// ```no_run |
573 | /// cc::Build::new() |
574 | /// .file("src/foo.c" ) |
575 | /// .shared_flag(true) |
576 | /// .compile("libfoo.so" ); |
577 | /// ``` |
578 | pub fn shared_flag(&mut self, shared_flag: bool) -> &mut Build { |
579 | self.shared_flag = Some(shared_flag); |
580 | self |
581 | } |
582 | |
583 | /// Set the `-static` flag. |
584 | /// |
585 | /// When enabled on systems that support dynamic linking, this prevents |
586 | /// linking with the shared libraries. |
587 | /// |
588 | /// # Example |
589 | /// |
590 | /// ```no_run |
591 | /// cc::Build::new() |
592 | /// .file("src/foo.c" ) |
593 | /// .shared_flag(true) |
594 | /// .static_flag(true) |
595 | /// .compile("foo" ); |
596 | /// ``` |
597 | pub fn static_flag(&mut self, static_flag: bool) -> &mut Build { |
598 | self.static_flag = Some(static_flag); |
599 | self |
600 | } |
601 | |
602 | /// Disables the generation of default compiler flags. The default compiler |
603 | /// flags may cause conflicts in some cross compiling scenarios. |
604 | /// |
605 | /// Setting the `CRATE_CC_NO_DEFAULTS` environment variable has the same |
606 | /// effect as setting this to `true`. The presence of the environment |
607 | /// variable and the value of `no_default_flags` will be OR'd together. |
608 | pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Build { |
609 | self.no_default_flags = no_default_flags; |
610 | self |
611 | } |
612 | |
613 | /// Add a file which will be compiled |
614 | pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build { |
615 | self.files.push(p.as_ref().to_path_buf()); |
616 | self |
617 | } |
618 | |
619 | /// Add files which will be compiled |
620 | pub fn files<P>(&mut self, p: P) -> &mut Build |
621 | where |
622 | P: IntoIterator, |
623 | P::Item: AsRef<Path>, |
624 | { |
625 | for file in p.into_iter() { |
626 | self.file(file); |
627 | } |
628 | self |
629 | } |
630 | |
631 | /// Set C++ support. |
632 | /// |
633 | /// The other `cpp_*` options will only become active if this is set to |
634 | /// `true`. |
635 | pub fn cpp(&mut self, cpp: bool) -> &mut Build { |
636 | self.cpp = cpp; |
637 | self |
638 | } |
639 | |
640 | /// Set CUDA C++ support. |
641 | /// |
642 | /// Enabling CUDA will pass the detected C/C++ toolchain as an argument to |
643 | /// the CUDA compiler, NVCC. NVCC itself accepts some limited GNU-like args; |
644 | /// any other arguments for the C/C++ toolchain will be redirected using |
645 | /// "-Xcompiler" flags. |
646 | /// |
647 | /// If enabled, this also implicitly enables C++ support. |
648 | pub fn cuda(&mut self, cuda: bool) -> &mut Build { |
649 | self.cuda = cuda; |
650 | if cuda { |
651 | self.cpp = true; |
652 | self.cudart = Some("static" .to_string()); |
653 | } |
654 | self |
655 | } |
656 | |
657 | /// Link CUDA run-time. |
658 | /// |
659 | /// This option mimics the `--cudart` NVCC command-line option. Just like |
660 | /// the original it accepts `{none|shared|static}`, with default being |
661 | /// `static`. The method has to be invoked after `.cuda(true)`, or not |
662 | /// at all, if the default is right for the project. |
663 | pub fn cudart(&mut self, cudart: &str) -> &mut Build { |
664 | if self.cuda { |
665 | self.cudart = Some(cudart.to_string()); |
666 | } |
667 | self |
668 | } |
669 | |
670 | /// Set warnings into errors flag. |
671 | /// |
672 | /// Disabled by default. |
673 | /// |
674 | /// Warning: turning warnings into errors only make sense |
675 | /// if you are a developer of the crate using cc-rs. |
676 | /// Some warnings only appear on some architecture or |
677 | /// specific version of the compiler. Any user of this crate, |
678 | /// or any other crate depending on it, could fail during |
679 | /// compile time. |
680 | /// |
681 | /// # Example |
682 | /// |
683 | /// ```no_run |
684 | /// cc::Build::new() |
685 | /// .file("src/foo.c" ) |
686 | /// .warnings_into_errors(true) |
687 | /// .compile("libfoo.a" ); |
688 | /// ``` |
689 | pub fn warnings_into_errors(&mut self, warnings_into_errors: bool) -> &mut Build { |
690 | self.warnings_into_errors = warnings_into_errors; |
691 | self |
692 | } |
693 | |
694 | /// Set warnings flags. |
695 | /// |
696 | /// Adds some flags: |
697 | /// - "-Wall" for MSVC. |
698 | /// - "-Wall", "-Wextra" for GNU and Clang. |
699 | /// |
700 | /// Enabled by default. |
701 | /// |
702 | /// # Example |
703 | /// |
704 | /// ```no_run |
705 | /// cc::Build::new() |
706 | /// .file("src/foo.c" ) |
707 | /// .warnings(false) |
708 | /// .compile("libfoo.a" ); |
709 | /// ``` |
710 | pub fn warnings(&mut self, warnings: bool) -> &mut Build { |
711 | self.warnings = Some(warnings); |
712 | self.extra_warnings = Some(warnings); |
713 | self |
714 | } |
715 | |
716 | /// Set extra warnings flags. |
717 | /// |
718 | /// Adds some flags: |
719 | /// - nothing for MSVC. |
720 | /// - "-Wextra" for GNU and Clang. |
721 | /// |
722 | /// Enabled by default. |
723 | /// |
724 | /// # Example |
725 | /// |
726 | /// ```no_run |
727 | /// // Disables -Wextra, -Wall remains enabled: |
728 | /// cc::Build::new() |
729 | /// .file("src/foo.c" ) |
730 | /// .extra_warnings(false) |
731 | /// .compile("libfoo.a" ); |
732 | /// ``` |
733 | pub fn extra_warnings(&mut self, warnings: bool) -> &mut Build { |
734 | self.extra_warnings = Some(warnings); |
735 | self |
736 | } |
737 | |
738 | /// Set the standard library to link against when compiling with C++ |
739 | /// support. |
740 | /// |
741 | /// See [`get_cpp_link_stdlib`](cc::Build::get_cpp_link_stdlib) documentation |
742 | /// for the default value. |
743 | /// If the `CXXSTDLIB` environment variable is set, its value will |
744 | /// override the default value, but not the value explicitly set by calling |
745 | /// this function. |
746 | /// |
747 | /// A value of `None` indicates that no automatic linking should happen, |
748 | /// otherwise cargo will link against the specified library. |
749 | /// |
750 | /// The given library name must not contain the `lib` prefix. |
751 | /// |
752 | /// Common values: |
753 | /// - `stdc++` for GNU |
754 | /// - `c++` for Clang |
755 | /// - `c++_shared` or `c++_static` for Android |
756 | /// |
757 | /// # Example |
758 | /// |
759 | /// ```no_run |
760 | /// cc::Build::new() |
761 | /// .file("src/foo.c" ) |
762 | /// .shared_flag(true) |
763 | /// .cpp_link_stdlib("stdc++" ) |
764 | /// .compile("libfoo.so" ); |
765 | /// ``` |
766 | pub fn cpp_link_stdlib<'a, V: Into<Option<&'a str>>>( |
767 | &mut self, |
768 | cpp_link_stdlib: V, |
769 | ) -> &mut Build { |
770 | self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(|s| s.into())); |
771 | self |
772 | } |
773 | |
774 | /// Force the C++ compiler to use the specified standard library. |
775 | /// |
776 | /// Setting this option will automatically set `cpp_link_stdlib` to the same |
777 | /// value. |
778 | /// |
779 | /// The default value of this option is always `None`. |
780 | /// |
781 | /// This option has no effect when compiling for a Visual Studio based |
782 | /// target. |
783 | /// |
784 | /// This option sets the `-stdlib` flag, which is only supported by some |
785 | /// compilers (clang, icc) but not by others (gcc). The library will not |
786 | /// detect which compiler is used, as such it is the responsibility of the |
787 | /// caller to ensure that this option is only used in conjunction with a |
788 | /// compiler which supports the `-stdlib` flag. |
789 | /// |
790 | /// A value of `None` indicates that no specific C++ standard library should |
791 | /// be used, otherwise `-stdlib` is added to the compile invocation. |
792 | /// |
793 | /// The given library name must not contain the `lib` prefix. |
794 | /// |
795 | /// Common values: |
796 | /// - `stdc++` for GNU |
797 | /// - `c++` for Clang |
798 | /// |
799 | /// # Example |
800 | /// |
801 | /// ```no_run |
802 | /// cc::Build::new() |
803 | /// .file("src/foo.c" ) |
804 | /// .cpp_set_stdlib("c++" ) |
805 | /// .compile("libfoo.a" ); |
806 | /// ``` |
807 | pub fn cpp_set_stdlib<'a, V: Into<Option<&'a str>>>( |
808 | &mut self, |
809 | cpp_set_stdlib: V, |
810 | ) -> &mut Build { |
811 | let cpp_set_stdlib = cpp_set_stdlib.into(); |
812 | self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into()); |
813 | self.cpp_link_stdlib(cpp_set_stdlib); |
814 | self |
815 | } |
816 | |
817 | /// Configures the target this configuration will be compiling for. |
818 | /// |
819 | /// This option is automatically scraped from the `TARGET` environment |
820 | /// variable by build scripts, so it's not required to call this function. |
821 | /// |
822 | /// # Example |
823 | /// |
824 | /// ```no_run |
825 | /// cc::Build::new() |
826 | /// .file("src/foo.c" ) |
827 | /// .target("aarch64-linux-android" ) |
828 | /// .compile("foo" ); |
829 | /// ``` |
830 | pub fn target(&mut self, target: &str) -> &mut Build { |
831 | self.target = Some(target.to_string()); |
832 | self |
833 | } |
834 | |
835 | /// Configures the host assumed by this configuration. |
836 | /// |
837 | /// This option is automatically scraped from the `HOST` environment |
838 | /// variable by build scripts, so it's not required to call this function. |
839 | /// |
840 | /// # Example |
841 | /// |
842 | /// ```no_run |
843 | /// cc::Build::new() |
844 | /// .file("src/foo.c" ) |
845 | /// .host("arm-linux-gnueabihf" ) |
846 | /// .compile("foo" ); |
847 | /// ``` |
848 | pub fn host(&mut self, host: &str) -> &mut Build { |
849 | self.host = Some(host.to_string()); |
850 | self |
851 | } |
852 | |
853 | /// Configures the optimization level of the generated object files. |
854 | /// |
855 | /// This option is automatically scraped from the `OPT_LEVEL` environment |
856 | /// variable by build scripts, so it's not required to call this function. |
857 | pub fn opt_level(&mut self, opt_level: u32) -> &mut Build { |
858 | self.opt_level = Some(opt_level.to_string()); |
859 | self |
860 | } |
861 | |
862 | /// Configures the optimization level of the generated object files. |
863 | /// |
864 | /// This option is automatically scraped from the `OPT_LEVEL` environment |
865 | /// variable by build scripts, so it's not required to call this function. |
866 | pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Build { |
867 | self.opt_level = Some(opt_level.to_string()); |
868 | self |
869 | } |
870 | |
871 | /// Configures whether the compiler will emit debug information when |
872 | /// generating object files. |
873 | /// |
874 | /// This option is automatically scraped from the `DEBUG` environment |
875 | /// variable by build scripts, so it's not required to call this function. |
876 | pub fn debug(&mut self, debug: bool) -> &mut Build { |
877 | self.debug = Some(debug); |
878 | self |
879 | } |
880 | |
881 | /// Configures whether the compiler will emit instructions to store |
882 | /// frame pointers during codegen. |
883 | /// |
884 | /// This option is automatically enabled when debug information is emitted. |
885 | /// Otherwise the target platform compiler's default will be used. |
886 | /// You can use this option to force a specific setting. |
887 | pub fn force_frame_pointer(&mut self, force: bool) -> &mut Build { |
888 | self.force_frame_pointer = Some(force); |
889 | self |
890 | } |
891 | |
892 | /// Configures the output directory where all object files and static |
893 | /// libraries will be located. |
894 | /// |
895 | /// This option is automatically scraped from the `OUT_DIR` environment |
896 | /// variable by build scripts, so it's not required to call this function. |
897 | pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Build { |
898 | self.out_dir = Some(out_dir.as_ref().to_owned()); |
899 | self |
900 | } |
901 | |
902 | /// Configures the compiler to be used to produce output. |
903 | /// |
904 | /// This option is automatically determined from the target platform or a |
905 | /// number of environment variables, so it's not required to call this |
906 | /// function. |
907 | pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Build { |
908 | self.compiler = Some(compiler.as_ref().to_owned()); |
909 | self |
910 | } |
911 | |
912 | /// Configures the tool used to assemble archives. |
913 | /// |
914 | /// This option is automatically determined from the target platform or a |
915 | /// number of environment variables, so it's not required to call this |
916 | /// function. |
917 | pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Build { |
918 | self.archiver = Some(archiver.as_ref().to_owned()); |
919 | self |
920 | } |
921 | |
922 | /// Configures the tool used to index archives. |
923 | /// |
924 | /// This option is automatically determined from the target platform or a |
925 | /// number of environment variables, so it's not required to call this |
926 | /// function. |
927 | pub fn ranlib<P: AsRef<Path>>(&mut self, ranlib: P) -> &mut Build { |
928 | self.ranlib = Some(ranlib.as_ref().to_owned()); |
929 | self |
930 | } |
931 | |
932 | /// Define whether metadata should be emitted for cargo allowing it to |
933 | /// automatically link the binary. Defaults to `true`. |
934 | /// |
935 | /// The emitted metadata is: |
936 | /// |
937 | /// - `rustc-link-lib=static=`*compiled lib* |
938 | /// - `rustc-link-search=native=`*target folder* |
939 | /// - When target is MSVC, the ATL-MFC libs are added via `rustc-link-search=native=` |
940 | /// - When C++ is enabled, the C++ stdlib is added via `rustc-link-lib` |
941 | /// - If `emit_rerun_if_env_changed` is not `false`, `rerun-if-env-changed=`*env* |
942 | /// |
943 | pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Build { |
944 | self.cargo_metadata = cargo_metadata; |
945 | self |
946 | } |
947 | |
948 | /// Adds a native library modifier that will be added to the |
949 | /// `rustc-link-lib=static:MODIFIERS=LIBRARY_NAME` metadata line |
950 | /// emitted for cargo if `cargo_metadata` is enabled. |
951 | /// See https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library |
952 | /// for the list of modifiers accepted by rustc. |
953 | pub fn link_lib_modifier(&mut self, link_lib_modifier: &str) -> &mut Build { |
954 | self.link_lib_modifiers.push(link_lib_modifier.to_string()); |
955 | self |
956 | } |
957 | |
958 | /// Configures whether the compiler will emit position independent code. |
959 | /// |
960 | /// This option defaults to `false` for `windows-gnu` and bare metal targets and |
961 | /// to `true` for all other targets. |
962 | pub fn pic(&mut self, pic: bool) -> &mut Build { |
963 | self.pic = Some(pic); |
964 | self |
965 | } |
966 | |
967 | /// Configures whether the Procedure Linkage Table is used for indirect |
968 | /// calls into shared libraries. |
969 | /// |
970 | /// The PLT is used to provide features like lazy binding, but introduces |
971 | /// a small performance loss due to extra pointer indirection. Setting |
972 | /// `use_plt` to `false` can provide a small performance increase. |
973 | /// |
974 | /// Note that skipping the PLT requires a recent version of GCC/Clang. |
975 | /// |
976 | /// This only applies to ELF targets. It has no effect on other platforms. |
977 | pub fn use_plt(&mut self, use_plt: bool) -> &mut Build { |
978 | self.use_plt = Some(use_plt); |
979 | self |
980 | } |
981 | |
982 | /// Define whether metadata should be emitted for cargo to detect environment |
983 | /// changes that should trigger a rebuild. |
984 | /// |
985 | /// This has no effect if the `cargo_metadata` option is `false`. |
986 | /// |
987 | /// This option defaults to `true`. |
988 | pub fn emit_rerun_if_env_changed(&mut self, emit_rerun_if_env_changed: bool) -> &mut Build { |
989 | self.emit_rerun_if_env_changed = emit_rerun_if_env_changed; |
990 | self |
991 | } |
992 | |
993 | /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. |
994 | /// |
995 | /// This option defaults to `false`, and affect only msvc targets. |
996 | pub fn static_crt(&mut self, static_crt: bool) -> &mut Build { |
997 | self.static_crt = Some(static_crt); |
998 | self |
999 | } |
1000 | |
1001 | #[doc (hidden)] |
1002 | pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build |
1003 | where |
1004 | A: AsRef<OsStr>, |
1005 | B: AsRef<OsStr>, |
1006 | { |
1007 | self.env |
1008 | .push((a.as_ref().to_owned(), b.as_ref().to_owned())); |
1009 | self |
1010 | } |
1011 | |
1012 | /// Run the compiler, generating the file `output` |
1013 | /// |
1014 | /// This will return a result instead of panicing; see compile() for the complete description. |
1015 | pub fn try_compile(&self, output: &str) -> Result<(), Error> { |
1016 | let mut output_components = Path::new(output).components(); |
1017 | match (output_components.next(), output_components.next()) { |
1018 | (Some(Component::Normal(_)), None) => {} |
1019 | _ => { |
1020 | return Err(Error::new( |
1021 | ErrorKind::InvalidArgument, |
1022 | "argument of `compile` must be a single normal path component" , |
1023 | )); |
1024 | } |
1025 | } |
1026 | |
1027 | let (lib_name, gnu_lib_name) = if output.starts_with("lib" ) && output.ends_with(".a" ) { |
1028 | (&output[3..output.len() - 2], output.to_owned()) |
1029 | } else { |
1030 | let mut gnu = String::with_capacity(5 + output.len()); |
1031 | gnu.push_str("lib" ); |
1032 | gnu.push_str(&output); |
1033 | gnu.push_str(".a" ); |
1034 | (output, gnu) |
1035 | }; |
1036 | let dst = self.get_out_dir()?; |
1037 | |
1038 | let mut objects = Vec::new(); |
1039 | for file in self.files.iter() { |
1040 | let obj = if file.has_root() { |
1041 | // If `file` is an absolute path, prefix the `basename` |
1042 | // with the `dirname`'s hash to ensure name uniqueness. |
1043 | let basename = file |
1044 | .file_name() |
1045 | .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "file_name() failure" ))? |
1046 | .to_string_lossy(); |
1047 | let dirname = file |
1048 | .parent() |
1049 | .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "parent() failure" ))? |
1050 | .to_string_lossy(); |
1051 | let mut hasher = hash_map::DefaultHasher::new(); |
1052 | hasher.write(dirname.to_string().as_bytes()); |
1053 | dst.join(format!(" {:016x}- {}" , hasher.finish(), basename)) |
1054 | .with_extension("o" ) |
1055 | } else { |
1056 | dst.join(file).with_extension("o" ) |
1057 | }; |
1058 | let obj = if !obj.starts_with(&dst) { |
1059 | dst.join(obj.file_name().ok_or_else(|| { |
1060 | Error::new(ErrorKind::IOError, "Getting object file details failed." ) |
1061 | })?) |
1062 | } else { |
1063 | obj |
1064 | }; |
1065 | |
1066 | match obj.parent() { |
1067 | Some(s) => fs::create_dir_all(s)?, |
1068 | None => { |
1069 | return Err(Error::new( |
1070 | ErrorKind::IOError, |
1071 | "Getting object file details failed." , |
1072 | )); |
1073 | } |
1074 | }; |
1075 | |
1076 | objects.push(Object::new(file.to_path_buf(), obj)); |
1077 | } |
1078 | self.compile_objects(&objects)?; |
1079 | self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; |
1080 | |
1081 | if self.get_target()?.contains("msvc" ) { |
1082 | let compiler = self.get_base_compiler()?; |
1083 | let atlmfc_lib = compiler |
1084 | .env() |
1085 | .iter() |
1086 | .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB" )) |
1087 | .and_then(|&(_, ref lib_paths)| { |
1088 | env::split_paths(lib_paths).find(|path| { |
1089 | let sub = Path::new("atlmfc/lib" ); |
1090 | path.ends_with(sub) || path.parent().map_or(false, |p| p.ends_with(sub)) |
1091 | }) |
1092 | }); |
1093 | |
1094 | if let Some(atlmfc_lib) = atlmfc_lib { |
1095 | self.print(&format!( |
1096 | "cargo:rustc-link-search=native= {}" , |
1097 | atlmfc_lib.display() |
1098 | )); |
1099 | } |
1100 | } |
1101 | |
1102 | if self.link_lib_modifiers.is_empty() { |
1103 | self.print(&format!("cargo:rustc-link-lib=static= {}" , lib_name)); |
1104 | } else { |
1105 | let m = self.link_lib_modifiers.join("," ); |
1106 | self.print(&format!("cargo:rustc-link-lib=static: {}= {}" , m, lib_name)); |
1107 | } |
1108 | self.print(&format!("cargo:rustc-link-search=native= {}" , dst.display())); |
1109 | |
1110 | // Add specific C++ libraries, if enabled. |
1111 | if self.cpp { |
1112 | if let Some(stdlib) = self.get_cpp_link_stdlib()? { |
1113 | self.print(&format!("cargo:rustc-link-lib= {}" , stdlib)); |
1114 | } |
1115 | } |
1116 | |
1117 | let cudart = match &self.cudart { |
1118 | Some(opt) => opt.as_str(), // {none|shared|static} |
1119 | None => "none" , |
1120 | }; |
1121 | if cudart != "none" { |
1122 | if let Some(nvcc) = which(&self.get_compiler().path) { |
1123 | // Try to figure out the -L search path. If it fails, |
1124 | // it's on user to specify one by passing it through |
1125 | // RUSTFLAGS environment variable. |
1126 | let mut libtst = false; |
1127 | let mut libdir = nvcc; |
1128 | libdir.pop(); // remove 'nvcc' |
1129 | libdir.push(".." ); |
1130 | let target_arch = env::var("CARGO_CFG_TARGET_ARCH" ).unwrap(); |
1131 | if cfg!(target_os = "linux" ) { |
1132 | libdir.push("targets" ); |
1133 | libdir.push(target_arch.to_owned() + "-linux" ); |
1134 | libdir.push("lib" ); |
1135 | libtst = true; |
1136 | } else if cfg!(target_env = "msvc" ) { |
1137 | libdir.push("lib" ); |
1138 | match target_arch.as_str() { |
1139 | "x86_64" => { |
1140 | libdir.push("x64" ); |
1141 | libtst = true; |
1142 | } |
1143 | "x86" => { |
1144 | libdir.push("Win32" ); |
1145 | libtst = true; |
1146 | } |
1147 | _ => libtst = false, |
1148 | } |
1149 | } |
1150 | if libtst && libdir.is_dir() { |
1151 | println!( |
1152 | "cargo:rustc-link-search=native= {}" , |
1153 | libdir.to_str().unwrap() |
1154 | ); |
1155 | } |
1156 | |
1157 | // And now the -l flag. |
1158 | let lib = match cudart { |
1159 | "shared" => "cudart" , |
1160 | "static" => "cudart_static" , |
1161 | bad => panic!("unsupported cudart option: {}" , bad), |
1162 | }; |
1163 | println!("cargo:rustc-link-lib= {}" , lib); |
1164 | } |
1165 | } |
1166 | |
1167 | Ok(()) |
1168 | } |
1169 | |
1170 | /// Run the compiler, generating the file `output` |
1171 | /// |
1172 | /// # Library name |
1173 | /// |
1174 | /// The `output` string argument determines the file name for the compiled |
1175 | /// library. The Rust compiler will create an assembly named "lib"+output+".a". |
1176 | /// MSVC will create a file named output+".lib". |
1177 | /// |
1178 | /// The choice of `output` is close to arbitrary, but: |
1179 | /// |
1180 | /// - must be nonempty, |
1181 | /// - must not contain a path separator (`/`), |
1182 | /// - must be unique across all `compile` invocations made by the same build |
1183 | /// script. |
1184 | /// |
1185 | /// If your build script compiles a single source file, the base name of |
1186 | /// that source file would usually be reasonable: |
1187 | /// |
1188 | /// ```no_run |
1189 | /// cc::Build::new().file("blobstore.c" ).compile("blobstore" ); |
1190 | /// ``` |
1191 | /// |
1192 | /// Compiling multiple source files, some people use their crate's name, or |
1193 | /// their crate's name + "-cc". |
1194 | /// |
1195 | /// Otherwise, please use your imagination. |
1196 | /// |
1197 | /// For backwards compatibility, if `output` starts with "lib" *and* ends |
1198 | /// with ".a", a second "lib" prefix and ".a" suffix do not get added on, |
1199 | /// but this usage is deprecated; please omit `lib` and `.a` in the argument |
1200 | /// that you pass. |
1201 | /// |
1202 | /// # Panics |
1203 | /// |
1204 | /// Panics if `output` is not formatted correctly or if one of the underlying |
1205 | /// compiler commands fails. It can also panic if it fails reading file names |
1206 | /// or creating directories. |
1207 | pub fn compile(&self, output: &str) { |
1208 | if let Err(e) = self.try_compile(output) { |
1209 | fail(&e.message); |
1210 | } |
1211 | } |
1212 | |
1213 | #[cfg (feature = "parallel" )] |
1214 | fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> { |
1215 | use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; |
1216 | use std::sync::Once; |
1217 | |
1218 | // Limit our parallelism globally with a jobserver. Start off by |
1219 | // releasing our own token for this process so we can have a bit of an |
1220 | // easier to write loop below. If this fails, though, then we're likely |
1221 | // on Windows with the main implicit token, so we just have a bit extra |
1222 | // parallelism for a bit and don't reacquire later. |
1223 | let server = jobserver(); |
1224 | let reacquire = server.release_raw().is_ok(); |
1225 | |
1226 | // When compiling objects in parallel we do a few dirty tricks to speed |
1227 | // things up: |
1228 | // |
1229 | // * First is that we use the `jobserver` crate to limit the parallelism |
1230 | // of this build script. The `jobserver` crate will use a jobserver |
1231 | // configured by Cargo for build scripts to ensure that parallelism is |
1232 | // coordinated across C compilations and Rust compilations. Before we |
1233 | // compile anything we make sure to wait until we acquire a token. |
1234 | // |
1235 | // Note that this jobserver is cached globally so we only used one per |
1236 | // process and only worry about creating it once. |
1237 | // |
1238 | // * Next we use a raw `thread::spawn` per thread to actually compile |
1239 | // objects in parallel. We only actually spawn a thread after we've |
1240 | // acquired a token to perform some work |
1241 | // |
1242 | // * Finally though we want to keep the dependencies of this crate |
1243 | // pretty light, so we avoid using a safe abstraction like `rayon` and |
1244 | // instead rely on some bits of `unsafe` code. We know that this stack |
1245 | // frame persists while everything is compiling so we use all the |
1246 | // stack-allocated objects without cloning/reallocating. We use a |
1247 | // transmute to `State` with a `'static` lifetime to persist |
1248 | // everything we need across the boundary, and the join-on-drop |
1249 | // semantics of `JoinOnDrop` should ensure that our stack frame is |
1250 | // alive while threads are alive. |
1251 | // |
1252 | // With all that in mind we compile all objects in a loop here, after we |
1253 | // acquire the appropriate tokens, Once all objects have been compiled |
1254 | // we join on all the threads and propagate the results of compilation. |
1255 | // |
1256 | // Note that as a slight optimization we try to break out as soon as |
1257 | // possible as soon as any compilation fails to ensure that errors get |
1258 | // out to the user as fast as possible. |
1259 | let error = AtomicBool::new(false); |
1260 | let mut threads = Vec::new(); |
1261 | for obj in objs { |
1262 | if error.load(SeqCst) { |
1263 | break; |
1264 | } |
1265 | let token = server.acquire()?; |
1266 | let state = State { |
1267 | build: self, |
1268 | obj, |
1269 | error: &error, |
1270 | }; |
1271 | let state = unsafe { std::mem::transmute::<State, State<'static>>(state) }; |
1272 | let thread = thread::spawn(|| { |
1273 | let state: State<'me> = state; // erase the `'static` lifetime |
1274 | let result = state.build.compile_object(state.obj); |
1275 | if result.is_err() { |
1276 | state.error.store(true, SeqCst); |
1277 | } |
1278 | drop(token); // make sure our jobserver token is released after the compile |
1279 | return result; |
1280 | }); |
1281 | threads.push(JoinOnDrop(Some(thread))); |
1282 | } |
1283 | |
1284 | for mut thread in threads { |
1285 | if let Some(thread) = thread.0.take() { |
1286 | thread.join().expect("thread should not panic" )?; |
1287 | } |
1288 | } |
1289 | |
1290 | // Reacquire our process's token before we proceed, which we released |
1291 | // before entering the loop above. |
1292 | if reacquire { |
1293 | server.acquire_raw()?; |
1294 | } |
1295 | |
1296 | return Ok(()); |
1297 | |
1298 | /// Shared state from the parent thread to the child thread. This |
1299 | /// package of pointers is temporarily transmuted to a `'static` |
1300 | /// lifetime to cross the thread boundary and then once the thread is |
1301 | /// running we erase the `'static` to go back to an anonymous lifetime. |
1302 | struct State<'a> { |
1303 | build: &'a Build, |
1304 | obj: &'a Object, |
1305 | error: &'a AtomicBool, |
1306 | } |
1307 | |
1308 | /// Returns a suitable `jobserver::Client` used to coordinate |
1309 | /// parallelism between build scripts. |
1310 | fn jobserver() -> &'static jobserver::Client { |
1311 | static INIT: Once = Once::new(); |
1312 | static mut JOBSERVER: Option<jobserver::Client> = None; |
1313 | |
1314 | fn _assert_sync<T: Sync>() {} |
1315 | _assert_sync::<jobserver::Client>(); |
1316 | |
1317 | unsafe { |
1318 | INIT.call_once(|| { |
1319 | let server = default_jobserver(); |
1320 | JOBSERVER = Some(server); |
1321 | }); |
1322 | JOBSERVER.as_ref().unwrap() |
1323 | } |
1324 | } |
1325 | |
1326 | unsafe fn default_jobserver() -> jobserver::Client { |
1327 | // Try to use the environmental jobserver which Cargo typically |
1328 | // initializes for us... |
1329 | if let Some(client) = jobserver::Client::from_env() { |
1330 | return client; |
1331 | } |
1332 | |
1333 | // ... but if that fails for whatever reason select something |
1334 | // reasonable and crate a new jobserver. Use `NUM_JOBS` if set (it's |
1335 | // configured by Cargo) and otherwise just fall back to a |
1336 | // semi-reasonable number. Note that we could use `num_cpus` here |
1337 | // but it's an extra dependency that will almost never be used, so |
1338 | // it's generally not too worth it. |
1339 | let mut parallelism = 4; |
1340 | if let Ok(amt) = env::var("NUM_JOBS" ) { |
1341 | if let Ok(amt) = amt.parse() { |
1342 | parallelism = amt; |
1343 | } |
1344 | } |
1345 | |
1346 | // If we create our own jobserver then be sure to reserve one token |
1347 | // for ourselves. |
1348 | let client = jobserver::Client::new(parallelism).expect("failed to create jobserver" ); |
1349 | client.acquire_raw().expect("failed to acquire initial" ); |
1350 | return client; |
1351 | } |
1352 | |
1353 | struct JoinOnDrop(Option<thread::JoinHandle<Result<(), Error>>>); |
1354 | |
1355 | impl Drop for JoinOnDrop { |
1356 | fn drop(&mut self) { |
1357 | if let Some(thread) = self.0.take() { |
1358 | drop(thread.join()); |
1359 | } |
1360 | } |
1361 | } |
1362 | } |
1363 | |
1364 | #[cfg (not(feature = "parallel" ))] |
1365 | fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { |
1366 | for obj in objs { |
1367 | self.compile_object(obj)?; |
1368 | } |
1369 | Ok(()) |
1370 | } |
1371 | |
1372 | fn compile_object(&self, obj: &Object) -> Result<(), Error> { |
1373 | let asm_ext = AsmFileExt::from_path(&obj.src); |
1374 | let is_asm = asm_ext.is_some(); |
1375 | let target = self.get_target()?; |
1376 | let msvc = target.contains("msvc" ); |
1377 | let compiler = self.try_get_compiler()?; |
1378 | let clang = compiler.family == ToolFamily::Clang; |
1379 | |
1380 | let (mut cmd, name) = if msvc && asm_ext == Some(AsmFileExt::DotAsm) { |
1381 | self.msvc_macro_assembler()? |
1382 | } else { |
1383 | let mut cmd = compiler.to_command(); |
1384 | for &(ref a, ref b) in self.env.iter() { |
1385 | cmd.env(a, b); |
1386 | } |
1387 | ( |
1388 | cmd, |
1389 | compiler |
1390 | .path |
1391 | .file_name() |
1392 | .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path." ))? |
1393 | .to_string_lossy() |
1394 | .into_owned(), |
1395 | ) |
1396 | }; |
1397 | let is_arm = target.contains("aarch64" ) || target.contains("arm" ); |
1398 | command_add_output_file(&mut cmd, &obj.dst, self.cuda, msvc, clang, is_asm, is_arm); |
1399 | // armasm and armasm64 don't requrie -c option |
1400 | if !msvc || !is_asm || !is_arm { |
1401 | cmd.arg("-c" ); |
1402 | } |
1403 | if self.cuda && self.cuda_file_count() > 1 { |
1404 | cmd.arg("--device-c" ); |
1405 | } |
1406 | if is_asm { |
1407 | cmd.args(&self.asm_flags); |
1408 | } |
1409 | if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_asm { |
1410 | // #513: For `clang-cl`, separate flags/options from the input file. |
1411 | // When cross-compiling macOS -> Windows, this avoids interpreting |
1412 | // common `/Users/...` paths as the `/U` flag and triggering |
1413 | // `-Wslash-u-filename` warning. |
1414 | cmd.arg("--" ); |
1415 | } |
1416 | cmd.arg(&obj.src); |
1417 | if cfg!(target_os = "macos" ) { |
1418 | self.fix_env_for_apple_os(&mut cmd)?; |
1419 | } |
1420 | |
1421 | run(&mut cmd, &name)?; |
1422 | Ok(()) |
1423 | } |
1424 | |
1425 | /// This will return a result instead of panicing; see expand() for the complete description. |
1426 | pub fn try_expand(&self) -> Result<Vec<u8>, Error> { |
1427 | let compiler = self.try_get_compiler()?; |
1428 | let mut cmd = compiler.to_command(); |
1429 | for &(ref a, ref b) in self.env.iter() { |
1430 | cmd.env(a, b); |
1431 | } |
1432 | cmd.arg("-E" ); |
1433 | |
1434 | assert!( |
1435 | self.files.len() <= 1, |
1436 | "Expand may only be called for a single file" |
1437 | ); |
1438 | |
1439 | for file in self.files.iter() { |
1440 | cmd.arg(file); |
1441 | } |
1442 | |
1443 | let name = compiler |
1444 | .path |
1445 | .file_name() |
1446 | .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path." ))? |
1447 | .to_string_lossy() |
1448 | .into_owned(); |
1449 | |
1450 | Ok(run_output(&mut cmd, &name)?) |
1451 | } |
1452 | |
1453 | /// Run the compiler, returning the macro-expanded version of the input files. |
1454 | /// |
1455 | /// This is only relevant for C and C++ files. |
1456 | /// |
1457 | /// # Panics |
1458 | /// Panics if more than one file is present in the config, or if compiler |
1459 | /// path has an invalid file name. |
1460 | /// |
1461 | /// # Example |
1462 | /// ```no_run |
1463 | /// let out = cc::Build::new().file("src/foo.c" ).expand(); |
1464 | /// ``` |
1465 | pub fn expand(&self) -> Vec<u8> { |
1466 | match self.try_expand() { |
1467 | Err(e) => fail(&e.message), |
1468 | Ok(v) => v, |
1469 | } |
1470 | } |
1471 | |
1472 | /// Get the compiler that's in use for this configuration. |
1473 | /// |
1474 | /// This function will return a `Tool` which represents the culmination |
1475 | /// of this configuration at a snapshot in time. The returned compiler can |
1476 | /// be inspected (e.g. the path, arguments, environment) to forward along to |
1477 | /// other tools, or the `to_command` method can be used to invoke the |
1478 | /// compiler itself. |
1479 | /// |
1480 | /// This method will take into account all configuration such as debug |
1481 | /// information, optimization level, include directories, defines, etc. |
1482 | /// Additionally, the compiler binary in use follows the standard |
1483 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
1484 | /// environment variables (a number of which are inspected here), and then |
1485 | /// falling back to the default configuration. |
1486 | /// |
1487 | /// # Panics |
1488 | /// |
1489 | /// Panics if an error occurred while determining the architecture. |
1490 | pub fn get_compiler(&self) -> Tool { |
1491 | match self.try_get_compiler() { |
1492 | Ok(tool) => tool, |
1493 | Err(e) => fail(&e.message), |
1494 | } |
1495 | } |
1496 | |
1497 | /// Get the compiler that's in use for this configuration. |
1498 | /// |
1499 | /// This will return a result instead of panicing; see get_compiler() for the complete description. |
1500 | pub fn try_get_compiler(&self) -> Result<Tool, Error> { |
1501 | let opt_level = self.get_opt_level()?; |
1502 | let target = self.get_target()?; |
1503 | |
1504 | let mut cmd = self.get_base_compiler()?; |
1505 | let envflags = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }); |
1506 | |
1507 | // Disable default flag generation via `no_default_flags` or environment variable |
1508 | let no_defaults = self.no_default_flags || self.getenv("CRATE_CC_NO_DEFAULTS" ).is_some(); |
1509 | |
1510 | if !no_defaults { |
1511 | self.add_default_flags(&mut cmd, &target, &opt_level)?; |
1512 | } else { |
1513 | println!("Info: default compiler flags are disabled" ); |
1514 | } |
1515 | |
1516 | for arg in envflags { |
1517 | cmd.push_cc_arg(arg.into()); |
1518 | } |
1519 | |
1520 | for directory in self.include_directories.iter() { |
1521 | cmd.args.push("-I" .into()); |
1522 | cmd.args.push(directory.into()); |
1523 | } |
1524 | |
1525 | // If warnings and/or extra_warnings haven't been explicitly set, |
1526 | // then we set them only if the environment doesn't already have |
1527 | // CFLAGS/CXXFLAGS, since those variables presumably already contain |
1528 | // the desired set of warnings flags. |
1529 | |
1530 | if self |
1531 | .warnings |
1532 | .unwrap_or(if self.has_flags() { false } else { true }) |
1533 | { |
1534 | let wflags = cmd.family.warnings_flags().into(); |
1535 | cmd.push_cc_arg(wflags); |
1536 | } |
1537 | |
1538 | if self |
1539 | .extra_warnings |
1540 | .unwrap_or(if self.has_flags() { false } else { true }) |
1541 | { |
1542 | if let Some(wflags) = cmd.family.extra_warnings_flags() { |
1543 | cmd.push_cc_arg(wflags.into()); |
1544 | } |
1545 | } |
1546 | |
1547 | for flag in self.flags.iter() { |
1548 | cmd.args.push(flag.into()); |
1549 | } |
1550 | |
1551 | for flag in self.flags_supported.iter() { |
1552 | if self.is_flag_supported(flag).unwrap_or(false) { |
1553 | cmd.push_cc_arg(flag.into()); |
1554 | } |
1555 | } |
1556 | |
1557 | for &(ref key, ref value) in self.definitions.iter() { |
1558 | if let Some(ref value) = *value { |
1559 | cmd.args.push(format!("-D {}= {}" , key, value).into()); |
1560 | } else { |
1561 | cmd.args.push(format!("-D {}" , key).into()); |
1562 | } |
1563 | } |
1564 | |
1565 | if self.warnings_into_errors { |
1566 | let warnings_to_errors_flag = cmd.family.warnings_to_errors_flag().into(); |
1567 | cmd.push_cc_arg(warnings_to_errors_flag); |
1568 | } |
1569 | |
1570 | Ok(cmd) |
1571 | } |
1572 | |
1573 | fn add_default_flags( |
1574 | &self, |
1575 | cmd: &mut Tool, |
1576 | target: &str, |
1577 | opt_level: &str, |
1578 | ) -> Result<(), Error> { |
1579 | // Non-target flags |
1580 | // If the flag is not conditioned on target variable, it belongs here :) |
1581 | match cmd.family { |
1582 | ToolFamily::Msvc { .. } => { |
1583 | cmd.push_cc_arg("-nologo" .into()); |
1584 | |
1585 | let crt_flag = match self.static_crt { |
1586 | Some(true) => "-MT" , |
1587 | Some(false) => "-MD" , |
1588 | None => { |
1589 | let features = self |
1590 | .getenv("CARGO_CFG_TARGET_FEATURE" ) |
1591 | .unwrap_or(String::new()); |
1592 | if features.contains("crt-static" ) { |
1593 | "-MT" |
1594 | } else { |
1595 | "-MD" |
1596 | } |
1597 | } |
1598 | }; |
1599 | cmd.push_cc_arg(crt_flag.into()); |
1600 | |
1601 | match &opt_level[..] { |
1602 | // Msvc uses /O1 to enable all optimizations that minimize code size. |
1603 | "z" | "s" | "1" => cmd.push_opt_unless_duplicate("-O1" .into()), |
1604 | // -O3 is a valid value for gcc and clang compilers, but not msvc. Cap to /O2. |
1605 | "2" | "3" => cmd.push_opt_unless_duplicate("-O2" .into()), |
1606 | _ => {} |
1607 | } |
1608 | } |
1609 | ToolFamily::Gnu | ToolFamily::Clang => { |
1610 | // arm-linux-androideabi-gcc 4.8 shipped with Android NDK does |
1611 | // not support '-Oz' |
1612 | if opt_level == "z" && cmd.family != ToolFamily::Clang { |
1613 | cmd.push_opt_unless_duplicate("-Os" .into()); |
1614 | } else { |
1615 | cmd.push_opt_unless_duplicate(format!("-O {}" , opt_level).into()); |
1616 | } |
1617 | |
1618 | if cmd.family == ToolFamily::Clang && target.contains("android" ) { |
1619 | // For compatibility with code that doesn't use pre-defined `__ANDROID__` macro. |
1620 | // If compiler used via ndk-build or cmake (officially supported build methods) |
1621 | // this macros is defined. |
1622 | // See https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/build/cmake/android.toolchain.cmake#456 |
1623 | // https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/build/core/build-binary.mk#141 |
1624 | cmd.push_opt_unless_duplicate("-DANDROID" .into()); |
1625 | } |
1626 | |
1627 | if !target.contains("apple-ios" ) && !target.contains("apple-watchos" ) { |
1628 | cmd.push_cc_arg("-ffunction-sections" .into()); |
1629 | cmd.push_cc_arg("-fdata-sections" .into()); |
1630 | } |
1631 | // Disable generation of PIC on bare-metal for now: rust-lld doesn't support this yet |
1632 | if self.pic.unwrap_or( |
1633 | !target.contains("windows" ) |
1634 | && !target.contains("-none-" ) |
1635 | && !target.contains("uefi" ), |
1636 | ) { |
1637 | cmd.push_cc_arg("-fPIC" .into()); |
1638 | // PLT only applies if code is compiled with PIC support, |
1639 | // and only for ELF targets. |
1640 | if target.contains("linux" ) && !self.use_plt.unwrap_or(true) { |
1641 | cmd.push_cc_arg("-fno-plt" .into()); |
1642 | } |
1643 | } |
1644 | } |
1645 | } |
1646 | |
1647 | if self.get_debug() { |
1648 | if self.cuda { |
1649 | // NVCC debug flag |
1650 | cmd.args.push("-G" .into()); |
1651 | } |
1652 | let family = cmd.family; |
1653 | family.add_debug_flags(cmd, self.get_dwarf_version()); |
1654 | } |
1655 | |
1656 | if self.get_force_frame_pointer() { |
1657 | let family = cmd.family; |
1658 | family.add_force_frame_pointer(cmd); |
1659 | } |
1660 | |
1661 | // Target flags |
1662 | match cmd.family { |
1663 | ToolFamily::Clang => { |
1664 | if !(target.contains("android" ) |
1665 | && android_clang_compiler_uses_target_arg_internally(&cmd.path)) |
1666 | { |
1667 | if target.contains("darwin" ) { |
1668 | if let Some(arch) = |
1669 | map_darwin_target_from_rust_to_compiler_architecture(target) |
1670 | { |
1671 | cmd.args |
1672 | .push(format!("--target= {}-apple-darwin" , arch).into()); |
1673 | } |
1674 | } else if target.contains("macabi" ) { |
1675 | if let Some(arch) = |
1676 | map_darwin_target_from_rust_to_compiler_architecture(target) |
1677 | { |
1678 | cmd.args |
1679 | .push(format!("--target= {}-apple-ios-macabi" , arch).into()); |
1680 | } |
1681 | } else if target.contains("ios-sim" ) { |
1682 | if let Some(arch) = |
1683 | map_darwin_target_from_rust_to_compiler_architecture(target) |
1684 | { |
1685 | let deployment_target = env::var("IPHONEOS_DEPLOYMENT_TARGET" ) |
1686 | .unwrap_or_else(|_| "7.0" .into()); |
1687 | cmd.args.push( |
1688 | format!( |
1689 | "--target= {}-apple-ios {}-simulator" , |
1690 | arch, deployment_target |
1691 | ) |
1692 | .into(), |
1693 | ); |
1694 | } |
1695 | } else if target.contains("watchos-sim" ) { |
1696 | if let Some(arch) = |
1697 | map_darwin_target_from_rust_to_compiler_architecture(target) |
1698 | { |
1699 | let deployment_target = env::var("WATCHOS_DEPLOYMENT_TARGET" ) |
1700 | .unwrap_or_else(|_| "5.0" .into()); |
1701 | cmd.args.push( |
1702 | format!( |
1703 | "--target= {}-apple-watchos {}-simulator" , |
1704 | arch, deployment_target |
1705 | ) |
1706 | .into(), |
1707 | ); |
1708 | } |
1709 | } else if target.starts_with("riscv64gc-" ) { |
1710 | cmd.args.push( |
1711 | format!("--target= {}" , target.replace("riscv64gc" , "riscv64" )).into(), |
1712 | ); |
1713 | } else if target.starts_with("riscv32gc-" ) { |
1714 | cmd.args.push( |
1715 | format!("--target= {}" , target.replace("riscv32gc" , "riscv32" )).into(), |
1716 | ); |
1717 | } else if target.contains("uefi" ) { |
1718 | if target.contains("x86_64" ) { |
1719 | cmd.args.push("--target=x86_64-unknown-windows-gnu" .into()); |
1720 | } else if target.contains("i686" ) { |
1721 | cmd.args.push("--target=i686-unknown-windows-gnu" .into()) |
1722 | } else if target.contains("aarch64" ) { |
1723 | cmd.args.push("--target=aarch64-unknown-windows-gnu" .into()) |
1724 | } |
1725 | } else { |
1726 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
1727 | } |
1728 | } |
1729 | } |
1730 | ToolFamily::Msvc { clang_cl } => { |
1731 | // This is an undocumented flag from MSVC but helps with making |
1732 | // builds more reproducible by avoiding putting timestamps into |
1733 | // files. |
1734 | cmd.push_cc_arg("-Brepro" .into()); |
1735 | |
1736 | if clang_cl { |
1737 | if target.contains("x86_64" ) { |
1738 | cmd.push_cc_arg("-m64" .into()); |
1739 | } else if target.contains("86" ) { |
1740 | cmd.push_cc_arg("-m32" .into()); |
1741 | cmd.push_cc_arg("-arch:IA32" .into()); |
1742 | } else { |
1743 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
1744 | } |
1745 | } else { |
1746 | if target.contains("i586" ) { |
1747 | cmd.push_cc_arg("-arch:IA32" .into()); |
1748 | } |
1749 | } |
1750 | |
1751 | // There is a check in corecrt.h that will generate a |
1752 | // compilation error if |
1753 | // _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE is |
1754 | // not defined to 1. The check was added in Windows |
1755 | // 8 days because only store apps were allowed on ARM. |
1756 | // This changed with the release of Windows 10 IoT Core. |
1757 | // The check will be going away in future versions of |
1758 | // the SDK, but for all released versions of the |
1759 | // Windows SDK it is required. |
1760 | if target.contains("arm" ) || target.contains("thumb" ) { |
1761 | cmd.args |
1762 | .push("-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1" .into()); |
1763 | } |
1764 | } |
1765 | ToolFamily::Gnu => { |
1766 | if target.contains("i686" ) || target.contains("i586" ) { |
1767 | cmd.args.push("-m32" .into()); |
1768 | } else if target == "x86_64-unknown-linux-gnux32" { |
1769 | cmd.args.push("-mx32" .into()); |
1770 | } else if target.contains("x86_64" ) || target.contains("powerpc64" ) { |
1771 | cmd.args.push("-m64" .into()); |
1772 | } |
1773 | |
1774 | if target.contains("darwin" ) { |
1775 | if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) |
1776 | { |
1777 | cmd.args.push("-arch" .into()); |
1778 | cmd.args.push(arch.into()); |
1779 | } |
1780 | } |
1781 | |
1782 | if target.contains("-kmc-solid_" ) { |
1783 | cmd.args.push("-finput-charset=utf-8" .into()); |
1784 | } |
1785 | |
1786 | if self.static_flag.is_none() { |
1787 | let features = self |
1788 | .getenv("CARGO_CFG_TARGET_FEATURE" ) |
1789 | .unwrap_or(String::new()); |
1790 | if features.contains("crt-static" ) { |
1791 | cmd.args.push("-static" .into()); |
1792 | } |
1793 | } |
1794 | |
1795 | // armv7 targets get to use armv7 instructions |
1796 | if (target.starts_with("armv7" ) || target.starts_with("thumbv7" )) |
1797 | && (target.contains("-linux-" ) || target.contains("-kmc-solid_" )) |
1798 | { |
1799 | cmd.args.push("-march=armv7-a" .into()); |
1800 | |
1801 | if target.ends_with("eabihf" ) { |
1802 | // lowest common denominator FPU |
1803 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
1804 | } |
1805 | } |
1806 | |
1807 | // (x86 Android doesn't say "eabi") |
1808 | if target.contains("-androideabi" ) && target.contains("v7" ) { |
1809 | // -march=armv7-a handled above |
1810 | cmd.args.push("-mthumb" .into()); |
1811 | if !target.contains("neon" ) { |
1812 | // On android we can guarantee some extra float instructions |
1813 | // (specified in the android spec online) |
1814 | // NEON guarantees even more; see below. |
1815 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
1816 | } |
1817 | cmd.args.push("-mfloat-abi=softfp" .into()); |
1818 | } |
1819 | |
1820 | if target.contains("neon" ) { |
1821 | cmd.args.push("-mfpu=neon-vfpv4" .into()); |
1822 | } |
1823 | |
1824 | if target.starts_with("armv4t-unknown-linux-" ) { |
1825 | cmd.args.push("-march=armv4t" .into()); |
1826 | cmd.args.push("-marm" .into()); |
1827 | cmd.args.push("-mfloat-abi=soft" .into()); |
1828 | } |
1829 | |
1830 | if target.starts_with("armv5te-unknown-linux-" ) { |
1831 | cmd.args.push("-march=armv5te" .into()); |
1832 | cmd.args.push("-marm" .into()); |
1833 | cmd.args.push("-mfloat-abi=soft" .into()); |
1834 | } |
1835 | |
1836 | // For us arm == armv6 by default |
1837 | if target.starts_with("arm-unknown-linux-" ) { |
1838 | cmd.args.push("-march=armv6" .into()); |
1839 | cmd.args.push("-marm" .into()); |
1840 | if target.ends_with("hf" ) { |
1841 | cmd.args.push("-mfpu=vfp" .into()); |
1842 | } else { |
1843 | cmd.args.push("-mfloat-abi=soft" .into()); |
1844 | } |
1845 | } |
1846 | |
1847 | // We can guarantee some settings for FRC |
1848 | if target.starts_with("arm-frc-" ) { |
1849 | cmd.args.push("-march=armv7-a" .into()); |
1850 | cmd.args.push("-mcpu=cortex-a9" .into()); |
1851 | cmd.args.push("-mfpu=vfpv3" .into()); |
1852 | cmd.args.push("-mfloat-abi=softfp" .into()); |
1853 | cmd.args.push("-marm" .into()); |
1854 | } |
1855 | |
1856 | // Turn codegen down on i586 to avoid some instructions. |
1857 | if target.starts_with("i586-unknown-linux-" ) { |
1858 | cmd.args.push("-march=pentium" .into()); |
1859 | } |
1860 | |
1861 | // Set codegen level for i686 correctly |
1862 | if target.starts_with("i686-unknown-linux-" ) { |
1863 | cmd.args.push("-march=i686" .into()); |
1864 | } |
1865 | |
1866 | // Looks like `musl-gcc` makes it hard for `-m32` to make its way |
1867 | // all the way to the linker, so we need to actually instruct the |
1868 | // linker that we're generating 32-bit executables as well. This'll |
1869 | // typically only be used for build scripts which transitively use |
1870 | // these flags that try to compile executables. |
1871 | if target == "i686-unknown-linux-musl" || target == "i586-unknown-linux-musl" { |
1872 | cmd.args.push("-Wl,-melf_i386" .into()); |
1873 | } |
1874 | |
1875 | if target.starts_with("thumb" ) { |
1876 | cmd.args.push("-mthumb" .into()); |
1877 | |
1878 | if target.ends_with("eabihf" ) { |
1879 | cmd.args.push("-mfloat-abi=hard" .into()) |
1880 | } |
1881 | } |
1882 | if target.starts_with("thumbv6m" ) { |
1883 | cmd.args.push("-march=armv6s-m" .into()); |
1884 | } |
1885 | if target.starts_with("thumbv7em" ) { |
1886 | cmd.args.push("-march=armv7e-m" .into()); |
1887 | |
1888 | if target.ends_with("eabihf" ) { |
1889 | cmd.args.push("-mfpu=fpv4-sp-d16" .into()) |
1890 | } |
1891 | } |
1892 | if target.starts_with("thumbv7m" ) { |
1893 | cmd.args.push("-march=armv7-m" .into()); |
1894 | } |
1895 | if target.starts_with("thumbv8m.base" ) { |
1896 | cmd.args.push("-march=armv8-m.base" .into()); |
1897 | } |
1898 | if target.starts_with("thumbv8m.main" ) { |
1899 | cmd.args.push("-march=armv8-m.main" .into()); |
1900 | |
1901 | if target.ends_with("eabihf" ) { |
1902 | cmd.args.push("-mfpu=fpv5-sp-d16" .into()) |
1903 | } |
1904 | } |
1905 | if target.starts_with("armebv7r" ) | target.starts_with("armv7r" ) { |
1906 | if target.starts_with("armeb" ) { |
1907 | cmd.args.push("-mbig-endian" .into()); |
1908 | } else { |
1909 | cmd.args.push("-mlittle-endian" .into()); |
1910 | } |
1911 | |
1912 | // ARM mode |
1913 | cmd.args.push("-marm" .into()); |
1914 | |
1915 | // R Profile |
1916 | cmd.args.push("-march=armv7-r" .into()); |
1917 | |
1918 | if target.ends_with("eabihf" ) { |
1919 | // Calling convention |
1920 | cmd.args.push("-mfloat-abi=hard" .into()); |
1921 | |
1922 | // lowest common denominator FPU |
1923 | // (see Cortex-R4 technical reference manual) |
1924 | cmd.args.push("-mfpu=vfpv3-d16" .into()) |
1925 | } else { |
1926 | // Calling convention |
1927 | cmd.args.push("-mfloat-abi=soft" .into()); |
1928 | } |
1929 | } |
1930 | if target.starts_with("armv7a" ) { |
1931 | cmd.args.push("-march=armv7-a" .into()); |
1932 | |
1933 | if target.ends_with("eabihf" ) { |
1934 | // lowest common denominator FPU |
1935 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
1936 | } |
1937 | } |
1938 | if target.starts_with("riscv32" ) || target.starts_with("riscv64" ) { |
1939 | // get the 32i/32imac/32imc/64gc/64imac/... part |
1940 | let mut parts = target.split('-' ); |
1941 | if let Some(arch) = parts.next() { |
1942 | let arch = &arch[5..]; |
1943 | if target.contains("linux" ) && arch.starts_with("64" ) { |
1944 | cmd.args.push(("-march=rv64gc" ).into()); |
1945 | cmd.args.push("-mabi=lp64d" .into()); |
1946 | } else if target.contains("freebsd" ) && arch.starts_with("64" ) { |
1947 | cmd.args.push(("-march=rv64gc" ).into()); |
1948 | cmd.args.push("-mabi=lp64d" .into()); |
1949 | } else if target.contains("openbsd" ) && arch.starts_with("64" ) { |
1950 | cmd.args.push(("-march=rv64gc" ).into()); |
1951 | cmd.args.push("-mabi=lp64d" .into()); |
1952 | } else if target.contains("linux" ) && arch.starts_with("32" ) { |
1953 | cmd.args.push(("-march=rv32gc" ).into()); |
1954 | cmd.args.push("-mabi=ilp32d" .into()); |
1955 | } else if arch.starts_with("64" ) { |
1956 | cmd.args.push(("-march=rv" .to_owned() + arch).into()); |
1957 | cmd.args.push("-mabi=lp64" .into()); |
1958 | } else { |
1959 | cmd.args.push(("-march=rv" .to_owned() + arch).into()); |
1960 | cmd.args.push("-mabi=ilp32" .into()); |
1961 | } |
1962 | cmd.args.push("-mcmodel=medany" .into()); |
1963 | } |
1964 | } |
1965 | } |
1966 | } |
1967 | |
1968 | if target.contains("apple-ios" ) || target.contains("apple-watchos" ) { |
1969 | self.ios_watchos_flags(cmd)?; |
1970 | } |
1971 | |
1972 | if self.static_flag.unwrap_or(false) { |
1973 | cmd.args.push("-static" .into()); |
1974 | } |
1975 | if self.shared_flag.unwrap_or(false) { |
1976 | cmd.args.push("-shared" .into()); |
1977 | } |
1978 | |
1979 | if self.cpp { |
1980 | match (self.cpp_set_stdlib.as_ref(), cmd.family) { |
1981 | (None, _) => {} |
1982 | (Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Clang) => { |
1983 | cmd.push_cc_arg(format!("-stdlib=lib {}" , stdlib).into()); |
1984 | } |
1985 | _ => { |
1986 | println!( |
1987 | "cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \ |
1988 | does not support this option, ignored" , |
1989 | cmd.family |
1990 | ); |
1991 | } |
1992 | } |
1993 | } |
1994 | |
1995 | Ok(()) |
1996 | } |
1997 | |
1998 | fn has_flags(&self) -> bool { |
1999 | let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" }; |
2000 | let flags_env_var_value = self.get_var(flags_env_var_name); |
2001 | if let Ok(_) = flags_env_var_value { |
2002 | true |
2003 | } else { |
2004 | false |
2005 | } |
2006 | } |
2007 | |
2008 | fn msvc_macro_assembler(&self) -> Result<(Command, String), Error> { |
2009 | let target = self.get_target()?; |
2010 | let tool = if target.contains("x86_64" ) { |
2011 | "ml64.exe" |
2012 | } else if target.contains("arm" ) { |
2013 | "armasm.exe" |
2014 | } else if target.contains("aarch64" ) { |
2015 | "armasm64.exe" |
2016 | } else { |
2017 | "ml.exe" |
2018 | }; |
2019 | let mut cmd = windows_registry::find(&target, tool).unwrap_or_else(|| self.cmd(tool)); |
2020 | cmd.arg("-nologo" ); // undocumented, yet working with armasm[64] |
2021 | for directory in self.include_directories.iter() { |
2022 | cmd.arg("-I" ).arg(directory); |
2023 | } |
2024 | if target.contains("aarch64" ) || target.contains("arm" ) { |
2025 | if self.get_debug() { |
2026 | cmd.arg("-g" ); |
2027 | } |
2028 | |
2029 | println!("cargo:warning=The MSVC ARM assemblers do not support -D flags" ); |
2030 | } else { |
2031 | if self.get_debug() { |
2032 | cmd.arg("-Zi" ); |
2033 | } |
2034 | |
2035 | for &(ref key, ref value) in self.definitions.iter() { |
2036 | if let Some(ref value) = *value { |
2037 | cmd.arg(&format!("-D {}= {}" , key, value)); |
2038 | } else { |
2039 | cmd.arg(&format!("-D {}" , key)); |
2040 | } |
2041 | } |
2042 | } |
2043 | |
2044 | if target.contains("i686" ) || target.contains("i586" ) { |
2045 | cmd.arg("-safeseh" ); |
2046 | } |
2047 | for flag in self.flags.iter() { |
2048 | cmd.arg(flag); |
2049 | } |
2050 | |
2051 | Ok((cmd, tool.to_string())) |
2052 | } |
2053 | |
2054 | fn assemble(&self, lib_name: &str, dst: &Path, objs: &[Object]) -> Result<(), Error> { |
2055 | // Delete the destination if it exists as we want to |
2056 | // create on the first iteration instead of appending. |
2057 | let _ = fs::remove_file(&dst); |
2058 | |
2059 | // Add objects to the archive in limited-length batches. This helps keep |
2060 | // the length of the command line within a reasonable length to avoid |
2061 | // blowing system limits on limiting platforms like Windows. |
2062 | let objs: Vec<_> = objs |
2063 | .iter() |
2064 | .map(|o| o.dst.clone()) |
2065 | .chain(self.objects.clone()) |
2066 | .collect(); |
2067 | for chunk in objs.chunks(100) { |
2068 | self.assemble_progressive(dst, chunk)?; |
2069 | } |
2070 | |
2071 | if self.cuda && self.cuda_file_count() > 0 { |
2072 | // Link the device-side code and add it to the target library, |
2073 | // so that non-CUDA linker can link the final binary. |
2074 | |
2075 | let out_dir = self.get_out_dir()?; |
2076 | let dlink = out_dir.join(lib_name.to_owned() + "_dlink.o" ); |
2077 | let mut nvcc = self.get_compiler().to_command(); |
2078 | nvcc.arg("--device-link" ) |
2079 | .arg("-o" ) |
2080 | .arg(dlink.clone()) |
2081 | .arg(dst); |
2082 | run(&mut nvcc, "nvcc" )?; |
2083 | self.assemble_progressive(dst, &[dlink])?; |
2084 | } |
2085 | |
2086 | let target = self.get_target()?; |
2087 | if target.contains("msvc" ) { |
2088 | // The Rust compiler will look for libfoo.a and foo.lib, but the |
2089 | // MSVC linker will also be passed foo.lib, so be sure that both |
2090 | // exist for now. |
2091 | |
2092 | let lib_dst = dst.with_file_name(format!(" {}.lib" , lib_name)); |
2093 | let _ = fs::remove_file(&lib_dst); |
2094 | match fs::hard_link(&dst, &lib_dst).or_else(|_| { |
2095 | // if hard-link fails, just copy (ignoring the number of bytes written) |
2096 | fs::copy(&dst, &lib_dst).map(|_| ()) |
2097 | }) { |
2098 | Ok(_) => (), |
2099 | Err(_) => { |
2100 | return Err(Error::new( |
2101 | ErrorKind::IOError, |
2102 | "Could not copy or create a hard-link to the generated lib file." , |
2103 | )); |
2104 | } |
2105 | }; |
2106 | } else { |
2107 | // Non-msvc targets (those using `ar`) need a separate step to add |
2108 | // the symbol table to archives since our construction command of |
2109 | // `cq` doesn't add it for us. |
2110 | let (mut ar, cmd, _any_flags) = self.get_ar()?; |
2111 | |
2112 | // NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s` |
2113 | // here represents a _mode_, not an arbitrary flag. Further discussion of this choice |
2114 | // can be seen in https://github.com/rust-lang/cc-rs/pull/763. |
2115 | run(ar.arg("s" ).arg(dst), &cmd)?; |
2116 | } |
2117 | |
2118 | Ok(()) |
2119 | } |
2120 | |
2121 | fn assemble_progressive(&self, dst: &Path, objs: &[PathBuf]) -> Result<(), Error> { |
2122 | let target = self.get_target()?; |
2123 | |
2124 | if target.contains("msvc" ) { |
2125 | let (mut cmd, program, any_flags) = self.get_ar()?; |
2126 | // NOTE: -out: here is an I/O flag, and so must be included even if $ARFLAGS/ar_flag is |
2127 | // in use. -nologo on the other hand is just a regular flag, and one that we'll skip if |
2128 | // the caller has explicitly dictated the flags they want. See |
2129 | // https://github.com/rust-lang/cc-rs/pull/763 for further discussion. |
2130 | let mut out = OsString::from("-out:" ); |
2131 | out.push(dst); |
2132 | cmd.arg(out); |
2133 | if !any_flags { |
2134 | cmd.arg("-nologo" ); |
2135 | } |
2136 | // If the library file already exists, add the library name |
2137 | // as an argument to let lib.exe know we are appending the objs. |
2138 | if dst.exists() { |
2139 | cmd.arg(dst); |
2140 | } |
2141 | cmd.args(objs); |
2142 | run(&mut cmd, &program)?; |
2143 | } else { |
2144 | let (mut ar, cmd, _any_flags) = self.get_ar()?; |
2145 | |
2146 | // Set an environment variable to tell the OSX archiver to ensure |
2147 | // that all dates listed in the archive are zero, improving |
2148 | // determinism of builds. AFAIK there's not really official |
2149 | // documentation of this but there's a lot of references to it if |
2150 | // you search google. |
2151 | // |
2152 | // You can reproduce this locally on a mac with: |
2153 | // |
2154 | // $ touch foo.c |
2155 | // $ cc -c foo.c -o foo.o |
2156 | // |
2157 | // # Notice that these two checksums are different |
2158 | // $ ar crus libfoo1.a foo.o && sleep 2 && ar crus libfoo2.a foo.o |
2159 | // $ md5sum libfoo*.a |
2160 | // |
2161 | // # Notice that these two checksums are the same |
2162 | // $ export ZERO_AR_DATE=1 |
2163 | // $ ar crus libfoo1.a foo.o && sleep 2 && touch foo.o && ar crus libfoo2.a foo.o |
2164 | // $ md5sum libfoo*.a |
2165 | // |
2166 | // In any case if this doesn't end up getting read, it shouldn't |
2167 | // cause that many issues! |
2168 | ar.env("ZERO_AR_DATE" , "1" ); |
2169 | |
2170 | // NOTE: We add cq here regardless of whether $ARFLAGS/ar_flag have been used because |
2171 | // it dictates the _mode_ ar runs in, which the setter of $ARFLAGS/ar_flag can't |
2172 | // dictate. See https://github.com/rust-lang/cc-rs/pull/763 for further discussion. |
2173 | run(ar.arg("cq" ).arg(dst).args(objs), &cmd)?; |
2174 | } |
2175 | |
2176 | Ok(()) |
2177 | } |
2178 | |
2179 | fn ios_watchos_flags(&self, cmd: &mut Tool) -> Result<(), Error> { |
2180 | enum ArchSpec { |
2181 | Device(&'static str), |
2182 | Simulator(&'static str), |
2183 | Catalyst(&'static str), |
2184 | } |
2185 | |
2186 | enum Os { |
2187 | Ios, |
2188 | WatchOs, |
2189 | } |
2190 | impl Display for Os { |
2191 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
2192 | match self { |
2193 | Os::Ios => f.write_str("iOS" ), |
2194 | Os::WatchOs => f.write_str("WatchOS" ), |
2195 | } |
2196 | } |
2197 | } |
2198 | |
2199 | let target = self.get_target()?; |
2200 | let os = if target.contains("-watchos" ) { |
2201 | Os::WatchOs |
2202 | } else { |
2203 | Os::Ios |
2204 | }; |
2205 | |
2206 | let arch = target.split('-' ).nth(0).ok_or_else(|| { |
2207 | Error::new( |
2208 | ErrorKind::ArchitectureInvalid, |
2209 | format!("Unknown architecture for {} target." , os).as_str(), |
2210 | ) |
2211 | })?; |
2212 | |
2213 | let is_catalyst = match target.split('-' ).nth(3) { |
2214 | Some(v) => v == "macabi" , |
2215 | None => false, |
2216 | }; |
2217 | |
2218 | let is_sim = match target.split('-' ).nth(3) { |
2219 | Some(v) => v == "sim" , |
2220 | None => false, |
2221 | }; |
2222 | |
2223 | let arch = if is_catalyst { |
2224 | match arch { |
2225 | "arm64e" => ArchSpec::Catalyst("arm64e" ), |
2226 | "arm64" | "aarch64" => ArchSpec::Catalyst("arm64" ), |
2227 | "x86_64" => ArchSpec::Catalyst("-m64" ), |
2228 | _ => { |
2229 | return Err(Error::new( |
2230 | ErrorKind::ArchitectureInvalid, |
2231 | "Unknown architecture for iOS target." , |
2232 | )); |
2233 | } |
2234 | } |
2235 | } else if is_sim { |
2236 | match arch { |
2237 | "arm64" | "aarch64" => ArchSpec::Simulator("arm64" ), |
2238 | "x86_64" => ArchSpec::Simulator("-m64" ), |
2239 | _ => { |
2240 | return Err(Error::new( |
2241 | ErrorKind::ArchitectureInvalid, |
2242 | "Unknown architecture for iOS simulator target." , |
2243 | )); |
2244 | } |
2245 | } |
2246 | } else { |
2247 | match arch { |
2248 | "arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7" ), |
2249 | "armv7k" => ArchSpec::Device("armv7k" ), |
2250 | "armv7s" | "thumbv7s" => ArchSpec::Device("armv7s" ), |
2251 | "arm64e" => ArchSpec::Device("arm64e" ), |
2252 | "arm64" | "aarch64" => ArchSpec::Device("arm64" ), |
2253 | "arm64_32" => ArchSpec::Device("arm64_32" ), |
2254 | "i386" | "i686" => ArchSpec::Simulator("-m32" ), |
2255 | "x86_64" => ArchSpec::Simulator("-m64" ), |
2256 | _ => { |
2257 | return Err(Error::new( |
2258 | ErrorKind::ArchitectureInvalid, |
2259 | format!("Unknown architecture for {} target." , os).as_str(), |
2260 | )); |
2261 | } |
2262 | } |
2263 | }; |
2264 | |
2265 | let (sdk_prefix, sim_prefix, min_version) = match os { |
2266 | Os::Ios => ( |
2267 | "iphone" , |
2268 | "ios-" , |
2269 | std::env::var("IPHONEOS_DEPLOYMENT_TARGET" ).unwrap_or_else(|_| "7.0" .into()), |
2270 | ), |
2271 | Os::WatchOs => ( |
2272 | "watch" , |
2273 | "watch" , |
2274 | std::env::var("WATCHOS_DEPLOYMENT_TARGET" ).unwrap_or_else(|_| "2.0" .into()), |
2275 | ), |
2276 | }; |
2277 | |
2278 | let sdk = match arch { |
2279 | ArchSpec::Device(arch) => { |
2280 | cmd.args.push("-arch" .into()); |
2281 | cmd.args.push(arch.into()); |
2282 | cmd.args |
2283 | .push(format!("-m {}os-version-min= {}" , sdk_prefix, min_version).into()); |
2284 | format!(" {}os" , sdk_prefix) |
2285 | } |
2286 | ArchSpec::Simulator(arch) => { |
2287 | if arch.starts_with('-' ) { |
2288 | // -m32 or -m64 |
2289 | cmd.args.push(arch.into()); |
2290 | } else { |
2291 | cmd.args.push("-arch" .into()); |
2292 | cmd.args.push(arch.into()); |
2293 | } |
2294 | cmd.args |
2295 | .push(format!("-m {}simulator-version-min= {}" , sim_prefix, min_version).into()); |
2296 | format!(" {}simulator" , sdk_prefix) |
2297 | } |
2298 | ArchSpec::Catalyst(_) => "macosx" .to_owned(), |
2299 | }; |
2300 | |
2301 | self.print(&format!("Detecting {} SDK path for {}" , os, sdk)); |
2302 | let sdk_path = if let Some(sdkroot) = env::var_os("SDKROOT" ) { |
2303 | sdkroot |
2304 | } else { |
2305 | self.apple_sdk_root(sdk.as_str())? |
2306 | }; |
2307 | |
2308 | cmd.args.push("-isysroot" .into()); |
2309 | cmd.args.push(sdk_path); |
2310 | // TODO: Remove this once Apple stops accepting apps built with Xcode 13 |
2311 | cmd.args.push("-fembed-bitcode" .into()); |
2312 | |
2313 | Ok(()) |
2314 | } |
2315 | |
2316 | fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command { |
2317 | let mut cmd = Command::new(prog); |
2318 | for &(ref a, ref b) in self.env.iter() { |
2319 | cmd.env(a, b); |
2320 | } |
2321 | cmd |
2322 | } |
2323 | |
2324 | fn get_base_compiler(&self) -> Result<Tool, Error> { |
2325 | if let Some(ref c) = self.compiler { |
2326 | return Ok(Tool::new(c.clone())); |
2327 | } |
2328 | let host = self.get_host()?; |
2329 | let target = self.get_target()?; |
2330 | let (env, msvc, gnu, traditional, clang) = if self.cpp { |
2331 | ("CXX" , "cl.exe" , "g++" , "c++" , "clang++" ) |
2332 | } else { |
2333 | ("CC" , "cl.exe" , "gcc" , "cc" , "clang" ) |
2334 | }; |
2335 | |
2336 | // On historical Solaris systems, "cc" may have been Sun Studio, which |
2337 | // is not flag-compatible with "gcc". This history casts a long shadow, |
2338 | // and many modern illumos distributions today ship GCC as "gcc" without |
2339 | // also making it available as "cc". |
2340 | let default = if host.contains("solaris" ) || host.contains("illumos" ) { |
2341 | gnu |
2342 | } else { |
2343 | traditional |
2344 | }; |
2345 | |
2346 | let cl_exe = windows_registry::find_tool(&target, "cl.exe" ); |
2347 | |
2348 | let tool_opt: Option<Tool> = self |
2349 | .env_tool(env) |
2350 | .map(|(tool, wrapper, args)| { |
2351 | // find the driver mode, if any |
2352 | const DRIVER_MODE: &str = "--driver-mode=" ; |
2353 | let driver_mode = args |
2354 | .iter() |
2355 | .find(|a| a.starts_with(DRIVER_MODE)) |
2356 | .map(|a| &a[DRIVER_MODE.len()..]); |
2357 | // Chop off leading/trailing whitespace to work around |
2358 | // semi-buggy build scripts which are shared in |
2359 | // makefiles/configure scripts (where spaces are far more |
2360 | // lenient) |
2361 | let mut t = Tool::with_clang_driver(PathBuf::from(tool.trim()), driver_mode); |
2362 | if let Some(cc_wrapper) = wrapper { |
2363 | t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); |
2364 | } |
2365 | for arg in args { |
2366 | t.cc_wrapper_args.push(arg.into()); |
2367 | } |
2368 | t |
2369 | }) |
2370 | .or_else(|| { |
2371 | if target.contains("emscripten" ) { |
2372 | let tool = if self.cpp { "em++" } else { "emcc" }; |
2373 | // Windows uses bat file so we have to be a bit more specific |
2374 | if cfg!(windows) { |
2375 | let mut t = Tool::new(PathBuf::from("cmd" )); |
2376 | t.args.push("/c" .into()); |
2377 | t.args.push(format!(" {}.bat" , tool).into()); |
2378 | Some(t) |
2379 | } else { |
2380 | Some(Tool::new(PathBuf::from(tool))) |
2381 | } |
2382 | } else { |
2383 | None |
2384 | } |
2385 | }) |
2386 | .or_else(|| cl_exe.clone()); |
2387 | |
2388 | let tool = match tool_opt { |
2389 | Some(t) => t, |
2390 | None => { |
2391 | let compiler = if host.contains("windows" ) && target.contains("windows" ) { |
2392 | if target.contains("msvc" ) { |
2393 | msvc.to_string() |
2394 | } else { |
2395 | let cc = if target.contains("llvm" ) { clang } else { gnu }; |
2396 | format!(" {}.exe" , cc) |
2397 | } |
2398 | } else if target.contains("apple-ios" ) { |
2399 | clang.to_string() |
2400 | } else if target.contains("apple-watchos" ) { |
2401 | clang.to_string() |
2402 | } else if target.contains("android" ) { |
2403 | autodetect_android_compiler(&target, &host, gnu, clang) |
2404 | } else if target.contains("cloudabi" ) { |
2405 | format!(" {}- {}" , target, traditional) |
2406 | } else if target == "wasm32-wasi" |
2407 | || target == "wasm32-unknown-wasi" |
2408 | || target == "wasm32-unknown-unknown" |
2409 | { |
2410 | "clang" .to_string() |
2411 | } else if target.contains("vxworks" ) { |
2412 | if self.cpp { |
2413 | "wr-c++" .to_string() |
2414 | } else { |
2415 | "wr-cc" .to_string() |
2416 | } |
2417 | } else if target.starts_with("armv7a-kmc-solid_" ) { |
2418 | format!("arm-kmc-eabi- {}" , gnu) |
2419 | } else if target.starts_with("aarch64-kmc-solid_" ) { |
2420 | format!("aarch64-kmc-elf- {}" , gnu) |
2421 | } else if self.get_host()? != target { |
2422 | let prefix = self.prefix_for_target(&target); |
2423 | match prefix { |
2424 | Some(prefix) => { |
2425 | let cc = if target.contains("llvm" ) { clang } else { gnu }; |
2426 | format!(" {}- {}" , prefix, cc) |
2427 | } |
2428 | None => default.to_string(), |
2429 | } |
2430 | } else { |
2431 | default.to_string() |
2432 | }; |
2433 | |
2434 | let mut t = Tool::new(PathBuf::from(compiler)); |
2435 | if let Some(cc_wrapper) = Self::rustc_wrapper_fallback() { |
2436 | t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); |
2437 | } |
2438 | t |
2439 | } |
2440 | }; |
2441 | |
2442 | let mut tool = if self.cuda { |
2443 | assert!( |
2444 | tool.args.is_empty(), |
2445 | "CUDA compilation currently assumes empty pre-existing args" |
2446 | ); |
2447 | let nvcc = match self.get_var("NVCC" ) { |
2448 | Err(_) => "nvcc" .into(), |
2449 | Ok(nvcc) => nvcc, |
2450 | }; |
2451 | let mut nvcc_tool = Tool::with_features(PathBuf::from(nvcc), None, self.cuda); |
2452 | nvcc_tool |
2453 | .args |
2454 | .push(format!("-ccbin= {}" , tool.path.display()).into()); |
2455 | nvcc_tool.family = tool.family; |
2456 | nvcc_tool |
2457 | } else { |
2458 | tool |
2459 | }; |
2460 | |
2461 | // New "standalone" C/C++ cross-compiler executables from recent Android NDK |
2462 | // are just shell scripts that call main clang binary (from Android NDK) with |
2463 | // proper `--target` argument. |
2464 | // |
2465 | // For example, armv7a-linux-androideabi16-clang passes |
2466 | // `--target=armv7a-linux-androideabi16` to clang. |
2467 | // |
2468 | // As the shell script calls the main clang binary, the command line limit length |
2469 | // on Windows is restricted to around 8k characters instead of around 32k characters. |
2470 | // To remove this limit, we call the main clang binary directly and construct the |
2471 | // `--target=` ourselves. |
2472 | if host.contains("windows" ) && android_clang_compiler_uses_target_arg_internally(&tool.path) |
2473 | { |
2474 | if let Some(path) = tool.path.file_name() { |
2475 | let file_name = path.to_str().unwrap().to_owned(); |
2476 | let (target, clang) = file_name.split_at(file_name.rfind("-" ).unwrap()); |
2477 | |
2478 | tool.path.set_file_name(clang.trim_start_matches("-" )); |
2479 | tool.path.set_extension("exe" ); |
2480 | tool.args.push(format!("--target= {}" , target).into()); |
2481 | |
2482 | // Additionally, shell scripts for target i686-linux-android versions 16 to 24 |
2483 | // pass the `mstackrealign` option so we do that here as well. |
2484 | if target.contains("i686-linux-android" ) { |
2485 | let (_, version) = target.split_at(target.rfind("d" ).unwrap() + 1); |
2486 | if let Ok(version) = version.parse::<u32>() { |
2487 | if version > 15 && version < 25 { |
2488 | tool.args.push("-mstackrealign" .into()); |
2489 | } |
2490 | } |
2491 | } |
2492 | }; |
2493 | } |
2494 | |
2495 | // If we found `cl.exe` in our environment, the tool we're returning is |
2496 | // an MSVC-like tool, *and* no env vars were set then set env vars for |
2497 | // the tool that we're returning. |
2498 | // |
2499 | // Env vars are needed for things like `link.exe` being put into PATH as |
2500 | // well as header include paths sometimes. These paths are automatically |
2501 | // included by default but if the `CC` or `CXX` env vars are set these |
2502 | // won't be used. This'll ensure that when the env vars are used to |
2503 | // configure for invocations like `clang-cl` we still get a "works out |
2504 | // of the box" experience. |
2505 | if let Some(cl_exe) = cl_exe { |
2506 | if tool.family == (ToolFamily::Msvc { clang_cl: true }) |
2507 | && tool.env.len() == 0 |
2508 | && target.contains("msvc" ) |
2509 | { |
2510 | for &(ref k, ref v) in cl_exe.env.iter() { |
2511 | tool.env.push((k.to_owned(), v.to_owned())); |
2512 | } |
2513 | } |
2514 | } |
2515 | |
2516 | Ok(tool) |
2517 | } |
2518 | |
2519 | fn get_var(&self, var_base: &str) -> Result<String, Error> { |
2520 | let target = self.get_target()?; |
2521 | let host = self.get_host()?; |
2522 | let kind = if host == target { "HOST" } else { "TARGET" }; |
2523 | let target_u = target.replace("-" , "_" ); |
2524 | let res = self |
2525 | .getenv(&format!(" {}_ {}" , var_base, target)) |
2526 | .or_else(|| self.getenv(&format!(" {}_ {}" , var_base, target_u))) |
2527 | .or_else(|| self.getenv(&format!(" {}_ {}" , kind, var_base))) |
2528 | .or_else(|| self.getenv(var_base)); |
2529 | |
2530 | match res { |
2531 | Some(res) => Ok(res), |
2532 | None => Err(Error::new( |
2533 | ErrorKind::EnvVarNotFound, |
2534 | &format!("Could not find environment variable {}." , var_base), |
2535 | )), |
2536 | } |
2537 | } |
2538 | |
2539 | fn envflags(&self, name: &str) -> Vec<String> { |
2540 | self.get_var(name) |
2541 | .unwrap_or(String::new()) |
2542 | .split_ascii_whitespace() |
2543 | .map(|slice| slice.to_string()) |
2544 | .collect() |
2545 | } |
2546 | |
2547 | /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER` |
2548 | fn rustc_wrapper_fallback() -> Option<String> { |
2549 | // No explicit CC wrapper was detected, but check if RUSTC_WRAPPER |
2550 | // is defined and is a build accelerator that is compatible with |
2551 | // C/C++ compilers (e.g. sccache) |
2552 | const VALID_WRAPPERS: &[&'static str] = &["sccache" , "cachepot" ]; |
2553 | |
2554 | let rustc_wrapper = std::env::var_os("RUSTC_WRAPPER" )?; |
2555 | let wrapper_path = Path::new(&rustc_wrapper); |
2556 | let wrapper_stem = wrapper_path.file_stem()?; |
2557 | |
2558 | if VALID_WRAPPERS.contains(&wrapper_stem.to_str()?) { |
2559 | Some(rustc_wrapper.to_str()?.to_owned()) |
2560 | } else { |
2561 | None |
2562 | } |
2563 | } |
2564 | |
2565 | /// Returns compiler path, optional modifier name from whitelist, and arguments vec |
2566 | fn env_tool(&self, name: &str) -> Option<(String, Option<String>, Vec<String>)> { |
2567 | let tool = match self.get_var(name) { |
2568 | Ok(tool) => tool, |
2569 | Err(_) => return None, |
2570 | }; |
2571 | |
2572 | // If this is an exact path on the filesystem we don't want to do any |
2573 | // interpretation at all, just pass it on through. This'll hopefully get |
2574 | // us to support spaces-in-paths. |
2575 | if Path::new(&tool).exists() { |
2576 | return Some((tool, None, Vec::new())); |
2577 | } |
2578 | |
2579 | // Ok now we want to handle a couple of scenarios. We'll assume from |
2580 | // here on out that spaces are splitting separate arguments. Two major |
2581 | // features we want to support are: |
2582 | // |
2583 | // CC='sccache cc' |
2584 | // |
2585 | // aka using `sccache` or any other wrapper/caching-like-thing for |
2586 | // compilations. We want to know what the actual compiler is still, |
2587 | // though, because our `Tool` API support introspection of it to see |
2588 | // what compiler is in use. |
2589 | // |
2590 | // additionally we want to support |
2591 | // |
2592 | // CC='cc -flag' |
2593 | // |
2594 | // where the CC env var is used to also pass default flags to the C |
2595 | // compiler. |
2596 | // |
2597 | // It's true that everything here is a bit of a pain, but apparently if |
2598 | // you're not literally make or bash then you get a lot of bug reports. |
2599 | let known_wrappers = ["ccache" , "distcc" , "sccache" , "icecc" , "cachepot" ]; |
2600 | |
2601 | let mut parts = tool.split_whitespace(); |
2602 | let maybe_wrapper = match parts.next() { |
2603 | Some(s) => s, |
2604 | None => return None, |
2605 | }; |
2606 | |
2607 | let file_stem = Path::new(maybe_wrapper) |
2608 | .file_stem() |
2609 | .unwrap() |
2610 | .to_str() |
2611 | .unwrap(); |
2612 | if known_wrappers.contains(&file_stem) { |
2613 | if let Some(compiler) = parts.next() { |
2614 | return Some(( |
2615 | compiler.to_string(), |
2616 | Some(maybe_wrapper.to_string()), |
2617 | parts.map(|s| s.to_string()).collect(), |
2618 | )); |
2619 | } |
2620 | } |
2621 | |
2622 | Some(( |
2623 | maybe_wrapper.to_string(), |
2624 | Self::rustc_wrapper_fallback(), |
2625 | parts.map(|s| s.to_string()).collect(), |
2626 | )) |
2627 | } |
2628 | |
2629 | /// Returns the C++ standard library: |
2630 | /// 1. If [cpp_link_stdlib](cc::Build::cpp_link_stdlib) is set, uses its value. |
2631 | /// 2. Else if the `CXXSTDLIB` environment variable is set, uses its value. |
2632 | /// 3. Else the default is `libc++` for OS X and BSDs, `libc++_shared` for Android, |
2633 | /// `None` for MSVC and `libstdc++` for anything else. |
2634 | fn get_cpp_link_stdlib(&self) -> Result<Option<String>, Error> { |
2635 | match self.cpp_link_stdlib.clone() { |
2636 | Some(s) => Ok(s), |
2637 | None => { |
2638 | if let Ok(stdlib) = self.get_var("CXXSTDLIB" ) { |
2639 | if stdlib.is_empty() { |
2640 | Ok(None) |
2641 | } else { |
2642 | Ok(Some(stdlib)) |
2643 | } |
2644 | } else { |
2645 | let target = self.get_target()?; |
2646 | if target.contains("msvc" ) { |
2647 | Ok(None) |
2648 | } else if target.contains("apple" ) { |
2649 | Ok(Some("c++" .to_string())) |
2650 | } else if target.contains("freebsd" ) { |
2651 | Ok(Some("c++" .to_string())) |
2652 | } else if target.contains("openbsd" ) { |
2653 | Ok(Some("c++" .to_string())) |
2654 | } else if target.contains("android" ) { |
2655 | Ok(Some("c++_shared" .to_string())) |
2656 | } else { |
2657 | Ok(Some("stdc++" .to_string())) |
2658 | } |
2659 | } |
2660 | } |
2661 | } |
2662 | } |
2663 | |
2664 | fn get_ar(&self) -> Result<(Command, String, bool), Error> { |
2665 | self.try_get_archiver_and_flags() |
2666 | } |
2667 | |
2668 | /// Get the archiver (ar) that's in use for this configuration. |
2669 | /// |
2670 | /// You can use [`Command::get_program`] to get just the path to the command. |
2671 | /// |
2672 | /// This method will take into account all configuration such as debug |
2673 | /// information, optimization level, include directories, defines, etc. |
2674 | /// Additionally, the compiler binary in use follows the standard |
2675 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
2676 | /// environment variables (a number of which are inspected here), and then |
2677 | /// falling back to the default configuration. |
2678 | /// |
2679 | /// # Panics |
2680 | /// |
2681 | /// Panics if an error occurred while determining the architecture. |
2682 | pub fn get_archiver(&self) -> Command { |
2683 | match self.try_get_archiver() { |
2684 | Ok(tool) => tool, |
2685 | Err(e) => fail(&e.message), |
2686 | } |
2687 | } |
2688 | |
2689 | /// Get the archiver that's in use for this configuration. |
2690 | /// |
2691 | /// This will return a result instead of panicing; |
2692 | /// see [`get_archiver()`] for the complete description. |
2693 | pub fn try_get_archiver(&self) -> Result<Command, Error> { |
2694 | Ok(self.try_get_archiver_and_flags()?.0) |
2695 | } |
2696 | |
2697 | fn try_get_archiver_and_flags(&self) -> Result<(Command, String, bool), Error> { |
2698 | let (mut cmd, name) = self.get_base_archiver()?; |
2699 | let flags = self.envflags("ARFLAGS" ); |
2700 | let mut any_flags = !flags.is_empty(); |
2701 | cmd.args(flags); |
2702 | for flag in &self.ar_flags { |
2703 | any_flags = true; |
2704 | cmd.arg(flag); |
2705 | } |
2706 | Ok((cmd, name, any_flags)) |
2707 | } |
2708 | |
2709 | fn get_base_archiver(&self) -> Result<(Command, String), Error> { |
2710 | if let Some(ref a) = self.archiver { |
2711 | return Ok((self.cmd(a), a.to_string_lossy().into_owned())); |
2712 | } |
2713 | |
2714 | self.get_base_archiver_variant("AR" , "ar" ) |
2715 | } |
2716 | |
2717 | /// Get the ranlib that's in use for this configuration. |
2718 | /// |
2719 | /// You can use [`Command::get_program`] to get just the path to the command. |
2720 | /// |
2721 | /// This method will take into account all configuration such as debug |
2722 | /// information, optimization level, include directories, defines, etc. |
2723 | /// Additionally, the compiler binary in use follows the standard |
2724 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
2725 | /// environment variables (a number of which are inspected here), and then |
2726 | /// falling back to the default configuration. |
2727 | /// |
2728 | /// # Panics |
2729 | /// |
2730 | /// Panics if an error occurred while determining the architecture. |
2731 | pub fn get_ranlib(&self) -> Command { |
2732 | match self.try_get_ranlib() { |
2733 | Ok(tool) => tool, |
2734 | Err(e) => fail(&e.message), |
2735 | } |
2736 | } |
2737 | |
2738 | /// Get the ranlib that's in use for this configuration. |
2739 | /// |
2740 | /// This will return a result instead of panicing; |
2741 | /// see [`get_ranlib()`] for the complete description. |
2742 | pub fn try_get_ranlib(&self) -> Result<Command, Error> { |
2743 | let mut cmd = self.get_base_ranlib()?; |
2744 | cmd.args(self.envflags("RANLIBFLAGS" )); |
2745 | Ok(cmd) |
2746 | } |
2747 | |
2748 | fn get_base_ranlib(&self) -> Result<Command, Error> { |
2749 | if let Some(ref r) = self.ranlib { |
2750 | return Ok(self.cmd(r)); |
2751 | } |
2752 | |
2753 | Ok(self.get_base_archiver_variant("RANLIB" , "ranlib" )?.0) |
2754 | } |
2755 | |
2756 | fn get_base_archiver_variant(&self, env: &str, tool: &str) -> Result<(Command, String), Error> { |
2757 | let target = self.get_target()?; |
2758 | let mut name = String::new(); |
2759 | let tool_opt: Option<Command> = self |
2760 | .env_tool(env) |
2761 | .map(|(tool, _wrapper, args)| { |
2762 | let mut cmd = self.cmd(tool); |
2763 | cmd.args(args); |
2764 | cmd |
2765 | }) |
2766 | .or_else(|| { |
2767 | if target.contains("emscripten" ) { |
2768 | // Windows use bat files so we have to be a bit more specific |
2769 | if cfg!(windows) { |
2770 | let mut cmd = self.cmd("cmd" ); |
2771 | name = format!("em {}.bat" , tool); |
2772 | cmd.arg("/c" ).arg(&name); |
2773 | Some(cmd) |
2774 | } else { |
2775 | name = format!("em {}" , tool); |
2776 | Some(self.cmd(&name)) |
2777 | } |
2778 | } else { |
2779 | None |
2780 | } |
2781 | }); |
2782 | |
2783 | let default = tool.to_string(); |
2784 | let tool = match tool_opt { |
2785 | Some(t) => t, |
2786 | None => { |
2787 | if target.contains("android" ) { |
2788 | name = format!(" {}- {}" , target.replace("armv7" , "arm" ), tool); |
2789 | self.cmd(&name) |
2790 | } else if target.contains("msvc" ) { |
2791 | // NOTE: There isn't really a ranlib on msvc, so arguably we should return |
2792 | // `None` somehow here. But in general, callers will already have to be aware |
2793 | // of not running ranlib on Windows anyway, so it feels okay to return lib.exe |
2794 | // here. |
2795 | |
2796 | let compiler = self.get_base_compiler()?; |
2797 | let mut lib = String::new(); |
2798 | if compiler.family == (ToolFamily::Msvc { clang_cl: true }) { |
2799 | // See if there is 'llvm-lib' next to 'clang-cl' |
2800 | // Another possibility could be to see if there is 'clang' |
2801 | // next to 'clang-cl' and use 'search_programs()' to locate |
2802 | // 'llvm-lib'. This is because 'clang-cl' doesn't support |
2803 | // the -print-search-dirs option. |
2804 | if let Some(mut cmd) = which(&compiler.path) { |
2805 | cmd.pop(); |
2806 | cmd.push("llvm-lib.exe" ); |
2807 | if let Some(llvm_lib) = which(&cmd) { |
2808 | lib = llvm_lib.to_str().unwrap().to_owned(); |
2809 | } |
2810 | } |
2811 | } |
2812 | |
2813 | if lib.is_empty() { |
2814 | name = String::from("lib.exe" ); |
2815 | match windows_registry::find(&target, "lib.exe" ) { |
2816 | Some(t) => t, |
2817 | None => self.cmd("lib.exe" ), |
2818 | } |
2819 | } else { |
2820 | name = lib; |
2821 | self.cmd(&name) |
2822 | } |
2823 | } else if target.contains("illumos" ) { |
2824 | // The default 'ar' on illumos uses a non-standard flags, |
2825 | // but the OS comes bundled with a GNU-compatible variant. |
2826 | // |
2827 | // Use the GNU-variant to match other Unix systems. |
2828 | name = format!("g {}" , tool); |
2829 | self.cmd(&name) |
2830 | } else if self.get_host()? != target { |
2831 | match self.prefix_for_target(&target) { |
2832 | Some(p) => { |
2833 | // GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both. |
2834 | // Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be |
2835 | // outright broken (such as when targetting freebsd with `--disable-lto` |
2836 | // toolchain where the archiver attempts to load the LTO plugin anyway but |
2837 | // fails to find one). |
2838 | // |
2839 | // The same applies to ranlib. |
2840 | let mut chosen = default; |
2841 | for &infix in &["" , "-gcc" ] { |
2842 | let target_p = format!(" {}{}- {}" , p, infix, tool); |
2843 | if Command::new(&target_p).output().is_ok() { |
2844 | chosen = target_p; |
2845 | break; |
2846 | } |
2847 | } |
2848 | name = chosen; |
2849 | self.cmd(&name) |
2850 | } |
2851 | None => { |
2852 | name = default; |
2853 | self.cmd(&name) |
2854 | } |
2855 | } |
2856 | } else { |
2857 | name = default; |
2858 | self.cmd(&name) |
2859 | } |
2860 | } |
2861 | }; |
2862 | |
2863 | Ok((tool, name)) |
2864 | } |
2865 | |
2866 | fn prefix_for_target(&self, target: &str) -> Option<String> { |
2867 | // Put aside RUSTC_LINKER's prefix to be used as last resort |
2868 | let rustc_linker = self.getenv("RUSTC_LINKER" ).unwrap_or("" .to_string()); |
2869 | // let linker_prefix = rustc_linker.strip_suffix("-gcc"); // >=1.45.0 |
2870 | let linker_prefix = if rustc_linker.len() > 4 { |
2871 | let (prefix, suffix) = rustc_linker.split_at(rustc_linker.len() - 4); |
2872 | if suffix == "-gcc" { |
2873 | Some(prefix) |
2874 | } else { |
2875 | None |
2876 | } |
2877 | } else { |
2878 | None |
2879 | }; |
2880 | // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" |
2881 | let cc_env = self.getenv("CROSS_COMPILE" ); |
2882 | let cross_compile = cc_env.as_ref().map(|s| s.trim_end_matches('-' ).to_owned()); |
2883 | cross_compile.or(match &target[..] { |
2884 | // Note: there is no `aarch64-pc-windows-gnu` target, only `-gnullvm` |
2885 | "aarch64-pc-windows-gnullvm" => Some("aarch64-w64-mingw32" ), |
2886 | "aarch64-uwp-windows-gnu" => Some("aarch64-w64-mingw32" ), |
2887 | "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu" ), |
2888 | "aarch64-unknown-linux-musl" => Some("aarch64-linux-musl" ), |
2889 | "aarch64-unknown-netbsd" => Some("aarch64--netbsd" ), |
2890 | "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
2891 | "armv4t-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
2892 | "armv5te-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
2893 | "armv5te-unknown-linux-musleabi" => Some("arm-linux-gnueabi" ), |
2894 | "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi" ), |
2895 | "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
2896 | "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi" ), |
2897 | "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
2898 | "arm-unknown-netbsd-eabi" => Some("arm--netbsdelf-eabi" ), |
2899 | "armv6-unknown-netbsd-eabihf" => Some("armv6--netbsdelf-eabihf" ), |
2900 | "armv7-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
2901 | "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
2902 | "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
2903 | "armv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
2904 | "armv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
2905 | "thumbv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
2906 | "thumbv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
2907 | "thumbv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
2908 | "thumbv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
2909 | "armv7-unknown-netbsd-eabihf" => Some("armv7--netbsdelf-eabihf" ), |
2910 | "hexagon-unknown-linux-musl" => Some("hexagon-linux-musl" ), |
2911 | "i586-unknown-linux-musl" => Some("musl" ), |
2912 | "i686-pc-windows-gnu" => Some("i686-w64-mingw32" ), |
2913 | "i686-uwp-windows-gnu" => Some("i686-w64-mingw32" ), |
2914 | "i686-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ |
2915 | "i686-linux-gnu" , |
2916 | "x86_64-linux-gnu" , // transparently support gcc-multilib |
2917 | ]), // explicit None if not found, so caller knows to fall back |
2918 | "i686-unknown-linux-musl" => Some("musl" ), |
2919 | "i686-unknown-netbsd" => Some("i486--netbsdelf" ), |
2920 | "mips-unknown-linux-gnu" => Some("mips-linux-gnu" ), |
2921 | "mips-unknown-linux-musl" => Some("mips-linux-musl" ), |
2922 | "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu" ), |
2923 | "mipsel-unknown-linux-musl" => Some("mipsel-linux-musl" ), |
2924 | "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64" ), |
2925 | "mips64el-unknown-linux-gnuabi64" => Some("mips64el-linux-gnuabi64" ), |
2926 | "mipsisa32r6-unknown-linux-gnu" => Some("mipsisa32r6-linux-gnu" ), |
2927 | "mipsisa32r6el-unknown-linux-gnu" => Some("mipsisa32r6el-linux-gnu" ), |
2928 | "mipsisa64r6-unknown-linux-gnuabi64" => Some("mipsisa64r6-linux-gnuabi64" ), |
2929 | "mipsisa64r6el-unknown-linux-gnuabi64" => Some("mipsisa64r6el-linux-gnuabi64" ), |
2930 | "powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu" ), |
2931 | "powerpc-unknown-linux-gnuspe" => Some("powerpc-linux-gnuspe" ), |
2932 | "powerpc-unknown-netbsd" => Some("powerpc--netbsd" ), |
2933 | "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu" ), |
2934 | "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu" ), |
2935 | "riscv32i-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
2936 | "riscv32-unknown-elf" , |
2937 | "riscv64-unknown-elf" , |
2938 | "riscv-none-embed" , |
2939 | ]), |
2940 | "riscv32imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
2941 | "riscv32-unknown-elf" , |
2942 | "riscv64-unknown-elf" , |
2943 | "riscv-none-embed" , |
2944 | ]), |
2945 | "riscv32imac-unknown-xous-elf" => self.find_working_gnu_prefix(&[ |
2946 | "riscv32-unknown-elf" , |
2947 | "riscv64-unknown-elf" , |
2948 | "riscv-none-embed" , |
2949 | ]), |
2950 | "riscv32imc-esp-espidf" => Some("riscv32-esp-elf" ), |
2951 | "riscv32imc-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
2952 | "riscv32-unknown-elf" , |
2953 | "riscv64-unknown-elf" , |
2954 | "riscv-none-embed" , |
2955 | ]), |
2956 | "riscv64gc-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
2957 | "riscv64-unknown-elf" , |
2958 | "riscv32-unknown-elf" , |
2959 | "riscv-none-embed" , |
2960 | ]), |
2961 | "riscv64imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
2962 | "riscv64-unknown-elf" , |
2963 | "riscv32-unknown-elf" , |
2964 | "riscv-none-embed" , |
2965 | ]), |
2966 | "riscv64gc-unknown-linux-gnu" => Some("riscv64-linux-gnu" ), |
2967 | "riscv32gc-unknown-linux-gnu" => Some("riscv32-linux-gnu" ), |
2968 | "riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl" ), |
2969 | "riscv32gc-unknown-linux-musl" => Some("riscv32-linux-musl" ), |
2970 | "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu" ), |
2971 | "sparc-unknown-linux-gnu" => Some("sparc-linux-gnu" ), |
2972 | "sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu" ), |
2973 | "sparc64-unknown-netbsd" => Some("sparc64--netbsd" ), |
2974 | "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris" ), |
2975 | "armv7a-none-eabi" => Some("arm-none-eabi" ), |
2976 | "armv7a-none-eabihf" => Some("arm-none-eabi" ), |
2977 | "armebv7r-none-eabi" => Some("arm-none-eabi" ), |
2978 | "armebv7r-none-eabihf" => Some("arm-none-eabi" ), |
2979 | "armv7r-none-eabi" => Some("arm-none-eabi" ), |
2980 | "armv7r-none-eabihf" => Some("arm-none-eabi" ), |
2981 | "thumbv6m-none-eabi" => Some("arm-none-eabi" ), |
2982 | "thumbv7em-none-eabi" => Some("arm-none-eabi" ), |
2983 | "thumbv7em-none-eabihf" => Some("arm-none-eabi" ), |
2984 | "thumbv7m-none-eabi" => Some("arm-none-eabi" ), |
2985 | "thumbv8m.base-none-eabi" => Some("arm-none-eabi" ), |
2986 | "thumbv8m.main-none-eabi" => Some("arm-none-eabi" ), |
2987 | "thumbv8m.main-none-eabihf" => Some("arm-none-eabi" ), |
2988 | "x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32" ), |
2989 | "x86_64-pc-windows-gnullvm" => Some("x86_64-w64-mingw32" ), |
2990 | "x86_64-uwp-windows-gnu" => Some("x86_64-w64-mingw32" ), |
2991 | "x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd" ), |
2992 | "x86_64-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ |
2993 | "x86_64-linux-gnu" , // rustfmt wrap |
2994 | ]), // explicit None if not found, so caller knows to fall back |
2995 | "x86_64-unknown-linux-musl" => Some("musl" ), |
2996 | "x86_64-unknown-netbsd" => Some("x86_64--netbsd" ), |
2997 | _ => linker_prefix, |
2998 | } |
2999 | .map(|x| x.to_owned())) |
3000 | } |
3001 | |
3002 | /// Some platforms have multiple, compatible, canonical prefixes. Look through |
3003 | /// each possible prefix for a compiler that exists and return it. The prefixes |
3004 | /// should be ordered from most-likely to least-likely. |
3005 | fn find_working_gnu_prefix(&self, prefixes: &[&'static str]) -> Option<&'static str> { |
3006 | let suffix = if self.cpp { "-g++" } else { "-gcc" }; |
3007 | let extension = std::env::consts::EXE_SUFFIX; |
3008 | |
3009 | // Loop through PATH entries searching for each toolchain. This ensures that we |
3010 | // are more likely to discover the toolchain early on, because chances are good |
3011 | // that the desired toolchain is in one of the higher-priority paths. |
3012 | env::var_os("PATH" ) |
3013 | .as_ref() |
3014 | .and_then(|path_entries| { |
3015 | env::split_paths(path_entries).find_map(|path_entry| { |
3016 | for prefix in prefixes { |
3017 | let target_compiler = format!(" {}{}{}" , prefix, suffix, extension); |
3018 | if path_entry.join(&target_compiler).exists() { |
3019 | return Some(prefix); |
3020 | } |
3021 | } |
3022 | None |
3023 | }) |
3024 | }) |
3025 | .map(|prefix| *prefix) |
3026 | .or_else(|| |
3027 | // If no toolchain was found, provide the first toolchain that was passed in. |
3028 | // This toolchain has been shown not to exist, however it will appear in the |
3029 | // error that is shown to the user which should make it easier to search for |
3030 | // where it should be obtained. |
3031 | prefixes.first().map(|prefix| *prefix)) |
3032 | } |
3033 | |
3034 | fn get_target(&self) -> Result<String, Error> { |
3035 | match self.target.clone() { |
3036 | Some(t) => Ok(t), |
3037 | None => Ok(self.getenv_unwrap("TARGET" )?), |
3038 | } |
3039 | } |
3040 | |
3041 | fn get_host(&self) -> Result<String, Error> { |
3042 | match self.host.clone() { |
3043 | Some(h) => Ok(h), |
3044 | None => Ok(self.getenv_unwrap("HOST" )?), |
3045 | } |
3046 | } |
3047 | |
3048 | fn get_opt_level(&self) -> Result<String, Error> { |
3049 | match self.opt_level.as_ref().cloned() { |
3050 | Some(ol) => Ok(ol), |
3051 | None => Ok(self.getenv_unwrap("OPT_LEVEL" )?), |
3052 | } |
3053 | } |
3054 | |
3055 | fn get_debug(&self) -> bool { |
3056 | self.debug.unwrap_or_else(|| match self.getenv("DEBUG" ) { |
3057 | Some(s) => s != "false" , |
3058 | None => false, |
3059 | }) |
3060 | } |
3061 | |
3062 | fn get_dwarf_version(&self) -> Option<u32> { |
3063 | // Tentatively matches the DWARF version defaults as of rustc 1.62. |
3064 | let target = self.get_target().ok()?; |
3065 | if target.contains("android" ) |
3066 | || target.contains("apple" ) |
3067 | || target.contains("dragonfly" ) |
3068 | || target.contains("freebsd" ) |
3069 | || target.contains("netbsd" ) |
3070 | || target.contains("openbsd" ) |
3071 | || target.contains("windows-gnu" ) |
3072 | { |
3073 | Some(2) |
3074 | } else if target.contains("linux" ) { |
3075 | Some(4) |
3076 | } else { |
3077 | None |
3078 | } |
3079 | } |
3080 | |
3081 | fn get_force_frame_pointer(&self) -> bool { |
3082 | self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) |
3083 | } |
3084 | |
3085 | fn get_out_dir(&self) -> Result<PathBuf, Error> { |
3086 | match self.out_dir.clone() { |
3087 | Some(p) => Ok(p), |
3088 | None => Ok(env::var_os("OUT_DIR" ).map(PathBuf::from).ok_or_else(|| { |
3089 | Error::new( |
3090 | ErrorKind::EnvVarNotFound, |
3091 | "Environment variable OUT_DIR not defined." , |
3092 | ) |
3093 | })?), |
3094 | } |
3095 | } |
3096 | |
3097 | fn getenv(&self, v: &str) -> Option<String> { |
3098 | // Returns true for environment variables cargo sets for build scripts: |
3099 | // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts |
3100 | // |
3101 | // This handles more of the vars than we actually use (it tries to check |
3102 | // complete-ish set), just to avoid needing maintenance if/when new |
3103 | // calls to `getenv`/`getenv_unwrap` are added. |
3104 | fn provided_by_cargo(envvar: &str) -> bool { |
3105 | match envvar { |
3106 | v if v.starts_with("CARGO" ) || v.starts_with("RUSTC" ) => true, |
3107 | "HOST" | "TARGET" | "RUSTDOC" | "OUT_DIR" | "OPT_LEVEL" | "DEBUG" | "PROFILE" |
3108 | | "NUM_JOBS" | "RUSTFLAGS" => true, |
3109 | _ => false, |
3110 | } |
3111 | } |
3112 | let mut cache = self.env_cache.lock().unwrap(); |
3113 | if let Some(val) = cache.get(v) { |
3114 | return val.clone(); |
3115 | } |
3116 | if self.emit_rerun_if_env_changed && !provided_by_cargo(v) { |
3117 | self.print(&format!("cargo:rerun-if-env-changed= {}" , v)); |
3118 | } |
3119 | let r = env::var(v).ok(); |
3120 | self.print(&format!(" {} = {:?}" , v, r)); |
3121 | cache.insert(v.to_string(), r.clone()); |
3122 | r |
3123 | } |
3124 | |
3125 | fn getenv_unwrap(&self, v: &str) -> Result<String, Error> { |
3126 | match self.getenv(v) { |
3127 | Some(s) => Ok(s), |
3128 | None => Err(Error::new( |
3129 | ErrorKind::EnvVarNotFound, |
3130 | &format!("Environment variable {} not defined." , v.to_string()), |
3131 | )), |
3132 | } |
3133 | } |
3134 | |
3135 | fn print(&self, s: &str) { |
3136 | if self.cargo_metadata { |
3137 | println!(" {}" , s); |
3138 | } |
3139 | } |
3140 | |
3141 | fn fix_env_for_apple_os(&self, cmd: &mut Command) -> Result<(), Error> { |
3142 | let target = self.get_target()?; |
3143 | let host = self.get_host()?; |
3144 | if host.contains("apple-darwin" ) && target.contains("apple-darwin" ) { |
3145 | // If, for example, `cargo` runs during the build of an XCode project, then `SDKROOT` environment variable |
3146 | // would represent the current target, and this is the problem for us, if we want to compile something |
3147 | // for the host, when host != target. |
3148 | // We can not just remove `SDKROOT`, because, again, for example, XCode add to PATH |
3149 | // /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin |
3150 | // and `cc` from this path can not find system include files, like `pthread.h`, if `SDKROOT` |
3151 | // is not set |
3152 | if let Ok(sdkroot) = env::var("SDKROOT" ) { |
3153 | if !sdkroot.contains("MacOSX" ) { |
3154 | let macos_sdk = self.apple_sdk_root("macosx" )?; |
3155 | cmd.env("SDKROOT" , macos_sdk); |
3156 | } |
3157 | } |
3158 | // Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at |
3159 | // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", |
3160 | // although this is apparently ignored when using the linker at "/usr/bin/ld". |
3161 | cmd.env_remove("IPHONEOS_DEPLOYMENT_TARGET" ); |
3162 | } |
3163 | Ok(()) |
3164 | } |
3165 | |
3166 | fn apple_sdk_root(&self, sdk: &str) -> Result<OsString, Error> { |
3167 | let mut cache = self |
3168 | .apple_sdk_root_cache |
3169 | .lock() |
3170 | .expect("apple_sdk_root_cache lock failed" ); |
3171 | if let Some(ret) = cache.get(sdk) { |
3172 | return Ok(ret.clone()); |
3173 | } |
3174 | |
3175 | let sdk_path = run_output( |
3176 | self.cmd("xcrun" ) |
3177 | .arg("--show-sdk-path" ) |
3178 | .arg("--sdk" ) |
3179 | .arg(sdk), |
3180 | "xcrun" , |
3181 | )?; |
3182 | |
3183 | let sdk_path = match String::from_utf8(sdk_path) { |
3184 | Ok(p) => p, |
3185 | Err(_) => { |
3186 | return Err(Error::new( |
3187 | ErrorKind::IOError, |
3188 | "Unable to determine Apple SDK path." , |
3189 | )); |
3190 | } |
3191 | }; |
3192 | let ret: OsString = sdk_path.trim().into(); |
3193 | cache.insert(sdk.into(), ret.clone()); |
3194 | Ok(ret) |
3195 | } |
3196 | |
3197 | fn cuda_file_count(&self) -> usize { |
3198 | self.files |
3199 | .iter() |
3200 | .filter(|file| file.extension() == Some(OsStr::new("cu" ))) |
3201 | .count() |
3202 | } |
3203 | } |
3204 | |
3205 | impl Default for Build { |
3206 | fn default() -> Build { |
3207 | Build::new() |
3208 | } |
3209 | } |
3210 | |
3211 | impl Tool { |
3212 | fn new(path: PathBuf) -> Self { |
3213 | Tool::with_features(path, None, false) |
3214 | } |
3215 | |
3216 | fn with_clang_driver(path: PathBuf, clang_driver: Option<&str>) -> Self { |
3217 | Self::with_features(path, clang_driver, false) |
3218 | } |
3219 | |
3220 | #[cfg (windows)] |
3221 | /// Explicitly set the `ToolFamily`, skipping name-based detection. |
3222 | fn with_family(path: PathBuf, family: ToolFamily) -> Self { |
3223 | Self { |
3224 | path: path, |
3225 | cc_wrapper_path: None, |
3226 | cc_wrapper_args: Vec::new(), |
3227 | args: Vec::new(), |
3228 | env: Vec::new(), |
3229 | family: family, |
3230 | cuda: false, |
3231 | removed_args: Vec::new(), |
3232 | } |
3233 | } |
3234 | |
3235 | fn with_features(path: PathBuf, clang_driver: Option<&str>, cuda: bool) -> Self { |
3236 | // Try to detect family of the tool from its name, falling back to Gnu. |
3237 | let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { |
3238 | if fname.contains("clang-cl" ) { |
3239 | ToolFamily::Msvc { clang_cl: true } |
3240 | } else if fname.ends_with("cl" ) || fname == "cl.exe" { |
3241 | ToolFamily::Msvc { clang_cl: false } |
3242 | } else if fname.contains("clang" ) { |
3243 | match clang_driver { |
3244 | Some("cl" ) => ToolFamily::Msvc { clang_cl: true }, |
3245 | _ => ToolFamily::Clang, |
3246 | } |
3247 | } else { |
3248 | ToolFamily::Gnu |
3249 | } |
3250 | } else { |
3251 | ToolFamily::Gnu |
3252 | }; |
3253 | |
3254 | Tool { |
3255 | path: path, |
3256 | cc_wrapper_path: None, |
3257 | cc_wrapper_args: Vec::new(), |
3258 | args: Vec::new(), |
3259 | env: Vec::new(), |
3260 | family: family, |
3261 | cuda: cuda, |
3262 | removed_args: Vec::new(), |
3263 | } |
3264 | } |
3265 | |
3266 | /// Add an argument to be stripped from the final command arguments. |
3267 | fn remove_arg(&mut self, flag: OsString) { |
3268 | self.removed_args.push(flag); |
3269 | } |
3270 | |
3271 | /// Add a flag, and optionally prepend the NVCC wrapper flag "-Xcompiler". |
3272 | /// |
3273 | /// Currently this is only used for compiling CUDA sources, since NVCC only |
3274 | /// accepts a limited set of GNU-like flags, and the rest must be prefixed |
3275 | /// with a "-Xcompiler" flag to get passed to the underlying C++ compiler. |
3276 | fn push_cc_arg(&mut self, flag: OsString) { |
3277 | if self.cuda { |
3278 | self.args.push("-Xcompiler" .into()); |
3279 | } |
3280 | self.args.push(flag); |
3281 | } |
3282 | |
3283 | fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool { |
3284 | let flag = flag.to_str().unwrap(); |
3285 | let mut chars = flag.chars(); |
3286 | |
3287 | // Only duplicate check compiler flags |
3288 | if self.is_like_msvc() { |
3289 | if chars.next() != Some('/' ) { |
3290 | return false; |
3291 | } |
3292 | } else if self.is_like_gnu() || self.is_like_clang() { |
3293 | if chars.next() != Some('-' ) { |
3294 | return false; |
3295 | } |
3296 | } |
3297 | |
3298 | // Check for existing optimization flags (-O, /O) |
3299 | if chars.next() == Some('O' ) { |
3300 | return self |
3301 | .args() |
3302 | .iter() |
3303 | .any(|ref a| a.to_str().unwrap_or("" ).chars().nth(1) == Some('O' )); |
3304 | } |
3305 | |
3306 | // TODO Check for existing -m..., -m...=..., /arch:... flags |
3307 | return false; |
3308 | } |
3309 | |
3310 | /// Don't push optimization arg if it conflicts with existing args |
3311 | fn push_opt_unless_duplicate(&mut self, flag: OsString) { |
3312 | if self.is_duplicate_opt_arg(&flag) { |
3313 | println!("Info: Ignoring duplicate arg {:?}" , &flag); |
3314 | } else { |
3315 | self.push_cc_arg(flag); |
3316 | } |
3317 | } |
3318 | |
3319 | /// Converts this compiler into a `Command` that's ready to be run. |
3320 | /// |
3321 | /// This is useful for when the compiler needs to be executed and the |
3322 | /// command returned will already have the initial arguments and environment |
3323 | /// variables configured. |
3324 | pub fn to_command(&self) -> Command { |
3325 | let mut cmd = match self.cc_wrapper_path { |
3326 | Some(ref cc_wrapper_path) => { |
3327 | let mut cmd = Command::new(&cc_wrapper_path); |
3328 | cmd.arg(&self.path); |
3329 | cmd |
3330 | } |
3331 | None => Command::new(&self.path), |
3332 | }; |
3333 | cmd.args(&self.cc_wrapper_args); |
3334 | |
3335 | let value = self |
3336 | .args |
3337 | .iter() |
3338 | .filter(|a| !self.removed_args.contains(a)) |
3339 | .collect::<Vec<_>>(); |
3340 | cmd.args(&value); |
3341 | |
3342 | for &(ref k, ref v) in self.env.iter() { |
3343 | cmd.env(k, v); |
3344 | } |
3345 | cmd |
3346 | } |
3347 | |
3348 | /// Returns the path for this compiler. |
3349 | /// |
3350 | /// Note that this may not be a path to a file on the filesystem, e.g. "cc", |
3351 | /// but rather something which will be resolved when a process is spawned. |
3352 | pub fn path(&self) -> &Path { |
3353 | &self.path |
3354 | } |
3355 | |
3356 | /// Returns the default set of arguments to the compiler needed to produce |
3357 | /// executables for the target this compiler generates. |
3358 | pub fn args(&self) -> &[OsString] { |
3359 | &self.args |
3360 | } |
3361 | |
3362 | /// Returns the set of environment variables needed for this compiler to |
3363 | /// operate. |
3364 | /// |
3365 | /// This is typically only used for MSVC compilers currently. |
3366 | pub fn env(&self) -> &[(OsString, OsString)] { |
3367 | &self.env |
3368 | } |
3369 | |
3370 | /// Returns the compiler command in format of CC environment variable. |
3371 | /// Or empty string if CC env was not present |
3372 | /// |
3373 | /// This is typically used by configure script |
3374 | pub fn cc_env(&self) -> OsString { |
3375 | match self.cc_wrapper_path { |
3376 | Some(ref cc_wrapper_path) => { |
3377 | let mut cc_env = cc_wrapper_path.as_os_str().to_owned(); |
3378 | cc_env.push(" " ); |
3379 | cc_env.push(self.path.to_path_buf().into_os_string()); |
3380 | for arg in self.cc_wrapper_args.iter() { |
3381 | cc_env.push(" " ); |
3382 | cc_env.push(arg); |
3383 | } |
3384 | cc_env |
3385 | } |
3386 | None => OsString::from("" ), |
3387 | } |
3388 | } |
3389 | |
3390 | /// Returns the compiler flags in format of CFLAGS environment variable. |
3391 | /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS |
3392 | /// This is typically used by configure script |
3393 | pub fn cflags_env(&self) -> OsString { |
3394 | let mut flags = OsString::new(); |
3395 | for (i, arg) in self.args.iter().enumerate() { |
3396 | if i > 0 { |
3397 | flags.push(" " ); |
3398 | } |
3399 | flags.push(arg); |
3400 | } |
3401 | flags |
3402 | } |
3403 | |
3404 | /// Whether the tool is GNU Compiler Collection-like. |
3405 | pub fn is_like_gnu(&self) -> bool { |
3406 | self.family == ToolFamily::Gnu |
3407 | } |
3408 | |
3409 | /// Whether the tool is Clang-like. |
3410 | pub fn is_like_clang(&self) -> bool { |
3411 | self.family == ToolFamily::Clang |
3412 | } |
3413 | |
3414 | /// Whether the tool is MSVC-like. |
3415 | pub fn is_like_msvc(&self) -> bool { |
3416 | match self.family { |
3417 | ToolFamily::Msvc { .. } => true, |
3418 | _ => false, |
3419 | } |
3420 | } |
3421 | } |
3422 | |
3423 | fn run(cmd: &mut Command, program: &str) -> Result<(), Error> { |
3424 | let (mut child, print) = spawn(cmd, program)?; |
3425 | let status = match child.wait() { |
3426 | Ok(s) => s, |
3427 | Err(_) => { |
3428 | return Err(Error::new( |
3429 | ErrorKind::ToolExecError, |
3430 | &format!( |
3431 | "Failed to wait on spawned child process, command {:?} with args {:?}." , |
3432 | cmd, program |
3433 | ), |
3434 | )); |
3435 | } |
3436 | }; |
3437 | print.join().unwrap(); |
3438 | println!(" {}" , status); |
3439 | |
3440 | if status.success() { |
3441 | Ok(()) |
3442 | } else { |
3443 | Err(Error::new( |
3444 | ErrorKind::ToolExecError, |
3445 | &format!( |
3446 | "Command {:?} with args {:?} did not execute successfully (status code {})." , |
3447 | cmd, program, status |
3448 | ), |
3449 | )) |
3450 | } |
3451 | } |
3452 | |
3453 | fn run_output(cmd: &mut Command, program: &str) -> Result<Vec<u8>, Error> { |
3454 | cmd.stdout(Stdio::piped()); |
3455 | let (mut child, print) = spawn(cmd, program)?; |
3456 | let mut stdout = vec![]; |
3457 | child |
3458 | .stdout |
3459 | .take() |
3460 | .unwrap() |
3461 | .read_to_end(&mut stdout) |
3462 | .unwrap(); |
3463 | let status = match child.wait() { |
3464 | Ok(s) => s, |
3465 | Err(_) => { |
3466 | return Err(Error::new( |
3467 | ErrorKind::ToolExecError, |
3468 | &format!( |
3469 | "Failed to wait on spawned child process, command {:?} with args {:?}." , |
3470 | cmd, program |
3471 | ), |
3472 | )); |
3473 | } |
3474 | }; |
3475 | print.join().unwrap(); |
3476 | println!(" {}" , status); |
3477 | |
3478 | if status.success() { |
3479 | Ok(stdout) |
3480 | } else { |
3481 | Err(Error::new( |
3482 | ErrorKind::ToolExecError, |
3483 | &format!( |
3484 | "Command {:?} with args {:?} did not execute successfully (status code {})." , |
3485 | cmd, program, status |
3486 | ), |
3487 | )) |
3488 | } |
3489 | } |
3490 | |
3491 | fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Error> { |
3492 | println!("running: {:?}" , cmd); |
3493 | |
3494 | // Capture the standard error coming from these programs, and write it out |
3495 | // with cargo:warning= prefixes. Note that this is a bit wonky to avoid |
3496 | // requiring the output to be UTF-8, we instead just ship bytes from one |
3497 | // location to another. |
3498 | match cmd.stderr(Stdio::piped()).spawn() { |
3499 | Ok(mut child) => { |
3500 | let stderr = BufReader::new(child.stderr.take().unwrap()); |
3501 | let print = thread::spawn(move || { |
3502 | for line in stderr.split(b' \n' ).filter_map(|l| l.ok()) { |
3503 | print!("cargo:warning=" ); |
3504 | std::io::stdout().write_all(&line).unwrap(); |
3505 | println!("" ); |
3506 | } |
3507 | }); |
3508 | Ok((child, print)) |
3509 | } |
3510 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { |
3511 | let extra = if cfg!(windows) { |
3512 | " (see https://github.com/rust-lang/cc-rs#compile-time-requirements \ |
3513 | for help)" |
3514 | } else { |
3515 | "" |
3516 | }; |
3517 | Err(Error::new( |
3518 | ErrorKind::ToolNotFound, |
3519 | &format!("Failed to find tool. Is ` {}` installed? {}" , program, extra), |
3520 | )) |
3521 | } |
3522 | Err(ref e) => Err(Error::new( |
3523 | ErrorKind::ToolExecError, |
3524 | &format!( |
3525 | "Command {:?} with args {:?} failed to start: {:?}" , |
3526 | cmd, program, e |
3527 | ), |
3528 | )), |
3529 | } |
3530 | } |
3531 | |
3532 | fn fail(s: &str) -> ! { |
3533 | eprintln!(" \n\nerror occurred: {}\n\n" , s); |
3534 | std::process::exit(code:1); |
3535 | } |
3536 | |
3537 | fn command_add_output_file( |
3538 | cmd: &mut Command, |
3539 | dst: &Path, |
3540 | cuda: bool, |
3541 | msvc: bool, |
3542 | clang: bool, |
3543 | is_asm: bool, |
3544 | is_arm: bool, |
3545 | ) { |
3546 | if msvc && !clang && !cuda && !(is_asm && is_arm) { |
3547 | let mut s: OsString = OsString::from("-Fo" ); |
3548 | s.push(&dst); |
3549 | cmd.arg(s); |
3550 | } else { |
3551 | cmd.arg("-o" ).arg(&dst); |
3552 | } |
3553 | } |
3554 | |
3555 | // Use by default minimum available API level |
3556 | // See note about naming here |
3557 | // https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/docs/BuildSystemMaintainers.md#Clang |
3558 | static NEW_STANDALONE_ANDROID_COMPILERS: [&str; 4] = [ |
3559 | "aarch64-linux-android21-clang" , |
3560 | "armv7a-linux-androideabi16-clang" , |
3561 | "i686-linux-android16-clang" , |
3562 | "x86_64-linux-android21-clang" , |
3563 | ]; |
3564 | |
3565 | // New "standalone" C/C++ cross-compiler executables from recent Android NDK |
3566 | // are just shell scripts that call main clang binary (from Android NDK) with |
3567 | // proper `--target` argument. |
3568 | // |
3569 | // For example, armv7a-linux-androideabi16-clang passes |
3570 | // `--target=armv7a-linux-androideabi16` to clang. |
3571 | // So to construct proper command line check if |
3572 | // `--target` argument would be passed or not to clang |
3573 | fn android_clang_compiler_uses_target_arg_internally(clang_path: &Path) -> bool { |
3574 | if let Some(filename: &OsStr) = clang_path.file_name() { |
3575 | if let Some(filename_str: &str) = filename.to_str() { |
3576 | filename_str.contains("android" ) |
3577 | } else { |
3578 | false |
3579 | } |
3580 | } else { |
3581 | false |
3582 | } |
3583 | } |
3584 | |
3585 | #[test ] |
3586 | fn test_android_clang_compiler_uses_target_arg_internally() { |
3587 | for version: i32 in 16..21 { |
3588 | assert!(android_clang_compiler_uses_target_arg_internally( |
3589 | &PathBuf::from(format!("armv7a-linux-androideabi {}-clang" , version)) |
3590 | )); |
3591 | assert!(android_clang_compiler_uses_target_arg_internally( |
3592 | &PathBuf::from(format!("armv7a-linux-androideabi {}-clang++" , version)) |
3593 | )); |
3594 | } |
3595 | assert!(!android_clang_compiler_uses_target_arg_internally( |
3596 | &PathBuf::from("clang" ) |
3597 | )); |
3598 | assert!(!android_clang_compiler_uses_target_arg_internally( |
3599 | &PathBuf::from("clang++" ) |
3600 | )); |
3601 | } |
3602 | |
3603 | fn autodetect_android_compiler(target: &str, host: &str, gnu: &str, clang: &str) -> String { |
3604 | let new_clang_key = match target { |
3605 | "aarch64-linux-android" => Some("aarch64" ), |
3606 | "armv7-linux-androideabi" => Some("armv7a" ), |
3607 | "i686-linux-android" => Some("i686" ), |
3608 | "x86_64-linux-android" => Some("x86_64" ), |
3609 | _ => None, |
3610 | }; |
3611 | |
3612 | let new_clang = new_clang_key |
3613 | .map(|key| { |
3614 | NEW_STANDALONE_ANDROID_COMPILERS |
3615 | .iter() |
3616 | .find(|x| x.starts_with(key)) |
3617 | }) |
3618 | .unwrap_or(None); |
3619 | |
3620 | if let Some(new_clang) = new_clang { |
3621 | if Command::new(new_clang).output().is_ok() { |
3622 | return (*new_clang).into(); |
3623 | } |
3624 | } |
3625 | |
3626 | let target = target |
3627 | .replace("armv7neon" , "arm" ) |
3628 | .replace("armv7" , "arm" ) |
3629 | .replace("thumbv7neon" , "arm" ) |
3630 | .replace("thumbv7" , "arm" ); |
3631 | let gnu_compiler = format!(" {}- {}" , target, gnu); |
3632 | let clang_compiler = format!(" {}- {}" , target, clang); |
3633 | |
3634 | // On Windows, the Android clang compiler is provided as a `.cmd` file instead |
3635 | // of a `.exe` file. `std::process::Command` won't run `.cmd` files unless the |
3636 | // `.cmd` is explicitly appended to the command name, so we do that here. |
3637 | let clang_compiler_cmd = format!(" {}- {}.cmd" , target, clang); |
3638 | |
3639 | // Check if gnu compiler is present |
3640 | // if not, use clang |
3641 | if Command::new(&gnu_compiler).output().is_ok() { |
3642 | gnu_compiler |
3643 | } else if host.contains("windows" ) && Command::new(&clang_compiler_cmd).output().is_ok() { |
3644 | clang_compiler_cmd |
3645 | } else { |
3646 | clang_compiler |
3647 | } |
3648 | } |
3649 | |
3650 | // Rust and clang/cc don't agree on how to name the target. |
3651 | fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option<&'static str> { |
3652 | if target.contains("x86_64" ) { |
3653 | Some("x86_64" ) |
3654 | } else if target.contains("arm64e" ) { |
3655 | Some("arm64e" ) |
3656 | } else if target.contains("aarch64" ) { |
3657 | Some("arm64" ) |
3658 | } else if target.contains("i686" ) { |
3659 | Some("i386" ) |
3660 | } else if target.contains("powerpc" ) { |
3661 | Some("ppc" ) |
3662 | } else if target.contains("powerpc64" ) { |
3663 | Some("ppc64" ) |
3664 | } else { |
3665 | None |
3666 | } |
3667 | } |
3668 | |
3669 | fn which(tool: &Path) -> Option<PathBuf> { |
3670 | fn check_exe(exe: &mut PathBuf) -> bool { |
3671 | let exe_ext: &str = std::env::consts::EXE_EXTENSION; |
3672 | exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists()) |
3673 | } |
3674 | |
3675 | // If |tool| is not just one "word," assume it's an actual path... |
3676 | if tool.components().count() > 1 { |
3677 | let mut exe: PathBuf = PathBuf::from(tool); |
3678 | return if check_exe(&mut exe) { Some(exe) } else { None }; |
3679 | } |
3680 | |
3681 | // Loop through PATH entries searching for the |tool|. |
3682 | let path_entries: OsString = env::var_os(key:"PATH" )?; |
3683 | env::split_paths(&path_entries).find_map(|path_entry: PathBuf| { |
3684 | let mut exe: PathBuf = path_entry.join(path:tool); |
3685 | return if check_exe(&mut exe) { Some(exe) } else { None }; |
3686 | }) |
3687 | } |
3688 | |
3689 | #[derive (Clone, Copy, PartialEq)] |
3690 | enum AsmFileExt { |
3691 | /// `.asm` files. On MSVC targets, we assume these should be passed to MASM |
3692 | /// (`ml{,64}.exe`). |
3693 | DotAsm, |
3694 | /// `.s` or `.S` files, which do not have the special handling on MSVC targets. |
3695 | DotS, |
3696 | } |
3697 | |
3698 | impl AsmFileExt { |
3699 | fn from_path(file: &Path) -> Option<Self> { |
3700 | if let Some(ext: &OsStr) = file.extension() { |
3701 | if let Some(ext: &str) = ext.to_str() { |
3702 | let ext: String = ext.to_lowercase(); |
3703 | match &*ext { |
3704 | "asm" => return Some(AsmFileExt::DotAsm), |
3705 | "s" => return Some(AsmFileExt::DotS), |
3706 | _ => return None, |
3707 | } |
3708 | } |
3709 | } |
3710 | None |
3711 | } |
3712 | } |
3713 | |