1 //! A build dependency for running `cmake` to build a native library
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
9 //! The builder-style configuration allows for various variables and such to be
10 //! passed down into the build as well.
14 //! Add this to your `Cargo.toml`:
17 //! [build-dependencies]
26 //! // Builds the project in the directory located in `libfoo`, installing it
28 //! let dst = cmake::build("libfoo");
30 //! println!("cargo:rustc-link-search=native={}", dst.display());
31 //! println!("cargo:rustc-link-lib=static=foo");
35 //! use cmake::Config;
37 //! let dst = Config::new("libfoo")
38 //! .define("FOO", "BAR")
41 //! println!("cargo:rustc-link-search=native={}", dst.display());
42 //! println!("cargo:rustc-link-lib=static=foo");
45 #![deny(missing_docs)]
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
;
57 /// Builder style configuration for a pending CMake build.
60 generator
: Option
<OsString
>,
64 defines
: Vec
<(OsString
, OsString
)>,
66 target
: 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
>,
75 always_configure
: bool
,
76 no_build_target
: bool
,
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.
90 /// // Builds the project in the directory located in `libfoo`, installing it
92 /// let dst = cmake::build("libfoo");
94 /// println!("cargo:rustc-link-search=native={}", dst.display());
95 /// println!("cargo:rustc-link-lib=static=foo");
98 pub fn build
<P
: AsRef
<Path
>>(path
: P
) -> PathBuf
{
99 Config
::new(path
.as_ref()).build()
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
{
107 path
: env
::current_dir().unwrap().join(path
),
109 cflags
: OsString
::new(),
110 cxxflags
: OsString
::new(),
111 asmflags
: OsString
::new(),
118 build_args
: Vec
::new(),
123 always_configure
: true,
124 no_build_target
: false,
125 verbose_cmake
: false,
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
);
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());
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());
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());
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());
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
174 .push((k
.as_ref().to_owned(), v
.as_ref().to_owned()));
178 /// Registers a dependency for this compilation on the native library built
179 /// by Cargo previously.
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());
188 /// Sets the target triple for this compilation.
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());
197 /// Disables the cmake target option for this compilation.
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
;
205 /// Sets the host triple for this compilation.
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());
214 /// Sets the output directory for this compilation.
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());
223 /// Sets the `CMAKE_BUILD_TYPE=build_type` variable.
225 /// By default, this value is automatically inferred from Rust's compilation
226 /// profile as follows:
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());
238 /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools.
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
);
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());
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
260 .push((key
.as_ref().to_owned(), value
.as_ref().to_owned()));
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());
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
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;
282 /// Forces CMake to always run before building the custom target.
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
;
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
;
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())
310 /// Run this configuration, compiling the library with all the configured
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() {
319 let mut t
= getenv_unwrap("TARGET");
320 if t
.ends_with("-darwin") && self.uses_cxx11
{
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();
331 .cargo_metadata(false)
336 .no_default_flags(ndk
);
338 c_cfg
.target(&target
);
340 let mut cxx_cfg
= cc
::Build
::new();
342 .cargo_metadata(false)
348 .no_default_flags(ndk
);
350 cxx_cfg
.target(&target
);
352 if let Some(static_crt
) = self.static_crt
{
353 c_cfg
.static_crt(static_crt
);
354 cxx_cfg
.static_crt(static_crt
);
356 if let Some(explicit_flag
) = self.pic
{
357 c_cfg
.pic(explicit_flag
);
358 cxx_cfg
.pic(explicit_flag
);
360 let c_compiler
= c_cfg
.get_compiler();
361 let cxx_compiler
= cxx_cfg
.get_compiler();
362 let asm_compiler
= c_cfg
.get_compiler();
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
);
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
));
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();
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
);
388 if self.verbose_cmake
{
390 cmd
.arg("--debug-output");
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");
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
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")
411 .map(|e
| e
.kind() != ErrorKind
::NotFound
)
413 let has_mingw32
= Command
::new("mingw32-make")
417 .map(|e
| e
.kind() != ErrorKind
::NotFound
)
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")
426 cmd
.arg("-G").arg(generator
);
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");
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=");
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;
458 using_nmake_generator
= self.generator
.as_ref().unwrap() == "NMake Makefiles";
460 if !is_ninja
&& !using_nmake_generator
{
461 if target
.contains("x86_64") {
462 cmd
.arg("-Thost=x64");
464 } else if target
.contains("thumbv7a") {
465 cmd
.arg("-Thost=x64");
467 } else if target
.contains("aarch64") {
468 cmd
.arg("-Thost=x64");
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");
483 panic
!("unsupported msvc target: {}", target
);
486 } else if target
.contains("redox") {
487 if !self.defined("CMAKE_SYSTEM_NAME") {
488 cmd
.arg("-DCMAKE_SYSTEM_NAME=Generic");
490 } else if target
.contains("solaris") {
491 if !self.defined("CMAKE_SYSTEM_NAME") {
492 cmd
.arg("-DCMAKE_SYSTEM_NAME=SunOS");
495 if let Some(ref generator
) = self.generator
{
496 cmd
.arg("-G").arg(generator
);
498 let profile
= self.profile
.clone().unwrap_or_else(|| {
499 // Automatically set the `CMAKE_BUILD_TYPE` if the user did not
502 // Determine Rust's profile, optimization level, and debug info:
508 #[derive(PartialEq, Debug)]
515 let rust_profile
= match &getenv_unwrap("PROFILE")[..] {
516 "debug" => RustProfile
::Debug
,
517 "release" | "bench" => RustProfile
::Release
,
520 "Warning: unknown Rust profile={}; defaulting to a release build.",
527 let opt_level
= match &getenv_unwrap("OPT_LEVEL")[..] {
528 "0" => OptLevel
::Debug
,
529 "1" | "2" | "3" => OptLevel
::Release
,
530 "s" | "z" => OptLevel
::Size
,
532 let default_opt_level
= match rust_profile
{
533 RustProfile
::Debug
=> OptLevel
::Debug
,
534 RustProfile
::Release
=> OptLevel
::Release
,
537 "Warning: unknown opt-level={}; defaulting to a {:?} build.",
538 unknown
, default_opt_level
544 let debug_info
: bool
= match &getenv_unwrap("DEBUG")[..] {
548 eprintln
!("Warning: unknown debug={}; defaulting to `true`.", unknown
);
553 match (opt_level
, debug_info
) {
554 (OptLevel
::Debug
, _
) => "Debug",
555 (OptLevel
::Release
, false) => "Release",
556 (OptLevel
::Release
, true) => "RelWithDebInfo",
557 (OptLevel
::Size
, _
) => "MinSizeRel",
561 for &(ref k
, ref v
) in &self.defines
{
562 let mut os
= OsString
::from("-D");
569 if !self.defined("CMAKE_INSTALL_PREFIX") {
570 let mut dstflag
= OsString
::from("-DCMAKE_INSTALL_PREFIX=");
575 let build_type
= self
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
583 .flat_map(|c
| c
.to_uppercase())
584 .collect
::<String
>();
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",
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
);
599 flagsflag
.push(extra
);
600 for arg
in compiler
.args() {
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
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
);
623 flagsflag
.push(extra
);
624 for arg
in compiler
.args() {
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
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
))
649 let mut ccompiler
= OsString
::from("-D");
650 ccompiler
.push(&tool_var
);
652 ccompiler
.push(find_exe(compiler
.path()));
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
661 if wchar
== b'
\\'
as u16 {
667 .collect
::<Vec
<_
>>();
668 ccompiler
= OsString
::from_wide(&wchars
);
674 set_compiler("C", &c_compiler
, &self.cflags
);
675 set_compiler("CXX", &cxx_compiler
, &self.cxxflags
);
676 set_compiler("ASM", &asm_compiler
, &self.asmflags
);
679 if !self.defined("CMAKE_BUILD_TYPE") {
680 cmd
.arg(&format
!("-DCMAKE_BUILD_TYPE={}", profile
));
683 if self.verbose_make
{
684 cmd
.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON");
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
));
693 for &(ref k
, ref v
) in c_compiler
.env().iter().chain(&self.env
) {
697 if self.always_configure
|| !build
.join("CMakeCache.txt").exists() {
698 run(cmd
.env("CMAKE_PREFIX_PATH", cmake_prefix_path
), "cmake");
700 println
!("CMake project was already configured. Skipping configuration step.");
703 let mut makeflags
= None
;
704 let mut parallel_flags
= None
;
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
));
711 Some(ref g
) if g
.contains("Visual Studio") => {
712 parallel_flags
= Some(format
!("/m:{}", s
));
714 Some(ref g
) if g
.contains("NMake") => {
715 // NMake creates `Makefile`s, but doesn't understand `-jN`.
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
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")) =>
731 makeflags
= Some(s
.clone())
734 // This looks like `make`, let's hope it understands `-jN`.
735 _
=> makeflags
= Some(OsString
::from(format
!("-j{}", s
))),
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
) {
749 if let Some(flags
) = makeflags
{
750 cmd
.env("MAKEFLAGS", flags
);
753 cmd
.arg("--build").arg(".");
755 if !self.no_build_target
{
756 cmd
.arg("--target").arg(target
);
762 .args(&self.build_args
)
763 .current_dir(&build
);
765 if let Some(flags
) = parallel_flags
{
769 run(&mut cmd
, "cmake");
771 println
!("cargo:root={}", dst
.display());
775 fn visual_studio_generator(&self, target
: &str) -> String
{
776 use cc
::windows_registry
::{find_vs_version, VsVers}
;
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",
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?"
788 Err(msg
) => panic
!(msg
),
790 if ["i686", "x86_64", "thumbv7a", "aarch64"]
792 .any(|t
| target
.contains(t
))
796 panic
!("unsupported msvc target: {}", target
);
800 fn defined(&self, var
: &str) -> bool
{
801 self.defines
.iter().any(|&(ref a
, _
)| a
== var
)
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]).
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")) {
821 let mut u8contents
= Vec
::new();
822 match f
.read_to_end(&mut u8contents
) {
826 let contents
= String
::from_utf8_lossy(&u8contents
);
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
)
833 .map(|cmake_home
| cmake_home
!= path
)
839 "detected home dir change, cleaning out entire build \
842 fs
::remove_dir_all(dir
).unwrap();
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
=> {
856 "failed to execute command: {}\nis `{}` not installed?",
860 Err(e
) => fail(&format
!("failed to execute command: {}", e
)),
862 if !status
.success() {
864 "command did not execute successfully, got: {}",
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())
877 fn getenv_unwrap(v
: &str) -> String
{
880 Err(..) => fail(&format
!("environment variable `{}` not defined", v
)),
884 fn fail(s
: &str) -> ! {
885 panic
!("\n{}\n\nbuild script failed, must exit now", s
)