Add persistent storage of installed toolchains (#3797)

Extends #3726 

Moves toolchain storage out of `UV_BOOTSTRAP_DIR` (`./bin`) into the
proper user data directory as defined by #3726.

Replaces `UV_BOOTSTRAP_DIR` with `UV_TOOLCHAIN_DIR` for customization.
Installed toolchains will be discovered without opt-in, but the idea is
still that these are not yet user-facing.
This commit is contained in:
Zanie Blue 2024-05-26 23:54:49 -04:00 committed by GitHub
parent 4191c331a7
commit 30e780e1dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 448 additions and 210 deletions

View file

@ -0,0 +1,18 @@
[package]
name = "uv-state"
version = "0.0.1"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
[lints]
workspace = true
[dependencies]
directories = { workspace = true }
tempfile = { workspace = true }
fs-err = { workspace = true }

108
crates/uv-state/src/lib.rs Normal file
View file

@ -0,0 +1,108 @@
use std::{
io::{self, Write},
path::{Path, PathBuf},
sync::Arc,
};
use directories::ProjectDirs;
use fs_err as fs;
use tempfile::{tempdir, TempDir};
/// The main state storage abstraction.
///
/// This is appropriate
#[derive(Debug, Clone)]
pub struct StateStore {
/// The state storage.
root: PathBuf,
/// A temporary state storage.
///
/// Included to ensure that the temporary store exists for the length of the operation, but
/// is dropped at the end as appropriate.
_temp_dir_drop: Option<Arc<TempDir>>,
}
impl StateStore {
/// A persistent state store at `root`.
pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> {
Ok(Self {
root: root.into(),
_temp_dir_drop: None,
})
}
/// Create a temporary state store.
pub fn temp() -> Result<Self, io::Error> {
let temp_dir = tempdir()?;
Ok(Self {
root: temp_dir.path().to_path_buf(),
_temp_dir_drop: Some(Arc::new(temp_dir)),
})
}
/// Return the root of the state store.
pub fn root(&self) -> &Path {
&self.root
}
/// Initialize the state store.
pub fn init(self) -> Result<Self, io::Error> {
let root = &self.root;
// Create the state store directory, if it doesn't exist.
fs::create_dir_all(root)?;
// Add a .gitignore.
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(root.join(".gitignore"))
{
Ok(mut file) => file.write_all(b"*")?,
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
Err(err) => return Err(err),
}
Ok(Self {
root: fs::canonicalize(root)?,
..self
})
}
/// The folder for a specific cache bucket
pub fn bucket(&self, state_bucket: StateBucket) -> PathBuf {
self.root.join(state_bucket.to_str())
}
/// Prefer, in order:
/// 1. The specific state directory specified by the user.
/// 2. The system-appropriate user-level data directory.
/// 3. A `.uv` directory in the current working directory.
///
/// Returns an absolute cache dir.
pub fn from_settings(state_dir: Option<PathBuf>) -> Result<Self, io::Error> {
if let Some(state_dir) = state_dir {
StateStore::from_path(state_dir)
} else if let Some(project_dirs) = ProjectDirs::from("", "", "uv") {
StateStore::from_path(project_dirs.data_dir())
} else {
StateStore::from_path(".uv")
}
}
}
/// The different kinds of data in the state store are stored in different bucket, which in our case
/// are subdirectories of the state store root.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum StateBucket {
// Managed toolchain
Toolchains,
}
impl StateBucket {
fn to_str(self) -> &'static str {
match self {
Self::Toolchains => "toolchains",
}
}
}