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 if version >= Version::new(3, 13) {
590 cmd.arg("-B").arg(&build_dir);
591 }
592
593 let mut is_ninja = false;
594 if let Some(ref generator) = generator {
595 is_ninja = generator.to_string_lossy().contains("Ninja");
596 }
597 if target.contains("windows-gnu") {
598 if host.contains("windows") {
599 if generator.is_none() {
603 let has_msys2 = Command::new("make")
607 .arg("--version")
608 .output()
609 .err()
610 .map(|e| e.kind() != ErrorKind::NotFound)
611 .unwrap_or(true);
612 let has_mingw32 = Command::new("mingw32-make")
613 .arg("--version")
614 .output()
615 .err()
616 .map(|e| e.kind() != ErrorKind::NotFound)
617 .unwrap_or(true);
618
619 let generator = match (has_msys2, has_mingw32) {
620 (true, _) => "MSYS Makefiles",
621 (false, true) => "MinGW Makefiles",
622 (false, false) => fail("no valid generator found for GNU toolchain; MSYS or MinGW must be installed")
623 };
624
625 cmd.arg("-G").arg(generator);
626 }
627 } else {
628 if !self.defined("CMAKE_RC_COMPILER") {
633 let exe = find_exe(c_compiler.path());
634 if let Some(name) = exe.file_name().unwrap().to_str() {
635 let name = name.replace("gcc", "windres");
636 let windres = exe.with_file_name(name);
637 if windres.is_file() {
638 let mut arg = OsString::from("-DCMAKE_RC_COMPILER=");
639 arg.push(&windres);
640 cmd.arg(arg);
641 }
642 }
643 }
644 }
645 } else if msvc {
646 let using_nmake_generator = if let Some(g) = &generator {
650 g == "NMake Makefiles" || g == "NMake Makefiles JOM"
651 } else {
652 cmd.arg("-G").arg(self.visual_studio_generator(&target));
653 false
654 };
655 if !is_ninja && !using_nmake_generator {
656 if target.contains("x86_64") {
657 if self.generator_toolset.is_none() {
658 cmd.arg("-Thost=x64");
659 }
660 cmd.arg("-Ax64");
661 } else if target.contains("thumbv7a") {
662 if self.generator_toolset.is_none() {
663 cmd.arg("-Thost=x64");
664 }
665 cmd.arg("-Aarm");
666 } else if target.contains("aarch64") {
667 if self.generator_toolset.is_none() {
668 cmd.arg("-Thost=x64");
669 }
670 cmd.arg("-AARM64");
671 } else if target.contains("i686") {
672 if self.generator_toolset.is_none() {
673 cmd.arg("-Thost=x86");
674 }
675 cmd.arg("-AWin32");
676 } else {
677 panic!("unsupported msvc target: {}", target);
678 }
679 }
680 } else if target.contains("darwin") && !self.defined("CMAKE_OSX_ARCHITECTURES") {
681 if target.contains("x86_64") {
682 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=x86_64");
683 } else if target.contains("aarch64") {
684 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=arm64");
685 } else {
686 panic!("unsupported darwin target: {}", target);
687 }
688 }
689 if let Some(ref generator) = generator {
690 cmd.arg("-G").arg(generator);
691 }
692 if let Some(ref generator_toolset) = self.generator_toolset {
693 cmd.arg("-T").arg(generator_toolset);
694 }
695 let profile = self.get_profile().to_string();
696 for (k, v) in &self.defines {
697 let mut os = OsString::from("-D");
698 os.push(k);
699 os.push("=");
700 os.push(v);
701 cmd.arg(os);
702 }
703
704 if !self.defined("CMAKE_INSTALL_PREFIX") {
705 let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX=");
706 dstflag.push(&dst);
707 cmd.arg(dstflag);
708 }
709
710 let build_type = self
711 .defines
712 .iter()
713 .find(|&(a, _)| a == "CMAKE_BUILD_TYPE")
714 .map(|x| x.1.to_str().unwrap())
715 .unwrap_or(&profile);
716 let build_type_upcase = build_type
717 .chars()
718 .flat_map(|c| c.to_uppercase())
719 .collect::<String>();
720
721 {
722 let skip_arg = |arg: &OsStr| match arg.to_str() {
724 Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g",
725 None => false,
726 };
727 let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| {
728 let flag_var = format!("CMAKE_{}_FLAGS", kind);
729 let tool_var = format!("CMAKE_{}_COMPILER", kind);
730 if !self.defined(&flag_var) {
731 let mut flagsflag = OsString::from("-D");
732 flagsflag.push(&flag_var);
733 flagsflag.push("=");
734 flagsflag.push(extra);
735 for arg in compiler.args() {
736 if skip_arg(arg) {
737 continue;
738 }
739 flagsflag.push(" ");
740 flagsflag.push(arg);
741 }
742 cmd.arg(flagsflag);
743 }
744
745 if generator.is_none() && msvc {
753 let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase);
754 if !self.defined(&flag_var_alt) {
755 let mut flagsflag = OsString::from("-D");
756 flagsflag.push(&flag_var_alt);
757 flagsflag.push("=");
758 flagsflag.push(extra);
759 for arg in compiler.args() {
760 if skip_arg(arg) {
761 continue;
762 }
763 flagsflag.push(" ");
764 flagsflag.push(arg);
765 }
766 cmd.arg(flagsflag);
767 }
768 }
769
770 if !self.defined("CMAKE_TOOLCHAIN_FILE")
781 && !self.defined(&tool_var)
782 && (env::consts::FAMILY != "windows" || (msvc && is_ninja))
783 {
784 let mut ccompiler = OsString::from("-D");
785 ccompiler.push(&tool_var);
786 ccompiler.push("=");
787 ccompiler.push(find_exe(compiler.path()));
788 #[cfg(windows)]
789 {
790 use std::os::windows::ffi::{OsStrExt, OsStringExt};
793 let wchars = ccompiler
794 .encode_wide()
795 .map(|wchar| {
796 if wchar == b'\\' as u16 {
797 '/' as u16
798 } else {
799 wchar
800 }
801 })
802 .collect::<Vec<_>>();
803 ccompiler = OsString::from_wide(&wchars);
804 }
805 cmd.arg(ccompiler);
806 }
807 };
808
809 set_compiler("C", &c_compiler, &self.cflags);
810 set_compiler("CXX", &cxx_compiler, &self.cxxflags);
811 set_compiler("ASM", &asm_compiler, &self.asmflags);
812 }
813
814 if !self.defined("CMAKE_BUILD_TYPE") {
815 cmd.arg(format!("-DCMAKE_BUILD_TYPE={}", profile));
816 }
817
818 if self.verbose_make {
819 cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON");
820 }
821
822 for (k, v) in c_compiler.env().iter().chain(&self.env) {
823 cmd.env(k, v);
824 }
825
826 if self.always_configure || !build_dir.join("CMakeCache.txt").exists() {
827 cmd.args(&self.configure_args);
828 run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake");
829 } else {
830 println!("CMake project was already configured. Skipping configuration step.");
831 }
832
833 let mut cmd = self.cmake_build_command(&target);
835 cmd.current_dir(&build_dir);
836
837 for (k, v) in c_compiler.env().iter().chain(&self.env) {
838 cmd.env(k, v);
839 }
840
841 let mut use_jobserver = false;
843 if fs::metadata(build_dir.join("Makefile")).is_ok() {
844 match env::var_os("CARGO_MAKEFLAGS") {
845 Some(ref makeflags)
854 if !(cfg!(windows)
855 || cfg!(target_os = "openbsd")
856 || cfg!(target_os = "netbsd")
857 || cfg!(target_os = "freebsd")
858 || cfg!(target_os = "dragonfly")
859 || (cfg!(target_os = "macos")
860 && !uses_named_pipe_jobserver(makeflags))) =>
861 {
862 use_jobserver = true;
863 cmd.env("MAKEFLAGS", makeflags);
864 }
865 _ => {}
866 }
867 }
868
869 cmd.arg("--build").arg(&build_dir);
870
871 if !self.no_build_target {
872 let target = self
873 .cmake_target
874 .clone()
875 .unwrap_or_else(|| "install".to_string());
876 cmd.arg("--target").arg(target);
877 }
878
879 cmd.arg("--config").arg(&profile);
880
881 if version >= Version::new(3, 12) && !use_jobserver {
884 if let Ok(s) = env::var("NUM_JOBS") {
885 cmd.arg("--parallel").arg(s);
887 }
888 }
889
890 if !&self.build_args.is_empty() {
891 cmd.arg("--").args(&self.build_args);
892 }
893
894 run(&mut cmd, "cmake");
895
896 println!("cargo:root={}", dst.display());
897 dst
898 }
899
900 fn cmake_executable(&mut self, target: &str) -> OsString {
901 self.getenv_target_os("CMAKE")
902 .or_else(|| find_cmake_executable(target))
903 .unwrap_or_else(|| OsString::from("cmake"))
904 }
905
906 fn cmake_configure_command(&mut self, target: &str) -> Command {
911 if target.contains("emscripten") {
912 let emcmake = self
913 .getenv_target_os("EMCMAKE")
914 .unwrap_or_else(|| OsString::from("emcmake"));
915 let mut cmd = Command::new(emcmake);
916 cmd.arg(self.cmake_executable(target));
917 cmd
918 } else {
919 Command::new(self.cmake_executable(target))
920 }
921 }
922
923 fn cmake_build_command(&mut self, target: &str) -> Command {
924 if target.contains("emscripten") {
925 let emmake = self
926 .getenv_target_os("EMMAKE")
927 .unwrap_or_else(|| OsString::from("emmake"));
928 let mut cmd = Command::new(emmake);
929 cmd.arg(self.cmake_executable(target));
930 cmd
931 } else {
932 Command::new(self.cmake_executable(target))
933 }
934 }
935
936 fn getenv_os(&mut self, v: &str) -> Option<OsString> {
937 if let Some(val) = self.env_cache.get(v) {
938 return val.clone();
939 }
940 let r = env::var_os(v);
941 println!("{} = {:?}", v, r);
942 self.env_cache.insert(v.to_string(), r.clone());
943 r
944 }
945
946 fn getenv_target_os(&mut self, var_base: &str) -> Option<OsString> {
948 let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
949 let target = self
950 .target
951 .clone()
952 .unwrap_or_else(|| getenv_unwrap("TARGET"));
953
954 let kind = if host == target { "HOST" } else { "TARGET" };
955 let target_u = target.replace('-', "_");
956 self.getenv_os(&format!("{}_{}", var_base, target))
957 .or_else(|| self.getenv_os(&format!("{}_{}", var_base, target_u)))
958 .or_else(|| self.getenv_os(&format!("{}_{}", kind, var_base)))
959 .or_else(|| self.getenv_os(var_base))
960 }
961
962 fn visual_studio_generator(&self, target: &str) -> String {
963 use cc::windows_registry::{find_vs_version, VsVers};
964
965 let base = match find_vs_version() {
966 Ok(VsVers::Vs18) => "Visual Studio 18 2026",
967 Ok(VsVers::Vs17) => "Visual Studio 17 2022",
968 Ok(VsVers::Vs16) => "Visual Studio 16 2019",
969 Ok(VsVers::Vs15) => "Visual Studio 15 2017",
970 Ok(VsVers::Vs14) => "Visual Studio 14 2015",
971 #[allow(deprecated)]
973 Ok(VsVers::Vs12) => "Visual Studio 12 2013",
974 Ok(_) => panic!(
975 "Visual studio version detected but this crate \
976 doesn't know how to generate cmake files for it, \
977 can the `cmake` crate be updated?"
978 ),
979 Err(msg) => panic!("{}", msg),
980 };
981 if ["i686", "x86_64", "thumbv7a", "aarch64"]
982 .iter()
983 .any(|t| target.contains(t))
984 {
985 base.to_string()
986 } else {
987 panic!("unsupported msvc target: {}", target);
988 }
989 }
990
991 fn defined(&self, var: &str) -> bool {
992 self.defines.iter().any(|(a, _)| a == var)
993 }
994
995 fn maybe_clear(&self, dir: &Path) {
1004 let path = try_canonicalize(&self.path);
1008
1009 let mut f = match File::open(dir.join("CMakeCache.txt")) {
1010 Ok(f) => f,
1011 Err(..) => return,
1012 };
1013 let mut u8contents = Vec::new();
1014 match f.read_to_end(&mut u8contents) {
1015 Ok(f) => f,
1016 Err(..) => return,
1017 };
1018 let contents = String::from_utf8_lossy(&u8contents);
1019 drop(f);
1020 for line in contents.lines() {
1021 if line.starts_with("CMAKE_HOME_DIRECTORY") {
1022 let needs_cleanup = match line.split('=').next_back() {
1023 Some(cmake_home) => fs::canonicalize(cmake_home)
1024 .ok()
1025 .map(|cmake_home| cmake_home != path)
1026 .unwrap_or(true),
1027 None => true,
1028 };
1029 if needs_cleanup {
1030 println!(
1031 "detected home dir change, cleaning out entire build \
1032 directory"
1033 );
1034 fs::remove_dir_all(dir).unwrap();
1035 }
1036 break;
1037 }
1038 }
1039 }
1040}
1041
1042#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1043struct Version {
1044 major: u32,
1045 minor: u32,
1046}
1047
1048impl Version {
1049 fn new(major: u32, minor: u32) -> Self {
1050 Self { major, minor }
1051 }
1052
1053 fn parse(s: &str) -> Option<Self> {
1054 let version = s.lines().next()?.strip_prefix("cmake version ")?;
1062 let mut digits = version.splitn(3, '.'); let major = digits.next()?.parse::<u32>().ok()?;
1064 let minor = digits.next()?.parse::<u32>().ok()?;
1065 Some(Version::new(major, minor))
1067 }
1068
1069 fn from_command(executable: &OsStr) -> Option<Self> {
1070 let output = Command::new(executable).arg("--version").output().ok()?;
1071 if !output.status.success() {
1072 return None;
1073 }
1074 let stdout = core::str::from_utf8(&output.stdout).ok()?;
1075 Self::parse(stdout)
1076 }
1077}
1078
1079impl Default for Version {
1080 fn default() -> Self {
1081 Self::new(3, 22)
1085 }
1086}
1087
1088fn run(cmd: &mut Command, program: &str) {
1089 eprintln!("running: {:?}", cmd);
1090 let status = match cmd.status() {
1091 Ok(status) => status,
1092 Err(ref e) if e.kind() == ErrorKind::NotFound => {
1093 fail(&format!(
1094 "failed to execute command: {}\nis `{}` not installed?",
1095 e, program
1096 ));
1097 }
1098 Err(e) => fail(&format!("failed to execute command: {}", e)),
1099 };
1100 if !status.success() {
1101 if status.code() == Some(127) {
1102 fail(&format!(
1103 "command did not execute successfully, got: {}, is `{}` not installed?",
1104 status, program
1105 ));
1106 }
1107 fail(&format!(
1108 "command did not execute successfully, got: {}",
1109 status
1110 ));
1111 }
1112}
1113
1114fn find_exe(path: &Path) -> PathBuf {
1115 env::split_paths(&env::var_os("PATH").unwrap_or_default())
1116 .map(|p| p.join(path))
1117 .find(|p| fs::metadata(p).is_ok())
1118 .unwrap_or_else(|| path.to_owned())
1119}
1120
1121fn getenv_unwrap(v: &str) -> String {
1122 match env::var(v) {
1123 Ok(s) => s,
1124 Err(..) => fail(&format!("environment variable `{}` not defined", v)),
1125 }
1126}
1127
1128fn fail(s: &str) -> ! {
1129 panic!("\n{}\n\nbuild script failed, must exit now", s)
1130}
1131
1132fn uses_named_pipe_jobserver(makeflags: &OsStr) -> bool {
1135 makeflags
1136 .to_string_lossy()
1137 .contains("--jobserver-auth=fifo:")
1140}
1141
1142fn try_canonicalize(path: &Path) -> PathBuf {
1145 let path = path.canonicalize().unwrap_or_else(|_| path.to_owned());
1146 #[cfg(windows)]
1150 {
1151 use std::os::windows::ffi::{OsStrExt, OsStringExt};
1152 let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
1153 if wide.starts_with(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]) {
1154 if wide.get(5..7) == Some(&[b':' as u16, b'\\' as u16]) {
1155 wide.copy_within(4.., 0);
1157 wide.truncate(wide.len() - 4);
1158 } else if wide.get(4..8) == Some(&[b'U' as u16, b'N' as u16, b'C' as u16, b'\\' as u16])
1159 {
1160 wide.copy_within(8.., 2);
1162 wide.truncate(wide.len() - (8 - 2));
1163 }
1164 return OsString::from_wide(&wide).into();
1165 }
1166 }
1167 path
1168}
1169
1170#[cfg(windows)]
1171fn find_cmake_executable(target: &str) -> Option<OsString> {
1172 use cc::windows_registry::find_tool;
1173
1174 let cmake_in_path = env::split_paths(&env::var_os("PATH").unwrap_or(OsString::new()))
1176 .any(|p| p.join("cmake.exe").exists());
1177 if cmake_in_path {
1178 None
1179 } else {
1180 find_tool(target, "devenv").and_then(|t| {
1181 t.path()
1182 .join("..\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe")
1183 .canonicalize()
1184 .ok()
1185 .map(OsString::from)
1186 })
1187 }
1188}
1189
1190#[cfg(not(windows))]
1191fn find_cmake_executable(_target: &str) -> Option<OsString> {
1192 None
1193}
1194
1195#[cfg(test)]
1196mod tests {
1197 use super::uses_named_pipe_jobserver;
1198 use super::Version;
1199
1200 #[test]
1201 fn test_cmake_version() {
1202 let text = "cmake version 3.22.2
1203
1204CMake suite maintained and supported by Kitware (kitware.com/cmake).
1205";
1206 let v = Version::parse(text).unwrap();
1207 assert_eq!(v, Version::new(3, 22));
1208 assert!(Version::new(3, 22) > Version::new(3, 21));
1209 assert!(Version::new(3, 22) < Version::new(3, 23));
1210
1211 let _v = Version::from_command("cmake".as_ref()).unwrap();
1212 }
1213
1214 #[test]
1215 fn test_uses_fifo_jobserver() {
1216 assert!(uses_named_pipe_jobserver(
1217 "-j --jobserver-auth=fifo:/foo".as_ref()
1218 ));
1219 assert!(!uses_named_pipe_jobserver(
1220 "-j --jobserver-auth=8:9".as_ref()
1221 ));
1222 }
1223}