]>
Commit | Line | Data |
---|---|---|
476ff2be SL |
1 | //! A library for build scripts to compile custom C code |
2 | //! | |
3 | //! This library is intended to be used as a `build-dependencies` entry in | |
4 | //! `Cargo.toml`: | |
5 | //! | |
6 | //! ```toml | |
7 | //! [build-dependencies] | |
8 | //! gcc = "0.3" | |
9 | //! ``` | |
10 | //! | |
11 | //! The purpose of this crate is to provide the utility functions necessary to | |
12 | //! compile C code into a static archive which is then linked into a Rust crate. | |
13 | //! The top-level `compile_library` function serves as a convenience and more | |
14 | //! advanced configuration is available through the `Config` builder. | |
15 | //! | |
16 | //! This crate will automatically detect situations such as cross compilation or | |
17 | //! other environment variables set by Cargo and will build code appropriately. | |
18 | //! | |
19 | //! # Examples | |
20 | //! | |
21 | //! Use the default configuration: | |
22 | //! | |
23 | //! ```no_run | |
24 | //! extern crate gcc; | |
25 | //! | |
26 | //! fn main() { | |
27 | //! gcc::compile_library("libfoo.a", &["src/foo.c"]); | |
28 | //! } | |
29 | //! ``` | |
30 | //! | |
31 | //! Use more advanced configuration: | |
32 | //! | |
33 | //! ```no_run | |
34 | //! extern crate gcc; | |
35 | //! | |
36 | //! fn main() { | |
37 | //! gcc::Config::new() | |
38 | //! .file("src/foo.c") | |
39 | //! .define("FOO", Some("bar")) | |
40 | //! .include("src") | |
41 | //! .compile("libfoo.a"); | |
42 | //! } | |
43 | //! ``` | |
44 | ||
8bb4bdeb | 45 | #![doc(html_root_url = "https://docs.rs/gcc/0.3")] |
476ff2be SL |
46 | #![cfg_attr(test, deny(warnings))] |
47 | #![deny(missing_docs)] | |
48 | ||
49 | #[cfg(feature = "parallel")] | |
50 | extern crate rayon; | |
51 | ||
52 | use std::env; | |
53 | use std::ffi::{OsString, OsStr}; | |
54 | use std::fs; | |
476ff2be | 55 | use std::path::{PathBuf, Path}; |
cc61c64b | 56 | use std::process::{Command, Stdio, Child}; |
8bb4bdeb | 57 | use std::io::{self, BufReader, BufRead, Read, Write}; |
cc61c64b | 58 | use std::thread::{self, JoinHandle}; |
476ff2be | 59 | |
7cac9316 XL |
60 | // These modules are all glue to support reading the MSVC version from |
61 | // the registry and from COM interfaces | |
476ff2be SL |
62 | #[cfg(windows)] |
63 | mod registry; | |
7cac9316 XL |
64 | #[cfg(windows)] |
65 | #[macro_use] | |
66 | mod winapi; | |
67 | #[cfg(windows)] | |
68 | mod com; | |
69 | #[cfg(windows)] | |
70 | mod setup_config; | |
71 | ||
476ff2be SL |
72 | pub mod windows_registry; |
73 | ||
74 | /// Extra configuration to pass to gcc. | |
75 | pub struct Config { | |
76 | include_directories: Vec<PathBuf>, | |
77 | definitions: Vec<(String, Option<String>)>, | |
78 | objects: Vec<PathBuf>, | |
79 | flags: Vec<String>, | |
80 | files: Vec<PathBuf>, | |
81 | cpp: bool, | |
82 | cpp_link_stdlib: Option<Option<String>>, | |
83 | cpp_set_stdlib: Option<String>, | |
84 | target: Option<String>, | |
85 | host: Option<String>, | |
86 | out_dir: Option<PathBuf>, | |
87 | opt_level: Option<String>, | |
88 | debug: Option<bool>, | |
89 | env: Vec<(OsString, OsString)>, | |
90 | compiler: Option<PathBuf>, | |
91 | archiver: Option<PathBuf>, | |
92 | cargo_metadata: bool, | |
93 | pic: Option<bool>, | |
7cac9316 | 94 | static_crt: Option<bool>, |
476ff2be SL |
95 | } |
96 | ||
97 | /// Configuration used to represent an invocation of a C compiler. | |
98 | /// | |
99 | /// This can be used to figure out what compiler is in use, what the arguments | |
100 | /// to it are, and what the environment variables look like for the compiler. | |
101 | /// This can be used to further configure other build systems (e.g. forward | |
102 | /// along CC and/or CFLAGS) or the `to_command` method can be used to run the | |
103 | /// compiler itself. | |
104 | pub struct Tool { | |
105 | path: PathBuf, | |
106 | args: Vec<OsString>, | |
107 | env: Vec<(OsString, OsString)>, | |
8bb4bdeb XL |
108 | family: ToolFamily |
109 | } | |
110 | ||
111 | /// Represents the family of tools this tool belongs to. | |
112 | /// | |
113 | /// Each family of tools differs in how and what arguments they accept. | |
114 | /// | |
115 | /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. | |
116 | #[derive(Copy, Clone, Debug)] | |
117 | enum ToolFamily { | |
118 | /// Tool is GNU Compiler Collection-like. | |
119 | Gnu, | |
120 | /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags | |
121 | /// and its cross-compilation approach is different. | |
122 | Clang, | |
123 | /// Tool is the MSVC cl.exe. | |
124 | Msvc, | |
125 | } | |
126 | ||
127 | impl ToolFamily { | |
128 | /// What the flag to request debug info for this family of tools look like | |
129 | fn debug_flag(&self) -> &'static str { | |
130 | match *self { | |
131 | ToolFamily::Msvc => "/Z7", | |
132 | ToolFamily::Gnu | | |
133 | ToolFamily::Clang => "-g", | |
134 | } | |
135 | } | |
136 | ||
137 | /// What the flag to include directories into header search path looks like | |
138 | fn include_flag(&self) -> &'static str { | |
139 | match *self { | |
140 | ToolFamily::Msvc => "/I", | |
141 | ToolFamily::Gnu | | |
142 | ToolFamily::Clang => "-I", | |
143 | } | |
144 | } | |
145 | ||
146 | /// What the flag to request macro-expanded source output looks like | |
147 | fn expand_flag(&self) -> &'static str { | |
148 | match *self { | |
149 | ToolFamily::Msvc => "/E", | |
150 | ToolFamily::Gnu | | |
151 | ToolFamily::Clang => "-E", | |
152 | } | |
153 | } | |
476ff2be SL |
154 | } |
155 | ||
156 | /// Compile a library from the given set of input C files. | |
157 | /// | |
158 | /// This will simply compile all files into object files and then assemble them | |
159 | /// into the output. This will read the standard environment variables to detect | |
160 | /// cross compilations and such. | |
161 | /// | |
162 | /// This function will also print all metadata on standard output for Cargo. | |
163 | /// | |
164 | /// # Example | |
165 | /// | |
166 | /// ```no_run | |
167 | /// gcc::compile_library("libfoo.a", &["foo.c", "bar.c"]); | |
168 | /// ``` | |
169 | pub fn compile_library(output: &str, files: &[&str]) { | |
170 | let mut c = Config::new(); | |
171 | for f in files.iter() { | |
172 | c.file(*f); | |
173 | } | |
8bb4bdeb | 174 | c.compile(output); |
476ff2be SL |
175 | } |
176 | ||
177 | impl Config { | |
178 | /// Construct a new instance of a blank set of configuration. | |
179 | /// | |
180 | /// This builder is finished with the `compile` function. | |
181 | pub fn new() -> Config { | |
182 | Config { | |
183 | include_directories: Vec::new(), | |
184 | definitions: Vec::new(), | |
185 | objects: Vec::new(), | |
186 | flags: Vec::new(), | |
187 | files: Vec::new(), | |
188 | cpp: false, | |
189 | cpp_link_stdlib: None, | |
190 | cpp_set_stdlib: None, | |
191 | target: None, | |
192 | host: None, | |
193 | out_dir: None, | |
194 | opt_level: None, | |
195 | debug: None, | |
196 | env: Vec::new(), | |
197 | compiler: None, | |
198 | archiver: None, | |
199 | cargo_metadata: true, | |
200 | pic: None, | |
7cac9316 | 201 | static_crt: None, |
476ff2be SL |
202 | } |
203 | } | |
204 | ||
205 | /// Add a directory to the `-I` or include path for headers | |
206 | pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Config { | |
207 | self.include_directories.push(dir.as_ref().to_path_buf()); | |
208 | self | |
209 | } | |
210 | ||
211 | /// Specify a `-D` variable with an optional value. | |
212 | pub fn define(&mut self, var: &str, val: Option<&str>) -> &mut Config { | |
213 | self.definitions.push((var.to_string(), val.map(|s| s.to_string()))); | |
214 | self | |
215 | } | |
216 | ||
217 | /// Add an arbitrary object file to link in | |
218 | pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Config { | |
219 | self.objects.push(obj.as_ref().to_path_buf()); | |
220 | self | |
221 | } | |
222 | ||
223 | /// Add an arbitrary flag to the invocation of the compiler | |
224 | pub fn flag(&mut self, flag: &str) -> &mut Config { | |
225 | self.flags.push(flag.to_string()); | |
226 | self | |
227 | } | |
228 | ||
229 | /// Add a file which will be compiled | |
230 | pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Config { | |
231 | self.files.push(p.as_ref().to_path_buf()); | |
232 | self | |
233 | } | |
234 | ||
235 | /// Set C++ support. | |
236 | /// | |
237 | /// The other `cpp_*` options will only become active if this is set to | |
238 | /// `true`. | |
239 | pub fn cpp(&mut self, cpp: bool) -> &mut Config { | |
240 | self.cpp = cpp; | |
241 | self | |
242 | } | |
243 | ||
244 | /// Set the standard library to link against when compiling with C++ | |
245 | /// support. | |
246 | /// | |
247 | /// The default value of this property depends on the current target: On | |
248 | /// OS X `Some("c++")` is used, when compiling for a Visual Studio based | |
249 | /// target `None` is used and for other targets `Some("stdc++")` is used. | |
250 | /// | |
251 | /// A value of `None` indicates that no automatic linking should happen, | |
252 | /// otherwise cargo will link against the specified library. | |
253 | /// | |
254 | /// The given library name must not contain the `lib` prefix. | |
8bb4bdeb | 255 | pub fn cpp_link_stdlib(&mut self, cpp_link_stdlib: Option<&str>) -> &mut Config { |
476ff2be SL |
256 | self.cpp_link_stdlib = Some(cpp_link_stdlib.map(|s| s.into())); |
257 | self | |
258 | } | |
259 | ||
260 | /// Force the C++ compiler to use the specified standard library. | |
261 | /// | |
262 | /// Setting this option will automatically set `cpp_link_stdlib` to the same | |
263 | /// value. | |
264 | /// | |
265 | /// The default value of this option is always `None`. | |
266 | /// | |
267 | /// This option has no effect when compiling for a Visual Studio based | |
268 | /// target. | |
269 | /// | |
270 | /// This option sets the `-stdlib` flag, which is only supported by some | |
271 | /// compilers (clang, icc) but not by others (gcc). The library will not | |
272 | /// detect which compiler is used, as such it is the responsibility of the | |
273 | /// caller to ensure that this option is only used in conjuction with a | |
274 | /// compiler which supports the `-stdlib` flag. | |
275 | /// | |
276 | /// A value of `None` indicates that no specific C++ standard library should | |
277 | /// be used, otherwise `-stdlib` is added to the compile invocation. | |
278 | /// | |
279 | /// The given library name must not contain the `lib` prefix. | |
8bb4bdeb | 280 | pub fn cpp_set_stdlib(&mut self, cpp_set_stdlib: Option<&str>) -> &mut Config { |
476ff2be SL |
281 | self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into()); |
282 | self.cpp_link_stdlib(cpp_set_stdlib); | |
283 | self | |
284 | } | |
285 | ||
286 | /// Configures the target this configuration will be compiling for. | |
287 | /// | |
288 | /// This option is automatically scraped from the `TARGET` environment | |
289 | /// variable by build scripts, so it's not required to call this function. | |
290 | pub fn target(&mut self, target: &str) -> &mut Config { | |
291 | self.target = Some(target.to_string()); | |
292 | self | |
293 | } | |
294 | ||
295 | /// Configures the host assumed by this configuration. | |
296 | /// | |
297 | /// This option is automatically scraped from the `HOST` environment | |
298 | /// variable by build scripts, so it's not required to call this function. | |
299 | pub fn host(&mut self, host: &str) -> &mut Config { | |
300 | self.host = Some(host.to_string()); | |
301 | self | |
302 | } | |
303 | ||
304 | /// Configures the optimization level of the generated object files. | |
305 | /// | |
306 | /// This option is automatically scraped from the `OPT_LEVEL` environment | |
307 | /// variable by build scripts, so it's not required to call this function. | |
308 | pub fn opt_level(&mut self, opt_level: u32) -> &mut Config { | |
309 | self.opt_level = Some(opt_level.to_string()); | |
310 | self | |
311 | } | |
312 | ||
313 | /// Configures the optimization level of the generated object files. | |
314 | /// | |
315 | /// This option is automatically scraped from the `OPT_LEVEL` environment | |
316 | /// variable by build scripts, so it's not required to call this function. | |
317 | pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Config { | |
318 | self.opt_level = Some(opt_level.to_string()); | |
319 | self | |
320 | } | |
321 | ||
322 | /// Configures whether the compiler will emit debug information when | |
323 | /// generating object files. | |
324 | /// | |
325 | /// This option is automatically scraped from the `PROFILE` environment | |
326 | /// variable by build scripts (only enabled when the profile is "debug"), so | |
327 | /// it's not required to call this function. | |
328 | pub fn debug(&mut self, debug: bool) -> &mut Config { | |
329 | self.debug = Some(debug); | |
330 | self | |
331 | } | |
332 | ||
333 | /// Configures the output directory where all object files and static | |
334 | /// libraries will be located. | |
335 | /// | |
336 | /// This option is automatically scraped from the `OUT_DIR` environment | |
337 | /// variable by build scripts, so it's not required to call this function. | |
338 | pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Config { | |
339 | self.out_dir = Some(out_dir.as_ref().to_owned()); | |
340 | self | |
341 | } | |
342 | ||
343 | /// Configures the compiler to be used to produce output. | |
344 | /// | |
345 | /// This option is automatically determined from the target platform or a | |
346 | /// number of environment variables, so it's not required to call this | |
347 | /// function. | |
348 | pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Config { | |
349 | self.compiler = Some(compiler.as_ref().to_owned()); | |
350 | self | |
351 | } | |
352 | ||
353 | /// Configures the tool used to assemble archives. | |
354 | /// | |
355 | /// This option is automatically determined from the target platform or a | |
356 | /// number of environment variables, so it's not required to call this | |
357 | /// function. | |
358 | pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Config { | |
359 | self.archiver = Some(archiver.as_ref().to_owned()); | |
360 | self | |
361 | } | |
362 | /// Define whether metadata should be emitted for cargo allowing it to | |
363 | /// automatically link the binary. Defaults to `true`. | |
364 | pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config { | |
365 | self.cargo_metadata = cargo_metadata; | |
366 | self | |
367 | } | |
368 | ||
369 | /// Configures whether the compiler will emit position independent code. | |
370 | /// | |
cc61c64b XL |
371 | /// This option defaults to `false` for `windows-gnu` targets and |
372 | /// to `true` for all other targets. | |
476ff2be SL |
373 | pub fn pic(&mut self, pic: bool) -> &mut Config { |
374 | self.pic = Some(pic); | |
375 | self | |
376 | } | |
377 | ||
7cac9316 XL |
378 | /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. |
379 | /// | |
380 | /// This option defaults to `false`, and affect only msvc targets. | |
381 | pub fn static_crt(&mut self, static_crt: bool) -> &mut Config { | |
382 | self.static_crt = Some(static_crt); | |
383 | self | |
384 | } | |
385 | ||
476ff2be SL |
386 | |
387 | #[doc(hidden)] | |
388 | pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Config | |
8bb4bdeb XL |
389 | where A: AsRef<OsStr>, |
390 | B: AsRef<OsStr> | |
476ff2be SL |
391 | { |
392 | self.env.push((a.as_ref().to_owned(), b.as_ref().to_owned())); | |
393 | self | |
394 | } | |
395 | ||
396 | /// Run the compiler, generating the file `output` | |
397 | /// | |
398 | /// The name `output` must begin with `lib` and end with `.a` | |
399 | pub fn compile(&self, output: &str) { | |
400 | assert!(output.starts_with("lib")); | |
401 | assert!(output.ends_with(".a")); | |
402 | let lib_name = &output[3..output.len() - 2]; | |
403 | let dst = self.get_out_dir(); | |
404 | ||
405 | let mut objects = Vec::new(); | |
406 | let mut src_dst = Vec::new(); | |
407 | for file in self.files.iter() { | |
408 | let obj = dst.join(file).with_extension("o"); | |
409 | let obj = if !obj.starts_with(&dst) { | |
410 | dst.join(obj.file_name().unwrap()) | |
411 | } else { | |
412 | obj | |
413 | }; | |
414 | fs::create_dir_all(&obj.parent().unwrap()).unwrap(); | |
415 | src_dst.push((file.to_path_buf(), obj.clone())); | |
416 | objects.push(obj); | |
417 | } | |
418 | self.compile_objects(&src_dst); | |
419 | self.assemble(lib_name, &dst.join(output), &objects); | |
420 | ||
421 | if self.get_target().contains("msvc") { | |
422 | let compiler = self.get_base_compiler(); | |
8bb4bdeb XL |
423 | let atlmfc_lib = compiler.env() |
424 | .iter() | |
425 | .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB")) | |
426 | .and_then(|&(_, ref lib_paths)| { | |
427 | env::split_paths(lib_paths).find(|path| { | |
428 | let sub = Path::new("atlmfc/lib"); | |
429 | path.ends_with(sub) || path.parent().map_or(false, |p| p.ends_with(sub)) | |
430 | }) | |
431 | }); | |
476ff2be SL |
432 | |
433 | if let Some(atlmfc_lib) = atlmfc_lib { | |
8bb4bdeb | 434 | self.print(&format!("cargo:rustc-link-search=native={}", atlmfc_lib.display())); |
476ff2be SL |
435 | } |
436 | } | |
437 | ||
438 | self.print(&format!("cargo:rustc-link-lib=static={}", | |
439 | &output[3..output.len() - 2])); | |
440 | self.print(&format!("cargo:rustc-link-search=native={}", dst.display())); | |
441 | ||
442 | // Add specific C++ libraries, if enabled. | |
443 | if self.cpp { | |
444 | if let Some(stdlib) = self.get_cpp_link_stdlib() { | |
445 | self.print(&format!("cargo:rustc-link-lib={}", stdlib)); | |
446 | } | |
447 | } | |
448 | } | |
449 | ||
450 | #[cfg(feature = "parallel")] | |
451 | fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) { | |
452 | use self::rayon::prelude::*; | |
453 | ||
454 | let mut cfg = rayon::Configuration::new(); | |
455 | if let Ok(amt) = env::var("NUM_JOBS") { | |
456 | if let Ok(amt) = amt.parse() { | |
7cac9316 | 457 | cfg = cfg.num_threads(amt); |
476ff2be SL |
458 | } |
459 | } | |
460 | drop(rayon::initialize(cfg)); | |
461 | ||
7cac9316 XL |
462 | objs.par_iter().with_max_len(1) |
463 | .for_each(|&(ref src, ref dst)| self.compile_object(src, dst)); | |
476ff2be SL |
464 | } |
465 | ||
466 | #[cfg(not(feature = "parallel"))] | |
467 | fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) { | |
468 | for &(ref src, ref dst) in objs { | |
469 | self.compile_object(src, dst); | |
470 | } | |
471 | } | |
472 | ||
473 | fn compile_object(&self, file: &Path, dst: &Path) { | |
474 | let is_asm = file.extension().and_then(|s| s.to_str()) == Some("asm"); | |
475 | let msvc = self.get_target().contains("msvc"); | |
476 | let (mut cmd, name) = if msvc && is_asm { | |
477 | self.msvc_macro_assembler() | |
478 | } else { | |
479 | let compiler = self.get_compiler(); | |
480 | let mut cmd = compiler.to_command(); | |
481 | for &(ref a, ref b) in self.env.iter() { | |
482 | cmd.env(a, b); | |
483 | } | |
8bb4bdeb XL |
484 | (cmd, |
485 | compiler.path | |
486 | .file_name() | |
487 | .unwrap() | |
488 | .to_string_lossy() | |
489 | .into_owned()) | |
476ff2be SL |
490 | }; |
491 | if msvc && is_asm { | |
492 | cmd.arg("/Fo").arg(dst); | |
493 | } else if msvc { | |
494 | let mut s = OsString::from("/Fo"); | |
495 | s.push(&dst); | |
496 | cmd.arg(s); | |
497 | } else { | |
498 | cmd.arg("-o").arg(&dst); | |
499 | } | |
8bb4bdeb | 500 | cmd.arg(if msvc { "/c" } else { "-c" }); |
476ff2be SL |
501 | cmd.arg(file); |
502 | ||
503 | run(&mut cmd, &name); | |
504 | } | |
505 | ||
8bb4bdeb XL |
506 | /// Run the compiler, returning the macro-expanded version of the input files. |
507 | /// | |
508 | /// This is only relevant for C and C++ files. | |
509 | pub fn expand(&self) -> Vec<u8> { | |
510 | let compiler = self.get_compiler(); | |
511 | let mut cmd = compiler.to_command(); | |
512 | for &(ref a, ref b) in self.env.iter() { | |
513 | cmd.env(a, b); | |
514 | } | |
515 | cmd.arg(compiler.family.expand_flag()); | |
516 | for file in self.files.iter() { | |
517 | cmd.arg(file); | |
518 | } | |
519 | ||
520 | let name = compiler.path | |
521 | .file_name() | |
522 | .unwrap() | |
523 | .to_string_lossy() | |
524 | .into_owned(); | |
525 | ||
cc61c64b | 526 | run_output(&mut cmd, &name) |
8bb4bdeb XL |
527 | } |
528 | ||
476ff2be SL |
529 | /// Get the compiler that's in use for this configuration. |
530 | /// | |
531 | /// This function will return a `Tool` which represents the culmination | |
532 | /// of this configuration at a snapshot in time. The returned compiler can | |
533 | /// be inspected (e.g. the path, arguments, environment) to forward along to | |
534 | /// other tools, or the `to_command` method can be used to invoke the | |
535 | /// compiler itself. | |
536 | /// | |
537 | /// This method will take into account all configuration such as debug | |
538 | /// information, optimization level, include directories, defines, etc. | |
539 | /// Additionally, the compiler binary in use follows the standard | |
540 | /// conventions for this path, e.g. looking at the explicitly set compiler, | |
541 | /// environment variables (a number of which are inspected here), and then | |
542 | /// falling back to the default configuration. | |
543 | pub fn get_compiler(&self) -> Tool { | |
544 | let opt_level = self.get_opt_level(); | |
476ff2be | 545 | let target = self.get_target(); |
476ff2be SL |
546 | |
547 | let mut cmd = self.get_base_compiler(); | |
8bb4bdeb XL |
548 | let nvcc = cmd.path.file_name() |
549 | .and_then(|p| p.to_str()).map(|p| p.contains("nvcc")) | |
476ff2be SL |
550 | .unwrap_or(false); |
551 | ||
8bb4bdeb XL |
552 | // Non-target flags |
553 | // If the flag is not conditioned on target variable, it belongs here :) | |
554 | match cmd.family { | |
555 | ToolFamily::Msvc => { | |
556 | cmd.args.push("/nologo".into()); | |
7cac9316 XL |
557 | |
558 | let crt_flag = match self.static_crt { | |
559 | Some(true) => "/MT", | |
560 | Some(false) => "/MD", | |
561 | None => { | |
562 | let features = env::var("CARGO_CFG_TARGET_FEATURE") | |
8bb4bdeb | 563 | .unwrap_or(String::new()); |
7cac9316 XL |
564 | if features.contains("crt-static") { |
565 | "/MT" | |
566 | } else { | |
567 | "/MD" | |
568 | } | |
569 | }, | |
570 | }; | |
571 | cmd.args.push(crt_flag.into()); | |
572 | ||
8bb4bdeb XL |
573 | match &opt_level[..] { |
574 | "z" | "s" => cmd.args.push("/Os".into()), | |
575 | "1" => cmd.args.push("/O1".into()), | |
576 | // -O3 is a valid value for gcc and clang compilers, but not msvc. Cap to /O2. | |
577 | "2" | "3" => cmd.args.push("/O2".into()), | |
578 | _ => {} | |
579 | } | |
476ff2be | 580 | } |
8bb4bdeb XL |
581 | ToolFamily::Gnu | |
582 | ToolFamily::Clang => { | |
583 | cmd.args.push(format!("-O{}", opt_level).into()); | |
584 | if !nvcc { | |
585 | cmd.args.push("-ffunction-sections".into()); | |
586 | cmd.args.push("-fdata-sections".into()); | |
587 | if self.pic.unwrap_or(!target.contains("windows-gnu")) { | |
588 | cmd.args.push("-fPIC".into()); | |
589 | } | |
590 | } else if self.pic.unwrap_or(false) { | |
591 | cmd.args.push("-Xcompiler".into()); | |
592 | cmd.args.push("\'-fPIC\'".into()); | |
593 | } | |
476ff2be | 594 | } |
476ff2be SL |
595 | } |
596 | for arg in self.envflags(if self.cpp {"CXXFLAGS"} else {"CFLAGS"}) { | |
597 | cmd.args.push(arg.into()); | |
598 | } | |
599 | ||
8bb4bdeb XL |
600 | if self.get_debug() { |
601 | cmd.args.push(cmd.family.debug_flag().into()); | |
476ff2be SL |
602 | } |
603 | ||
8bb4bdeb XL |
604 | // Target flags |
605 | match cmd.family { | |
606 | ToolFamily::Clang => { | |
607 | cmd.args.push(format!("--target={}", target).into()); | |
476ff2be | 608 | } |
8bb4bdeb XL |
609 | ToolFamily::Msvc => { |
610 | if target.contains("i586") { | |
611 | cmd.args.push("/ARCH:IA32".into()); | |
612 | } | |
476ff2be | 613 | } |
8bb4bdeb XL |
614 | ToolFamily::Gnu => { |
615 | if target.contains("i686") || target.contains("i586") { | |
616 | cmd.args.push("-m32".into()); | |
617 | } else if target.contains("x86_64") || target.contains("powerpc64") { | |
618 | cmd.args.push("-m64".into()); | |
619 | } | |
476ff2be | 620 | |
8bb4bdeb XL |
621 | if target.contains("musl") { |
622 | cmd.args.push("-static".into()); | |
623 | } | |
476ff2be | 624 | |
8bb4bdeb | 625 | // armv7 targets get to use armv7 instructions |
7cac9316 | 626 | if target.starts_with("armv7-") && target.contains("-linux-") { |
8bb4bdeb XL |
627 | cmd.args.push("-march=armv7-a".into()); |
628 | } | |
476ff2be | 629 | |
8bb4bdeb XL |
630 | // On android we can guarantee some extra float instructions |
631 | // (specified in the android spec online) | |
632 | if target.starts_with("armv7-linux-androideabi") { | |
633 | cmd.args.push("-march=armv7-a".into()); | |
634 | cmd.args.push("-mfpu=vfpv3-d16".into()); | |
7cac9316 | 635 | cmd.args.push("-mfloat-abi=softfp".into()); |
8bb4bdeb | 636 | } |
476ff2be | 637 | |
8bb4bdeb XL |
638 | // For us arm == armv6 by default |
639 | if target.starts_with("arm-unknown-linux-") { | |
640 | cmd.args.push("-march=armv6".into()); | |
641 | cmd.args.push("-marm".into()); | |
642 | } | |
476ff2be | 643 | |
7cac9316 XL |
644 | // We can guarantee some settings for FRC |
645 | if target.starts_with("arm-frc-") { | |
646 | cmd.args.push("-march=armv7-a".into()); | |
647 | cmd.args.push("-mcpu=cortex-a9".into()); | |
648 | cmd.args.push("-mfpu=vfpv3".into()); | |
649 | cmd.args.push("-mfloat-abi=softfp".into()); | |
650 | cmd.args.push("-marm".into()); | |
651 | } | |
652 | ||
8bb4bdeb XL |
653 | // Turn codegen down on i586 to avoid some instructions. |
654 | if target.starts_with("i586-unknown-linux-") { | |
655 | cmd.args.push("-march=pentium".into()); | |
656 | } | |
476ff2be | 657 | |
8bb4bdeb XL |
658 | // Set codegen level for i686 correctly |
659 | if target.starts_with("i686-unknown-linux-") { | |
660 | cmd.args.push("-march=i686".into()); | |
661 | } | |
476ff2be | 662 | |
8bb4bdeb XL |
663 | // Looks like `musl-gcc` makes is hard for `-m32` to make its way |
664 | // all the way to the linker, so we need to actually instruct the | |
665 | // linker that we're generating 32-bit executables as well. This'll | |
666 | // typically only be used for build scripts which transitively use | |
667 | // these flags that try to compile executables. | |
668 | if target == "i686-unknown-linux-musl" { | |
669 | cmd.args.push("-Wl,-melf_i386".into()); | |
670 | } | |
476ff2be | 671 | |
8bb4bdeb XL |
672 | if target.starts_with("thumb") { |
673 | cmd.args.push("-mthumb".into()); | |
476ff2be | 674 | |
8bb4bdeb XL |
675 | if target.ends_with("eabihf") { |
676 | cmd.args.push("-mfloat-abi=hard".into()) | |
677 | } | |
476ff2be | 678 | } |
8bb4bdeb XL |
679 | if target.starts_with("thumbv6m") { |
680 | cmd.args.push("-march=armv6s-m".into()); | |
681 | } | |
682 | if target.starts_with("thumbv7em") { | |
683 | cmd.args.push("-march=armv7e-m".into()); | |
476ff2be | 684 | |
8bb4bdeb XL |
685 | if target.ends_with("eabihf") { |
686 | cmd.args.push("-mfpu=fpv4-sp-d16".into()) | |
687 | } | |
688 | } | |
689 | if target.starts_with("thumbv7m") { | |
690 | cmd.args.push("-march=armv7-m".into()); | |
476ff2be SL |
691 | } |
692 | } | |
476ff2be SL |
693 | } |
694 | ||
8bb4bdeb XL |
695 | if target.contains("-ios") { |
696 | // FIXME: potential bug. iOS is always compiled with Clang, but Gcc compiler may be | |
697 | // detected instead. | |
698 | self.ios_flags(&mut cmd); | |
699 | } | |
700 | ||
701 | if self.cpp { | |
702 | match (self.cpp_set_stdlib.as_ref(), cmd.family) { | |
703 | (None, _) => { } | |
704 | (Some(stdlib), ToolFamily::Gnu) | | |
705 | (Some(stdlib), ToolFamily::Clang) => { | |
706 | cmd.args.push(format!("-stdlib=lib{}", stdlib).into()); | |
707 | } | |
708 | _ => { | |
709 | println!("cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \ | |
710 | does not support this option, ignored", cmd.family); | |
711 | } | |
476ff2be SL |
712 | } |
713 | } | |
714 | ||
715 | for directory in self.include_directories.iter() { | |
8bb4bdeb | 716 | cmd.args.push(cmd.family.include_flag().into()); |
476ff2be SL |
717 | cmd.args.push(directory.into()); |
718 | } | |
719 | ||
720 | for flag in self.flags.iter() { | |
721 | cmd.args.push(flag.into()); | |
722 | } | |
723 | ||
724 | for &(ref key, ref value) in self.definitions.iter() { | |
8bb4bdeb | 725 | let lead = if let ToolFamily::Msvc = cmd.family {"/"} else {"-"}; |
7cac9316 | 726 | if let Some(ref value) = *value { |
476ff2be SL |
727 | cmd.args.push(format!("{}D{}={}", lead, key, value).into()); |
728 | } else { | |
729 | cmd.args.push(format!("{}D{}", lead, key).into()); | |
730 | } | |
731 | } | |
732 | cmd | |
733 | } | |
734 | ||
735 | fn msvc_macro_assembler(&self) -> (Command, String) { | |
736 | let target = self.get_target(); | |
8bb4bdeb XL |
737 | let tool = if target.contains("x86_64") { |
738 | "ml64.exe" | |
739 | } else { | |
740 | "ml.exe" | |
741 | }; | |
742 | let mut cmd = windows_registry::find(&target, tool).unwrap_or_else(|| self.cmd(tool)); | |
476ff2be SL |
743 | for directory in self.include_directories.iter() { |
744 | cmd.arg("/I").arg(directory); | |
745 | } | |
746 | for &(ref key, ref value) in self.definitions.iter() { | |
7cac9316 | 747 | if let Some(ref value) = *value { |
476ff2be SL |
748 | cmd.arg(&format!("/D{}={}", key, value)); |
749 | } else { | |
750 | cmd.arg(&format!("/D{}", key)); | |
751 | } | |
752 | } | |
753 | ||
754 | if target.contains("i686") || target.contains("i586") { | |
755 | cmd.arg("/safeseh"); | |
756 | } | |
757 | for flag in self.flags.iter() { | |
758 | cmd.arg(flag); | |
759 | } | |
760 | ||
761 | (cmd, tool.to_string()) | |
762 | } | |
763 | ||
764 | fn assemble(&self, lib_name: &str, dst: &Path, objects: &[PathBuf]) { | |
765 | // Delete the destination if it exists as the `ar` tool at least on Unix | |
766 | // appends to it, which we don't want. | |
767 | let _ = fs::remove_file(&dst); | |
768 | ||
769 | let target = self.get_target(); | |
770 | if target.contains("msvc") { | |
771 | let mut cmd = match self.archiver { | |
772 | Some(ref s) => self.cmd(s), | |
7cac9316 | 773 | None => windows_registry::find(&target, "lib.exe").unwrap_or_else(|| self.cmd("lib.exe")), |
476ff2be SL |
774 | }; |
775 | let mut out = OsString::from("/OUT:"); | |
776 | out.push(dst); | |
8bb4bdeb XL |
777 | run(cmd.arg(out) |
778 | .arg("/nologo") | |
779 | .args(objects) | |
780 | .args(&self.objects), | |
781 | "lib.exe"); | |
476ff2be SL |
782 | |
783 | // The Rust compiler will look for libfoo.a and foo.lib, but the | |
784 | // MSVC linker will also be passed foo.lib, so be sure that both | |
785 | // exist for now. | |
786 | let lib_dst = dst.with_file_name(format!("{}.lib", lib_name)); | |
787 | let _ = fs::remove_file(&lib_dst); | |
8bb4bdeb XL |
788 | fs::hard_link(&dst, &lib_dst) |
789 | .or_else(|_| { | |
790 | // if hard-link fails, just copy (ignoring the number of bytes written) | |
791 | fs::copy(&dst, &lib_dst).map(|_| ()) | |
792 | }) | |
8bb4bdeb | 793 | .expect("Copying from {:?} to {:?} failed.");; |
476ff2be SL |
794 | } else { |
795 | let ar = self.get_ar(); | |
796 | let cmd = ar.file_name().unwrap().to_string_lossy(); | |
8bb4bdeb XL |
797 | run(self.cmd(&ar) |
798 | .arg("crs") | |
799 | .arg(dst) | |
800 | .args(objects) | |
801 | .args(&self.objects), | |
802 | &cmd); | |
476ff2be SL |
803 | } |
804 | } | |
805 | ||
806 | fn ios_flags(&self, cmd: &mut Tool) { | |
807 | enum ArchSpec { | |
808 | Device(&'static str), | |
809 | Simulator(&'static str), | |
810 | } | |
811 | ||
812 | let target = self.get_target(); | |
813 | let arch = target.split('-').nth(0).unwrap(); | |
814 | let arch = match arch { | |
815 | "arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7"), | |
816 | "armv7s" | "thumbv7s" => ArchSpec::Device("armv7s"), | |
817 | "arm64" | "aarch64" => ArchSpec::Device("arm64"), | |
818 | "i386" | "i686" => ArchSpec::Simulator("-m32"), | |
819 | "x86_64" => ArchSpec::Simulator("-m64"), | |
8bb4bdeb | 820 | _ => fail("Unknown arch for iOS target"), |
476ff2be SL |
821 | }; |
822 | ||
823 | let sdk = match arch { | |
824 | ArchSpec::Device(arch) => { | |
825 | cmd.args.push("-arch".into()); | |
826 | cmd.args.push(arch.into()); | |
827 | cmd.args.push("-miphoneos-version-min=7.0".into()); | |
828 | "iphoneos" | |
8bb4bdeb | 829 | } |
476ff2be SL |
830 | ArchSpec::Simulator(arch) => { |
831 | cmd.args.push(arch.into()); | |
832 | cmd.args.push("-mios-simulator-version-min=7.0".into()); | |
833 | "iphonesimulator" | |
834 | } | |
835 | }; | |
836 | ||
837 | self.print(&format!("Detecting iOS SDK path for {}", sdk)); | |
838 | let sdk_path = self.cmd("xcrun") | |
839 | .arg("--show-sdk-path") | |
840 | .arg("--sdk") | |
841 | .arg(sdk) | |
842 | .stderr(Stdio::inherit()) | |
843 | .output() | |
844 | .unwrap() | |
845 | .stdout; | |
846 | ||
847 | let sdk_path = String::from_utf8(sdk_path).unwrap(); | |
848 | ||
849 | cmd.args.push("-isysroot".into()); | |
850 | cmd.args.push(sdk_path.trim().into()); | |
851 | } | |
852 | ||
853 | fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command { | |
854 | let mut cmd = Command::new(prog); | |
855 | for &(ref a, ref b) in self.env.iter() { | |
856 | cmd.env(a, b); | |
857 | } | |
7cac9316 | 858 | cmd |
476ff2be SL |
859 | } |
860 | ||
861 | fn get_base_compiler(&self) -> Tool { | |
862 | if let Some(ref c) = self.compiler { | |
8bb4bdeb | 863 | return Tool::new(c.clone()); |
476ff2be SL |
864 | } |
865 | let host = self.get_host(); | |
866 | let target = self.get_target(); | |
cc61c64b XL |
867 | let (env, msvc, gnu) = if self.cpp { |
868 | ("CXX", "cl.exe", "g++") | |
476ff2be | 869 | } else { |
cc61c64b | 870 | ("CC", "cl.exe", "gcc") |
476ff2be | 871 | }; |
cc61c64b XL |
872 | |
873 | let default = if host.contains("solaris") { | |
874 | // In this case, c++/cc unlikely to exist or be correct. | |
875 | gnu | |
876 | } else if self.cpp { | |
877 | "c++" | |
878 | } else { | |
879 | "cc" | |
880 | }; | |
881 | ||
8bb4bdeb XL |
882 | self.env_tool(env) |
883 | .map(|(tool, args)| { | |
884 | let mut t = Tool::new(PathBuf::from(tool)); | |
885 | for arg in args { | |
886 | t.args.push(arg.into()); | |
476ff2be | 887 | } |
7cac9316 | 888 | t |
8bb4bdeb XL |
889 | }) |
890 | .or_else(|| { | |
891 | if target.contains("emscripten") { | |
7cac9316 XL |
892 | //Windows uses bat file so we have to be a bit more specific |
893 | let tool = if self.cpp { | |
894 | if cfg!(windows) { | |
895 | "em++.bat" | |
896 | } else { | |
897 | "em++" | |
898 | } | |
8bb4bdeb | 899 | } else { |
7cac9316 XL |
900 | if cfg!(windows) { |
901 | "emcc.bat" | |
902 | } else { | |
903 | "emcc" | |
904 | } | |
905 | }; | |
906 | ||
907 | Some(Tool::new(PathBuf::from(tool))) | |
476ff2be | 908 | } else { |
8bb4bdeb | 909 | None |
476ff2be | 910 | } |
8bb4bdeb XL |
911 | }) |
912 | .or_else(|| windows_registry::find_tool(&target, "cl.exe")) | |
913 | .unwrap_or_else(|| { | |
914 | let compiler = if host.contains("windows") && target.contains("windows") { | |
915 | if target.contains("msvc") { | |
916 | msvc.to_string() | |
917 | } else { | |
918 | format!("{}.exe", gnu) | |
919 | } | |
920 | } else if target.contains("android") { | |
921 | format!("{}-{}", target.replace("armv7", "arm"), gnu) | |
922 | } else if self.get_host() != target { | |
923 | // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" | |
924 | let cc_env = self.getenv("CROSS_COMPILE"); | |
925 | let cross_compile = cc_env.as_ref().map(|s| s.trim_right_matches('-')); | |
926 | let prefix = cross_compile.or(match &target[..] { | |
927 | "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"), | |
928 | "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), | |
7cac9316 | 929 | "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi"), |
8bb4bdeb XL |
930 | "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), |
931 | "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"), | |
932 | "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), | |
933 | "arm-unknown-netbsdelf-eabi" => Some("arm--netbsdelf-eabi"), | |
934 | "armv6-unknown-netbsdelf-eabihf" => Some("armv6--netbsdelf-eabihf"), | |
935 | "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), | |
936 | "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), | |
937 | "armv7-unknown-netbsdelf-eabihf" => Some("armv7--netbsdelf-eabihf"), | |
938 | "i686-pc-windows-gnu" => Some("i686-w64-mingw32"), | |
939 | "i686-unknown-linux-musl" => Some("musl"), | |
940 | "i686-unknown-netbsdelf" => Some("i486--netbsdelf"), | |
941 | "mips-unknown-linux-gnu" => Some("mips-linux-gnu"), | |
942 | "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu"), | |
943 | "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64"), | |
944 | "mips64el-unknown-linux-gnuabi64" => Some("mips64el-linux-gnuabi64"), | |
945 | "powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu"), | |
946 | "powerpc-unknown-netbsd" => Some("powerpc--netbsd"), | |
947 | "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"), | |
948 | "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"), | |
949 | "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"), | |
950 | "sparc64-unknown-netbsd" => Some("sparc64--netbsd"), | |
cc61c64b | 951 | "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"), |
8bb4bdeb XL |
952 | "thumbv6m-none-eabi" => Some("arm-none-eabi"), |
953 | "thumbv7em-none-eabi" => Some("arm-none-eabi"), | |
954 | "thumbv7em-none-eabihf" => Some("arm-none-eabi"), | |
955 | "thumbv7m-none-eabi" => Some("arm-none-eabi"), | |
956 | "x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32"), | |
957 | "x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd"), | |
958 | "x86_64-unknown-linux-musl" => Some("musl"), | |
959 | "x86_64-unknown-netbsd" => Some("x86_64--netbsd"), | |
960 | _ => None, | |
961 | }); | |
962 | match prefix { | |
963 | Some(prefix) => format!("{}-{}", prefix, gnu), | |
964 | None => default.to_string(), | |
965 | } | |
966 | } else { | |
967 | default.to_string() | |
968 | }; | |
969 | Tool::new(PathBuf::from(compiler)) | |
970 | }) | |
476ff2be SL |
971 | } |
972 | ||
973 | fn get_var(&self, var_base: &str) -> Result<String, String> { | |
974 | let target = self.get_target(); | |
975 | let host = self.get_host(); | |
8bb4bdeb | 976 | let kind = if host == target { "HOST" } else { "TARGET" }; |
476ff2be SL |
977 | let target_u = target.replace("-", "_"); |
978 | let res = self.getenv(&format!("{}_{}", var_base, target)) | |
979 | .or_else(|| self.getenv(&format!("{}_{}", var_base, target_u))) | |
980 | .or_else(|| self.getenv(&format!("{}_{}", kind, var_base))) | |
981 | .or_else(|| self.getenv(var_base)); | |
982 | ||
983 | match res { | |
984 | Some(res) => Ok(res), | |
985 | None => Err("could not get environment variable".to_string()), | |
986 | } | |
987 | } | |
988 | ||
989 | fn envflags(&self, name: &str) -> Vec<String> { | |
8bb4bdeb XL |
990 | self.get_var(name) |
991 | .unwrap_or(String::new()) | |
992 | .split(|c: char| c.is_whitespace()) | |
993 | .filter(|s| !s.is_empty()) | |
476ff2be SL |
994 | .map(|s| s.to_string()) |
995 | .collect() | |
996 | } | |
997 | ||
998 | fn env_tool(&self, name: &str) -> Option<(String, Vec<String>)> { | |
999 | self.get_var(name).ok().map(|tool| { | |
1000 | let whitelist = ["ccache", "distcc"]; | |
1001 | for t in whitelist.iter() { | |
7cac9316 | 1002 | if tool.starts_with(t) && tool[t.len()..].starts_with(' ') { |
8bb4bdeb | 1003 | return (t.to_string(), vec![tool[t.len()..].trim_left().to_string()]); |
476ff2be SL |
1004 | } |
1005 | } | |
1006 | (tool, Vec::new()) | |
1007 | }) | |
1008 | } | |
1009 | ||
1010 | /// Returns the default C++ standard library for the current target: `libc++` | |
1011 | /// for OS X and `libstdc++` for anything else. | |
1012 | fn get_cpp_link_stdlib(&self) -> Option<String> { | |
1013 | self.cpp_link_stdlib.clone().unwrap_or_else(|| { | |
1014 | let target = self.get_target(); | |
1015 | if target.contains("msvc") { | |
1016 | None | |
1017 | } else if target.contains("darwin") { | |
1018 | Some("c++".to_string()) | |
1019 | } else if target.contains("freebsd") { | |
1020 | Some("c++".to_string()) | |
1021 | } else { | |
1022 | Some("stdc++".to_string()) | |
1023 | } | |
1024 | }) | |
1025 | } | |
1026 | ||
1027 | fn get_ar(&self) -> PathBuf { | |
8bb4bdeb XL |
1028 | self.archiver |
1029 | .clone() | |
1030 | .or_else(|| self.get_var("AR").map(PathBuf::from).ok()) | |
1031 | .unwrap_or_else(|| { | |
1032 | if self.get_target().contains("android") { | |
1033 | PathBuf::from(format!("{}-ar", self.get_target().replace("armv7", "arm"))) | |
1034 | } else if self.get_target().contains("emscripten") { | |
7cac9316 XL |
1035 | //Windows use bat files so we have to be a bit more specific |
1036 | let tool = if cfg!(windows) { | |
1037 | "emar.bat" | |
1038 | } else { | |
1039 | "emar" | |
1040 | }; | |
1041 | ||
1042 | PathBuf::from(tool) | |
8bb4bdeb XL |
1043 | } else { |
1044 | PathBuf::from("ar") | |
1045 | } | |
1046 | }) | |
476ff2be SL |
1047 | } |
1048 | ||
1049 | fn get_target(&self) -> String { | |
1050 | self.target.clone().unwrap_or_else(|| self.getenv_unwrap("TARGET")) | |
1051 | } | |
1052 | ||
1053 | fn get_host(&self) -> String { | |
1054 | self.host.clone().unwrap_or_else(|| self.getenv_unwrap("HOST")) | |
1055 | } | |
1056 | ||
1057 | fn get_opt_level(&self) -> String { | |
8bb4bdeb | 1058 | self.opt_level.as_ref().cloned().unwrap_or_else(|| self.getenv_unwrap("OPT_LEVEL")) |
476ff2be SL |
1059 | } |
1060 | ||
1061 | fn get_debug(&self) -> bool { | |
1062 | self.debug.unwrap_or_else(|| self.getenv_unwrap("PROFILE") == "debug") | |
1063 | } | |
1064 | ||
1065 | fn get_out_dir(&self) -> PathBuf { | |
8bb4bdeb | 1066 | self.out_dir.clone().unwrap_or_else(|| env::var_os("OUT_DIR").map(PathBuf::from).unwrap()) |
476ff2be SL |
1067 | } |
1068 | ||
1069 | fn getenv(&self, v: &str) -> Option<String> { | |
1070 | let r = env::var(v).ok(); | |
1071 | self.print(&format!("{} = {:?}", v, r)); | |
1072 | r | |
1073 | } | |
1074 | ||
1075 | fn getenv_unwrap(&self, v: &str) -> String { | |
1076 | match self.getenv(v) { | |
1077 | Some(s) => s, | |
1078 | None => fail(&format!("environment variable `{}` not defined", v)), | |
1079 | } | |
1080 | } | |
1081 | ||
1082 | fn print(&self, s: &str) { | |
1083 | if self.cargo_metadata { | |
1084 | println!("{}", s); | |
1085 | } | |
1086 | } | |
1087 | } | |
1088 | ||
1089 | impl Tool { | |
1090 | fn new(path: PathBuf) -> Tool { | |
8bb4bdeb XL |
1091 | // Try to detect family of the tool from its name, falling back to Gnu. |
1092 | let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { | |
1093 | if fname.contains("clang") { | |
1094 | ToolFamily::Clang | |
7cac9316 | 1095 | } else if fname.contains("cl") && !fname.contains("uclibc") { |
8bb4bdeb XL |
1096 | ToolFamily::Msvc |
1097 | } else { | |
1098 | ToolFamily::Gnu | |
1099 | } | |
1100 | } else { | |
1101 | ToolFamily::Gnu | |
1102 | }; | |
476ff2be SL |
1103 | Tool { |
1104 | path: path, | |
1105 | args: Vec::new(), | |
1106 | env: Vec::new(), | |
8bb4bdeb | 1107 | family: family |
476ff2be SL |
1108 | } |
1109 | } | |
1110 | ||
1111 | /// Converts this compiler into a `Command` that's ready to be run. | |
1112 | /// | |
1113 | /// This is useful for when the compiler needs to be executed and the | |
1114 | /// command returned will already have the initial arguments and environment | |
1115 | /// variables configured. | |
1116 | pub fn to_command(&self) -> Command { | |
1117 | let mut cmd = Command::new(&self.path); | |
1118 | cmd.args(&self.args); | |
1119 | for &(ref k, ref v) in self.env.iter() { | |
1120 | cmd.env(k, v); | |
1121 | } | |
8bb4bdeb | 1122 | cmd |
476ff2be SL |
1123 | } |
1124 | ||
1125 | /// Returns the path for this compiler. | |
1126 | /// | |
1127 | /// Note that this may not be a path to a file on the filesystem, e.g. "cc", | |
1128 | /// but rather something which will be resolved when a process is spawned. | |
1129 | pub fn path(&self) -> &Path { | |
1130 | &self.path | |
1131 | } | |
1132 | ||
1133 | /// Returns the default set of arguments to the compiler needed to produce | |
1134 | /// executables for the target this compiler generates. | |
1135 | pub fn args(&self) -> &[OsString] { | |
1136 | &self.args | |
1137 | } | |
1138 | ||
1139 | /// Returns the set of environment variables needed for this compiler to | |
1140 | /// operate. | |
1141 | /// | |
1142 | /// This is typically only used for MSVC compilers currently. | |
1143 | pub fn env(&self) -> &[(OsString, OsString)] { | |
1144 | &self.env | |
1145 | } | |
1146 | } | |
1147 | ||
cc61c64b XL |
1148 | fn run(cmd: &mut Command, program: &str) { |
1149 | let (mut child, print) = spawn(cmd, program); | |
1150 | let status = child.wait().expect("failed to wait on child process"); | |
1151 | print.join().unwrap(); | |
7cac9316 | 1152 | println!("{}", status); |
cc61c64b XL |
1153 | if !status.success() { |
1154 | fail(&format!("command did not execute successfully, got: {}", status)); | |
1155 | } | |
1156 | } | |
1157 | ||
1158 | fn run_output(cmd: &mut Command, program: &str) -> Vec<u8> { | |
1159 | cmd.stdout(Stdio::piped()); | |
1160 | let (mut child, print) = spawn(cmd, program); | |
1161 | let mut stdout = vec![]; | |
1162 | child.stdout.take().unwrap().read_to_end(&mut stdout).unwrap(); | |
1163 | let status = child.wait().expect("failed to wait on child process"); | |
1164 | print.join().unwrap(); | |
7cac9316 | 1165 | println!("{}", status); |
cc61c64b XL |
1166 | if !status.success() { |
1167 | fail(&format!("command did not execute successfully, got: {}", status)); | |
1168 | } | |
7cac9316 | 1169 | stdout |
cc61c64b XL |
1170 | } |
1171 | ||
1172 | fn spawn(cmd: &mut Command, program: &str) -> (Child, JoinHandle<()>) { | |
476ff2be | 1173 | println!("running: {:?}", cmd); |
cc61c64b | 1174 | |
476ff2be SL |
1175 | // Capture the standard error coming from these programs, and write it out |
1176 | // with cargo:warning= prefixes. Note that this is a bit wonky to avoid | |
1177 | // requiring the output to be UTF-8, we instead just ship bytes from one | |
1178 | // location to another. | |
cc61c64b | 1179 | match cmd.stderr(Stdio::piped()).spawn() { |
476ff2be SL |
1180 | Ok(mut child) => { |
1181 | let stderr = BufReader::new(child.stderr.take().unwrap()); | |
cc61c64b | 1182 | let print = thread::spawn(move || { |
8bb4bdeb XL |
1183 | for line in stderr.split(b'\n').filter_map(|l| l.ok()) { |
1184 | print!("cargo:warning="); | |
1185 | std::io::stdout().write_all(&line).unwrap(); | |
1186 | println!(""); | |
1187 | } | |
1188 | }); | |
cc61c64b | 1189 | (child, print) |
476ff2be | 1190 | } |
476ff2be SL |
1191 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { |
1192 | let extra = if cfg!(windows) { | |
1193 | " (see https://github.com/alexcrichton/gcc-rs#compile-time-requirements \ | |
1194 | for help)" | |
1195 | } else { | |
1196 | "" | |
1197 | }; | |
1198 | fail(&format!("failed to execute command: {}\nIs `{}` \ | |
8bb4bdeb XL |
1199 | not installed?{}", |
1200 | e, | |
1201 | program, | |
1202 | extra)); | |
476ff2be SL |
1203 | } |
1204 | Err(e) => fail(&format!("failed to execute command: {}", e)), | |
476ff2be SL |
1205 | } |
1206 | } | |
1207 | ||
1208 | fn fail(s: &str) -> ! { | |
1209 | println!("\n\n{}\n\n", s); | |
1210 | panic!() | |
1211 | } |