]> git.proxmox.com Git - rustc.git/blob - vendor/cmake/src/lib.rs
New upstream version 1.52.0~beta.3+dfsg1
[rustc.git] / vendor / cmake / src / lib.rs
1 //! A build dependency for running `cmake` to build a native library
2 //!
3 //! This crate provides some necessary boilerplate and shim support for running
4 //! the system `cmake` command to build a native library. It will add
5 //! appropriate cflags for building code to link into Rust, handle cross
6 //! compilation, and use the necessary generator for the platform being
7 //! targeted.
8 //!
9 //! The builder-style configuration allows for various variables and such to be
10 //! passed down into the build as well.
11 //!
12 //! ## Installation
13 //!
14 //! Add this to your `Cargo.toml`:
15 //!
16 //! ```toml
17 //! [build-dependencies]
18 //! cmake = "0.1"
19 //! ```
20 //!
21 //! ## Examples
22 //!
23 //! ```no_run
24 //! use cmake;
25 //!
26 //! // Builds the project in the directory located in `libfoo`, installing it
27 //! // into $OUT_DIR
28 //! let dst = cmake::build("libfoo");
29 //!
30 //! println!("cargo:rustc-link-search=native={}", dst.display());
31 //! println!("cargo:rustc-link-lib=static=foo");
32 //! ```
33 //!
34 //! ```no_run
35 //! use cmake::Config;
36 //!
37 //! let dst = Config::new("libfoo")
38 //! .define("FOO", "BAR")
39 //! .cflag("-foo")
40 //! .build();
41 //! println!("cargo:rustc-link-search=native={}", dst.display());
42 //! println!("cargo:rustc-link-lib=static=foo");
43 //! ```
44
45 #![deny(missing_docs)]
46
47 extern crate cc;
48
49 use std::env;
50 use std::ffi::{OsStr, OsString};
51 use std::fs::{self, File};
52 use std::io::prelude::*;
53 use std::io::ErrorKind;
54 use std::path::{Path, PathBuf};
55 use std::process::Command;
56
57 /// Builder style configuration for a pending CMake build.
58 pub struct Config {
59 path: PathBuf,
60 generator: Option<OsString>,
61 cflags: OsString,
62 cxxflags: OsString,
63 asmflags: OsString,
64 defines: Vec<(OsString, OsString)>,
65 deps: Vec<String>,
66 target: Option<String>,
67 host: Option<String>,
68 out_dir: Option<PathBuf>,
69 profile: Option<String>,
70 build_args: Vec<OsString>,
71 cmake_target: Option<String>,
72 env: Vec<(OsString, OsString)>,
73 static_crt: Option<bool>,
74 uses_cxx11: bool,
75 always_configure: bool,
76 no_build_target: bool,
77 verbose_cmake: bool,
78 verbose_make: bool,
79 pic: Option<bool>,
80 }
81
82 /// Builds the native library rooted at `path` with the default cmake options.
83 /// This will return the directory in which the library was installed.
84 ///
85 /// # Examples
86 ///
87 /// ```no_run
88 /// use cmake;
89 ///
90 /// // Builds the project in the directory located in `libfoo`, installing it
91 /// // into $OUT_DIR
92 /// let dst = cmake::build("libfoo");
93 ///
94 /// println!("cargo:rustc-link-search=native={}", dst.display());
95 /// println!("cargo:rustc-link-lib=static=foo");
96 /// ```
97 ///
98 pub fn build<P: AsRef<Path>>(path: P) -> PathBuf {
99 Config::new(path.as_ref()).build()
100 }
101
102 impl Config {
103 /// Creates a new blank set of configuration to build the project specified
104 /// at the path `path`.
105 pub fn new<P: AsRef<Path>>(path: P) -> Config {
106 Config {
107 path: env::current_dir().unwrap().join(path),
108 generator: None,
109 cflags: OsString::new(),
110 cxxflags: OsString::new(),
111 asmflags: OsString::new(),
112 defines: Vec::new(),
113 deps: Vec::new(),
114 profile: None,
115 out_dir: None,
116 target: None,
117 host: None,
118 build_args: Vec::new(),
119 cmake_target: None,
120 env: Vec::new(),
121 static_crt: None,
122 uses_cxx11: false,
123 always_configure: true,
124 no_build_target: false,
125 verbose_cmake: false,
126 verbose_make: false,
127 pic: None,
128 }
129 }
130
131 /// Sets flag for PIC. Otherwise use cc::Build platform default
132 pub fn pic(&mut self, explicit_flag: bool) -> &mut Config {
133 self.pic = Some(explicit_flag);
134 self
135 }
136
137 /// Sets the build-tool generator (`-G`) for this compilation.
138 pub fn generator<T: AsRef<OsStr>>(&mut self, generator: T) -> &mut Config {
139 self.generator = Some(generator.as_ref().to_owned());
140 self
141 }
142
143 /// Adds a custom flag to pass down to the C compiler, supplementing those
144 /// that this library already passes.
145 pub fn cflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
146 self.cflags.push(" ");
147 self.cflags.push(flag.as_ref());
148 self
149 }
150
151 /// Adds a custom flag to pass down to the C++ compiler, supplementing those
152 /// that this library already passes.
153 pub fn cxxflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
154 self.cxxflags.push(" ");
155 self.cxxflags.push(flag.as_ref());
156 self
157 }
158
159 /// Adds a custom flag to pass down to the ASM compiler, supplementing those
160 /// that this library already passes.
161 pub fn asmflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
162 self.asmflags.push(" ");
163 self.asmflags.push(flag.as_ref());
164 self
165 }
166
167 /// Adds a new `-D` flag to pass to cmake during the generation step.
168 pub fn define<K, V>(&mut self, k: K, v: V) -> &mut Config
169 where
170 K: AsRef<OsStr>,
171 V: AsRef<OsStr>,
172 {
173 self.defines
174 .push((k.as_ref().to_owned(), v.as_ref().to_owned()));
175 self
176 }
177
178 /// Registers a dependency for this compilation on the native library built
179 /// by Cargo previously.
180 ///
181 /// This registration will modify the `CMAKE_PREFIX_PATH` environment
182 /// variable for the build system generation step.
183 pub fn register_dep(&mut self, dep: &str) -> &mut Config {
184 self.deps.push(dep.to_string());
185 self
186 }
187
188 /// Sets the target triple for this compilation.
189 ///
190 /// This is automatically scraped from `$TARGET` which is set for Cargo
191 /// build scripts so it's not necessary to call this from a build script.
192 pub fn target(&mut self, target: &str) -> &mut Config {
193 self.target = Some(target.to_string());
194 self
195 }
196
197 /// Disables the cmake target option for this compilation.
198 ///
199 /// Note that this isn't related to the target triple passed to the compiler!
200 pub fn no_build_target(&mut self, no_build_target: bool) -> &mut Config {
201 self.no_build_target = no_build_target;
202 self
203 }
204
205 /// Sets the host triple for this compilation.
206 ///
207 /// This is automatically scraped from `$HOST` which is set for Cargo
208 /// build scripts so it's not necessary to call this from a build script.
209 pub fn host(&mut self, host: &str) -> &mut Config {
210 self.host = Some(host.to_string());
211 self
212 }
213
214 /// Sets the output directory for this compilation.
215 ///
216 /// This is automatically scraped from `$OUT_DIR` which is set for Cargo
217 /// build scripts so it's not necessary to call this from a build script.
218 pub fn out_dir<P: AsRef<Path>>(&mut self, out: P) -> &mut Config {
219 self.out_dir = Some(out.as_ref().to_path_buf());
220 self
221 }
222
223 /// Sets the `CMAKE_BUILD_TYPE=build_type` variable.
224 ///
225 /// By default, this value is automatically inferred from Rust's compilation
226 /// profile as follows:
227 ///
228 /// * if `opt-level=0` then `CMAKE_BUILD_TYPE=Debug`,
229 /// * if `opt-level={1,2,3}` and:
230 /// * `debug=false` then `CMAKE_BUILD_TYPE=Release`
231 /// * otherwise `CMAKE_BUILD_TYPE=RelWithDebInfo`
232 /// * if `opt-level={s,z}` then `CMAKE_BUILD_TYPE=MinSizeRel`
233 pub fn profile(&mut self, profile: &str) -> &mut Config {
234 self.profile = Some(profile.to_string());
235 self
236 }
237
238 /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools.
239 ///
240 /// This option defaults to `false`, and affect only msvc targets.
241 pub fn static_crt(&mut self, static_crt: bool) -> &mut Config {
242 self.static_crt = Some(static_crt);
243 self
244 }
245
246 /// Add an argument to the final `cmake` build step
247 pub fn build_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut Config {
248 self.build_args.push(arg.as_ref().to_owned());
249 self
250 }
251
252 /// Configure an environment variable for the `cmake` processes spawned by
253 /// this crate in the `build` step.
254 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Config
255 where
256 K: AsRef<OsStr>,
257 V: AsRef<OsStr>,
258 {
259 self.env
260 .push((key.as_ref().to_owned(), value.as_ref().to_owned()));
261 self
262 }
263
264 /// Sets the build target for the final `cmake` build step, this will
265 /// default to "install" if not specified.
266 pub fn build_target(&mut self, target: &str) -> &mut Config {
267 self.cmake_target = Some(target.to_string());
268 self
269 }
270
271 /// Alters the default target triple on OSX to ensure that c++11 is
272 /// available. Does not change the target triple if it is explicitly
273 /// specified.
274 ///
275 /// This does not otherwise affect any CXX flags, i.e. it does not set
276 /// -std=c++11 or -stdlib=libc++.
277 pub fn uses_cxx11(&mut self) -> &mut Config {
278 self.uses_cxx11 = true;
279 self
280 }
281
282 /// Forces CMake to always run before building the custom target.
283 ///
284 /// In some cases, when you have a big project, you can disable
285 /// subsequents runs of cmake to make `cargo build` faster.
286 pub fn always_configure(&mut self, always_configure: bool) -> &mut Config {
287 self.always_configure = always_configure;
288 self
289 }
290
291 /// Sets very verbose output.
292 pub fn very_verbose(&mut self, value: bool) -> &mut Config {
293 self.verbose_cmake = value;
294 self.verbose_make = value;
295 self
296 }
297
298 // Simple heuristic to determine if we're cross-compiling using the Android
299 // NDK toolchain file.
300 fn uses_android_ndk(&self) -> bool {
301 // `ANDROID_ABI` is the only required flag:
302 // https://developer.android.com/ndk/guides/cmake#android_abi
303 self.defined("ANDROID_ABI")
304 && self.defines.iter().any(|(flag, value)| {
305 flag == "CMAKE_TOOLCHAIN_FILE"
306 && Path::new(value).file_name() == Some("android.toolchain.cmake".as_ref())
307 })
308 }
309
310 /// Run this configuration, compiling the library with all the configured
311 /// options.
312 ///
313 /// This will run both the build system generator command as well as the
314 /// command to build the library.
315 pub fn build(&mut self) -> PathBuf {
316 let target = match self.target.clone() {
317 Some(t) => t,
318 None => {
319 let mut t = getenv_unwrap("TARGET");
320 if t.ends_with("-darwin") && self.uses_cxx11 {
321 t = t + "11"
322 }
323 t
324 }
325 };
326 let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
327 let msvc = target.contains("msvc");
328 let ndk = self.uses_android_ndk();
329 let mut c_cfg = cc::Build::new();
330 c_cfg
331 .cargo_metadata(false)
332 .opt_level(0)
333 .debug(false)
334 .warnings(false)
335 .host(&host)
336 .no_default_flags(ndk);
337 if !ndk {
338 c_cfg.target(&target);
339 }
340 let mut cxx_cfg = cc::Build::new();
341 cxx_cfg
342 .cargo_metadata(false)
343 .cpp(true)
344 .opt_level(0)
345 .debug(false)
346 .warnings(false)
347 .host(&host)
348 .no_default_flags(ndk);
349 if !ndk {
350 cxx_cfg.target(&target);
351 }
352 if let Some(static_crt) = self.static_crt {
353 c_cfg.static_crt(static_crt);
354 cxx_cfg.static_crt(static_crt);
355 }
356 if let Some(explicit_flag) = self.pic {
357 c_cfg.pic(explicit_flag);
358 cxx_cfg.pic(explicit_flag);
359 }
360 let c_compiler = c_cfg.get_compiler();
361 let cxx_compiler = cxx_cfg.get_compiler();
362 let asm_compiler = c_cfg.get_compiler();
363
364 let dst = self
365 .out_dir
366 .clone()
367 .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR")));
368 let build = dst.join("build");
369 self.maybe_clear(&build);
370 let _ = fs::create_dir(&build);
371
372 // Add all our dependencies to our cmake paths
373 let mut cmake_prefix_path = Vec::new();
374 for dep in &self.deps {
375 let dep = dep.to_uppercase().replace('-', "_");
376 if let Some(root) = env::var_os(&format!("DEP_{}_ROOT", dep)) {
377 cmake_prefix_path.push(PathBuf::from(root));
378 }
379 }
380 let system_prefix = env::var_os("CMAKE_PREFIX_PATH").unwrap_or(OsString::new());
381 cmake_prefix_path.extend(env::split_paths(&system_prefix).map(|s| s.to_owned()));
382 let cmake_prefix_path = env::join_paths(&cmake_prefix_path).unwrap();
383
384 // Build up the first cmake command to build the build system.
385 let executable = env::var("CMAKE").unwrap_or("cmake".to_owned());
386 let mut cmd = Command::new(&executable);
387
388 if self.verbose_cmake {
389 cmd.arg("-Wdev");
390 cmd.arg("--debug-output");
391 }
392
393 cmd.arg(&self.path).current_dir(&build);
394 let mut is_ninja = false;
395 if let Some(ref generator) = self.generator {
396 is_ninja = generator.to_string_lossy().contains("Ninja");
397 }
398 if target.contains("windows-gnu") {
399 if host.contains("windows") {
400 // On MinGW we need to coerce cmake to not generate a visual
401 // studio build system but instead use makefiles that MinGW can
402 // use to build.
403 if self.generator.is_none() {
404 // If make.exe isn't found, that means we may be using a MinGW
405 // toolchain instead of a MSYS2 toolchain. If neither is found,
406 // the build cannot continue.
407 let has_msys2 = Command::new("make")
408 .arg("--version")
409 .output()
410 .err()
411 .map(|e| e.kind() != ErrorKind::NotFound)
412 .unwrap_or(true);
413 let has_mingw32 = Command::new("mingw32-make")
414 .arg("--version")
415 .output()
416 .err()
417 .map(|e| e.kind() != ErrorKind::NotFound)
418 .unwrap_or(true);
419
420 let generator = match (has_msys2, has_mingw32) {
421 (true, _) => "MSYS Makefiles",
422 (false, true) => "MinGW Makefiles",
423 (false, false) => fail("no valid generator found for GNU toolchain; MSYS or MinGW must be installed")
424 };
425
426 cmd.arg("-G").arg(generator);
427 }
428 } else {
429 // If we're cross compiling onto windows, then set some
430 // variables which will hopefully get things to succeed. Some
431 // systems may need the `windres` or `dlltool` variables set, so
432 // set them if possible.
433 if !self.defined("CMAKE_SYSTEM_NAME") {
434 cmd.arg("-DCMAKE_SYSTEM_NAME=Windows");
435 }
436 if !self.defined("CMAKE_RC_COMPILER") {
437 let exe = find_exe(c_compiler.path());
438 if let Some(name) = exe.file_name().unwrap().to_str() {
439 let name = name.replace("gcc", "windres");
440 let windres = exe.with_file_name(name);
441 if windres.is_file() {
442 let mut arg = OsString::from("-DCMAKE_RC_COMPILER=");
443 arg.push(&windres);
444 cmd.arg(arg);
445 }
446 }
447 }
448 }
449 } else if msvc {
450 // If we're on MSVC we need to be sure to use the right generator or
451 // otherwise we won't get 32/64 bit correct automatically.
452 // This also guarantees that NMake generator isn't chosen implicitly.
453 let using_nmake_generator;
454 if self.generator.is_none() {
455 cmd.arg("-G").arg(self.visual_studio_generator(&target));
456 using_nmake_generator = false;
457 } else {
458 using_nmake_generator = self.generator.as_ref().unwrap() == "NMake Makefiles";
459 }
460 if !is_ninja && !using_nmake_generator {
461 if target.contains("x86_64") {
462 cmd.arg("-Thost=x64");
463 cmd.arg("-Ax64");
464 } else if target.contains("thumbv7a") {
465 cmd.arg("-Thost=x64");
466 cmd.arg("-Aarm");
467 } else if target.contains("aarch64") {
468 cmd.arg("-Thost=x64");
469 cmd.arg("-AARM64");
470 } else if target.contains("i686") {
471 use cc::windows_registry::{find_vs_version, VsVers};
472 match find_vs_version() {
473 Ok(VsVers::Vs16) => {
474 // 32-bit x86 toolset used to be the default for all hosts,
475 // but Visual Studio 2019 changed the default toolset to match the host,
476 // so we need to manually override it for x86 targets
477 cmd.arg("-Thost=x86");
478 cmd.arg("-AWin32");
479 }
480 _ => {}
481 };
482 } else {
483 panic!("unsupported msvc target: {}", target);
484 }
485 }
486 } else if target.contains("redox") {
487 if !self.defined("CMAKE_SYSTEM_NAME") {
488 cmd.arg("-DCMAKE_SYSTEM_NAME=Generic");
489 }
490 } else if target.contains("solaris") {
491 if !self.defined("CMAKE_SYSTEM_NAME") {
492 cmd.arg("-DCMAKE_SYSTEM_NAME=SunOS");
493 }
494 }
495 if let Some(ref generator) = self.generator {
496 cmd.arg("-G").arg(generator);
497 }
498 let profile = self.profile.clone().unwrap_or_else(|| {
499 // Automatically set the `CMAKE_BUILD_TYPE` if the user did not
500 // specify one.
501
502 // Determine Rust's profile, optimization level, and debug info:
503 #[derive(PartialEq)]
504 enum RustProfile {
505 Debug,
506 Release,
507 }
508 #[derive(PartialEq, Debug)]
509 enum OptLevel {
510 Debug,
511 Release,
512 Size,
513 }
514
515 let rust_profile = match &getenv_unwrap("PROFILE")[..] {
516 "debug" => RustProfile::Debug,
517 "release" | "bench" => RustProfile::Release,
518 unknown => {
519 eprintln!(
520 "Warning: unknown Rust profile={}; defaulting to a release build.",
521 unknown
522 );
523 RustProfile::Release
524 }
525 };
526
527 let opt_level = match &getenv_unwrap("OPT_LEVEL")[..] {
528 "0" => OptLevel::Debug,
529 "1" | "2" | "3" => OptLevel::Release,
530 "s" | "z" => OptLevel::Size,
531 unknown => {
532 let default_opt_level = match rust_profile {
533 RustProfile::Debug => OptLevel::Debug,
534 RustProfile::Release => OptLevel::Release,
535 };
536 eprintln!(
537 "Warning: unknown opt-level={}; defaulting to a {:?} build.",
538 unknown, default_opt_level
539 );
540 default_opt_level
541 }
542 };
543
544 let debug_info: bool = match &getenv_unwrap("DEBUG")[..] {
545 "false" => false,
546 "true" => true,
547 unknown => {
548 eprintln!("Warning: unknown debug={}; defaulting to `true`.", unknown);
549 true
550 }
551 };
552
553 match (opt_level, debug_info) {
554 (OptLevel::Debug, _) => "Debug",
555 (OptLevel::Release, false) => "Release",
556 (OptLevel::Release, true) => "RelWithDebInfo",
557 (OptLevel::Size, _) => "MinSizeRel",
558 }
559 .to_string()
560 });
561 for &(ref k, ref v) in &self.defines {
562 let mut os = OsString::from("-D");
563 os.push(k);
564 os.push("=");
565 os.push(v);
566 cmd.arg(os);
567 }
568
569 if !self.defined("CMAKE_INSTALL_PREFIX") {
570 let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX=");
571 dstflag.push(&dst);
572 cmd.arg(dstflag);
573 }
574
575 let build_type = self
576 .defines
577 .iter()
578 .find(|&&(ref a, _)| a == "CMAKE_BUILD_TYPE")
579 .map(|x| x.1.to_str().unwrap())
580 .unwrap_or(&profile);
581 let build_type_upcase = build_type
582 .chars()
583 .flat_map(|c| c.to_uppercase())
584 .collect::<String>();
585
586 {
587 // let cmake deal with optimization/debuginfo
588 let skip_arg = |arg: &OsStr| match arg.to_str() {
589 Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g",
590 None => false,
591 };
592 let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| {
593 let flag_var = format!("CMAKE_{}_FLAGS", kind);
594 let tool_var = format!("CMAKE_{}_COMPILER", kind);
595 if !self.defined(&flag_var) {
596 let mut flagsflag = OsString::from("-D");
597 flagsflag.push(&flag_var);
598 flagsflag.push("=");
599 flagsflag.push(extra);
600 for arg in compiler.args() {
601 if skip_arg(arg) {
602 continue;
603 }
604 flagsflag.push(" ");
605 flagsflag.push(arg);
606 }
607 cmd.arg(flagsflag);
608 }
609
610 // The visual studio generator apparently doesn't respect
611 // `CMAKE_C_FLAGS` but does respect `CMAKE_C_FLAGS_RELEASE` and
612 // such. We need to communicate /MD vs /MT, so set those vars
613 // here.
614 //
615 // Note that for other generators, though, this *overrides*
616 // things like the optimization flags, which is bad.
617 if self.generator.is_none() && msvc {
618 let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase);
619 if !self.defined(&flag_var_alt) {
620 let mut flagsflag = OsString::from("-D");
621 flagsflag.push(&flag_var_alt);
622 flagsflag.push("=");
623 flagsflag.push(extra);
624 for arg in compiler.args() {
625 if skip_arg(arg) {
626 continue;
627 }
628 flagsflag.push(" ");
629 flagsflag.push(arg);
630 }
631 cmd.arg(flagsflag);
632 }
633 }
634
635 // Apparently cmake likes to have an absolute path to the
636 // compiler as otherwise it sometimes thinks that this variable
637 // changed as it thinks the found compiler, /usr/bin/cc,
638 // differs from the specified compiler, cc. Not entirely sure
639 // what's up, but at least this means cmake doesn't get
640 // confused?
641 //
642 // Also specify this on Windows only if we use MSVC with Ninja,
643 // as it's not needed for MSVC with Visual Studio generators and
644 // for MinGW it doesn't really vary.
645 if !self.defined("CMAKE_TOOLCHAIN_FILE")
646 && !self.defined(&tool_var)
647 && (env::consts::FAMILY != "windows" || (msvc && is_ninja))
648 {
649 let mut ccompiler = OsString::from("-D");
650 ccompiler.push(&tool_var);
651 ccompiler.push("=");
652 ccompiler.push(find_exe(compiler.path()));
653 #[cfg(windows)]
654 {
655 // CMake doesn't like unescaped `\`s in compiler paths
656 // so we either have to escape them or replace with `/`s.
657 use std::os::windows::ffi::{OsStrExt, OsStringExt};
658 let wchars = ccompiler
659 .encode_wide()
660 .map(|wchar| {
661 if wchar == b'\\' as u16 {
662 '/' as u16
663 } else {
664 wchar
665 }
666 })
667 .collect::<Vec<_>>();
668 ccompiler = OsString::from_wide(&wchars);
669 }
670 cmd.arg(ccompiler);
671 }
672 };
673
674 set_compiler("C", &c_compiler, &self.cflags);
675 set_compiler("CXX", &cxx_compiler, &self.cxxflags);
676 set_compiler("ASM", &asm_compiler, &self.asmflags);
677 }
678
679 if !self.defined("CMAKE_BUILD_TYPE") {
680 cmd.arg(&format!("-DCMAKE_BUILD_TYPE={}", profile));
681 }
682
683 if self.verbose_make {
684 cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON");
685 }
686
687 if !self.defined("CMAKE_TOOLCHAIN_FILE") {
688 if let Ok(s) = env::var("CMAKE_TOOLCHAIN_FILE") {
689 cmd.arg(&format!("-DCMAKE_TOOLCHAIN_FILE={}", s));
690 }
691 }
692
693 for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
694 cmd.env(k, v);
695 }
696
697 if self.always_configure || !build.join("CMakeCache.txt").exists() {
698 run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake");
699 } else {
700 println!("CMake project was already configured. Skipping configuration step.");
701 }
702
703 let mut makeflags = None;
704 let mut parallel_flags = None;
705
706 if let Ok(s) = env::var("NUM_JOBS") {
707 match self.generator.as_ref().map(|g| g.to_string_lossy()) {
708 Some(ref g) if g.contains("Ninja") => {
709 parallel_flags = Some(format!("-j{}", s));
710 }
711 Some(ref g) if g.contains("Visual Studio") => {
712 parallel_flags = Some(format!("/m:{}", s));
713 }
714 Some(ref g) if g.contains("NMake") => {
715 // NMake creates `Makefile`s, but doesn't understand `-jN`.
716 }
717 _ if fs::metadata(&build.join("Makefile")).is_ok() => {
718 match env::var_os("CARGO_MAKEFLAGS") {
719 // Only do this on non-windows and non-bsd
720 // On Windows, we could be invoking make instead of
721 // mingw32-make which doesn't work with our jobserver
722 // bsdmake also does not work with our job server
723 Some(ref s)
724 if !(cfg!(windows)
725 || cfg!(target_os = "openbsd")
726 || cfg!(target_os = "netbsd")
727 || cfg!(target_os = "freebsd")
728 || cfg!(target_os = "bitrig")
729 || cfg!(target_os = "dragonflybsd")) =>
730 {
731 makeflags = Some(s.clone())
732 }
733
734 // This looks like `make`, let's hope it understands `-jN`.
735 _ => makeflags = Some(OsString::from(format!("-j{}", s))),
736 }
737 }
738 _ => {}
739 }
740 }
741
742 // And build!
743 let target = self.cmake_target.clone().unwrap_or("install".to_string());
744 let mut cmd = Command::new(&executable);
745 for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
746 cmd.env(k, v);
747 }
748
749 if let Some(flags) = makeflags {
750 cmd.env("MAKEFLAGS", flags);
751 }
752
753 cmd.arg("--build").arg(".");
754
755 if !self.no_build_target {
756 cmd.arg("--target").arg(target);
757 }
758
759 cmd.arg("--config")
760 .arg(&profile)
761 .arg("--")
762 .args(&self.build_args)
763 .current_dir(&build);
764
765 if let Some(flags) = parallel_flags {
766 cmd.arg(flags);
767 }
768
769 run(&mut cmd, "cmake");
770
771 println!("cargo:root={}", dst.display());
772 return dst;
773 }
774
775 fn visual_studio_generator(&self, target: &str) -> String {
776 use cc::windows_registry::{find_vs_version, VsVers};
777
778 let base = match find_vs_version() {
779 Ok(VsVers::Vs16) => "Visual Studio 16 2019",
780 Ok(VsVers::Vs15) => "Visual Studio 15 2017",
781 Ok(VsVers::Vs14) => "Visual Studio 14 2015",
782 Ok(VsVers::Vs12) => "Visual Studio 12 2013",
783 Ok(_) => panic!(
784 "Visual studio version detected but this crate \
785 doesn't know how to generate cmake files for it, \
786 can the `cmake` crate be updated?"
787 ),
788 Err(msg) => panic!(msg),
789 };
790 if ["i686", "x86_64", "thumbv7a", "aarch64"]
791 .iter()
792 .any(|t| target.contains(t))
793 {
794 base.to_string()
795 } else {
796 panic!("unsupported msvc target: {}", target);
797 }
798 }
799
800 fn defined(&self, var: &str) -> bool {
801 self.defines.iter().any(|&(ref a, _)| a == var)
802 }
803
804 // If a cmake project has previously been built (e.g. CMakeCache.txt already
805 // exists), then cmake will choke if the source directory for the original
806 // project being built has changed. Detect this situation through the
807 // `CMAKE_HOME_DIRECTORY` variable that cmake emits and if it doesn't match
808 // we blow away the build directory and start from scratch (the recommended
809 // solution apparently [1]).
810 //
811 // [1]: https://cmake.org/pipermail/cmake/2012-August/051545.html
812 fn maybe_clear(&self, dir: &Path) {
813 // CMake will apparently store canonicalized paths which normally
814 // isn't relevant to us but we canonicalize it here to ensure
815 // we're both checking the same thing.
816 let path = fs::canonicalize(&self.path).unwrap_or(self.path.clone());
817 let mut f = match File::open(dir.join("CMakeCache.txt")) {
818 Ok(f) => f,
819 Err(..) => return,
820 };
821 let mut u8contents = Vec::new();
822 match f.read_to_end(&mut u8contents) {
823 Ok(f) => f,
824 Err(..) => return,
825 };
826 let contents = String::from_utf8_lossy(&u8contents);
827 drop(f);
828 for line in contents.lines() {
829 if line.starts_with("CMAKE_HOME_DIRECTORY") {
830 let needs_cleanup = match line.split('=').next_back() {
831 Some(cmake_home) => fs::canonicalize(cmake_home)
832 .ok()
833 .map(|cmake_home| cmake_home != path)
834 .unwrap_or(true),
835 None => true,
836 };
837 if needs_cleanup {
838 println!(
839 "detected home dir change, cleaning out entire build \
840 directory"
841 );
842 fs::remove_dir_all(dir).unwrap();
843 }
844 break;
845 }
846 }
847 }
848 }
849
850 fn run(cmd: &mut Command, program: &str) {
851 println!("running: {:?}", cmd);
852 let status = match cmd.status() {
853 Ok(status) => status,
854 Err(ref e) if e.kind() == ErrorKind::NotFound => {
855 fail(&format!(
856 "failed to execute command: {}\nis `{}` not installed?",
857 e, program
858 ));
859 }
860 Err(e) => fail(&format!("failed to execute command: {}", e)),
861 };
862 if !status.success() {
863 fail(&format!(
864 "command did not execute successfully, got: {}",
865 status
866 ));
867 }
868 }
869
870 fn find_exe(path: &Path) -> PathBuf {
871 env::split_paths(&env::var_os("PATH").unwrap_or(OsString::new()))
872 .map(|p| p.join(path))
873 .find(|p| fs::metadata(p).is_ok())
874 .unwrap_or(path.to_owned())
875 }
876
877 fn getenv_unwrap(v: &str) -> String {
878 match env::var(v) {
879 Ok(s) => s,
880 Err(..) => fail(&format!("environment variable `{}` not defined", v)),
881 }
882 }
883
884 fn fail(s: &str) -> ! {
885 panic!("\n{}\n\nbuild script failed, must exit now", s)
886 }