mc_build_rs/
env.rs

1// Copyright (c) 2023 The MobileCoin Foundation
2
3//! This module contains a structure wrapping up the build environment.
4
5use crate::vars::*;
6use displaydoc::Display;
7#[cfg(test)]
8use mockall::mock;
9use std::{
10    borrow::ToOwned,
11    collections::{hash_map::Iter as HashMapIter, hash_set::Iter as HashSetIter, HashMap, HashSet},
12    env::{split_paths, var, var_os, vars, VarError},
13    num::ParseIntError,
14    path::{Path, PathBuf},
15    str::FromStr,
16};
17
18/// An enumeration of target family types
19#[derive(Clone, Copy, Debug)]
20pub enum TargetFamily {
21    /// The environment is some form of unix
22    Unix,
23    /// The environment is some form of windows
24    Windows,
25    /// The environment is wasm
26    Wasm,
27}
28
29/// An enumeration of errors which can occur while parsing the target family
30/// environment variable
31#[derive(Clone, Debug, Display)]
32pub enum TargetFamilyError {
33    /// Unknown family: {0}
34    Unknown(String),
35}
36
37impl TryFrom<&str> for TargetFamily {
38    type Error = TargetFamilyError;
39
40    fn try_from(src: &str) -> Result<TargetFamily, Self::Error> {
41        match src {
42            "unix" => Ok(TargetFamily::Unix),
43            "windows" => Ok(TargetFamily::Windows),
44            "wasm" => Ok(TargetFamily::Wasm),
45            other => Err(TargetFamilyError::Unknown(other.to_owned())),
46        }
47    }
48}
49
50/// An enumeration of endianness types
51#[derive(Clone, Copy, Debug)]
52pub enum Endianness {
53    /// The target platform is little-endian
54    Little,
55    /// The target platform is big-endian
56    Big,
57}
58
59/// An enumeration of errors which can occur while parsing the endianness
60/// environment variable
61#[derive(Clone, Debug, Display)]
62pub enum EndiannessError {
63    /// Unknown endianness: {0}
64    Unknown(String),
65}
66
67impl TryFrom<&str> for Endianness {
68    type Error = EndiannessError;
69
70    fn try_from(src: &str) -> Result<Endianness, Self::Error> {
71        match src {
72            "little" => Ok(Endianness::Little),
73            "big" => Ok(Endianness::Big),
74            other => Err(EndiannessError::Unknown(other.to_owned())),
75        }
76    }
77}
78
79/// An enumeration of errors which can occur when parsing the build environment
80#[derive(Clone, Debug, Display)]
81pub enum EnvironmentError {
82    /// Environment variable {0} not readable: {1}
83    Var(String, VarError),
84    /// Endianness error: {0}
85    Endianness(EndiannessError),
86    /// Target family error: {0}
87    TargetFamily(TargetFamilyError),
88    /// Could not parse {0}: {1}
89    ParseInt(String, ParseIntError),
90    /// Output directory badly constructed: {0:?}
91    OutDir(PathBuf),
92}
93
94impl From<EndiannessError> for EnvironmentError {
95    fn from(src: EndiannessError) -> EnvironmentError {
96        EnvironmentError::Endianness(src)
97    }
98}
99
100impl From<TargetFamilyError> for EnvironmentError {
101    fn from(src: TargetFamilyError) -> EnvironmentError {
102        EnvironmentError::TargetFamily(src)
103    }
104}
105
106fn read_depvars() -> HashMap<String, String> {
107    vars()
108        .filter_map(|(mut key, value)| {
109            if key.starts_with("DEP_") {
110                key.replace_range(.."DEP_".len(), "");
111                Some((key, value))
112            } else {
113                None
114            }
115        })
116        .collect()
117}
118
119/// Collect all the cargo features currently set.
120fn read_features() -> HashSet<String> {
121    vars()
122        .filter_map(|(mut key, _value)| {
123            if key.starts_with("CARGO_FEATURE_") {
124                key.replace_range(.."CARGO_FEATURE_".len(), "");
125                while let Some(pos) = key.find('_') {
126                    key.replace_range(pos..=pos, "-");
127                }
128                key.make_ascii_lowercase();
129                Some(key)
130            } else {
131                None
132            }
133        })
134        .collect()
135}
136
137/// Parse an integer from a string
138fn parse_int_var<T: FromStr<Err = ParseIntError>>(env_var: &str) -> Result<T, EnvironmentError> {
139    var(env_var)
140        .map_err(|e| EnvironmentError::Var(env_var.to_owned(), e))?
141        .parse::<T>()
142        .map_err(|e| EnvironmentError::ParseInt(env_var.to_owned(), e))
143}
144
145/// Create a pathbuf from the contents of the given environment variable
146fn env_to_opt_pathbuf(name: &str) -> Option<PathBuf> {
147    var(name).ok().and_then(|v| {
148        if v.is_empty() {
149            None
150        } else {
151            Some(PathBuf::from(v))
152        }
153    })
154}
155
156/// A description of the current build environment
157
158#[derive(Clone, Debug)]
159pub struct Environment {
160    cargo_path: PathBuf,
161    out_path: PathBuf,
162    features: HashSet<String>,
163
164    // CARGO_MANIFEST_*
165    manifest_dir: PathBuf,
166    manifest_links: Option<String>,
167
168    // CARGO_PKG_*
169    pkg_version: String,
170    version_major: u64,
171    version_minor: u64,
172    version_patch: u64,
173    version_pre: Option<String>,
174    authors: HashSet<String>,
175    name: String,
176    description: String,
177    homepage: String,
178    repository: String,
179
180    // CARGO_CFG_*
181    debug_assertions: bool,
182    proc_macro: bool,
183    target_arch: String,
184    target_endian: Endianness,
185    target_env: String,
186    target_family: TargetFamily,
187    target_has_atomic: HashSet<String>,
188    target_has_atomic_load_store: HashSet<String>,
189    target_os: String,
190    target_pointer_width: usize,
191    target_thread_local: bool,
192    target_vendor: String,
193    target_features: HashSet<String>,
194
195    // DEP_<CRATE>_VAR
196    depvars: HashMap<String, String>,
197
198    // Other variables
199    target: String,
200    host: String,
201    num_jobs: usize,
202    opt_level: usize,
203    debug: bool,
204    profile: String,
205    rustc: PathBuf,
206    rustdoc: PathBuf,
207    linker: PathBuf,
208    locked: bool,
209
210    // Derived variables
211    target_dir: PathBuf,
212    profile_target_dir: PathBuf,
213}
214
215#[cfg(test)]
216mock! {
217    pub Environment {
218        pub fn cargo(&self) -> &Path;
219        pub fn locked(&self) -> bool;
220        pub fn profile(&self) -> &str;
221    }
222
223    impl Clone for Environment {
224        fn clone(&self) -> Self;
225    }
226}
227
228impl Default for Environment {
229    fn default() -> Environment {
230        Environment::new().expect("Could not read environment")
231    }
232}
233
234impl Environment {
235    /// Construct a new build configuration structure, or die trying.
236    pub fn new() -> Result<Environment, EnvironmentError> {
237        let out_dir = PathBuf::from(
238            var(ENV_OUT_DIR).map_err(|e| EnvironmentError::Var(ENV_OUT_DIR.to_owned(), e))?,
239        );
240        // Convert ths to path?
241        let cargo_target_dir = PathBuf::from(
242            var(ENV_CARGO_TARGET_DIR)
243                .map_err(|e| EnvironmentError::Var(ENV_CARGO_TARGET_DIR.to_owned(), e))?,
244        );
245        let target =
246            var(ENV_TARGET).map_err(|e| EnvironmentError::Var(ENV_TARGET.to_owned(), e))?;
247        let profile =
248            var(ENV_PROFILE).map_err(|e| EnvironmentError::Var(ENV_PROFILE.to_owned(), e))?;
249
250        let (target_dir, profile_target_dir) =
251            Self::get_target_profile_dir(&out_dir, &cargo_target_dir, target)
252                .ok_or_else(|| EnvironmentError::OutDir(out_dir.clone()))
253                .unwrap();
254
255        let target_has_atomic = var(ENV_CARGO_CFG_TARGET_HAS_ATOMIC)
256            .unwrap_or_default()
257            .split(',')
258            .map(ToOwned::to_owned)
259            .collect::<HashSet<String>>();
260
261        let target_has_atomic_load_store = var(ENV_CARGO_CFG_TARGET_HAS_ATOMIC_LOAD_STORE)
262            .unwrap_or_default()
263            .split(',')
264            .map(ToOwned::to_owned)
265            .collect::<HashSet<String>>();
266
267        let linker = env_to_opt_pathbuf(ENV_RUSTC_LINKER)
268            .or_else(|| env_to_opt_pathbuf(ENV_LD))
269            .or_else(|| {
270                Some(
271                    var_os(ENV_PATH)
272                        .and_then(|paths| {
273                            split_paths(&paths)
274                                .filter_map(|dir| {
275                                    let full_path = dir.join("ld");
276                                    if full_path.is_file() {
277                                        Some(full_path)
278                                    } else {
279                                        None
280                                    }
281                                })
282                                .next()
283                        })
284                        .expect("Could not find `ld` in path environment variable"),
285                )
286            })
287            .expect("Could not find linker to use");
288
289        let features = read_features();
290        let depvars = read_depvars();
291
292        Ok(Self {
293            // CARGO_*
294            cargo_path: var(ENV_CARGO)
295                .map_err(|e| EnvironmentError::Var(ENV_CARGO.to_owned(), e))?
296                .into(),
297            locked: var(ENV_CARGO_LOCKED).is_ok(),
298
299            // CARGO_MANIFEST_*
300            manifest_dir: var(ENV_CARGO_MANIFEST_DIR)
301                .map_err(|e| EnvironmentError::Var(ENV_CARGO_MANIFEST_DIR.to_owned(), e))?
302                .into(),
303            manifest_links: var(ENV_CARGO_MANIFEST_LINKS).ok(),
304
305            // Other variables
306            debug: var(ENV_DEBUG).is_ok(),
307            host: var(ENV_HOST).map_err(|e| EnvironmentError::Var(ENV_HOST.to_owned(), e))?,
308            linker,
309            num_jobs: parse_int_var(ENV_NUM_JOBS)?,
310            out_path: out_dir,
311            opt_level: parse_int_var(ENV_OPT_LEVEL)?,
312            profile,
313            rustc: var(ENV_RUSTC)
314                .map_err(|e| EnvironmentError::Var(ENV_RUSTC.to_owned(), e))?
315                .into(),
316            rustdoc: var(ENV_RUSTDOC)
317                .map_err(|e| EnvironmentError::Var(ENV_RUSTDOC.to_owned(), e))?
318                .into(),
319            target: var(ENV_TARGET).map_err(|e| EnvironmentError::Var(ENV_TARGET.to_owned(), e))?,
320
321            // CARGO_FEATURE_*
322            features,
323            // DEP_<crate>_<var>
324            depvars,
325
326            // CARGO_PKG_*
327            pkg_version: var(ENV_CARGO_PKG_VERSION)
328                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_VERSION.to_owned(), e))?,
329            version_major: parse_int_var(ENV_CARGO_PKG_VERSION_MAJOR)?,
330            version_minor: parse_int_var(ENV_CARGO_PKG_VERSION_MINOR)?,
331            version_patch: parse_int_var(ENV_CARGO_PKG_VERSION_PATCH)?,
332            version_pre: match var(ENV_CARGO_PKG_VERSION_PRE) {
333                Ok(value) => {
334                    if value.is_empty() {
335                        None
336                    } else {
337                        Some(value)
338                    }
339                }
340                Err(VarError::NotPresent) => None,
341                Err(other) => {
342                    return Err(EnvironmentError::Var(
343                        ENV_CARGO_PKG_VERSION_PRE.to_owned(),
344                        other,
345                    ))
346                }
347            },
348            authors: var(ENV_CARGO_PKG_AUTHORS)
349                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_AUTHORS.to_owned(), e))?
350                .split(':')
351                .map(ToOwned::to_owned)
352                .collect(),
353            name: var(ENV_CARGO_PKG_NAME)
354                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_NAME.to_owned(), e))?,
355            description: var(ENV_CARGO_PKG_DESCRIPTION)
356                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_DESCRIPTION.to_owned(), e))?,
357            homepage: var(ENV_CARGO_PKG_HOMEPAGE)
358                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_HOMEPAGE.to_owned(), e))?,
359            repository: var(ENV_CARGO_PKG_REPOSITORY)
360                .map_err(|e| EnvironmentError::Var(ENV_CARGO_PKG_REPOSITORY.to_owned(), e))?,
361
362            // CARGO_CFG_*
363            debug_assertions: var(ENV_CARGO_CFG_DEBUG_ASSERTIONS).is_ok(),
364            proc_macro: var(ENV_CARGO_CFG_PROC_MACRO).is_ok(),
365            target_arch: var(ENV_CARGO_CFG_TARGET_ARCH)
366                .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_ARCH.to_owned(), e))?,
367            target_endian: Endianness::try_from(
368                var(ENV_CARGO_CFG_TARGET_ENDIAN)
369                    .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_ENDIAN.to_owned(), e))?
370                    .as_str(),
371            )?,
372            target_env: var(ENV_CARGO_CFG_TARGET_ENV)
373                .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_ENV.to_owned(), e))?,
374            target_family: TargetFamily::try_from(
375                var(ENV_CARGO_CFG_TARGET_FAMILY)
376                    .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_FAMILY.to_owned(), e))?
377                    .as_ref(),
378            )?,
379            target_features: var(ENV_CARGO_CFG_TARGET_FEATURE)
380                .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_FEATURE.to_owned(), e))?
381                .split(',')
382                .map(ToOwned::to_owned)
383                .collect(),
384            target_has_atomic,
385            target_has_atomic_load_store,
386            target_os: var(ENV_CARGO_CFG_TARGET_OS)
387                .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_OS.to_owned(), e))?,
388            target_pointer_width: parse_int_var(ENV_CARGO_CFG_TARGET_POINTER_WIDTH)?,
389            target_thread_local: var(ENV_CARGO_CFG_TARGET_THREAD_LOCAL).is_ok(),
390            target_vendor: var(ENV_CARGO_CFG_TARGET_VENDOR)
391                .map_err(|e| EnvironmentError::Var(ENV_CARGO_CFG_TARGET_VENDOR.to_owned(), e))?,
392
393            // Derived variables
394            target_dir,
395            profile_target_dir,
396        })
397    }
398
399    fn get_target_profile_dir(
400        out_dir: &Path,
401        target_dir: &PathBuf,
402        target: String,
403    ) -> Option<(PathBuf, PathBuf)> {
404        let mut ancestor = out_dir.ancestors().peekable();
405        while let Some(current_path) = ancestor.next() {
406            if let Some(parent_path) = ancestor.peek() {
407                if !target.is_empty() && parent_path.ends_with(&target)
408                    || !target_dir.as_os_str().is_empty() && parent_path.ends_with(target_dir)
409                    || parent_path.ends_with("target")
410                {
411                    let tuple = (PathBuf::from(parent_path), PathBuf::from(current_path));
412
413                    return Some(tuple);
414                }
415            }
416        }
417        None
418    }
419
420    /// Get the path to the cargo executables
421    pub fn cargo(&self) -> &Path {
422        &self.cargo_path
423    }
424
425    /// Get whether cargo was invoked with the `--locked` flag
426    pub fn locked(&self) -> bool {
427        self.locked
428    }
429
430    /// Get a reference to a hash set of enabled cargo features (as
431    /// `lower-kebab-case` strings)
432    pub fn features(&self) -> HashSetIter<String> {
433        self.features.iter()
434    }
435
436    /// Get whether a feature is enabled or not.
437    ///
438    /// Feature names are normalized into `lower-kebab-case` (as opposed to
439    /// `UPPER_SNAKE_CASE`).
440    pub fn feature(&self, feature: &str) -> bool {
441        self.features.contains(feature)
442    }
443
444    /// Get a reference to a hash map of variables injected by the current
445    /// crate's dependencies
446    pub fn depvars(&self) -> HashMapIter<String, String> {
447        self.depvars.iter()
448    }
449
450    /// Get the contents of a particular depvar, if one is provided.
451    pub fn depvar(&self, var: &str) -> Option<&str> {
452        self.depvars.get(var).map(String::as_str)
453    }
454
455    /// Get the directory where the current `Cargo.toml` resides
456    pub fn dir(&self) -> &Path {
457        &self.manifest_dir
458    }
459
460    /// Get the string contents of this crate's `links` key
461    pub fn links(&self) -> Option<&str> {
462        self.manifest_links.as_deref()
463    }
464
465    /// Get whether debug is enabled on this build
466    pub fn debug(&self) -> bool {
467        self.debug
468    }
469
470    /// Get the hostname of the build
471    pub fn host(&self) -> &str {
472        &self.host
473    }
474
475    /// Get the path to the linker executable being used
476    pub fn linker(&self) -> &Path {
477        &self.linker
478    }
479
480    /// Get the number of jobs which can be run in parallel
481    pub fn num_jobs(&self) -> usize {
482        self.num_jobs
483    }
484
485    /// Get the output directory path
486    pub fn out_dir(&self) -> &Path {
487        &self.out_path
488    }
489
490    /// Get the optimization level
491    pub fn opt_level(&self) -> usize {
492        self.opt_level
493    }
494
495    /// Get the build profile as a string
496    pub fn profile(&self) -> &str {
497        &self.profile
498    }
499
500    /// Get the path to the rustc compiler being used
501    pub fn rustc(&self) -> &Path {
502        &self.rustc
503    }
504
505    /// Get the path to the rustdoc executable being used
506    pub fn rustdoc(&self) -> &Path {
507        &self.rustdoc
508    }
509
510    /// Get the target triple string
511    pub fn target(&self) -> &str {
512        &self.target
513    }
514
515    /// Get the package version string
516    pub fn version(&self) -> &str {
517        &self.pkg_version
518    }
519
520    /// Get the package version major number
521    pub fn version_major(&self) -> u64 {
522        self.version_major
523    }
524
525    /// Get the package version minor number
526    pub fn version_minor(&self) -> u64 {
527        self.version_minor
528    }
529
530    /// Get the package version patch number
531    pub fn version_patch(&self) -> u64 {
532        self.version_patch
533    }
534
535    /// Get the package version pre-release number
536    pub fn version_pre(&self) -> Option<String> {
537        self.version_pre.clone()
538    }
539
540    /// Get a reference to a hash set of package author strings
541    pub fn authors(&self) -> &HashSet<String> {
542        &self.authors
543    }
544
545    /// Get the name of the package of the current package
546    pub fn name(&self) -> &str {
547        &self.name
548    }
549
550    /// Get the description of the current package
551    pub fn description(&self) -> &str {
552        &self.description
553    }
554
555    /// Get the homepage of the current package
556    pub fn homepage(&self) -> &str {
557        &self.homepage
558    }
559
560    /// Get the repository of the current package
561    pub fn repository(&self) -> &str {
562        &self.repository
563    }
564
565    /// Get whether or not debug assertions are enabled in this build
566    pub fn debug_assertions(&self) -> bool {
567        self.debug_assertions
568    }
569
570    /// Get whether or not proc macros are enabled in this build
571    pub fn proc_macro(&self) -> bool {
572        self.proc_macro
573    }
574
575    /// Get the target architecture
576    pub fn target_arch(&self) -> &str {
577        &self.target_arch
578    }
579
580    /// Get the endianness
581    pub fn target_endian(&self) -> Endianness {
582        self.target_endian
583    }
584
585    /// Get the target environment
586    pub fn target_env(&self) -> &str {
587        &self.target_env
588    }
589
590    /// Get the target architecture family
591    pub fn target_family(&self) -> TargetFamily {
592        self.target_family
593    }
594
595    /// Get a reference to the target feature set
596    pub fn target_features(&self) -> &HashSet<String> {
597        &self.target_features
598    }
599
600    /// Get a list of types which support atomic operations on the target
601    /// platform
602    pub fn target_has_atomic(&self) -> &HashSet<String> {
603        &self.target_has_atomic
604    }
605
606    /// Get a list of types which support atomic load and store
607    pub fn target_has_atomic_load_store(&self) -> &HashSet<String> {
608        &self.target_has_atomic_load_store
609    }
610
611    /// Get the target OS
612    pub fn target_os(&self) -> &str {
613        &self.target_os
614    }
615
616    /// Get the target pointer width
617    pub fn target_pointer_width(&self) -> usize {
618        self.target_pointer_width
619    }
620
621    /// Get whether thread-local storage is available
622    pub fn target_thread_local(&self) -> bool {
623        self.target_thread_local
624    }
625
626    /// Get the target triple vendor
627    pub fn target_vendor(&self) -> &str {
628        &self.target_vendor
629    }
630
631    /// Get the target directory (i.e. the `--target-dir` flag)
632    pub fn target_dir(&self) -> &Path {
633        &self.target_dir
634    }
635
636    /// Get the profile target directory
637    pub fn profile_target_dir(&self) -> &Path {
638        &self.profile_target_dir
639    }
640}
641
642#[cfg(test)]
643mod tests {
644    use super::*;
645    use fluent_asserter::prelude::*;
646
647    fn setup_env(env_values: HashMap<&str, Option<&str>>) -> Environment {
648        temp_env::with_vars(
649            [
650                (
651                    ENV_OUT_DIR,
652                    if env_values.get(ENV_OUT_DIR).is_some() {
653                        env_values.get(ENV_OUT_DIR).cloned().unwrap()
654                    } else {
655                        Some("/path_to_out_dir")
656                    },
657                ),
658                (
659                    ENV_TARGET,
660                    if env_values.get(ENV_TARGET).is_some() {
661                        env_values.get(ENV_TARGET).cloned().unwrap()
662                    } else {
663                        Some("target")
664                    },
665                ),
666                (
667                    ENV_PROFILE,
668                    if env_values.get(ENV_PROFILE).is_some() {
669                        env_values.get(ENV_PROFILE).cloned().unwrap()
670                    } else {
671                        Some("profile")
672                    },
673                ),
674                (
675                    ENV_CARGO,
676                    if env_values.get(ENV_CARGO).is_some() {
677                        env_values.get(ENV_CARGO).cloned().unwrap()
678                    } else {
679                        Some("/path_to_cargo")
680                    },
681                ),
682                (ENV_HOST, Some("host")),
683                (ENV_NUM_JOBS, Some("11")),
684                (ENV_OPT_LEVEL, Some("2")),
685                (ENV_RUSTC, Some("rustc")),
686                (ENV_RUSTDOC, Some("rustdoc")),
687                (
688                    ENV_CARGO_TARGET_DIR,
689                    if env_values.get(ENV_CARGO_TARGET_DIR).is_some() {
690                        env_values.get(ENV_CARGO_TARGET_DIR).cloned().unwrap()
691                    } else {
692                        Some("/path_to_target_dir")
693                    },
694                ),
695                (ENV_CARGO_PKG_VERSION, Some("2.1.0-pre0")),
696                (ENV_CARGO_PKG_AUTHORS, Some("MobileCoin")),
697                (ENV_CARGO_PKG_NAME, Some("mc-build-rs")),
698                (ENV_CARGO_PKG_DESCRIPTION, Some("")),
699                (ENV_CARGO_PKG_HOMEPAGE, Some("")),
700                (ENV_CARGO_PKG_REPOSITORY, Some("")),
701                (ENV_CARGO_CFG_TARGET_ARCH, Some("x86_64")),
702                (ENV_CARGO_CFG_TARGET_ENDIAN, Some("little")),
703                (ENV_CARGO_CFG_TARGET_ENV, Some("")),
704                (ENV_CARGO_CFG_TARGET_FAMILY, Some("unix")),
705                (ENV_CARGO_CFG_TARGET_FEATURE, Some("adx,aes,avx,avx2,")),
706                (ENV_CARGO_CFG_TARGET_OS, Some("linux")),
707                (ENV_CARGO_CFG_TARGET_POINTER_WIDTH, Some("64")),
708                (ENV_CARGO_CFG_TARGET_VENDOR, Some("unknown")),
709            ],
710            || {
711                return Environment::default();
712            },
713        )
714    }
715
716    #[test]
717    fn init_env() {
718        let expected_out_dir = "/x86_64-unknown-linux-gnu/path_to_out_directory";
719        let expected_target = "x86_64-unknown-linux-gnu";
720        let expected_cargo_path = "/path_to_cargo";
721        let expected_profile = "debug";
722        let expected_cargo_package_version = "2.1.0-pre0";
723
724        let mut values = HashMap::new();
725        values.insert(ENV_OUT_DIR, Some(expected_out_dir));
726        values.insert(ENV_TARGET, Some(expected_target));
727        values.insert(ENV_CARGO, Some(expected_cargo_path));
728        values.insert(ENV_PROFILE, Some(expected_profile));
729        values.insert(ENV_CARGO_PKG_VERSION, Some(expected_cargo_package_version));
730
731        let env = setup_env(values);
732
733        assert_eq!(
734            env.out_dir(),
735            PathBuf::from_str(expected_out_dir).expect("Fail")
736        );
737        assert_eq!(env.target, expected_target);
738        assert_eq!(env.profile, expected_profile);
739        assert_eq!(
740            env.cargo_path,
741            PathBuf::from_str(expected_cargo_path).expect("Fail")
742        );
743        assert_eq!(env.pkg_version, expected_cargo_package_version);
744    }
745
746    #[test]
747    fn match_target_type() {
748        let out_dir = "path_to/target/x86_64-unknown-linux-gnu/debug/path/to/out";
749        let target = "x86_64-unknown-linux-gnu";
750        let expected_target_dir = "path_to/target/x86_64-unknown-linux-gnu/";
751        let expected_profile_dir = "path_to/target/x86_64-unknown-linux-gnu/debug";
752
753        let mut values = HashMap::new();
754        values.insert(ENV_OUT_DIR, Some(out_dir));
755        values.insert(ENV_TARGET, Some(target));
756
757        let env = setup_env(values);
758
759        assert_eq!(
760            env.target_dir,
761            PathBuf::from_str(expected_target_dir).expect("Fail")
762        );
763        assert_eq!(
764            env.profile_target_dir,
765            PathBuf::from_str(expected_profile_dir).expect("Fail")
766        );
767    }
768
769    #[test]
770    fn match_target_dir() {
771        let target_dir = "path_to/target";
772        let out_dir = "path_to/target/debug/path/to/out";
773        let target = "x86_64-unknown-linux-gnu";
774        let expected_target_dir = "path_to/target";
775        let expected_profile_dir = "path_to/target/debug";
776
777        let mut values = HashMap::new();
778        values.insert(ENV_OUT_DIR, Some(out_dir));
779        values.insert(ENV_CARGO_TARGET_DIR, Some(target_dir));
780        values.insert(ENV_TARGET, Some(target));
781
782        let env = setup_env(values);
783
784        assert_eq!(
785            env.target_dir,
786            PathBuf::from_str(expected_target_dir).expect("Fail")
787        );
788        assert_eq!(
789            env.profile_target_dir,
790            PathBuf::from_str(expected_profile_dir).expect("Fail")
791        );
792    }
793
794    #[test]
795    fn match_target_string() {
796        let target_dir = "";
797        let out_dir = "path_to/target/debug/path/to/out";
798        let target = "x86_64-unknown-linux-gnu";
799        let expected_target_dir = "path_to/target/";
800        let expected_profile_dir = "path_to/target/debug";
801
802        let mut values = HashMap::new();
803        values.insert(ENV_OUT_DIR, Some(out_dir));
804        values.insert(ENV_CARGO_TARGET_DIR, Some(target_dir));
805        values.insert(ENV_TARGET, Some(target));
806
807        let env = setup_env(values);
808
809        assert_eq!(
810            env.target_dir,
811            PathBuf::from_str(expected_target_dir).expect("Fail")
812        );
813        assert_eq!(
814            env.profile_target_dir,
815            PathBuf::from_str(expected_profile_dir).expect("Fail")
816        );
817    }
818
819    #[test]
820    fn err_out_dir() {
821        let target_dir = "different/path_to_target";
822        let out_dir = "path_to/debug/path/to/out";
823        let target = "x86_64-unknown-linux-gnu";
824
825        let mut values = HashMap::new();
826        values.insert(ENV_OUT_DIR, Some(out_dir));
827        values.insert(ENV_CARGO_TARGET_DIR, Some(target_dir));
828        values.insert(ENV_TARGET, Some(target));
829
830        assert_that_code!(|| setup_env(values))
831            .panics()
832            .with_message(
833            "called `Result::unwrap()` on an `Err` value: OutDir(\"path_to/debug/path/to/out\")",
834        );
835    }
836}