mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25: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",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"toml_edit",
|
||||
"tracing",
|
||||
"uv-cache",
|
||||
"uv-fs",
|
||||
|
|
|
@ -28,5 +28,4 @@ tracing = { workspace = true }
|
|||
fs-err = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
toml_edit = { workspace = true }
|
||||
dirs-sys = { workspace = true }
|
||||
|
|
|
@ -12,20 +12,21 @@ use uv_cache::Cache;
|
|||
use uv_fs::{LockedFile, Simplified};
|
||||
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};
|
||||
mod tools_toml;
|
||||
mod receipt;
|
||||
mod tool;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IO(#[from] io::Error),
|
||||
// TODO(zanieb): Improve the error handling here
|
||||
#[error("Failed to update `tools.toml` metadata at {0}")]
|
||||
TomlEdit(PathBuf, #[source] tools_toml::Error),
|
||||
#[error("Failed to read `tools.toml` metadata at {0}")]
|
||||
TomlRead(PathBuf, #[source] Box<toml::de::Error>),
|
||||
#[error("Failed to update `uv-receipt.toml` at {0}")]
|
||||
ReceiptWrite(PathBuf, #[source] Box<toml::ser::Error>),
|
||||
#[error("Failed to read `uv-receipt.toml` at {0}")]
|
||||
ReceiptRead(PathBuf, #[source] Box<toml::de::Error>),
|
||||
#[error(transparent)]
|
||||
VirtualEnvError(#[from] uv_virtualenv::Error),
|
||||
#[error("Failed to read package entry points {0}")]
|
||||
|
@ -36,6 +37,8 @@ pub enum Error {
|
|||
NoExecutableDirectory,
|
||||
#[error(transparent)]
|
||||
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.
|
||||
|
@ -51,7 +54,10 @@ impl InstalledTools {
|
|||
Self { root: root.into() }
|
||||
}
|
||||
|
||||
/// Create a new [`InstalledTools`] from settings.
|
||||
///
|
||||
/// Prefer, in order:
|
||||
///
|
||||
/// 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`
|
||||
/// 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 {
|
||||
self.root.join("tools.toml")
|
||||
/// Return the metadata for all installed tools.
|
||||
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.
|
||||
pub fn toml(&self) -> Result<ToolsToml, Error> {
|
||||
match fs_err::read_to_string(self.tools_toml_path()) {
|
||||
Ok(contents) => Ok(ToolsToml::from_string(contents)
|
||||
.map_err(|err| Error::TomlRead(self.tools_toml_path(), Box::new(err)))?),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(ToolsToml::default()),
|
||||
Err(err) => Err(err.into()),
|
||||
/// Get the receipt for the given tool.
|
||||
pub fn get_tool_receipt(&self, name: &str) -> Result<Option<Tool>, Error> {
|
||||
let path = self.root.join(name).join("uv-receipt.toml");
|
||||
match ToolReceipt::from_path(&path) {
|
||||
Ok(tool_receipt) => Ok(Some(tool_receipt.tool)),
|
||||
Err(Error::IO(err)) if err.kind() == io::ErrorKind::NotFound => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toml_mut(&self) -> Result<ToolsTomlMut, Error> {
|
||||
let toml = self.toml()?;
|
||||
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> {
|
||||
/// Lock the tools directory.
|
||||
fn acquire_lock(&self) -> Result<LockedFile, Error> {
|
||||
Ok(LockedFile::acquire(
|
||||
self.root.join(".lock"),
|
||||
self.root.user_display(),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn add_tool_entry(&self, name: &str, tool: &Tool) -> Result<(), Error> {
|
||||
let _lock = self.acquire_lock();
|
||||
/// Lock a tool directory.
|
||||
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()?;
|
||||
toml_mut
|
||||
.add_tool(name, tool)
|
||||
.map_err(|err| Error::TomlEdit(self.tools_toml_path(), err))?;
|
||||
/// Add a receipt for a tool.
|
||||
///
|
||||
/// Any existing receipt will be replaced.
|
||||
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`.
|
||||
fs_err::write(self.tools_toml_path(), toml_mut.to_string())?;
|
||||
fs_err::write(&path, doc)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -189,6 +222,7 @@ impl InstalledTools {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
/// Return the path of the tools directory.
|
||||
pub fn root(&self) -> &Path {
|
||||
&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 pypi_types::VerbatimParsedUrl;
|
||||
use tracing::{debug, trace};
|
||||
use tracing::debug;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::Connectivity;
|
||||
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?
|
||||
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
|
||||
let reinstall_entry_points = if existing_tool_entry.is_some() {
|
||||
let reinstall_entry_points = if existing_tool_receipt.is_some() {
|
||||
if force {
|
||||
debug!("Replacing existing tool due to `--force` flag.");
|
||||
false
|
||||
|
@ -93,7 +93,6 @@ pub(crate) async fn install(
|
|||
bail!("Expected at least one requirement")
|
||||
};
|
||||
let tool = Tool::new(requirements, python.clone());
|
||||
let path = installed_tools.tools_toml_path();
|
||||
|
||||
let interpreter = Toolchain::find(
|
||||
&python
|
||||
|
@ -138,7 +137,7 @@ pub(crate) async fn install(
|
|||
|
||||
// Exit early if we're not supposed to be reinstalling entry points
|
||||
// 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}`")?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
@ -214,18 +213,16 @@ pub(crate) async fn install(
|
|||
#[cfg(windows)]
|
||||
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!(
|
||||
printer.stdout(),
|
||||
"Installed: {}",
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ fn tool_install() {
|
|||
|
||||
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||
tool_dir
|
||||
.child("tools.toml")
|
||||
.child("black")
|
||||
.child("uv-receipt.toml")
|
||||
.assert(predicate::path::exists());
|
||||
|
||||
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||
|
@ -72,10 +73,10 @@ fn tool_install() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["black"]
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -149,11 +150,10 @@ fn tool_install() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have an additional tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
flask = { requirements = ["flask"] }
|
||||
// We should have a new tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["flask"]
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
@ -197,7 +197,8 @@ fn tool_install_already_installed() {
|
|||
|
||||
tool_dir.child("black").assert(predicate::path::is_dir());
|
||||
tool_dir
|
||||
.child("tools.toml")
|
||||
.child("black")
|
||||
.child("uv-receipt.toml")
|
||||
.assert(predicate::path::exists());
|
||||
|
||||
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
|
||||
|
@ -225,10 +226,10 @@ fn tool_install_already_installed() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["black"]
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -254,10 +255,10 @@ fn tool_install_already_installed() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should not have an additional tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
// We should not have an additional tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["black"]
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -378,7 +379,7 @@ fn tool_install_entry_point_exists() {
|
|||
assert!(!tool_dir.child("black").exists());
|
||||
|
||||
// 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!({
|
||||
filters => context.filters(),
|
||||
|
@ -480,10 +481,10 @@ fn tool_install_entry_point_exists() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We write a tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
// We write a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["black"]
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -509,10 +510,10 @@ fn tool_install_entry_point_exists() {
|
|||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool entry
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("tools.toml")).unwrap(), @r###"
|
||||
[tools]
|
||||
black = { requirements = ["black"] }
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = ["black"]
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue