cargo_wasix/
config.rs

1use std::path::PathBuf;
2
3use crate::{tool_path::ToolPath, Cache};
4use anyhow::Result;
5use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
6
7pub struct Config {
8    cache: Option<Cache>,
9    verbose: bool,
10    choice: ColorChoice,
11    pub is_offline: bool,
12}
13
14impl Config {
15    pub fn new() -> Config {
16        Config {
17            cache: None,
18            verbose: false,
19            choice: if atty::is(atty::Stream::Stderr) {
20                ColorChoice::Auto
21            } else {
22                ColorChoice::Never
23            },
24            // Offline env var disables toolchain downloads and update checks.
25            is_offline: std::env::var("CARGO_WASIX_OFFLINE").is_ok_and(|v| v == "1" || v == "true"),
26        }
27    }
28
29    pub fn data_dir() -> Result<PathBuf, anyhow::Error> {
30        let dir = if let Ok(dir) = std::env::var("WASIX_DATA_DIR") {
31            dir.into()
32        } else if let Some(root) = dirs::data_dir() {
33            root.join("cargo-wasix")
34        } else if let Some(home) = dirs::home_dir() {
35            home.join(".cargo-wasix")
36        } else {
37            anyhow::bail!("Could not determine cargo-wasix data dir. set WASIX_DATA_DIR env var");
38        };
39
40        Ok(dir)
41    }
42
43    pub fn cache_dir() -> Result<PathBuf, anyhow::Error> {
44        let dir = if let Ok(dir) = std::env::var("WASIX_CACHE_DIR") {
45            dir.into()
46        } else if let Some(root) = dirs::cache_dir() {
47            root.join("cargo-wasix")
48        } else if let Ok(data) = Self::data_dir() {
49            data.join("cache")
50        } else if let Some(home) = dirs::home_dir() {
51            home.join(".cargo-wasix").join("cache")
52        } else {
53            anyhow::bail!("Could not determine cargo-wasix cache dir. set WASIX_CACHE_DIR env var");
54        };
55
56        Ok(dir)
57    }
58
59    pub fn toolchain_dir() -> Result<PathBuf, anyhow::Error> {
60        Self::data_dir().map(|d| d.join("toolchains"))
61    }
62
63    fn lockfile_path() -> Result<PathBuf, anyhow::Error> {
64        Self::data_dir().map(|p| p.join("rustup-lock"))
65    }
66
67    pub fn acquire_lock() -> Result<crate::utils::FileLock, anyhow::Error> {
68        crate::utils::flock(&Self::lockfile_path()?)
69    }
70
71    pub fn load_cache(&mut self) -> Result<()> {
72        assert!(self.cache.is_none());
73        self.cache = Some(Cache::new(Self::cache_dir()?)?);
74        Ok(())
75    }
76
77    pub fn cache(&self) -> &Cache {
78        self.cache.as_ref().expect("cache not loaded yet")
79    }
80
81    pub fn is_verbose(&self) -> bool {
82        self.verbose
83    }
84
85    pub fn verbose(&self, f: impl FnOnce()) {
86        if self.verbose {
87            f();
88        }
89    }
90
91    pub fn set_verbose(&mut self, verbose: bool) {
92        self.verbose = verbose;
93    }
94
95    pub fn status(&self, name: &str, rest: &str) {
96        let mut shell = StandardStream::stderr(self.choice);
97        drop(shell.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true)));
98        eprint!("{:>12}", name);
99        drop(shell.reset());
100        eprintln!(" {}", rest);
101    }
102
103    pub fn print_error(&self, err: &anyhow::Error) {
104        if let Some(code) = crate::utils::normal_process_exit_code(err) {
105            std::process::exit(code);
106        }
107        let mut shell = StandardStream::stderr(self.choice);
108        drop(shell.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true)));
109        eprint!("error");
110        drop(shell.reset());
111        eprintln!(": {}", err);
112        for cause in err.chain().skip(1) {
113            eprintln!();
114            drop(shell.set_color(ColorSpec::new().set_bold(true)));
115            eprint!("Caused by");
116            drop(shell.reset());
117            eprintln!(":");
118            eprintln!("    {}", cause.to_string().replace('\n', "\n    "));
119        }
120    }
121
122    pub fn info(&self, msg: &str) {
123        let mut shell = StandardStream::stderr(self.choice);
124        drop(shell.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true)));
125        eprint!("info");
126        drop(shell.reset());
127        eprintln!(": {}", msg);
128    }
129
130    pub fn warn(&self, msg: &str) {
131        let mut shell = StandardStream::stderr(self.choice);
132        drop(shell.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true)));
133        eprint!("warn");
134        drop(shell.reset());
135        eprintln!(": {}", msg);
136    }
137
138    /// Returns the path to execute a tool, which may be the cache path to
139    /// download it to if unavailable, and whether the path has been
140    /// overridden.
141    ///
142    /// To override the path used for a tool, set an env var of the tool's name
143    /// in uppercase with hyphens replaced with underscores to the desired
144    /// path. For example, `WASM_BINDGEN=path/to/wasm-bindgen` to override the
145    /// `wasm-bindgen` used, or `WASM_OPT=path/to/wasm-opt` for `wasm-opt`.  or
146    /// the `cache` as the fallback.
147    fn get_tool(&self, tool: &str, version: Option<&str>) -> (PathBuf, bool) {
148        if let Some(s) = std::env::var_os(tool.to_uppercase().replace('-', "_")) {
149            (s.into(), true)
150        } else {
151            let mut cache_path = self.cache().root().join(tool);
152            if let Some(v) = version {
153                cache_path.push(v);
154                cache_path.push(tool)
155            }
156            (cache_path, false)
157        }
158    }
159
160    /// Get the path to our `wasm-opt`, which may be the cache path where it
161    /// should be download to if missing, and whether the path has been
162    /// overridden.
163    ///
164    /// Overridable via setting the `WASM_OPT=path/to/wasm-opt` env var.
165    pub fn get_wasm_opt(&self) -> ToolPath {
166        let (path, is_overridden) = self.get_tool("wasm-opt", None);
167        if !is_overridden {
168            let mut bin = ["bin", "wasm-opt"].iter().collect::<PathBuf>();
169            bin.set_extension(std::env::consts::EXE_EXTENSION);
170
171            let bin_path = path.join(&bin);
172            let mut sub_paths = vec![bin];
173
174            // wasm-opt on MacOS requires a dylib to execute
175            if cfg!(target_os = "macos") {
176                let mut dylib = ["lib", "libbinaryen"].iter().collect::<PathBuf>();
177                dylib.set_extension(std::env::consts::DLL_EXTENSION);
178                sub_paths.push(dylib);
179            }
180
181            ToolPath::Cached {
182                bin_path,
183                base: path,
184                sub_paths,
185            }
186        } else {
187            ToolPath::Overridden(path)
188        }
189    }
190}