mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Move from a shared tools.toml
to separated tool receipts (#4560)
Refactors the installed tool metadata per commentary in #4492 We now store a `uv-receipt.toml` per tool install instead of a single `tools.toml`
This commit is contained in:
parent
909b69dfa2
commit
fc681ec738
8 changed files with 182 additions and 195 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5025,7 +5025,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml",
|
"toml",
|
||||||
"toml_edit",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"uv-cache",
|
"uv-cache",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
|
|
|
@ -28,5 +28,4 @@ tracing = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
toml_edit = { workspace = true }
|
|
||||||
dirs-sys = { workspace = true }
|
dirs-sys = { workspace = true }
|
||||||
|
|
|
@ -12,20 +12,21 @@ use uv_cache::Cache;
|
||||||
use uv_fs::{LockedFile, Simplified};
|
use uv_fs::{LockedFile, Simplified};
|
||||||
use uv_toolchain::{Interpreter, PythonEnvironment};
|
use uv_toolchain::{Interpreter, PythonEnvironment};
|
||||||
|
|
||||||
pub use tools_toml::{Tool, ToolsToml, ToolsTomlMut};
|
pub use receipt::ToolReceipt;
|
||||||
|
pub use tool::Tool;
|
||||||
|
|
||||||
use uv_state::{StateBucket, StateStore};
|
use uv_state::{StateBucket, StateStore};
|
||||||
mod tools_toml;
|
mod receipt;
|
||||||
|
mod tool;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
IO(#[from] io::Error),
|
IO(#[from] io::Error),
|
||||||
// TODO(zanieb): Improve the error handling here
|
#[error("Failed to update `uv-receipt.toml` at {0}")]
|
||||||
#[error("Failed to update `tools.toml` metadata at {0}")]
|
ReceiptWrite(PathBuf, #[source] Box<toml::ser::Error>),
|
||||||
TomlEdit(PathBuf, #[source] tools_toml::Error),
|
#[error("Failed to read `uv-receipt.toml` at {0}")]
|
||||||
#[error("Failed to read `tools.toml` metadata at {0}")]
|
ReceiptRead(PathBuf, #[source] Box<toml::de::Error>),
|
||||||
TomlRead(PathBuf, #[source] Box<toml::de::Error>),
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
VirtualEnvError(#[from] uv_virtualenv::Error),
|
VirtualEnvError(#[from] uv_virtualenv::Error),
|
||||||
#[error("Failed to read package entry points {0}")]
|
#[error("Failed to read package entry points {0}")]
|
||||||
|
@ -36,6 +37,8 @@ pub enum Error {
|
||||||
NoExecutableDirectory,
|
NoExecutableDirectory,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
EnvironmentError(#[from] uv_toolchain::Error),
|
EnvironmentError(#[from] uv_toolchain::Error),
|
||||||
|
#[error("Failed to find a receipt for tool `{0}` at {1}")]
|
||||||
|
MissingToolReceipt(String, PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of uv-managed tools installed on the current system.
|
/// A collection of uv-managed tools installed on the current system.
|
||||||
|
@ -51,7 +54,10 @@ impl InstalledTools {
|
||||||
Self { root: root.into() }
|
Self { root: root.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new [`InstalledTools`] from settings.
|
||||||
|
///
|
||||||
/// Prefer, in order:
|
/// Prefer, in order:
|
||||||
|
///
|
||||||
/// 1. The specific tool directory specified by the user, i.e., `UV_TOOL_DIR`
|
/// 1. The specific tool directory specified by the user, i.e., `UV_TOOL_DIR`
|
||||||
/// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/tools`
|
/// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/tools`
|
||||||
/// 3. A directory in the local data directory, e.g., `./.uv/tools`
|
/// 3. A directory in the local data directory, e.g., `./.uv/tools`
|
||||||
|
@ -65,47 +71,74 @@ impl InstalledTools {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tools_toml_path(&self) -> PathBuf {
|
/// Return the metadata for all installed tools.
|
||||||
self.root.join("tools.toml")
|
pub fn tools(&self) -> Result<Vec<(String, Tool)>, Error> {
|
||||||
|
let _lock = self.acquire_lock();
|
||||||
|
let mut tools = Vec::new();
|
||||||
|
for directory in uv_fs::directories(self.root()) {
|
||||||
|
let name = directory.file_name().unwrap().to_string_lossy().to_string();
|
||||||
|
let path = directory.join("uv-receipt.toml");
|
||||||
|
let contents = match fs_err::read_to_string(&path) {
|
||||||
|
Ok(contents) => contents,
|
||||||
|
// TODO(zanieb): Consider warning on malformed tools instead
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||||
|
return Err(Error::MissingToolReceipt(name.clone(), path.clone()))
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
let tool_receipt = ToolReceipt::from_string(contents)
|
||||||
|
.map_err(|err| Error::ReceiptRead(path, Box::new(err)))?;
|
||||||
|
tools.push((name, tool_receipt.tool));
|
||||||
|
}
|
||||||
|
Ok(tools)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the toml tracking tools.
|
/// Get the receipt for the given tool.
|
||||||
pub fn toml(&self) -> Result<ToolsToml, Error> {
|
pub fn get_tool_receipt(&self, name: &str) -> Result<Option<Tool>, Error> {
|
||||||
match fs_err::read_to_string(self.tools_toml_path()) {
|
let path = self.root.join(name).join("uv-receipt.toml");
|
||||||
Ok(contents) => Ok(ToolsToml::from_string(contents)
|
match ToolReceipt::from_path(&path) {
|
||||||
.map_err(|err| Error::TomlRead(self.tools_toml_path(), Box::new(err)))?),
|
Ok(tool_receipt) => Ok(Some(tool_receipt.tool)),
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(ToolsToml::default()),
|
Err(Error::IO(err)) if err.kind() == io::ErrorKind::NotFound => Ok(None),
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toml_mut(&self) -> Result<ToolsTomlMut, Error> {
|
/// Lock the tools directory.
|
||||||
let toml = self.toml()?;
|
fn acquire_lock(&self) -> Result<LockedFile, Error> {
|
||||||
ToolsTomlMut::from_toml(&toml).map_err(|err| Error::TomlEdit(self.tools_toml_path(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_tool_entry(&self, name: &str) -> Result<Option<Tool>, Error> {
|
|
||||||
let toml = self.toml()?;
|
|
||||||
Ok(toml.tools.and_then(|tools| tools.get(name).cloned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acquire_lock(&self) -> Result<LockedFile, Error> {
|
|
||||||
Ok(LockedFile::acquire(
|
Ok(LockedFile::acquire(
|
||||||
self.root.join(".lock"),
|
self.root.join(".lock"),
|
||||||
self.root.user_display(),
|
self.root.user_display(),
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_tool_entry(&self, name: &str, tool: &Tool) -> Result<(), Error> {
|
/// Lock a tool directory.
|
||||||
let _lock = self.acquire_lock();
|
fn acquire_tool_lock(&self, name: &str) -> Result<LockedFile, Error> {
|
||||||
|
let path = self.root.join(name);
|
||||||
|
Ok(LockedFile::acquire(
|
||||||
|
path.join(".lock"),
|
||||||
|
path.user_display(),
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
let mut toml_mut = self.toml_mut()?;
|
/// Add a receipt for a tool.
|
||||||
toml_mut
|
///
|
||||||
.add_tool(name, tool)
|
/// Any existing receipt will be replaced.
|
||||||
.map_err(|err| Error::TomlEdit(self.tools_toml_path(), err))?;
|
pub fn add_tool_receipt(&self, name: &str, tool: Tool) -> Result<(), Error> {
|
||||||
|
let _lock = self.acquire_tool_lock(name);
|
||||||
|
|
||||||
|
let tool_receipt = ToolReceipt::from(tool);
|
||||||
|
let path = self.root.join(name).join("uv-receipt.toml");
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Adding metadata entry for tool `{name}` at {}",
|
||||||
|
path.user_display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let doc = toml::to_string(&tool_receipt)
|
||||||
|
.map_err(|err| Error::ReceiptWrite(path.clone(), Box::new(err)))?;
|
||||||
|
|
||||||
// Save the modified `tools.toml`.
|
// Save the modified `tools.toml`.
|
||||||
fs_err::write(self.tools_toml_path(), toml_mut.to_string())?;
|
fs_err::write(&path, doc)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -189,6 +222,7 @@ impl InstalledTools {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the path of the tools directory.
|
||||||
pub fn root(&self) -> &Path {
|
pub fn root(&self) -> &Path {
|
||||||
&self.root
|
&self.root
|
||||||
}
|
}
|
||||||
|
|
51
crates/uv-tool/src/receipt.rs
Normal file
51
crates/uv-tool/src/receipt.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// A `uv-receipt.toml` file tracking the installation of a tool.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ToolReceipt {
|
||||||
|
pub(crate) tool: Tool,
|
||||||
|
|
||||||
|
/// The raw unserialized document.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) raw: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolReceipt {
|
||||||
|
/// Parse a [`ToolReceipt`] from a raw TOML string.
|
||||||
|
pub(crate) fn from_string(raw: String) -> Result<Self, toml::de::Error> {
|
||||||
|
let tool = toml::from_str(&raw)?;
|
||||||
|
Ok(ToolReceipt { raw, ..tool })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a [`ToolReceipt`] from the given path.
|
||||||
|
pub(crate) fn from_path(path: &Path) -> Result<ToolReceipt, crate::Error> {
|
||||||
|
match fs_err::read_to_string(path) {
|
||||||
|
Ok(contents) => Ok(ToolReceipt::from_string(contents)
|
||||||
|
.map_err(|err| crate::Error::ReceiptRead(path.to_owned(), Box::new(err)))?),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore raw document in comparison.
|
||||||
|
impl PartialEq for ToolReceipt {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.tool.eq(&other.tool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ToolReceipt {}
|
||||||
|
|
||||||
|
impl From<Tool> for ToolReceipt {
|
||||||
|
fn from(tool: Tool) -> Self {
|
||||||
|
ToolReceipt {
|
||||||
|
tool,
|
||||||
|
raw: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
crates/uv-tool/src/tool.rs
Normal file
25
crates/uv-tool/src/tool.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use pypi_types::VerbatimParsedUrl;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A tool entry.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Tool {
|
||||||
|
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
||||||
|
python: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tool {
|
||||||
|
/// Create a new `Tool`.
|
||||||
|
pub fn new(
|
||||||
|
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
||||||
|
python: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
requirements,
|
||||||
|
python,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,119 +0,0 @@
|
||||||
use pypi_types::VerbatimParsedUrl;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::{fmt, mem};
|
|
||||||
use thiserror::Error;
|
|
||||||
use toml_edit::{DocumentMut, Item, Table, TomlError, Value};
|
|
||||||
|
|
||||||
/// A `tools.toml` with an (optional) `[tools]` section.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
|
||||||
pub struct ToolsToml {
|
|
||||||
pub(crate) tools: Option<BTreeMap<String, Tool>>,
|
|
||||||
|
|
||||||
/// The raw unserialized document.
|
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) raw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolsToml {
|
|
||||||
/// Parse a `ToolsToml` from a raw TOML string.
|
|
||||||
pub(crate) fn from_string(raw: String) -> Result<Self, toml::de::Error> {
|
|
||||||
let tools = toml::from_str(&raw)?;
|
|
||||||
Ok(ToolsToml { raw, ..tools })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore raw document in comparison.
|
|
||||||
impl PartialEq for ToolsToml {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.tools.eq(&other.tools)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for ToolsToml {}
|
|
||||||
|
|
||||||
/// A `[[tools]]` entry.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
||||||
pub struct Tool {
|
|
||||||
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
|
||||||
python: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tool {
|
|
||||||
/// Create a new `Tool`.
|
|
||||||
pub fn new(
|
|
||||||
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
|
||||||
python: Option<String>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
requirements,
|
|
||||||
python,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raw and mutable representation of a `tools.toml`.
|
|
||||||
///
|
|
||||||
/// This is useful for operations that require editing an existing `tools.toml` while
|
|
||||||
/// preserving comments and other structure.
|
|
||||||
pub struct ToolsTomlMut {
|
|
||||||
doc: DocumentMut,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Failed to parse `tools.toml`")]
|
|
||||||
Parse(#[from] Box<TomlError>),
|
|
||||||
#[error("Failed to serialize `tools.toml`")]
|
|
||||||
Serialize(#[from] Box<toml::ser::Error>),
|
|
||||||
#[error("`tools.toml` is malformed")]
|
|
||||||
MalformedTools,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolsTomlMut {
|
|
||||||
/// Initialize a `ToolsTomlMut` from a `ToolsToml`.
|
|
||||||
pub fn from_toml(tools: &ToolsToml) -> Result<Self, Error> {
|
|
||||||
Ok(Self {
|
|
||||||
doc: tools.raw.parse().map_err(Box::new)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a tool to `tools`.
|
|
||||||
pub fn add_tool(&mut self, name: &str, tool: &Tool) -> Result<(), Error> {
|
|
||||||
// Get or create `tools`.
|
|
||||||
let tools = self
|
|
||||||
.doc
|
|
||||||
.entry("tools")
|
|
||||||
.or_insert(Item::Table(Table::new()))
|
|
||||||
.as_table_mut()
|
|
||||||
.ok_or(Error::MalformedTools)?;
|
|
||||||
|
|
||||||
add_tool(name, tool, tools)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a tool to the given `tools` table.
|
|
||||||
pub(crate) fn add_tool(name: &str, tool: &Tool, tools: &mut Table) -> Result<(), Error> {
|
|
||||||
// Serialize as an inline table.
|
|
||||||
let mut doc = toml::to_string(tool)
|
|
||||||
.map_err(Box::new)?
|
|
||||||
.parse::<DocumentMut>()
|
|
||||||
.unwrap();
|
|
||||||
let table = mem::take(doc.as_table_mut()).into_inline_table();
|
|
||||||
|
|
||||||
tools.insert(name, Item::Value(Value::InlineTable(table)));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ToolsTomlMut {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.doc.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ use itertools::Itertools;
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use pypi_types::VerbatimParsedUrl;
|
use pypi_types::VerbatimParsedUrl;
|
||||||
use tracing::{debug, trace};
|
use tracing::debug;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
use uv_configuration::{Concurrency, PreviewMode, Reinstall};
|
use uv_configuration::{Concurrency, PreviewMode, Reinstall};
|
||||||
|
@ -53,9 +53,9 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
// TODO(zanieb): Figure out the interface here, do we infer the name or do we match the `run --from` interface?
|
// TODO(zanieb): Figure out the interface here, do we infer the name or do we match the `run --from` interface?
|
||||||
let from = Requirement::<VerbatimParsedUrl>::from_str(&from)?;
|
let from = Requirement::<VerbatimParsedUrl>::from_str(&from)?;
|
||||||
let existing_tool_entry = installed_tools.find_tool_entry(&name)?;
|
let existing_tool_receipt = installed_tools.get_tool_receipt(&name)?;
|
||||||
// TODO(zanieb): Automatically replace an existing tool if the request differs
|
// TODO(zanieb): Automatically replace an existing tool if the request differs
|
||||||
let reinstall_entry_points = if existing_tool_entry.is_some() {
|
let reinstall_entry_points = if existing_tool_receipt.is_some() {
|
||||||
if force {
|
if force {
|
||||||
debug!("Replacing existing tool due to `--force` flag.");
|
debug!("Replacing existing tool due to `--force` flag.");
|
||||||
false
|
false
|
||||||
|
@ -93,7 +93,6 @@ pub(crate) async fn install(
|
||||||
bail!("Expected at least one requirement")
|
bail!("Expected at least one requirement")
|
||||||
};
|
};
|
||||||
let tool = Tool::new(requirements, python.clone());
|
let tool = Tool::new(requirements, python.clone());
|
||||||
let path = installed_tools.tools_toml_path();
|
|
||||||
|
|
||||||
let interpreter = Toolchain::find(
|
let interpreter = Toolchain::find(
|
||||||
&python
|
&python
|
||||||
|
@ -138,7 +137,7 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
// Exit early if we're not supposed to be reinstalling entry points
|
// Exit early if we're not supposed to be reinstalling entry points
|
||||||
// e.g. `--reinstall-package` was used for some dependency
|
// e.g. `--reinstall-package` was used for some dependency
|
||||||
if existing_tool_entry.is_some() && !reinstall_entry_points {
|
if existing_tool_receipt.is_some() && !reinstall_entry_points {
|
||||||
writeln!(printer.stderr(), "Updated environment for tool `{name}`")?;
|
writeln!(printer.stderr(), "Updated environment for tool `{name}`")?;
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
@ -214,18 +213,16 @@ pub(crate) async fn install(
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fs_err::copy(path, target).context("Failed to install entrypoint")?;
|
fs_err::copy(path, target).context("Failed to install entrypoint")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Adding receipt for tool `{name}`",);
|
||||||
|
let installed_tools = installed_tools.init()?;
|
||||||
|
installed_tools.add_tool_receipt(&name, tool)?;
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stdout(),
|
printer.stdout(),
|
||||||
"Installed: {}",
|
"Installed: {}",
|
||||||
targets.iter().map(|(name, _, _)| name).join(", ")
|
targets.iter().map(|(name, _, _)| name).join(", ")
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
trace!(
|
|
||||||
"Tracking installed tool `{name}` in tool metadata at `{}`",
|
|
||||||
path.user_display()
|
|
||||||
);
|
|
||||||
let installed_tools = installed_tools.init()?;
|
|
||||||
installed_tools.add_tool_entry(&name, &tool)?;
|
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ fn tool_install() {
|
||||||
|
|
||||||
tool_dir.child("black").assert(predicate::path::is_dir());
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||||
tool_dir
|
tool_dir
|
||||||
.child("tools.toml")
|
.child("black")
|
||||||
|
.child("uv-receipt.toml")
|
||||||
.assert(predicate::path::exists());
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
@ -72,10 +73,10 @@ fn tool_install() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We should have a tool entry
|
// We should have a tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["black"]
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -149,11 +150,10 @@ fn tool_install() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We should have an additional tool entry
|
// We should have a new tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["flask"]
|
||||||
flask = { requirements = ["flask"] }
|
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,8 @@ fn tool_install_already_installed() {
|
||||||
|
|
||||||
tool_dir.child("black").assert(predicate::path::is_dir());
|
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||||
tool_dir
|
tool_dir
|
||||||
.child("tools.toml")
|
.child("black")
|
||||||
|
.child("uv-receipt.toml")
|
||||||
.assert(predicate::path::exists());
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
@ -225,10 +226,10 @@ fn tool_install_already_installed() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We should have a tool entry
|
// We should have a tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["black"]
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -254,10 +255,10 @@ fn tool_install_already_installed() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We should not have an additional tool entry
|
// We should not have an additional tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["black"]
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -378,7 +379,7 @@ fn tool_install_entry_point_exists() {
|
||||||
assert!(!tool_dir.child("black").exists());
|
assert!(!tool_dir.child("black").exists());
|
||||||
|
|
||||||
// We should not write a tools entry
|
// We should not write a tools entry
|
||||||
assert!(!tool_dir.join("tools.toml").exists());
|
assert!(!tool_dir.join("black").join("uv-receipt.toml").exists());
|
||||||
|
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
|
@ -480,10 +481,10 @@ fn tool_install_entry_point_exists() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We write a tool entry
|
// We write a tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["black"]
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -509,10 +510,10 @@ fn tool_install_entry_point_exists() {
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
// We should have a tool entry
|
// We should have a tool receipt
|
||||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
[tools]
|
[tool]
|
||||||
black = { requirements = ["black"] }
|
requirements = ["black"]
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue