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