mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
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:
parent
063a0a4384
commit
1fc6a59707
64 changed files with 583 additions and 1813 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue