]>
Commit | Line | Data |
---|---|---|
476ff2be SL |
1 | //! A build dependency for running `cmake` to build a native library |
2 | //! | |
3 | //! This crate provides some necessary boilerplate and shim support for running | |
4 | //! the system `cmake` command to build a native library. It will add | |
5 | //! appropriate cflags for building code to link into Rust, handle cross | |
6 | //! compilation, and use the necessary generator for the platform being | |
7 | //! targeted. | |
8 | //! | |
9 | //! The builder-style configuration allows for various variables and such to be | |
10 | //! passed down into the build as well. | |
11 | //! | |
12 | //! ## Installation | |
13 | //! | |
14 | //! Add this to your `Cargo.toml`: | |
15 | //! | |
16 | //! ```toml | |
17 | //! [build-dependencies] | |
18 | //! cmake = "0.1" | |
19 | //! ``` | |
20 | //! | |
21 | //! ## Examples | |
22 | //! | |
23 | //! ```no_run | |
24 | //! use cmake; | |
25 | //! | |
26 | //! // Builds the project in the directory located in `libfoo`, installing it | |
27 | //! // into $OUT_DIR | |
28 | //! let dst = cmake::build("libfoo"); | |
29 | //! | |
30 | //! println!("cargo:rustc-link-search=native={}", dst.display()); | |
31 | //! println!("cargo:rustc-link-lib=static=foo"); | |
32 | //! ``` | |
33 | //! | |
34 | //! ```no_run | |
35 | //! use cmake::Config; | |
36 | //! | |
37 | //! let dst = Config::new("libfoo") | |
38 | //! .define("FOO", "BAR") | |
39 | //! .cflag("-foo") | |
40 | //! .build(); | |
41 | //! println!("cargo:rustc-link-search=native={}", dst.display()); | |
42 | //! println!("cargo:rustc-link-lib=static=foo"); | |
43 | //! ``` | |
44 | ||
45 | #![deny(missing_docs)] | |
46 | ||
ea8adc8c | 47 | extern crate cc; |
476ff2be SL |
48 | |
49 | use std::env; | |
48663c56 | 50 | use std::ffi::{OsStr, OsString}; |
476ff2be | 51 | use std::fs::{self, File}; |
476ff2be | 52 | use std::io::prelude::*; |
48663c56 | 53 | use std::io::ErrorKind; |
476ff2be SL |
54 | use std::path::{Path, PathBuf}; |
55 | use std::process::Command; | |
56 | ||
476ff2be SL |
57 | /// Builder style configuration for a pending CMake build. |
58 | pub struct Config { | |
59 | path: PathBuf, | |
60 | generator: Option<OsString>, | |
61 | cflags: OsString, | |
62 | cxxflags: OsString, | |
3dfed10e | 63 | asmflags: OsString, |
476ff2be SL |
64 | defines: Vec<(OsString, OsString)>, |
65 | deps: Vec<String>, | |
66 | target: Option<String>, | |
67 | host: Option<String>, | |
68 | out_dir: Option<PathBuf>, | |
69 | profile: Option<String>, | |
70 | build_args: Vec<OsString>, | |
71 | cmake_target: Option<String>, | |
cc61c64b | 72 | env: Vec<(OsString, OsString)>, |
7cac9316 | 73 | static_crt: Option<bool>, |
abe05a73 | 74 | uses_cxx11: bool, |
b7449926 XL |
75 | always_configure: bool, |
76 | no_build_target: bool, | |
77 | verbose_cmake: bool, | |
78 | verbose_make: bool, | |
3dfed10e | 79 | pic: Option<bool>, |
476ff2be SL |
80 | } |
81 | ||
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. | |
84 | /// | |
85 | /// # Examples | |
86 | /// | |
87 | /// ```no_run | |
88 | /// use cmake; | |
89 | /// | |
90 | /// // Builds the project in the directory located in `libfoo`, installing it | |
91 | /// // into $OUT_DIR | |
92 | /// let dst = cmake::build("libfoo"); | |
93 | /// | |
94 | /// println!("cargo:rustc-link-search=native={}", dst.display()); | |
95 | /// println!("cargo:rustc-link-lib=static=foo"); | |
96 | /// ``` | |
97 | /// | |
98 | pub fn build<P: AsRef<Path>>(path: P) -> PathBuf { | |
99 | Config::new(path.as_ref()).build() | |
100 | } | |
101 | ||
102 | impl Config { | |
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 { | |
106 | Config { | |
107 | path: env::current_dir().unwrap().join(path), | |
108 | generator: None, | |
109 | cflags: OsString::new(), | |
110 | cxxflags: OsString::new(), | |
3dfed10e | 111 | asmflags: OsString::new(), |
476ff2be SL |
112 | defines: Vec::new(), |
113 | deps: Vec::new(), | |
114 | profile: None, | |
115 | out_dir: None, | |
116 | target: None, | |
117 | host: None, | |
118 | build_args: Vec::new(), | |
119 | cmake_target: None, | |
cc61c64b | 120 | env: Vec::new(), |
7cac9316 | 121 | static_crt: None, |
b7449926 XL |
122 | uses_cxx11: false, |
123 | always_configure: true, | |
124 | no_build_target: false, | |
125 | verbose_cmake: false, | |
126 | verbose_make: false, | |
3dfed10e | 127 | pic: None, |
476ff2be SL |
128 | } |
129 | } | |
130 | ||
3dfed10e XL |
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); | |
134 | self | |
135 | } | |
136 | ||
476ff2be SL |
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()); | |
140 | self | |
141 | } | |
142 | ||
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()); | |
148 | self | |
149 | } | |
150 | ||
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()); | |
156 | self | |
157 | } | |
158 | ||
3dfed10e XL |
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()); | |
164 | self | |
165 | } | |
166 | ||
476ff2be SL |
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 | |
48663c56 XL |
169 | where |
170 | K: AsRef<OsStr>, | |
171 | V: AsRef<OsStr>, | |
476ff2be | 172 | { |
48663c56 XL |
173 | self.defines |
174 | .push((k.as_ref().to_owned(), v.as_ref().to_owned())); | |
476ff2be SL |
175 | self |
176 | } | |
177 | ||
178 | /// Registers a dependency for this compilation on the native library built | |
179 | /// by Cargo previously. | |
180 | /// | |
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()); | |
185 | self | |
186 | } | |
187 | ||
188 | /// Sets the target triple for this compilation. | |
189 | /// | |
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()); | |
194 | self | |
195 | } | |
196 | ||
dfeec247 XL |
197 | /// Disables the cmake target option for this compilation. |
198 | /// | |
199 | /// Note that this isn't related to the target triple passed to the compiler! | |
b7449926 XL |
200 | pub fn no_build_target(&mut self, no_build_target: bool) -> &mut Config { |
201 | self.no_build_target = no_build_target; | |
202 | self | |
203 | } | |
204 | ||
476ff2be SL |
205 | /// Sets the host triple for this compilation. |
206 | /// | |
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()); | |
211 | self | |
212 | } | |
213 | ||
214 | /// Sets the output directory for this compilation. | |
215 | /// | |
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()); | |
220 | self | |
221 | } | |
222 | ||
b7449926 | 223 | /// Sets the `CMAKE_BUILD_TYPE=build_type` variable. |
476ff2be | 224 | /// |
b7449926 XL |
225 | /// By default, this value is automatically inferred from Rust's compilation |
226 | /// profile as follows: | |
227 | /// | |
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` | |
476ff2be SL |
233 | pub fn profile(&mut self, profile: &str) -> &mut Config { |
234 | self.profile = Some(profile.to_string()); | |
235 | self | |
236 | } | |
237 | ||
7cac9316 XL |
238 | /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. |
239 | /// | |
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); | |
243 | self | |
244 | } | |
245 | ||
476ff2be SL |
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()); | |
249 | self | |
250 | } | |
251 | ||
cc61c64b XL |
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 | |
48663c56 XL |
255 | where |
256 | K: AsRef<OsStr>, | |
257 | V: AsRef<OsStr>, | |
cc61c64b | 258 | { |
48663c56 XL |
259 | self.env |
260 | .push((key.as_ref().to_owned(), value.as_ref().to_owned())); | |
cc61c64b XL |
261 | self |
262 | } | |
263 | ||
476ff2be SL |
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()); | |
268 | self | |
269 | } | |
270 | ||
abe05a73 XL |
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 | |
273 | /// specified. | |
274 | /// | |
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; | |
279 | self | |
280 | } | |
281 | ||
b7449926 XL |
282 | /// Forces CMake to always run before building the custom target. |
283 | /// | |
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; | |
288 | self | |
289 | } | |
290 | ||
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; | |
295 | self | |
296 | } | |
297 | ||
dfeec247 XL |
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()) | |
307 | }) | |
308 | } | |
309 | ||
476ff2be SL |
310 | /// Run this configuration, compiling the library with all the configured |
311 | /// options. | |
312 | /// | |
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 { | |
abe05a73 XL |
316 | let target = match self.target.clone() { |
317 | Some(t) => t, | |
318 | None => { | |
319 | let mut t = getenv_unwrap("TARGET"); | |
320 | if t.ends_with("-darwin") && self.uses_cxx11 { | |
321 | t = t + "11" | |
322 | } | |
323 | t | |
324 | } | |
325 | }; | |
48663c56 | 326 | let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST")); |
476ff2be | 327 | let msvc = target.contains("msvc"); |
dfeec247 | 328 | let ndk = self.uses_android_ndk(); |
ea8adc8c | 329 | let mut c_cfg = cc::Build::new(); |
48663c56 XL |
330 | c_cfg |
331 | .cargo_metadata(false) | |
7cac9316 XL |
332 | .opt_level(0) |
333 | .debug(false) | |
ea8adc8c | 334 | .warnings(false) |
dfeec247 XL |
335 | .host(&host) |
336 | .no_default_flags(ndk); | |
337 | if !ndk { | |
338 | c_cfg.target(&target); | |
339 | } | |
ea8adc8c | 340 | let mut cxx_cfg = cc::Build::new(); |
48663c56 XL |
341 | cxx_cfg |
342 | .cargo_metadata(false) | |
7cac9316 XL |
343 | .cpp(true) |
344 | .opt_level(0) | |
345 | .debug(false) | |
ea8adc8c | 346 | .warnings(false) |
dfeec247 XL |
347 | .host(&host) |
348 | .no_default_flags(ndk); | |
349 | if !ndk { | |
350 | cxx_cfg.target(&target); | |
351 | } | |
7cac9316 XL |
352 | if let Some(static_crt) = self.static_crt { |
353 | c_cfg.static_crt(static_crt); | |
354 | cxx_cfg.static_crt(static_crt); | |
355 | } | |
3dfed10e XL |
356 | if let Some(explicit_flag) = self.pic { |
357 | c_cfg.pic(explicit_flag); | |
358 | cxx_cfg.pic(explicit_flag); | |
359 | } | |
7cac9316 XL |
360 | let c_compiler = c_cfg.get_compiler(); |
361 | let cxx_compiler = cxx_cfg.get_compiler(); | |
3dfed10e | 362 | let asm_compiler = c_cfg.get_compiler(); |
476ff2be | 363 | |
48663c56 XL |
364 | let dst = self |
365 | .out_dir | |
366 | .clone() | |
367 | .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR"))); | |
476ff2be SL |
368 | let build = dst.join("build"); |
369 | self.maybe_clear(&build); | |
370 | let _ = fs::create_dir(&build); | |
371 | ||
372 | // Add all our dependencies to our cmake paths | |
373 | let mut cmake_prefix_path = Vec::new(); | |
374 | for dep in &self.deps { | |
b7449926 | 375 | let dep = dep.to_uppercase().replace('-', "_"); |
476ff2be SL |
376 | if let Some(root) = env::var_os(&format!("DEP_{}_ROOT", dep)) { |
377 | cmake_prefix_path.push(PathBuf::from(root)); | |
378 | } | |
379 | } | |
48663c56 XL |
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())); | |
476ff2be SL |
382 | let cmake_prefix_path = env::join_paths(&cmake_prefix_path).unwrap(); |
383 | ||
384 | // Build up the first cmake command to build the build system. | |
ff7c6d11 | 385 | let executable = env::var("CMAKE").unwrap_or("cmake".to_owned()); |
48663c56 | 386 | let mut cmd = Command::new(&executable); |
b7449926 XL |
387 | |
388 | if self.verbose_cmake { | |
389 | cmd.arg("-Wdev"); | |
390 | cmd.arg("--debug-output"); | |
391 | } | |
392 | ||
48663c56 XL |
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"); | |
397 | } | |
476ff2be SL |
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 | |
402 | // use to build. | |
403 | if self.generator.is_none() { | |
b7449926 XL |
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. | |
48663c56 XL |
407 | let has_msys2 = Command::new("make") |
408 | .arg("--version") | |
409 | .output() | |
410 | .err() | |
411 | .map(|e| e.kind() != ErrorKind::NotFound) | |
412 | .unwrap_or(true); | |
413 | let has_mingw32 = Command::new("mingw32-make") | |
414 | .arg("--version") | |
415 | .output() | |
416 | .err() | |
417 | .map(|e| e.kind() != ErrorKind::NotFound) | |
418 | .unwrap_or(true); | |
b7449926 XL |
419 | |
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") | |
424 | }; | |
425 | ||
426 | cmd.arg("-G").arg(generator); | |
476ff2be SL |
427 | } |
428 | } else { | |
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"); | |
435 | } | |
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="); | |
443 | arg.push(&windres); | |
444 | cmd.arg(arg); | |
445 | } | |
446 | } | |
447 | } | |
448 | } | |
449 | } else if msvc { | |
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. | |
8bb4bdeb | 452 | // This also guarantees that NMake generator isn't chosen implicitly. |
48663c56 | 453 | let using_nmake_generator; |
476ff2be SL |
454 | if self.generator.is_none() { |
455 | cmd.arg("-G").arg(self.visual_studio_generator(&target)); | |
48663c56 XL |
456 | using_nmake_generator = false; |
457 | } else { | |
458 | using_nmake_generator = self.generator.as_ref().unwrap() == "NMake Makefiles"; | |
459 | } | |
dfeec247 XL |
460 | if !is_ninja && !using_nmake_generator { |
461 | if target.contains("x86_64") { | |
462 | cmd.arg("-Thost=x64"); | |
463 | cmd.arg("-Ax64"); | |
464 | } else if target.contains("thumbv7a") { | |
465 | cmd.arg("-Thost=x64"); | |
466 | cmd.arg("-Aarm"); | |
467 | } else if target.contains("aarch64") { | |
468 | cmd.arg("-Thost=x64"); | |
469 | cmd.arg("-AARM64"); | |
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"); | |
478 | cmd.arg("-AWin32"); | |
479 | } | |
480 | _ => {} | |
481 | }; | |
482 | } else { | |
483 | panic!("unsupported msvc target: {}", target); | |
484 | } | |
476ff2be | 485 | } |
ea8adc8c XL |
486 | } else if target.contains("redox") { |
487 | if !self.defined("CMAKE_SYSTEM_NAME") { | |
488 | cmd.arg("-DCMAKE_SYSTEM_NAME=Generic"); | |
489 | } | |
83c7162d XL |
490 | } else if target.contains("solaris") { |
491 | if !self.defined("CMAKE_SYSTEM_NAME") { | |
492 | cmd.arg("-DCMAKE_SYSTEM_NAME=SunOS"); | |
493 | } | |
476ff2be SL |
494 | } |
495 | if let Some(ref generator) = self.generator { | |
496 | cmd.arg("-G").arg(generator); | |
497 | } | |
6a06907d XL |
498 | let profile = self.profile.clone().unwrap_or_else(|| { |
499 | // Automatically set the `CMAKE_BUILD_TYPE` if the user did not | |
500 | // specify one. | |
501 | ||
502 | // Determine Rust's profile, optimization level, and debug info: | |
503 | #[derive(PartialEq)] | |
504 | enum RustProfile { | |
505 | Debug, | |
506 | Release, | |
507 | } | |
508 | #[derive(PartialEq, Debug)] | |
509 | enum OptLevel { | |
510 | Debug, | |
511 | Release, | |
512 | Size, | |
513 | } | |
514 | ||
515 | let rust_profile = match &getenv_unwrap("PROFILE")[..] { | |
516 | "debug" => RustProfile::Debug, | |
517 | "release" | "bench" => RustProfile::Release, | |
518 | unknown => { | |
519 | eprintln!( | |
520 | "Warning: unknown Rust profile={}; defaulting to a release build.", | |
521 | unknown | |
522 | ); | |
523 | RustProfile::Release | |
524 | } | |
525 | }; | |
526 | ||
527 | let opt_level = match &getenv_unwrap("OPT_LEVEL")[..] { | |
528 | "0" => OptLevel::Debug, | |
529 | "1" | "2" | "3" => OptLevel::Release, | |
530 | "s" | "z" => OptLevel::Size, | |
531 | unknown => { | |
532 | let default_opt_level = match rust_profile { | |
533 | RustProfile::Debug => OptLevel::Debug, | |
534 | RustProfile::Release => OptLevel::Release, | |
535 | }; | |
536 | eprintln!( | |
537 | "Warning: unknown opt-level={}; defaulting to a {:?} build.", | |
538 | unknown, default_opt_level | |
539 | ); | |
540 | default_opt_level | |
541 | } | |
542 | }; | |
543 | ||
544 | let debug_info: bool = match &getenv_unwrap("DEBUG")[..] { | |
545 | "false" => false, | |
546 | "true" => true, | |
547 | unknown => { | |
548 | eprintln!("Warning: unknown debug={}; defaulting to `true`.", unknown); | |
549 | true | |
550 | } | |
551 | }; | |
552 | ||
553 | match (opt_level, debug_info) { | |
554 | (OptLevel::Debug, _) => "Debug", | |
555 | (OptLevel::Release, false) => "Release", | |
556 | (OptLevel::Release, true) => "RelWithDebInfo", | |
557 | (OptLevel::Size, _) => "MinSizeRel", | |
558 | } | |
559 | .to_string() | |
560 | }); | |
476ff2be SL |
561 | for &(ref k, ref v) in &self.defines { |
562 | let mut os = OsString::from("-D"); | |
563 | os.push(k); | |
564 | os.push("="); | |
565 | os.push(v); | |
566 | cmd.arg(os); | |
567 | } | |
568 | ||
569 | if !self.defined("CMAKE_INSTALL_PREFIX") { | |
570 | let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX="); | |
571 | dstflag.push(&dst); | |
572 | cmd.arg(dstflag); | |
573 | } | |
574 | ||
48663c56 XL |
575 | let build_type = self |
576 | .defines | |
577 | .iter() | |
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 | |
582 | .chars() | |
583 | .flat_map(|c| c.to_uppercase()) | |
584 | .collect::<String>(); | |
8bb4bdeb | 585 | |
476ff2be | 586 | { |
8bb4bdeb | 587 | // let cmake deal with optimization/debuginfo |
48663c56 XL |
588 | let skip_arg = |arg: &OsStr| match arg.to_str() { |
589 | Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g", | |
590 | None => false, | |
8bb4bdeb | 591 | }; |
48663c56 | 592 | let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| { |
476ff2be SL |
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); | |
598 | flagsflag.push("="); | |
599 | flagsflag.push(extra); | |
600 | for arg in compiler.args() { | |
8bb4bdeb | 601 | if skip_arg(arg) { |
48663c56 | 602 | continue; |
8bb4bdeb | 603 | } |
476ff2be SL |
604 | flagsflag.push(" "); |
605 | flagsflag.push(arg); | |
606 | } | |
607 | cmd.arg(flagsflag); | |
608 | } | |
609 | ||
8bb4bdeb XL |
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 | |
613 | // here. | |
614 | // | |
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 { | |
48663c56 | 618 | let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase); |
8bb4bdeb XL |
619 | if !self.defined(&flag_var_alt) { |
620 | let mut flagsflag = OsString::from("-D"); | |
621 | flagsflag.push(&flag_var_alt); | |
622 | flagsflag.push("="); | |
623 | flagsflag.push(extra); | |
624 | for arg in compiler.args() { | |
625 | if skip_arg(arg) { | |
48663c56 | 626 | continue; |
8bb4bdeb XL |
627 | } |
628 | flagsflag.push(" "); | |
629 | flagsflag.push(arg); | |
630 | } | |
631 | cmd.arg(flagsflag); | |
632 | } | |
633 | } | |
634 | ||
476ff2be SL |
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 | |
640 | // confused? | |
641 | // | |
8bb4bdeb XL |
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. | |
476ff2be | 645 | if !self.defined("CMAKE_TOOLCHAIN_FILE") |
48663c56 XL |
646 | && !self.defined(&tool_var) |
647 | && (env::consts::FAMILY != "windows" || (msvc && is_ninja)) | |
648 | { | |
476ff2be SL |
649 | let mut ccompiler = OsString::from("-D"); |
650 | ccompiler.push(&tool_var); | |
651 | ccompiler.push("="); | |
652 | ccompiler.push(find_exe(compiler.path())); | |
48663c56 XL |
653 | #[cfg(windows)] |
654 | { | |
8bb4bdeb XL |
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}; | |
48663c56 XL |
658 | let wchars = ccompiler |
659 | .encode_wide() | |
660 | .map(|wchar| { | |
661 | if wchar == b'\\' as u16 { | |
662 | '/' as u16 | |
663 | } else { | |
664 | wchar | |
665 | } | |
dfeec247 XL |
666 | }) |
667 | .collect::<Vec<_>>(); | |
8bb4bdeb XL |
668 | ccompiler = OsString::from_wide(&wchars); |
669 | } | |
476ff2be SL |
670 | cmd.arg(ccompiler); |
671 | } | |
672 | }; | |
673 | ||
674 | set_compiler("C", &c_compiler, &self.cflags); | |
675 | set_compiler("CXX", &cxx_compiler, &self.cxxflags); | |
3dfed10e | 676 | set_compiler("ASM", &asm_compiler, &self.asmflags); |
476ff2be SL |
677 | } |
678 | ||
679 | if !self.defined("CMAKE_BUILD_TYPE") { | |
680 | cmd.arg(&format!("-DCMAKE_BUILD_TYPE={}", profile)); | |
681 | } | |
682 | ||
b7449926 XL |
683 | if self.verbose_make { |
684 | cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"); | |
685 | } | |
686 | ||
476ff2be SL |
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)); | |
690 | } | |
691 | } | |
692 | ||
cc61c64b | 693 | for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) { |
8bb4bdeb XL |
694 | cmd.env(k, v); |
695 | } | |
696 | ||
b7449926 XL |
697 | if self.always_configure || !build.join("CMakeCache.txt").exists() { |
698 | run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake"); | |
699 | } else { | |
700 | println!("CMake project was already configured. Skipping configuration step."); | |
701 | } | |
476ff2be | 702 | |
abe05a73 | 703 | let mut makeflags = None; |
48663c56 XL |
704 | let mut parallel_flags = None; |
705 | ||
8bb4bdeb XL |
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") => { | |
48663c56 | 709 | parallel_flags = Some(format!("-j{}", s)); |
8bb4bdeb XL |
710 | } |
711 | Some(ref g) if g.contains("Visual Studio") => { | |
48663c56 | 712 | parallel_flags = Some(format!("/m:{}", s)); |
8faf50e0 XL |
713 | } |
714 | Some(ref g) if g.contains("NMake") => { | |
715 | // NMake creates `Makefile`s, but doesn't understand `-jN`. | |
716 | } | |
48663c56 | 717 | _ if fs::metadata(&build.join("Makefile")).is_ok() => { |
abe05a73 | 718 | match env::var_os("CARGO_MAKEFLAGS") { |
83c7162d XL |
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 | |
48663c56 XL |
723 | Some(ref s) |
724 | if !(cfg!(windows) | |
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")) => | |
730 | { | |
731 | makeflags = Some(s.clone()) | |
732 | } | |
abe05a73 XL |
733 | |
734 | // This looks like `make`, let's hope it understands `-jN`. | |
48663c56 | 735 | _ => makeflags = Some(OsString::from(format!("-j{}", s))), |
abe05a73 | 736 | } |
8bb4bdeb | 737 | } |
abe05a73 | 738 | _ => {} |
476ff2be SL |
739 | } |
740 | } | |
741 | ||
742 | // And build! | |
743 | let target = self.cmake_target.clone().unwrap_or("install".to_string()); | |
48663c56 | 744 | let mut cmd = Command::new(&executable); |
cc61c64b | 745 | for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) { |
8bb4bdeb XL |
746 | cmd.env(k, v); |
747 | } | |
48663c56 | 748 | |
abe05a73 XL |
749 | if let Some(flags) = makeflags { |
750 | cmd.env("MAKEFLAGS", flags); | |
751 | } | |
b7449926 XL |
752 | |
753 | cmd.arg("--build").arg("."); | |
754 | ||
755 | if !self.no_build_target { | |
756 | cmd.arg("--target").arg(target); | |
757 | } | |
48663c56 XL |
758 | |
759 | cmd.arg("--config") | |
760 | .arg(&profile) | |
761 | .arg("--") | |
762 | .args(&self.build_args) | |
b7449926 XL |
763 | .current_dir(&build); |
764 | ||
48663c56 XL |
765 | if let Some(flags) = parallel_flags { |
766 | cmd.arg(flags); | |
767 | } | |
768 | ||
b7449926 | 769 | run(&mut cmd, "cmake"); |
476ff2be SL |
770 | |
771 | println!("cargo:root={}", dst.display()); | |
48663c56 | 772 | return dst; |
476ff2be SL |
773 | } |
774 | ||
775 | fn visual_studio_generator(&self, target: &str) -> String { | |
ea8adc8c | 776 | use cc::windows_registry::{find_vs_version, VsVers}; |
041b39d2 XL |
777 | |
778 | let base = match find_vs_version() { | |
48663c56 | 779 | Ok(VsVers::Vs16) => "Visual Studio 16 2019", |
041b39d2 XL |
780 | Ok(VsVers::Vs15) => "Visual Studio 15 2017", |
781 | Ok(VsVers::Vs14) => "Visual Studio 14 2015", | |
782 | Ok(VsVers::Vs12) => "Visual Studio 12 2013", | |
48663c56 XL |
783 | Ok(_) => panic!( |
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?" | |
787 | ), | |
041b39d2 | 788 | Err(msg) => panic!(msg), |
476ff2be | 789 | }; |
dfeec247 XL |
790 | if ["i686", "x86_64", "thumbv7a", "aarch64"] |
791 | .iter() | |
792 | .any(|t| target.contains(t)) | |
793 | { | |
476ff2be | 794 | base.to_string() |
476ff2be SL |
795 | } else { |
796 | panic!("unsupported msvc target: {}", target); | |
797 | } | |
798 | } | |
476ff2be SL |
799 | |
800 | fn defined(&self, var: &str) -> bool { | |
801 | self.defines.iter().any(|&(ref a, _)| a == var) | |
802 | } | |
803 | ||
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]). | |
810 | // | |
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()); | |
476ff2be SL |
817 | let mut f = match File::open(dir.join("CMakeCache.txt")) { |
818 | Ok(f) => f, | |
819 | Err(..) => return, | |
820 | }; | |
821 | let mut u8contents = Vec::new(); | |
822 | match f.read_to_end(&mut u8contents) { | |
823 | Ok(f) => f, | |
824 | Err(..) => return, | |
825 | }; | |
826 | let contents = String::from_utf8_lossy(&u8contents); | |
827 | drop(f); | |
828 | for line in contents.lines() { | |
8bb4bdeb XL |
829 | if line.starts_with("CMAKE_HOME_DIRECTORY") { |
830 | let needs_cleanup = match line.split('=').next_back() { | |
48663c56 XL |
831 | Some(cmake_home) => fs::canonicalize(cmake_home) |
832 | .ok() | |
833 | .map(|cmake_home| cmake_home != path) | |
834 | .unwrap_or(true), | |
835 | None => true, | |
8bb4bdeb XL |
836 | }; |
837 | if needs_cleanup { | |
48663c56 XL |
838 | println!( |
839 | "detected home dir change, cleaning out entire build \ | |
840 | directory" | |
841 | ); | |
8bb4bdeb XL |
842 | fs::remove_dir_all(dir).unwrap(); |
843 | } | |
48663c56 | 844 | break; |
476ff2be SL |
845 | } |
846 | } | |
847 | } | |
848 | } | |
849 | ||
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 => { | |
48663c56 XL |
855 | fail(&format!( |
856 | "failed to execute command: {}\nis `{}` not installed?", | |
857 | e, program | |
858 | )); | |
476ff2be SL |
859 | } |
860 | Err(e) => fail(&format!("failed to execute command: {}", e)), | |
861 | }; | |
862 | if !status.success() { | |
48663c56 XL |
863 | fail(&format!( |
864 | "command did not execute successfully, got: {}", | |
865 | status | |
866 | )); | |
476ff2be SL |
867 | } |
868 | } | |
869 | ||
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()) | |
875 | } | |
876 | ||
877 | fn getenv_unwrap(v: &str) -> String { | |
878 | match env::var(v) { | |
879 | Ok(s) => s, | |
880 | Err(..) => fail(&format!("environment variable `{}` not defined", v)), | |
881 | } | |
882 | } | |
883 | ||
884 | fn fail(s: &str) -> ! { | |
885 | panic!("\n{}\n\nbuild script failed, must exit now", s) | |
886 | } |