1 | //! A library for [Cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) |
2 | //! to compile a set of C/C++/assembly/CUDA files into a static archive for Cargo |
3 | //! to link into the crate being built. This crate does not compile code itself; |
4 | //! it calls out to the default compiler for the platform. This crate will |
5 | //! automatically detect situations such as cross compilation and |
6 | //! [various environment variables](#external-configuration-via-environment-variables) and will build code appropriately. |
7 | //! |
8 | //! # Example |
9 | //! |
10 | //! First, you'll want to both add a build script for your crate (`build.rs`) and |
11 | //! also add this crate to your `Cargo.toml` via: |
12 | //! |
13 | //! ```toml |
14 | //! [build-dependencies] |
15 | //! cc = "1.0" |
16 | //! ``` |
17 | //! |
18 | //! Next up, you'll want to write a build script like so: |
19 | //! |
20 | //! ```rust,no_run |
21 | //! // build.rs |
22 | //! cc::Build::new() |
23 | //! .file("foo.c" ) |
24 | //! .file("bar.c" ) |
25 | //! .compile("foo" ); |
26 | //! ``` |
27 | //! |
28 | //! And that's it! Running `cargo build` should take care of the rest and your Rust |
29 | //! application will now have the C files `foo.c` and `bar.c` compiled into a file |
30 | //! named `libfoo.a`. If the C files contain |
31 | //! |
32 | //! ```c |
33 | //! void foo_function(void) { ... } |
34 | //! ``` |
35 | //! |
36 | //! and |
37 | //! |
38 | //! ```c |
39 | //! int32_t bar_function(int32_t x) { ... } |
40 | //! ``` |
41 | //! |
42 | //! you can call them from Rust by declaring them in |
43 | //! your Rust code like so: |
44 | //! |
45 | //! ```rust,no_run |
46 | //! extern "C" { |
47 | //! fn foo_function(); |
48 | //! fn bar_function(x: i32) -> i32; |
49 | //! } |
50 | //! |
51 | //! pub fn call() { |
52 | //! unsafe { |
53 | //! foo_function(); |
54 | //! bar_function(42); |
55 | //! } |
56 | //! } |
57 | //! |
58 | //! fn main() { |
59 | //! call(); |
60 | //! } |
61 | //! ``` |
62 | //! |
63 | //! See [the Rustonomicon](https://doc.rust-lang.org/nomicon/ffi.html) for more details. |
64 | //! |
65 | //! # External configuration via environment variables |
66 | //! |
67 | //! To control the programs and flags used for building, the builder can set a |
68 | //! number of different environment variables. |
69 | //! |
70 | //! * `CFLAGS` - a series of space separated flags passed to compilers. Note that |
71 | //! individual flags cannot currently contain spaces, so doing |
72 | //! something like: `-L=foo\ bar` is not possible. |
73 | //! * `CC` - the actual C compiler used. Note that this is used as an exact |
74 | //! executable name, so (for example) no extra flags can be passed inside |
75 | //! this variable, and the builder must ensure that there aren't any |
76 | //! trailing spaces. This compiler must understand the `-c` flag. For |
77 | //! certain `TARGET`s, it also is assumed to know about other flags (most |
78 | //! common is `-fPIC`). |
79 | //! * `AR` - the `ar` (archiver) executable to use to build the static library. |
80 | //! * `CRATE_CC_NO_DEFAULTS` - the default compiler flags may cause conflicts in |
81 | //! some cross compiling scenarios. Setting this variable |
82 | //! will disable the generation of default compiler |
83 | //! flags. |
84 | //! * `CC_ENABLE_DEBUG_OUTPUT` - if set, compiler command invocations and exit codes will |
85 | //! be logged to stdout. This is useful for debugging build script issues, but can be |
86 | //! overly verbose for normal use. |
87 | //! * `CC_SHELL_ESCAPED_FLAGS` - if set, `*FLAGS` will be parsed as if they were shell |
88 | //! arguments (similar to `make` and `cmake`) rather than splitting them on each space. |
89 | //! For example, with `CFLAGS='a "b c"'`, the compiler will be invoked with 2 arguments - |
90 | //! `a` and `b c` - rather than 3: `a`, `"b` and `c"`. |
91 | //! * `CXX...` - see [C++ Support](#c-support). |
92 | //! |
93 | //! Furthermore, projects using this crate may specify custom environment variables |
94 | //! to be inspected, for example via the `Build::try_flags_from_environment` |
95 | //! function. Consult the project’s own documentation or its use of the `cc` crate |
96 | //! for any additional variables it may use. |
97 | //! |
98 | //! Each of these variables can also be supplied with certain prefixes and suffixes, |
99 | //! in the following prioritized order: |
100 | //! |
101 | //! 1. `<var>_<target>` - for example, `CC_x86_64-unknown-linux-gnu` |
102 | //! 2. `<var>_<target_with_underscores>` - for example, `CC_x86_64_unknown_linux_gnu` |
103 | //! 3. `<build-kind>_<var>` - for example, `HOST_CC` or `TARGET_CFLAGS` |
104 | //! 4. `<var>` - a plain `CC`, `AR` as above. |
105 | //! |
106 | //! If none of these variables exist, cc-rs uses built-in defaults. |
107 | //! |
108 | //! In addition to the above optional environment variables, `cc-rs` has some |
109 | //! functions with hard requirements on some variables supplied by [cargo's |
110 | //! build-script driver][cargo] that it has the `TARGET`, `OUT_DIR`, `OPT_LEVEL`, |
111 | //! and `HOST` variables. |
112 | //! |
113 | //! [cargo]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script |
114 | //! |
115 | //! # Optional features |
116 | //! |
117 | //! ## Parallel |
118 | //! |
119 | //! Currently cc-rs supports parallel compilation (think `make -jN`) but this |
120 | //! feature is turned off by default. To enable cc-rs to compile C/C++ in parallel, |
121 | //! you can change your dependency to: |
122 | //! |
123 | //! ```toml |
124 | //! [build-dependencies] |
125 | //! cc = { version = "1.0", features = ["parallel"] } |
126 | //! ``` |
127 | //! |
128 | //! By default cc-rs will limit parallelism to `$NUM_JOBS`, or if not present it |
129 | //! will limit it to the number of cpus on the machine. If you are using cargo, |
130 | //! use `-jN` option of `build`, `test` and `run` commands as `$NUM_JOBS` |
131 | //! is supplied by cargo. |
132 | //! |
133 | //! # Compile-time Requirements |
134 | //! |
135 | //! To work properly this crate needs access to a C compiler when the build script |
136 | //! is being run. This crate does not ship a C compiler with it. The compiler |
137 | //! required varies per platform, but there are three broad categories: |
138 | //! |
139 | //! * Unix platforms require `cc` to be the C compiler. This can be found by |
140 | //! installing cc/clang on Linux distributions and Xcode on macOS, for example. |
141 | //! * Windows platforms targeting MSVC (e.g. your target triple ends in `-msvc`) |
142 | //! require Visual Studio to be installed. `cc-rs` attempts to locate it, and |
143 | //! if it fails, `cl.exe` is expected to be available in `PATH`. This can be |
144 | //! set up by running the appropriate developer tools shell. |
145 | //! * Windows platforms targeting MinGW (e.g. your target triple ends in `-gnu`) |
146 | //! require `cc` to be available in `PATH`. We recommend the |
147 | //! [MinGW-w64](https://www.mingw-w64.org/) distribution. |
148 | //! You may also acquire it via |
149 | //! [MSYS2](https://www.msys2.org/), as explained [here][msys2-help]. Make sure |
150 | //! to install the appropriate architecture corresponding to your installation of |
151 | //! rustc. GCC from older [MinGW](http://www.mingw.org/) project is compatible |
152 | //! only with 32-bit rust compiler. |
153 | //! |
154 | //! [msys2-help]: https://github.com/rust-lang/rust/blob/master/INSTALL.md#building-on-windows |
155 | //! |
156 | //! # C++ support |
157 | //! |
158 | //! `cc-rs` supports C++ libraries compilation by using the `cpp` method on |
159 | //! `Build`: |
160 | //! |
161 | //! ```rust,no_run |
162 | //! cc::Build::new() |
163 | //! .cpp(true) // Switch to C++ library compilation. |
164 | //! .file("foo.cpp" ) |
165 | //! .compile("foo" ); |
166 | //! ``` |
167 | //! |
168 | //! For C++ libraries, the `CXX` and `CXXFLAGS` environment variables are used instead of `CC` and `CFLAGS`. |
169 | //! |
170 | //! The C++ standard library may be linked to the crate target. By default it's `libc++` for macOS, FreeBSD, and OpenBSD, `libc++_shared` for Android, nothing for MSVC, and `libstdc++` for anything else. It can be changed in one of two ways: |
171 | //! |
172 | //! 1. by using the `cpp_link_stdlib` method on `Build`: |
173 | //! ```rust,no_run |
174 | //! cc::Build::new() |
175 | //! .cpp(true) |
176 | //! .file("foo.cpp" ) |
177 | //! .cpp_link_stdlib("stdc++" ) // use libstdc++ |
178 | //! .compile("foo" ); |
179 | //! ``` |
180 | //! 2. by setting the `CXXSTDLIB` environment variable. |
181 | //! |
182 | //! In particular, for Android you may want to [use `c++_static` if you have at most one shared library](https://developer.android.com/ndk/guides/cpp-support). |
183 | //! |
184 | //! Remember that C++ does name mangling so `extern "C"` might be required to enable Rust linker to find your functions. |
185 | //! |
186 | //! # CUDA C++ support |
187 | //! |
188 | //! `cc-rs` also supports compiling CUDA C++ libraries by using the `cuda` method |
189 | //! on `Build`: |
190 | //! |
191 | //! ```rust,no_run |
192 | //! cc::Build::new() |
193 | //! // Switch to CUDA C++ library compilation using NVCC. |
194 | //! .cuda(true) |
195 | //! .cudart("static" ) |
196 | //! // Generate code for Maxwell (GTX 970, 980, 980 Ti, Titan X). |
197 | //! .flag("-gencode" ).flag("arch=compute_52,code=sm_52" ) |
198 | //! // Generate code for Maxwell (Jetson TX1). |
199 | //! .flag("-gencode" ).flag("arch=compute_53,code=sm_53" ) |
200 | //! // Generate code for Pascal (GTX 1070, 1080, 1080 Ti, Titan Xp). |
201 | //! .flag("-gencode" ).flag("arch=compute_61,code=sm_61" ) |
202 | //! // Generate code for Pascal (Tesla P100). |
203 | //! .flag("-gencode" ).flag("arch=compute_60,code=sm_60" ) |
204 | //! // Generate code for Pascal (Jetson TX2). |
205 | //! .flag("-gencode" ).flag("arch=compute_62,code=sm_62" ) |
206 | //! // Generate code in parallel |
207 | //! .flag("-t0" ) |
208 | //! .file("bar.cu" ) |
209 | //! .compile("bar" ); |
210 | //! ``` |
211 | |
212 | #![doc (html_root_url = "https://docs.rs/cc/1.0" )] |
213 | #![deny (warnings)] |
214 | #![deny (missing_docs)] |
215 | #![deny (clippy::disallowed_methods)] |
216 | #![warn (clippy::doc_markdown)] |
217 | |
218 | use std::borrow::Cow; |
219 | use std::collections::HashMap; |
220 | use std::env; |
221 | use std::ffi::{OsStr, OsString}; |
222 | use std::fmt::{self, Display, Formatter}; |
223 | use std::fs; |
224 | use std::io::{self, Write}; |
225 | use std::path::{Component, Path, PathBuf}; |
226 | #[cfg (feature = "parallel" )] |
227 | use std::process::Child; |
228 | use std::process::Command; |
229 | use std::sync::{Arc, RwLock}; |
230 | |
231 | use shlex::Shlex; |
232 | |
233 | #[cfg (feature = "parallel" )] |
234 | mod parallel; |
235 | mod windows; |
236 | // Regardless of whether this should be in this crate's public API, |
237 | // it has been since 2015, so don't break it. |
238 | pub use windows::find_tools as windows_registry; |
239 | |
240 | mod command_helpers; |
241 | use command_helpers::*; |
242 | |
243 | mod tool; |
244 | pub use tool::Tool; |
245 | use tool::ToolFamily; |
246 | |
247 | mod target_info; |
248 | mod tempfile; |
249 | |
250 | mod utilities; |
251 | use utilities::*; |
252 | |
253 | #[derive (Debug, Eq, PartialEq, Hash)] |
254 | struct CompilerFlag { |
255 | compiler: Box<Path>, |
256 | flag: Box<OsStr>, |
257 | } |
258 | |
259 | type Env = Option<Arc<OsStr>>; |
260 | |
261 | /// A builder for compilation of a native library. |
262 | /// |
263 | /// A `Build` is the main type of the `cc` crate and is used to control all the |
264 | /// various configuration options and such of a compile. You'll find more |
265 | /// documentation on each method itself. |
266 | #[derive (Clone, Debug)] |
267 | pub struct Build { |
268 | include_directories: Vec<Arc<Path>>, |
269 | definitions: Vec<(Arc<str>, Option<Arc<str>>)>, |
270 | objects: Vec<Arc<Path>>, |
271 | flags: Vec<Arc<OsStr>>, |
272 | flags_supported: Vec<Arc<OsStr>>, |
273 | known_flag_support_status_cache: Arc<RwLock<HashMap<CompilerFlag, bool>>>, |
274 | ar_flags: Vec<Arc<OsStr>>, |
275 | asm_flags: Vec<Arc<OsStr>>, |
276 | no_default_flags: bool, |
277 | files: Vec<Arc<Path>>, |
278 | cpp: bool, |
279 | cpp_link_stdlib: Option<Option<Arc<str>>>, |
280 | cpp_set_stdlib: Option<Arc<str>>, |
281 | cuda: bool, |
282 | cudart: Option<Arc<str>>, |
283 | ccbin: bool, |
284 | std: Option<Arc<str>>, |
285 | target: Option<Arc<str>>, |
286 | /// The host compiler. |
287 | /// |
288 | /// Try to not access this directly, and instead prefer `cfg!(...)`. |
289 | host: Option<Arc<str>>, |
290 | out_dir: Option<Arc<Path>>, |
291 | opt_level: Option<Arc<str>>, |
292 | debug: Option<bool>, |
293 | force_frame_pointer: Option<bool>, |
294 | env: Vec<(Arc<OsStr>, Arc<OsStr>)>, |
295 | compiler: Option<Arc<Path>>, |
296 | archiver: Option<Arc<Path>>, |
297 | ranlib: Option<Arc<Path>>, |
298 | cargo_output: CargoOutput, |
299 | link_lib_modifiers: Vec<Arc<OsStr>>, |
300 | pic: Option<bool>, |
301 | use_plt: Option<bool>, |
302 | static_crt: Option<bool>, |
303 | shared_flag: Option<bool>, |
304 | static_flag: Option<bool>, |
305 | warnings_into_errors: bool, |
306 | warnings: Option<bool>, |
307 | extra_warnings: Option<bool>, |
308 | env_cache: Arc<RwLock<HashMap<Box<str>, Env>>>, |
309 | apple_sdk_root_cache: Arc<RwLock<HashMap<Box<str>, Arc<OsStr>>>>, |
310 | apple_versions_cache: Arc<RwLock<HashMap<Box<str>, Arc<str>>>>, |
311 | emit_rerun_if_env_changed: bool, |
312 | cached_compiler_family: Arc<RwLock<HashMap<Box<Path>, ToolFamily>>>, |
313 | shell_escaped_flags: Option<bool>, |
314 | } |
315 | |
316 | /// Represents the types of errors that may occur while using cc-rs. |
317 | #[derive (Clone, Debug)] |
318 | enum ErrorKind { |
319 | /// Error occurred while performing I/O. |
320 | IOError, |
321 | /// Invalid architecture supplied. |
322 | ArchitectureInvalid, |
323 | /// Environment variable not found, with the var in question as extra info. |
324 | EnvVarNotFound, |
325 | /// Error occurred while using external tools (ie: invocation of compiler). |
326 | ToolExecError, |
327 | /// Error occurred due to missing external tools. |
328 | ToolNotFound, |
329 | /// One of the function arguments failed validation. |
330 | InvalidArgument, |
331 | /// No known macro is defined for the compiler when discovering tool family |
332 | ToolFamilyMacroNotFound, |
333 | /// Invalid target |
334 | InvalidTarget, |
335 | #[cfg (feature = "parallel" )] |
336 | /// jobserver helpthread failure |
337 | JobserverHelpThreadError, |
338 | } |
339 | |
340 | /// Represents an internal error that occurred, with an explanation. |
341 | #[derive (Clone, Debug)] |
342 | pub struct Error { |
343 | /// Describes the kind of error that occurred. |
344 | kind: ErrorKind, |
345 | /// More explanation of error that occurred. |
346 | message: Cow<'static, str>, |
347 | } |
348 | |
349 | impl Error { |
350 | fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Error { |
351 | Error { |
352 | kind, |
353 | message: message.into(), |
354 | } |
355 | } |
356 | } |
357 | |
358 | impl From<io::Error> for Error { |
359 | fn from(e: io::Error) -> Error { |
360 | Error::new(kind:ErrorKind::IOError, message:format!(" {}" , e)) |
361 | } |
362 | } |
363 | |
364 | impl Display for Error { |
365 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
366 | write!(f, " {:?}: {}" , self.kind, self.message) |
367 | } |
368 | } |
369 | |
370 | impl std::error::Error for Error {} |
371 | |
372 | /// Represents an object. |
373 | /// |
374 | /// This is a source file -> object file pair. |
375 | #[derive (Clone, Debug)] |
376 | struct Object { |
377 | src: PathBuf, |
378 | dst: PathBuf, |
379 | } |
380 | |
381 | impl Object { |
382 | /// Create a new source file -> object file pair. |
383 | fn new(src: PathBuf, dst: PathBuf) -> Object { |
384 | Object { src, dst } |
385 | } |
386 | } |
387 | |
388 | impl Build { |
389 | /// Construct a new instance of a blank set of configuration. |
390 | /// |
391 | /// This builder is finished with the [`compile`] function. |
392 | /// |
393 | /// [`compile`]: struct.Build.html#method.compile |
394 | pub fn new() -> Build { |
395 | Build { |
396 | include_directories: Vec::new(), |
397 | definitions: Vec::new(), |
398 | objects: Vec::new(), |
399 | flags: Vec::new(), |
400 | flags_supported: Vec::new(), |
401 | known_flag_support_status_cache: Arc::new(RwLock::new(HashMap::new())), |
402 | ar_flags: Vec::new(), |
403 | asm_flags: Vec::new(), |
404 | no_default_flags: false, |
405 | files: Vec::new(), |
406 | shared_flag: None, |
407 | static_flag: None, |
408 | cpp: false, |
409 | cpp_link_stdlib: None, |
410 | cpp_set_stdlib: None, |
411 | cuda: false, |
412 | cudart: None, |
413 | ccbin: true, |
414 | std: None, |
415 | target: None, |
416 | host: None, |
417 | out_dir: None, |
418 | opt_level: None, |
419 | debug: None, |
420 | force_frame_pointer: None, |
421 | env: Vec::new(), |
422 | compiler: None, |
423 | archiver: None, |
424 | ranlib: None, |
425 | cargo_output: CargoOutput::new(), |
426 | link_lib_modifiers: Vec::new(), |
427 | pic: None, |
428 | use_plt: None, |
429 | static_crt: None, |
430 | warnings: None, |
431 | extra_warnings: None, |
432 | warnings_into_errors: false, |
433 | env_cache: Arc::new(RwLock::new(HashMap::new())), |
434 | apple_sdk_root_cache: Arc::new(RwLock::new(HashMap::new())), |
435 | apple_versions_cache: Arc::new(RwLock::new(HashMap::new())), |
436 | emit_rerun_if_env_changed: true, |
437 | cached_compiler_family: Arc::default(), |
438 | shell_escaped_flags: None, |
439 | } |
440 | } |
441 | |
442 | /// Add a directory to the `-I` or include path for headers |
443 | /// |
444 | /// # Example |
445 | /// |
446 | /// ```no_run |
447 | /// use std::path::Path; |
448 | /// |
449 | /// let library_path = Path::new("/path/to/library" ); |
450 | /// |
451 | /// cc::Build::new() |
452 | /// .file("src/foo.c" ) |
453 | /// .include(library_path) |
454 | /// .include("src" ) |
455 | /// .compile("foo" ); |
456 | /// ``` |
457 | pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Build { |
458 | self.include_directories.push(dir.as_ref().into()); |
459 | self |
460 | } |
461 | |
462 | /// Add multiple directories to the `-I` include path. |
463 | /// |
464 | /// # Example |
465 | /// |
466 | /// ```no_run |
467 | /// # use std::path::Path; |
468 | /// # let condition = true; |
469 | /// # |
470 | /// let mut extra_dir = None; |
471 | /// if condition { |
472 | /// extra_dir = Some(Path::new("/path/to" )); |
473 | /// } |
474 | /// |
475 | /// cc::Build::new() |
476 | /// .file("src/foo.c" ) |
477 | /// .includes(extra_dir) |
478 | /// .compile("foo" ); |
479 | /// ``` |
480 | pub fn includes<P>(&mut self, dirs: P) -> &mut Build |
481 | where |
482 | P: IntoIterator, |
483 | P::Item: AsRef<Path>, |
484 | { |
485 | for dir in dirs { |
486 | self.include(dir); |
487 | } |
488 | self |
489 | } |
490 | |
491 | /// Specify a `-D` variable with an optional value. |
492 | /// |
493 | /// # Example |
494 | /// |
495 | /// ```no_run |
496 | /// cc::Build::new() |
497 | /// .file("src/foo.c" ) |
498 | /// .define("FOO" , "BAR" ) |
499 | /// .define("BAZ" , None) |
500 | /// .compile("foo" ); |
501 | /// ``` |
502 | pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build { |
503 | self.definitions |
504 | .push((var.into(), val.into().map(Into::into))); |
505 | self |
506 | } |
507 | |
508 | /// Add an arbitrary object file to link in |
509 | pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Build { |
510 | self.objects.push(obj.as_ref().into()); |
511 | self |
512 | } |
513 | |
514 | /// Add arbitrary object files to link in |
515 | pub fn objects<P>(&mut self, objs: P) -> &mut Build |
516 | where |
517 | P: IntoIterator, |
518 | P::Item: AsRef<Path>, |
519 | { |
520 | for obj in objs { |
521 | self.object(obj); |
522 | } |
523 | self |
524 | } |
525 | |
526 | /// Add an arbitrary flag to the invocation of the compiler |
527 | /// |
528 | /// # Example |
529 | /// |
530 | /// ```no_run |
531 | /// cc::Build::new() |
532 | /// .file("src/foo.c" ) |
533 | /// .flag("-ffunction-sections" ) |
534 | /// .compile("foo" ); |
535 | /// ``` |
536 | pub fn flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build { |
537 | self.flags.push(flag.as_ref().into()); |
538 | self |
539 | } |
540 | |
541 | /// Removes a compiler flag that was added by [`Build::flag`]. |
542 | /// |
543 | /// Will not remove flags added by other means (default flags, |
544 | /// flags from env, and so on). |
545 | /// |
546 | /// # Example |
547 | /// ``` |
548 | /// cc::Build::new() |
549 | /// .file("src/foo.c" ) |
550 | /// .flag("unwanted_flag" ) |
551 | /// .remove_flag("unwanted_flag" ); |
552 | /// ``` |
553 | |
554 | pub fn remove_flag(&mut self, flag: &str) -> &mut Build { |
555 | self.flags.retain(|other_flag| &**other_flag != flag); |
556 | self |
557 | } |
558 | |
559 | /// Add a flag to the invocation of the ar |
560 | /// |
561 | /// # Example |
562 | /// |
563 | /// ```no_run |
564 | /// cc::Build::new() |
565 | /// .file("src/foo.c" ) |
566 | /// .file("src/bar.c" ) |
567 | /// .ar_flag("/NODEFAULTLIB:libc.dll" ) |
568 | /// .compile("foo" ); |
569 | /// ``` |
570 | pub fn ar_flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build { |
571 | self.ar_flags.push(flag.as_ref().into()); |
572 | self |
573 | } |
574 | |
575 | /// Add a flag that will only be used with assembly files. |
576 | /// |
577 | /// The flag will be applied to input files with either a `.s` or |
578 | /// `.asm` extension (case insensitive). |
579 | /// |
580 | /// # Example |
581 | /// |
582 | /// ```no_run |
583 | /// cc::Build::new() |
584 | /// .asm_flag("-Wa,-defsym,abc=1" ) |
585 | /// .file("src/foo.S" ) // The asm flag will be applied here |
586 | /// .file("src/bar.c" ) // The asm flag will not be applied here |
587 | /// .compile("foo" ); |
588 | /// ``` |
589 | pub fn asm_flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build { |
590 | self.asm_flags.push(flag.as_ref().into()); |
591 | self |
592 | } |
593 | |
594 | fn ensure_check_file(&self) -> Result<PathBuf, Error> { |
595 | let out_dir = self.get_out_dir()?; |
596 | let src = if self.cuda { |
597 | assert!(self.cpp); |
598 | out_dir.join("flag_check.cu" ) |
599 | } else if self.cpp { |
600 | out_dir.join("flag_check.cpp" ) |
601 | } else { |
602 | out_dir.join("flag_check.c" ) |
603 | }; |
604 | |
605 | if !src.exists() { |
606 | let mut f = fs::File::create(&src)?; |
607 | write!(f, "int main(void) {{ return 0; }}" )?; |
608 | } |
609 | |
610 | Ok(src) |
611 | } |
612 | |
613 | /// Run the compiler to test if it accepts the given flag. |
614 | /// |
615 | /// For a convenience method for setting flags conditionally, |
616 | /// see `flag_if_supported()`. |
617 | /// |
618 | /// It may return error if it's unable to run the compiler with a test file |
619 | /// (e.g. the compiler is missing or a write to the `out_dir` failed). |
620 | /// |
621 | /// Note: Once computed, the result of this call is stored in the |
622 | /// `known_flag_support` field. If `is_flag_supported(flag)` |
623 | /// is called again, the result will be read from the hash table. |
624 | pub fn is_flag_supported(&self, flag: impl AsRef<OsStr>) -> Result<bool, Error> { |
625 | self.is_flag_supported_inner( |
626 | flag.as_ref(), |
627 | self.get_base_compiler()?.path(), |
628 | &self.get_target()?, |
629 | ) |
630 | } |
631 | |
632 | fn is_flag_supported_inner( |
633 | &self, |
634 | flag: &OsStr, |
635 | compiler_path: &Path, |
636 | target: &str, |
637 | ) -> Result<bool, Error> { |
638 | let compiler_flag = CompilerFlag { |
639 | compiler: compiler_path.into(), |
640 | flag: flag.into(), |
641 | }; |
642 | |
643 | if let Some(is_supported) = self |
644 | .known_flag_support_status_cache |
645 | .read() |
646 | .unwrap() |
647 | .get(&compiler_flag) |
648 | .cloned() |
649 | { |
650 | return Ok(is_supported); |
651 | } |
652 | |
653 | let out_dir = self.get_out_dir()?; |
654 | let src = self.ensure_check_file()?; |
655 | let obj = out_dir.join("flag_check" ); |
656 | |
657 | let mut compiler = { |
658 | let mut cfg = Build::new(); |
659 | cfg.flag(flag) |
660 | .compiler(compiler_path) |
661 | .cargo_metadata(self.cargo_output.metadata) |
662 | .target(target) |
663 | .opt_level(0) |
664 | .debug(false) |
665 | .cpp(self.cpp) |
666 | .cuda(self.cuda) |
667 | .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed); |
668 | if let Some(host) = &self.host { |
669 | cfg.host(host); |
670 | } |
671 | cfg.try_get_compiler()? |
672 | }; |
673 | |
674 | // Clang uses stderr for verbose output, which yields a false positive |
675 | // result if the CFLAGS/CXXFLAGS include -v to aid in debugging. |
676 | if compiler.family.verbose_stderr() { |
677 | compiler.remove_arg("-v" .into()); |
678 | } |
679 | if compiler.is_like_clang() { |
680 | // Avoid reporting that the arg is unsupported just because the |
681 | // compiler complains that it wasn't used. |
682 | compiler.push_cc_arg("-Wno-unused-command-line-argument" .into()); |
683 | } |
684 | |
685 | let mut cmd = compiler.to_command(); |
686 | let is_arm = target.contains("aarch64" ) || target.contains("arm" ); |
687 | let clang = compiler.is_like_clang(); |
688 | let gnu = compiler.family == ToolFamily::Gnu; |
689 | command_add_output_file( |
690 | &mut cmd, |
691 | &obj, |
692 | CmdAddOutputFileArgs { |
693 | cuda: self.cuda, |
694 | is_assembler_msvc: false, |
695 | msvc: compiler.is_like_msvc(), |
696 | clang, |
697 | gnu, |
698 | is_asm: false, |
699 | is_arm, |
700 | }, |
701 | ); |
702 | |
703 | // Checking for compiler flags does not require linking |
704 | cmd.arg("-c" ); |
705 | |
706 | cmd.arg(&src); |
707 | |
708 | let output = cmd.output()?; |
709 | let is_supported = output.status.success() && output.stderr.is_empty(); |
710 | |
711 | self.known_flag_support_status_cache |
712 | .write() |
713 | .unwrap() |
714 | .insert(compiler_flag, is_supported); |
715 | |
716 | Ok(is_supported) |
717 | } |
718 | |
719 | /// Add an arbitrary flag to the invocation of the compiler if it supports it |
720 | /// |
721 | /// # Example |
722 | /// |
723 | /// ```no_run |
724 | /// cc::Build::new() |
725 | /// .file("src/foo.c" ) |
726 | /// .flag_if_supported("-Wlogical-op" ) // only supported by GCC |
727 | /// .flag_if_supported("-Wunreachable-code" ) // only supported by clang |
728 | /// .compile("foo" ); |
729 | /// ``` |
730 | pub fn flag_if_supported(&mut self, flag: impl AsRef<OsStr>) -> &mut Build { |
731 | self.flags_supported.push(flag.as_ref().into()); |
732 | self |
733 | } |
734 | |
735 | /// Add flags from the specified environment variable. |
736 | /// |
737 | /// Normally the `cc` crate will consult with the standard set of environment |
738 | /// variables (such as `CFLAGS` and `CXXFLAGS`) to construct the compiler invocation. Use of |
739 | /// this method provides additional levers for the end user to use when configuring the build |
740 | /// process. |
741 | /// |
742 | /// Just like the standard variables, this method will search for an environment variable with |
743 | /// appropriate target prefixes, when appropriate. |
744 | /// |
745 | /// # Examples |
746 | /// |
747 | /// This method is particularly beneficial in introducing the ability to specify crate-specific |
748 | /// flags. |
749 | /// |
750 | /// ```no_run |
751 | /// cc::Build::new() |
752 | /// .file("src/foo.c" ) |
753 | /// .try_flags_from_environment(concat!(env!("CARGO_PKG_NAME" ), "_CFLAGS" )) |
754 | /// .expect("the environment variable must be specified and UTF-8" ) |
755 | /// .compile("foo" ); |
756 | /// ``` |
757 | /// |
758 | pub fn try_flags_from_environment(&mut self, environ_key: &str) -> Result<&mut Build, Error> { |
759 | let flags = self.envflags(environ_key)?; |
760 | self.flags.extend( |
761 | flags |
762 | .into_iter() |
763 | .map(|flag| Arc::from(OsString::from(flag).as_os_str())), |
764 | ); |
765 | Ok(self) |
766 | } |
767 | |
768 | /// Set the `-shared` flag. |
769 | /// |
770 | /// When enabled, the compiler will produce a shared object which can |
771 | /// then be linked with other objects to form an executable. |
772 | /// |
773 | /// # Example |
774 | /// |
775 | /// ```no_run |
776 | /// cc::Build::new() |
777 | /// .file("src/foo.c" ) |
778 | /// .shared_flag(true) |
779 | /// .compile("libfoo.so" ); |
780 | /// ``` |
781 | pub fn shared_flag(&mut self, shared_flag: bool) -> &mut Build { |
782 | self.shared_flag = Some(shared_flag); |
783 | self |
784 | } |
785 | |
786 | /// Set the `-static` flag. |
787 | /// |
788 | /// When enabled on systems that support dynamic linking, this prevents |
789 | /// linking with the shared libraries. |
790 | /// |
791 | /// # Example |
792 | /// |
793 | /// ```no_run |
794 | /// cc::Build::new() |
795 | /// .file("src/foo.c" ) |
796 | /// .shared_flag(true) |
797 | /// .static_flag(true) |
798 | /// .compile("foo" ); |
799 | /// ``` |
800 | pub fn static_flag(&mut self, static_flag: bool) -> &mut Build { |
801 | self.static_flag = Some(static_flag); |
802 | self |
803 | } |
804 | |
805 | /// Disables the generation of default compiler flags. The default compiler |
806 | /// flags may cause conflicts in some cross compiling scenarios. |
807 | /// |
808 | /// Setting the `CRATE_CC_NO_DEFAULTS` environment variable has the same |
809 | /// effect as setting this to `true`. The presence of the environment |
810 | /// variable and the value of `no_default_flags` will be OR'd together. |
811 | pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Build { |
812 | self.no_default_flags = no_default_flags; |
813 | self |
814 | } |
815 | |
816 | /// Add a file which will be compiled |
817 | pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build { |
818 | self.files.push(p.as_ref().into()); |
819 | self |
820 | } |
821 | |
822 | /// Add files which will be compiled |
823 | pub fn files<P>(&mut self, p: P) -> &mut Build |
824 | where |
825 | P: IntoIterator, |
826 | P::Item: AsRef<Path>, |
827 | { |
828 | for file in p.into_iter() { |
829 | self.file(file); |
830 | } |
831 | self |
832 | } |
833 | |
834 | /// Get the files which will be compiled |
835 | pub fn get_files(&self) -> impl Iterator<Item = &Path> { |
836 | self.files.iter().map(AsRef::as_ref) |
837 | } |
838 | |
839 | /// Set C++ support. |
840 | /// |
841 | /// The other `cpp_*` options will only become active if this is set to |
842 | /// `true`. |
843 | /// |
844 | /// The name of the C++ standard library to link is decided by: |
845 | /// 1. If [`cpp_link_stdlib`](Build::cpp_link_stdlib) is set, use its value. |
846 | /// 2. Else if the `CXXSTDLIB` environment variable is set, use its value. |
847 | /// 3. Else the default is `c++` for OS X and BSDs, `c++_shared` for Android, |
848 | /// `None` for MSVC and `stdc++` for anything else. |
849 | pub fn cpp(&mut self, cpp: bool) -> &mut Build { |
850 | self.cpp = cpp; |
851 | self |
852 | } |
853 | |
854 | /// Set CUDA C++ support. |
855 | /// |
856 | /// Enabling CUDA will invoke the CUDA compiler, NVCC. While NVCC accepts |
857 | /// the most common compiler flags, e.g. `-std=c++17`, some project-specific |
858 | /// flags might have to be prefixed with "-Xcompiler" flag, for example as |
859 | /// `.flag("-Xcompiler").flag("-fpermissive")`. See the documentation for |
860 | /// `nvcc`, the CUDA compiler driver, at <https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/> |
861 | /// for more information. |
862 | /// |
863 | /// If enabled, this also implicitly enables C++ support. |
864 | pub fn cuda(&mut self, cuda: bool) -> &mut Build { |
865 | self.cuda = cuda; |
866 | if cuda { |
867 | self.cpp = true; |
868 | self.cudart = Some("static" .into()); |
869 | } |
870 | self |
871 | } |
872 | |
873 | /// Link CUDA run-time. |
874 | /// |
875 | /// This option mimics the `--cudart` NVCC command-line option. Just like |
876 | /// the original it accepts `{none|shared|static}`, with default being |
877 | /// `static`. The method has to be invoked after `.cuda(true)`, or not |
878 | /// at all, if the default is right for the project. |
879 | pub fn cudart(&mut self, cudart: &str) -> &mut Build { |
880 | if self.cuda { |
881 | self.cudart = Some(cudart.into()); |
882 | } |
883 | self |
884 | } |
885 | |
886 | /// Set CUDA host compiler. |
887 | /// |
888 | /// By default, a `-ccbin` flag will be passed to NVCC to specify the |
889 | /// underlying host compiler. The value of `-ccbin` is the same as the |
890 | /// chosen C++ compiler. This is not always desired, because NVCC might |
891 | /// not support that compiler. In this case, you can remove the `-ccbin` |
892 | /// flag so that NVCC will choose the host compiler by itself. |
893 | pub fn ccbin(&mut self, ccbin: bool) -> &mut Build { |
894 | self.ccbin = ccbin; |
895 | self |
896 | } |
897 | |
898 | /// Specify the C or C++ language standard version. |
899 | /// |
900 | /// These values are common to modern versions of GCC, Clang and MSVC: |
901 | /// - `c11` for ISO/IEC 9899:2011 |
902 | /// - `c17` for ISO/IEC 9899:2018 |
903 | /// - `c++14` for ISO/IEC 14882:2014 |
904 | /// - `c++17` for ISO/IEC 14882:2017 |
905 | /// - `c++20` for ISO/IEC 14882:2020 |
906 | /// |
907 | /// Other values have less broad support, e.g. MSVC does not support `c++11` |
908 | /// (`c++14` is the minimum), `c89` (omit the flag instead) or `c99`. |
909 | /// |
910 | /// For compiling C++ code, you should also set `.cpp(true)`. |
911 | /// |
912 | /// The default is that no standard flag is passed to the compiler, so the |
913 | /// language version will be the compiler's default. |
914 | /// |
915 | /// # Example |
916 | /// |
917 | /// ```no_run |
918 | /// cc::Build::new() |
919 | /// .file("src/modern.cpp" ) |
920 | /// .cpp(true) |
921 | /// .std("c++17" ) |
922 | /// .compile("modern" ); |
923 | /// ``` |
924 | pub fn std(&mut self, std: &str) -> &mut Build { |
925 | self.std = Some(std.into()); |
926 | self |
927 | } |
928 | |
929 | /// Set warnings into errors flag. |
930 | /// |
931 | /// Disabled by default. |
932 | /// |
933 | /// Warning: turning warnings into errors only make sense |
934 | /// if you are a developer of the crate using cc-rs. |
935 | /// Some warnings only appear on some architecture or |
936 | /// specific version of the compiler. Any user of this crate, |
937 | /// or any other crate depending on it, could fail during |
938 | /// compile time. |
939 | /// |
940 | /// # Example |
941 | /// |
942 | /// ```no_run |
943 | /// cc::Build::new() |
944 | /// .file("src/foo.c" ) |
945 | /// .warnings_into_errors(true) |
946 | /// .compile("libfoo.a" ); |
947 | /// ``` |
948 | pub fn warnings_into_errors(&mut self, warnings_into_errors: bool) -> &mut Build { |
949 | self.warnings_into_errors = warnings_into_errors; |
950 | self |
951 | } |
952 | |
953 | /// Set warnings flags. |
954 | /// |
955 | /// Adds some flags: |
956 | /// - "-Wall" for MSVC. |
957 | /// - "-Wall", "-Wextra" for GNU and Clang. |
958 | /// |
959 | /// Enabled by default. |
960 | /// |
961 | /// # Example |
962 | /// |
963 | /// ```no_run |
964 | /// cc::Build::new() |
965 | /// .file("src/foo.c" ) |
966 | /// .warnings(false) |
967 | /// .compile("libfoo.a" ); |
968 | /// ``` |
969 | pub fn warnings(&mut self, warnings: bool) -> &mut Build { |
970 | self.warnings = Some(warnings); |
971 | self.extra_warnings = Some(warnings); |
972 | self |
973 | } |
974 | |
975 | /// Set extra warnings flags. |
976 | /// |
977 | /// Adds some flags: |
978 | /// - nothing for MSVC. |
979 | /// - "-Wextra" for GNU and Clang. |
980 | /// |
981 | /// Enabled by default. |
982 | /// |
983 | /// # Example |
984 | /// |
985 | /// ```no_run |
986 | /// // Disables -Wextra, -Wall remains enabled: |
987 | /// cc::Build::new() |
988 | /// .file("src/foo.c" ) |
989 | /// .extra_warnings(false) |
990 | /// .compile("libfoo.a" ); |
991 | /// ``` |
992 | pub fn extra_warnings(&mut self, warnings: bool) -> &mut Build { |
993 | self.extra_warnings = Some(warnings); |
994 | self |
995 | } |
996 | |
997 | /// Set the standard library to link against when compiling with C++ |
998 | /// support. |
999 | /// |
1000 | /// If the `CXXSTDLIB` environment variable is set, its value will |
1001 | /// override the default value, but not the value explicitly set by calling |
1002 | /// this function. |
1003 | /// |
1004 | /// A value of `None` indicates that no automatic linking should happen, |
1005 | /// otherwise cargo will link against the specified library. |
1006 | /// |
1007 | /// The given library name must not contain the `lib` prefix. |
1008 | /// |
1009 | /// Common values: |
1010 | /// - `stdc++` for GNU |
1011 | /// - `c++` for Clang |
1012 | /// - `c++_shared` or `c++_static` for Android |
1013 | /// |
1014 | /// # Example |
1015 | /// |
1016 | /// ```no_run |
1017 | /// cc::Build::new() |
1018 | /// .file("src/foo.c" ) |
1019 | /// .shared_flag(true) |
1020 | /// .cpp_link_stdlib("stdc++" ) |
1021 | /// .compile("libfoo.so" ); |
1022 | /// ``` |
1023 | pub fn cpp_link_stdlib<'a, V: Into<Option<&'a str>>>( |
1024 | &mut self, |
1025 | cpp_link_stdlib: V, |
1026 | ) -> &mut Build { |
1027 | self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(Arc::from)); |
1028 | self |
1029 | } |
1030 | |
1031 | /// Force the C++ compiler to use the specified standard library. |
1032 | /// |
1033 | /// Setting this option will automatically set `cpp_link_stdlib` to the same |
1034 | /// value. |
1035 | /// |
1036 | /// The default value of this option is always `None`. |
1037 | /// |
1038 | /// This option has no effect when compiling for a Visual Studio based |
1039 | /// target. |
1040 | /// |
1041 | /// This option sets the `-stdlib` flag, which is only supported by some |
1042 | /// compilers (clang, icc) but not by others (gcc). The library will not |
1043 | /// detect which compiler is used, as such it is the responsibility of the |
1044 | /// caller to ensure that this option is only used in conjunction with a |
1045 | /// compiler which supports the `-stdlib` flag. |
1046 | /// |
1047 | /// A value of `None` indicates that no specific C++ standard library should |
1048 | /// be used, otherwise `-stdlib` is added to the compile invocation. |
1049 | /// |
1050 | /// The given library name must not contain the `lib` prefix. |
1051 | /// |
1052 | /// Common values: |
1053 | /// - `stdc++` for GNU |
1054 | /// - `c++` for Clang |
1055 | /// |
1056 | /// # Example |
1057 | /// |
1058 | /// ```no_run |
1059 | /// cc::Build::new() |
1060 | /// .file("src/foo.c" ) |
1061 | /// .cpp_set_stdlib("c++" ) |
1062 | /// .compile("libfoo.a" ); |
1063 | /// ``` |
1064 | pub fn cpp_set_stdlib<'a, V: Into<Option<&'a str>>>( |
1065 | &mut self, |
1066 | cpp_set_stdlib: V, |
1067 | ) -> &mut Build { |
1068 | let cpp_set_stdlib = cpp_set_stdlib.into().map(Arc::from); |
1069 | self.cpp_set_stdlib.clone_from(&cpp_set_stdlib); |
1070 | self.cpp_link_stdlib = Some(cpp_set_stdlib); |
1071 | self |
1072 | } |
1073 | |
1074 | /// Configures the target this configuration will be compiling for. |
1075 | /// |
1076 | /// This option is automatically scraped from the `TARGET` environment |
1077 | /// variable by build scripts, so it's not required to call this function. |
1078 | /// |
1079 | /// # Example |
1080 | /// |
1081 | /// ```no_run |
1082 | /// cc::Build::new() |
1083 | /// .file("src/foo.c" ) |
1084 | /// .target("aarch64-linux-android" ) |
1085 | /// .compile("foo" ); |
1086 | /// ``` |
1087 | pub fn target(&mut self, target: &str) -> &mut Build { |
1088 | self.target = Some(target.into()); |
1089 | self |
1090 | } |
1091 | |
1092 | /// Configures the host assumed by this configuration. |
1093 | /// |
1094 | /// This option is automatically scraped from the `HOST` environment |
1095 | /// variable by build scripts, so it's not required to call this function. |
1096 | /// |
1097 | /// # Example |
1098 | /// |
1099 | /// ```no_run |
1100 | /// cc::Build::new() |
1101 | /// .file("src/foo.c" ) |
1102 | /// .host("arm-linux-gnueabihf" ) |
1103 | /// .compile("foo" ); |
1104 | /// ``` |
1105 | pub fn host(&mut self, host: &str) -> &mut Build { |
1106 | self.host = Some(host.into()); |
1107 | self |
1108 | } |
1109 | |
1110 | /// Configures the optimization level of the generated object files. |
1111 | /// |
1112 | /// This option is automatically scraped from the `OPT_LEVEL` environment |
1113 | /// variable by build scripts, so it's not required to call this function. |
1114 | pub fn opt_level(&mut self, opt_level: u32) -> &mut Build { |
1115 | self.opt_level = Some(opt_level.to_string().into()); |
1116 | self |
1117 | } |
1118 | |
1119 | /// Configures the optimization level of the generated object files. |
1120 | /// |
1121 | /// This option is automatically scraped from the `OPT_LEVEL` environment |
1122 | /// variable by build scripts, so it's not required to call this function. |
1123 | pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Build { |
1124 | self.opt_level = Some(opt_level.into()); |
1125 | self |
1126 | } |
1127 | |
1128 | /// Configures whether the compiler will emit debug information when |
1129 | /// generating object files. |
1130 | /// |
1131 | /// This option is automatically scraped from the `DEBUG` environment |
1132 | /// variable by build scripts, so it's not required to call this function. |
1133 | pub fn debug(&mut self, debug: bool) -> &mut Build { |
1134 | self.debug = Some(debug); |
1135 | self |
1136 | } |
1137 | |
1138 | /// Configures whether the compiler will emit instructions to store |
1139 | /// frame pointers during codegen. |
1140 | /// |
1141 | /// This option is automatically enabled when debug information is emitted. |
1142 | /// Otherwise the target platform compiler's default will be used. |
1143 | /// You can use this option to force a specific setting. |
1144 | pub fn force_frame_pointer(&mut self, force: bool) -> &mut Build { |
1145 | self.force_frame_pointer = Some(force); |
1146 | self |
1147 | } |
1148 | |
1149 | /// Configures the output directory where all object files and static |
1150 | /// libraries will be located. |
1151 | /// |
1152 | /// This option is automatically scraped from the `OUT_DIR` environment |
1153 | /// variable by build scripts, so it's not required to call this function. |
1154 | pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Build { |
1155 | self.out_dir = Some(out_dir.as_ref().into()); |
1156 | self |
1157 | } |
1158 | |
1159 | /// Configures the compiler to be used to produce output. |
1160 | /// |
1161 | /// This option is automatically determined from the target platform or a |
1162 | /// number of environment variables, so it's not required to call this |
1163 | /// function. |
1164 | pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Build { |
1165 | self.compiler = Some(compiler.as_ref().into()); |
1166 | self |
1167 | } |
1168 | |
1169 | /// Configures the tool used to assemble archives. |
1170 | /// |
1171 | /// This option is automatically determined from the target platform or a |
1172 | /// number of environment variables, so it's not required to call this |
1173 | /// function. |
1174 | pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Build { |
1175 | self.archiver = Some(archiver.as_ref().into()); |
1176 | self |
1177 | } |
1178 | |
1179 | /// Configures the tool used to index archives. |
1180 | /// |
1181 | /// This option is automatically determined from the target platform or a |
1182 | /// number of environment variables, so it's not required to call this |
1183 | /// function. |
1184 | pub fn ranlib<P: AsRef<Path>>(&mut self, ranlib: P) -> &mut Build { |
1185 | self.ranlib = Some(ranlib.as_ref().into()); |
1186 | self |
1187 | } |
1188 | |
1189 | /// Define whether metadata should be emitted for cargo allowing it to |
1190 | /// automatically link the binary. Defaults to `true`. |
1191 | /// |
1192 | /// The emitted metadata is: |
1193 | /// |
1194 | /// - `rustc-link-lib=static=`*compiled lib* |
1195 | /// - `rustc-link-search=native=`*target folder* |
1196 | /// - When target is MSVC, the ATL-MFC libs are added via `rustc-link-search=native=` |
1197 | /// - When C++ is enabled, the C++ stdlib is added via `rustc-link-lib` |
1198 | /// - If `emit_rerun_if_env_changed` is not `false`, `rerun-if-env-changed=`*env* |
1199 | /// |
1200 | pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Build { |
1201 | self.cargo_output.metadata = cargo_metadata; |
1202 | self |
1203 | } |
1204 | |
1205 | /// Define whether compile warnings should be emitted for cargo. Defaults to |
1206 | /// `true`. |
1207 | /// |
1208 | /// If disabled, compiler messages will not be printed. |
1209 | /// Issues unrelated to the compilation will always produce cargo warnings regardless of this setting. |
1210 | pub fn cargo_warnings(&mut self, cargo_warnings: bool) -> &mut Build { |
1211 | self.cargo_output.warnings = cargo_warnings; |
1212 | self |
1213 | } |
1214 | |
1215 | /// Define whether debug information should be emitted for cargo. Defaults to whether |
1216 | /// or not the environment variable `CC_ENABLE_DEBUG_OUTPUT` is set. |
1217 | /// |
1218 | /// If enabled, the compiler will emit debug information when generating object files, |
1219 | /// such as the command invoked and the exit status. |
1220 | pub fn cargo_debug(&mut self, cargo_debug: bool) -> &mut Build { |
1221 | self.cargo_output.debug = cargo_debug; |
1222 | self |
1223 | } |
1224 | |
1225 | /// Define whether compiler output (to stdout) should be emitted. Defaults to `true` |
1226 | /// (forward compiler stdout to this process' stdout) |
1227 | /// |
1228 | /// Some compilers emit errors to stdout, so if you *really* need stdout to be clean |
1229 | /// you should also set this to `false`. |
1230 | pub fn cargo_output(&mut self, cargo_output: bool) -> &mut Build { |
1231 | self.cargo_output.output = if cargo_output { |
1232 | OutputKind::Forward |
1233 | } else { |
1234 | OutputKind::Discard |
1235 | }; |
1236 | self |
1237 | } |
1238 | |
1239 | /// Adds a native library modifier that will be added to the |
1240 | /// `rustc-link-lib=static:MODIFIERS=LIBRARY_NAME` metadata line |
1241 | /// emitted for cargo if `cargo_metadata` is enabled. |
1242 | /// See <https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library> |
1243 | /// for the list of modifiers accepted by rustc. |
1244 | pub fn link_lib_modifier(&mut self, link_lib_modifier: impl AsRef<OsStr>) -> &mut Build { |
1245 | self.link_lib_modifiers |
1246 | .push(link_lib_modifier.as_ref().into()); |
1247 | self |
1248 | } |
1249 | |
1250 | /// Configures whether the compiler will emit position independent code. |
1251 | /// |
1252 | /// This option defaults to `false` for `windows-gnu` and bare metal targets and |
1253 | /// to `true` for all other targets. |
1254 | pub fn pic(&mut self, pic: bool) -> &mut Build { |
1255 | self.pic = Some(pic); |
1256 | self |
1257 | } |
1258 | |
1259 | /// Configures whether the Procedure Linkage Table is used for indirect |
1260 | /// calls into shared libraries. |
1261 | /// |
1262 | /// The PLT is used to provide features like lazy binding, but introduces |
1263 | /// a small performance loss due to extra pointer indirection. Setting |
1264 | /// `use_plt` to `false` can provide a small performance increase. |
1265 | /// |
1266 | /// Note that skipping the PLT requires a recent version of GCC/Clang. |
1267 | /// |
1268 | /// This only applies to ELF targets. It has no effect on other platforms. |
1269 | pub fn use_plt(&mut self, use_plt: bool) -> &mut Build { |
1270 | self.use_plt = Some(use_plt); |
1271 | self |
1272 | } |
1273 | |
1274 | /// Define whether metadata should be emitted for cargo to detect environment |
1275 | /// changes that should trigger a rebuild. |
1276 | /// |
1277 | /// NOTE that cc does not emit metadata to detect changes for `PATH`, since it could |
1278 | /// be changed every comilation yet does not affect the result of compilation |
1279 | /// (i.e. rust-analyzer adds temporary directory to `PATH`). |
1280 | /// |
1281 | /// cc in general, has no way detecting changes to compiler, as there are so many ways to |
1282 | /// change it and sidestep the detection, for example the compiler might be wrapped in a script |
1283 | /// so detecting change of the file, or using checksum won't work. |
1284 | /// |
1285 | /// We recommend users to decide for themselves, if they want rebuild if the compiler has been upgraded |
1286 | /// or changed, and how to detect that. |
1287 | /// |
1288 | /// This has no effect if the `cargo_metadata` option is `false`. |
1289 | /// |
1290 | /// This option defaults to `true`. |
1291 | pub fn emit_rerun_if_env_changed(&mut self, emit_rerun_if_env_changed: bool) -> &mut Build { |
1292 | self.emit_rerun_if_env_changed = emit_rerun_if_env_changed; |
1293 | self |
1294 | } |
1295 | |
1296 | /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. |
1297 | /// |
1298 | /// This option defaults to `false`, and affect only msvc targets. |
1299 | pub fn static_crt(&mut self, static_crt: bool) -> &mut Build { |
1300 | self.static_crt = Some(static_crt); |
1301 | self |
1302 | } |
1303 | |
1304 | /// Configure whether *FLAGS variables are parsed using `shlex`, similarly to `make` and |
1305 | /// `cmake`. |
1306 | /// |
1307 | /// This option defaults to `false`. |
1308 | pub fn shell_escaped_flags(&mut self, shell_escaped_flags: bool) -> &mut Build { |
1309 | self.shell_escaped_flags = Some(shell_escaped_flags); |
1310 | self |
1311 | } |
1312 | |
1313 | #[doc (hidden)] |
1314 | pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build |
1315 | where |
1316 | A: AsRef<OsStr>, |
1317 | B: AsRef<OsStr>, |
1318 | { |
1319 | self.env.push((a.as_ref().into(), b.as_ref().into())); |
1320 | self |
1321 | } |
1322 | |
1323 | /// Run the compiler, generating the file `output` |
1324 | /// |
1325 | /// This will return a result instead of panicking; see [`Self::compile()`] for |
1326 | /// the complete description. |
1327 | pub fn try_compile(&self, output: &str) -> Result<(), Error> { |
1328 | let mut output_components = Path::new(output).components(); |
1329 | match (output_components.next(), output_components.next()) { |
1330 | (Some(Component::Normal(_)), None) => {} |
1331 | _ => { |
1332 | return Err(Error::new( |
1333 | ErrorKind::InvalidArgument, |
1334 | "argument of `compile` must be a single normal path component" , |
1335 | )); |
1336 | } |
1337 | } |
1338 | |
1339 | let (lib_name, gnu_lib_name) = if output.starts_with("lib" ) && output.ends_with(".a" ) { |
1340 | (&output[3..output.len() - 2], output.to_owned()) |
1341 | } else { |
1342 | let mut gnu = String::with_capacity(5 + output.len()); |
1343 | gnu.push_str("lib" ); |
1344 | gnu.push_str(output); |
1345 | gnu.push_str(".a" ); |
1346 | (output, gnu) |
1347 | }; |
1348 | let dst = self.get_out_dir()?; |
1349 | |
1350 | let objects = objects_from_files(&self.files, &dst)?; |
1351 | |
1352 | self.compile_objects(&objects)?; |
1353 | self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; |
1354 | |
1355 | let target = self.get_target()?; |
1356 | if target.contains("msvc" ) { |
1357 | let compiler = self.get_base_compiler()?; |
1358 | let atlmfc_lib = compiler |
1359 | .env() |
1360 | .iter() |
1361 | .find(|&(var, _)| var.as_os_str() == OsStr::new("LIB" )) |
1362 | .and_then(|(_, lib_paths)| { |
1363 | env::split_paths(lib_paths).find(|path| { |
1364 | let sub = Path::new("atlmfc/lib" ); |
1365 | path.ends_with(sub) || path.parent().map_or(false, |p| p.ends_with(sub)) |
1366 | }) |
1367 | }); |
1368 | |
1369 | if let Some(atlmfc_lib) = atlmfc_lib { |
1370 | self.cargo_output.print_metadata(&format_args!( |
1371 | "cargo:rustc-link-search=native= {}" , |
1372 | atlmfc_lib.display() |
1373 | )); |
1374 | } |
1375 | } |
1376 | |
1377 | if self.link_lib_modifiers.is_empty() { |
1378 | self.cargo_output |
1379 | .print_metadata(&format_args!("cargo:rustc-link-lib=static= {}" , lib_name)); |
1380 | } else { |
1381 | self.cargo_output.print_metadata(&format_args!( |
1382 | "cargo:rustc-link-lib=static: {}= {}" , |
1383 | JoinOsStrs { |
1384 | slice: &self.link_lib_modifiers, |
1385 | delimiter: ',' |
1386 | }, |
1387 | lib_name |
1388 | )); |
1389 | } |
1390 | self.cargo_output.print_metadata(&format_args!( |
1391 | "cargo:rustc-link-search=native= {}" , |
1392 | dst.display() |
1393 | )); |
1394 | |
1395 | // Add specific C++ libraries, if enabled. |
1396 | if self.cpp { |
1397 | if let Some(stdlib) = self.get_cpp_link_stdlib()? { |
1398 | self.cargo_output |
1399 | .print_metadata(&format_args!("cargo:rustc-link-lib= {}" , stdlib.display())); |
1400 | } |
1401 | // Link c++ lib from WASI sysroot |
1402 | if Build::is_wasi_target(target.as_ref()) { |
1403 | if let Ok(wasi_sysroot) = self.wasi_sysroot() { |
1404 | self.cargo_output.print_metadata(&format_args!( |
1405 | "cargo:rustc-flags=-L {}/lib/ {} -lstatic=c++ -lstatic=c++abi" , |
1406 | Path::new(&wasi_sysroot).display(), |
1407 | target |
1408 | )); |
1409 | } |
1410 | } |
1411 | } |
1412 | |
1413 | let cudart = match &self.cudart { |
1414 | Some(opt) => opt, // {none|shared|static} |
1415 | None => "none" , |
1416 | }; |
1417 | if cudart != "none" { |
1418 | if let Some(nvcc) = self.which(&self.get_compiler().path, None) { |
1419 | // Try to figure out the -L search path. If it fails, |
1420 | // it's on user to specify one by passing it through |
1421 | // RUSTFLAGS environment variable. |
1422 | let mut libtst = false; |
1423 | let mut libdir = nvcc; |
1424 | libdir.pop(); // remove 'nvcc' |
1425 | libdir.push(".." ); |
1426 | let target_arch = self.getenv_unwrap_str("CARGO_CFG_TARGET_ARCH" )?; |
1427 | if cfg!(target_os = "linux" ) { |
1428 | libdir.push("targets" ); |
1429 | libdir.push(target_arch.to_owned() + "-linux" ); |
1430 | libdir.push("lib" ); |
1431 | libtst = true; |
1432 | } else if cfg!(target_env = "msvc" ) { |
1433 | libdir.push("lib" ); |
1434 | match target_arch.as_str() { |
1435 | "x86_64" => { |
1436 | libdir.push("x64" ); |
1437 | libtst = true; |
1438 | } |
1439 | "x86" => { |
1440 | libdir.push("Win32" ); |
1441 | libtst = true; |
1442 | } |
1443 | _ => libtst = false, |
1444 | } |
1445 | } |
1446 | if libtst && libdir.is_dir() { |
1447 | self.cargo_output.print_metadata(&format_args!( |
1448 | "cargo:rustc-link-search=native= {}" , |
1449 | libdir.to_str().unwrap() |
1450 | )); |
1451 | } |
1452 | |
1453 | // And now the -l flag. |
1454 | let lib = match cudart { |
1455 | "shared" => "cudart" , |
1456 | "static" => "cudart_static" , |
1457 | bad => panic!("unsupported cudart option: {}" , bad), |
1458 | }; |
1459 | self.cargo_output |
1460 | .print_metadata(&format_args!("cargo:rustc-link-lib= {}" , lib)); |
1461 | } |
1462 | } |
1463 | |
1464 | Ok(()) |
1465 | } |
1466 | |
1467 | /// Run the compiler, generating the file `output` |
1468 | /// |
1469 | /// # Library name |
1470 | /// |
1471 | /// The `output` string argument determines the file name for the compiled |
1472 | /// library. The Rust compiler will create an assembly named "lib"+output+".a". |
1473 | /// MSVC will create a file named output+".lib". |
1474 | /// |
1475 | /// The choice of `output` is close to arbitrary, but: |
1476 | /// |
1477 | /// - must be nonempty, |
1478 | /// - must not contain a path separator (`/`), |
1479 | /// - must be unique across all `compile` invocations made by the same build |
1480 | /// script. |
1481 | /// |
1482 | /// If your build script compiles a single source file, the base name of |
1483 | /// that source file would usually be reasonable: |
1484 | /// |
1485 | /// ```no_run |
1486 | /// cc::Build::new().file("blobstore.c" ).compile("blobstore" ); |
1487 | /// ``` |
1488 | /// |
1489 | /// Compiling multiple source files, some people use their crate's name, or |
1490 | /// their crate's name + "-cc". |
1491 | /// |
1492 | /// Otherwise, please use your imagination. |
1493 | /// |
1494 | /// For backwards compatibility, if `output` starts with "lib" *and* ends |
1495 | /// with ".a", a second "lib" prefix and ".a" suffix do not get added on, |
1496 | /// but this usage is deprecated; please omit `lib` and `.a` in the argument |
1497 | /// that you pass. |
1498 | /// |
1499 | /// # Panics |
1500 | /// |
1501 | /// Panics if `output` is not formatted correctly or if one of the underlying |
1502 | /// compiler commands fails. It can also panic if it fails reading file names |
1503 | /// or creating directories. |
1504 | pub fn compile(&self, output: &str) { |
1505 | if let Err(e) = self.try_compile(output) { |
1506 | fail(&e.message); |
1507 | } |
1508 | } |
1509 | |
1510 | /// Run the compiler, generating intermediate files, but without linking |
1511 | /// them into an archive file. |
1512 | /// |
1513 | /// This will return a list of compiled object files, in the same order |
1514 | /// as they were passed in as `file`/`files` methods. |
1515 | pub fn compile_intermediates(&self) -> Vec<PathBuf> { |
1516 | match self.try_compile_intermediates() { |
1517 | Ok(v) => v, |
1518 | Err(e) => fail(&e.message), |
1519 | } |
1520 | } |
1521 | |
1522 | /// Run the compiler, generating intermediate files, but without linking |
1523 | /// them into an archive file. |
1524 | /// |
1525 | /// This will return a result instead of panicking; see `compile_intermediates()` for the complete description. |
1526 | pub fn try_compile_intermediates(&self) -> Result<Vec<PathBuf>, Error> { |
1527 | let dst = self.get_out_dir()?; |
1528 | let objects = objects_from_files(&self.files, &dst)?; |
1529 | |
1530 | self.compile_objects(&objects)?; |
1531 | |
1532 | Ok(objects.into_iter().map(|v| v.dst).collect()) |
1533 | } |
1534 | |
1535 | #[cfg (feature = "parallel" )] |
1536 | fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { |
1537 | use std::cell::Cell; |
1538 | |
1539 | use parallel::async_executor::{block_on, YieldOnce}; |
1540 | |
1541 | if objs.len() <= 1 { |
1542 | for obj in objs { |
1543 | let (mut cmd, name) = self.create_compile_object_cmd(obj)?; |
1544 | run(&mut cmd, &name, &self.cargo_output)?; |
1545 | } |
1546 | |
1547 | return Ok(()); |
1548 | } |
1549 | |
1550 | // Limit our parallelism globally with a jobserver. |
1551 | let mut tokens = parallel::job_token::ActiveJobTokenServer::new(); |
1552 | |
1553 | // When compiling objects in parallel we do a few dirty tricks to speed |
1554 | // things up: |
1555 | // |
1556 | // * First is that we use the `jobserver` crate to limit the parallelism |
1557 | // of this build script. The `jobserver` crate will use a jobserver |
1558 | // configured by Cargo for build scripts to ensure that parallelism is |
1559 | // coordinated across C compilations and Rust compilations. Before we |
1560 | // compile anything we make sure to wait until we acquire a token. |
1561 | // |
1562 | // Note that this jobserver is cached globally so we only used one per |
1563 | // process and only worry about creating it once. |
1564 | // |
1565 | // * Next we use spawn the process to actually compile objects in |
1566 | // parallel after we've acquired a token to perform some work |
1567 | // |
1568 | // With all that in mind we compile all objects in a loop here, after we |
1569 | // acquire the appropriate tokens, Once all objects have been compiled |
1570 | // we wait on all the processes and propagate the results of compilation. |
1571 | |
1572 | let pendings = Cell::new(Vec::<( |
1573 | Command, |
1574 | Cow<'static, Path>, |
1575 | KillOnDrop, |
1576 | parallel::job_token::JobToken, |
1577 | )>::new()); |
1578 | let is_disconnected = Cell::new(false); |
1579 | let has_made_progress = Cell::new(false); |
1580 | |
1581 | let wait_future = async { |
1582 | let mut error = None; |
1583 | // Buffer the stdout |
1584 | let mut stdout = io::BufWriter::with_capacity(128, io::stdout()); |
1585 | |
1586 | loop { |
1587 | // If the other end of the pipe is already disconnected, then we're not gonna get any new jobs, |
1588 | // so it doesn't make sense to reuse the tokens; in fact, |
1589 | // releasing them as soon as possible (once we know that the other end is disconnected) is beneficial. |
1590 | // Imagine that the last file built takes an hour to finish; in this scenario, |
1591 | // by not releasing the tokens before that last file is done we would effectively block other processes from |
1592 | // starting sooner - even though we only need one token for that last file, not N others that were acquired. |
1593 | |
1594 | let mut pendings_is_empty = false; |
1595 | |
1596 | cell_update(&pendings, |mut pendings| { |
1597 | // Try waiting on them. |
1598 | pendings.retain_mut(|(cmd, program, child, _token)| { |
1599 | match try_wait_on_child( |
1600 | cmd, |
1601 | program, |
1602 | &mut child.0, |
1603 | &mut stdout, |
1604 | &mut child.1, |
1605 | ) { |
1606 | Ok(Some(())) => { |
1607 | // Task done, remove the entry |
1608 | has_made_progress.set(true); |
1609 | false |
1610 | } |
1611 | Ok(None) => true, // Task still not finished, keep the entry |
1612 | Err(err) => { |
1613 | // Task fail, remove the entry. |
1614 | // Since we can only return one error, log the error to make |
1615 | // sure users always see all the compilation failures. |
1616 | has_made_progress.set(true); |
1617 | |
1618 | if self.cargo_output.warnings { |
1619 | let _ = writeln!(stdout, "cargo:warning= {}" , err); |
1620 | } |
1621 | error = Some(err); |
1622 | |
1623 | false |
1624 | } |
1625 | } |
1626 | }); |
1627 | pendings_is_empty = pendings.is_empty(); |
1628 | pendings |
1629 | }); |
1630 | |
1631 | if pendings_is_empty && is_disconnected.get() { |
1632 | break if let Some(err) = error { |
1633 | Err(err) |
1634 | } else { |
1635 | Ok(()) |
1636 | }; |
1637 | } |
1638 | |
1639 | YieldOnce::default().await; |
1640 | } |
1641 | }; |
1642 | let spawn_future = async { |
1643 | for obj in objs { |
1644 | let (mut cmd, program) = self.create_compile_object_cmd(obj)?; |
1645 | let token = tokens.acquire().await?; |
1646 | let mut child = spawn(&mut cmd, &program, &self.cargo_output)?; |
1647 | let mut stderr_forwarder = StderrForwarder::new(&mut child); |
1648 | stderr_forwarder.set_non_blocking()?; |
1649 | |
1650 | cell_update(&pendings, |mut pendings| { |
1651 | pendings.push((cmd, program, KillOnDrop(child, stderr_forwarder), token)); |
1652 | pendings |
1653 | }); |
1654 | |
1655 | has_made_progress.set(true); |
1656 | } |
1657 | is_disconnected.set(true); |
1658 | |
1659 | Ok::<_, Error>(()) |
1660 | }; |
1661 | |
1662 | return block_on(wait_future, spawn_future, &has_made_progress); |
1663 | |
1664 | struct KillOnDrop(Child, StderrForwarder); |
1665 | |
1666 | impl Drop for KillOnDrop { |
1667 | fn drop(&mut self) { |
1668 | let child = &mut self.0; |
1669 | |
1670 | child.kill().ok(); |
1671 | } |
1672 | } |
1673 | |
1674 | fn cell_update<T, F>(cell: &Cell<T>, f: F) |
1675 | where |
1676 | T: Default, |
1677 | F: FnOnce(T) -> T, |
1678 | { |
1679 | let old = cell.take(); |
1680 | let new = f(old); |
1681 | cell.set(new); |
1682 | } |
1683 | } |
1684 | |
1685 | #[cfg (not(feature = "parallel" ))] |
1686 | fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { |
1687 | for obj in objs { |
1688 | let (mut cmd, name) = self.create_compile_object_cmd(obj)?; |
1689 | run(&mut cmd, &name, &self.cargo_output)?; |
1690 | } |
1691 | |
1692 | Ok(()) |
1693 | } |
1694 | |
1695 | fn create_compile_object_cmd( |
1696 | &self, |
1697 | obj: &Object, |
1698 | ) -> Result<(Command, Cow<'static, Path>), Error> { |
1699 | let asm_ext = AsmFileExt::from_path(&obj.src); |
1700 | let is_asm = asm_ext.is_some(); |
1701 | let target = self.get_target()?; |
1702 | let msvc = target.contains("msvc" ); |
1703 | let compiler = self.try_get_compiler()?; |
1704 | let clang = compiler.is_like_clang(); |
1705 | let gnu = compiler.family == ToolFamily::Gnu; |
1706 | |
1707 | let is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm); |
1708 | let (mut cmd, name) = if is_assembler_msvc { |
1709 | let (cmd, name) = self.msvc_macro_assembler()?; |
1710 | (cmd, Cow::Borrowed(Path::new(name))) |
1711 | } else { |
1712 | let mut cmd = compiler.to_command(); |
1713 | for (a, b) in self.env.iter() { |
1714 | cmd.env(a, b); |
1715 | } |
1716 | ( |
1717 | cmd, |
1718 | compiler |
1719 | .path |
1720 | .file_name() |
1721 | .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path." )) |
1722 | .map(PathBuf::from) |
1723 | .map(Cow::Owned)?, |
1724 | ) |
1725 | }; |
1726 | let is_arm = target.contains("aarch64" ) || target.contains("arm" ); |
1727 | command_add_output_file( |
1728 | &mut cmd, |
1729 | &obj.dst, |
1730 | CmdAddOutputFileArgs { |
1731 | cuda: self.cuda, |
1732 | is_assembler_msvc, |
1733 | msvc: compiler.is_like_msvc(), |
1734 | clang, |
1735 | gnu, |
1736 | is_asm, |
1737 | is_arm, |
1738 | }, |
1739 | ); |
1740 | // armasm and armasm64 don't requrie -c option |
1741 | if !is_assembler_msvc || !is_arm { |
1742 | cmd.arg("-c" ); |
1743 | } |
1744 | if self.cuda && self.cuda_file_count() > 1 { |
1745 | cmd.arg("--device-c" ); |
1746 | } |
1747 | if is_asm { |
1748 | cmd.args(self.asm_flags.iter().map(std::ops::Deref::deref)); |
1749 | } |
1750 | if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_assembler_msvc { |
1751 | // #513: For `clang-cl`, separate flags/options from the input file. |
1752 | // When cross-compiling macOS -> Windows, this avoids interpreting |
1753 | // common `/Users/...` paths as the `/U` flag and triggering |
1754 | // `-Wslash-u-filename` warning. |
1755 | cmd.arg("--" ); |
1756 | } |
1757 | cmd.arg(&obj.src); |
1758 | if cfg!(target_os = "macos" ) { |
1759 | self.fix_env_for_apple_os(&mut cmd)?; |
1760 | } |
1761 | |
1762 | Ok((cmd, name)) |
1763 | } |
1764 | |
1765 | /// This will return a result instead of panicking; see [`Self::expand()`] for |
1766 | /// the complete description. |
1767 | pub fn try_expand(&self) -> Result<Vec<u8>, Error> { |
1768 | let compiler = self.try_get_compiler()?; |
1769 | let mut cmd = compiler.to_command(); |
1770 | for (a, b) in self.env.iter() { |
1771 | cmd.env(a, b); |
1772 | } |
1773 | cmd.arg("-E" ); |
1774 | |
1775 | assert!( |
1776 | self.files.len() <= 1, |
1777 | "Expand may only be called for a single file" |
1778 | ); |
1779 | |
1780 | let is_asm = self |
1781 | .files |
1782 | .iter() |
1783 | .map(std::ops::Deref::deref) |
1784 | .find_map(AsmFileExt::from_path) |
1785 | .is_some(); |
1786 | |
1787 | if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_asm { |
1788 | // #513: For `clang-cl`, separate flags/options from the input file. |
1789 | // When cross-compiling macOS -> Windows, this avoids interpreting |
1790 | // common `/Users/...` paths as the `/U` flag and triggering |
1791 | // `-Wslash-u-filename` warning. |
1792 | cmd.arg("--" ); |
1793 | } |
1794 | |
1795 | cmd.args(self.files.iter().map(std::ops::Deref::deref)); |
1796 | |
1797 | let name = compiler |
1798 | .path |
1799 | .file_name() |
1800 | .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path." ))?; |
1801 | |
1802 | run_output(&mut cmd, name, &self.cargo_output) |
1803 | } |
1804 | |
1805 | /// Run the compiler, returning the macro-expanded version of the input files. |
1806 | /// |
1807 | /// This is only relevant for C and C++ files. |
1808 | /// |
1809 | /// # Panics |
1810 | /// Panics if more than one file is present in the config, or if compiler |
1811 | /// path has an invalid file name. |
1812 | /// |
1813 | /// # Example |
1814 | /// ```no_run |
1815 | /// let out = cc::Build::new().file("src/foo.c" ).expand(); |
1816 | /// ``` |
1817 | pub fn expand(&self) -> Vec<u8> { |
1818 | match self.try_expand() { |
1819 | Err(e) => fail(&e.message), |
1820 | Ok(v) => v, |
1821 | } |
1822 | } |
1823 | |
1824 | /// Get the compiler that's in use for this configuration. |
1825 | /// |
1826 | /// This function will return a `Tool` which represents the culmination |
1827 | /// of this configuration at a snapshot in time. The returned compiler can |
1828 | /// be inspected (e.g. the path, arguments, environment) to forward along to |
1829 | /// other tools, or the `to_command` method can be used to invoke the |
1830 | /// compiler itself. |
1831 | /// |
1832 | /// This method will take into account all configuration such as debug |
1833 | /// information, optimization level, include directories, defines, etc. |
1834 | /// Additionally, the compiler binary in use follows the standard |
1835 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
1836 | /// environment variables (a number of which are inspected here), and then |
1837 | /// falling back to the default configuration. |
1838 | /// |
1839 | /// # Panics |
1840 | /// |
1841 | /// Panics if an error occurred while determining the architecture. |
1842 | pub fn get_compiler(&self) -> Tool { |
1843 | match self.try_get_compiler() { |
1844 | Ok(tool) => tool, |
1845 | Err(e) => fail(&e.message), |
1846 | } |
1847 | } |
1848 | |
1849 | /// Get the compiler that's in use for this configuration. |
1850 | /// |
1851 | /// This will return a result instead of panicking; see |
1852 | /// [`get_compiler()`](Self::get_compiler) for the complete description. |
1853 | pub fn try_get_compiler(&self) -> Result<Tool, Error> { |
1854 | let opt_level = self.get_opt_level()?; |
1855 | let target = self.get_target()?; |
1856 | |
1857 | let mut cmd = self.get_base_compiler()?; |
1858 | |
1859 | // Disable default flag generation via `no_default_flags` or environment variable |
1860 | let no_defaults = self.no_default_flags || self.getenv_boolean("CRATE_CC_NO_DEFAULTS" ); |
1861 | |
1862 | if !no_defaults { |
1863 | self.add_default_flags(&mut cmd, &target, &opt_level)?; |
1864 | } |
1865 | |
1866 | if let Some(ref std) = self.std { |
1867 | let separator = match cmd.family { |
1868 | ToolFamily::Msvc { .. } => ':' , |
1869 | ToolFamily::Gnu | ToolFamily::Clang { .. } => '=' , |
1870 | }; |
1871 | cmd.push_cc_arg(format!("-std {}{}" , separator, std).into()); |
1872 | } |
1873 | |
1874 | for directory in self.include_directories.iter() { |
1875 | cmd.args.push("-I" .into()); |
1876 | cmd.args.push(directory.as_os_str().into()); |
1877 | } |
1878 | |
1879 | if let Ok(flags) = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }) { |
1880 | for arg in flags { |
1881 | cmd.push_cc_arg(arg.into()); |
1882 | } |
1883 | } |
1884 | |
1885 | // If warnings and/or extra_warnings haven't been explicitly set, |
1886 | // then we set them only if the environment doesn't already have |
1887 | // CFLAGS/CXXFLAGS, since those variables presumably already contain |
1888 | // the desired set of warnings flags. |
1889 | |
1890 | if self.warnings.unwrap_or(!self.has_flags()) { |
1891 | let wflags = cmd.family.warnings_flags().into(); |
1892 | cmd.push_cc_arg(wflags); |
1893 | } |
1894 | |
1895 | if self.extra_warnings.unwrap_or(!self.has_flags()) { |
1896 | if let Some(wflags) = cmd.family.extra_warnings_flags() { |
1897 | cmd.push_cc_arg(wflags.into()); |
1898 | } |
1899 | } |
1900 | |
1901 | for flag in self.flags.iter() { |
1902 | cmd.args.push((**flag).into()); |
1903 | } |
1904 | |
1905 | for flag in self.flags_supported.iter() { |
1906 | if self |
1907 | .is_flag_supported_inner(flag, &cmd.path, &target) |
1908 | .unwrap_or(false) |
1909 | { |
1910 | cmd.push_cc_arg((**flag).into()); |
1911 | } |
1912 | } |
1913 | |
1914 | for (key, value) in self.definitions.iter() { |
1915 | if let Some(ref value) = *value { |
1916 | cmd.args.push(format!("-D {}= {}" , key, value).into()); |
1917 | } else { |
1918 | cmd.args.push(format!("-D {}" , key).into()); |
1919 | } |
1920 | } |
1921 | |
1922 | if self.warnings_into_errors { |
1923 | let warnings_to_errors_flag = cmd.family.warnings_to_errors_flag().into(); |
1924 | cmd.push_cc_arg(warnings_to_errors_flag); |
1925 | } |
1926 | |
1927 | Ok(cmd) |
1928 | } |
1929 | |
1930 | fn add_default_flags( |
1931 | &self, |
1932 | cmd: &mut Tool, |
1933 | target: &str, |
1934 | opt_level: &str, |
1935 | ) -> Result<(), Error> { |
1936 | // Non-target flags |
1937 | // If the flag is not conditioned on target variable, it belongs here :) |
1938 | match cmd.family { |
1939 | ToolFamily::Msvc { .. } => { |
1940 | cmd.push_cc_arg("-nologo" .into()); |
1941 | |
1942 | let crt_flag = match self.static_crt { |
1943 | Some(true) => "-MT" , |
1944 | Some(false) => "-MD" , |
1945 | None => { |
1946 | let features = self.getenv("CARGO_CFG_TARGET_FEATURE" ); |
1947 | let features = features.as_deref().unwrap_or_default(); |
1948 | if features.to_string_lossy().contains("crt-static" ) { |
1949 | "-MT" |
1950 | } else { |
1951 | "-MD" |
1952 | } |
1953 | } |
1954 | }; |
1955 | cmd.push_cc_arg(crt_flag.into()); |
1956 | |
1957 | match opt_level { |
1958 | // Msvc uses /O1 to enable all optimizations that minimize code size. |
1959 | "z" | "s" | "1" => cmd.push_opt_unless_duplicate("-O1" .into()), |
1960 | // -O3 is a valid value for gcc and clang compilers, but not msvc. Cap to /O2. |
1961 | "2" | "3" => cmd.push_opt_unless_duplicate("-O2" .into()), |
1962 | _ => {} |
1963 | } |
1964 | } |
1965 | ToolFamily::Gnu | ToolFamily::Clang { .. } => { |
1966 | // arm-linux-androideabi-gcc 4.8 shipped with Android NDK does |
1967 | // not support '-Oz' |
1968 | if opt_level == "z" && !cmd.is_like_clang() { |
1969 | cmd.push_opt_unless_duplicate("-Os" .into()); |
1970 | } else { |
1971 | cmd.push_opt_unless_duplicate(format!("-O {}" , opt_level).into()); |
1972 | } |
1973 | |
1974 | if cmd.is_like_clang() && target.contains("windows" ) { |
1975 | // Disambiguate mingw and msvc on Windows. Problem is that |
1976 | // depending on the origin clang can default to a mismatchig |
1977 | // run-time. |
1978 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
1979 | } |
1980 | |
1981 | if cmd.is_like_clang() && target.contains("android" ) { |
1982 | // For compatibility with code that doesn't use pre-defined `__ANDROID__` macro. |
1983 | // If compiler used via ndk-build or cmake (officially supported build methods) |
1984 | // this macros is defined. |
1985 | // See https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/build/cmake/android.toolchain.cmake#456 |
1986 | // https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/build/core/build-binary.mk#141 |
1987 | cmd.push_opt_unless_duplicate("-DANDROID" .into()); |
1988 | } |
1989 | |
1990 | if !target.contains("apple-ios" ) |
1991 | && !target.contains("apple-watchos" ) |
1992 | && !target.contains("apple-tvos" ) |
1993 | && !target.contains("apple-visionos" ) |
1994 | { |
1995 | cmd.push_cc_arg("-ffunction-sections" .into()); |
1996 | cmd.push_cc_arg("-fdata-sections" .into()); |
1997 | } |
1998 | // Disable generation of PIC on bare-metal for now: rust-lld doesn't support this yet |
1999 | if self.pic.unwrap_or_else(|| { |
2000 | !target.contains("windows" ) |
2001 | && !target.contains("-none-" ) |
2002 | && !target.ends_with("-none" ) |
2003 | && !target.contains("uefi" ) |
2004 | && !Build::is_wasi_target(target) |
2005 | }) { |
2006 | cmd.push_cc_arg("-fPIC" .into()); |
2007 | // PLT only applies if code is compiled with PIC support, |
2008 | // and only for ELF targets. |
2009 | if target.contains("linux" ) && !self.use_plt.unwrap_or(true) { |
2010 | cmd.push_cc_arg("-fno-plt" .into()); |
2011 | } |
2012 | } |
2013 | if Build::is_wasi_target(target) { |
2014 | // WASI does not support exceptions yet. |
2015 | // https://github.com/WebAssembly/exception-handling |
2016 | cmd.push_cc_arg("-fno-exceptions" .into()); |
2017 | // Link clang sysroot |
2018 | if let Ok(wasi_sysroot) = self.wasi_sysroot() { |
2019 | cmd.push_cc_arg( |
2020 | format!("--sysroot= {}" , Path::new(&wasi_sysroot).display()).into(), |
2021 | ); |
2022 | } |
2023 | |
2024 | if target.contains("threads" ) { |
2025 | cmd.push_cc_arg("-pthread" .into()); |
2026 | } |
2027 | } |
2028 | } |
2029 | } |
2030 | |
2031 | if self.get_debug() { |
2032 | if self.cuda { |
2033 | // NVCC debug flag |
2034 | cmd.args.push("-G" .into()); |
2035 | } |
2036 | let family = cmd.family; |
2037 | family.add_debug_flags(cmd, self.get_dwarf_version()); |
2038 | } |
2039 | |
2040 | if self.get_force_frame_pointer() { |
2041 | let family = cmd.family; |
2042 | family.add_force_frame_pointer(cmd); |
2043 | } |
2044 | |
2045 | if !cmd.is_like_msvc() { |
2046 | if target.contains("i686" ) || target.contains("i586" ) { |
2047 | cmd.args.push("-m32" .into()); |
2048 | } else if target == "x86_64-unknown-linux-gnux32" { |
2049 | cmd.args.push("-mx32" .into()); |
2050 | } else if target.contains("x86_64" ) || target.contains("powerpc64" ) { |
2051 | cmd.args.push("-m64" .into()); |
2052 | } |
2053 | } |
2054 | |
2055 | // Target flags |
2056 | match cmd.family { |
2057 | ToolFamily::Clang { .. } => { |
2058 | if !(cmd.has_internal_target_arg |
2059 | || (target.contains("android" ) |
2060 | && android_clang_compiler_uses_target_arg_internally(&cmd.path))) |
2061 | { |
2062 | let (arch, rest) = target.split_once('-' ).ok_or_else(|| { |
2063 | Error::new( |
2064 | ErrorKind::InvalidTarget, |
2065 | format!("Invalid target ` {}`: no `-` in it" , target), |
2066 | ) |
2067 | })?; |
2068 | |
2069 | if target.contains("darwin" ) { |
2070 | if let Some(arch) = |
2071 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2072 | { |
2073 | cmd.args |
2074 | .push(format!("--target= {}-apple-darwin" , arch).into()); |
2075 | } |
2076 | } else if target.contains("macabi" ) { |
2077 | if let Some(arch) = |
2078 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2079 | { |
2080 | cmd.args |
2081 | .push(format!("--target= {}-apple-ios-macabi" , arch).into()); |
2082 | } |
2083 | } else if target.contains("ios-sim" ) { |
2084 | if let Some(arch) = |
2085 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2086 | { |
2087 | let sdk_details = |
2088 | apple_os_sdk_parts(AppleOs::Ios, &AppleArchSpec::Simulator("" )); |
2089 | let deployment_target = |
2090 | self.apple_deployment_version(AppleOs::Ios, None, &sdk_details.sdk); |
2091 | cmd.args.push( |
2092 | format!( |
2093 | "--target= {}-apple-ios {}-simulator" , |
2094 | arch, deployment_target |
2095 | ) |
2096 | .into(), |
2097 | ); |
2098 | } |
2099 | } else if target.contains("watchos-sim" ) { |
2100 | if let Some(arch) = |
2101 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2102 | { |
2103 | let sdk_details = |
2104 | apple_os_sdk_parts(AppleOs::WatchOs, &AppleArchSpec::Simulator("" )); |
2105 | let deployment_target = self.apple_deployment_version( |
2106 | AppleOs::WatchOs, |
2107 | None, |
2108 | &sdk_details.sdk, |
2109 | ); |
2110 | cmd.args.push( |
2111 | format!( |
2112 | "--target= {}-apple-watchos {}-simulator" , |
2113 | arch, deployment_target |
2114 | ) |
2115 | .into(), |
2116 | ); |
2117 | } |
2118 | } else if target.contains("tvos-sim" ) || target.contains("x86_64-apple-tvos" ) { |
2119 | if let Some(arch) = |
2120 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2121 | { |
2122 | let sdk_details = |
2123 | apple_os_sdk_parts(AppleOs::TvOs, &AppleArchSpec::Simulator("" )); |
2124 | let deployment_target = self.apple_deployment_version( |
2125 | AppleOs::TvOs, |
2126 | None, |
2127 | &sdk_details.sdk, |
2128 | ); |
2129 | cmd.args.push( |
2130 | format!( |
2131 | "--target= {}-apple-tvos {}-simulator" , |
2132 | arch, deployment_target |
2133 | ) |
2134 | .into(), |
2135 | ); |
2136 | } |
2137 | } else if target.contains("aarch64-apple-tvos" ) { |
2138 | if let Some(arch) = |
2139 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2140 | { |
2141 | let sdk_details = |
2142 | apple_os_sdk_parts(AppleOs::TvOs, &AppleArchSpec::Device("" )); |
2143 | let deployment_target = self.apple_deployment_version( |
2144 | AppleOs::TvOs, |
2145 | None, |
2146 | &sdk_details.sdk, |
2147 | ); |
2148 | cmd.args.push( |
2149 | format!("--target= {}-apple-tvos {}" , arch, deployment_target).into(), |
2150 | ); |
2151 | } |
2152 | } else if target.contains("visionos-sim" ) { |
2153 | if let Some(arch) = |
2154 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2155 | { |
2156 | let sdk_details = apple_os_sdk_parts( |
2157 | AppleOs::VisionOS, |
2158 | &AppleArchSpec::Simulator("" ), |
2159 | ); |
2160 | let deployment_target = self.apple_deployment_version( |
2161 | AppleOs::VisionOS, |
2162 | None, |
2163 | &sdk_details.sdk, |
2164 | ); |
2165 | cmd.args.push( |
2166 | format!( |
2167 | "--target= {}-apple-xros {}-simulator" , |
2168 | arch, deployment_target |
2169 | ) |
2170 | .into(), |
2171 | ); |
2172 | } |
2173 | } else if target.contains("visionos" ) { |
2174 | if let Some(arch) = |
2175 | map_darwin_target_from_rust_to_compiler_architecture(target) |
2176 | { |
2177 | let sdk_details = |
2178 | apple_os_sdk_parts(AppleOs::VisionOS, &AppleArchSpec::Device("" )); |
2179 | let deployment_target = self.apple_deployment_version( |
2180 | AppleOs::VisionOS, |
2181 | None, |
2182 | &sdk_details.sdk, |
2183 | ); |
2184 | cmd.args.push( |
2185 | format!("--target= {}-apple-xros {}" , arch, deployment_target).into(), |
2186 | ); |
2187 | } |
2188 | } else if let Ok(index) = target_info::RISCV_ARCH_MAPPING |
2189 | .binary_search_by_key(&arch, |(arch, _)| arch) |
2190 | { |
2191 | cmd.args.push( |
2192 | format!( |
2193 | "--target= {}- {}" , |
2194 | target_info::RISCV_ARCH_MAPPING[index].1, |
2195 | rest |
2196 | ) |
2197 | .into(), |
2198 | ); |
2199 | } else if target.contains("uefi" ) { |
2200 | if target.contains("x86_64" ) { |
2201 | cmd.args.push("--target=x86_64-unknown-windows-gnu" .into()); |
2202 | } else if target.contains("i686" ) { |
2203 | cmd.args.push("--target=i686-unknown-windows-gnu" .into()) |
2204 | } else if target.contains("aarch64" ) { |
2205 | cmd.args.push("--target=aarch64-unknown-windows-gnu" .into()) |
2206 | } |
2207 | } else if target.ends_with("-freebsd" ) { |
2208 | // FreeBSD only supports C++11 and above when compiling against libc++ |
2209 | // (available from FreeBSD 10 onwards). Under FreeBSD, clang uses libc++ by |
2210 | // default on FreeBSD 10 and newer unless `--target` is manually passed to |
2211 | // the compiler, in which case its default behavior differs: |
2212 | // * If --target=xxx-unknown-freebsdX(.Y) is specified and X is greater than |
2213 | // or equal to 10, clang++ uses libc++ |
2214 | // * If --target=xxx-unknown-freebsd is specified (without a version), |
2215 | // clang++ cannot assume libc++ is available and reverts to a default of |
2216 | // libstdc++ (this behavior was changed in llvm 14). |
2217 | // |
2218 | // This breaks C++11 (or greater) builds if targeting FreeBSD with the |
2219 | // generic xxx-unknown-freebsd triple on clang 13 or below *without* |
2220 | // explicitly specifying that libc++ should be used. |
2221 | // When cross-compiling, we can't infer from the rust/cargo target triple |
2222 | // which major version of FreeBSD we are targeting, so we need to make sure |
2223 | // that libc++ is used (unless the user has explicitly specified otherwise). |
2224 | // There's no compelling reason to use a different approach when compiling |
2225 | // natively. |
2226 | if self.cpp && self.cpp_set_stdlib.is_none() { |
2227 | cmd.push_cc_arg("-stdlib=libc++" .into()); |
2228 | } |
2229 | |
2230 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
2231 | } else if let Ok(index) = target_info::WINDOWS_TRIPLE_MAPPING |
2232 | .binary_search_by_key(&target, |(target, _)| target) |
2233 | { |
2234 | cmd.args.push( |
2235 | format!( |
2236 | "--target= {}- {}" , |
2237 | target_info::WINDOWS_TRIPLE_MAPPING[index].1, |
2238 | rest |
2239 | ) |
2240 | .into(), |
2241 | ) |
2242 | } else { |
2243 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
2244 | } |
2245 | } |
2246 | } |
2247 | ToolFamily::Msvc { clang_cl } => { |
2248 | // This is an undocumented flag from MSVC but helps with making |
2249 | // builds more reproducible by avoiding putting timestamps into |
2250 | // files. |
2251 | cmd.push_cc_arg("-Brepro" .into()); |
2252 | |
2253 | if clang_cl { |
2254 | if target.contains("x86_64" ) { |
2255 | cmd.push_cc_arg("-m64" .into()); |
2256 | } else if target.contains("86" ) { |
2257 | cmd.push_cc_arg("-m32" .into()); |
2258 | cmd.push_cc_arg("-arch:IA32" .into()); |
2259 | } else { |
2260 | cmd.push_cc_arg(format!("--target= {}" , target).into()); |
2261 | } |
2262 | } else if target.contains("i586" ) { |
2263 | cmd.push_cc_arg("-arch:IA32" .into()); |
2264 | } else if target.contains("arm64ec" ) { |
2265 | cmd.push_cc_arg("-arm64EC" .into()); |
2266 | } |
2267 | // There is a check in corecrt.h that will generate a |
2268 | // compilation error if |
2269 | // _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE is |
2270 | // not defined to 1. The check was added in Windows |
2271 | // 8 days because only store apps were allowed on ARM. |
2272 | // This changed with the release of Windows 10 IoT Core. |
2273 | // The check will be going away in future versions of |
2274 | // the SDK, but for all released versions of the |
2275 | // Windows SDK it is required. |
2276 | if target.contains("arm" ) || target.contains("thumb" ) { |
2277 | cmd.args |
2278 | .push("-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1" .into()); |
2279 | } |
2280 | } |
2281 | ToolFamily::Gnu => { |
2282 | if target.contains("darwin" ) { |
2283 | if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) |
2284 | { |
2285 | cmd.args.push("-arch" .into()); |
2286 | cmd.args.push(arch.into()); |
2287 | } |
2288 | } |
2289 | |
2290 | if target.contains("-kmc-solid_" ) { |
2291 | cmd.args.push("-finput-charset=utf-8" .into()); |
2292 | } |
2293 | |
2294 | if self.static_flag.is_none() { |
2295 | let features = self.getenv("CARGO_CFG_TARGET_FEATURE" ); |
2296 | let features = features.as_deref().unwrap_or_default(); |
2297 | if features.to_string_lossy().contains("crt-static" ) { |
2298 | cmd.args.push("-static" .into()); |
2299 | } |
2300 | } |
2301 | |
2302 | // armv7 targets get to use armv7 instructions |
2303 | if (target.starts_with("armv7" ) || target.starts_with("thumbv7" )) |
2304 | && (target.contains("-linux-" ) || target.contains("-kmc-solid_" )) |
2305 | { |
2306 | cmd.args.push("-march=armv7-a" .into()); |
2307 | |
2308 | if target.ends_with("eabihf" ) { |
2309 | // lowest common denominator FPU |
2310 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
2311 | } |
2312 | } |
2313 | |
2314 | // (x86 Android doesn't say "eabi") |
2315 | if target.contains("-androideabi" ) && target.contains("v7" ) { |
2316 | // -march=armv7-a handled above |
2317 | cmd.args.push("-mthumb" .into()); |
2318 | if !target.contains("neon" ) { |
2319 | // On android we can guarantee some extra float instructions |
2320 | // (specified in the android spec online) |
2321 | // NEON guarantees even more; see below. |
2322 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
2323 | } |
2324 | cmd.args.push("-mfloat-abi=softfp" .into()); |
2325 | } |
2326 | |
2327 | if target.contains("neon" ) { |
2328 | cmd.args.push("-mfpu=neon-vfpv4" .into()); |
2329 | } |
2330 | |
2331 | if target.starts_with("armv4t-unknown-linux-" ) { |
2332 | cmd.args.push("-march=armv4t" .into()); |
2333 | cmd.args.push("-marm" .into()); |
2334 | cmd.args.push("-mfloat-abi=soft" .into()); |
2335 | } |
2336 | |
2337 | if target.starts_with("armv5te-unknown-linux-" ) { |
2338 | cmd.args.push("-march=armv5te" .into()); |
2339 | cmd.args.push("-marm" .into()); |
2340 | cmd.args.push("-mfloat-abi=soft" .into()); |
2341 | } |
2342 | |
2343 | // For us arm == armv6 by default |
2344 | if target.starts_with("arm-unknown-linux-" ) { |
2345 | cmd.args.push("-march=armv6" .into()); |
2346 | cmd.args.push("-marm" .into()); |
2347 | if target.ends_with("hf" ) { |
2348 | cmd.args.push("-mfpu=vfp" .into()); |
2349 | } else { |
2350 | cmd.args.push("-mfloat-abi=soft" .into()); |
2351 | } |
2352 | } |
2353 | |
2354 | // We can guarantee some settings for FRC |
2355 | if target.starts_with("arm-frc-" ) { |
2356 | cmd.args.push("-march=armv7-a" .into()); |
2357 | cmd.args.push("-mcpu=cortex-a9" .into()); |
2358 | cmd.args.push("-mfpu=vfpv3" .into()); |
2359 | cmd.args.push("-mfloat-abi=softfp" .into()); |
2360 | cmd.args.push("-marm" .into()); |
2361 | } |
2362 | |
2363 | // Turn codegen down on i586 to avoid some instructions. |
2364 | if target.starts_with("i586-unknown-linux-" ) { |
2365 | cmd.args.push("-march=pentium" .into()); |
2366 | } |
2367 | |
2368 | // Set codegen level for i686 correctly |
2369 | if target.starts_with("i686-unknown-linux-" ) { |
2370 | cmd.args.push("-march=i686" .into()); |
2371 | } |
2372 | |
2373 | // Looks like `musl-gcc` makes it hard for `-m32` to make its way |
2374 | // all the way to the linker, so we need to actually instruct the |
2375 | // linker that we're generating 32-bit executables as well. This'll |
2376 | // typically only be used for build scripts which transitively use |
2377 | // these flags that try to compile executables. |
2378 | if target == "i686-unknown-linux-musl" || target == "i586-unknown-linux-musl" { |
2379 | cmd.args.push("-Wl,-melf_i386" .into()); |
2380 | } |
2381 | |
2382 | if (target.starts_with("arm" ) || target.starts_with("thumb" )) |
2383 | && target.ends_with("-none-eabihf" ) |
2384 | { |
2385 | cmd.args.push("-mfloat-abi=hard" .into()) |
2386 | } |
2387 | if target.starts_with("thumb" ) { |
2388 | cmd.args.push("-mthumb" .into()); |
2389 | } |
2390 | if target.starts_with("thumbv6m" ) { |
2391 | cmd.args.push("-march=armv6s-m" .into()); |
2392 | } |
2393 | if target.starts_with("thumbv7em" ) { |
2394 | cmd.args.push("-march=armv7e-m" .into()); |
2395 | |
2396 | if target.ends_with("eabihf" ) { |
2397 | cmd.args.push("-mfpu=fpv4-sp-d16" .into()) |
2398 | } |
2399 | } |
2400 | if target.starts_with("thumbv7m" ) { |
2401 | cmd.args.push("-march=armv7-m" .into()); |
2402 | } |
2403 | if target.starts_with("thumbv8m.base" ) { |
2404 | cmd.args.push("-march=armv8-m.base" .into()); |
2405 | } |
2406 | if target.starts_with("thumbv8m.main" ) { |
2407 | cmd.args.push("-march=armv8-m.main" .into()); |
2408 | |
2409 | if target.ends_with("eabihf" ) { |
2410 | cmd.args.push("-mfpu=fpv5-sp-d16" .into()) |
2411 | } |
2412 | } |
2413 | if target.starts_with("armebv7r" ) | target.starts_with("armv7r" ) { |
2414 | if target.starts_with("armeb" ) { |
2415 | cmd.args.push("-mbig-endian" .into()); |
2416 | } else { |
2417 | cmd.args.push("-mlittle-endian" .into()); |
2418 | } |
2419 | |
2420 | // ARM mode |
2421 | cmd.args.push("-marm" .into()); |
2422 | |
2423 | // R Profile |
2424 | cmd.args.push("-march=armv7-r" .into()); |
2425 | |
2426 | if target.ends_with("eabihf" ) { |
2427 | // lowest common denominator FPU |
2428 | // (see Cortex-R4 technical reference manual) |
2429 | cmd.args.push("-mfpu=vfpv3-d16" .into()) |
2430 | } |
2431 | } |
2432 | if target.starts_with("armv7a" ) { |
2433 | cmd.args.push("-march=armv7-a" .into()); |
2434 | |
2435 | if target.ends_with("eabihf" ) { |
2436 | // lowest common denominator FPU |
2437 | cmd.args.push("-mfpu=vfpv3-d16" .into()); |
2438 | } |
2439 | } |
2440 | if target.starts_with("riscv32" ) || target.starts_with("riscv64" ) { |
2441 | // get the 32i/32imac/32imc/64gc/64imac/... part |
2442 | let mut parts = target.split('-' ); |
2443 | if let Some(arch) = parts.next() { |
2444 | let arch = &arch[5..]; |
2445 | if arch.starts_with("64" ) { |
2446 | if target.contains("linux" ) |
2447 | | target.contains("freebsd" ) |
2448 | | target.contains("netbsd" ) |
2449 | | target.contains("linux" ) |
2450 | { |
2451 | cmd.args.push(("-march=rv64gc" ).into()); |
2452 | cmd.args.push("-mabi=lp64d" .into()); |
2453 | } else { |
2454 | cmd.args.push(("-march=rv" .to_owned() + arch).into()); |
2455 | cmd.args.push("-mabi=lp64" .into()); |
2456 | } |
2457 | } else if arch.starts_with("32" ) { |
2458 | if target.contains("linux" ) { |
2459 | cmd.args.push(("-march=rv32gc" ).into()); |
2460 | cmd.args.push("-mabi=ilp32d" .into()); |
2461 | } else { |
2462 | cmd.args.push(("-march=rv" .to_owned() + arch).into()); |
2463 | cmd.args.push("-mabi=ilp32" .into()); |
2464 | } |
2465 | } else { |
2466 | cmd.args.push("-mcmodel=medany" .into()); |
2467 | } |
2468 | } |
2469 | } |
2470 | } |
2471 | } |
2472 | |
2473 | if target.contains("-apple-" ) { |
2474 | self.apple_flags(cmd)?; |
2475 | } |
2476 | |
2477 | if self.static_flag.unwrap_or(false) { |
2478 | cmd.args.push("-static" .into()); |
2479 | } |
2480 | if self.shared_flag.unwrap_or(false) { |
2481 | cmd.args.push("-shared" .into()); |
2482 | } |
2483 | |
2484 | if self.cpp { |
2485 | match (self.cpp_set_stdlib.as_ref(), cmd.family) { |
2486 | (None, _) => {} |
2487 | (Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Clang { .. }) => { |
2488 | cmd.push_cc_arg(format!("-stdlib=lib {}" , stdlib).into()); |
2489 | } |
2490 | _ => { |
2491 | self.cargo_output.print_warning(&format_args!("cpp_set_stdlib is specified, but the {:?} compiler does not support this option, ignored" , cmd.family)); |
2492 | } |
2493 | } |
2494 | } |
2495 | |
2496 | Ok(()) |
2497 | } |
2498 | |
2499 | fn has_flags(&self) -> bool { |
2500 | let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" }; |
2501 | let flags_env_var_value = self.getenv_with_target_prefixes(flags_env_var_name); |
2502 | flags_env_var_value.is_ok() |
2503 | } |
2504 | |
2505 | fn msvc_macro_assembler(&self) -> Result<(Command, &'static str), Error> { |
2506 | let target = self.get_target()?; |
2507 | let tool = if target.contains("x86_64" ) { |
2508 | "ml64.exe" |
2509 | } else if target.contains("arm" ) { |
2510 | "armasm.exe" |
2511 | } else if target.contains("aarch64" ) { |
2512 | "armasm64.exe" |
2513 | } else { |
2514 | "ml.exe" |
2515 | }; |
2516 | let mut cmd = self |
2517 | .windows_registry_find(&target, tool) |
2518 | .unwrap_or_else(|| self.cmd(tool)); |
2519 | cmd.arg("-nologo" ); // undocumented, yet working with armasm[64] |
2520 | for directory in self.include_directories.iter() { |
2521 | cmd.arg("-I" ).arg(&**directory); |
2522 | } |
2523 | if target.contains("aarch64" ) || target.contains("arm" ) { |
2524 | if self.get_debug() { |
2525 | cmd.arg("-g" ); |
2526 | } |
2527 | |
2528 | for (key, value) in self.definitions.iter() { |
2529 | cmd.arg("-PreDefine" ); |
2530 | if let Some(ref value) = *value { |
2531 | if let Ok(i) = value.parse::<i32>() { |
2532 | cmd.arg(format!(" {} SETA {}" , key, i)); |
2533 | } else if value.starts_with('"' ) && value.ends_with('"' ) { |
2534 | cmd.arg(format!(" {} SETS {}" , key, value)); |
2535 | } else { |
2536 | cmd.arg(format!(" {} SETS \"{}\"" , key, value)); |
2537 | } |
2538 | } else { |
2539 | cmd.arg(format!(" {} SETL {}" , key, "{TRUE}" )); |
2540 | } |
2541 | } |
2542 | } else { |
2543 | if self.get_debug() { |
2544 | cmd.arg("-Zi" ); |
2545 | } |
2546 | |
2547 | for (key, value) in self.definitions.iter() { |
2548 | if let Some(ref value) = *value { |
2549 | cmd.arg(format!("-D {}= {}" , key, value)); |
2550 | } else { |
2551 | cmd.arg(format!("-D {}" , key)); |
2552 | } |
2553 | } |
2554 | } |
2555 | |
2556 | if target.contains("i686" ) || target.contains("i586" ) { |
2557 | cmd.arg("-safeseh" ); |
2558 | } |
2559 | |
2560 | Ok((cmd, tool)) |
2561 | } |
2562 | |
2563 | fn assemble(&self, lib_name: &str, dst: &Path, objs: &[Object]) -> Result<(), Error> { |
2564 | // Delete the destination if it exists as we want to |
2565 | // create on the first iteration instead of appending. |
2566 | let _ = fs::remove_file(dst); |
2567 | |
2568 | // Add objects to the archive in limited-length batches. This helps keep |
2569 | // the length of the command line within a reasonable length to avoid |
2570 | // blowing system limits on limiting platforms like Windows. |
2571 | let objs: Vec<_> = objs |
2572 | .iter() |
2573 | .map(|o| o.dst.as_path()) |
2574 | .chain(self.objects.iter().map(std::ops::Deref::deref)) |
2575 | .collect(); |
2576 | for chunk in objs.chunks(100) { |
2577 | self.assemble_progressive(dst, chunk)?; |
2578 | } |
2579 | |
2580 | if self.cuda && self.cuda_file_count() > 0 { |
2581 | // Link the device-side code and add it to the target library, |
2582 | // so that non-CUDA linker can link the final binary. |
2583 | |
2584 | let out_dir = self.get_out_dir()?; |
2585 | let dlink = out_dir.join(lib_name.to_owned() + "_dlink.o" ); |
2586 | let mut nvcc = self.get_compiler().to_command(); |
2587 | nvcc.arg("--device-link" ).arg("-o" ).arg(&dlink).arg(dst); |
2588 | run(&mut nvcc, "nvcc" , &self.cargo_output)?; |
2589 | self.assemble_progressive(dst, &[dlink.as_path()])?; |
2590 | } |
2591 | |
2592 | let target = self.get_target()?; |
2593 | if target.contains("msvc" ) { |
2594 | // The Rust compiler will look for libfoo.a and foo.lib, but the |
2595 | // MSVC linker will also be passed foo.lib, so be sure that both |
2596 | // exist for now. |
2597 | |
2598 | let lib_dst = dst.with_file_name(format!(" {}.lib" , lib_name)); |
2599 | let _ = fs::remove_file(&lib_dst); |
2600 | match fs::hard_link(dst, &lib_dst).or_else(|_| { |
2601 | // if hard-link fails, just copy (ignoring the number of bytes written) |
2602 | fs::copy(dst, &lib_dst).map(|_| ()) |
2603 | }) { |
2604 | Ok(_) => (), |
2605 | Err(_) => { |
2606 | return Err(Error::new( |
2607 | ErrorKind::IOError, |
2608 | "Could not copy or create a hard-link to the generated lib file." , |
2609 | )); |
2610 | } |
2611 | }; |
2612 | } else { |
2613 | // Non-msvc targets (those using `ar`) need a separate step to add |
2614 | // the symbol table to archives since our construction command of |
2615 | // `cq` doesn't add it for us. |
2616 | let (mut ar, cmd, _any_flags) = self.get_ar()?; |
2617 | |
2618 | // NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s` |
2619 | // here represents a _mode_, not an arbitrary flag. Further discussion of this choice |
2620 | // can be seen in https://github.com/rust-lang/cc-rs/pull/763. |
2621 | run(ar.arg("s" ).arg(dst), &cmd, &self.cargo_output)?; |
2622 | } |
2623 | |
2624 | Ok(()) |
2625 | } |
2626 | |
2627 | fn assemble_progressive(&self, dst: &Path, objs: &[&Path]) -> Result<(), Error> { |
2628 | let target = self.get_target()?; |
2629 | |
2630 | let (mut cmd, program, any_flags) = self.get_ar()?; |
2631 | if target.contains("msvc" ) && !program.to_string_lossy().contains("llvm-ar" ) { |
2632 | // NOTE: -out: here is an I/O flag, and so must be included even if $ARFLAGS/ar_flag is |
2633 | // in use. -nologo on the other hand is just a regular flag, and one that we'll skip if |
2634 | // the caller has explicitly dictated the flags they want. See |
2635 | // https://github.com/rust-lang/cc-rs/pull/763 for further discussion. |
2636 | let mut out = OsString::from("-out:" ); |
2637 | out.push(dst); |
2638 | cmd.arg(out); |
2639 | if !any_flags { |
2640 | cmd.arg("-nologo" ); |
2641 | } |
2642 | // If the library file already exists, add the library name |
2643 | // as an argument to let lib.exe know we are appending the objs. |
2644 | if dst.exists() { |
2645 | cmd.arg(dst); |
2646 | } |
2647 | cmd.args(objs); |
2648 | run(&mut cmd, &program, &self.cargo_output)?; |
2649 | } else { |
2650 | // Set an environment variable to tell the OSX archiver to ensure |
2651 | // that all dates listed in the archive are zero, improving |
2652 | // determinism of builds. AFAIK there's not really official |
2653 | // documentation of this but there's a lot of references to it if |
2654 | // you search google. |
2655 | // |
2656 | // You can reproduce this locally on a mac with: |
2657 | // |
2658 | // $ touch foo.c |
2659 | // $ cc -c foo.c -o foo.o |
2660 | // |
2661 | // # Notice that these two checksums are different |
2662 | // $ ar crus libfoo1.a foo.o && sleep 2 && ar crus libfoo2.a foo.o |
2663 | // $ md5sum libfoo*.a |
2664 | // |
2665 | // # Notice that these two checksums are the same |
2666 | // $ export ZERO_AR_DATE=1 |
2667 | // $ ar crus libfoo1.a foo.o && sleep 2 && touch foo.o && ar crus libfoo2.a foo.o |
2668 | // $ md5sum libfoo*.a |
2669 | // |
2670 | // In any case if this doesn't end up getting read, it shouldn't |
2671 | // cause that many issues! |
2672 | cmd.env("ZERO_AR_DATE" , "1" ); |
2673 | |
2674 | // NOTE: We add cq here regardless of whether $ARFLAGS/ar_flag have been used because |
2675 | // it dictates the _mode_ ar runs in, which the setter of $ARFLAGS/ar_flag can't |
2676 | // dictate. See https://github.com/rust-lang/cc-rs/pull/763 for further discussion. |
2677 | run( |
2678 | cmd.arg("cq" ).arg(dst).args(objs), |
2679 | &program, |
2680 | &self.cargo_output, |
2681 | )?; |
2682 | } |
2683 | |
2684 | Ok(()) |
2685 | } |
2686 | |
2687 | fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> { |
2688 | let target = self.get_target()?; |
2689 | let os = if target.contains("-darwin" ) { |
2690 | AppleOs::MacOs |
2691 | } else if target.contains("-watchos" ) { |
2692 | AppleOs::WatchOs |
2693 | } else if target.contains("-tvos" ) { |
2694 | AppleOs::TvOs |
2695 | } else if target.contains("-visionos" ) { |
2696 | AppleOs::VisionOS |
2697 | } else { |
2698 | AppleOs::Ios |
2699 | }; |
2700 | let is_mac = matches!(os, AppleOs::MacOs); |
2701 | |
2702 | let arch_str = target.split('-' ).nth(0).ok_or_else(|| { |
2703 | Error::new( |
2704 | ErrorKind::ArchitectureInvalid, |
2705 | format!("Unknown architecture for {:?} target." , os), |
2706 | ) |
2707 | })?; |
2708 | |
2709 | let is_catalyst = match target.split('-' ).nth(3) { |
2710 | Some(v) => v == "macabi" , |
2711 | None => false, |
2712 | }; |
2713 | |
2714 | let is_arm_sim = match target.split('-' ).nth(3) { |
2715 | Some(v) => v == "sim" , |
2716 | None => false, |
2717 | }; |
2718 | |
2719 | let arch = if is_mac { |
2720 | match arch_str { |
2721 | "i686" => AppleArchSpec::Device("-m32" ), |
2722 | "x86_64" | "x86_64h" | "aarch64" | "arm64e" => AppleArchSpec::Device("-m64" ), |
2723 | _ => { |
2724 | return Err(Error::new( |
2725 | ErrorKind::ArchitectureInvalid, |
2726 | "Unknown architecture for macOS target." , |
2727 | )); |
2728 | } |
2729 | } |
2730 | } else if is_catalyst { |
2731 | match arch_str { |
2732 | "arm64e" => AppleArchSpec::Catalyst("arm64e" ), |
2733 | "arm64" | "aarch64" => AppleArchSpec::Catalyst("arm64" ), |
2734 | "x86_64" | "x86_64h" => AppleArchSpec::Catalyst("-m64" ), |
2735 | _ => { |
2736 | return Err(Error::new( |
2737 | ErrorKind::ArchitectureInvalid, |
2738 | "Unknown architecture for iOS target." , |
2739 | )); |
2740 | } |
2741 | } |
2742 | } else if is_arm_sim { |
2743 | match arch_str { |
2744 | "arm64" | "aarch64" => AppleArchSpec::Simulator("arm64" ), |
2745 | "x86_64" | "x86_64h" => AppleArchSpec::Simulator("-m64" ), |
2746 | _ => { |
2747 | return Err(Error::new( |
2748 | ErrorKind::ArchitectureInvalid, |
2749 | "Unknown architecture for simulator target." , |
2750 | )); |
2751 | } |
2752 | } |
2753 | } else { |
2754 | match arch_str { |
2755 | "arm" | "armv7" | "thumbv7" => AppleArchSpec::Device("armv7" ), |
2756 | "armv7k" => AppleArchSpec::Device("armv7k" ), |
2757 | "armv7s" | "thumbv7s" => AppleArchSpec::Device("armv7s" ), |
2758 | "arm64e" => AppleArchSpec::Device("arm64e" ), |
2759 | "arm64" | "aarch64" => AppleArchSpec::Device("arm64" ), |
2760 | "arm64_32" => AppleArchSpec::Device("arm64_32" ), |
2761 | "i386" | "i686" => AppleArchSpec::Simulator("-m32" ), |
2762 | "x86_64" | "x86_64h" => AppleArchSpec::Simulator("-m64" ), |
2763 | _ => { |
2764 | return Err(Error::new( |
2765 | ErrorKind::ArchitectureInvalid, |
2766 | format!("Unknown architecture for {:?} target." , os), |
2767 | )); |
2768 | } |
2769 | } |
2770 | }; |
2771 | |
2772 | let sdk_details = apple_os_sdk_parts(os, &arch); |
2773 | let min_version = self.apple_deployment_version(os, Some(arch_str), &sdk_details.sdk); |
2774 | |
2775 | match arch { |
2776 | AppleArchSpec::Device(_) if is_mac => { |
2777 | cmd.args |
2778 | .push(format!("-mmacosx-version-min= {}" , min_version).into()); |
2779 | } |
2780 | AppleArchSpec::Device(arch) => { |
2781 | cmd.args.push("-arch" .into()); |
2782 | cmd.args.push(arch.into()); |
2783 | // `-mxros-version-min` does not exist |
2784 | // https://github.com/llvm/llvm-project/issues/88271 |
2785 | if os != AppleOs::VisionOS { |
2786 | cmd.args.push( |
2787 | format!("-m {}os-version-min= {}" , sdk_details.sdk_prefix, min_version) |
2788 | .into(), |
2789 | ); |
2790 | } |
2791 | } |
2792 | AppleArchSpec::Simulator(arch) => { |
2793 | if arch.starts_with('-' ) { |
2794 | // -m32 or -m64 |
2795 | cmd.args.push(arch.into()); |
2796 | } else { |
2797 | cmd.args.push("-arch" .into()); |
2798 | cmd.args.push(arch.into()); |
2799 | } |
2800 | if os != AppleOs::VisionOS { |
2801 | cmd.args.push( |
2802 | format!( |
2803 | "-m {}simulator-version-min= {}" , |
2804 | sdk_details.sim_prefix, min_version |
2805 | ) |
2806 | .into(), |
2807 | ); |
2808 | } |
2809 | } |
2810 | AppleArchSpec::Catalyst(_) => {} |
2811 | }; |
2812 | |
2813 | // AppleClang sometimes requires sysroot even for darwin |
2814 | if cmd.is_xctoolchain_clang() || !target.ends_with("-darwin" ) { |
2815 | self.cargo_output.print_metadata(&format_args!( |
2816 | "Detecting {:?} SDK path for {}" , |
2817 | os, sdk_details.sdk |
2818 | )); |
2819 | let sdk_path = self.apple_sdk_root(&sdk_details.sdk)?; |
2820 | |
2821 | cmd.args.push("-isysroot" .into()); |
2822 | cmd.args.push(OsStr::new(&sdk_path).to_owned()); |
2823 | |
2824 | if let AppleArchSpec::Catalyst(_) = arch { |
2825 | // Mac Catalyst uses the macOS SDK, but to compile against and |
2826 | // link to iOS-specific frameworks, we should have the support |
2827 | // library stubs in the include and library search path. |
2828 | let ios_support = Path::new(&sdk_path).join("System/iOSSupport" ); |
2829 | |
2830 | cmd.args.extend([ |
2831 | // Header search path |
2832 | OsString::from("-isystem" ), |
2833 | ios_support.join("usr/include" ).into(), |
2834 | // Framework header search path |
2835 | OsString::from("-iframework" ), |
2836 | ios_support.join("System/Library/Frameworks" ).into(), |
2837 | // Library search path |
2838 | { |
2839 | let mut s = OsString::from("-L" ); |
2840 | s.push(ios_support.join("usr/lib" )); |
2841 | s |
2842 | }, |
2843 | // Framework linker search path |
2844 | { |
2845 | // Technically, we _could_ avoid emitting `-F`, as |
2846 | // `-iframework` implies it, but let's keep it in for |
2847 | // clarity. |
2848 | let mut s = OsString::from("-F" ); |
2849 | s.push(ios_support.join("System/Library/Frameworks" )); |
2850 | s |
2851 | }, |
2852 | ]); |
2853 | } |
2854 | } |
2855 | |
2856 | Ok(()) |
2857 | } |
2858 | |
2859 | fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command { |
2860 | let mut cmd = Command::new(prog); |
2861 | for (a, b) in self.env.iter() { |
2862 | cmd.env(a, b); |
2863 | } |
2864 | cmd |
2865 | } |
2866 | |
2867 | fn get_base_compiler(&self) -> Result<Tool, Error> { |
2868 | let out_dir = self.get_out_dir().ok(); |
2869 | let out_dir = out_dir.as_deref(); |
2870 | |
2871 | if let Some(c) = &self.compiler { |
2872 | return Ok(Tool::new( |
2873 | (**c).to_owned(), |
2874 | &self.cached_compiler_family, |
2875 | &self.cargo_output, |
2876 | out_dir, |
2877 | )); |
2878 | } |
2879 | let target = self.get_target()?; |
2880 | let target = &*target; |
2881 | let (env, msvc, gnu, traditional, clang) = if self.cpp { |
2882 | ("CXX" , "cl.exe" , "g++" , "c++" , "clang++" ) |
2883 | } else { |
2884 | ("CC" , "cl.exe" , "gcc" , "cc" , "clang" ) |
2885 | }; |
2886 | |
2887 | // On historical Solaris systems, "cc" may have been Sun Studio, which |
2888 | // is not flag-compatible with "gcc". This history casts a long shadow, |
2889 | // and many modern illumos distributions today ship GCC as "gcc" without |
2890 | // also making it available as "cc". |
2891 | let default = if cfg!(target_os = "solaris" ) || cfg!(target_os = "illumos" ) { |
2892 | gnu |
2893 | } else { |
2894 | traditional |
2895 | }; |
2896 | |
2897 | let cl_exe = self.windows_registry_find_tool(target, "cl.exe" ); |
2898 | |
2899 | let tool_opt: Option<Tool> = self |
2900 | .env_tool(env) |
2901 | .map(|(tool, wrapper, args)| { |
2902 | // find the driver mode, if any |
2903 | const DRIVER_MODE: &str = "--driver-mode=" ; |
2904 | let driver_mode = args |
2905 | .iter() |
2906 | .find(|a| a.starts_with(DRIVER_MODE)) |
2907 | .map(|a| &a[DRIVER_MODE.len()..]); |
2908 | // Chop off leading/trailing whitespace to work around |
2909 | // semi-buggy build scripts which are shared in |
2910 | // makefiles/configure scripts (where spaces are far more |
2911 | // lenient) |
2912 | let mut t = Tool::with_clang_driver( |
2913 | tool, |
2914 | driver_mode, |
2915 | &self.cached_compiler_family, |
2916 | &self.cargo_output, |
2917 | out_dir, |
2918 | ); |
2919 | if let Some(cc_wrapper) = wrapper { |
2920 | t.cc_wrapper_path = Some(Path::new(&cc_wrapper).to_owned()); |
2921 | } |
2922 | for arg in args { |
2923 | t.cc_wrapper_args.push(arg.into()); |
2924 | } |
2925 | t |
2926 | }) |
2927 | .or_else(|| { |
2928 | if target.contains("emscripten" ) { |
2929 | let tool = if self.cpp { "em++" } else { "emcc" }; |
2930 | // Windows uses bat file so we have to be a bit more specific |
2931 | if cfg!(windows) { |
2932 | let mut t = Tool::with_family( |
2933 | PathBuf::from("cmd" ), |
2934 | ToolFamily::Clang { zig_cc: false }, |
2935 | ); |
2936 | t.args.push("/c" .into()); |
2937 | t.args.push(format!(" {}.bat" , tool).into()); |
2938 | Some(t) |
2939 | } else { |
2940 | Some(Tool::new( |
2941 | PathBuf::from(tool), |
2942 | &self.cached_compiler_family, |
2943 | &self.cargo_output, |
2944 | out_dir, |
2945 | )) |
2946 | } |
2947 | } else { |
2948 | None |
2949 | } |
2950 | }) |
2951 | .or_else(|| cl_exe.clone()); |
2952 | |
2953 | let tool = match tool_opt { |
2954 | Some(t) => t, |
2955 | None => { |
2956 | let compiler = if cfg!(windows) && target.contains("windows" ) { |
2957 | if target.contains("msvc" ) { |
2958 | msvc.to_string() |
2959 | } else { |
2960 | let cc = if target.contains("llvm" ) { clang } else { gnu }; |
2961 | format!(" {}.exe" , cc) |
2962 | } |
2963 | } else if target.contains("apple-ios" ) |
2964 | | target.contains("apple-watchos" ) |
2965 | | target.contains("apple-tvos" ) |
2966 | | target.contains("apple-visionos" ) |
2967 | { |
2968 | clang.to_string() |
2969 | } else if target.contains("android" ) { |
2970 | autodetect_android_compiler(target, gnu, clang) |
2971 | } else if target.contains("cloudabi" ) { |
2972 | format!(" {}- {}" , target, traditional) |
2973 | } else if Build::is_wasi_target(target) { |
2974 | if self.cpp { |
2975 | "clang++" .to_string() |
2976 | } else { |
2977 | "clang" .to_string() |
2978 | } |
2979 | } else if target.contains("vxworks" ) { |
2980 | if self.cpp { |
2981 | "wr-c++" .to_string() |
2982 | } else { |
2983 | "wr-cc" .to_string() |
2984 | } |
2985 | } else if target.starts_with("armv7a-kmc-solid_" ) { |
2986 | format!("arm-kmc-eabi- {}" , gnu) |
2987 | } else if target.starts_with("aarch64-kmc-solid_" ) { |
2988 | format!("aarch64-kmc-elf- {}" , gnu) |
2989 | } else if self.get_is_cross_compile()? { |
2990 | let prefix = self.prefix_for_target(target); |
2991 | match prefix { |
2992 | Some(prefix) => { |
2993 | let cc = if target.contains("llvm" ) { clang } else { gnu }; |
2994 | format!(" {}- {}" , prefix, cc) |
2995 | } |
2996 | None => default.to_string(), |
2997 | } |
2998 | } else { |
2999 | default.to_string() |
3000 | }; |
3001 | |
3002 | let mut t = Tool::new( |
3003 | PathBuf::from(compiler), |
3004 | &self.cached_compiler_family, |
3005 | &self.cargo_output, |
3006 | out_dir, |
3007 | ); |
3008 | if let Some(cc_wrapper) = self.rustc_wrapper_fallback() { |
3009 | t.cc_wrapper_path = Some(Path::new(&cc_wrapper).to_owned()); |
3010 | } |
3011 | t |
3012 | } |
3013 | }; |
3014 | |
3015 | let mut tool = if self.cuda { |
3016 | assert!( |
3017 | tool.args.is_empty(), |
3018 | "CUDA compilation currently assumes empty pre-existing args" |
3019 | ); |
3020 | let nvcc = match self.getenv_with_target_prefixes("NVCC" ) { |
3021 | Err(_) => PathBuf::from("nvcc" ), |
3022 | Ok(nvcc) => PathBuf::from(&*nvcc), |
3023 | }; |
3024 | let mut nvcc_tool = Tool::with_features( |
3025 | nvcc, |
3026 | None, |
3027 | self.cuda, |
3028 | &self.cached_compiler_family, |
3029 | &self.cargo_output, |
3030 | out_dir, |
3031 | ); |
3032 | if self.ccbin { |
3033 | nvcc_tool |
3034 | .args |
3035 | .push(format!("-ccbin= {}" , tool.path.display()).into()); |
3036 | } |
3037 | nvcc_tool.family = tool.family; |
3038 | nvcc_tool |
3039 | } else { |
3040 | tool |
3041 | }; |
3042 | |
3043 | // New "standalone" C/C++ cross-compiler executables from recent Android NDK |
3044 | // are just shell scripts that call main clang binary (from Android NDK) with |
3045 | // proper `--target` argument. |
3046 | // |
3047 | // For example, armv7a-linux-androideabi16-clang passes |
3048 | // `--target=armv7a-linux-androideabi16` to clang. |
3049 | // |
3050 | // As the shell script calls the main clang binary, the command line limit length |
3051 | // on Windows is restricted to around 8k characters instead of around 32k characters. |
3052 | // To remove this limit, we call the main clang binary directly and construct the |
3053 | // `--target=` ourselves. |
3054 | if cfg!(windows) && android_clang_compiler_uses_target_arg_internally(&tool.path) { |
3055 | if let Some(path) = tool.path.file_name() { |
3056 | let file_name = path.to_str().unwrap().to_owned(); |
3057 | let (target, clang) = file_name.split_at(file_name.rfind('-' ).unwrap()); |
3058 | |
3059 | tool.has_internal_target_arg = true; |
3060 | tool.path.set_file_name(clang.trim_start_matches('-' )); |
3061 | tool.path.set_extension("exe" ); |
3062 | tool.args.push(format!("--target= {}" , target).into()); |
3063 | |
3064 | // Additionally, shell scripts for target i686-linux-android versions 16 to 24 |
3065 | // pass the `mstackrealign` option so we do that here as well. |
3066 | if target.contains("i686-linux-android" ) { |
3067 | let (_, version) = target.split_at(target.rfind('d' ).unwrap() + 1); |
3068 | if let Ok(version) = version.parse::<u32>() { |
3069 | if version > 15 && version < 25 { |
3070 | tool.args.push("-mstackrealign" .into()); |
3071 | } |
3072 | } |
3073 | } |
3074 | }; |
3075 | } |
3076 | |
3077 | // If we found `cl.exe` in our environment, the tool we're returning is |
3078 | // an MSVC-like tool, *and* no env vars were set then set env vars for |
3079 | // the tool that we're returning. |
3080 | // |
3081 | // Env vars are needed for things like `link.exe` being put into PATH as |
3082 | // well as header include paths sometimes. These paths are automatically |
3083 | // included by default but if the `CC` or `CXX` env vars are set these |
3084 | // won't be used. This'll ensure that when the env vars are used to |
3085 | // configure for invocations like `clang-cl` we still get a "works out |
3086 | // of the box" experience. |
3087 | if let Some(cl_exe) = cl_exe { |
3088 | if tool.family == (ToolFamily::Msvc { clang_cl: true }) |
3089 | && tool.env.is_empty() |
3090 | && target.contains("msvc" ) |
3091 | { |
3092 | for (k, v) in cl_exe.env.iter() { |
3093 | tool.env.push((k.to_owned(), v.to_owned())); |
3094 | } |
3095 | } |
3096 | } |
3097 | |
3098 | if target.contains("msvc" ) && tool.family == ToolFamily::Gnu { |
3099 | self.cargo_output |
3100 | .print_warning(&"GNU compiler is not supported for this target" ); |
3101 | } |
3102 | |
3103 | Ok(tool) |
3104 | } |
3105 | |
3106 | /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER` |
3107 | fn rustc_wrapper_fallback(&self) -> Option<Arc<OsStr>> { |
3108 | // No explicit CC wrapper was detected, but check if RUSTC_WRAPPER |
3109 | // is defined and is a build accelerator that is compatible with |
3110 | // C/C++ compilers (e.g. sccache) |
3111 | const VALID_WRAPPERS: &[&str] = &["sccache" , "cachepot" , "buildcache" ]; |
3112 | |
3113 | let rustc_wrapper = self.getenv("RUSTC_WRAPPER" )?; |
3114 | let wrapper_path = Path::new(&rustc_wrapper); |
3115 | let wrapper_stem = wrapper_path.file_stem()?; |
3116 | |
3117 | if VALID_WRAPPERS.contains(&wrapper_stem.to_str()?) { |
3118 | Some(rustc_wrapper) |
3119 | } else { |
3120 | None |
3121 | } |
3122 | } |
3123 | |
3124 | /// Returns compiler path, optional modifier name from whitelist, and arguments vec |
3125 | fn env_tool(&self, name: &str) -> Option<(PathBuf, Option<Arc<OsStr>>, Vec<String>)> { |
3126 | let tool = self.getenv_with_target_prefixes(name).ok()?; |
3127 | let tool = tool.to_string_lossy(); |
3128 | let tool = tool.trim(); |
3129 | |
3130 | if tool.is_empty() { |
3131 | return None; |
3132 | } |
3133 | |
3134 | // If this is an exact path on the filesystem we don't want to do any |
3135 | // interpretation at all, just pass it on through. This'll hopefully get |
3136 | // us to support spaces-in-paths. |
3137 | if Path::new(tool).exists() { |
3138 | return Some(( |
3139 | PathBuf::from(tool), |
3140 | self.rustc_wrapper_fallback(), |
3141 | Vec::new(), |
3142 | )); |
3143 | } |
3144 | |
3145 | // Ok now we want to handle a couple of scenarios. We'll assume from |
3146 | // here on out that spaces are splitting separate arguments. Two major |
3147 | // features we want to support are: |
3148 | // |
3149 | // CC='sccache cc' |
3150 | // |
3151 | // aka using `sccache` or any other wrapper/caching-like-thing for |
3152 | // compilations. We want to know what the actual compiler is still, |
3153 | // though, because our `Tool` API support introspection of it to see |
3154 | // what compiler is in use. |
3155 | // |
3156 | // additionally we want to support |
3157 | // |
3158 | // CC='cc -flag' |
3159 | // |
3160 | // where the CC env var is used to also pass default flags to the C |
3161 | // compiler. |
3162 | // |
3163 | // It's true that everything here is a bit of a pain, but apparently if |
3164 | // you're not literally make or bash then you get a lot of bug reports. |
3165 | let mut known_wrappers = vec![ |
3166 | "ccache" , |
3167 | "distcc" , |
3168 | "sccache" , |
3169 | "icecc" , |
3170 | "cachepot" , |
3171 | "buildcache" , |
3172 | ]; |
3173 | let custom_wrapper = self.getenv("CC_KNOWN_WRAPPER_CUSTOM" ); |
3174 | if custom_wrapper.is_some() { |
3175 | known_wrappers.push(custom_wrapper.as_deref().unwrap().to_str().unwrap()); |
3176 | } |
3177 | |
3178 | let mut parts = tool.split_whitespace(); |
3179 | let maybe_wrapper = match parts.next() { |
3180 | Some(s) => s, |
3181 | None => return None, |
3182 | }; |
3183 | |
3184 | let file_stem = Path::new(maybe_wrapper).file_stem()?.to_str()?; |
3185 | if known_wrappers.contains(&file_stem) { |
3186 | if let Some(compiler) = parts.next() { |
3187 | return Some(( |
3188 | compiler.into(), |
3189 | Some(Arc::<OsStr>::from(OsStr::new(&maybe_wrapper))), |
3190 | parts.map(|s| s.to_string()).collect(), |
3191 | )); |
3192 | } |
3193 | } |
3194 | |
3195 | Some(( |
3196 | maybe_wrapper.into(), |
3197 | self.rustc_wrapper_fallback(), |
3198 | parts.map(|s| s.to_string()).collect(), |
3199 | )) |
3200 | } |
3201 | |
3202 | /// Returns the C++ standard library: |
3203 | /// 1. If [`cpp_link_stdlib`](cc::Build::cpp_link_stdlib) is set, uses its value. |
3204 | /// 2. Else if the `CXXSTDLIB` environment variable is set, uses its value. |
3205 | /// 3. Else the default is `c++` for OS X and BSDs, `c++_shared` for Android, |
3206 | /// `None` for MSVC and `stdc++` for anything else. |
3207 | fn get_cpp_link_stdlib(&self) -> Result<Option<Cow<'_, Path>>, Error> { |
3208 | match &self.cpp_link_stdlib { |
3209 | Some(s) => Ok(s.as_deref().map(Path::new).map(Cow::Borrowed)), |
3210 | None => { |
3211 | if let Ok(stdlib) = self.getenv_with_target_prefixes("CXXSTDLIB" ) { |
3212 | if stdlib.is_empty() { |
3213 | Ok(None) |
3214 | } else { |
3215 | Ok(Some(Cow::Owned(Path::new(&stdlib).to_owned()))) |
3216 | } |
3217 | } else { |
3218 | let target = self.get_target()?; |
3219 | if target.contains("msvc" ) { |
3220 | Ok(None) |
3221 | } else if target.contains("apple" ) |
3222 | | target.contains("freebsd" ) |
3223 | | target.contains("openbsd" ) |
3224 | | target.contains("aix" ) |
3225 | | target.contains("linux-ohos" ) |
3226 | | target.contains("-wasi" ) |
3227 | { |
3228 | Ok(Some(Cow::Borrowed(Path::new("c++" )))) |
3229 | } else if target.contains("android" ) { |
3230 | Ok(Some(Cow::Borrowed(Path::new("c++_shared" )))) |
3231 | } else { |
3232 | Ok(Some(Cow::Borrowed(Path::new("stdc++" )))) |
3233 | } |
3234 | } |
3235 | } |
3236 | } |
3237 | } |
3238 | |
3239 | fn get_ar(&self) -> Result<(Command, PathBuf, bool), Error> { |
3240 | self.try_get_archiver_and_flags() |
3241 | } |
3242 | |
3243 | /// Get the archiver (ar) that's in use for this configuration. |
3244 | /// |
3245 | /// You can use [`Command::get_program`] to get just the path to the command. |
3246 | /// |
3247 | /// This method will take into account all configuration such as debug |
3248 | /// information, optimization level, include directories, defines, etc. |
3249 | /// Additionally, the compiler binary in use follows the standard |
3250 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
3251 | /// environment variables (a number of which are inspected here), and then |
3252 | /// falling back to the default configuration. |
3253 | /// |
3254 | /// # Panics |
3255 | /// |
3256 | /// Panics if an error occurred while determining the architecture. |
3257 | pub fn get_archiver(&self) -> Command { |
3258 | match self.try_get_archiver() { |
3259 | Ok(tool) => tool, |
3260 | Err(e) => fail(&e.message), |
3261 | } |
3262 | } |
3263 | |
3264 | /// Get the archiver that's in use for this configuration. |
3265 | /// |
3266 | /// This will return a result instead of panicking; |
3267 | /// see [`Self::get_archiver`] for the complete description. |
3268 | pub fn try_get_archiver(&self) -> Result<Command, Error> { |
3269 | Ok(self.try_get_archiver_and_flags()?.0) |
3270 | } |
3271 | |
3272 | fn try_get_archiver_and_flags(&self) -> Result<(Command, PathBuf, bool), Error> { |
3273 | let (mut cmd, name) = self.get_base_archiver()?; |
3274 | let mut any_flags = false; |
3275 | if let Ok(flags) = self.envflags("ARFLAGS" ) { |
3276 | any_flags |= !flags.is_empty(); |
3277 | cmd.args(flags); |
3278 | } |
3279 | for flag in &self.ar_flags { |
3280 | any_flags = true; |
3281 | cmd.arg(&**flag); |
3282 | } |
3283 | Ok((cmd, name, any_flags)) |
3284 | } |
3285 | |
3286 | fn get_base_archiver(&self) -> Result<(Command, PathBuf), Error> { |
3287 | if let Some(ref a) = self.archiver { |
3288 | let archiver = &**a; |
3289 | return Ok((self.cmd(archiver), archiver.into())); |
3290 | } |
3291 | |
3292 | self.get_base_archiver_variant("AR" , "ar" ) |
3293 | } |
3294 | |
3295 | /// Get the ranlib that's in use for this configuration. |
3296 | /// |
3297 | /// You can use [`Command::get_program`] to get just the path to the command. |
3298 | /// |
3299 | /// This method will take into account all configuration such as debug |
3300 | /// information, optimization level, include directories, defines, etc. |
3301 | /// Additionally, the compiler binary in use follows the standard |
3302 | /// conventions for this path, e.g. looking at the explicitly set compiler, |
3303 | /// environment variables (a number of which are inspected here), and then |
3304 | /// falling back to the default configuration. |
3305 | /// |
3306 | /// # Panics |
3307 | /// |
3308 | /// Panics if an error occurred while determining the architecture. |
3309 | pub fn get_ranlib(&self) -> Command { |
3310 | match self.try_get_ranlib() { |
3311 | Ok(tool) => tool, |
3312 | Err(e) => fail(&e.message), |
3313 | } |
3314 | } |
3315 | |
3316 | /// Get the ranlib that's in use for this configuration. |
3317 | /// |
3318 | /// This will return a result instead of panicking; |
3319 | /// see [`Self::get_ranlib`] for the complete description. |
3320 | pub fn try_get_ranlib(&self) -> Result<Command, Error> { |
3321 | let mut cmd = self.get_base_ranlib()?; |
3322 | if let Ok(flags) = self.envflags("RANLIBFLAGS" ) { |
3323 | cmd.args(flags); |
3324 | } |
3325 | Ok(cmd) |
3326 | } |
3327 | |
3328 | fn get_base_ranlib(&self) -> Result<Command, Error> { |
3329 | if let Some(ref r) = self.ranlib { |
3330 | return Ok(self.cmd(&**r)); |
3331 | } |
3332 | |
3333 | Ok(self.get_base_archiver_variant("RANLIB" , "ranlib" )?.0) |
3334 | } |
3335 | |
3336 | fn get_base_archiver_variant( |
3337 | &self, |
3338 | env: &str, |
3339 | tool: &str, |
3340 | ) -> Result<(Command, PathBuf), Error> { |
3341 | let target = self.get_target()?; |
3342 | let mut name = PathBuf::new(); |
3343 | let tool_opt: Option<Command> = self |
3344 | .env_tool(env) |
3345 | .map(|(tool, _wrapper, args)| { |
3346 | name.clone_from(&tool); |
3347 | let mut cmd = self.cmd(tool); |
3348 | cmd.args(args); |
3349 | cmd |
3350 | }) |
3351 | .or_else(|| { |
3352 | if target.contains("emscripten" ) { |
3353 | // Windows use bat files so we have to be a bit more specific |
3354 | if cfg!(windows) { |
3355 | let mut cmd = self.cmd("cmd" ); |
3356 | name = format!("em {}.bat" , tool).into(); |
3357 | cmd.arg("/c" ).arg(&name); |
3358 | Some(cmd) |
3359 | } else { |
3360 | name = format!("em {}" , tool).into(); |
3361 | Some(self.cmd(&name)) |
3362 | } |
3363 | } else if target.starts_with("wasm32" ) { |
3364 | // Formally speaking one should be able to use this approach, |
3365 | // parsing -print-search-dirs output, to cover all clang targets, |
3366 | // including Android SDKs and other cross-compilation scenarios... |
3367 | // And even extend it to gcc targets by searching for "ar" instead |
3368 | // of "llvm-ar"... |
3369 | let compiler = self.get_base_compiler().ok()?; |
3370 | if compiler.is_like_clang() { |
3371 | name = format!("llvm- {}" , tool).into(); |
3372 | self.search_programs( |
3373 | &mut self.cmd(&compiler.path), |
3374 | &name, |
3375 | &self.cargo_output, |
3376 | ) |
3377 | .map(|name| self.cmd(name)) |
3378 | } else { |
3379 | None |
3380 | } |
3381 | } else { |
3382 | None |
3383 | } |
3384 | }); |
3385 | |
3386 | let default = tool.to_string(); |
3387 | let tool = match tool_opt { |
3388 | Some(t) => t, |
3389 | None => { |
3390 | if target.contains("android" ) { |
3391 | name = format!("llvm- {}" , tool).into(); |
3392 | match Command::new(&name).arg("--version" ).status() { |
3393 | Ok(status) if status.success() => (), |
3394 | _ => name = format!(" {}- {}" , target.replace("armv7" , "arm" ), tool).into(), |
3395 | } |
3396 | self.cmd(&name) |
3397 | } else if target.contains("msvc" ) { |
3398 | // NOTE: There isn't really a ranlib on msvc, so arguably we should return |
3399 | // `None` somehow here. But in general, callers will already have to be aware |
3400 | // of not running ranlib on Windows anyway, so it feels okay to return lib.exe |
3401 | // here. |
3402 | |
3403 | let compiler = self.get_base_compiler()?; |
3404 | let mut lib = String::new(); |
3405 | if compiler.family == (ToolFamily::Msvc { clang_cl: true }) { |
3406 | // See if there is 'llvm-lib' next to 'clang-cl' |
3407 | // Another possibility could be to see if there is 'clang' |
3408 | // next to 'clang-cl' and use 'search_programs()' to locate |
3409 | // 'llvm-lib'. This is because 'clang-cl' doesn't support |
3410 | // the -print-search-dirs option. |
3411 | if let Some(mut cmd) = self.which(&compiler.path, None) { |
3412 | cmd.pop(); |
3413 | cmd.push("llvm-lib.exe" ); |
3414 | if let Some(llvm_lib) = self.which(&cmd, None) { |
3415 | llvm_lib.to_str().unwrap().clone_into(&mut lib); |
3416 | } |
3417 | } |
3418 | } |
3419 | |
3420 | if lib.is_empty() { |
3421 | name = PathBuf::from("lib.exe" ); |
3422 | let mut cmd = match self.windows_registry_find(&target, "lib.exe" ) { |
3423 | Some(t) => t, |
3424 | None => self.cmd("lib.exe" ), |
3425 | }; |
3426 | if target.contains("arm64ec" ) { |
3427 | cmd.arg("/machine:arm64ec" ); |
3428 | } |
3429 | cmd |
3430 | } else { |
3431 | name = lib.into(); |
3432 | self.cmd(&name) |
3433 | } |
3434 | } else if target.contains("illumos" ) { |
3435 | // The default 'ar' on illumos uses a non-standard flags, |
3436 | // but the OS comes bundled with a GNU-compatible variant. |
3437 | // |
3438 | // Use the GNU-variant to match other Unix systems. |
3439 | name = format!("g {}" , tool).into(); |
3440 | self.cmd(&name) |
3441 | } else if self.get_is_cross_compile()? { |
3442 | match self.prefix_for_target(&target) { |
3443 | Some(p) => { |
3444 | // GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both. |
3445 | // Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be |
3446 | // outright broken (such as when targeting freebsd with `--disable-lto` |
3447 | // toolchain where the archiver attempts to load the LTO plugin anyway but |
3448 | // fails to find one). |
3449 | // |
3450 | // The same applies to ranlib. |
3451 | let mut chosen = default; |
3452 | for &infix in &["" , "-gcc" ] { |
3453 | let target_p = format!(" {}{}- {}" , p, infix, tool); |
3454 | if Command::new(&target_p).output().is_ok() { |
3455 | chosen = target_p; |
3456 | break; |
3457 | } |
3458 | } |
3459 | name = chosen.into(); |
3460 | self.cmd(&name) |
3461 | } |
3462 | None => { |
3463 | name = default.into(); |
3464 | self.cmd(&name) |
3465 | } |
3466 | } |
3467 | } else { |
3468 | name = default.into(); |
3469 | self.cmd(&name) |
3470 | } |
3471 | } |
3472 | }; |
3473 | |
3474 | Ok((tool, name)) |
3475 | } |
3476 | |
3477 | fn prefix_for_target(&self, target: &str) -> Option<Cow<'static, str>> { |
3478 | // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" |
3479 | self.getenv("CROSS_COMPILE" ) |
3480 | .as_deref() |
3481 | .map(|s| s.to_string_lossy().trim_end_matches('-' ).to_owned()) |
3482 | .map(Cow::Owned) |
3483 | .or_else(|| { |
3484 | // Put aside RUSTC_LINKER's prefix to be used as second choice, after CROSS_COMPILE |
3485 | self.getenv("RUSTC_LINKER" ).and_then(|var| { |
3486 | var.to_string_lossy() |
3487 | .strip_suffix("-gcc" ) |
3488 | .map(str::to_string) |
3489 | .map(Cow::Owned) |
3490 | }) |
3491 | }) |
3492 | .or_else(|| { |
3493 | match target { |
3494 | // Note: there is no `aarch64-pc-windows-gnu` target, only `-gnullvm` |
3495 | "aarch64-pc-windows-gnullvm" => Some("aarch64-w64-mingw32" ), |
3496 | "aarch64-uwp-windows-gnu" => Some("aarch64-w64-mingw32" ), |
3497 | "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu" ), |
3498 | "aarch64-unknown-linux-musl" => Some("aarch64-linux-musl" ), |
3499 | "aarch64-unknown-netbsd" => Some("aarch64--netbsd" ), |
3500 | "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
3501 | "armv4t-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
3502 | "armv5te-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
3503 | "armv5te-unknown-linux-musleabi" => Some("arm-linux-gnueabi" ), |
3504 | "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi" ), |
3505 | "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
3506 | "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi" ), |
3507 | "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
3508 | "arm-unknown-netbsd-eabi" => Some("arm--netbsdelf-eabi" ), |
3509 | "armv6-unknown-netbsd-eabihf" => Some("armv6--netbsdelf-eabihf" ), |
3510 | "armv7-unknown-linux-gnueabi" => Some("arm-linux-gnueabi" ), |
3511 | "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
3512 | "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
3513 | "armv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
3514 | "armv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
3515 | "thumbv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
3516 | "thumbv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
3517 | "thumbv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf" ), |
3518 | "thumbv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf" ), |
3519 | "armv7-unknown-netbsd-eabihf" => Some("armv7--netbsdelf-eabihf" ), |
3520 | "hexagon-unknown-linux-musl" => Some("hexagon-linux-musl" ), |
3521 | "i586-unknown-linux-musl" => Some("musl" ), |
3522 | "i686-pc-windows-gnu" => Some("i686-w64-mingw32" ), |
3523 | "i686-uwp-windows-gnu" => Some("i686-w64-mingw32" ), |
3524 | "i686-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ |
3525 | "i686-linux-gnu" , |
3526 | "x86_64-linux-gnu" , // transparently support gcc-multilib |
3527 | ]), // explicit None if not found, so caller knows to fall back |
3528 | "i686-unknown-linux-musl" => Some("musl" ), |
3529 | "i686-unknown-netbsd" => Some("i486--netbsdelf" ), |
3530 | "loongarch64-unknown-linux-gnu" => Some("loongarch64-linux-gnu" ), |
3531 | "mips-unknown-linux-gnu" => Some("mips-linux-gnu" ), |
3532 | "mips-unknown-linux-musl" => Some("mips-linux-musl" ), |
3533 | "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu" ), |
3534 | "mipsel-unknown-linux-musl" => Some("mipsel-linux-musl" ), |
3535 | "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64" ), |
3536 | "mips64el-unknown-linux-gnuabi64" => Some("mips64el-linux-gnuabi64" ), |
3537 | "mipsisa32r6-unknown-linux-gnu" => Some("mipsisa32r6-linux-gnu" ), |
3538 | "mipsisa32r6el-unknown-linux-gnu" => Some("mipsisa32r6el-linux-gnu" ), |
3539 | "mipsisa64r6-unknown-linux-gnuabi64" => Some("mipsisa64r6-linux-gnuabi64" ), |
3540 | "mipsisa64r6el-unknown-linux-gnuabi64" => Some("mipsisa64r6el-linux-gnuabi64" ), |
3541 | "powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu" ), |
3542 | "powerpc-unknown-linux-gnuspe" => Some("powerpc-linux-gnuspe" ), |
3543 | "powerpc-unknown-netbsd" => Some("powerpc--netbsd" ), |
3544 | "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu" ), |
3545 | "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu" ), |
3546 | "riscv32i-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
3547 | "riscv32-unknown-elf" , |
3548 | "riscv64-unknown-elf" , |
3549 | "riscv-none-embed" , |
3550 | ]), |
3551 | "riscv32imac-esp-espidf" => Some("riscv32-esp-elf" ), |
3552 | "riscv32imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
3553 | "riscv32-unknown-elf" , |
3554 | "riscv64-unknown-elf" , |
3555 | "riscv-none-embed" , |
3556 | ]), |
3557 | "riscv32imac-unknown-xous-elf" => self.find_working_gnu_prefix(&[ |
3558 | "riscv32-unknown-elf" , |
3559 | "riscv64-unknown-elf" , |
3560 | "riscv-none-embed" , |
3561 | ]), |
3562 | "riscv32imc-esp-espidf" => Some("riscv32-esp-elf" ), |
3563 | "riscv32imc-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
3564 | "riscv32-unknown-elf" , |
3565 | "riscv64-unknown-elf" , |
3566 | "riscv-none-embed" , |
3567 | ]), |
3568 | "riscv64gc-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
3569 | "riscv64-unknown-elf" , |
3570 | "riscv32-unknown-elf" , |
3571 | "riscv-none-embed" , |
3572 | ]), |
3573 | "riscv64imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ |
3574 | "riscv64-unknown-elf" , |
3575 | "riscv32-unknown-elf" , |
3576 | "riscv-none-embed" , |
3577 | ]), |
3578 | "riscv64gc-unknown-linux-gnu" => Some("riscv64-linux-gnu" ), |
3579 | "riscv32gc-unknown-linux-gnu" => Some("riscv32-linux-gnu" ), |
3580 | "riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl" ), |
3581 | "riscv32gc-unknown-linux-musl" => Some("riscv32-linux-musl" ), |
3582 | "riscv64gc-unknown-netbsd" => Some("riscv64--netbsd" ), |
3583 | "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu" ), |
3584 | "sparc-unknown-linux-gnu" => Some("sparc-linux-gnu" ), |
3585 | "sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu" ), |
3586 | "sparc64-unknown-netbsd" => Some("sparc64--netbsd" ), |
3587 | "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris" ), |
3588 | "armv7a-none-eabi" => Some("arm-none-eabi" ), |
3589 | "armv7a-none-eabihf" => Some("arm-none-eabi" ), |
3590 | "armebv7r-none-eabi" => Some("arm-none-eabi" ), |
3591 | "armebv7r-none-eabihf" => Some("arm-none-eabi" ), |
3592 | "armv7r-none-eabi" => Some("arm-none-eabi" ), |
3593 | "armv7r-none-eabihf" => Some("arm-none-eabi" ), |
3594 | "armv8r-none-eabihf" => Some("arm-none-eabi" ), |
3595 | "thumbv6m-none-eabi" => Some("arm-none-eabi" ), |
3596 | "thumbv7em-none-eabi" => Some("arm-none-eabi" ), |
3597 | "thumbv7em-none-eabihf" => Some("arm-none-eabi" ), |
3598 | "thumbv7m-none-eabi" => Some("arm-none-eabi" ), |
3599 | "thumbv8m.base-none-eabi" => Some("arm-none-eabi" ), |
3600 | "thumbv8m.main-none-eabi" => Some("arm-none-eabi" ), |
3601 | "thumbv8m.main-none-eabihf" => Some("arm-none-eabi" ), |
3602 | "x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32" ), |
3603 | "x86_64-pc-windows-gnullvm" => Some("x86_64-w64-mingw32" ), |
3604 | "x86_64-uwp-windows-gnu" => Some("x86_64-w64-mingw32" ), |
3605 | "x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd" ), |
3606 | "x86_64-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ |
3607 | "x86_64-linux-gnu" , // rustfmt wrap |
3608 | ]), // explicit None if not found, so caller knows to fall back |
3609 | "x86_64-unknown-linux-musl" => Some("musl" ), |
3610 | "x86_64-unknown-netbsd" => Some("x86_64--netbsd" ), |
3611 | _ => None, |
3612 | } |
3613 | .map(Cow::Borrowed) |
3614 | }) |
3615 | } |
3616 | |
3617 | /// Some platforms have multiple, compatible, canonical prefixes. Look through |
3618 | /// each possible prefix for a compiler that exists and return it. The prefixes |
3619 | /// should be ordered from most-likely to least-likely. |
3620 | fn find_working_gnu_prefix(&self, prefixes: &[&'static str]) -> Option<&'static str> { |
3621 | let suffix = if self.cpp { "-g++" } else { "-gcc" }; |
3622 | let extension = std::env::consts::EXE_SUFFIX; |
3623 | |
3624 | // Loop through PATH entries searching for each toolchain. This ensures that we |
3625 | // are more likely to discover the toolchain early on, because chances are good |
3626 | // that the desired toolchain is in one of the higher-priority paths. |
3627 | self.getenv("PATH" ) |
3628 | .as_ref() |
3629 | .and_then(|path_entries| { |
3630 | env::split_paths(path_entries).find_map(|path_entry| { |
3631 | for prefix in prefixes { |
3632 | let target_compiler = format!(" {}{}{}" , prefix, suffix, extension); |
3633 | if path_entry.join(&target_compiler).exists() { |
3634 | return Some(prefix); |
3635 | } |
3636 | } |
3637 | None |
3638 | }) |
3639 | }) |
3640 | .copied() |
3641 | // If no toolchain was found, provide the first toolchain that was passed in. |
3642 | // This toolchain has been shown not to exist, however it will appear in the |
3643 | // error that is shown to the user which should make it easier to search for |
3644 | // where it should be obtained. |
3645 | .or_else(|| prefixes.first().copied()) |
3646 | } |
3647 | |
3648 | fn get_target(&self) -> Result<Cow<'_, str>, Error> { |
3649 | match &self.target { |
3650 | Some(t) => Ok(Cow::Borrowed(t)), |
3651 | None => self.getenv_unwrap_str("TARGET" ).map(Cow::Owned), |
3652 | } |
3653 | } |
3654 | |
3655 | fn get_is_cross_compile(&self) -> Result<bool, Error> { |
3656 | let target = self.get_target()?; |
3657 | let host: Cow<'_, str> = match &self.host { |
3658 | Some(h) => Cow::Borrowed(h), |
3659 | None => Cow::Owned(self.getenv_unwrap_str("HOST" )?), |
3660 | }; |
3661 | Ok(host != target) |
3662 | } |
3663 | |
3664 | fn get_opt_level(&self) -> Result<Cow<'_, str>, Error> { |
3665 | match &self.opt_level { |
3666 | Some(ol) => Ok(Cow::Borrowed(ol)), |
3667 | None => self.getenv_unwrap_str("OPT_LEVEL" ).map(Cow::Owned), |
3668 | } |
3669 | } |
3670 | |
3671 | fn get_debug(&self) -> bool { |
3672 | self.debug.unwrap_or_else(|| self.getenv_boolean("DEBUG" )) |
3673 | } |
3674 | |
3675 | fn get_shell_escaped_flags(&self) -> bool { |
3676 | self.shell_escaped_flags |
3677 | .unwrap_or_else(|| self.getenv_boolean("CC_SHELL_ESCAPED_FLAGS" )) |
3678 | } |
3679 | |
3680 | fn get_dwarf_version(&self) -> Option<u32> { |
3681 | // Tentatively matches the DWARF version defaults as of rustc 1.62. |
3682 | let target = self.get_target().ok()?; |
3683 | if target.contains("android" ) |
3684 | || target.contains("apple" ) |
3685 | || target.contains("dragonfly" ) |
3686 | || target.contains("freebsd" ) |
3687 | || target.contains("netbsd" ) |
3688 | || target.contains("openbsd" ) |
3689 | || target.contains("windows-gnu" ) |
3690 | { |
3691 | Some(2) |
3692 | } else if target.contains("linux" ) { |
3693 | Some(4) |
3694 | } else { |
3695 | None |
3696 | } |
3697 | } |
3698 | |
3699 | fn get_force_frame_pointer(&self) -> bool { |
3700 | self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) |
3701 | } |
3702 | |
3703 | fn get_out_dir(&self) -> Result<Cow<'_, Path>, Error> { |
3704 | match &self.out_dir { |
3705 | Some(p) => Ok(Cow::Borrowed(&**p)), |
3706 | None => self |
3707 | .getenv("OUT_DIR" ) |
3708 | .as_deref() |
3709 | .map(PathBuf::from) |
3710 | .map(Cow::Owned) |
3711 | .ok_or_else(|| { |
3712 | Error::new( |
3713 | ErrorKind::EnvVarNotFound, |
3714 | "Environment variable OUT_DIR not defined." , |
3715 | ) |
3716 | }), |
3717 | } |
3718 | } |
3719 | |
3720 | #[allow (clippy::disallowed_methods)] |
3721 | fn getenv(&self, v: &str) -> Option<Arc<OsStr>> { |
3722 | // Returns true for environment variables cargo sets for build scripts: |
3723 | // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts |
3724 | // |
3725 | // This handles more of the vars than we actually use (it tries to check |
3726 | // complete-ish set), just to avoid needing maintenance if/when new |
3727 | // calls to `getenv`/`getenv_unwrap` are added. |
3728 | fn provided_by_cargo(envvar: &str) -> bool { |
3729 | match envvar { |
3730 | v if v.starts_with("CARGO" ) || v.starts_with("RUSTC" ) => true, |
3731 | "HOST" | "TARGET" | "RUSTDOC" | "OUT_DIR" | "OPT_LEVEL" | "DEBUG" | "PROFILE" |
3732 | | "NUM_JOBS" | "RUSTFLAGS" => true, |
3733 | _ => false, |
3734 | } |
3735 | } |
3736 | if let Some(val) = self.env_cache.read().unwrap().get(v).cloned() { |
3737 | return val; |
3738 | } |
3739 | // Excluding `PATH` prevents spurious rebuilds on Windows, see |
3740 | // <https://github.com/rust-lang/cc-rs/pull/1215> for details. |
3741 | if self.emit_rerun_if_env_changed && !provided_by_cargo(v) && v != "PATH" { |
3742 | self.cargo_output |
3743 | .print_metadata(&format_args!("cargo:rerun-if-env-changed= {}" , v)); |
3744 | } |
3745 | let r = env::var_os(v).map(Arc::from); |
3746 | self.cargo_output.print_metadata(&format_args!( |
3747 | " {} = {}" , |
3748 | v, |
3749 | OptionOsStrDisplay(r.as_deref()) |
3750 | )); |
3751 | self.env_cache.write().unwrap().insert(v.into(), r.clone()); |
3752 | r |
3753 | } |
3754 | |
3755 | /// get boolean flag that is either true or false |
3756 | fn getenv_boolean(&self, v: &str) -> bool { |
3757 | match self.getenv(v) { |
3758 | Some(s) => &*s != "0" && &*s != "false" && !s.is_empty(), |
3759 | None => false, |
3760 | } |
3761 | } |
3762 | |
3763 | fn getenv_unwrap(&self, v: &str) -> Result<Arc<OsStr>, Error> { |
3764 | match self.getenv(v) { |
3765 | Some(s) => Ok(s), |
3766 | None => Err(Error::new( |
3767 | ErrorKind::EnvVarNotFound, |
3768 | format!("Environment variable {} not defined." , v), |
3769 | )), |
3770 | } |
3771 | } |
3772 | |
3773 | fn getenv_unwrap_str(&self, v: &str) -> Result<String, Error> { |
3774 | let env = self.getenv_unwrap(v)?; |
3775 | env.to_str().map(String::from).ok_or_else(|| { |
3776 | Error::new( |
3777 | ErrorKind::EnvVarNotFound, |
3778 | format!("Environment variable {} is not valid utf-8." , v), |
3779 | ) |
3780 | }) |
3781 | } |
3782 | |
3783 | fn getenv_with_target_prefixes(&self, var_base: &str) -> Result<Arc<OsStr>, Error> { |
3784 | let target = self.get_target()?; |
3785 | let kind = if self.get_is_cross_compile()? { |
3786 | "TARGET" |
3787 | } else { |
3788 | "HOST" |
3789 | }; |
3790 | let target_u = target.replace('-' , "_" ); |
3791 | let res = self |
3792 | .getenv(&format!(" {}_ {}" , var_base, target)) |
3793 | .or_else(|| self.getenv(&format!(" {}_ {}" , var_base, target_u))) |
3794 | .or_else(|| self.getenv(&format!(" {}_ {}" , kind, var_base))) |
3795 | .or_else(|| self.getenv(var_base)); |
3796 | |
3797 | match res { |
3798 | Some(res) => Ok(res), |
3799 | None => Err(Error::new( |
3800 | ErrorKind::EnvVarNotFound, |
3801 | format!("Could not find environment variable {}." , var_base), |
3802 | )), |
3803 | } |
3804 | } |
3805 | |
3806 | fn envflags(&self, name: &str) -> Result<Vec<String>, Error> { |
3807 | let env_os = self.getenv_with_target_prefixes(name)?; |
3808 | let env = env_os.to_string_lossy(); |
3809 | |
3810 | if self.get_shell_escaped_flags() { |
3811 | Ok(Shlex::new(&env).collect()) |
3812 | } else { |
3813 | Ok(env |
3814 | .split_ascii_whitespace() |
3815 | .map(ToString::to_string) |
3816 | .collect()) |
3817 | } |
3818 | } |
3819 | |
3820 | fn fix_env_for_apple_os(&self, cmd: &mut Command) -> Result<(), Error> { |
3821 | let target = self.get_target()?; |
3822 | if cfg!(target_os = "macos" ) && target.contains("apple-darwin" ) { |
3823 | // Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at |
3824 | // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", |
3825 | // although this is apparently ignored when using the linker at "/usr/bin/ld". |
3826 | cmd.env_remove("IPHONEOS_DEPLOYMENT_TARGET" ); |
3827 | } |
3828 | Ok(()) |
3829 | } |
3830 | |
3831 | fn apple_sdk_root_inner(&self, sdk: &str) -> Result<Arc<OsStr>, Error> { |
3832 | // Code copied from rustc's compiler/rustc_codegen_ssa/src/back/link.rs. |
3833 | if let Some(sdkroot) = self.getenv("SDKROOT" ) { |
3834 | let p = Path::new(&sdkroot); |
3835 | let does_sdkroot_contain = |strings: &[&str]| { |
3836 | let sdkroot_str = p.to_string_lossy(); |
3837 | strings.iter().any(|s| sdkroot_str.contains(s)) |
3838 | }; |
3839 | match sdk { |
3840 | // Ignore `SDKROOT` if it's clearly set for the wrong platform. |
3841 | "appletvos" |
3842 | if does_sdkroot_contain(&["TVSimulator.platform" , "MacOSX.platform" ]) => {} |
3843 | "appletvsimulator" |
3844 | if does_sdkroot_contain(&["TVOS.platform" , "MacOSX.platform" ]) => {} |
3845 | "iphoneos" |
3846 | if does_sdkroot_contain(&["iPhoneSimulator.platform" , "MacOSX.platform" ]) => {} |
3847 | "iphonesimulator" |
3848 | if does_sdkroot_contain(&["iPhoneOS.platform" , "MacOSX.platform" ]) => {} |
3849 | "macosx10.15" |
3850 | if does_sdkroot_contain(&["iPhoneOS.platform" , "iPhoneSimulator.platform" ]) => { |
3851 | } |
3852 | "watchos" |
3853 | if does_sdkroot_contain(&["WatchSimulator.platform" , "MacOSX.platform" ]) => {} |
3854 | "watchsimulator" |
3855 | if does_sdkroot_contain(&["WatchOS.platform" , "MacOSX.platform" ]) => {} |
3856 | "xros" if does_sdkroot_contain(&["XRSimulator.platform" , "MacOSX.platform" ]) => {} |
3857 | "xrsimulator" if does_sdkroot_contain(&["XROS.platform" , "MacOSX.platform" ]) => {} |
3858 | // Ignore `SDKROOT` if it's not a valid path. |
3859 | _ if !p.is_absolute() || p == Path::new("/" ) || !p.exists() => {} |
3860 | _ => return Ok(sdkroot), |
3861 | } |
3862 | } |
3863 | |
3864 | let sdk_path = run_output( |
3865 | self.cmd("xcrun" ) |
3866 | .arg("--show-sdk-path" ) |
3867 | .arg("--sdk" ) |
3868 | .arg(sdk), |
3869 | "xcrun" , |
3870 | &self.cargo_output, |
3871 | )?; |
3872 | |
3873 | let sdk_path = match String::from_utf8(sdk_path) { |
3874 | Ok(p) => p, |
3875 | Err(_) => { |
3876 | return Err(Error::new( |
3877 | ErrorKind::IOError, |
3878 | "Unable to determine Apple SDK path." , |
3879 | )); |
3880 | } |
3881 | }; |
3882 | Ok(Arc::from(OsStr::new(sdk_path.trim()))) |
3883 | } |
3884 | |
3885 | fn apple_sdk_root(&self, sdk: &str) -> Result<Arc<OsStr>, Error> { |
3886 | if let Some(ret) = self |
3887 | .apple_sdk_root_cache |
3888 | .read() |
3889 | .expect("apple_sdk_root_cache lock failed" ) |
3890 | .get(sdk) |
3891 | .cloned() |
3892 | { |
3893 | return Ok(ret); |
3894 | } |
3895 | let sdk_path = self.apple_sdk_root_inner(sdk)?; |
3896 | self.apple_sdk_root_cache |
3897 | .write() |
3898 | .expect("apple_sdk_root_cache lock failed" ) |
3899 | .insert(sdk.into(), sdk_path.clone()); |
3900 | Ok(sdk_path) |
3901 | } |
3902 | |
3903 | fn apple_deployment_version(&self, os: AppleOs, arch_str: Option<&str>, sdk: &str) -> Arc<str> { |
3904 | if let Some(ret) = self |
3905 | .apple_versions_cache |
3906 | .read() |
3907 | .expect("apple_versions_cache lock failed" ) |
3908 | .get(sdk) |
3909 | .cloned() |
3910 | { |
3911 | return ret; |
3912 | } |
3913 | |
3914 | let default_deployment_from_sdk = || -> Option<Arc<str>> { |
3915 | let version = run_output( |
3916 | self.cmd("xcrun" ) |
3917 | .arg("--show-sdk-version" ) |
3918 | .arg("--sdk" ) |
3919 | .arg(sdk), |
3920 | "xcrun" , |
3921 | &self.cargo_output, |
3922 | ) |
3923 | .ok()?; |
3924 | |
3925 | Some(Arc::from(std::str::from_utf8(&version).ok()?.trim())) |
3926 | }; |
3927 | |
3928 | let deployment_from_env = |name: &str| -> Option<Arc<str>> { |
3929 | // note that self.env isn't hit in production codepaths, its mostly just for tests which don't |
3930 | // set the real env |
3931 | self.env |
3932 | .iter() |
3933 | .find(|(k, _)| &**k == OsStr::new(name)) |
3934 | .map(|(_, v)| v) |
3935 | .cloned() |
3936 | .or_else(|| self.getenv(name))? |
3937 | .to_str() |
3938 | .map(Arc::from) |
3939 | }; |
3940 | |
3941 | // Determines if the acquired deployment target is too low to support modern C++ on some Apple platform. |
3942 | // |
3943 | // A long time ago they used libstdc++, but since macOS 10.9 and iOS 7 libc++ has been the library the SDKs provide to link against. |
3944 | // If a `cc`` config wants to use C++, we round up to these versions as the baseline. |
3945 | let maybe_cpp_version_baseline = |deployment_target_ver: Arc<str>| -> Option<Arc<str>> { |
3946 | if !self.cpp { |
3947 | return Some(deployment_target_ver); |
3948 | } |
3949 | |
3950 | let mut deployment_target = deployment_target_ver |
3951 | .split('.' ) |
3952 | .map(|v| v.parse::<u32>().expect("integer version" )); |
3953 | |
3954 | match os { |
3955 | AppleOs::MacOs => { |
3956 | let major = deployment_target.next().unwrap_or(0); |
3957 | let minor = deployment_target.next().unwrap_or(0); |
3958 | |
3959 | // If below 10.9, we ignore it and let the SDK's target definitions handle it. |
3960 | if major == 10 && minor < 9 { |
3961 | self.cargo_output.print_warning(&format_args!( |
3962 | "macOS deployment target ( {}) too low, it will be increased" , |
3963 | deployment_target_ver |
3964 | )); |
3965 | return None; |
3966 | } |
3967 | } |
3968 | AppleOs::Ios => { |
3969 | let major = deployment_target.next().unwrap_or(0); |
3970 | |
3971 | // If below 10.7, we ignore it and let the SDK's target definitions handle it. |
3972 | if major < 7 { |
3973 | self.cargo_output.print_warning(&format_args!( |
3974 | "iOS deployment target ( {}) too low, it will be increased" , |
3975 | deployment_target_ver |
3976 | )); |
3977 | return None; |
3978 | } |
3979 | } |
3980 | // watchOS, tvOS, visionOS, and others are all new enough that libc++ is their baseline. |
3981 | _ => {} |
3982 | } |
3983 | |
3984 | // If the deployment target met or exceeded the C++ baseline |
3985 | Some(deployment_target_ver) |
3986 | }; |
3987 | |
3988 | // The hardcoded minimums here are subject to change in a future compiler release, |
3989 | // and only exist as last resort fallbacks. Don't consider them stable. |
3990 | // `cc` doesn't use rustc's `--print deployment-target`` because the compiler's defaults |
3991 | // don't align well with Apple's SDKs and other third-party libraries that require ~generally~ higher |
3992 | // deployment targets. rustc isn't interested in those by default though so its fine to be different here. |
3993 | // |
3994 | // If no explicit target is passed, `cc` defaults to the current Xcode SDK's `DefaultDeploymentTarget` for better |
3995 | // compatibility. This is also the crate's historical behavior and what has become a relied-on value. |
3996 | // |
3997 | // The ordering of env -> XCode SDK -> old rustc defaults is intentional for performance when using |
3998 | // an explicit target. |
3999 | let version: Arc<str> = match os { |
4000 | AppleOs::MacOs => deployment_from_env("MACOSX_DEPLOYMENT_TARGET" ) |
4001 | .and_then(maybe_cpp_version_baseline) |
4002 | .or_else(default_deployment_from_sdk) |
4003 | .unwrap_or_else(|| { |
4004 | if arch_str == Some("aarch64" ) { |
4005 | "11.0" .into() |
4006 | } else { |
4007 | let default: Arc<str> = Arc::from("10.7" ); |
4008 | maybe_cpp_version_baseline(default.clone()).unwrap_or(default) |
4009 | } |
4010 | }), |
4011 | |
4012 | AppleOs::Ios => deployment_from_env("IPHONEOS_DEPLOYMENT_TARGET" ) |
4013 | .and_then(maybe_cpp_version_baseline) |
4014 | .or_else(default_deployment_from_sdk) |
4015 | .unwrap_or_else(|| "7.0" .into()), |
4016 | |
4017 | AppleOs::WatchOs => deployment_from_env("WATCHOS_DEPLOYMENT_TARGET" ) |
4018 | .or_else(default_deployment_from_sdk) |
4019 | .unwrap_or_else(|| "5.0" .into()), |
4020 | |
4021 | AppleOs::TvOs => deployment_from_env("TVOS_DEPLOYMENT_TARGET" ) |
4022 | .or_else(default_deployment_from_sdk) |
4023 | .unwrap_or_else(|| "9.0" .into()), |
4024 | |
4025 | AppleOs::VisionOS => deployment_from_env("XROS_DEPLOYMENT_TARGET" ) |
4026 | .or_else(default_deployment_from_sdk) |
4027 | .unwrap_or_else(|| "1.0" .into()), |
4028 | }; |
4029 | |
4030 | self.apple_versions_cache |
4031 | .write() |
4032 | .expect("apple_versions_cache lock failed" ) |
4033 | .insert(sdk.into(), version.clone()); |
4034 | |
4035 | version |
4036 | } |
4037 | |
4038 | fn wasi_sysroot(&self) -> Result<Arc<OsStr>, Error> { |
4039 | if let Some(wasi_sysroot_path) = self.getenv("WASI_SYSROOT" ) { |
4040 | Ok(wasi_sysroot_path) |
4041 | } else { |
4042 | Err(Error::new( |
4043 | ErrorKind::EnvVarNotFound, |
4044 | "Environment variable WASI_SYSROOT not defined. Download sysroot from GitHub & setup environment variable WASI_SYSROOT targeting the folder." , |
4045 | )) |
4046 | } |
4047 | } |
4048 | fn is_wasi_target(target: &str) -> bool { |
4049 | const TARGETS: [&str; 7] = [ |
4050 | "wasm32-wasi" , |
4051 | "wasm32-wasip1" , |
4052 | "wasm32-wasip1-threads" , |
4053 | "wasm32-wasip2" , |
4054 | "wasm32-wasi-threads" , |
4055 | "wasm32-unknown-wasi" , |
4056 | "wasm32-unknown-unknown" , |
4057 | ]; |
4058 | TARGETS.contains(&target) |
4059 | } |
4060 | |
4061 | fn cuda_file_count(&self) -> usize { |
4062 | self.files |
4063 | .iter() |
4064 | .filter(|file| file.extension() == Some(OsStr::new("cu" ))) |
4065 | .count() |
4066 | } |
4067 | |
4068 | fn which(&self, tool: &Path, path_entries: Option<&OsStr>) -> Option<PathBuf> { |
4069 | fn check_exe(mut exe: PathBuf) -> Option<PathBuf> { |
4070 | let exe_ext = std::env::consts::EXE_EXTENSION; |
4071 | let check = |
4072 | exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists()); |
4073 | check.then_some(exe) |
4074 | } |
4075 | |
4076 | // Loop through PATH entries searching for the |tool|. |
4077 | let find_exe_in_path = |path_entries: &OsStr| -> Option<PathBuf> { |
4078 | env::split_paths(path_entries).find_map(|path_entry| check_exe(path_entry.join(tool))) |
4079 | }; |
4080 | |
4081 | // If |tool| is not just one "word," assume it's an actual path... |
4082 | if tool.components().count() > 1 { |
4083 | check_exe(PathBuf::from(tool)) |
4084 | } else { |
4085 | path_entries |
4086 | .and_then(find_exe_in_path) |
4087 | .or_else(|| find_exe_in_path(&self.getenv("PATH" )?)) |
4088 | } |
4089 | } |
4090 | |
4091 | /// search for |prog| on 'programs' path in '|cc| -print-search-dirs' output |
4092 | fn search_programs( |
4093 | &self, |
4094 | cc: &mut Command, |
4095 | prog: &Path, |
4096 | cargo_output: &CargoOutput, |
4097 | ) -> Option<PathBuf> { |
4098 | let search_dirs = run_output( |
4099 | cc.arg("-print-search-dirs" ), |
4100 | "cc" , |
4101 | // this doesn't concern the compilation so we always want to show warnings. |
4102 | cargo_output, |
4103 | ) |
4104 | .ok()?; |
4105 | // clang driver appears to be forcing UTF-8 output even on Windows, |
4106 | // hence from_utf8 is assumed to be usable in all cases. |
4107 | let search_dirs = std::str::from_utf8(&search_dirs).ok()?; |
4108 | for dirs in search_dirs.split([' \r' , ' \n' ]) { |
4109 | if let Some(path) = dirs.strip_prefix("programs: =" ) { |
4110 | return self.which(prog, Some(OsStr::new(path))); |
4111 | } |
4112 | } |
4113 | None |
4114 | } |
4115 | |
4116 | fn windows_registry_find(&self, target: &str, tool: &str) -> Option<Command> { |
4117 | self.windows_registry_find_tool(target, tool) |
4118 | .map(|c| c.to_command()) |
4119 | } |
4120 | |
4121 | fn windows_registry_find_tool(&self, target: &str, tool: &str) -> Option<Tool> { |
4122 | struct BuildEnvGetter<'s>(&'s Build); |
4123 | |
4124 | impl windows_registry::EnvGetter for BuildEnvGetter<'_> { |
4125 | fn get_env(&self, name: &str) -> Option<windows_registry::Env> { |
4126 | self.0.getenv(name).map(windows_registry::Env::Arced) |
4127 | } |
4128 | } |
4129 | |
4130 | windows_registry::find_tool_inner(target, tool, &BuildEnvGetter(self)) |
4131 | } |
4132 | } |
4133 | |
4134 | impl Default for Build { |
4135 | fn default() -> Build { |
4136 | Build::new() |
4137 | } |
4138 | } |
4139 | |
4140 | fn fail(s: &str) -> ! { |
4141 | eprintln!(" \n\nerror occurred: {}\n\n" , s); |
4142 | std::process::exit(code:1); |
4143 | } |
4144 | |
4145 | #[derive (Clone, Copy, PartialEq)] |
4146 | enum AppleOs { |
4147 | MacOs, |
4148 | Ios, |
4149 | WatchOs, |
4150 | TvOs, |
4151 | VisionOS, |
4152 | } |
4153 | |
4154 | impl std::fmt::Debug for AppleOs { |
4155 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
4156 | match self { |
4157 | AppleOs::MacOs => f.write_str(data:"macOS" ), |
4158 | AppleOs::Ios => f.write_str(data:"iOS" ), |
4159 | AppleOs::WatchOs => f.write_str(data:"WatchOS" ), |
4160 | AppleOs::TvOs => f.write_str(data:"AppleTVOS" ), |
4161 | AppleOs::VisionOS => f.write_str(data:"visionOS" ), |
4162 | } |
4163 | } |
4164 | } |
4165 | |
4166 | struct AppleSdkTargetParts { |
4167 | sdk_prefix: &'static str, |
4168 | sim_prefix: &'static str, |
4169 | sdk: Cow<'static, str>, |
4170 | } |
4171 | |
4172 | fn apple_os_sdk_parts(os: AppleOs, arch: &AppleArchSpec) -> AppleSdkTargetParts { |
4173 | let (sdk_prefix: &'static str, sim_prefix: &'static str) = match os { |
4174 | AppleOs::MacOs => ("macosx" , "" ), |
4175 | AppleOs::Ios => ("iphone" , "ios-" ), |
4176 | AppleOs::WatchOs => ("watch" , "watch" ), |
4177 | AppleOs::TvOs => ("appletv" , "appletv" ), |
4178 | AppleOs::VisionOS => ("xr" , "xr" ), |
4179 | }; |
4180 | let sdk: Cow<'_, str> = match arch { |
4181 | AppleArchSpec::Device(_) if os == AppleOs::MacOs => Cow::Borrowed("macosx" ), |
4182 | AppleArchSpec::Device(_) => format!(" {}os" , sdk_prefix).into(), |
4183 | AppleArchSpec::Simulator(_) => format!(" {}simulator" , sdk_prefix).into(), |
4184 | AppleArchSpec::Catalyst(_) => Cow::Borrowed("macosx" ), |
4185 | }; |
4186 | |
4187 | AppleSdkTargetParts { |
4188 | sdk_prefix, |
4189 | sim_prefix, |
4190 | sdk, |
4191 | } |
4192 | } |
4193 | |
4194 | #[allow (dead_code)] |
4195 | enum AppleArchSpec { |
4196 | Device(&'static str), |
4197 | Simulator(&'static str), |
4198 | #[allow (dead_code)] |
4199 | Catalyst(&'static str), |
4200 | } |
4201 | |
4202 | // Use by default minimum available API level |
4203 | // See note about naming here |
4204 | // https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/docs/BuildSystemMaintainers.md#Clang |
4205 | static NEW_STANDALONE_ANDROID_COMPILERS: [&str; 4] = [ |
4206 | "aarch64-linux-android21-clang" , |
4207 | "armv7a-linux-androideabi16-clang" , |
4208 | "i686-linux-android16-clang" , |
4209 | "x86_64-linux-android21-clang" , |
4210 | ]; |
4211 | |
4212 | // New "standalone" C/C++ cross-compiler executables from recent Android NDK |
4213 | // are just shell scripts that call main clang binary (from Android NDK) with |
4214 | // proper `--target` argument. |
4215 | // |
4216 | // For example, armv7a-linux-androideabi16-clang passes |
4217 | // `--target=armv7a-linux-androideabi16` to clang. |
4218 | // So to construct proper command line check if |
4219 | // `--target` argument would be passed or not to clang |
4220 | fn android_clang_compiler_uses_target_arg_internally(clang_path: &Path) -> bool { |
4221 | if let Some(filename: &OsStr) = clang_path.file_name() { |
4222 | if let Some(filename_str: &str) = filename.to_str() { |
4223 | if let Some(idx: usize) = filename_str.rfind('-' ) { |
4224 | return filename_str.split_at(mid:idx).0.contains("android" ); |
4225 | } |
4226 | } |
4227 | } |
4228 | false |
4229 | } |
4230 | |
4231 | fn autodetect_android_compiler(target: &str, gnu: &str, clang: &str) -> String { |
4232 | let new_clang_key = match target { |
4233 | "aarch64-linux-android" => Some("aarch64" ), |
4234 | "armv7-linux-androideabi" => Some("armv7a" ), |
4235 | "i686-linux-android" => Some("i686" ), |
4236 | "x86_64-linux-android" => Some("x86_64" ), |
4237 | _ => None, |
4238 | }; |
4239 | |
4240 | let new_clang = new_clang_key |
4241 | .map(|key| { |
4242 | NEW_STANDALONE_ANDROID_COMPILERS |
4243 | .iter() |
4244 | .find(|x| x.starts_with(key)) |
4245 | }) |
4246 | .unwrap_or(None); |
4247 | |
4248 | if let Some(new_clang) = new_clang { |
4249 | if Command::new(new_clang).output().is_ok() { |
4250 | return (*new_clang).into(); |
4251 | } |
4252 | } |
4253 | |
4254 | let target = target |
4255 | .replace("armv7neon" , "arm" ) |
4256 | .replace("armv7" , "arm" ) |
4257 | .replace("thumbv7neon" , "arm" ) |
4258 | .replace("thumbv7" , "arm" ); |
4259 | let gnu_compiler = format!(" {}- {}" , target, gnu); |
4260 | let clang_compiler = format!(" {}- {}" , target, clang); |
4261 | |
4262 | // On Windows, the Android clang compiler is provided as a `.cmd` file instead |
4263 | // of a `.exe` file. `std::process::Command` won't run `.cmd` files unless the |
4264 | // `.cmd` is explicitly appended to the command name, so we do that here. |
4265 | let clang_compiler_cmd = format!(" {}- {}.cmd" , target, clang); |
4266 | |
4267 | // Check if gnu compiler is present |
4268 | // if not, use clang |
4269 | if Command::new(&gnu_compiler).output().is_ok() { |
4270 | gnu_compiler |
4271 | } else if cfg!(windows) && Command::new(&clang_compiler_cmd).output().is_ok() { |
4272 | clang_compiler_cmd |
4273 | } else { |
4274 | clang_compiler |
4275 | } |
4276 | } |
4277 | |
4278 | // Rust and clang/cc don't agree on how to name the target. |
4279 | fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option<&'static str> { |
4280 | if target.contains("x86_64h" ) { |
4281 | Some("x86_64h" ) |
4282 | } else if target.contains("x86_64" ) { |
4283 | Some("x86_64" ) |
4284 | } else if target.contains("arm64e" ) { |
4285 | Some("arm64e" ) |
4286 | } else if target.contains("aarch64" ) { |
4287 | Some("arm64" ) |
4288 | } else if target.contains("i686" ) { |
4289 | Some("i386" ) |
4290 | } else if target.contains("powerpc" ) { |
4291 | Some("ppc" ) |
4292 | } else if target.contains("powerpc64" ) { |
4293 | Some("ppc64" ) |
4294 | } else { |
4295 | None |
4296 | } |
4297 | } |
4298 | |
4299 | #[derive (Clone, Copy, PartialEq)] |
4300 | enum AsmFileExt { |
4301 | /// `.asm` files. On MSVC targets, we assume these should be passed to MASM |
4302 | /// (`ml{,64}.exe`). |
4303 | DotAsm, |
4304 | /// `.s` or `.S` files, which do not have the special handling on MSVC targets. |
4305 | DotS, |
4306 | } |
4307 | |
4308 | impl AsmFileExt { |
4309 | fn from_path(file: &Path) -> Option<Self> { |
4310 | if let Some(ext: &OsStr) = file.extension() { |
4311 | if let Some(ext: &str) = ext.to_str() { |
4312 | let ext: String = ext.to_lowercase(); |
4313 | match &*ext { |
4314 | "asm" => return Some(AsmFileExt::DotAsm), |
4315 | "s" => return Some(AsmFileExt::DotS), |
4316 | _ => return None, |
4317 | } |
4318 | } |
4319 | } |
4320 | None |
4321 | } |
4322 | } |
4323 | |
4324 | #[cfg (test)] |
4325 | mod tests { |
4326 | use super::*; |
4327 | |
4328 | #[test ] |
4329 | fn test_android_clang_compiler_uses_target_arg_internally() { |
4330 | for version in 16..21 { |
4331 | assert!(android_clang_compiler_uses_target_arg_internally( |
4332 | &PathBuf::from(format!("armv7a-linux-androideabi{}-clang" , version)) |
4333 | )); |
4334 | assert!(android_clang_compiler_uses_target_arg_internally( |
4335 | &PathBuf::from(format!("armv7a-linux-androideabi{}-clang++" , version)) |
4336 | )); |
4337 | } |
4338 | assert!(!android_clang_compiler_uses_target_arg_internally( |
4339 | &PathBuf::from("clang-i686-linux-android" ) |
4340 | )); |
4341 | assert!(!android_clang_compiler_uses_target_arg_internally( |
4342 | &PathBuf::from("clang" ) |
4343 | )); |
4344 | assert!(!android_clang_compiler_uses_target_arg_internally( |
4345 | &PathBuf::from("clang++" ) |
4346 | )); |
4347 | } |
4348 | } |
4349 | |