Respect tool upgrades in uv tool install (#4736)

## Summary

For now the semantics are such that if the requested requirements from
the command line don't match the receipt (or if any `--reinstall` or
`--upgrade` is requested), we proceed with an install, passing the
`--reinstall` and `--upgrade` to the underlying Python environment.

This may lead to some unintuitive behaviors, but it's simplest for now.
For example:

- `uv tool install black<24` followed by `uv tool install black
--upgrade` will install the latest version of `black`, removing the
`<24` constraint.
- `uv tool install black --with black-plugin` followed by `uv tool
install black` will remove `black-plugin`.

Closes https://github.com/astral-sh/uv/issues/4659.
This commit is contained in:
Charlie Marsh 2024-07-02 16:46:31 -04:00 committed by GitHub
parent 21187e1f36
commit 32dc9bef59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 293 additions and 62 deletions

View file

@ -5,7 +5,7 @@ use url::Url;
use pep440_rs::VersionSpecifiers;
use pep508_rs::{MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl};
use uv_git::{GitReference, GitSha};
use uv_git::{GitReference, GitSha, GitUrl};
use uv_normalize::{ExtraName, PackageName};
use crate::{
@ -66,6 +66,80 @@ impl From<Requirement> for pep508_rs::Requirement<VerbatimUrl> {
}
}
impl From<Requirement> for pep508_rs::Requirement<VerbatimParsedUrl> {
/// Convert a [`Requirement`] to a [`pep508_rs::Requirement`].
fn from(requirement: Requirement) -> Self {
pep508_rs::Requirement {
name: requirement.name,
extras: requirement.extras,
marker: requirement.marker,
origin: requirement.origin,
version_or_url: match requirement.source {
RequirementSource::Registry { specifier, .. } => {
Some(VersionOrUrl::VersionSpecifier(specifier))
}
RequirementSource::Url {
subdirectory,
location,
url,
} => Some(VersionOrUrl::Url(VerbatimParsedUrl {
parsed_url: ParsedUrl::Archive(ParsedArchiveUrl {
url: location,
subdirectory,
}),
verbatim: url,
})),
RequirementSource::Git {
repository,
reference,
precise,
subdirectory,
url,
} => {
let git_url = if let Some(precise) = precise {
GitUrl::new(repository, reference).with_precise(precise)
} else {
GitUrl::new(repository, reference)
};
Some(VersionOrUrl::Url(VerbatimParsedUrl {
parsed_url: ParsedUrl::Git(ParsedGitUrl {
url: git_url,
subdirectory,
}),
verbatim: url,
}))
}
RequirementSource::Path {
install_path,
lock_path,
url,
} => Some(VersionOrUrl::Url(VerbatimParsedUrl {
parsed_url: ParsedUrl::Path(ParsedPathUrl {
url: url.to_url(),
install_path,
lock_path,
}),
verbatim: url,
})),
RequirementSource::Directory {
install_path,
lock_path,
editable,
url,
} => Some(VersionOrUrl::Url(VerbatimParsedUrl {
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl {
url: url.to_url(),
install_path,
lock_path,
editable,
}),
verbatim: url,
})),
},
}
}
}
impl From<pep508_rs::Requirement<VerbatimParsedUrl>> for Requirement {
/// Convert a [`pep508_rs::Requirement`] to a [`Requirement`].
fn from(requirement: pep508_rs::Requirement<VerbatimParsedUrl>) -> Self {