libthemis_src/
lib.rs

1// Copyright 2018 (c) rust-themis developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Building native Themis library.
16//!
17//! This crate can be used in `[build-dependencies]` for building Themis library in **build.rs**
18//! for future inclusion into your Rust binaries as a static library.
19//!
20//! # Dependencies
21//!
22//! We expect all native Themis dependencies to be installed and correctly configured:
23//!
24//!   - C compiler
25//!   - GNU Make
26//!   - OpenSSL, LibreSSL, or BoringSSL
27//!
28//! Please refer to [the official documentation][docs] on installing and configuring dependencies.
29//!
30//! [docs]: https://github.com/cossacklabs/themis/wiki/Building-and-installing
31//!
32//! # Examples
33//!
34//! Typical usage from a `*-sys` crate looks like this:
35//!
36//! ```no_run
37//! fn main() {
38//!     #[cfg(feature = "vendored")]
39//!     libthemis_src::make();
40//!
41//!     // Go on with your usual build.rs business, pkg_config crate
42//!     // should be able to locate the local installation of Themis.
43//!     // You'll probably need to use the static library.
44//! }
45//! ```
46
47use std::env;
48use std::fs;
49use std::path::{Path, PathBuf};
50use std::process::{Command, Stdio};
51
52// Compile-time check for that we really bundle Themis source code.
53const _THEMIS_MAKEFILE: &[u8] = include_bytes!("../themis/Makefile");
54
55/// A builder (literally!) for Themis, produces [`Library`].
56///
57/// [`Library`]: struct.Library.html
58#[derive(Default)]
59pub struct Build {
60    out_dir: Option<PathBuf>,
61}
62
63/// Installed Themis library resulting from a [`Build`].
64///
65/// [`Build`]: struct.Build.html
66pub struct Library {
67    prefix: PathBuf,
68}
69
70/// Build and install Themis into the default location then tell **pkg-config** about it.
71pub fn make() {
72    Build::new().build().set_pkg_config_path();
73}
74
75/// Verifies binary dependencies of Themis build. Panics if dependencies are not satisfied.
76fn check_dependencies() {
77    fn fails_to_run(terms: &[&str]) -> bool {
78        Command::new(&terms[0])
79            .args(&terms[1..])
80            .stdout(Stdio::null())
81            .stderr(Stdio::null())
82            .status()
83            .is_err()
84    }
85
86    if fails_to_run(&["make", "--version"]) {
87        panic!(
88            "
89
90It seems your system does not have GNU make installed. Make is required
91to build Themis from source.
92
93Please install \"make\" or \"build-essential\" package and try again.
94
95        "
96        );
97    }
98
99    if fails_to_run(&["cc", "--version"]) {
100        panic!(
101            "
102
103It seems your system does not have a C compiler installed. C compiler
104is required to build Themis from source.
105
106Please install \"clang\" (or \"gcc\" and \"g++\") package and try again.
107
108        "
109        );
110    }
111
112    // TODO: check for SomethingSSL, it would be nice for the user
113}
114
115impl Build {
116    /// Prepares a new build.
117    pub fn new() -> Build {
118        Build {
119            out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("themis")),
120        }
121    }
122
123    /// Overrides output directory. Use it if OUT_DIR environment variable is not set or you want
124    /// to customize the output location.
125    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
126        self.out_dir = Some(path.as_ref().to_path_buf());
127        self
128    }
129
130    /// Builds Themis, panics on any errors.
131    pub fn build(&self) -> Library {
132        check_dependencies();
133
134        let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
135        let themis_src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("themis");
136        let themis_build_dir = out_dir.join("build");
137        let themis_install_dir = out_dir.join("install");
138
139        // Cargo requires build scripts to never write anything outside of OUT_DIR.
140        // Take care to honor this requirement. It is checked by tools during builds.
141
142        if !out_dir.exists() {
143            fs::create_dir(&out_dir).expect("mkdir themis");
144        }
145        if themis_build_dir.exists() {
146            fs::remove_dir_all(&themis_build_dir).expect("rm -r themis/build");
147        }
148        if themis_install_dir.exists() {
149            fs::remove_dir_all(&themis_install_dir).expect("rm -r themis/install");
150        }
151
152        fs::create_dir(&themis_build_dir).expect("mkdir themis/build");
153        fs::create_dir(&themis_install_dir).expect("mkdir themis/install");
154
155        // Now we can build Themis and install it properly into OUT_DIR.
156        let mut themis_build_and_install = make_cmd::make();
157        themis_build_and_install
158            .current_dir(&themis_src_dir)
159            .stdout(Stdio::null())
160            .env("BUILD_PATH", &themis_build_dir)
161            .env("PREFIX", &themis_install_dir)
162            .arg("install");
163
164        // Cargo sets DEBUG environment variable to zero in release builds, but Themis build simply
165        // checks existence of this variable. We need to unset it to get real release builds,
166        if cfg!(debug) {
167            themis_build_and_install.env("DEBUG", "1");
168        } else {
169            themis_build_and_install.env_remove("DEBUG");
170        }
171
172        let status = themis_build_and_install
173            .status()
174            .expect("failed to run Themis build");
175
176        if !status.success() {
177            panic!("Themis build failed: {}", status);
178        }
179
180        Library {
181            prefix: themis_install_dir,
182        }
183    }
184}
185
186impl Library {
187    /// Installation prefix of the Themis library.
188    pub fn prefix(&self) -> &Path {
189        &self.prefix
190    }
191
192    /// Adds installed Themis library location to PKG_CONFIG_PATH environment variable.
193    pub fn set_pkg_config_path(&self) {
194        let mut paths = env::var_os("PKG_CONFIG_PATH").unwrap_or_default();
195        if !paths.is_empty() {
196            paths.push(":");
197        }
198        paths.push(self.prefix.join("lib/pkgconfig"));
199
200        env::set_var("PKG_CONFIG_PATH", paths);
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    use std::env;
209    use std::ffi::OsStr;
210
211    #[test]
212    fn build_and_install() {
213        let temp_dir = tempfile::tempdir().expect("temporary directory");
214        let library = Build::new().out_dir(&temp_dir).build();
215
216        assert!(library.prefix().join("include/themis/themis.h").exists());
217        assert!(library.prefix().join("lib/pkgconfig/libthemis.pc").exists());
218        assert!(library.prefix().join("lib").read_dir().unwrap().count() > 0);
219    }
220
221    #[test]
222    #[allow(non_snake_case)]
223    fn build_and_install_to_OUT_DIR() {
224        let temp_dir = tempfile::tempdir().expect("temporary directory");
225        let library = with_env_var("OUT_DIR", temp_dir.path(), || Build::new().build());
226
227        assert!(library.prefix().join("include/themis/themis.h").exists());
228        assert!(library.prefix().join("lib/pkgconfig/libthemis.pc").exists());
229        assert!(library.prefix().join("lib").read_dir().unwrap().count() > 0);
230    }
231
232    #[test]
233    fn pkg_config_setting() {
234        let temp_dir = tempfile::tempdir().expect("temporary directory");
235        let library = Build::new().out_dir(&temp_dir).build();
236
237        with_env_var("PKG_CONFIG_PATH", "", || {
238            library.set_pkg_config_path();
239
240            let pkg_path = env::var("PKG_CONFIG_PATH").expect("PKG_CONFIG_PATH");
241            let prefix = library.prefix().to_str().expect("prefix").to_owned();
242            assert!(pkg_path.contains(&prefix));
243        });
244    }
245
246    fn with_env_var<K, V, F, T>(key: K, value: V, f: F) -> T
247    where
248        K: AsRef<OsStr>,
249        V: AsRef<OsStr>,
250        F: FnOnce() -> T,
251    {
252        let old_value = env::var_os(&key);
253        env::set_var(&key, value);
254        let result = f();
255        match old_value {
256            Some(old_value) => env::set_var(&key, old_value),
257            None => env::remove_var(&key),
258        }
259        result
260    }
261}