Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple packages for uv tool upgrade/uninstall #7037

Merged
merged 2 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3109,7 +3109,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"))]
Expand All @@ -3121,7 +3121,7 @@ pub struct ToolUninstallArgs {
pub struct ToolUpgradeArgs {
/// The name of the tool to upgrade.
#[arg(required = true)]
pub name: Option<PackageName>,
pub name: Option<Vec<PackageName>>,
blueraft marked this conversation as resolved.
Show resolved Hide resolved

/// Upgrade all tools.
#[arg(long, conflicts_with("name"))]
Expand Down
56 changes: 33 additions & 23 deletions crates/uv/src/commands/tool/uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(());
}
Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
bail!("`{name}` is not installed");
}
Err(err) => {
return Err(err.into());
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());
}
}
}
};
};

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()? {
Expand Down
25 changes: 14 additions & 11 deletions crates/uv/src/commands/tool/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::settings::ResolverInstallerSettings;

/// Upgrade a tool.
pub(crate) async fn upgrade(
name: Option<PackageName>,
name: Option<Vec<PackageName>>,
connectivity: Connectivity,
args: ResolverInstallerOptions,
filesystem: ResolverInstallerOptions,
Expand All @@ -34,16 +34,19 @@ 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> = {
let names: BTreeSet<_> = name.unwrap_or_default().into_iter().collect();
if names.is_empty() {
installed_tools
.tools()
.unwrap_or_default()
.into_iter()
.map(|(name, _)| name)
.collect()
} else {
names
}
};

if names.is_empty() {
writeln!(printer.stderr(), "Nothing to upgrade")?;
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: Option<Vec<PackageName>>,
pub(crate) args: ResolverInstallerOptions,
pub(crate) filesystem: ResolverInstallerOptions,
}
Expand Down Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions crates/uv/tests/tool_uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
75 changes: 75 additions & 0 deletions crates/uv/tests/tool_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2832,7 +2832,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>
Expand Down Expand Up @@ -3150,7 +3150,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>
Expand Down
Loading