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 let build = dst.join("build");
558 self.maybe_clear(&build);
559 let _ = fs::create_dir_all(&build);
560
561 let mut cmake_prefix_path = Vec::new();
563 for dep in &self.deps {
564 let dep = dep.to_uppercase().replace('-', "_");
565 if let Some(root) = env::var_os(format!("DEP_{}_ROOT", dep)) {
566 cmake_prefix_path.push(PathBuf::from(root));
567 }
568 }
569 let system_prefix = self
570 .getenv_target_os("CMAKE_PREFIX_PATH")
571 .unwrap_or_default();
572 cmake_prefix_path.extend(env::split_paths(&system_prefix));
573 let cmake_prefix_path = env::join_paths(&cmake_prefix_path).unwrap();
574
575 let mut cmd = self.cmake_configure_command(&target);
577
578 let version = Version::from_command(cmd.get_program()).unwrap_or_default();
579
580 if self.verbose_cmake {
581 cmd.arg("-Wdev");
582 cmd.arg("--debug-output");
583 }
584
585 cmd.arg(&self.path).current_dir(&build);
586 let mut is_ninja = false;
587 if let Some(ref generator) = generator {
588 is_ninja = generator.to_string_lossy().contains("Ninja");
589 }
590 if target.contains("windows-gnu") {
591 if host.contains("windows") {
592 if generator.is_none() {
596 let has_msys2 = Command::new("make")
600 .arg("--version")
601 .output()
602 .err()
603 .map(|e| e.kind() != ErrorKind::NotFound)
604 .unwrap_or(true);
605 let has_mingw32 = Command::new("mingw32-make")
606 .arg("--version")
607 .output()
608 .err()
609 .map(|e| e.kind() != ErrorKind::NotFound)
610 .unwrap_or(true);
611
612 let generator = match (has_msys2, has_mingw32) {
613 (true, _) => "MSYS Makefiles",
614 (false, true) => "MinGW Makefiles",
615 (false, false) => fail("no valid generator found for GNU toolchain; MSYS or MinGW must be installed")
616 };
617
618 cmd.arg("-G").arg(generator);
619 }
620 } else {
621 if !self.defined("CMAKE_RC_COMPILER") {
626 let exe = find_exe(c_compiler.path());
627 if let Some(name) = exe.file_name().unwrap().to_str() {
628 let name = name.replace("gcc", "windres");
629 let windres = exe.with_file_name(name);
630 if windres.is_file() {
631 let mut arg = OsString::from("-DCMAKE_RC_COMPILER=");
632 arg.push(&windres);
633 cmd.arg(arg);
634 }
635 }
636 }
637 }
638 } else if msvc {
639 let using_nmake_generator = if let Some(g) = &generator {
643 g == "NMake Makefiles" || g == "NMake Makefiles JOM"
644 } else {
645 cmd.arg("-G").arg(self.visual_studio_generator(&target));
646 false
647 };
648 if !is_ninja && !using_nmake_generator {
649 if target.contains("x86_64") {
650 if self.generator_toolset.is_none() {
651 cmd.arg("-Thost=x64");
652 }
653 cmd.arg("-Ax64");
654 } else if target.contains("thumbv7a") {
655 if self.generator_toolset.is_none() {
656 cmd.arg("-Thost=x64");
657 }
658 cmd.arg("-Aarm");
659 } else if target.contains("aarch64") {
660 if self.generator_toolset.is_none() {
661 cmd.arg("-Thost=x64");
662 }
663 cmd.arg("-AARM64");
664 } else if target.contains("i686") {
665 if self.generator_toolset.is_none() {
666 cmd.arg("-Thost=x86");
667 }
668 cmd.arg("-AWin32");
669 } else {
670 panic!("unsupported msvc target: {}", target);
671 }
672 }
673 } else if target.contains("darwin") && !self.defined("CMAKE_OSX_ARCHITECTURES") {
674 if target.contains("x86_64") {
675 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=x86_64");
676 } else if target.contains("aarch64") {
677 cmd.arg("-DCMAKE_OSX_ARCHITECTURES=arm64");
678 } else {
679 panic!("unsupported darwin target: {}", target);
680 }
681 }
682 if let Some(ref generator) = generator {
683 cmd.arg("-G").arg(generator);
684 }
685 if let Some(ref generator_toolset) = self.generator_toolset {
686 cmd.arg("-T").arg(generator_toolset);
687 }
688 let profile = self.get_profile().to_string();
689 for (k, v) in &self.defines {
690 let mut os = OsString::from("-D");
691 os.push(k);
692 os.push("=");
693 os.push(v);
694 cmd.arg(os);
695 }
696
697 if !self.defined("CMAKE_INSTALL_PREFIX") {
698 let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX=");
699 dstflag.push(&dst);
700 cmd.arg(dstflag);
701 }
702
703 let build_type = self
704 .defines
705 .iter()
706 .find(|&(a, _)| a == "CMAKE_BUILD_TYPE")
707 .map(|x| x.1.to_str().unwrap())
708 .unwrap_or(&profile);
709 let build_type_upcase = build_type
710 .chars()
711 .flat_map(|c| c.to_uppercase())
712 .collect::<String>();
713
714 {
715 let skip_arg = |arg: &OsStr| match arg.to_str() {
717 Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g",
718 None => false,
719 };
720 let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| {
721 let flag_var = format!("CMAKE_{}_FLAGS", kind);
722 let tool_var = format!("CMAKE_{}_COMPILER", kind);
723 if !self.defined(&flag_var) {
724 let mut flagsflag = OsString::from("-D");
725 flagsflag.push(&flag_var);
726 flagsflag.push("=");
727 flagsflag.push(extra);
728 for arg in compiler.args() {
729 if skip_arg(arg) {
730 continue;
731 }
732 flagsflag.push(" ");
733 flagsflag.push(arg);
734 }
735 cmd.arg(flagsflag);
736 }
737
738 if generator.is_none() && msvc {
746 let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase);
747 if !self.defined(&flag_var_alt) {
748 let mut flagsflag = OsString::from("-D");
749 flagsflag.push(&flag_var_alt);
750 flagsflag.push("=");
751 flagsflag.push(extra);
752 for arg in compiler.args() {
753 if skip_arg(arg) {
754 continue;
755 }
756 flagsflag.push(" ");
757 flagsflag.push(arg);
758 }
759 cmd.arg(flagsflag);
760 }
761 }
762
763 if !self.defined("CMAKE_TOOLCHAIN_FILE")
774 && !self.defined(&tool_var)
775 && (env::consts::FAMILY != "windows" || (msvc && is_ninja))
776 {
777 let mut ccompiler = OsString::from("-D");
778 ccompiler.push(&tool_var);
779 ccompiler.push("=");
780 ccompiler.push(find_exe(compiler.path()));
781 #[cfg(windows)]
782 {
783 use std::os::windows::ffi::{OsStrExt, OsStringExt};
786 let wchars = ccompiler
787 .encode_wide()
788 .map(|wchar| {
789 if wchar == b'\\' as u16 {
790 '/' as u16
791 } else {
792 wchar
793 }
794 })
795 .collect::<Vec<_>>();
796 ccompiler = OsString::from_wide(&wchars);
797 }
798 cmd.arg(ccompiler);
799 }
800 };
801
802 set_compiler("C", &c_compiler, &self.cflags);
803 set_compiler("CXX", &cxx_compiler, &self.cxxflags);
804 set_compiler("ASM", &asm_compiler, &self.asmflags);
805 }
806
807 if !self.defined("CMAKE_BUILD_TYPE") {
808 cmd.arg(format!("-DCMAKE_BUILD_TYPE={}", profile));
809 }
810
811 if self.verbose_make {
812 cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON");
813 }
814
815 for (k, v) in c_compiler.env().iter().chain(&self.env) {
816 cmd.env(k, v);
817 }
818
819 if self.always_configure || !build.join("CMakeCache.txt").exists() {
820 cmd.args(&self.configure_args);
821 run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake");
822 } else {
823 println!("CMake project was already configured. Skipping configuration step.");
824 }
825
826 let mut cmd = self.cmake_build_command(&target);
828 cmd.current_dir(&build);
829
830 for (k, v) in c_compiler.env().iter().chain(&self.env) {
831 cmd.env(k, v);
832 }
833
834 let mut use_jobserver = false;
836 if fs::metadata(build.join("Makefile")).is_ok() {
837 match env::var_os("CARGO_MAKEFLAGS") {
838 Some(ref makeflags)
847 if !(cfg!(windows)
848 || cfg!(target_os = "openbsd")
849 || cfg!(target_os = "netbsd")
850 || cfg!(target_os = "freebsd")
851 || cfg!(target_os = "dragonfly")
852 || (cfg!(target_os = "macos")
853 && !uses_named_pipe_jobserver(makeflags))) =>
854 {
855 use_jobserver = true;
856 cmd.env("MAKEFLAGS", makeflags);
857 }
858 _ => {}
859 }
860 }
861
862 cmd.arg("--build").arg(&build);
863
864 if !self.no_build_target {
865 let target = self
866 .cmake_target
867 .clone()
868 .unwrap_or_else(|| "install".to_string());
869 cmd.arg("--target").arg(target);
870 }
871
872 cmd.arg("--config").arg(&profile);
873
874 if version >= Version::new(3, 12) && !use_jobserver {
877 if let Ok(s) = env::var("NUM_JOBS") {
878 cmd.arg("--parallel").arg(s);
880 }
881 }
882
883 if !&self.build_args.is_empty() {
884 cmd.arg("--").args(&self.build_args);
885 }
886
887 run(&mut cmd, "cmake");
888
889 println!("cargo:root={}", dst.display());
890 dst
891 }
892
893 fn cmake_executable(&mut self) -> OsString {
894 self.getenv_target_os("CMAKE")
895 .unwrap_or_else(|| OsString::from("cmake"))
896 }
897
898 fn cmake_configure_command(&mut self, target: &str) -> Command {
903 if target.contains("emscripten") {
904 let emcmake = self
905 .getenv_target_os("EMCMAKE")
906 .unwrap_or_else(|| OsString::from("emcmake"));
907 let mut cmd = Command::new(emcmake);
908 cmd.arg(self.cmake_executable());
909 cmd
910 } else {
911 Command::new(self.cmake_executable())
912 }
913 }
914
915 fn cmake_build_command(&mut self, target: &str) -> Command {
916 if target.contains("emscripten") {
917 let emmake = self
918 .getenv_target_os("EMMAKE")
919 .unwrap_or_else(|| OsString::from("emmake"));
920 let mut cmd = Command::new(emmake);
921 cmd.arg(self.cmake_executable());
922 cmd
923 } else {
924 Command::new(self.cmake_executable())
925 }
926 }
927
928 fn getenv_os(&mut self, v: &str) -> Option<OsString> {
929 if let Some(val) = self.env_cache.get(v) {
930 return val.clone();
931 }
932 let r = env::var_os(v);
933 println!("{} = {:?}", v, r);
934 self.env_cache.insert(v.to_string(), r.clone());
935 r
936 }
937
938 fn getenv_target_os(&mut self, var_base: &str) -> Option<OsString> {
940 let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
941 let target = self
942 .target
943 .clone()
944 .unwrap_or_else(|| getenv_unwrap("TARGET"));
945
946 let kind = if host == target { "HOST" } else { "TARGET" };
947 let target_u = target.replace('-', "_");
948 self.getenv_os(&format!("{}_{}", var_base, target))
949 .or_else(|| self.getenv_os(&format!("{}_{}", var_base, target_u)))
950 .or_else(|| self.getenv_os(&format!("{}_{}", kind, var_base)))
951 .or_else(|| self.getenv_os(var_base))
952 }
953
954 fn visual_studio_generator(&self, target: &str) -> String {
955 use cc::windows_registry::{find_vs_version, VsVers};
956
957 let base = match find_vs_version() {
958 Ok(VsVers::Vs17) => "Visual Studio 17 2022",
959 Ok(VsVers::Vs16) => "Visual Studio 16 2019",
960 Ok(VsVers::Vs15) => "Visual Studio 15 2017",
961 Ok(VsVers::Vs14) => "Visual Studio 14 2015",
962 #[allow(deprecated)]
964 Ok(VsVers::Vs12) => "Visual Studio 12 2013",
965 Ok(_) => panic!(
966 "Visual studio version detected but this crate \
967 doesn't know how to generate cmake files for it, \
968 can the `cmake` crate be updated?"
969 ),
970 Err(msg) => panic!("{}", msg),
971 };
972 if ["i686", "x86_64", "thumbv7a", "aarch64"]
973 .iter()
974 .any(|t| target.contains(t))
975 {
976 base.to_string()
977 } else {
978 panic!("unsupported msvc target: {}", target);
979 }
980 }
981
982 fn defined(&self, var: &str) -> bool {
983 self.defines.iter().any(|(a, _)| a == var)
984 }
985
986 fn maybe_clear(&self, dir: &Path) {
995 let path = fs::canonicalize(&self.path).unwrap_or_else(|_| self.path.clone());
999 let mut f = match File::open(dir.join("CMakeCache.txt")) {
1000 Ok(f) => f,
1001 Err(..) => return,
1002 };
1003 let mut u8contents = Vec::new();
1004 match f.read_to_end(&mut u8contents) {
1005 Ok(f) => f,
1006 Err(..) => return,
1007 };
1008 let contents = String::from_utf8_lossy(&u8contents);
1009 drop(f);
1010 for line in contents.lines() {
1011 if line.starts_with("CMAKE_HOME_DIRECTORY") {
1012 let needs_cleanup = match line.split('=').next_back() {
1013 Some(cmake_home) => fs::canonicalize(cmake_home)
1014 .ok()
1015 .map(|cmake_home| cmake_home != path)
1016 .unwrap_or(true),
1017 None => true,
1018 };
1019 if needs_cleanup {
1020 println!(
1021 "detected home dir change, cleaning out entire build \
1022 directory"
1023 );
1024 fs::remove_dir_all(dir).unwrap();
1025 }
1026 break;
1027 }
1028 }
1029 }
1030}
1031
1032#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1033struct Version {
1034 major: u32,
1035 minor: u32,
1036}
1037
1038impl Version {
1039 fn new(major: u32, minor: u32) -> Self {
1040 Self { major, minor }
1041 }
1042
1043 fn parse(s: &str) -> Option<Self> {
1044 let version = s.lines().next()?.strip_prefix("cmake version ")?;
1052 let mut digits = version.splitn(3, '.'); let major = digits.next()?.parse::<u32>().ok()?;
1054 let minor = digits.next()?.parse::<u32>().ok()?;
1055 Some(Version::new(major, minor))
1057 }
1058
1059 fn from_command(executable: &OsStr) -> Option<Self> {
1060 let output = Command::new(executable).arg("--version").output().ok()?;
1061 if !output.status.success() {
1062 return None;
1063 }
1064 let stdout = core::str::from_utf8(&output.stdout).ok()?;
1065 Self::parse(stdout)
1066 }
1067}
1068
1069impl Default for Version {
1070 fn default() -> Self {
1071 Self::new(3, 22)
1075 }
1076}
1077
1078fn run(cmd: &mut Command, program: &str) {
1079 println!("running: {:?}", cmd);
1080 let status = match cmd.status() {
1081 Ok(status) => status,
1082 Err(ref e) if e.kind() == ErrorKind::NotFound => {
1083 fail(&format!(
1084 "failed to execute command: {}\nis `{}` not installed?",
1085 e, program
1086 ));
1087 }
1088 Err(e) => fail(&format!("failed to execute command: {}", e)),
1089 };
1090 if !status.success() {
1091 if status.code() == Some(127) {
1092 fail(&format!(
1093 "command did not execute successfully, got: {}, is `{}` not installed?",
1094 status, program
1095 ));
1096 }
1097 fail(&format!(
1098 "command did not execute successfully, got: {}",
1099 status
1100 ));
1101 }
1102}
1103
1104fn find_exe(path: &Path) -> PathBuf {
1105 env::split_paths(&env::var_os("PATH").unwrap_or_default())
1106 .map(|p| p.join(path))
1107 .find(|p| fs::metadata(p).is_ok())
1108 .unwrap_or_else(|| path.to_owned())
1109}
1110
1111fn getenv_unwrap(v: &str) -> String {
1112 match env::var(v) {
1113 Ok(s) => s,
1114 Err(..) => fail(&format!("environment variable `{}` not defined", v)),
1115 }
1116}
1117
1118fn fail(s: &str) -> ! {
1119 panic!("\n{}\n\nbuild script failed, must exit now", s)
1120}
1121
1122fn uses_named_pipe_jobserver(makeflags: &OsStr) -> bool {
1125 makeflags
1126 .to_string_lossy()
1127 .contains("--jobserver-auth=fifo:")
1130}
1131
1132#[cfg(test)]
1133mod tests {
1134 use super::uses_named_pipe_jobserver;
1135 use super::Version;
1136
1137 #[test]
1138 fn test_cmake_version() {
1139 let text = "cmake version 3.22.2
1140
1141CMake suite maintained and supported by Kitware (kitware.com/cmake).
1142";
1143 let v = Version::parse(text).unwrap();
1144 assert_eq!(v, Version::new(3, 22));
1145 assert!(Version::new(3, 22) > Version::new(3, 21));
1146 assert!(Version::new(3, 22) < Version::new(3, 23));
1147
1148 let _v = Version::from_command("cmake".as_ref()).unwrap();
1149 }
1150
1151 #[test]
1152 fn test_uses_fifo_jobserver() {
1153 assert!(uses_named_pipe_jobserver(
1154 "-j --jobserver-auth=fifo:/foo".as_ref()
1155 ));
1156 assert!(!uses_named_pipe_jobserver(
1157 "-j --jobserver-auth=8:9".as_ref()
1158 ));
1159 }
1160}