bind_builder/types/
cmake_builder.rs

1use std::{env, fs};
2use std::ffi::OsStr;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use cmake::Config;
6use crate::variables::{get_profile, target_directory};
7
8fn cmake_executable() -> String {
9    env::var("CMAKE")
10        .unwrap_or_else(|_| String::from("cmake"))
11}
12
13/// Builder for cloning, configuring, building and installing a CMake project.
14pub struct CMakeBuilder {
15    name: String,
16    cmake_config: Option<Config>,
17    build_directory: Option<PathBuf>,
18    install_directory: PathBuf,
19    build_target: Option<String>
20}
21
22impl CMakeBuilder {
23
24    /// Create a new `CMakeBuilder` from a git repository.
25    ///
26    /// This function uses the git command therefore it will inherit the git configuration and
27    /// credentials from your system.
28    pub fn clone(
29        name: &str,
30        url: &str,
31        tag: &str,
32    ) -> CMakeBuilder {
33
34        let target_directory = target_directory();
35        let clone_directory = target_directory.parent().unwrap()
36            .join("git")
37            .join(name);
38
39        // Setup temp repository if it does not exist, instead of cloning we do this to
40        // reduce the amount of stuff we have to pull.
41        if !clone_directory.exists() {
42            fs::create_dir_all(clone_directory.as_path())
43                .expect("Could not create directory, does the path exist?");
44
45            Command::new("git")
46                .arg("init")
47                .current_dir(clone_directory.as_path())
48                .status()
49                .expect("Could not init repo, is git installed?");
50
51            Command::new("git")
52                .arg("remote")
53                .arg("add")
54                .arg("origin")
55                .arg(url)
56                .current_dir(clone_directory.as_path())
57                .status()
58                .expect("Could not add remote, is git installed?");
59        }
60
61        Command::new("git")
62            .arg("fetch")
63            .arg("origin")
64            .arg(tag)
65            .current_dir(clone_directory.as_path())
66            .status()
67            .expect("Could not fetch repo, is git installed?");
68
69        Command::new("git")
70            .arg("reset")
71            .arg("--hard")
72            .arg(tag)
73            .current_dir(clone_directory.as_path())
74            .status()
75            .expect("Could not checkout tag, is git installed?");
76
77        Command::new("git")
78            .arg("submodule")
79            .arg("update")
80            .arg("--init")
81            .arg("--recursive")
82            .current_dir(clone_directory.as_path())
83            .status()
84            .expect("Could not init submodules, is git installed?");
85
86        CMakeBuilder::from(name, clone_directory.as_path())
87    }
88
89    /// Create a new `CMakeBuilder` from an existing cmake project.
90    pub fn from(
91        name: &str,
92        path: &Path,
93    ) -> CMakeBuilder {
94
95        // Windows does not like canonicalize on some paths. It will result in cl.exe
96        // failing to use the path.
97        // https://github.com/rust-lang/rust/issues/42869
98        // https://github.com/alexcrichton/cc-rs/issues/169
99        let absolute_path = if cfg!(windows) {
100            path.to_path_buf()
101        } else {
102            fs::canonicalize(path)
103                .expect("Path not found, make sure the build directory exists.")
104        };
105
106        let configure_directory = absolute_path
107            .join(format!("cmake-bind-builder-{}", get_profile().as_str()));
108
109        let install_directory = configure_directory
110            .join("install");
111
112        let mut project = CMakeBuilder {
113            name: name.to_string(),
114            cmake_config: Some(Config::new(absolute_path)),
115            build_directory: None,
116            install_directory: install_directory.clone(),
117            build_target: None
118        };
119
120        project.cmake_config.as_mut().unwrap().out_dir(configure_directory);
121        project.cmake_config.as_mut().unwrap().define("CMAKE_SKIP_INSTALL_ALL_DEPENDENCY", "true");
122
123        project
124    }
125
126    /// Create a new `CMakeBuilder` from an existing cmake build directory.
127    pub fn from_build_directory(
128        name: &str,
129        build_path: &Path,
130    ) -> CMakeBuilder {
131
132        // Windows does not like canonicalize on some paths. It will result in cl.exe
133        // failing to use the path.
134        // https://github.com/rust-lang/rust/issues/42869
135        // https://github.com/alexcrichton/cc-rs/issues/169
136        let absolute_path = if cfg!(windows) {
137            build_path.to_path_buf()
138        } else {
139            fs::canonicalize(build_path)
140                .expect("Path not found, make sure the build directory exists.")
141        };
142
143        let install_directory = absolute_path
144            .join(format!("cmake-bind-builder-{}", get_profile().as_str()))
145            .join("install");
146
147        let project = CMakeBuilder {
148            name: name.to_string(),
149            cmake_config: None,
150            build_directory: Some(absolute_path),
151            install_directory: install_directory.clone(),
152            build_target: None
153        };
154
155        project
156    }
157
158    /// Sets the build-tool generator (`-G`) for this compilation.
159    ///
160    /// If unset, this crate will use the `CMAKE_GENERATOR` environment variable
161    /// if set. Otherwise, it will guess the best generator to use based on the
162    /// build target.
163    pub fn generator<T: AsRef<OsStr>>(&mut self, generator: T) -> &mut CMakeBuilder {
164        if let Some(config) = self.cmake_config.as_mut() {
165            config.generator(generator);
166        }
167
168        self
169    }
170
171    /// Sets the toolset name (-T) if supported by generator.
172    /// Can be used to compile with CLang/LLV instead of msvc when Visual Studio generator is selected.
173    ///
174    /// If unset, will use the default toolset of the selected generator.
175    pub fn generator_toolset<T: AsRef<OsStr>>(&mut self, toolset_name: T) -> &mut CMakeBuilder {
176        if let Some(config) = self.cmake_config.as_mut() {
177            config.generator_toolset(toolset_name);
178        }
179
180        self
181    }
182
183    /// Adds a custom flag to pass down to the C compiler, supplementing those
184    /// that this library already passes.
185    pub fn cflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut CMakeBuilder {
186        if let Some(config) = self.cmake_config.as_mut() {
187            config.cflag(flag);
188        }
189
190        self
191    }
192
193    /// Adds a custom flag to pass down to the C++ compiler, supplementing those
194    /// that this library already passes.
195    pub fn cxxflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut CMakeBuilder {
196        if let Some(config) = self.cmake_config.as_mut() {
197            config.cxxflag(flag);
198        }
199
200        self
201    }
202
203    /// Adds a custom flag to pass down to the ASM compiler, supplementing those
204    /// that this library already passes.
205    pub fn asmflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut CMakeBuilder {
206        if let Some(config) = self.cmake_config.as_mut() {
207            config.asmflag(flag);
208        }
209
210        self
211    }
212
213    /// Adds a new `-D` flag to pass to cmake during the generation step.
214    pub fn define<K, V>(&mut self, k: K, v: V) -> &mut CMakeBuilder
215        where
216            K: AsRef<OsStr>,
217            V: AsRef<OsStr>,
218    {
219        if let Some(config) = self.cmake_config.as_mut() {
220            config.define(k, v);
221        }
222
223        self
224    }
225
226    /// Registers a dependency for this compilation on the native library built
227    /// by Cargo previously.
228    ///
229    /// This registration will modify the `CMAKE_PREFIX_PATH` environment
230    /// variable for the build system generation step.
231    pub fn register_dep(&mut self, dep: &str) -> &mut CMakeBuilder {
232        if let Some(config) = self.cmake_config.as_mut() {
233            config.register_dep(dep);
234        }
235
236        self
237    }
238
239    /// Sets the target triple for this compilation.
240    ///
241    /// This is automatically scraped from `$TARGET` which is set for Cargo
242    /// build scripts so it's not necessary to call this from a build script.
243    pub fn target(&mut self, target: &str) -> &mut CMakeBuilder {
244        if let Some(config) = self.cmake_config.as_mut() {
245            config.target(target);
246        }
247
248        self
249    }
250
251    /// Sets the host triple for this compilation.
252    ///
253    /// This is automatically scraped from `$HOST` which is set for Cargo
254    /// build scripts so it's not necessary to call this from a build script.
255    pub fn host(&mut self, host: &str) -> &mut CMakeBuilder {
256        if let Some(config) = self.cmake_config.as_mut() {
257            config.host(host);
258        }
259
260        self
261    }
262
263    /// Sets the `CMAKE_BUILD_TYPE=build_type` variable.
264    ///
265    /// By default, this value is automatically inferred from Rust's compilation
266    /// profile as follows:
267    ///
268    /// * if `opt-level=0` then `CMAKE_BUILD_TYPE=Debug`,
269    /// * if `opt-level={1,2,3}` and:
270    ///   * `debug=false` then `CMAKE_BUILD_TYPE=Release`
271    ///   * otherwise `CMAKE_BUILD_TYPE=RelWithDebInfo`
272    /// * if `opt-level={s,z}` then `CMAKE_BUILD_TYPE=MinSizeRel`
273    pub fn profile(&mut self, profile: &str) -> &mut CMakeBuilder {
274        if let Some(config) = self.cmake_config.as_mut() {
275            config.profile(profile);
276        }
277
278        self
279    }
280
281    /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools.
282    ///
283    /// This option defaults to `false`, and affect only msvc targets.
284    pub fn static_crt(&mut self, static_crt: bool) -> &mut CMakeBuilder {
285        if let Some(config) = self.cmake_config.as_mut() {
286            config.static_crt(static_crt);
287        }
288
289        self
290    }
291
292    /// Add an argument to the `cmake` configure step
293    pub fn configure_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut CMakeBuilder {
294        if let Some(config) = self.cmake_config.as_mut() {
295            config.configure_arg(arg);
296        }
297
298        self
299    }
300
301    /// Add an argument to the final `cmake` build step
302    pub fn build_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut CMakeBuilder {
303        if let Some(config) = self.cmake_config.as_mut() {
304            config.build_arg(arg);
305        }
306
307        self
308    }
309
310    /// Configure an environment variable for the `cmake` processes spawned by
311    /// this crate in the `build` step.
312    pub fn env<K, V>(&mut self, key: K, value: V) -> &mut CMakeBuilder
313        where
314            K: AsRef<OsStr>,
315            V: AsRef<OsStr>,
316    {
317        if let Some(config) = self.cmake_config.as_mut() {
318            config.env(key, value);
319        }
320
321        self
322    }
323
324    /// Forces CMake to always run before building the custom target.
325    ///
326    /// In some cases, when you have a big project, you can disable
327    /// subsequents runs of cmake to make `cargo build` faster.
328    pub fn always_configure(&mut self, always_configure: bool) -> &mut CMakeBuilder {
329        if let Some(config) = self.cmake_config.as_mut() {
330            config.always_configure(always_configure);
331        }
332
333        self
334    }
335
336    /// Sets very verbose output.
337    pub fn very_verbose(&mut self, value: bool) -> &mut CMakeBuilder {
338        if let Some(config) = self.cmake_config.as_mut() {
339            config.very_verbose(value);
340        }
341
342        self
343    }
344
345    /// Specify the build target for the final `cmake` build step, this will
346    /// default to all.
347    pub fn build_target(
348        &mut self,
349        target: &str
350    ) -> &mut CMakeBuilder {
351        self.build_target = Some(target.to_string());
352        self
353    }
354
355    /// Run this configuration, compiling the library with all the configured
356    /// options.
357    ///
358    /// This will run both the build system generator command and the
359    /// command to build the library.
360    pub fn build(&mut self) -> CMakeBuilder {
361
362        let build_directory = match self.cmake_config.as_mut() {
363            Some(config) => {
364                config.build_target(
365                    self.build_target.clone().unwrap_or("all".to_string()).as_str()
366                )
367                    // We also need to set CMAKE_INSTALL_PREFIX while building otherwise the
368                    // cmake crate will default and override with an incorrect path.
369                    .define("CMAKE_INSTALL_PREFIX", self.install_directory.clone().to_str().unwrap())
370
371                    .build()
372                    .join("build")
373            },
374            None => {
375                self.build_directory.clone()
376                    .expect("Could not find build directory argument, is it set?")
377            }
378        };
379
380        Command::new(cmake_executable())
381            // Actual install command
382            .arg("--install")
383            .arg(".")
384
385            .arg("--prefix")
386            .arg(self.install_directory.clone().to_str().unwrap())
387
388            .current_dir(build_directory.clone())
389            .status()
390            .expect("Could not install repo, is cmake installed?");
391
392        // Make a new object. Since we can't clone/copy cmake::Config :(
393        let name = self.name.clone();
394        let install_directory = self.install_directory.clone();
395        let build_target = self.build_target.clone();
396
397        CMakeBuilder {
398            name,
399            cmake_config: None,
400            build_directory: Some(build_directory.clone()),
401            install_directory,
402            build_target
403        }
404    }
405
406    pub (crate) fn get_install_directory(&self) -> &PathBuf {
407        &self.install_directory
408    }
409
410    pub (crate) fn get_build_target(&self) -> &Option<String> { &self.build_target }
411}