svm/
paths.rs

1use crate::SvmError;
2use std::{
3    ffi::OsString,
4    fs, io,
5    path::{Path, PathBuf},
6    sync::OnceLock,
7};
8
9/// Setup SVM home directory.
10pub fn setup_data_dir() -> Result<(), SvmError> {
11    // create $XDG_DATA_HOME or ~/.local/share/svm, or fallback to ~/.svm
12    let data_dir = data_dir();
13
14    // Create the directory, continuing if the directory came into existence after the check
15    // for this if statement. This may happen if two copies of SVM run simultaneously (e.g CI).
16    fs::create_dir_all(data_dir).or_else(|err| match err.kind() {
17        io::ErrorKind::AlreadyExists => Ok(()),
18        _ => Err(err),
19    })?;
20
21    // Check that the SVM directory is indeed a directory, and not e.g. a file.
22    if !data_dir.is_dir() {
23        return Err(SvmError::IoError(io::Error::new(
24            io::ErrorKind::AlreadyExists,
25            format!("svm data dir '{}' is not a directory", data_dir.display()),
26        )));
27    }
28
29    // Create `$SVM/.global-version`.
30    let global_version = global_version_path();
31    if !global_version.exists() {
32        fs::File::create(global_version)?;
33    }
34
35    Ok(())
36}
37
38/// Returns the path to the default data directory.
39///
40/// Returns `~/.svm` if it exists, otherwise uses `$XDG_DATA_HOME/svm`.
41pub fn data_dir() -> &'static Path {
42    static ONCE: OnceLock<PathBuf> = OnceLock::new();
43    ONCE.get_or_init(|| {
44        #[cfg(test)]
45        {
46            let dir = tempfile::tempdir().expect("could not create temp directory");
47            dir.path().join(".svm")
48        }
49        #[cfg(not(test))]
50        {
51            resolve_data_dir()
52        }
53    })
54}
55
56#[allow(dead_code)]
57fn resolve_data_dir() -> PathBuf {
58    let home_dir = dirs::home_dir()
59        .expect("could not detect user home directory")
60        .join(".svm");
61
62    let data_dir = dirs::data_dir().expect("could not detect user data directory");
63    if !home_dir.exists() && data_dir.exists() {
64        data_dir.join("svm")
65    } else {
66        home_dir
67    }
68}
69
70/// Returns the path to the global version file.
71pub fn global_version_path() -> &'static Path {
72    static ONCE: OnceLock<PathBuf> = OnceLock::new();
73    ONCE.get_or_init(|| data_dir().join(".global-version"))
74}
75
76/// Returns the path to a specific Solc version's directory.
77///
78/// Note that this is not the path to the actual Solc binary file;
79/// use [`version_binary`] for that instead.
80///
81/// This is currently `data_dir() / {version}`.
82pub fn version_path(version: &str) -> PathBuf {
83    data_dir().join(version)
84}
85
86/// Derive path to a specific Solc version's binary file.
87///
88/// This is currently `data_dir() / {version} / solc-{version}`.
89pub fn version_binary(version: &str) -> PathBuf {
90    let data_dir = data_dir();
91    let sep = std::path::MAIN_SEPARATOR_STR;
92    let cap =
93        data_dir.as_os_str().len() + sep.len() + version.len() + sep.len() + 5 + version.len();
94    let mut binary = OsString::with_capacity(cap);
95    binary.push(data_dir);
96    debug_assert!(!data_dir.ends_with(sep));
97    binary.push(sep);
98
99    binary.push(version);
100    binary.push(sep);
101
102    binary.push("solc-");
103    binary.push(version);
104    PathBuf::from(binary)
105}