mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Allow multiple packages for uv tool upgrade/uninstall
(#7037)
## Summary Resolves https://github.com/astral-sh/uv/issues/6571 ## Test Plan `cargo test`
This commit is contained in:
parent
c1effd6b05
commit
ff39950545
7 changed files with 175 additions and 41 deletions
|
@ -3196,7 +3196,7 @@ pub struct ToolDirArgs {
|
|||
pub struct ToolUninstallArgs {
|
||||
/// The name of the tool to uninstall.
|
||||
#[arg(required = true)]
|
||||
pub name: Option<PackageName>,
|
||||
pub name: Option<Vec<PackageName>>,
|
||||
|
||||
/// Uninstall all tools.
|
||||
#[arg(long, conflicts_with("name"))]
|
||||
|
@ -3208,7 +3208,7 @@ pub struct ToolUninstallArgs {
|
|||
pub struct ToolUpgradeArgs {
|
||||
/// The name of the tool to upgrade.
|
||||
#[arg(required = true)]
|
||||
pub name: Option<PackageName>,
|
||||
pub name: Vec<PackageName>,
|
||||
|
||||
/// Upgrade all tools.
|
||||
#[arg(long, conflicts_with("name"))]
|
||||
|
|
|
@ -13,13 +13,19 @@ use crate::commands::ExitStatus;
|
|||
use crate::printer::Printer;
|
||||
|
||||
/// Uninstall a tool.
|
||||
pub(crate) async fn uninstall(name: Option<PackageName>, printer: Printer) -> Result<ExitStatus> {
|
||||
pub(crate) async fn uninstall(
|
||||
name: Option<Vec<PackageName>>,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
let installed_tools = InstalledTools::from_settings()?.init()?;
|
||||
let _lock = match installed_tools.lock().await {
|
||||
Ok(lock) => lock,
|
||||
Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
if let Some(name) = name {
|
||||
bail!("`{name}` is not installed");
|
||||
if let Some(names) = name {
|
||||
for name in names {
|
||||
writeln!(printer.stderr(), "`{name}` is not installed")?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
writeln!(printer.stderr(), "Nothing to uninstall")?;
|
||||
return Ok(ExitStatus::Success);
|
||||
|
@ -88,31 +94,35 @@ impl IgnoreCurrentlyBeingDeleted for Result<(), std::io::Error> {
|
|||
/// Perform the uninstallation.
|
||||
async fn do_uninstall(
|
||||
installed_tools: &InstalledTools,
|
||||
name: Option<PackageName>,
|
||||
names: Option<Vec<PackageName>>,
|
||||
printer: Printer,
|
||||
) -> Result<()> {
|
||||
let mut dangling = false;
|
||||
let mut entrypoints = if let Some(name) = name {
|
||||
let Some(receipt) = installed_tools.get_tool_receipt(&name)? else {
|
||||
// If the tool is not installed, attempt to remove the environment anyway.
|
||||
match installed_tools.remove_environment(&name) {
|
||||
Ok(()) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Removed dangling environment for `{name}`"
|
||||
)?;
|
||||
return Ok(());
|
||||
let mut entrypoints = if let Some(names) = names {
|
||||
let mut entrypoints = vec![];
|
||||
for name in names {
|
||||
let Some(receipt) = installed_tools.get_tool_receipt(&name)? else {
|
||||
// If the tool is not installed properly, attempt to remove the environment anyway.
|
||||
match installed_tools.remove_environment(&name) {
|
||||
Ok(()) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Removed dangling environment for `{name}`"
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!("`{name}` is not installed");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!("`{name}` is not installed");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
uninstall_tool(&name, &receipt, installed_tools).await?
|
||||
entrypoints.extend(uninstall_tool(&name, &receipt, installed_tools).await?);
|
||||
}
|
||||
entrypoints
|
||||
} else {
|
||||
let mut entrypoints = vec![];
|
||||
for (name, receipt) in installed_tools.tools()? {
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::settings::ResolverInstallerSettings;
|
|||
|
||||
/// Upgrade a tool.
|
||||
pub(crate) async fn upgrade(
|
||||
name: Option<PackageName>,
|
||||
name: Vec<PackageName>,
|
||||
connectivity: Connectivity,
|
||||
args: ResolverInstallerOptions,
|
||||
filesystem: ResolverInstallerOptions,
|
||||
|
@ -34,16 +34,18 @@ pub(crate) async fn upgrade(
|
|||
let installed_tools = InstalledTools::from_settings()?.init()?;
|
||||
let _lock = installed_tools.lock().await?;
|
||||
|
||||
let names: BTreeSet<PackageName> =
|
||||
name.map(|name| BTreeSet::from_iter([name]))
|
||||
.unwrap_or_else(|| {
|
||||
installed_tools
|
||||
.tools()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(name, _)| name)
|
||||
.collect()
|
||||
});
|
||||
let names: BTreeSet<PackageName> = {
|
||||
if name.is_empty() {
|
||||
installed_tools
|
||||
.tools()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(name, _)| name)
|
||||
.collect()
|
||||
} else {
|
||||
name.into_iter().collect()
|
||||
}
|
||||
};
|
||||
|
||||
if names.is_empty() {
|
||||
writeln!(printer.stderr(), "Nothing to upgrade")?;
|
||||
|
|
|
@ -414,7 +414,7 @@ impl ToolInstallSettings {
|
|||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ToolUpgradeSettings {
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) name: Vec<PackageName>,
|
||||
pub(crate) args: ResolverInstallerOptions,
|
||||
pub(crate) filesystem: ResolverInstallerOptions,
|
||||
}
|
||||
|
@ -445,7 +445,7 @@ impl ToolUpgradeSettings {
|
|||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
name: name.filter(|_| !all),
|
||||
name: if all { vec![] } else { name },
|
||||
args,
|
||||
filesystem,
|
||||
}
|
||||
|
@ -473,7 +473,7 @@ impl ToolListSettings {
|
|||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ToolUninstallSettings {
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) name: Option<Vec<PackageName>>,
|
||||
}
|
||||
|
||||
impl ToolUninstallSettings {
|
||||
|
|
|
@ -68,6 +68,53 @@ fn tool_uninstall() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_uninstall_multiple_names() {
|
||||
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `black`
|
||||
context
|
||||
.tool_install()
|
||||
.arg("black==24.2.0")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
context
|
||||
.tool_install()
|
||||
.arg("ruff==0.3.4")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_uninstall().arg("black").arg("ruff")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Uninstalled 3 executables: black, blackd, ruff
|
||||
"###);
|
||||
|
||||
// After uninstalling the tool, it shouldn't be listed.
|
||||
uv_snapshot!(context.filters(), context.tool_list()
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
No tools installed
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_uninstall_not_installed() {
|
||||
let context = TestContext::new("3.12").with_filtered_exe_suffix();
|
||||
|
|
|
@ -56,6 +56,81 @@ fn test_tool_upgrade_name() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_upgrade_multiple_names() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `python-dotenv` from Test PyPI, to get an outdated version.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("python-dotenv")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple/")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ python-dotenv==0.10.2.post2
|
||||
Installed 1 executable: dotenv
|
||||
"###);
|
||||
|
||||
// Install `babel` from Test PyPI, to get an outdated version.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("babel")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple/")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ babel==2.6.0
|
||||
+ pytz==2018.5
|
||||
Installed 1 executable: pybabel
|
||||
"###);
|
||||
|
||||
// Upgrade `babel` and `python-dotenv` from PyPI.
|
||||
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||
.arg("babel")
|
||||
.arg("python-dotenv")
|
||||
.arg("--index-url")
|
||||
.arg("https://pypi.org/simple/")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Updated babel v2.6.0 -> v2.14.0
|
||||
- babel==2.6.0
|
||||
+ babel==2.14.0
|
||||
- pytz==2018.5
|
||||
Installed 1 executable: pybabel
|
||||
Updated python-dotenv v0.10.2.post2 -> v1.0.1
|
||||
- python-dotenv==0.10.2.post2
|
||||
+ python-dotenv==1.0.1
|
||||
Installed 1 executable: dotenv
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_upgrade_all() {
|
||||
let context = TestContext::new("3.12")
|
||||
|
|
|
@ -2838,7 +2838,7 @@ If a tool was installed with specific settings, they will be respected on upgrad
|
|||
<h3 class="cli-reference">Usage</h3>
|
||||
|
||||
```
|
||||
uv tool upgrade [OPTIONS] <NAME>
|
||||
uv tool upgrade [OPTIONS] <NAME>...
|
||||
```
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
@ -3156,7 +3156,7 @@ Uninstall a tool
|
|||
<h3 class="cli-reference">Usage</h3>
|
||||
|
||||
```
|
||||
uv tool uninstall [OPTIONS] <NAME>
|
||||
uv tool uninstall [OPTIONS] <NAME>...
|
||||
```
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue