mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
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:
parent
4191c331a7
commit
30e780e1dd
15 changed files with 448 additions and 210 deletions
18
crates/uv-state/Cargo.toml
Normal file
18
crates/uv-state/Cargo.toml
Normal 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
108
crates/uv-state/src/lib.rs
Normal 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",
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue