Remove special-casing for editable requirements (#3869)

## Summary

There are a few behavior changes in here:

- We now enforce `--require-hashes` for editables, like pip. So if you
use `--require-hashes` with an editable requirement, we'll reject it. I
could change this if it seems off.
- We now treat source tree requirements, editable or not (e.g., both `-e
./black` and `./black`) as if `--refresh` is always enabled. This
doesn't mean that we _always_ rebuild them; but if you pass
`--reinstall`, then yes, we always rebuild them. I think this is an
improvement and is close to how editables work today.

Closes #3844.

Closes #2695.
This commit is contained in:
Charlie Marsh 2024-05-28 11:49:34 -04:00 committed by GitHub
parent 063a0a4384
commit 1fc6a59707
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 583 additions and 1813 deletions

View file

@ -1,8 +1,6 @@
use std::collections::{BTreeMap, BTreeSet};
use url::Url;
use pep508_rs::{RequirementOrigin, VerbatimUrl};
use pep508_rs::RequirementOrigin;
use uv_fs::Simplified;
use uv_normalize::PackageName;
@ -40,35 +38,19 @@ impl std::fmt::Display for SourceAnnotation {
/// A collection of source annotations.
#[derive(Default, Debug, Clone)]
pub struct SourceAnnotations {
packages: BTreeMap<PackageName, BTreeSet<SourceAnnotation>>,
editables: BTreeMap<Url, BTreeSet<SourceAnnotation>>,
}
pub struct SourceAnnotations(BTreeMap<PackageName, BTreeSet<SourceAnnotation>>);
impl SourceAnnotations {
/// Add a source annotation to the collection for the given package.
pub fn add(&mut self, package: &PackageName, annotation: SourceAnnotation) {
self.packages
self.0
.entry(package.clone())
.or_default()
.insert(annotation);
}
/// Add an source annotation to the collection for the given editable.
pub fn add_editable(&mut self, url: &VerbatimUrl, annotation: SourceAnnotation) {
self.editables
.entry(url.to_url())
.or_default()
.insert(annotation);
}
/// Return the source annotations for a given package.
pub fn get(&self, package: &PackageName) -> Option<&BTreeSet<SourceAnnotation>> {
self.packages.get(package)
}
/// Return the source annotations for a given editable.
pub fn get_editable(&self, url: &VerbatimUrl) -> Option<&BTreeSet<SourceAnnotation>> {
self.editables.get(url.raw())
self.0.get(package)
}
}

View file

@ -47,6 +47,14 @@ impl BuildableSource<'_> {
Self::Url(_) => None,
}
}
/// Returns `true` if the source is editable.
pub fn is_editable(&self) -> bool {
match self {
Self::Dist(dist) => dist.is_editable(),
Self::Url(url) => url.is_editable(),
}
}
}
impl std::fmt::Display for BuildableSource<'_> {
@ -77,6 +85,14 @@ impl<'a> SourceUrl<'a> {
Self::Directory(dist) => dist.url,
}
}
/// Returns `true` if the source is editable.
pub fn is_editable(&self) -> bool {
matches!(
self,
Self::Directory(DirectorySourceUrl { editable: true, .. })
)
}
}
impl std::fmt::Display for SourceUrl<'_> {
@ -151,6 +167,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
pub struct DirectorySourceUrl<'a> {
pub url: &'a Url,
pub path: Cow<'a, Path>,
pub editable: bool,
}
impl std::fmt::Display for DirectorySourceUrl<'_> {
@ -164,6 +181,7 @@ impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
Self {
url: &dist.url,
path: Cow::Borrowed(&dist.path),
editable: dist.editable,
}
}
}

View file

@ -131,14 +131,6 @@ impl CachedDist {
}
}
/// Returns `true` if the distribution is editable.
pub fn editable(&self) -> bool {
match self {
Self::Registry(_) => false,
Self::Url(dist) => dist.editable,
}
}
/// Returns the [`WheelFilename`] of the distribution.
pub fn filename(&self) -> &WheelFilename {
match self {

View file

@ -1,95 +0,0 @@
use std::borrow::Cow;
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::path::PathBuf;
use url::Url;
use pep508_rs::VerbatimUrl;
use uv_normalize::ExtraName;
use crate::Verbatim;
#[derive(Debug, Clone)]
pub struct LocalEditable {
/// The underlying [`EditableRequirement`] from the `requirements.txt` file.
pub url: VerbatimUrl,
/// Either the path to the editable or its checkout.
pub path: PathBuf,
/// The extras that should be installed.
pub extras: Vec<ExtraName>,
}
impl LocalEditable {
/// Return the editable as a [`Url`].
pub fn url(&self) -> &VerbatimUrl {
&self.url
}
/// Return the resolved path to the editable.
pub fn raw(&self) -> &Url {
self.url.raw()
}
}
impl Verbatim for LocalEditable {
fn verbatim(&self) -> Cow<'_, str> {
self.url.verbatim()
}
}
impl std::fmt::Display for LocalEditable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.url, f)
}
}
/// A collection of [`LocalEditable`]s.
#[derive(Debug, Clone)]
pub struct LocalEditables(Vec<LocalEditable>);
impl LocalEditables {
/// Merge and dedupe a list of [`LocalEditable`]s.
///
/// This function will deduplicate any editables that point to identical paths, merging their
/// extras.
pub fn from_editables(editables: impl Iterator<Item = LocalEditable>) -> Self {
let mut map = BTreeMap::new();
for editable in editables {
match map.entry(editable.path.clone()) {
Entry::Vacant(entry) => {
entry.insert(editable);
}
Entry::Occupied(mut entry) => {
let existing = entry.get_mut();
existing.extras.extend(editable.extras);
}
}
}
Self(map.into_values().collect())
}
/// Return the number of editables.
pub fn len(&self) -> usize {
self.0.len()
}
/// Return whether the editables are empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Return the editables as a vector.
pub fn into_vec(self) -> Vec<LocalEditable> {
self.0
}
}
impl IntoIterator for LocalEditables {
type Item = LocalEditable;
type IntoIter = std::vec::IntoIter<LocalEditable>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

View file

@ -267,12 +267,10 @@ impl InstalledDist {
/// Return true if the distribution is editable.
pub fn is_editable(&self) -> bool {
match self {
Self::Registry(_) => false,
Self::Url(dist) => dist.editable,
Self::EggInfo(_) => false,
Self::LegacyEditable(_) => true,
}
matches!(
self,
Self::LegacyEditable(_) | Self::Url(InstalledDirectUrlDist { editable: true, .. })
)
}
/// Return the [`Url`] of the distribution, if it is editable.

View file

@ -51,7 +51,6 @@ pub use crate::any::*;
pub use crate::buildable::*;
pub use crate::cached::*;
pub use crate::diagnostic::*;
pub use crate::editable::*;
pub use crate::error::*;
pub use crate::file::*;
pub use crate::hash::*;
@ -70,7 +69,6 @@ mod any;
mod buildable;
mod cached;
mod diagnostic;
mod editable;
mod error;
mod file;
mod hash;
@ -401,24 +399,15 @@ impl Dist {
ParsedUrl::Archive(archive) => {
Self::from_http_url(name, url.verbatim, archive.url, archive.subdirectory)
}
ParsedUrl::Path(file) => Self::from_file_url(name, url.verbatim, &file.path, false),
ParsedUrl::Path(file) => {
Self::from_file_url(name, url.verbatim, &file.path, file.editable)
}
ParsedUrl::Git(git) => {
Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
}
}
}
/// Create a [`Dist`] for a local editable distribution.
pub fn from_editable(name: PackageName, editable: LocalEditable) -> Result<Self, Error> {
let LocalEditable { url, path, .. } = editable;
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
name,
url,
path,
editable: true,
})))
}
/// Return true if the distribution is editable.
pub fn is_editable(&self) -> bool {
match self {

View file

@ -44,6 +44,11 @@ impl Requirement {
true
}
}
/// Returns `true` if the requirement is editable.
pub fn is_editable(&self) -> bool {
self.source.is_editable()
}
}
impl From<pep508_rs::Requirement<VerbatimParsedUrl>> for Requirement {
@ -190,7 +195,7 @@ impl RequirementSource {
ParsedUrl::Path(local_file) => RequirementSource::Path {
path: local_file.path,
url,
editable: false,
editable: local_file.editable,
},
ParsedUrl::Git(git) => RequirementSource::Git {
url,

View file

@ -1,11 +1,9 @@
use std::collections::BTreeMap;
use pep508_rs::VerbatimUrl;
use uv_normalize::{ExtraName, PackageName};
use crate::{
BuiltDist, Diagnostic, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist,
LocalEditable, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
BuiltDist, Diagnostic, Dist, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
};
/// A set of packages pinned at specific versions.
@ -67,35 +65,6 @@ impl Resolution {
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
&self.diagnostics
}
/// Return an iterator over the [`LocalEditable`] entities in this resolution.
pub fn editables(&self) -> impl Iterator<Item = LocalEditable> + '_ {
self.packages.values().filter_map(|dist| match dist {
ResolvedDist::Installable(Dist::Source(SourceDist::Directory(
DirectorySourceDist {
path,
url,
editable: true,
..
},
))) => Some(LocalEditable {
url: url.clone(),
path: path.clone(),
extras: vec![],
}),
ResolvedDist::Installed(InstalledDist::Url(InstalledDirectUrlDist {
path,
url,
editable: true,
..
})) => Some(LocalEditable {
url: VerbatimUrl::from_url(url.clone()),
path: path.clone(),
extras: vec![],
}),
_ => None,
})
}
}
#[derive(Debug, Clone)]

View file

@ -73,4 +73,21 @@ impl UnresolvedRequirement {
)),
}
}
/// Returns `true` if the requirement is editable.
pub fn is_editable(&self) -> bool {
match self {
Self::Named(requirement) => requirement.is_editable(),
Self::Unnamed(requirement) => requirement.url.is_editable(),
}
}
}
impl From<Requirement> for UnresolvedRequirementSpecification {
fn from(requirement: Requirement) -> Self {
Self {
requirement: UnresolvedRequirement::Named(requirement),
hashes: Vec::new(),
}
}
}