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