1#![deny(missing_docs)]
46
47extern crate cc;
48
49use std::collections::HashMap;
50use std::env;
51use std::ffi::{OsStr, OsString};
52use std::fs::{self, File};
53use std::io::prelude::*;
54use std::io::ErrorKind;
55use std::path::{Path, PathBuf};
56use std::process::Command;
57
58pub struct Config {
60 path: PathBuf,
61 generator: Option<OsString>,
62 generator_toolset: Option<OsString>,
63 cflags: OsString,
64 cxxflags: OsString,
65 asmflags: OsString,
66 defines: Vec<(OsString, OsString)>,
67 deps: Vec<String>,
68 target: Option<String>,
69 host: Option<String>,
70 out_dir: Option<PathBuf>,
71 profile: Option<String>,
72 configure_args: Vec<OsString>,
73 build_args: Vec<OsString>,
74 cmake_target: Option<String>,
75 env: Vec<(OsString, OsString)>,
76 static_crt: Option<bool>,
77 uses_cxx11: bool,
78 always_configure: bool,
79 no_build_target: bool,
80 no_default_flags: bool,
81 verbose_cmake: bool,
82 verbose_make: bool,
83 pic: Option<bool>,
84 c_cfg: Option<cc::Build>,
85 cxx_cfg: Option<cc::Build>,
86 env_cache: HashMap<String, Option<OsString>>,
87}
88
89pub fn build<P: AsRef<Path>>(path: P) -> PathBuf {
106 Config::new(path.as_ref()).build()
107}
108
109impl Config {
110 pub fn get_profile(&self) -> &str {
118 if let Some(profile) = self.profile.as_ref() {
119 profile
120 } else {
121 #[derive(PartialEq)]
123 enum RustProfile {
124 Debug,
125 Release,
126 }
127 #[derive(PartialEq, Debug)]
128 enum OptLevel {
129 Debug,
130 Release,
131 Size,
132 }
133
134 let rust_profile = match &getenv_unwrap("PROFILE")[..] {
135 "debug" => RustProfile::Debug,
136 "release" | "bench" => RustProfile::Release,
137 unknown => {
138 eprintln!(
139 "Warning: unknown Rust profile={}; defaulting to a release build.",
140 unknown
141 );
142 RustProfile::Release
143 }
144 };
145
146 let opt_level = match &getenv_unwrap("OPT_LEVEL")[..] {
147 "0" => OptLevel::Debug,
148 "1" | "2" | "3" => OptLevel::Release,
149 "s" | "z" => OptLevel::Size,
150 unknown => {
151 let default_opt_level = match rust_profile {
152 RustProfile::Debug => OptLevel::Debug,
153 RustProfile::Release => OptLevel::Release,
154 };
155 eprintln!(
156 "Warning: unknown opt-level={}; defaulting to a {:?} build.",
157 unknown, default_opt_level
158 );
159 default_opt_level
160 }
161 };
162
163 let debug_info: bool = match &getenv_unwrap("DEBUG")[..] {
164 "false" => false,
165 "true" => true,
166 unknown => {
167 eprintln!("Warning: unknown debug={}; defaulting to `true`.", unknown);
168 true
169 }
170 };
171
172 match (opt_level, debug_info) {
173 (OptLevel::Debug, _) => "Debug",
174 (OptLevel::Release, false) => "Release",
175 (OptLevel::Release, true) => "RelWithDebInfo",
176 (OptLevel::Size, _) => "MinSizeRel",
177 }
178 }
179 }
180
181 pub fn new<P: AsRef<Path>>(path: P) -> Config {
184 Config {
185 path: env::current_dir().unwrap().join(path),
186 generator: None,
187 generator_toolset: None,
188 no_default_flags: false,
189 cflags: OsString::new(),
190 cxxflags: OsString::new(),
191 asmflags: OsString::new(),
192 defines: Vec::new(),
193 deps: Vec::new(),
194 profile: None,
195 out_dir: None,
196 target: None,
197 host: None,
198 configure_args: Vec::new(),
199 build_args: Vec::new(),
200 cmake_target: None,
201 env: Vec::new(),
202 static_crt: None,
203 uses_cxx11: false,
204 always_configure: true,
205 no_build_target: false,
206 verbose_cmake: false,
207 verbose_make: false,
208 pic: None,
209 c_cfg: None,
210 cxx_cfg: None,
211 env_cache: HashMap::new(),
212 }
213 }
214
215 pub fn pic(&mut self, explicit_flag: bool) -> &mut Config {
217 self.pic = Some(explicit_flag);
218 self
219 }
220
221 pub fn generator<T: AsRef<OsStr>>(&mut self, generator: T) -> &mut Config {
227 self.generator = Some(generator.as_ref().to_owned());
228 self
229 }
230
231 pub fn generator_toolset<T: AsRef<OsStr>>(&mut self, toolset_name: T) -> &mut Config {
236 self.generator_toolset = Some(toolset_name.as_ref().to_owned());
237 self
238 }
239
240 pub fn cflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
243 self.cflags.push(" ");
244 self.cflags.push(flag.as_ref());
245 self
246 }
247
248 pub fn cxxflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
251 self.cxxflags.push(" ");
252 self.cxxflags.push(flag.as_ref());
253 self
254 }
255
256 pub fn asmflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
259 self.asmflags.push(" ");
260 self.asmflags.push(flag.as_ref());
261 self
262 }
263
264 pub fn define<K, V>(&mut self, k: K, v: V) -> &mut Config
266 where
267 K: AsRef<OsStr>,
268 V: AsRef<OsStr>,
269 {
270 self.defines
271 .push((k.as_ref().to_owned(), v.as_ref().to_owned()));
272 self
273 }
274
275 pub fn register_dep(&mut self, dep: &str) -> &mut Config {
284 self.deps.push(dep.to_string());
285 self
286 }
287
288 pub fn target(&mut self, target: &str) -> &mut Config {
293 self.target = Some(target.to_string());
294 self
295 }
296
297 pub fn no_build_target(&mut self, no_build_target: bool) -> &mut Config {
301 self.no_build_target = no_build_target;
302 self
303 }
304
305 pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Config {
308 self.no_default_flags = no_default_flags;
309 self
310 }
311
312 pub fn host(&mut self, host: &str) -> &mut Config {
317 self.host = Some(host.to_string());
318 self
319 }
320
321 pub fn out_dir<P: AsRef<Path>>(&mut self, out: P) -> &mut Config {
326 self.out_dir = Some(out.as_ref().to_path_buf());
327 self
328 }
329
330 pub fn profile(&mut self, profile: &str) -> &mut Config {
341 self.profile = Some(profile.to_string());
342 self
343 }
344
345 pub fn static_crt(&mut self, static_crt: bool) -> &mut Config {
349 self.static_crt = Some(static_crt);
350 self
351 }
352
353 pub fn configure_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut Config {
355 self.configure_args.push(arg.as_ref().to_owned());
356 self
357 }
358
359 pub fn build_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut Config {
361 self.build_args.push(arg.as_ref().to_owned());
362 self
363 }
364
365 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Config
368 where
369 K: AsRef<OsStr>,
370 V: AsRef<OsStr>,
371 {
372 self.env
373 .push((key.as_ref().to_owned(), value.as_ref().to_owned()));
374 self
375 }
376
377 pub fn build_target(&mut self, target: &str) -> &mut Config {
380 self.cmake_target = Some(target.to_string());
381 self
382 }
383
384 #[deprecated = "no longer does anything, C++ is determined based on `cc::Build`, and the macOS issue has been fixed upstream"]
391 pub fn uses_cxx11(&mut self) -> &mut Config {
392 self.uses_cxx11 = true;
393 self
394 }
395
396 pub fn always_configure(&mut self, always_configure: bool) -> &mut Config {
401 self.always_configure = always_configure;
402 self
403 }
404
405 pub fn very_verbose(&mut self, value: bool) -> &mut Config {
407 self.verbose_cmake = value;
408 self.verbose_make = value;
409 self
410 }
411
412 fn uses_android_ndk(&self) -> bool {
415 self.defined("ANDROID_ABI")
418 && self.defines.iter().any(|(flag, value)| {
419 flag == "CMAKE_TOOLCHAIN_FILE"
420 && Path::new(value).file_name() == Some("android.toolchain.cmake".as_ref())
421 })
422 }
423
424 pub fn init_c_cfg(&mut self, c_cfg: cc::Build) -> &mut Config {
426 self.c_cfg = Some(c_cfg);
427 self
428 }
429
430 pub fn init_cxx_cfg(&mut self, cxx_cfg: cc::Build) -> &mut Config {
432 self.cxx_cfg = Some(cxx_cfg);
433 self
434 }
435
436 pub fn build(&mut self) -> PathBuf {
442 let target = match self.target.clone() {
443 Some(t) => t,
444 None => getenv_unwrap("TARGET"),
445 };
446 let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
447
448 if !self.defined("CMAKE_TOOLCHAIN_FILE") {
451 if let Some(s) = self.getenv_target_os("CMAKE_TOOLCHAIN_FILE") {
452 self.define("CMAKE_TOOLCHAIN_FILE", s);
453 } else if target.contains("redox") {
454 if !self.defined("CMAKE_SYSTEM_NAME") {
455 self.define("CMAKE_SYSTEM_NAME", "Generic");
456 }
457 } else if target != host && !self.defined("CMAKE_SYSTEM_NAME") {
458 let os = getenv_unwrap("CARGO_CFG_TARGET_OS");
460 let arch = getenv_unwrap("CARGO_CFG_TARGET_ARCH");
461 let (system_name, system_processor) = match (os.as_str(), arch.as_str()) {
467 ("android", "arm") => ("Android", "armv7-a"),
468 ("android", "x86") => ("Android", "i686"),
469 ("android", arch) => ("Android", arch),
470 ("dragonfly", arch) => ("DragonFly", arch),
471 ("macos", "aarch64") => ("Darwin", "arm64"),
472 ("macos", arch) => ("Darwin", arch),
473 ("freebsd", "x86_64") => ("FreeBSD", "amd64"),
474 ("freebsd", arch) => ("FreeBSD", arch),
475 ("fuchsia", arch) => ("Fuchsia", arch),
476 ("haiku", arch) => ("Haiku", arch),
477 ("ios", "aarch64") => ("iOS", "arm64"),
478 ("ios", arch) => ("iOS", arch),
479 ("linux", arch) => {
480 let name = "Linux";
481 match arch {
482 "powerpc" => (name, "ppc"),
483 "powerpc64" => (name, "ppc64"),
484 "powerpc64le" => (name, "ppc64le"),
485 _ => (name, arch),
486 }
487 }
488 ("netbsd", arch) => ("NetBSD", arch),
489 ("openbsd", "x86_64") => ("OpenBSD", "amd64"),
490 ("openbsd", arch) => ("OpenBSD", arch),
491 ("solaris", arch) => ("SunOS", arch),
492 ("tvos", "aarch64") => ("tvOS", "arm64"),
493 ("tvos", arch) => ("tvOS", arch),
494 ("visionos", "aarch64") => ("visionOS", "arm64"),
495 ("visionos", arch) => ("visionOS", arch),
496 ("watchos", "aarch64") => ("watchOS", "arm64"),
497 ("watchos", arch) => ("watchOS", arch),
498 ("windows", "x86_64") => ("Windows", "AMD64"),
499 ("windows", "x86") => ("Windows", "X86"),
500 ("windows", "aarch64") => ("Windows", "ARM64"),
501 ("none", arch) => ("Generic", arch),
502 (os, arch) => (os, arch),
504 };
505 self.define("CMAKE_SYSTEM_NAME", system_name);
506 self.define("CMAKE_SYSTEM_PROCESSOR", system_processor);
507 }
508 }
509
510 let generator = self
511 .generator
512 .clone()
513 .or_else(|| self.getenv_target_os("CMAKE_GENERATOR"));
514
515 let msvc = target.contains("msvc");
516 let ndk = self.uses_android_ndk();
517 let mut c_cfg = self.c_cfg.clone().unwrap_or_default();
518 c_cfg
519 .cargo_metadata(false)
520 .cpp(false)
521 .opt_level(0)
522 .debug(false)
523 .warnings(false)
524 .host(&host)
525 .no_default_flags(ndk || self.no_default_flags);
526 if !ndk {
527 c_cfg.target(&target);
528 }
529 let mut cxx_cfg = self.cxx_cfg.clone().unwrap_or_default();
530 cxx_cfg
531 .cargo_metadata(false)
532 .cpp(true)
533 .opt_level(0)
534 .debug(false)
535 .warnings(false)
536 .host(&host)
537 .no_default_flags(ndk || self.no_default_flags);
538 if !ndk {
539 cxx_cfg.target(&target);
540 }
541 if let Some(static_crt) = self.static_crt {
542 c_cfg.static_crt(static_crt);
543 cxx_cfg.static_crt(static_crt);
544 }
545 if let Some(explicit_flag) = self.pic {
546 c_cfg.pic(explicit_flag);
547 cxx_cfg.pic(explicit_flag);
548 }
549 let c_compiler = c_cfg.get_compiler();
550 let cxx_compiler = cxx_cfg.get_compiler();
551 let asm_compiler = c_cfg.get_compiler();
552
553 let dst = self
554 .out_dir
555 .clone()
556 .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR")));
557
558 let build_dir = try_canonicalize(&dst.join("build"));
559
560 self.maybe_clear(&build_dir);
561 let _ = fs::create_dir_all(&build_dir);
562
563 let mut cmake_prefix_path = Vec::new();
565 for dep in &self.deps {
566 let dep = dep.to_uppercase().replace('-', "_");
567 if let Some(root) = env::var_os(format!("DEP_{}_ROOT", dep)) {
568 cmake_prefix_path.push(PathBuf::from(root));
569 }
570 }
571 let system_prefix = self
572 .getenv_target_os("CMAKE_PREFIX_PATH")
573 .unwrap_or_default();
574 cmake_prefix_path.extend(env::split_paths(&system_prefix));
575 let cmake_prefix_path = env::join_paths(&cmake_prefix_path).unwrap();
576
577 let mut cmd = self.cmake_configure_command(&target);
579
580 let version = Version::from_command(cmd.get_program()).unwrap_or_default();
581
582 if self.verbose_cmake {
583 cmd.arg("-Wdev");
584 cmd.arg("--debug-output");
585 }
586
587 cmd.arg(&self.path).current_dir(&build_dir);
588
589 cmd.arg("-B").arg(&build_dir);
590
591 let mut is_ninja = false;
592 if let Some(ref generator) = generator {
593 is_ninja = generator.to_string_lossy().contains("Ninja");
594 }
595 if target.contains("windows-gnu") {
596 if host.contains("windows") {
597 if generator.is_none() {
601 let has_msys2 = Command::new("make")
605 .arg("--version")
606 .output()
607 .err()
608 .map(|e| e.kind() != ErrorKind::NotFound)
609 .unwrap_or(true);
610 let has_mingw32 = Command::new("mingw32-make")
611 .arg("--version")
612 .output()
613 .err()
614 .map(|e| e.kind() != ErrorKind::NotFound)
615 .unwrap_or(true);
616
617 let generator = match (has_msys2, has_mingw32) {
618 (true, _) => "MSYS Makefiles",
619 (false, true) => "MinGW Makefiles",
620 (false, false) => fail("no valid generator found for GNU toolchain; MSYS or MinGW must be installed")
621 };
622
623 cmd.arg("-G").arg(generator);
624 }
625 } else {
626 if !self.defined("CMAKE_RC_COMPILER") {
631 let exe = find_exe(c_compiler.path());
632 if let Some(name) = exe.file_name().unwrap().to_str() {
633 let name = name.replace("gcc", "windres");
634 let windres = exe.with_file_name(name);
635 if windres.is_file() {
636 let mut arg = OsString::from("-DCMAKE_RC_COMPILER=");
637 arg.push(&windres);
638 cmd.arg(arg);
639 }
640 }
641 }
642 }
643 } else if msvc {
644 let using_nmake_generator = if let Some(g) = &generator {
648 g == "NMake Makefiles" || g == "NMake Makefiles JOM"
649 } else {
650 cmd.arg("-G").arg(self.visual_studio_generator(&target));
651 false
652 };
653 if !is_ninja && !using_nmake_generator {
654 if target.contains("x86_64") {
655 if self.generator_toolset.is_none() {
656 cmd.arg("-Thost=x64");
657 }
658 cmd.arg("-Ax64");
659 } else if target.contains("thumbv7a") {
660 if self.generator_toolset.is_none() {
661 cmd.arg("-Thost=x64");
662 }
663 cmd.arg("-Aarm");
664 } else if target.contains("aarch64") {
665 if self.generator_toolset.is_none() {
666 cmd.arg("-Thost=x64");
667 }
668 cmd.arg("-AARM64");
669 } else if target.contains("i686") {
670 if self.generator_toolset.is_none() {
671 cmd.arg("-Thost=x86");
672 }
673 cmd.arg("-AWin32");
674 } else {
675 panic!("unsupported msvc target: {}", target);
676 }
677 }
678 } else if target.contains("darwin") && !self.defined("CMAKE_OSX_ARCHITECTURES") {
679 if target.contains("x86_64") {
680 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=x86_64");
681 } else if target.contains("aarch64") {
682 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=arm64");
683 } else {
684 panic!("unsupported darwin target: {}", target);
685 }
686 }
687 if let Some(ref generator) = generator {
688 cmd.arg("-G").arg(generator);
689 }
690 if let Some(ref generator_toolset) = self.generator_toolset {
691 cmd.arg("-T").arg(generator_toolset);
692 }
693 let profile = self.get_profile().to_string();
694 for (k, v) in &self.defines {
695 let mut os = OsString::from("-D");
696 os.push(k);
697 os.push("=");
698 os.push(v);
699 cmd.arg(os);
700 }
701
702 if !self.defined("CMAKE_INSTALL_PREFIX") {
703 let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX=");
704 dstflag.push(&dst);
705 cmd.arg(dstflag);
706 }
707
708 let build_type = self
709 .defines
710 .iter()
711 .find(|&(a, _)| a == "CMAKE_BUILD_TYPE")
712 .map(|x| x.1.to_str().unwrap())
713 .unwrap_or(&profile);
714 let build_type_upcase = build_type
715 .chars()
716 .flat_map(|c| c.to_uppercase())
717 .collect::<String>();
718
719 {
720 let skip_arg = |arg: &OsStr| match arg.to_str() {
722 Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g",
723 None => false,
724 };
725 let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| {
726 let flag_var = format!("CMAKE_{}_FLAGS", kind);
727 let tool_var = format!("CMAKE_{}_COMPILER", kind);
728 if !self.defined(&flag_var) {
729 let mut flagsflag = OsString::from("-D");
730 flagsflag.push(&flag_var);
731 flagsflag.push("=");
732 flagsflag.push(extra);
733 for arg in compiler.args() {
734 if skip_arg(arg) {
735 continue;
736 }
737 flagsflag.push(" ");
738 flagsflag.push(arg);
739 }
740 cmd.arg(flagsflag);
741 }
742
743 if generator.is_none() && msvc {
751 let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase);
752 if !self.defined(&flag_var_alt) {
753 let mut flagsflag = OsString::from("-D");
754 flagsflag.push(&flag_var_alt);
755 flagsflag.push("=");
756 flagsflag.push(extra);
757 for arg in compiler.args() {
758 if skip_arg(arg) {
759 continue;
760 }
761 flagsflag.push(" ");
762 flagsflag.push(arg);
763 }
764 cmd.arg(flagsflag);
765 }
766 }
767
768 if !self.defined("CMAKE_TOOLCHAIN_FILE")
779 && !self.defined(&tool_var)
780 && (env::consts::FAMILY != "windows" || (msvc && is_ninja))
781 {
782 let mut ccompiler = OsString::from("-D");
783 ccompiler.push(&tool_var);
784 ccompiler.push("=");
785 ccompiler.push(find_exe(compiler.path()));
786 #[cfg(windows)]
787 {
788 use std::os::windows::ffi::{OsStrExt, OsStringExt};
791 let wchars = ccompiler
792 .encode_wide()
793 .map(|wchar| {
794 if wchar == b'\\' as u16 {
795 '/' as u16
796 } else {
797 wchar
798 }
799 })
800 .collect::<Vec<_>>();
801 ccompiler = OsString::from_wide(&wchars);
802 }
803 cmd.arg(ccompiler);
804 }
805 };
806
807 set_compiler("C", &c_compiler, &self.cflags);
808 set_compiler("CXX", &cxx_compiler, &self.cxxflags);
809 set_compiler("ASM", &asm_compiler, &self.asmflags);
810 }
811
812 if !self.defined("CMAKE_BUILD_TYPE") {
813 cmd.arg(format!("-DCMAKE_BUILD_TYPE={}", profile));
814 }
815
816 if self.verbose_make {
817 cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON");
818 }
819
820 for (k, v) in c_compiler.env().iter().chain(&self.env) {
821 cmd.env(k, v);
822 }
823
824 if self.always_configure || !build_dir.join("CMakeCache.txt").exists() {
825 cmd.args(&self.configure_args);
826 run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake");
827 } else {
828 println!("CMake project was already configured. Skipping configuration step.");
829 }
830
831 let mut cmd = self.cmake_build_command(&target);
833 cmd.current_dir(&build_dir);
834
835 for (k, v) in c_compiler.env().iter().chain(&self.env) {
836 cmd.env(k, v);
837 }
838
839 let mut use_jobserver = false;
841 if fs::metadata(build_dir.join("Makefile")).is_ok() {
842 match env::var_os("CARGO_MAKEFLAGS") {
843 Some(ref makeflags)
852 if !(cfg!(windows)
853 || cfg!(target_os = "openbsd")
854 || cfg!(target_os = "netbsd")
855 || cfg!(target_os = "freebsd")
856 || cfg!(target_os = "dragonfly")
857 || (cfg!(target_os = "macos")
858 && !uses_named_pipe_jobserver(makeflags))) =>
859 {
860 use_jobserver = true;
861 cmd.env("MAKEFLAGS", makeflags);
862 }
863 _ => {}
864 }
865 }
866
867 cmd.arg("--build").arg(&build_dir);
868
869 if !self.no_build_target {
870 let target = self
871 .cmake_target
872 .clone()
873 .unwrap_or_else(|| "install".to_string());
874 cmd.arg("--target").arg(target);
875 }
876
877 cmd.arg("--config").arg(&profile);
878
879 if version >= Version::new(3, 12) && !use_jobserver {
882 if let Ok(s) = env::var("NUM_JOBS") {
883 cmd.arg("--parallel").arg(s);
885 }
886 }
887
888 if !&self.build_args.is_empty() {
889 cmd.arg("--").args(&self.build_args);
890 }
891
892 run(&mut cmd, "cmake");
893
894 println!("cargo:root={}", dst.display());
895 dst
896 }
897
898 fn cmake_executable(&mut self) -> OsString {
899 self.getenv_target_os("CMAKE")
900 .unwrap_or_else(|| OsString::from("cmake"))
901 }
902
903 fn cmake_configure_command(&mut self, target: &str) -> Command {
908 if target.contains("emscripten") {
909 let emcmake = self
910 .getenv_target_os("EMCMAKE")
911 .unwrap_or_else(|| OsString::from("emcmake"));
912 let mut cmd = Command::new(emcmake);
913 cmd.arg(self.cmake_executable());
914 cmd
915 } else {
916 Command::new(self.cmake_executable())
917 }
918 }
919
920 fn cmake_build_command(&mut self, target: &str) -> Command {
921 if target.contains("emscripten") {
922 let emmake = self
923 .getenv_target_os("EMMAKE")
924 .unwrap_or_else(|| OsString::from("emmake"));
925 let mut cmd = Command::new(emmake);
926 cmd.arg(self.cmake_executable());
927 cmd
928 } else {
929 Command::new(self.cmake_executable())
930 }
931 }
932
933 fn getenv_os(&mut self, v: &str) -> Option<OsString> {
934 if let Some(val) = self.env_cache.get(v) {
935 return val.clone();
936 }
937 let r = env::var_os(v);
938 println!("{} = {:?}", v, r);
939 self.env_cache.insert(v.to_string(), r.clone());
940 r
941 }
942
943 fn getenv_target_os(&mut self, var_base: &str) -> Option<OsString> {
945 let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
946 let target = self
947 .target
948 .clone()
949 .unwrap_or_else(|| getenv_unwrap("TARGET"));
950
951 let kind = if host == target { "HOST" } else { "TARGET" };
952 let target_u = target.replace('-', "_");
953 self.getenv_os(&format!("{}_{}", var_base, target))
954 .or_else(|| self.getenv_os(&format!("{}_{}", var_base, target_u)))
955 .or_else(|| self.getenv_os(&format!("{}_{}", kind, var_base)))
956 .or_else(|| self.getenv_os(var_base))
957 }
958
959 fn visual_studio_generator(&self, target: &str) -> String {
960 use cc::windows_registry::{find_vs_version, VsVers};
961
962 let base = match find_vs_version() {
963 Ok(VsVers::Vs18) => "Visual Studio 18 2026",
964 Ok(VsVers::Vs17) => "Visual Studio 17 2022",
965 Ok(VsVers::Vs16) => "Visual Studio 16 2019",
966 Ok(VsVers::Vs15) => "Visual Studio 15 2017",
967 Ok(VsVers::Vs14) => "Visual Studio 14 2015",
968 #[allow(deprecated)]
970 Ok(VsVers::Vs12) => "Visual Studio 12 2013",
971 Ok(_) => panic!(
972 "Visual studio version detected but this crate \
973 doesn't know how to generate cmake files for it, \
974 can the `cmake` crate be updated?"
975 ),
976 Err(msg) => panic!("{}", msg),
977 };
978 if ["i686", "x86_64", "thumbv7a", "aarch64"]
979 .iter()
980 .any(|t| target.contains(t))
981 {
982 base.to_string()
983 } else {
984 panic!("unsupported msvc target: {}", target);
985 }
986 }
987
988 fn defined(&self, var: &str) -> bool {
989 self.defines.iter().any(|(a, _)| a == var)
990 }
991
992 fn maybe_clear(&self, dir: &Path) {
1001 let path = try_canonicalize(&self.path);
1005
1006 let mut f = match File::open(dir.join("CMakeCache.txt")) {
1007 Ok(f) => f,
1008 Err(..) => return,
1009 };
1010 let mut u8contents = Vec::new();
1011 match f.read_to_end(&mut u8contents) {
1012 Ok(f) => f,
1013 Err(..) => return,
1014 };
1015 let contents = String::from_utf8_lossy(&u8contents);
1016 drop(f);
1017 for line in contents.lines() {
1018 if line.starts_with("CMAKE_HOME_DIRECTORY") {
1019 let needs_cleanup = match line.split('=').next_back() {
1020 Some(cmake_home) => fs::canonicalize(cmake_home)
1021 .ok()
1022 .map(|cmake_home| cmake_home != path)
1023 .unwrap_or(true),
1024 None => true,
1025 };
1026 if needs_cleanup {
1027 println!(
1028 "detected home dir change, cleaning out entire build \
1029 directory"
1030 );
1031 fs::remove_dir_all(dir).unwrap();
1032 }
1033 break;
1034 }
1035 }
1036 }
1037}
1038
1039#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1040struct Version {
1041 major: u32,
1042 minor: u32,
1043}
1044
1045impl Version {
1046 fn new(major: u32, minor: u32) -> Self {
1047 Self { major, minor }
1048 }
1049
1050 fn parse(s: &str) -> Option<Self> {
1051 let version = s.lines().next()?.strip_prefix("cmake version ")?;
1059 let mut digits = version.splitn(3, '.'); let major = digits.next()?.parse::<u32>().ok()?;
1061 let minor = digits.next()?.parse::<u32>().ok()?;
1062 Some(Version::new(major, minor))
1064 }
1065
1066 fn from_command(executable: &OsStr) -> Option<Self> {
1067 let output = Command::new(executable).arg("--version").output().ok()?;
1068 if !output.status.success() {
1069 return None;
1070 }
1071 let stdout = core::str::from_utf8(&output.stdout).ok()?;
1072 Self::parse(stdout)
1073 }
1074}
1075
1076impl Default for Version {
1077 fn default() -> Self {
1078 Self::new(3, 22)
1082 }
1083}
1084
1085fn run(cmd: &mut Command, program: &str) {
1086 eprintln!("running: {:?}", cmd);
1087 let status = match cmd.status() {
1088 Ok(status) => status,
1089 Err(ref e) if e.kind() == ErrorKind::NotFound => {
1090 fail(&format!(
1091 "failed to execute command: {}\nis `{}` not installed?",
1092 e, program
1093 ));
1094 }
1095 Err(e) => fail(&format!("failed to execute command: {}", e)),
1096 };
1097 if !status.success() {
1098 if status.code() == Some(127) {
1099 fail(&format!(
1100 "command did not execute successfully, got: {}, is `{}` not installed?",
1101 status, program
1102 ));
1103 }
1104 fail(&format!(
1105 "command did not execute successfully, got: {}",
1106 status
1107 ));
1108 }
1109}
1110
1111fn find_exe(path: &Path) -> PathBuf {
1112 env::split_paths(&env::var_os("PATH").unwrap_or_default())
1113 .map(|p| p.join(path))
1114 .find(|p| fs::metadata(p).is_ok())
1115 .unwrap_or_else(|| path.to_owned())
1116}
1117
1118fn getenv_unwrap(v: &str) -> String {
1119 match env::var(v) {
1120 Ok(s) => s,
1121 Err(..) => fail(&format!("environment variable `{}` not defined", v)),
1122 }
1123}
1124
1125fn fail(s: &str) -> ! {
1126 panic!("\n{}\n\nbuild script failed, must exit now", s)
1127}
1128
1129fn uses_named_pipe_jobserver(makeflags: &OsStr) -> bool {
1132 makeflags
1133 .to_string_lossy()
1134 .contains("--jobserver-auth=fifo:")
1137}
1138
1139fn try_canonicalize(path: &Path) -> PathBuf {
1142 let path = path.canonicalize().unwrap_or_else(|_| path.to_owned());
1143 #[cfg(windows)]
1147 {
1148 use std::os::windows::ffi::{OsStrExt, OsStringExt};
1149 let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
1150 if wide.starts_with(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]) {
1151 if wide.get(5..7) == Some(&[b':' as u16, b'\\' as u16]) {
1152 wide.copy_within(4.., 0);
1154 wide.truncate(wide.len() - 4);
1155 } else if wide.get(4..8) == Some(&[b'U' as u16, b'N' as u16, b'C' as u16, b'\\' as u16])
1156 {
1157 wide.copy_within(8.., 2);
1159 wide.truncate(wide.len() - (8 - 2));
1160 }
1161 return OsString::from_wide(&wide).into();
1162 }
1163 }
1164 path
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::uses_named_pipe_jobserver;
1170 use super::Version;
1171
1172 #[test]
1173 fn test_cmake_version() {
1174 let text = "cmake version 3.22.2
1175
1176CMake suite maintained and supported by Kitware (kitware.com/cmake).
1177";
1178 let v = Version::parse(text).unwrap();
1179 assert_eq!(v, Version::new(3, 22));
1180 assert!(Version::new(3, 22) > Version::new(3, 21));
1181 assert!(Version::new(3, 22) < Version::new(3, 23));
1182
1183 let _v = Version::from_command("cmake".as_ref()).unwrap();
1184 }
1185
1186 #[test]
1187 fn test_uses_fifo_jobserver() {
1188 assert!(uses_named_pipe_jobserver(
1189 "-j --jobserver-auth=fifo:/foo".as_ref()
1190 ));
1191 assert!(!uses_named_pipe_jobserver(
1192 "-j --jobserver-auth=8:9".as_ref()
1193 ));
1194 }
1195}