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