1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use {
    crate::{cargo::OptLevel, mei::Mei},
    std::{
        path::{Path, PathBuf},
        sync::OnceLock,
    },
};

impl OptLevel {
    pub fn current() -> Self {
        Mei::get().vars().opt_level
    }
}

pub fn target_dir() -> &'static Path {
    &Mei::get().vars().target_dir
}

pub fn root_dir() -> &'static Path {
    &Mei::get().vars().root_dir
}

pub fn bin_dir() -> &'static Path {
    &Mei::get().vars().bin_dir
}

pub fn mei_dir() -> &'static Path {
    Mei::get().vars().make_mei_dir()
}

pub(crate) struct Vars {
    opt_level: OptLevel,
    root_dir: PathBuf,
    bin_dir: PathBuf,
    target_dir: PathBuf,
    mei_dir: OnceLock<PathBuf>,
}

impl Vars {
    pub fn new() -> Self {
        let opt_level = {
            let level = var("OPT_LEVEL");
            match OptLevel::from_str(&level) {
                Some(level) => level,
                None => panic!("unknown OPT_LEVEL value: {level}"),
            }
        };

        let out_dir = PathBuf::from(var("OUT_DIR"));
        let Some(target_dir) = get_target_dir(&out_dir) else {
            panic!("failed to find target directory");
        };

        let root_dir = match target_dir.parent() {
            Some(root_dir) => root_dir.to_owned(),
            None => panic!("failed to find root directory"),
        };

        let bin_dir = root_dir.join("bin");

        Self {
            opt_level,
            target_dir,
            root_dir,
            bin_dir,
            mei_dir: OnceLock::new(),
        }
    }

    fn make_mei_dir(&self) -> &Path {
        self.mei_dir.get_or_init(|| {
            let mei = target_dir().join("mei");
            crate::fs::create_dir(&mei);
            mei
        })
    }
}

/// Returns the target directory.
///
/// Currently there is no direct way to get the path, so a workaround is used.
/// The problem discussion: <https://github.com/rust-lang/cargo/issues/9661>
fn get_target_dir(mut current: &Path) -> Option<PathBuf> {
    let skip_triple = var("TARGET") == var("HOST");
    let skip_parent_dirs = if skip_triple { 4 } else { 5 };

    for _ in 0..skip_parent_dirs {
        current = current.parent()?;
    }

    Some(PathBuf::from(current))
}

fn var(key: &str) -> String {
    use std::env::{self, VarError};

    match env::var(key) {
        Ok(var) => var,
        Err(VarError::NotPresent) => panic!("the {key} variable should be set"),
        Err(VarError::NotUnicode(var)) => {
            panic!("the {key} variable should be utf-8 encoded, but {var:?} is not")
        }
    }
}