diff --git a/Cargo.lock b/Cargo.lock index 5d5cc0152..7e90c7417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4486,7 +4486,6 @@ dependencies = [ "filetime", "flate2", "fs-err", - "indexmap", "indicatif", "indoc", "insta", @@ -4501,7 +4500,6 @@ dependencies = [ "pypi-types", "rayon", "regex", - "requirements-txt", "reqwest", "rustc-hash", "serde", @@ -4868,7 +4866,6 @@ dependencies = [ "platform-tags", "pypi-types", "rayon", - "requirements-txt", "rustc-hash", "serde", "tempfile", diff --git a/crates/distribution-types/src/annotation.rs b/crates/distribution-types/src/annotation.rs index f6cd207f5..91c7a71ee 100644 --- a/crates/distribution-types/src/annotation.rs +++ b/crates/distribution-types/src/annotation.rs @@ -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>, - editables: BTreeMap>, -} +pub struct SourceAnnotations(BTreeMap>); 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> { - self.packages.get(package) - } - - /// Return the source annotations for a given editable. - pub fn get_editable(&self, url: &VerbatimUrl) -> Option<&BTreeSet> { - self.editables.get(url.raw()) + self.0.get(package) } } diff --git a/crates/distribution-types/src/buildable.rs b/crates/distribution-types/src/buildable.rs index e3bee3cdf..c693402ec 100644 --- a/crates/distribution-types/src/buildable.rs +++ b/crates/distribution-types/src/buildable.rs @@ -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, } } } diff --git a/crates/distribution-types/src/cached.rs b/crates/distribution-types/src/cached.rs index 3ad68e8fc..2d8264a2f 100644 --- a/crates/distribution-types/src/cached.rs +++ b/crates/distribution-types/src/cached.rs @@ -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 { diff --git a/crates/distribution-types/src/editable.rs b/crates/distribution-types/src/editable.rs deleted file mode 100644 index 5894b1151..000000000 --- a/crates/distribution-types/src/editable.rs +++ /dev/null @@ -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, -} - -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); - -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) -> 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 { - self.0 - } -} - -impl IntoIterator for LocalEditables { - type Item = LocalEditable; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} diff --git a/crates/distribution-types/src/installed.rs b/crates/distribution-types/src/installed.rs index c3f5bbfbd..8879ad319 100644 --- a/crates/distribution-types/src/installed.rs +++ b/crates/distribution-types/src/installed.rs @@ -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. diff --git a/crates/distribution-types/src/lib.rs b/crates/distribution-types/src/lib.rs index dd64687f8..4e10a38c8 100644 --- a/crates/distribution-types/src/lib.rs +++ b/crates/distribution-types/src/lib.rs @@ -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 { - 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 { diff --git a/crates/distribution-types/src/requirement.rs b/crates/distribution-types/src/requirement.rs index 7fe68bee0..4f235d702 100644 --- a/crates/distribution-types/src/requirement.rs +++ b/crates/distribution-types/src/requirement.rs @@ -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> 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, diff --git a/crates/distribution-types/src/resolution.rs b/crates/distribution-types/src/resolution.rs index 463a5f4d9..8f5a1a222 100644 --- a/crates/distribution-types/src/resolution.rs +++ b/crates/distribution-types/src/resolution.rs @@ -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 + '_ { - 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)] diff --git a/crates/distribution-types/src/specified_requirement.rs b/crates/distribution-types/src/specified_requirement.rs index 4287cfd3c..8fed17d62 100644 --- a/crates/distribution-types/src/specified_requirement.rs +++ b/crates/distribution-types/src/specified_requirement.rs @@ -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 for UnresolvedRequirementSpecification { + fn from(requirement: Requirement) -> Self { + Self { + requirement: UnresolvedRequirement::Named(requirement), + hashes: Vec::new(), + } + } } diff --git a/crates/pypi-types/src/parsed_url.rs b/crates/pypi-types/src/parsed_url.rs index 04485143b..2da76928e 100644 --- a/crates/pypi-types/src/parsed_url.rs +++ b/crates/pypi-types/src/parsed_url.rs @@ -33,6 +33,13 @@ pub struct VerbatimParsedUrl { pub verbatim: VerbatimUrl, } +impl VerbatimParsedUrl { + /// Returns `true` if the URL is editable. + pub fn is_editable(&self) -> bool { + self.parsed_url.is_editable() + } +} + impl Pep508Url for VerbatimParsedUrl { type Err = ParsedUrlError; @@ -150,6 +157,13 @@ pub enum ParsedUrl { Archive(ParsedArchiveUrl), } +impl ParsedUrl { + /// Returns `true` if the URL is editable. + pub fn is_editable(&self) -> bool { + matches!(self, Self::Path(ParsedPathUrl { editable: true, .. })) + } +} + /// A local path url /// /// Examples: diff --git a/crates/requirements-txt/src/lib.rs b/crates/requirements-txt/src/lib.rs index e2ab9aac0..68166c033 100644 --- a/crates/requirements-txt/src/lib.rs +++ b/crates/requirements-txt/src/lib.rs @@ -47,9 +47,9 @@ use url::Url; use distribution_types::{Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification}; use pep508_rs::{ expand_env_vars, split_scheme, strip_host, Extras, MarkerTree, Pep508Error, Pep508ErrorSource, - RequirementOrigin, Scheme, VerbatimUrl, + RequirementOrigin, Scheme, UnnamedRequirement, VerbatimUrl, }; -use pypi_types::VerbatimParsedUrl; +use pypi_types::{ParsedPathUrl, ParsedUrl, VerbatimParsedUrl}; #[cfg(feature = "http")] use uv_client::BaseClient; use uv_client::BaseClientBuilder; @@ -418,6 +418,27 @@ impl From for UnresolvedRequirementSpecification { } } +impl From for UnresolvedRequirementSpecification { + fn from(value: EditableRequirement) -> Self { + Self { + requirement: UnresolvedRequirement::Unnamed(UnnamedRequirement { + url: VerbatimParsedUrl { + parsed_url: ParsedUrl::Path(ParsedPathUrl { + url: value.url.to_url(), + path: value.path, + editable: true, + }), + verbatim: value.url, + }, + extras: value.extras, + marker: value.marker, + origin: value.origin, + }), + hashes: vec![], + } + } +} + /// Parsed and flattened requirements.txt with requirements and constraints #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct RequirementsTxt { diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index b7f7f7c55..936329d90 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -129,7 +129,7 @@ impl Cache { pub fn from_path(root: impl Into) -> Self { Self { root: root.into(), - refresh: Refresh::None, + refresh: Refresh::None(Timestamp::now()), _temp_dir_drop: None, } } @@ -139,7 +139,7 @@ impl Cache { let temp_dir = tempdir()?; Ok(Self { root: temp_dir.path().to_path_buf(), - refresh: Refresh::None, + refresh: Refresh::None(Timestamp::now()), _temp_dir_drop: Some(Arc::new(temp_dir)), }) } @@ -183,13 +183,16 @@ impl Cache { /// Returns `true` if a cache entry must be revalidated given the [`Refresh`] policy. pub fn must_revalidate(&self, package: &PackageName) -> bool { match &self.refresh { - Refresh::None => false, + Refresh::None(_) => false, Refresh::All(_) => true, Refresh::Packages(packages, _) => packages.contains(package), } } - /// Returns `true` if a cache entry is up-to-date given the [`Refresh`] policy. + /// Returns the [`Freshness`] for a cache entry, validating it against the [`Refresh`] policy. + /// + /// A cache entry is considered fresh if it was created after the cache itself was + /// initialized, or if the [`Refresh`] policy does not require revalidation. pub fn freshness( &self, entry: &CacheEntry, @@ -197,7 +200,7 @@ impl Cache { ) -> io::Result { // Grab the cutoff timestamp, if it's relevant. let timestamp = match &self.refresh { - Refresh::None => return Ok(Freshness::Fresh), + Refresh::None(_) => return Ok(Freshness::Fresh), Refresh::All(timestamp) => timestamp, Refresh::Packages(packages, timestamp) => { if package.map_or(true, |package| packages.contains(package)) { @@ -221,6 +224,26 @@ impl Cache { } } + /// Returns `true` if a cache entry is up-to-date. Unlike [`Cache::freshness`], this method does + /// not take the [`Refresh`] policy into account. + /// + /// A cache entry is considered up-to-date if it was created after the [`Cache`] instance itself + /// was initialized. + pub fn is_fresh(&self, entry: &CacheEntry) -> io::Result { + // Grab the cutoff timestamp. + let timestamp = match &self.refresh { + Refresh::None(timestamp) => timestamp, + Refresh::All(timestamp) => timestamp, + Refresh::Packages(_packages, timestamp) => timestamp, + }; + + match fs::metadata(entry.path()) { + Ok(metadata) => Ok(Timestamp::from_metadata(&metadata) >= *timestamp), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false), + Err(err) => Err(err), + } + } + /// Persist a temporary directory to the artifact store, returning its unique ID. pub async fn persist( &self, @@ -897,10 +920,13 @@ impl Freshness { } /// A refresh policy for cache entries. +/// +/// Each policy stores a timestamp, even if no entries are refreshed, to enable out-of-policy +/// freshness checks via [`Cache::is_fresh`]. #[derive(Debug, Clone)] pub enum Refresh { /// Don't refresh any entries. - None, + None(Timestamp), /// Refresh entries linked to the given packages, if created before the given timestamp. Packages(Vec, Timestamp), /// Refresh all entries created before the given timestamp. @@ -910,14 +936,15 @@ pub enum Refresh { impl Refresh { /// Determine the refresh strategy to use based on the command-line arguments. pub fn from_args(refresh: Option, refresh_package: Vec) -> Self { + let timestamp = Timestamp::now(); match refresh { - Some(true) => Self::All(Timestamp::now()), - Some(false) => Self::None, + Some(true) => Self::All(timestamp), + Some(false) => Self::None(timestamp), None => { if refresh_package.is_empty() { - Self::None + Self::None(timestamp) } else { - Self::Packages(refresh_package, Timestamp::now()) + Self::Packages(refresh_package, timestamp) } } } @@ -925,6 +952,6 @@ impl Refresh { /// Returns `true` if no packages should be reinstalled. pub fn is_none(&self) -> bool { - matches!(self, Self::None) + matches!(self, Self::None(_)) } } diff --git a/crates/uv-cache/src/wheel.rs b/crates/uv-cache/src/wheel.rs index 7de919dc5..38619b837 100644 --- a/crates/uv-cache/src/wheel.rs +++ b/crates/uv-cache/src/wheel.rs @@ -14,6 +14,8 @@ pub enum WheelCache<'a> { Url(&'a Url), /// A path dependency, which we key by URL. Path(&'a Url), + /// An editable dependency, which we key by URL. + Editable(&'a Url), /// A Git dependency, which we key by URL and SHA. /// /// Note that this variant only exists for source distributions; wheels can't be delivered @@ -35,6 +37,9 @@ impl<'a> WheelCache<'a> { WheelCache::Path(url) => WheelCacheKind::Path .root() .join(digest(&CanonicalUrl::new(url))), + WheelCache::Editable(url) => WheelCacheKind::Editable + .root() + .join(digest(&CanonicalUrl::new(url))), WheelCache::Git(url, sha) => WheelCacheKind::Git .root() .join(digest(&CanonicalUrl::new(url))) @@ -58,6 +63,8 @@ pub(crate) enum WheelCacheKind { Url, /// A cache of data from a local path. Path, + /// A cache of data from an editable URL. + Editable, /// A cache of data from a Git repository. Git, } @@ -69,6 +76,7 @@ impl WheelCacheKind { Self::Index => "index", Self::Url => "url", Self::Path => "path", + Self::Editable => "editable", Self::Git => "git", } } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index a09c43cc6..36926a142 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -199,7 +199,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { remote, reinstalls, extraneous: _, - } = Planner::with_requirements(&requirements).build( + } = Planner::new(&requirements).build( site_packages, &Reinstall::None, &NoBinary::None, diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index 1d7462efb..ebab360f6 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -11,16 +11,16 @@ use tempfile::TempDir; use tokio::io::{AsyncRead, AsyncSeekExt, ReadBuf}; use tokio::sync::Semaphore; use tokio_util::compat::FuturesAsyncReadCompatExt; -use tracing::{info_span, instrument, warn, Instrument}; +use tracing::{debug, info_span, instrument, warn, Instrument}; use url::Url; use distribution_filename::WheelFilename; use distribution_types::{ - BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, IndexLocations, - LocalEditable, Name, SourceDist, + BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, IndexLocations, Name, + SourceDist, }; use platform_tags::Tags; -use pypi_types::{HashDigest, Metadata23}; +use pypi_types::HashDigest; use uv_cache::{ArchiveId, ArchiveTimestamp, CacheBucket, CacheEntry, Timestamp, WheelCache}; use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, @@ -133,32 +133,6 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { } } - /// Build a directory into an editable wheel. - pub async fn build_wheel_editable( - &self, - editable: &LocalEditable, - editable_wheel_dir: &Path, - ) -> Result<(LocalWheel, Metadata23), Error> { - // Build the wheel. - let (dist, disk_filename, filename, metadata) = self - .builder - .build_editable(editable, editable_wheel_dir) - .await?; - - // Unzip into the editable wheel directory. - let path = editable_wheel_dir.join(&disk_filename); - let target = editable_wheel_dir.join(cache_key::digest(&editable.path)); - let id = self.unzip_wheel(&path, &target).await?; - let wheel = LocalWheel { - dist, - filename, - archive: self.build_context.cache().archive(&id), - hashes: vec![], - }; - - Ok((wheel, metadata)) - } - /// Fetch a wheel from the cache or download it from the index. /// /// While hashes will be generated in all cases, hash-checking is _not_ enforced and should @@ -432,7 +406,11 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Optimization: Skip source dist download when we must not build them anyway. if no_build { - return Err(Error::NoBuild); + if source.is_editable() { + debug!("Allowing build for editable source distribution: {source}"); + } else { + return Err(Error::NoBuild); + } } let lock = self.locks.acquire(source).await; @@ -443,6 +421,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .download_and_build_metadata(source, hashes, &self.client) .boxed_local() .await?; + Ok(metadata) } diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index 9b5f344dd..5927929c0 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -92,7 +92,11 @@ impl<'a> BuiltWheelIndex<'a> { ) -> Result, Error> { let cache_shard = self.cache.shard( CacheBucket::BuiltWheels, - WheelCache::Path(&source_dist.url).root(), + if source_dist.editable { + WheelCache::Editable(&source_dist.url).root() + } else { + WheelCache::Path(&source_dist.url).root() + }, ); // Read the revision from the cache. diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 493ba8332..a16367881 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -16,8 +16,8 @@ use zip::ZipArchive; use distribution_filename::WheelFilename; use distribution_types::{ - BuildableSource, DirectorySourceDist, DirectorySourceUrl, Dist, FileLocation, GitSourceUrl, - HashPolicy, Hashed, LocalEditable, PathSourceUrl, RemoteSource, SourceDist, SourceUrl, + BuildableSource, DirectorySourceUrl, FileLocation, GitSourceUrl, HashPolicy, Hashed, + PathSourceUrl, RemoteSource, SourceDist, SourceUrl, }; use install_wheel_rs::metadata::read_archive_metadata; use platform_tags::Tags; @@ -369,7 +369,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .boxed_local() .await? } - BuildableSource::Url(SourceUrl::Path(resource)) => { let cache_shard = self.build_context.cache().shard( CacheBucket::BuiltWheels, @@ -825,7 +824,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Ok(revision) } - /// Build a source distribution from a local source tree (i.e., directory). + /// Build a source distribution from a local source tree (i.e., directory), either editable or + /// non-editable. async fn source_tree( &self, source: &BuildableSource<'_>, @@ -840,15 +840,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = self.build_context.cache().shard( CacheBucket::BuiltWheels, - WheelCache::Path(resource.url).root(), + if resource.editable { + WheelCache::Editable(resource.url).root() + } else { + WheelCache::Path(resource.url).root() + }, ); let _lock = lock_shard(&cache_shard).await?; // Fetch the revision for the source distribution. - let revision = self - .source_tree_revision(source, resource, &cache_shard) - .await?; + let revision = self.source_tree_revision(resource, &cache_shard).await?; // Scope all operations to the revision. Within the revision, there's no need to check for // freshness, since entries have to be fresher than the revision itself. @@ -889,7 +891,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }) } - /// Build the source distribution's metadata from a local source tree (i.e., a directory). + /// Build the source distribution's metadata from a local source tree (i.e., a directory), + /// either editable or non-editable. /// /// If the build backend supports `prepare_metadata_for_build_wheel`, this method will avoid /// building the wheel. @@ -906,15 +909,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = self.build_context.cache().shard( CacheBucket::BuiltWheels, - WheelCache::Path(resource.url).root(), + if resource.editable { + WheelCache::Editable(resource.url).root() + } else { + WheelCache::Path(resource.url).root() + }, ); let _lock = lock_shard(&cache_shard).await?; // Fetch the revision for the source distribution. - let revision = self - .source_tree_revision(source, resource, &cache_shard) - .await?; + let revision = self.source_tree_revision(resource, &cache_shard).await?; // Scope all operations to the revision. Within the revision, there's no need to check for // freshness, since entries have to be fresher than the revision itself. @@ -971,7 +976,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { /// Return the [`Revision`] for a local source tree, refreshing it if necessary. async fn source_tree_revision( &self, - source: &BuildableSource<'_>, resource: &DirectorySourceUrl<'_>, cache_shard: &CacheShard, ) -> Result { @@ -982,16 +986,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { return Err(Error::DirWithoutEntrypoint(resource.path.to_path_buf())); }; - // Read the existing metadata from the cache. + // Read the existing metadata from the cache. We treat source trees as if `--refresh` is + // always set, since they're mutable. let entry = cache_shard.entry(LOCAL_REVISION); - let freshness = self + let is_fresh = self .build_context .cache() - .freshness(&entry, source.name()) + .is_fresh(&entry) .map_err(Error::CacheRead)?; // If the revision is fresh, return it. - if freshness.is_fresh() { + if is_fresh { if let Some(pointer) = LocalRevisionPointer::read_from(&entry)? { if pointer.timestamp == modified.timestamp() { return Ok(pointer.into_revision()); @@ -1299,8 +1304,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source.name().is_some_and(|name| packages.contains(name)) } }; + if no_build { - return Err(Error::NoBuild); + if source.is_editable() { + debug!("Allowing build for editable source distribution: {source}"); + } else { + return Err(Error::NoBuild); + } } // Build the wheel. @@ -1314,7 +1324,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { subdirectory, &source.to_string(), source.as_dist(), - BuildKind::Wheel, + if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }, ) .await .map_err(|err| Error::Build(source.to_string(), err))? @@ -1383,7 +1397,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { subdirectory, &source.to_string(), source.as_dist(), - BuildKind::Wheel, + if source.is_editable() { + BuildKind::Editable + } else { + BuildKind::Wheel + }, ) .await .map_err(|err| Error::Build(source.to_string(), err))?; @@ -1410,49 +1428,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Ok(Some(metadata)) } - /// Build a single directory into an editable wheel - pub async fn build_editable( - &self, - editable: &LocalEditable, - editable_wheel_dir: &Path, - ) -> Result<(Dist, String, WheelFilename, Metadata23), Error> { - debug!("Building (editable) {editable}"); - - // Verify that the editable exists. - if !editable.path.exists() { - return Err(Error::NotFound(editable.path.clone())); - } - - // Build the wheel. - let disk_filename = self - .build_context - .setup_build( - &editable.path, - None, - &editable.to_string(), - None, - BuildKind::Editable, - ) - .await - .map_err(|err| Error::BuildEditable(editable.to_string(), err))? - .wheel(editable_wheel_dir) - .await - .map_err(|err| Error::BuildEditable(editable.to_string(), err))?; - let filename = WheelFilename::from_str(&disk_filename)?; - - // We finally have the name of the package and can construct the dist. - let dist = Dist::Source(SourceDist::Directory(DirectorySourceDist { - name: filename.name.clone(), - url: editable.url().clone(), - path: editable.path.clone(), - editable: true, - })); - let metadata = read_wheel_metadata(&filename, editable_wheel_dir.join(&disk_filename))?; - - debug!("Finished building (editable): {dist}"); - Ok((dist, disk_filename, filename, metadata)) - } - /// Returns a GET [`reqwest::Request`] for the given URL. fn request(url: Url, client: &RegistryClient) -> Result { client diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index b62cda5c6..6c1357edb 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -21,7 +21,6 @@ pep440_rs = { workspace = true } pep508_rs = { workspace = true } platform-tags = { workspace = true } pypi-types = { workspace = true } -requirements-txt = { workspace = true } uv-cache = { workspace = true } uv-configuration = { workspace = true } uv-distribution = { workspace = true } diff --git a/crates/uv-installer/src/downloader.rs b/crates/uv-installer/src/downloader.rs index 0aa84f574..5b0bd3c54 100644 --- a/crates/uv-installer/src/downloader.rs +++ b/crates/uv-installer/src/downloader.rs @@ -1,24 +1,18 @@ use std::cmp::Reverse; -use std::path::Path; use std::sync::Arc; -use futures::{stream::FuturesUnordered, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt}; +use futures::{stream::FuturesUnordered, FutureExt, Stream, TryFutureExt, TryStreamExt}; use pep508_rs::PackageName; use tokio::task::JoinError; use tracing::instrument; use url::Url; -use distribution_types::{ - BuildableSource, CachedDist, Dist, Hashed, Identifier, LocalEditable, LocalEditables, - RemoteSource, -}; +use distribution_types::{BuildableSource, CachedDist, Dist, Hashed, Identifier, RemoteSource}; use platform_tags::Tags; use uv_cache::Cache; use uv_distribution::{DistributionDatabase, LocalWheel}; use uv_types::{BuildContext, HashStrategy, InFlight}; -use crate::editable::BuiltEditable; - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Failed to unzip wheel: {0}")] @@ -115,55 +109,6 @@ impl<'a, Context: BuildContext> Downloader<'a, Context> { Ok(wheels) } - /// Build a set of editables - #[instrument(skip_all)] - pub async fn build_editables( - &self, - editables: LocalEditables, - editable_wheel_dir: &Path, - ) -> Result, Error> { - // Build editables in parallel - let mut results = Vec::with_capacity(editables.len()); - let mut fetches = editables - .into_iter() - .map(|editable| async move { - let task_id = self - .reporter - .as_ref() - .map(|reporter| reporter.on_editable_build_start(&editable)); - let (local_wheel, metadata) = self - .database - .build_wheel_editable(&editable, editable_wheel_dir) - .await - .map_err(Error::Editable)?; - let cached_dist = CachedDist::from(local_wheel); - if let Some(task_id) = task_id { - if let Some(reporter) = &self.reporter { - reporter.on_editable_build_complete(&editable, task_id); - } - } - Ok::<_, Error>((editable, cached_dist, metadata)) - }) - .collect::>(); - - while let Some((editable, wheel, metadata)) = fetches.next().await.transpose()? { - if let Some(reporter) = self.reporter.as_ref() { - reporter.on_progress(&wheel); - } - results.push(BuiltEditable { - editable, - wheel, - metadata, - }); - } - - if let Some(reporter) = self.reporter.as_ref() { - reporter.on_complete(); - } - - Ok(results) - } - /// Download, build, and unzip a single wheel. #[instrument(skip_all, fields(name = % dist, size = ? dist.size(), url = dist.file().map(| file | file.url.to_string()).unwrap_or_default()))] pub async fn get_wheel(&self, dist: Dist, in_flight: &InFlight) -> Result { @@ -241,12 +186,6 @@ pub trait Reporter: Send + Sync { /// Callback to invoke when a source distribution build is complete. fn on_build_complete(&self, source: &BuildableSource, id: usize); - /// Callback to invoke when a editable build is kicked off. - fn on_editable_build_start(&self, dist: &LocalEditable) -> usize; - - /// Callback to invoke when a editable build is complete. - fn on_editable_build_complete(&self, dist: &LocalEditable, id: usize); - /// Callback to invoke when a repository checkout begins. fn on_checkout_start(&self, url: &Url, rev: &str) -> usize; diff --git a/crates/uv-installer/src/editable.rs b/crates/uv-installer/src/editable.rs deleted file mode 100644 index c0915a0d7..000000000 --- a/crates/uv-installer/src/editable.rs +++ /dev/null @@ -1,158 +0,0 @@ -use serde::Deserialize; -use std::path::Path; - -use distribution_types::{ - CachedDist, InstalledDist, InstalledMetadata, InstalledVersion, LocalEditable, Name, -}; -use pypi_types::Metadata23; - -use uv_normalize::PackageName; - -/// An editable distribution that has been installed. -#[derive(Debug, Clone)] -pub struct InstalledEditable { - pub editable: LocalEditable, - pub wheel: InstalledDist, - pub metadata: Metadata23, -} - -/// An editable distribution that has been built. -#[derive(Debug, Clone)] -pub struct BuiltEditable { - pub editable: LocalEditable, - pub wheel: CachedDist, - pub metadata: Metadata23, -} - -/// An editable distribution that has been resolved to a concrete distribution. -#[derive(Debug, Clone)] -#[allow(clippy::large_enum_variant)] -pub enum ResolvedEditable { - /// The editable is already installed in the environment. - Installed(InstalledEditable), - /// The editable has been built and is ready to be installed. - Built(BuiltEditable), -} - -impl ResolvedEditable { - /// Return the [`LocalEditable`] for the distribution. - pub fn local(&self) -> &LocalEditable { - match self { - Self::Installed(dist) => &dist.editable, - Self::Built(dist) => &dist.editable, - } - } - - /// Return the [`Metadata23`] for the distribution. - pub fn metadata(&self) -> &Metadata23 { - match self { - Self::Installed(dist) => &dist.metadata, - Self::Built(dist) => &dist.metadata, - } - } -} - -impl Name for InstalledEditable { - fn name(&self) -> &PackageName { - &self.metadata.name - } -} - -impl Name for BuiltEditable { - fn name(&self) -> &PackageName { - &self.metadata.name - } -} - -impl Name for ResolvedEditable { - fn name(&self) -> &PackageName { - match self { - Self::Installed(dist) => dist.name(), - Self::Built(dist) => dist.name(), - } - } -} - -impl InstalledMetadata for InstalledEditable { - fn installed_version(&self) -> InstalledVersion { - self.wheel.installed_version() - } -} - -impl InstalledMetadata for BuiltEditable { - fn installed_version(&self) -> InstalledVersion { - self.wheel.installed_version() - } -} - -impl InstalledMetadata for ResolvedEditable { - fn installed_version(&self) -> InstalledVersion { - match self { - Self::Installed(dist) => dist.installed_version(), - Self::Built(dist) => dist.installed_version(), - } - } -} - -impl std::fmt::Display for InstalledEditable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}{}", self.name(), self.installed_version()) - } -} - -impl std::fmt::Display for BuiltEditable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}{}", self.name(), self.installed_version()) - } -} - -impl std::fmt::Display for ResolvedEditable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}{}", self.name(), self.installed_version()) - } -} - -/// Returns `true` if the source tree at the given path contains dynamic metadata. -pub fn is_dynamic(path: &Path) -> bool { - // If there's no `pyproject.toml`, we assume it's dynamic. - let Ok(contents) = fs_err::read_to_string(path.join("pyproject.toml")) else { - return true; - }; - let Ok(pyproject_toml) = toml::from_str::(&contents) else { - return true; - }; - // If `[project]` is not present, we assume it's dynamic. - let Some(project) = pyproject_toml.project else { - // ...unless it appears to be a Poetry project. - return pyproject_toml - .tool - .map_or(true, |tool| tool.poetry.is_none()); - }; - // `[project.dynamic]` must be present and non-empty. - project.dynamic.is_some_and(|dynamic| !dynamic.is_empty()) -} - -/// A pyproject.toml as specified in PEP 517. -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct PyProjectToml { - project: Option, - tool: Option, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct Project { - dynamic: Option>, -} - -#[derive(Deserialize, Debug)] -struct Tool { - poetry: Option, -} - -#[derive(Deserialize, Debug)] -struct ToolPoetry { - #[allow(dead_code)] - name: Option, -} diff --git a/crates/uv-installer/src/lib.rs b/crates/uv-installer/src/lib.rs index effa69ca9..1a9e37124 100644 --- a/crates/uv-installer/src/lib.rs +++ b/crates/uv-installer/src/lib.rs @@ -1,6 +1,5 @@ pub use compile::{compile_tree, CompileError}; pub use downloader::{Downloader, Reporter as DownloadReporter}; -pub use editable::{is_dynamic, BuiltEditable, InstalledEditable, ResolvedEditable}; pub use installer::{Installer, Reporter as InstallReporter}; pub use plan::{Plan, Planner}; pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic}; @@ -8,7 +7,7 @@ pub use uninstall::{uninstall, UninstallError}; mod compile; mod downloader; -mod editable; + mod installer; mod plan; mod satisfies; diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index b6dd5b9c0..d14420100 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -5,14 +5,13 @@ use std::str::FromStr; use anyhow::{bail, Result}; use rustc_hash::FxHashMap; -use tracing::{debug, warn}; +use tracing::debug; use distribution_filename::WheelFilename; use distribution_types::{ CachedDirectUrlDist, CachedDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, - Error, GitSourceDist, Hashed, IndexLocations, InstalledDist, InstalledMetadata, - InstalledVersion, Name, PathBuiltDist, PathSourceDist, RemoteSource, Requirement, - RequirementSource, Verbatim, + Error, GitSourceDist, Hashed, IndexLocations, InstalledDist, Name, PathBuiltDist, + PathSourceDist, RemoteSource, Requirement, RequirementSource, Verbatim, }; use platform_tags::Tags; use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache}; @@ -26,32 +25,18 @@ use uv_interpreter::PythonEnvironment; use uv_types::HashStrategy; use crate::satisfies::RequirementSatisfaction; -use crate::{ResolvedEditable, SitePackages}; +use crate::SitePackages; /// A planner to generate an [`Plan`] based on a set of requirements. #[derive(Debug)] pub struct Planner<'a> { requirements: &'a [Requirement], - editable_requirements: &'a [ResolvedEditable], } impl<'a> Planner<'a> { /// Set the requirements use in the [`Plan`]. - #[must_use] - pub fn with_requirements(requirements: &'a [Requirement]) -> Self { - Self { - requirements, - editable_requirements: &[], - } - } - - /// Set the editable requirements use in the [`Plan`]. - #[must_use] - pub fn with_editable_requirements(self, editable_requirements: &'a [ResolvedEditable]) -> Self { - Self { - editable_requirements, - ..self - } + pub fn new(requirements: &'a [Requirement]) -> Self { + Self { requirements } } /// Partition a set of requirements into those that should be linked from the cache, those that @@ -90,56 +75,6 @@ impl<'a> Planner<'a> { BuildHasherDefault::default(), ); - // Remove any editable requirements. - for requirement in self.editable_requirements { - // If we see the same requirement twice, then we have a conflict. - let specifier = Specifier::Editable(requirement.installed_version()); - match seen.entry(requirement.name().clone()) { - Entry::Occupied(value) => { - if value.get() == &specifier { - continue; - } - bail!( - "Detected duplicate package in requirements: {}", - requirement.name() - ); - } - Entry::Vacant(entry) => { - entry.insert(specifier); - } - } - - match requirement { - ResolvedEditable::Installed(installed) => { - debug!("Treating editable requirement as immutable: {installed}"); - - // Remove from the site-packages index, to avoid marking as extraneous. - let Some(editable) = installed.wheel.as_editable() else { - warn!("Editable requirement is not editable: {installed}"); - continue; - }; - let existing = site_packages.remove_editables(editable); - if existing.is_empty() { - warn!("Editable requirement is not installed: {installed}"); - continue; - } - } - ResolvedEditable::Built(built) => { - debug!("Treating editable requirement as mutable: {built}"); - - // Remove any editables. - let existing = site_packages.remove_editables(built.editable.raw()); - reinstalls.extend(existing); - - // Remove any non-editable installs of the same package. - let existing = site_packages.remove_packages(built.name()); - reinstalls.extend(existing); - - cached.push(built.wheel.clone()); - } - } - } - for requirement in self.requirements { // Filter out incompatible requirements. if !requirement.evaluate_markers(Some(venv.interpreter().markers()), &[]) { @@ -147,10 +82,9 @@ impl<'a> Planner<'a> { } // If we see the same requirement twice, then we have a conflict. - let specifier = Specifier::NonEditable(&requirement.source); match seen.entry(requirement.name.clone()) { Entry::Occupied(value) => { - if value.get() == &specifier { + if value.get() == &&requirement.source { continue; } bail!( @@ -159,7 +93,7 @@ impl<'a> Planner<'a> { ); } Entry::Vacant(entry) => { - entry.insert(specifier); + entry.insert(&requirement.source); } } @@ -195,6 +129,9 @@ impl<'a> Planner<'a> { RequirementSatisfaction::OutOfDate => { debug!("Requirement installed, but not fresh: {distribution}"); } + RequirementSatisfaction::Dynamic => { + debug!("Requirement installed, but dynamic: {distribution}"); + } } reinstalls.push(distribution.clone()); } @@ -332,7 +269,7 @@ impl<'a> Planner<'a> { continue; } } - RequirementSource::Path { url, .. } => { + RequirementSource::Path { url, editable, .. } => { // Store the canonicalized path, which also serves to validate that it exists. let path = match url .to_file_path() @@ -352,7 +289,7 @@ impl<'a> Planner<'a> { name: requirement.name.clone(), url: url.clone(), path, - editable: false, + editable: *editable, }; // Find the most-compatible wheel from the cache, since we don't know @@ -478,14 +415,6 @@ impl<'a> Planner<'a> { } } -#[derive(Debug, PartialEq, Eq)] -enum Specifier<'a> { - /// An editable requirement, marked by the installed version of the package. - Editable(InstalledVersion<'a>), - /// A non-editable requirement, marked by the version or URL specifier. - NonEditable(&'a RequirementSource), -} - #[derive(Debug, Default)] pub struct Plan { /// The distributions that are not already installed in the current environment, but are diff --git a/crates/uv-installer/src/satisfies.rs b/crates/uv-installer/src/satisfies.rs index 331d4b70e..4f807334d 100644 --- a/crates/uv-installer/src/satisfies.rs +++ b/crates/uv-installer/src/satisfies.rs @@ -1,8 +1,11 @@ -use anyhow::Result; -use cache_key::{CanonicalUrl, RepositoryUrl}; use std::fmt::Debug; +use std::path::Path; + +use anyhow::Result; +use serde::Deserialize; use tracing::{debug, trace}; +use cache_key::{CanonicalUrl, RepositoryUrl}; use distribution_types::{InstalledDirectUrlDist, InstalledDist, RequirementSource}; use pypi_types::{DirInfo, DirectUrl, VcsInfo, VcsKind}; use uv_cache::{ArchiveTarget, ArchiveTimestamp}; @@ -12,6 +15,7 @@ pub(crate) enum RequirementSatisfaction { Mismatch, Satisfied, OutOfDate, + Dynamic, } impl RequirementSatisfaction { @@ -187,9 +191,59 @@ impl RequirementSatisfaction { return Ok(Self::OutOfDate); } + // Does the package have dynamic metadata? + if is_dynamic(path) { + return Ok(Self::Dynamic); + } + // Otherwise, assume the requirement is up-to-date. Ok(Self::Satisfied) } } } } + +/// Returns `true` if the source tree at the given path contains dynamic metadata. +fn is_dynamic(path: &Path) -> bool { + // If there's no `pyproject.toml`, we assume it's dynamic. + let Ok(contents) = fs_err::read_to_string(path.join("pyproject.toml")) else { + return true; + }; + let Ok(pyproject_toml) = toml::from_str::(&contents) else { + return true; + }; + // If `[project]` is not present, we assume it's dynamic. + let Some(project) = pyproject_toml.project else { + // ...unless it appears to be a Poetry project. + return pyproject_toml + .tool + .map_or(true, |tool| tool.poetry.is_none()); + }; + // `[project.dynamic]` must be present and non-empty. + project.dynamic.is_some_and(|dynamic| !dynamic.is_empty()) +} + +/// A pyproject.toml as specified in PEP 517. +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +struct PyProjectToml { + project: Option, + tool: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +struct Project { + dynamic: Option>, +} + +#[derive(Deserialize, Debug)] +struct Tool { + poetry: Option, +} + +#[derive(Deserialize, Debug)] +struct ToolPoetry { + #[allow(dead_code)] + name: Option, +} diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index 8f2176919..8afbd299a 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -13,13 +13,10 @@ use distribution_types::{ }; use pep440_rs::{Version, VersionSpecifiers}; use pypi_types::VerbatimParsedUrl; -use requirements_txt::EditableRequirement; -use uv_cache::{ArchiveTarget, ArchiveTimestamp}; use uv_interpreter::PythonEnvironment; use uv_normalize::PackageName; use uv_types::InstalledPackagesProvider; -use crate::is_dynamic; use crate::satisfies::RequirementSatisfaction; /// An index over the packages installed in an environment. @@ -289,7 +286,6 @@ impl SitePackages { pub fn satisfies( &self, requirements: &[UnresolvedRequirementSpecification], - editables: &[EditableRequirement], constraints: &[Requirement], ) -> Result { let mut stack = Vec::with_capacity(requirements.len()); @@ -308,58 +304,6 @@ impl SitePackages { } } - // Verify that all editable requirements are met. - for requirement in editables { - let installed = self.get_editables(requirement.raw()); - match installed.as_slice() { - [] => { - // The package isn't installed. - return Ok(SatisfiesResult::Unsatisfied(requirement.to_string())); - } - [distribution] => { - // Is the editable out-of-date? - if !ArchiveTimestamp::up_to_date_with( - &requirement.path, - ArchiveTarget::Install(distribution), - )? { - return Ok(SatisfiesResult::Unsatisfied(requirement.to_string())); - } - - // Does the editable have dynamic metadata? - if is_dynamic(&requirement.path) { - return Ok(SatisfiesResult::Unsatisfied(requirement.to_string())); - } - - // Recurse into the dependencies. - let metadata = distribution - .metadata() - .with_context(|| format!("Failed to read metadata for: {distribution}"))?; - - // Add the dependencies to the queue. - for dependency in metadata.requires_dist { - if dependency.evaluate_markers( - self.venv.interpreter().markers(), - &requirement.extras, - ) { - let dependency = UnresolvedRequirementSpecification { - requirement: UnresolvedRequirement::Named(Requirement::from( - dependency, - )), - hashes: vec![], - }; - if seen.insert(dependency.clone()) { - stack.push(dependency); - } - } - } - } - _ => { - // There are multiple installed distributions for the same package. - return Ok(SatisfiesResult::Unsatisfied(requirement.to_string())); - } - } - } - // Verify that all non-editable requirements are met. while let Some(entry) = stack.pop() { let installed = match &entry.requirement { @@ -378,7 +322,9 @@ impl SitePackages { distribution, entry.requirement.source().as_ref(), )? { - RequirementSatisfaction::Mismatch | RequirementSatisfaction::OutOfDate => { + RequirementSatisfaction::Mismatch + | RequirementSatisfaction::OutOfDate + | RequirementSatisfaction::Dynamic => { return Ok(SatisfiesResult::Unsatisfied(entry.requirement.to_string())) } RequirementSatisfaction::Satisfied => {} @@ -387,7 +333,8 @@ impl SitePackages { for constraint in constraints { match RequirementSatisfaction::check(distribution, &constraint.source)? { RequirementSatisfaction::Mismatch - | RequirementSatisfaction::OutOfDate => { + | RequirementSatisfaction::OutOfDate + | RequirementSatisfaction::Dynamic => { return Ok(SatisfiesResult::Unsatisfied( entry.requirement.to_string(), )) diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index 3275ee107..623f578e4 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -16,7 +16,7 @@ distribution-types = { workspace = true } pep440_rs = { workspace = true } pep508_rs = { workspace = true } pypi-types = { workspace = true } -requirements-txt = { workspace = true, features = ["reqwest"] } +requirements-txt = { workspace = true, features = ["http"] } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-distribution = { workspace = true } diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index 4a97ea82d..b4c5d3547 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -14,7 +14,7 @@ use pep508_rs::MarkerEnvironment; use uv_configuration::{Constraints, Overrides}; use uv_distribution::{DistributionDatabase, Reporter}; use uv_git::GitUrl; -use uv_resolver::{BuiltEditableMetadata, InMemoryIndex, MetadataResponse}; +use uv_resolver::{InMemoryIndex, MetadataResponse}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; #[derive(Debug, Error)] @@ -50,8 +50,6 @@ pub struct LookaheadResolver<'a, Context: BuildContext> { constraints: &'a Constraints, /// The overrides for the project. overrides: &'a Overrides, - /// The editable requirements for the project. - editables: &'a [BuiltEditableMetadata], /// The required hashes for the project. hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. @@ -67,7 +65,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { requirements: &'a [Requirement], constraints: &'a Constraints, overrides: &'a Overrides, - editables: &'a [BuiltEditableMetadata], hasher: &'a HashStrategy, index: &'a InMemoryIndex, database: DistributionDatabase<'a, Context>, @@ -76,7 +73,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { requirements, constraints, overrides, - editables, hasher, index, database, @@ -111,13 +107,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { .constraints .apply(self.overrides.apply(self.requirements)) .filter(|requirement| requirement.evaluate_markers(markers, &[])) - .chain(self.editables.iter().flat_map(|editable| { - self.constraints - .apply(self.overrides.apply(&editable.requirements.dependencies)) - .filter(|requirement| { - requirement.evaluate_markers(markers, &editable.built.extras) - }) - })) .cloned() .collect(); @@ -184,9 +173,8 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { RequirementSource::Path { path, url, - // TODO(konsti): Figure out why we lose the editable here (does it matter?) - editable: _, - } => Dist::from_file_url(requirement.name, url, &path, false)?, + editable, + } => Dist::from_file_url(requirement.name, url, &path, editable)?, }; // Fetch the metadata for the distribution. diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index da181535b..5ee4e0a97 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -104,6 +104,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { let source = SourceUrl::Directory(DirectorySourceUrl { url: &url, path: Cow::Borrowed(source_tree), + editable: false, }); // Determine the hash policy. Since we don't have a package name, we perform a diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index cdd4af072..ec9381f0f 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -28,7 +28,6 @@ //! `source_trees`. use std::collections::VecDeque; -use std::iter; use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; @@ -66,8 +65,6 @@ pub struct RequirementsSpecification { pub constraints: Vec, /// The overrides for the project. pub overrides: Vec, - /// Package to install as editable installs - pub editables: Vec, /// The source trees from which to extract requirements. pub source_trees: Vec, /// The extras used to collect requirements. @@ -121,13 +118,18 @@ impl RequirementsSpecification { .requirements .into_iter() .map(UnresolvedRequirementSpecification::from) + .chain( + requirements_txt + .editables + .into_iter() + .map(UnresolvedRequirementSpecification::from), + ) .collect(), constraints: requirements_txt .constraints .into_iter() .map(Requirement::from) .collect(), - editables: requirements_txt.editables, index_url: requirements_txt.index_url.map(IndexUrl::from), extra_index_urls: requirements_txt .extra_index_urls @@ -191,6 +193,7 @@ impl RequirementsSpecification { .find(|member| is_same_file(member.root(), &requirement.path).unwrap_or(false)) .map(|member| (member.pyproject_toml(), workspace)) }); + let editable_spec = if let Some((pyproject_toml, workspace)) = project_in_exiting_workspace { Self::parse_direct_pyproject_toml( @@ -222,22 +225,23 @@ impl RequirementsSpecification { requirement.path.user_display() ); return Ok(Self { - editables: vec![requirement], + requirements: vec![UnresolvedRequirementSpecification::from(requirement)], ..Self::default() }); }; if let Some(editable_spec) = editable_spec { - // We only collect the editables here to keep the count of root packages - // correct. + // We only collect the editables here to keep the count of root packages correct. // TODO(konsti): Collect all workspace packages, even the non-editable ones. - let editables = editable_spec - .editables - .into_iter() - .chain(iter::once(requirement)) - .collect(); Ok(Self { - editables, + requirements: editable_spec + .requirements + .into_iter() + .chain(std::iter::once(UnresolvedRequirementSpecification::from( + requirement, + ))) + .filter(|entry| entry.requirement.is_editable()) + .collect(), ..Self::default() }) } else { @@ -246,7 +250,7 @@ impl RequirementsSpecification { requirement.path.user_display() ); Ok(Self { - editables: vec![requirement], + requirements: vec![UnresolvedRequirementSpecification::from(requirement)], ..Self::default() }) } @@ -361,14 +365,13 @@ impl RequirementsSpecification { preview: PreviewMode, project: Pep621Metadata, ) -> Result { - let mut seen_editables = FxHashSet::from_iter([project.name.clone()]); + let mut seen = FxHashSet::from_iter([project.name.clone()]); let mut queue = VecDeque::from([project.name.clone()]); - let mut editables = Vec::new(); let mut requirements = Vec::new(); let mut used_extras = FxHashSet::default(); while let Some(project_name) = queue.pop_front() { - let Some(current) = &workspace.packages().get(&project_name) else { + let Some(current) = workspace.packages().get(&project_name) else { continue; }; trace!("Processing metadata for workspace package {project_name}"); @@ -396,40 +399,30 @@ impl RequirementsSpecification { current.root().user_display() ) })?; - used_extras.extend(project.used_extras); - // Partition into editable and non-editable requirements. - for requirement in project.requirements { - if let RequirementSource::Path { - path, - editable: true, - url, - } = requirement.source - { - editables.push(EditableRequirement { - url, - path, - marker: requirement.marker, - extras: requirement.extras, - origin: requirement.origin, - }); - - if seen_editables.insert(requirement.name.clone()) { + // Recurse into any editables. + for requirement in &project.requirements { + if matches!( + requirement.source, + RequirementSource::Path { editable: true, .. } + ) { + if seen.insert(requirement.name.clone()) { queue.push_back(requirement.name.clone()); } - } else { - requirements.push(UnresolvedRequirementSpecification { - requirement: UnresolvedRequirement::Named(requirement), - hashes: vec![], - }); } } + + // Collect the requirements and extras. + used_extras.extend(project.used_extras); + requirements.extend(project.requirements); } let spec = Self { project: Some(project.name), - editables, - requirements, + requirements: requirements + .into_iter() + .map(UnresolvedRequirementSpecification::from) + .collect(), extras: used_extras, ..Self::default() }; @@ -459,7 +452,6 @@ impl RequirementsSpecification { spec.constraints.extend(source.constraints); spec.overrides.extend(source.overrides); spec.extras.extend(source.extras); - spec.editables.extend(source.editables); spec.source_trees.extend(source.source_trees); // Use the first project name discovered. diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index d07e3cb84..bf728a35c 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -224,6 +224,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { SourceUrl::Directory(DirectorySourceUrl { url: &requirement.url.verbatim, path: Cow::Borrowed(&parsed_path_url.path), + editable: parsed_path_url.editable, }) } // If it's not a directory, assume it's a file. diff --git a/crates/uv-resolver/src/editables.rs b/crates/uv-resolver/src/editables.rs deleted file mode 100644 index 00287b756..000000000 --- a/crates/uv-resolver/src/editables.rs +++ /dev/null @@ -1,44 +0,0 @@ -use rustc_hash::FxHashMap; - -use distribution_types::{LocalEditable, Requirements}; -use pypi_types::Metadata23; -use uv_normalize::PackageName; - -/// A built editable for which we know its dependencies and other static metadata. -#[derive(Debug, Clone)] -pub struct BuiltEditableMetadata { - pub built: LocalEditable, - pub metadata: Metadata23, - pub requirements: Requirements, -} - -/// A set of editable packages, indexed by package name. -#[derive(Debug, Default, Clone)] -pub(crate) struct Editables(FxHashMap); - -impl Editables { - /// Create a new set of editables from a set of requirements. - pub(crate) fn from_requirements(requirements: Vec) -> Self { - Self( - requirements - .into_iter() - .map(|editable| (editable.metadata.name.clone(), editable)) - .collect(), - ) - } - - /// Get the editable for a package. - pub(crate) fn get(&self, name: &PackageName) -> Option<&BuiltEditableMetadata> { - self.0.get(name) - } - - /// Returns `true` if the given package is editable. - pub(crate) fn contains(&self, name: &PackageName) -> bool { - self.0.contains_key(name) - } - - /// Iterate over all editables. - pub(crate) fn iter(&self) -> impl Iterator { - self.0.values() - } -} diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 8418cd2a0..7de3437e5 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -1,5 +1,4 @@ pub use dependency_mode::DependencyMode; -pub use editables::BuiltEditableMetadata; pub use error::ResolveError; pub use exclude_newer::ExcludeNewer; pub use exclusions::Exclusions; @@ -25,7 +24,6 @@ mod candidate_selector; mod dependency_mode; mod dependency_provider; -mod editables; mod error; mod exclude_newer; mod exclusions; diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index 9ca0da4e4..943f72b90 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -6,7 +6,6 @@ use uv_configuration::{Constraints, Overrides}; use uv_normalize::PackageName; use uv_types::RequestedRequirements; -use crate::editables::BuiltEditableMetadata; use crate::{preferences::Preference, DependencyMode, Exclusions}; /// A manifest of requirements, constraints, and preferences. @@ -31,12 +30,6 @@ pub struct Manifest { /// The name of the project. pub(crate) project: Option, - /// The editable requirements for the project, which are built in advance. - /// - /// The requirements of the editables should be included in resolution as if they were - /// direct requirements in their own right. - pub(crate) editables: Vec, - /// The installed packages to exclude from consideration during resolution. /// /// These typically represent packages that are being upgraded or reinstalled @@ -59,7 +52,6 @@ impl Manifest { overrides: Overrides, preferences: Vec, project: Option, - editables: Vec, exclusions: Exclusions, lookaheads: Vec, ) -> Self { @@ -69,7 +61,6 @@ impl Manifest { overrides, preferences, project, - editables, exclusions, lookaheads, } @@ -82,7 +73,6 @@ impl Manifest { overrides: Overrides::default(), preferences: Vec::new(), project: None, - editables: Vec::new(), exclusions: Exclusions::default(), lookaheads: Vec::new(), } @@ -113,13 +103,6 @@ impl Manifest { requirement.evaluate_markers(markers, lookahead.extras()) }) }) - .chain(self.editables.iter().flat_map(move |editable| { - self.overrides - .apply(&editable.requirements.dependencies) - .filter(move |requirement| { - requirement.evaluate_markers(markers, &editable.built.extras) - }) - })) .chain( self.overrides .apply(&self.requirements) @@ -175,13 +158,6 @@ impl Manifest { requirement.evaluate_markers(markers, lookahead.extras()) }) }) - .chain(self.editables.iter().flat_map(move |editable| { - self.overrides - .apply(&editable.requirements.dependencies) - .filter(move |requirement| { - requirement.evaluate_markers(markers, &editable.built.extras) - }) - })) .chain( self.overrides .apply(&self.requirements) @@ -213,6 +189,6 @@ impl Manifest { /// Returns the number of input requirements. pub fn num_requirements(&self) -> usize { - self.requirements.len() + self.editables.len() + self.requirements.len() } } diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index eda3c9908..49c6e3eae 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -49,11 +49,6 @@ impl PubGrubDependencies { Ok(Self(dependencies)) } - /// Add a [`PubGrubPackage`] and [`PubGrubVersion`] range into the dependencies. - pub(crate) fn push(&mut self, package: PubGrubPackage, version: Range) { - self.0.push((package, version)); - } - /// Iterate over the dependencies. pub(crate) fn iter(&self) -> impl Iterator)> { self.0.iter() diff --git a/crates/uv-resolver/src/pubgrub/mod.rs b/crates/uv-resolver/src/pubgrub/mod.rs index ff6c9ae83..828d94c0b 100644 --- a/crates/uv-resolver/src/pubgrub/mod.rs +++ b/crates/uv-resolver/src/pubgrub/mod.rs @@ -1,4 +1,4 @@ -pub(crate) use crate::pubgrub::dependencies::{PubGrubDependencies, PubGrubRequirement}; +pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies; pub(crate) use crate::pubgrub::distribution::PubGrubDistribution; pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority}; diff --git a/crates/uv-resolver/src/resolution/display.rs b/crates/uv-resolver/src/resolution/display.rs index e2e6a72a4..dbda99f50 100644 --- a/crates/uv-resolver/src/resolution/display.rs +++ b/crates/uv-resolver/src/resolution/display.rs @@ -1,15 +1,12 @@ -use std::borrow::Cow; use std::collections::BTreeSet; use owo_colors::OwoColorize; use petgraph::visit::EdgeRef; use petgraph::Direction; -use distribution_types::{IndexUrl, LocalEditable, Name, SourceAnnotations, Verbatim}; -use pypi_types::HashDigest; +use distribution_types::{Name, SourceAnnotations}; use uv_normalize::PackageName; -use crate::resolution::AnnotatedDist; use crate::ResolutionGraph; /// A [`std::fmt::Display`] implementation for the resolution graph. @@ -77,48 +74,6 @@ impl<'a> DisplayResolutionGraph<'a> { } } -#[derive(Debug)] -enum Node<'a> { - /// A node linked to an editable distribution. - Editable(&'a LocalEditable), - /// A node linked to a non-editable distribution. - Distribution(&'a AnnotatedDist), -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -enum NodeKey<'a> { - /// A node linked to an editable distribution, sorted by verbatim representation. - Editable(Cow<'a, str>), - /// A node linked to a non-editable distribution, sorted by package name. - Distribution(&'a PackageName), -} - -impl<'a> Node<'a> { - /// Return a comparable key for the node. - fn key(&self) -> NodeKey<'a> { - match self { - Node::Editable(editable) => NodeKey::Editable(editable.verbatim()), - Node::Distribution(annotated) => NodeKey::Distribution(annotated.name()), - } - } - - /// Return the [`IndexUrl`] of the distribution, if any. - fn index(&self) -> Option<&IndexUrl> { - match self { - Node::Editable(_) => None, - Node::Distribution(annotated) => annotated.dist.index(), - } - } - - /// Return the hashes of the distribution. - fn hashes(&self) -> &[HashDigest] { - match self { - Node::Editable(_) => &[], - Node::Distribution(annotated) => &annotated.hashes, - } - } -} - /// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses. impl std::fmt::Display for DisplayResolutionGraph<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -134,32 +89,22 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { return None; } - let node = if let Some(editable) = self.resolution.editables.get(name) { - Node::Editable(&editable.built) - } else { - Node::Distribution(dist) - }; - Some((index, node)) + Some((index, dist)) }) .collect::>(); // Sort the nodes by name, but with editable packages first. - nodes.sort_unstable_by_key(|(index, node)| (node.key(), *index)); + nodes.sort_unstable_by_key(|(index, node)| (node.to_comparator(), *index)); // Print out the dependency graph. for (index, node) in nodes { // Display the node itself. - let mut line = match node { - Node::Editable(editable) => format!("-e {}", editable.verbatim()), - Node::Distribution(dist) => { - dist.to_requirements_txt(self.include_extras).to_string() - } - }; + let mut line = node.to_requirements_txt(self.include_extras).to_string(); // Display the distribution hashes, if any. let mut has_hashes = false; if self.show_hashes { - for hash in node.hashes() { + for hash in &node.hashes { has_hashes = true; line.push_str(" \\\n"); line.push_str(" --hash="); @@ -184,12 +129,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { // Include all external sources (e.g., requirements files). let default = BTreeSet::default(); - let source = match node { - Node::Editable(editable) => { - self.sources.get_editable(&editable.url).unwrap_or(&default) - } - Node::Distribution(dist) => self.sources.get(dist.name()).unwrap_or(&default), - }; + let source = self.sources.get(node.name()).unwrap_or(&default); match self.annotation_style { AnnotationStyle::Line => match edges.as_slice() { @@ -261,7 +201,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { // If enabled, include indexes to indicate which index was used for each package (e.g., // `# from https://pypi.org/simple`). if self.include_index_annotation { - if let Some(index) = node.index() { + if let Some(index) = node.dist.index() { let url = index.redacted(); writeln!(f, "{}", format!(" # from {url}").green())?; } diff --git a/crates/uv-resolver/src/resolution/graph.rs b/crates/uv-resolver/src/resolution/graph.rs index 1df1fbe63..7041bc8cd 100644 --- a/crates/uv-resolver/src/resolution/graph.rs +++ b/crates/uv-resolver/src/resolution/graph.rs @@ -16,7 +16,6 @@ use pypi_types::{ParsedUrlError, Yanked}; use uv_normalize::PackageName; use crate::dependency_provider::UvDependencyProvider; -use crate::editables::Editables; use crate::pins::FilePins; use crate::preferences::Preferences; use crate::pubgrub::{PubGrubDistribution, PubGrubPackageInner}; @@ -34,8 +33,6 @@ use crate::{ pub struct ResolutionGraph { /// The underlying graph. pub(crate) petgraph: petgraph::graph::Graph, petgraph::Directed>, - /// The set of editable requirements in this resolution. - pub(crate) editables: Editables, /// Any diagnostics that were encountered while building the graph. pub(crate) diagnostics: Vec, } @@ -50,7 +47,6 @@ impl ResolutionGraph { distributions: &FxOnceMap>, state: &State, preferences: &Preferences, - editables: Editables, ) -> anyhow::Result { // Collect and validate the extras. let mut extras = FxHashMap::default(); @@ -102,50 +98,34 @@ impl ResolutionGraph { marker: None, url: Some(url), } => { - if let Some(editable) = editables.get(name) { - if editable.metadata.provides_extras.contains(extra) { - extras - .entry(name.clone()) - .or_insert_with(Vec::new) - .push(extra.clone()); - } else { - let dist = Dist::from_editable(name.clone(), editable.built.clone())?; + let dist = PubGrubDistribution::from_url(name, url); - diagnostics.push(ResolutionDiagnostic::MissingExtra { - dist: dist.into(), - extra: extra.clone(), - }); - } + let response = distributions.get(&dist.version_id()).unwrap_or_else(|| { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }); + + let MetadataResponse::Found(archive) = &*response else { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }; + + if archive.metadata.provides_extras.contains(extra) { + extras + .entry(name.clone()) + .or_insert_with(Vec::new) + .push(extra.clone()); } else { - let dist = PubGrubDistribution::from_url(name, url); + let dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?; - let response = distributions.get(&dist.version_id()).unwrap_or_else(|| { - panic!( - "Every package should have metadata: {:?}", - dist.version_id() - ) + diagnostics.push(ResolutionDiagnostic::MissingExtra { + dist: dist.into(), + extra: extra.clone(), }); - - let MetadataResponse::Found(archive) = &*response else { - panic!( - "Every package should have metadata: {:?}", - dist.version_id() - ) - }; - - if archive.metadata.provides_extras.contains(extra) { - extras - .entry(name.clone()) - .or_insert_with(Vec::new) - .push(extra.clone()); - } else { - let dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?; - - diagnostics.push(ResolutionDiagnostic::MissingExtra { - dist: dist.into(), - extra: extra.clone(), - }); - } } } _ => {} @@ -254,75 +234,60 @@ impl ResolutionGraph { url: Some(url), } => { // Create the distribution. - if let Some(editable) = editables.get(name) { - let dist = Dist::from_editable(name.clone(), editable.built.clone())?; - // Add the distribution to the graph. - let index = petgraph.add_node(AnnotatedDist { - dist: dist.into(), - extras: editable.built.extras.clone(), - hashes: vec![], - metadata: editable.metadata.clone(), - }); - inverse.insert(name, index); - } else { - let dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?; + let dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?; - // Extract the hashes, preserving those that were already present in the - // lockfile if necessary. - let hashes = if let Some(digests) = preferences - .match_hashes(name, version) - .filter(|digests| !digests.is_empty()) - { - digests.to_vec() - } else if let Some(metadata_response) = - distributions.get(&dist.version_id()) - { - if let MetadataResponse::Found(ref archive) = *metadata_response { - let mut digests = archive.hashes.clone(); - digests.sort_unstable(); - digests - } else { - vec![] - } + // Extract the hashes, preserving those that were already present in the + // lockfile if necessary. + let hashes = if let Some(digests) = preferences + .match_hashes(name, version) + .filter(|digests| !digests.is_empty()) + { + digests.to_vec() + } else if let Some(metadata_response) = distributions.get(&dist.version_id()) { + if let MetadataResponse::Found(ref archive) = *metadata_response { + let mut digests = archive.hashes.clone(); + digests.sort_unstable(); + digests } else { vec![] - }; - - // Extract the metadata. - let metadata = { - let dist = PubGrubDistribution::from_url(name, url); - - let response = - distributions.get(&dist.version_id()).unwrap_or_else(|| { - panic!( - "Every package should have metadata: {:?}", - dist.version_id() - ) - }); - - let MetadataResponse::Found(archive) = &*response else { - panic!( - "Every package should have metadata: {:?}", - dist.version_id() - ) - }; - - archive.metadata.clone() - }; - - // Extract the extras. - let extras = extras.get(name).cloned().unwrap_or_default(); - - // Add the distribution to the graph. - let index = petgraph.add_node(AnnotatedDist { - dist: dist.into(), - extras, - hashes, - metadata, - }); - inverse.insert(name, index); + } + } else { + vec![] }; + + // Extract the metadata. + let metadata = { + let dist = PubGrubDistribution::from_url(name, url); + + let response = distributions.get(&dist.version_id()).unwrap_or_else(|| { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }); + + let MetadataResponse::Found(archive) = &*response else { + panic!( + "Every package should have metadata: {:?}", + dist.version_id() + ) + }; + + archive.metadata.clone() + }; + + // Extract the extras. + let extras = extras.get(name).cloned().unwrap_or_default(); + + // Add the distribution to the graph. + let index = petgraph.add_node(AnnotatedDist { + dist: dist.into(), + extras, + hashes, + metadata, + }); + inverse.insert(name, index); } _ => {} }; @@ -380,7 +345,6 @@ impl ResolutionGraph { Ok(Self { petgraph, - editables, diagnostics, }) } @@ -523,13 +487,7 @@ impl ResolutionGraph { } // Ensure that we consider markers from direct dependencies. - let direct_reqs = manifest.requirements.iter().chain( - manifest - .editables - .iter() - .flat_map(|editable| &editable.requirements.dependencies), - ); - for direct_req in manifest.apply(direct_reqs) { + for direct_req in manifest.apply(manifest.requirements.iter()) { let Some(ref marker_tree) = direct_req.marker else { continue; }; diff --git a/crates/uv-resolver/src/resolution/mod.rs b/crates/uv-resolver/src/resolution/mod.rs index 1c18461ed..19ae7f976 100644 --- a/crates/uv-resolver/src/resolution/mod.rs +++ b/crates/uv-resolver/src/resolution/mod.rs @@ -34,6 +34,14 @@ impl AnnotatedDist { /// unnamed requirement for relative paths, which can't be represented with PEP 508 (but are /// supported in `requirements.txt`). pub(crate) fn to_requirements_txt(&self, include_extras: bool) -> Cow { + // If the URL is editable, write it as an editable requirement. + if self.dist.is_editable() { + if let VersionOrUrlRef::Url(url) = self.dist.version_or_url() { + let given = url.verbatim(); + return Cow::Owned(format!("-e {given}")); + } + } + // If the URL is not _definitively_ an absolute `file://` URL, write it as a relative path. if self.dist.is_local() { if let VersionOrUrlRef::Url(url) = self.dist.version_or_url() { @@ -94,6 +102,22 @@ impl AnnotatedDist { )) } } + + pub(crate) fn to_comparator(&self) -> RequirementsTxtComparator { + if self.dist.is_editable() { + if let VersionOrUrlRef::Url(url) = self.dist.version_or_url() { + return RequirementsTxtComparator::Url(url.verbatim()); + } + } + + RequirementsTxtComparator::Name(self.name()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum RequirementsTxtComparator<'a> { + Url(Cow<'a, str>), + Name(&'a PackageName), } impl Name for AnnotatedDist { diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index d4b833a82..a921a79ef 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -37,14 +37,13 @@ use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider}; use crate::candidate_selector::{CandidateDist, CandidateSelector}; use crate::dependency_provider::UvDependencyProvider; -use crate::editables::Editables; use crate::error::ResolveError; use crate::manifest::Manifest; use crate::pins::FilePins; use crate::preferences::Preferences; use crate::pubgrub::{ PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, - PubGrubPriorities, PubGrubPython, PubGrubRequirement, PubGrubSpecifier, + PubGrubPriorities, PubGrubPython, PubGrubSpecifier, }; use crate::python_requirement::PythonRequirement; use crate::resolution::ResolutionGraph; @@ -85,7 +84,6 @@ struct ResolverState { overrides: Overrides, preferences: Preferences, exclusions: Exclusions, - editables: Editables, urls: Urls, locals: Locals, dependency_mode: DependencyMode, @@ -192,7 +190,6 @@ impl overrides: manifest.overrides, preferences: Preferences::from_iter(manifest.preferences, markers), exclusions: manifest.exclusions, - editables: Editables::from_requirements(manifest.editables), hasher: hasher.clone(), markers: markers.cloned(), python_requirement: python_requirement.clone(), @@ -343,7 +340,6 @@ impl ResolverState ResolverState ResolverState ResolverState dependencies, Err(err) => { return Ok(Dependencies::Unavailable( @@ -872,54 +838,6 @@ impl ResolverState ResolverState PubGrubDistribution::from_url(name, url), - None => PubGrubDistribution::from_registry(name, version), - }; - let version_id = dist.version_id(); - // Wait for the metadata to be available. - self.index - .distributions() - .wait_blocking(&version_id) - .ok_or(ResolveError::Unregistered)?; - } + let dist = match url { + Some(url) => PubGrubDistribution::from_url(name, url), + None => PubGrubDistribution::from_registry(name, version), + }; + let version_id = dist.version_id(); + + // Wait for the metadata to be available. + self.index + .distributions() + .wait_blocking(&version_id) + .ok_or(ResolveError::Unregistered)?; return Ok(Dependencies::Available(Vec::default())); } - // Determine if the distribution is editable. - if let Some(editable) = self.editables.get(name) { - let requirements: Vec<_> = editable - .metadata - .requires_dist - .iter() - .cloned() - .map(Requirement::from) - .collect(); - let dependencies = PubGrubDependencies::from_requirements( - &requirements, - &self.constraints, - &self.overrides, - Some(name), - extra.as_ref(), - &self.urls, - &self.locals, - self.markers.as_ref(), - )?; - - for (dep_package, dep_version) in dependencies.iter() { - debug!("Adding transitive dependency for {package}=={version}: {dep_package}{dep_version}"); - - // Update the package priorities. - priorities.insert(dep_package, dep_version); - - // Emit a request to fetch the metadata for this package. - self.visit_package(dep_package, request_sink)?; - } - - return Ok(Dependencies::Available(dependencies.into())); - } - // Determine the distribution to lookup. let dist = match url { Some(url) => PubGrubDistribution::from_url(name, url), @@ -1232,6 +1115,7 @@ impl ResolverState ResolverState { diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 1a7e6d66b..c99a6af7b 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -22,36 +22,6 @@ impl Urls { ) -> Result { let mut urls: FxHashMap = FxHashMap::default(); - // Add the editables themselves to the list of required URLs. - for editable in &manifest.editables { - let editable_url = VerbatimParsedUrl { - parsed_url: ParsedUrl::Path(ParsedPathUrl { - url: editable.built.url.to_url(), - path: editable.built.path.clone(), - editable: true, - }), - verbatim: editable.built.url.clone(), - }; - if let Some(previous) = - urls.insert(editable.metadata.name.clone(), editable_url.clone()) - { - if !is_equal(&previous.verbatim, &editable_url.verbatim) { - if is_same_reference(&previous.verbatim, &editable_url.verbatim) { - debug!( - "Allowing {} as a variant of {}", - editable_url.verbatim, previous.verbatim - ); - } else { - return Err(ResolveError::ConflictingUrlsDirect( - editable.metadata.name.clone(), - previous.verbatim.verbatim().to_string(), - editable_url.verbatim.verbatim().to_string(), - )); - } - } - } - } - // Add all direct requirements and constraints. If there are any conflicts, return an error. for requirement in manifest.requirements(markers, dependencies) { match &requirement.source { diff --git a/crates/uv-resolver/tests/resolver.rs b/crates/uv-resolver/tests/resolver.rs index 048f065df..6d98c8f8c 100644 --- a/crates/uv-resolver/tests/resolver.rs +++ b/crates/uv-resolver/tests/resolver.rs @@ -300,7 +300,6 @@ async fn black_mypy_extensions() -> Result<()> { Overrides::default(), vec![], None, - vec![], Exclusions::default(), vec![], ); @@ -341,7 +340,6 @@ async fn black_mypy_extensions_extra() -> Result<()> { Overrides::default(), vec![], None, - vec![], Exclusions::default(), vec![], ); @@ -382,7 +380,6 @@ async fn black_flake8() -> Result<()> { Overrides::default(), vec![], None, - vec![], Exclusions::default(), vec![], ); @@ -480,7 +477,6 @@ async fn black_respect_preference() -> Result<()> { Version::from_str("23.9.0")?, )], None, - vec![], Exclusions::default(), vec![], ); @@ -521,7 +517,6 @@ async fn black_ignore_preference() -> Result<()> { Version::from_str("23.9.2")?, )], None, - vec![], Exclusions::default(), vec![], ); diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 727f62816..592a1d7aa 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -19,7 +19,6 @@ install-wheel-rs = { workspace = true, features = ["clap"], default-features = f pep508_rs = { workspace = true } platform-tags = { workspace = true } pypi-types = { workspace = true } -requirements-txt = { workspace = true, features = ["http"] } uv-auth = { workspace = true } uv-cache = { workspace = true, features = ["clap"] } uv-client = { workspace = true } @@ -45,7 +44,6 @@ clap = { workspace = true, features = ["derive", "string", "wrap_help"] } clap_complete_command = { workspace = true } flate2 = { workspace = true, default-features = false } fs-err = { workspace = true, features = ["tokio"] } -indexmap = { workspace = true } indicatif = { workspace = true } itertools = { workspace = true } miette = { workspace = true, features = ["fancy"] } diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 6625b8009..fc7b90daf 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -7,21 +7,15 @@ use std::path::Path; use std::str::FromStr; use anstream::{eprint, AutoStream, StripStream}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use fs_err as fs; -use indexmap::IndexMap; use itertools::Itertools; use owo_colors::OwoColorize; -use tempfile::tempdir_in; use tracing::debug; -use distribution_types::{ - IndexLocations, LocalEditable, LocalEditables, SourceAnnotation, SourceAnnotations, Verbatim, -}; -use distribution_types::{Requirement, Requirements}; +use distribution_types::{IndexLocations, SourceAnnotation, SourceAnnotations, Verbatim}; use install_wheel_rs::linker::LinkMode; use platform_tags::Tags; -use requirements_txt::EditableRequirement; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; @@ -33,7 +27,6 @@ use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_fs::Simplified; -use uv_installer::Downloader; use uv_interpreter::{ find_best_interpreter, find_interpreter, InterpreterRequest, PythonEnvironment, SystemPython, VersionRequest, @@ -45,15 +38,15 @@ use uv_requirements::{ RequirementsSource, RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ - AnnotationStyle, BuiltEditableMetadata, DependencyMode, DisplayResolutionGraph, ExcludeNewer, - Exclusions, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode, - PythonRequirement, ResolutionMode, Resolver, + AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, + InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, + Resolver, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; use crate::commands::pip::operations; -use crate::commands::reporters::{DownloadReporter, ResolverReporter}; +use crate::commands::reporters::ResolverReporter; use crate::commands::{elapsed, ExitStatus}; use crate::printer::Printer; @@ -123,7 +116,6 @@ pub(crate) async fn pip_compile( requirements, constraints, overrides, - editables, source_trees, extras: used_extras, index_url, @@ -420,98 +412,10 @@ pub(crate) async fn pip_compile( } } - for editable in &editables { - if let Some(origin) = &editable.origin { - sources.add_editable( - editable.url(), - SourceAnnotation::Requirement(origin.clone()), - ); - } - } - // Collect constraints and overrides. let constraints = Constraints::from_requirements(constraints); let overrides = Overrides::from_requirements(overrides); - // Build the editables and add their requirements - let editables = if editables.is_empty() { - Vec::new() - } else { - let start = std::time::Instant::now(); - - let editables = LocalEditables::from_editables(editables.into_iter().map(|editable| { - let EditableRequirement { - url, - extras, - path, - marker: _, - origin: _, - } = editable; - LocalEditable { url, path, extras } - })); - - let downloader = Downloader::new( - &cache, - &tags, - &hasher, - DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), - ) - .with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64)); - - // Build all editables. - let editable_wheel_dir = tempdir_in(cache.root())?; - let editables: Vec = downloader - .build_editables(editables, editable_wheel_dir.path()) - .await - .context("Failed to build editables")? - .into_iter() - .map(|built_editable| { - let requirements = Requirements { - dependencies: built_editable - .metadata - .requires_dist - .iter() - .cloned() - .map(Requirement::from) - .collect(), - optional_dependencies: IndexMap::default(), - }; - BuiltEditableMetadata { - built: built_editable.editable, - metadata: built_editable.metadata, - requirements, - } - }) - .collect(); - - // Validate that the editables are compatible with the target Python version. - for editable in &editables { - if let Some(python_requires) = editable.metadata.requires_python.as_ref() { - if !python_requires.contains(python_requirement.target()) { - return Err(anyhow!( - "Editable `{}` requires Python {}, but resolution targets Python {}", - editable.metadata.name, - python_requires, - python_requirement.target() - )); - } - } - } - - let s = if editables.len() == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Built {} in {}", - format!("{} editable{}", editables.len(), s).bold(), - elapsed(start.elapsed()) - ) - .dimmed() - )?; - editables - }; - // Determine any lookahead requirements. let lookaheads = match dependency_mode { DependencyMode::Transitive => { @@ -519,7 +423,6 @@ pub(crate) async fn pip_compile( &requirements, &constraints, &overrides, - &editables, &hasher, &top_level_index, DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), @@ -538,7 +441,6 @@ pub(crate) async fn pip_compile( overrides, preferences, project, - editables, // Do not consider any installed packages during resolution. Exclusions::All, lookaheads, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 506cea34c..0ab5ad3b2 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -33,7 +33,6 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight}; use crate::commands::pip::operations; use crate::commands::pip::operations::Modifications; use crate::commands::{elapsed, ExitStatus}; -use crate::editables::ResolvedEditables; use crate::printer::Printer; /// Install packages into the current environment. @@ -89,7 +88,6 @@ pub(crate) async fn pip_install( requirements, constraints, overrides, - editables, source_trees, index_url, extra_index_urls, @@ -169,7 +167,7 @@ pub(crate) async fn pip_install( && overrides.is_empty() && uv_lock.is_none() { - match site_packages.satisfies(&requirements, &editables, &constraints)? { + match site_packages.satisfies(&requirements, &constraints)? { // If the requirements are already satisfied, we're done. SatisfiesResult::Fresh { recursive_requirements, @@ -183,13 +181,7 @@ pub(crate) async fn pip_install( debug!("Requirement satisfied: {requirement}"); } } - if !editables.is_empty() { - debug!( - "All editables satisfied: {}", - editables.iter().map(ToString::to_string).join(" | ") - ); - } - let num_requirements = requirements.len() + editables.len(); + let num_requirements = requirements.len(); let s = if num_requirements == 1 { "" } else { "s" }; writeln!( printer.stderr(), @@ -326,25 +318,6 @@ pub(crate) async fn pip_install( ) .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()); - // Build all editable distributions. The editables are shared between resolution and - // installation, and should live for the duration of the command. - let editables = ResolvedEditables::resolve( - editables - .into_iter() - .map(ResolvedEditables::from_requirement), - &site_packages, - &reinstall, - &hasher, - venv.interpreter(), - &tags, - &cache, - &client, - &resolve_dispatch, - concurrency, - printer, - ) - .await?; - // Resolve the requirements. let resolution = if let Some(ref root) = uv_lock { let root = PackageName::new(root.to_string())?; @@ -367,7 +340,6 @@ pub(crate) async fn pip_install( source_trees, project, extras, - &editables, site_packages.clone(), &hasher, &reinstall, @@ -426,7 +398,6 @@ pub(crate) async fn pip_install( // Sync the environment. operations::install( &resolution, - &editables, site_packages, Modifications::Sufficient, &reinstall, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 78485e70f..108bf7528 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -26,7 +26,7 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_fs::Simplified; -use uv_installer::{Downloader, Plan, Planner, ResolvedEditable, SitePackages}; +use uv_installer::{Downloader, Plan, Planner, SitePackages}; use uv_interpreter::{Interpreter, PythonEnvironment}; use uv_normalize::PackageName; use uv_requirements::{ @@ -43,7 +43,6 @@ use uv_warnings::warn_user; use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter}; use crate::commands::DryRunEvent; use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind}; -use crate::editables::ResolvedEditables; use crate::printer::Printer; /// Consolidate the requirements for an installation. @@ -110,7 +109,6 @@ pub(crate) async fn resolve( source_trees: Vec, project: Option, extras: &ExtrasSpecification, - editables: &ResolvedEditables, installed_packages: InstalledPackages, hasher: &HashStrategy, reinstall: &Reinstall, @@ -176,9 +174,6 @@ pub(crate) async fn resolve( let overrides = Overrides::from_requirements(overrides); let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers); - // Map the editables to their metadata. - let editables = editables.as_metadata(); - // Determine any lookahead requirements. let lookaheads = match options.dependency_mode { DependencyMode::Transitive => { @@ -186,7 +181,6 @@ pub(crate) async fn resolve( &requirements, &constraints, &overrides, - &editables, hasher, index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), @@ -215,7 +209,6 @@ pub(crate) async fn resolve( overrides, preferences, project, - editables, exclusions, lookaheads, ); @@ -282,7 +275,6 @@ pub(crate) enum Modifications { #[allow(clippy::too_many_arguments)] pub(crate) async fn install( resolution: &Resolution, - editables: &[ResolvedEditable], site_packages: SitePackages, modifications: Modifications, reinstall: &Reinstall, @@ -303,26 +295,12 @@ pub(crate) async fn install( ) -> Result<(), Error> { let start = std::time::Instant::now(); - // Extract the requirements from the resolution, filtering out any editables that were already - // required. If a package is already installed as editable, it may appear in the resolution - // despite not being explicitly requested. - let requirements = resolution - .requirements() - .filter(|requirement| { - if requirement.source.is_editable() { - !editables - .iter() - .any(|editable| requirement.name == *editable.name()) - } else { - true - } - }) - .collect::>(); + // Extract the requirements from the resolution. + let requirements = resolution.requirements().collect::>(); // Partition into those that should be linked from the cache (`local`), those that need to be // downloaded (`remote`), and those that should be removed (`extraneous`). - let plan = Planner::with_requirements(&requirements) - .with_editable_requirements(editables) + let plan = Planner::new(&requirements) .build( site_packages, reinstall, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 30ca0014c..b5785b497 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -31,7 +31,6 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight}; use crate::commands::pip::operations; use crate::commands::pip::operations::Modifications; use crate::commands::ExitStatus; -use crate::editables::ResolvedEditables; use crate::printer::Printer; /// Install a set of locked requirements into the current Python environment. @@ -86,7 +85,6 @@ pub(crate) async fn pip_sync( requirements, constraints, overrides, - editables, source_trees, index_url, extra_index_urls, @@ -107,7 +105,7 @@ pub(crate) async fn pip_sync( .await?; // Validate that the requirements are non-empty. - let num_requirements = requirements.len() + source_trees.len() + editables.len(); + let num_requirements = requirements.len() + source_trees.len(); if num_requirements == 0 { writeln!(printer.stderr(), "No requirements found")?; return Ok(ExitStatus::Success); @@ -277,25 +275,6 @@ pub(crate) async fn pip_sync( // Determine the set of installed packages. let site_packages = SitePackages::from_executable(&venv)?; - // Build all editable distributions. The editables are shared between resolution and - // installation, and should live for the duration of the command. - let editables = ResolvedEditables::resolve( - editables - .into_iter() - .map(ResolvedEditables::from_requirement), - &site_packages, - reinstall, - &hasher, - venv.interpreter(), - &tags, - &cache, - &client, - &resolve_dispatch, - concurrency, - printer, - ) - .await?; - let options = OptionsBuilder::new() .resolution_mode(resolution_mode) .prerelease_mode(prerelease_mode) @@ -311,7 +290,6 @@ pub(crate) async fn pip_sync( source_trees, project, &extras, - &editables, site_packages.clone(), &hasher, reinstall, @@ -369,7 +347,6 @@ pub(crate) async fn pip_sync( // Sync the environment. operations::install( &resolution, - &editables, site_packages, Modifications::Exact, reinstall, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 13d78c09b..1d1bb3266 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -16,7 +16,6 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; use crate::commands::{pip, project, ExitStatus}; -use crate::editables::ResolvedEditables; use crate::printer::Printer; /// Resolve the project requirements into a lockfile. @@ -104,26 +103,6 @@ pub(crate) async fn lock( concurrency, ); - // Build all editable distributions. The editables are shared between resolution and - // installation, and should live for the duration of the command. - let editables = ResolvedEditables::resolve( - spec.editables - .iter() - .cloned() - .map(ResolvedEditables::from_requirement), - &EmptyInstalledPackages, - &reinstall, - &hasher, - &interpreter, - tags, - cache, - &client, - &build_dispatch, - concurrency, - printer, - ) - .await?; - // Resolve the requirements. let resolution = pip::operations::resolve( spec.requirements, @@ -132,7 +111,6 @@ pub(crate) async fn lock( spec.source_trees, spec.project, &extras, - &editables, EmptyInstalledPackages, &hasher, &reinstall, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 8d0926a0f..9da5583c0 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -24,7 +24,6 @@ use uv_requirements::{ use uv_resolver::{FlatIndex, InMemoryIndex, Options}; use uv_types::{BuildIsolation, HashStrategy, InFlight}; -use crate::editables::ResolvedEditables; use crate::printer::Printer; pub(crate) mod lock; @@ -118,7 +117,7 @@ pub(crate) async fn update_environment( // Check if the current environment satisfies the requirements let site_packages = SitePackages::from_executable(&venv)?; if spec.source_trees.is_empty() { - match site_packages.satisfies(&spec.requirements, &spec.editables, &spec.constraints)? { + match site_packages.satisfies(&spec.requirements, &spec.constraints)? { // If the requirements are already satisfied, we're done. SatisfiesResult::Fresh { recursive_requirements, @@ -131,12 +130,6 @@ pub(crate) async fn update_environment( .sorted() .join(" | ") ); - if !spec.editables.is_empty() { - debug!( - "All editables satisfied: {}", - spec.editables.iter().map(ToString::to_string).join(", ") - ); - } return Ok(venv); } SatisfiesResult::Unsatisfied(requirement) => { @@ -196,26 +189,6 @@ pub(crate) async fn update_environment( concurrency, ); - // Build all editable distributions. The editables are shared between resolution and - // installation, and should live for the duration of the command. - let editables = ResolvedEditables::resolve( - spec.editables - .iter() - .cloned() - .map(ResolvedEditables::from_requirement), - &site_packages, - &reinstall, - &hasher, - &interpreter, - tags, - cache, - &client, - &resolve_dispatch, - concurrency, - printer, - ) - .await?; - // Resolve the requirements. let resolution = match pip::operations::resolve( spec.requirements, @@ -224,7 +197,6 @@ pub(crate) async fn update_environment( spec.source_trees, spec.project, &extras, - &editables, site_packages.clone(), &hasher, &reinstall, @@ -275,7 +247,6 @@ pub(crate) async fn update_environment( // Sync the environment. pip::operations::install( &resolution, - &editables, site_packages, pip::operations::Modifications::Sufficient, &reinstall, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index b5ee160f6..b2bfbcbb8 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -16,7 +16,6 @@ use uv_warnings::warn_user; use crate::commands::pip::operations::Modifications; use crate::commands::{pip, project, ExitStatus}; -use crate::editables::ResolvedEditables; use crate::printer::Printer; /// Sync the project environment. @@ -90,28 +89,9 @@ pub(crate) async fn sync( let site_packages = SitePackages::from_executable(&venv)?; - // Build any editables. - let editables = ResolvedEditables::resolve( - resolution.editables(), - &site_packages, - &reinstall, - &hasher, - venv.interpreter(), - tags, - cache, - &client, - &build_dispatch, - concurrency, - printer, - ) - .await?; - - let site_packages = SitePackages::from_executable(&venv)?; - // Sync the environment. pip::operations::install( &resolution, - &editables, site_packages, Modifications::Sufficient, &reinstall, diff --git a/crates/uv/src/commands/reporters.rs b/crates/uv/src/commands/reporters.rs index cb0bb95ba..fd6dcf4f7 100644 --- a/crates/uv/src/commands/reporters.rs +++ b/crates/uv/src/commands/reporters.rs @@ -7,8 +7,7 @@ use rustc_hash::FxHashMap; use url::Url; use distribution_types::{ - BuildableSource, CachedDist, DistributionMetadata, LocalEditable, Name, SourceDist, - VersionOrUrlRef, + BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef, }; use uv_normalize::PackageName; @@ -43,7 +42,7 @@ impl BarState { } impl ProgressReporter { - fn on_any_build_start(&self, color_string: &str) -> usize { + fn on_build_start(&self, source: &BuildableSource) -> usize { let mut state = self.state.lock().unwrap(); let id = state.id(); @@ -53,21 +52,29 @@ impl ProgressReporter { ); progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap()); - progress.set_message(format!("{} {}", "Building".bold().cyan(), color_string)); + progress.set_message(format!( + "{} {}", + "Building".bold().cyan(), + source.to_color_string() + )); state.headers += 1; state.bars.insert(id, progress); id } - fn on_any_build_complete(&self, color_string: &str, id: usize) { + fn on_build_complete(&self, source: &BuildableSource, id: usize) { let progress = { let mut state = self.state.lock().unwrap(); state.headers -= 1; state.bars.remove(&id).unwrap() }; - progress.finish_with_message(format!(" {} {}", "Built".bold().green(), color_string)); + progress.finish_with_message(format!( + " {} {}", + "Built".bold().green(), + source.to_color_string() + )); } fn on_download_start(&self, name: &PackageName, size: Option) -> usize { @@ -198,21 +205,11 @@ impl uv_installer::DownloadReporter for DownloadReporter { } fn on_build_start(&self, source: &BuildableSource) -> usize { - self.reporter.on_any_build_start(&source.to_color_string()) + self.reporter.on_build_start(source) } fn on_build_complete(&self, source: &BuildableSource, id: usize) { - self.reporter - .on_any_build_complete(&source.to_color_string(), id); - } - - fn on_editable_build_start(&self, dist: &LocalEditable) -> usize { - self.reporter.on_any_build_start(&dist.to_color_string()) - } - - fn on_editable_build_complete(&self, dist: &LocalEditable, id: usize) { - self.reporter - .on_any_build_complete(&dist.to_color_string(), id); + self.reporter.on_build_complete(source, id); } fn on_download_start(&self, name: &PackageName, size: Option) -> usize { @@ -290,12 +287,11 @@ impl uv_resolver::ResolverReporter for ResolverReporter { } fn on_build_start(&self, source: &BuildableSource) -> usize { - self.reporter.on_any_build_start(&source.to_color_string()) + self.reporter.on_build_start(source) } fn on_build_complete(&self, source: &BuildableSource, id: usize) { - self.reporter - .on_any_build_complete(&source.to_color_string(), id); + self.reporter.on_build_complete(source, id); } fn on_checkout_start(&self, url: &Url, rev: &str) -> usize { @@ -321,12 +317,11 @@ impl uv_resolver::ResolverReporter for ResolverReporter { impl uv_distribution::Reporter for ResolverReporter { fn on_build_start(&self, source: &BuildableSource) -> usize { - self.reporter.on_any_build_start(&source.to_color_string()) + self.reporter.on_build_start(source) } fn on_build_complete(&self, source: &BuildableSource, id: usize) { - self.reporter - .on_any_build_complete(&source.to_color_string(), id); + self.reporter.on_build_complete(source, id); } fn on_download_start(&self, name: &PackageName, size: Option) -> usize { @@ -406,9 +401,3 @@ impl ColorDisplay for BuildableSource<'_> { } } } - -impl ColorDisplay for LocalEditable { - fn to_color_string(&self) -> String { - format!("{}", self.to_string().dimmed()) - } -} diff --git a/crates/uv/src/editables.rs b/crates/uv/src/editables.rs deleted file mode 100644 index 9ac2e6bdc..000000000 --- a/crates/uv/src/editables.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::fmt::Write; -use std::ops::Deref; - -use anyhow::{anyhow, Context, Result}; -use indexmap::IndexMap; -use owo_colors::OwoColorize; - -use distribution_types::{ - InstalledDist, LocalEditable, LocalEditables, Name, Requirement, Requirements, -}; -use platform_tags::Tags; -use requirements_txt::EditableRequirement; -use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache}; -use uv_client::RegistryClient; -use uv_configuration::{Concurrency, Reinstall}; -use uv_dispatch::BuildDispatch; -use uv_distribution::DistributionDatabase; -use uv_installer::{is_dynamic, Downloader, InstalledEditable, ResolvedEditable}; -use uv_interpreter::Interpreter; -use uv_resolver::BuiltEditableMetadata; -use uv_types::{HashStrategy, InstalledPackagesProvider}; - -use crate::commands::elapsed; -use crate::commands::reporters::DownloadReporter; -use crate::printer::Printer; - -#[derive(Debug, Default)] -pub(crate) struct ResolvedEditables { - /// The set of resolved editables, including both those that were already installed and those - /// that were built. - pub(crate) editables: Vec, - /// The temporary directory in which the built editables were stored. - #[allow(dead_code)] - temp_dir: Option, -} - -impl Deref for ResolvedEditables { - type Target = [ResolvedEditable]; - - fn deref(&self) -> &Self::Target { - &self.editables - } -} - -impl ResolvedEditables { - /// Resolve the set of editables that need to be installed. - #[allow(clippy::too_many_arguments)] - pub(crate) async fn resolve( - editables: impl IntoIterator, - installed_packages: &impl InstalledPackagesProvider, - reinstall: &Reinstall, - hasher: &HashStrategy, - interpreter: &Interpreter, - tags: &Tags, - cache: &Cache, - client: &RegistryClient, - build_dispatch: &BuildDispatch<'_>, - concurrency: Concurrency, - printer: Printer, - ) -> Result { - // Partition the editables into those that are already installed, and those that must be built. - let mut installed = Vec::new(); - let mut builds = Vec::new(); - for editable in editables { - match reinstall { - Reinstall::None => { - if let [dist] = installed_packages.get_editables(editable.raw()).as_slice() { - if let Some(editable) = up_to_date(&editable, dist)? { - installed.push(editable); - } else { - builds.push(editable); - } - } else { - builds.push(editable); - } - } - Reinstall::All => { - builds.push(editable); - } - Reinstall::Packages(packages) => { - if let [dist] = installed_packages.get_editables(editable.raw()).as_slice() { - if packages.contains(dist.name()) { - builds.push(editable); - } else if let Some(editable) = up_to_date(&editable, dist)? { - installed.push(editable); - } else { - builds.push(editable); - } - } else { - builds.push(editable); - } - } - } - } - - // Build any editables. - let (built_editables, temp_dir) = if builds.is_empty() { - (Vec::new(), None) - } else { - let start = std::time::Instant::now(); - - let downloader = Downloader::new( - cache, - tags, - hasher, - DistributionDatabase::new(client, build_dispatch, concurrency.downloads), - ) - .with_reporter(DownloadReporter::from(printer).with_length(builds.len() as u64)); - - let editables = LocalEditables::from_editables(builds.into_iter()); - - let temp_dir = tempfile::tempdir_in(cache.root())?; - - let editables: Vec<_> = downloader - .build_editables(editables, temp_dir.path()) - .await - .context("Failed to build editables")? - .into_iter() - .collect(); - - // Validate that the editables are compatible with the target Python version. - for editable in &editables { - if let Some(python_requires) = editable.metadata.requires_python.as_ref() { - if !python_requires.contains(interpreter.python_version()) { - return Err(anyhow!( - "Editable `{}` requires Python {}, but {} is installed", - editable.metadata.name, - python_requires, - interpreter.python_version() - )); - } - } - } - - let s = if editables.len() == 1 { "" } else { "s" }; - writeln!( - printer.stderr(), - "{}", - format!( - "Built {} in {}", - format!("{} editable{}", editables.len(), s).bold(), - elapsed(start.elapsed()) - ) - .dimmed() - )?; - - (editables, Some(temp_dir)) - }; - - let editables = installed - .into_iter() - .map(ResolvedEditable::Installed) - .chain(built_editables.into_iter().map(ResolvedEditable::Built)) - .collect::>(); - - Ok(Self { - editables, - temp_dir, - }) - } - - pub(crate) fn as_metadata(&self) -> Vec { - self.iter() - .map(|editable| { - let dependencies: Vec<_> = editable - .metadata() - .requires_dist - .iter() - .cloned() - .map(Requirement::from) - .collect(); - BuiltEditableMetadata { - built: editable.local().clone(), - metadata: editable.metadata().clone(), - requirements: Requirements { - dependencies, - optional_dependencies: IndexMap::default(), - }, - } - }) - .collect() - } - - /// Convert an [`EditableRequirement`] into a [`LocalEditable`]. - pub(crate) fn from_requirement(editable: EditableRequirement) -> LocalEditable { - LocalEditable { - url: editable.url, - path: editable.path, - extras: editable.extras, - } - } -} - -/// Returns the [`InstalledEditable`] if the installed distribution is up-to-date for the given -/// requirement. -fn up_to_date(editable: &LocalEditable, dist: &InstalledDist) -> Result> { - // If the editable isn't up-to-date, don't reuse it. - if !ArchiveTimestamp::up_to_date_with(&editable.path, ArchiveTarget::Install(dist))? { - return Ok(None); - }; - - // If the editable is dynamic, don't reuse it. - if is_dynamic(&editable.path) { - return Ok(None); - }; - - // If we can't read the metadata from the installed distribution, don't reuse it. - let Ok(metadata) = dist.metadata() else { - return Ok(None); - }; - - Ok(Some(InstalledEditable { - editable: editable.clone(), - wheel: (*dist).clone(), - metadata, - })) -} diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index cf55e9309..c345832a0 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -44,7 +44,6 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; mod cli; mod commands; mod compat; -mod editables; mod logging; mod printer; mod settings; diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index ffb1d4aa4..46bfde9c8 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3377,7 +3377,6 @@ fn compile_editable() -> Result<()> { # via aiohttp ----- stderr ----- - Built 2 editables in [TIME] Resolved 13 packages in [TIME] "###); @@ -3428,7 +3427,6 @@ fn deduplicate_editable() -> Result<()> { # via aiohttp ----- stderr ----- - Built 1 editable in [TIME] Resolved 9 packages in [TIME] "###); @@ -3507,7 +3505,6 @@ fn compile_editable_url_requirement() -> Result<()> { # via hatchling-editable ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] "###); @@ -4009,7 +4006,6 @@ fn generate_hashes_editable() -> Result<()> { # via anyio ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] "###); @@ -4219,7 +4215,6 @@ coverage = ["example[test]", "extras>=0.0.1,<=0.0.2"] # via extras ----- stderr ----- - Built 1 editable in [TIME] Resolved 3 packages in [TIME] "### ); @@ -4436,8 +4431,7 @@ fn missing_editable_requirement() -> Result<()> { ----- stdout ----- ----- stderr ----- - error: Failed to build editables - Caused by: Source distribution not found at: [TEMP_DIR]/foo/anyio-3.7.0.tar.gz + error: Distribution not found at: file://[TEMP_DIR]/foo/anyio-3.7.0.tar.gz "###); Ok(()) @@ -5610,7 +5604,6 @@ dependencies = [ # via -r requirements.in ----- stderr ----- - Built 2 editables in [TIME] Resolved 2 packages in [TIME] "### ); @@ -5637,7 +5630,6 @@ fn editable_invalid_extra() -> Result<()> { # via -r [TEMP_DIR]/requirements.in ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] warning: The package `black @ file://[WORKSPACE]/scripts/packages/black_editable` does not have an extra named `empty`. "###); @@ -5882,9 +5874,7 @@ fn conflicting_url_markers() -> Result<()> { Ok(()) } -/// Override a regular package with an editable. -/// -/// At present, this incorrectly resolves to the regular package. +/// Override a regular package with an editable. This should resolve to the editable package. #[test] fn editable_override() -> Result<()> { let context = TestContext::new("3.12"); @@ -5895,41 +5885,32 @@ fn editable_override() -> Result<()> { // Add an editable override. let overrides_txt = context.temp_dir.child("overrides.txt"); - overrides_txt.write_str("-e file://../../scripts/packages/black_editable")?; + overrides_txt.write_str("-e ../../scripts/packages/black_editable")?; - uv_snapshot!(context.compile() - .arg("requirements.in") - .arg("--override") - .arg("overrides.txt"), @r###" + uv_snapshot!(context.filters(), context.compile() + .arg(requirements_in.path()) + .arg("--override") + .arg(overrides_txt.path()) + .current_dir(current_dir()?), @r###" success: true exit_code: 0 ----- stdout ----- # This file was autogenerated by uv via the following command: - # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --override overrides.txt - black==24.3.0 - # via -r requirements.in - click==8.1.7 - # via black - mypy-extensions==1.0.0 - # via black - packaging==24.0 - # via black - pathspec==0.12.1 - # via black - platformdirs==4.2.0 - # via black + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --override [TEMP_DIR]/overrides.txt + -e ../../scripts/packages/black_editable + # via + # --override [TEMP_DIR]/overrides.txt + # -r [TEMP_DIR]/requirements.in ----- stderr ----- - Resolved 6 packages in [TIME] + Resolved 1 package in [TIME] "### ); Ok(()) } -/// Override an editable with a regular package. -/// -/// At present, this incorrectly resolves to the editable. +/// Override an editable with a regular package. This should resolve to the regular package. #[test] fn override_editable() -> Result<()> { let context = TestContext::new("3.12"); @@ -5949,12 +5930,23 @@ fn override_editable() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --override [TEMP_DIR]/overrides.txt - -e ../../scripts/packages/black_editable - # via -r [TEMP_DIR]/requirements.in + black==23.10.1 + # via + # --override [TEMP_DIR]/overrides.txt + # -r [TEMP_DIR]/requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black ----- stderr ----- - Built 1 editable in [TIME] - Resolved 1 package in [TIME] + Resolved 6 packages in [TIME] "###); Ok(()) @@ -6306,7 +6298,6 @@ fn editable_direct_dependency() -> Result<()> { # via setuptools-editable ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] "###); @@ -6498,11 +6489,13 @@ requires-python = "<=3.8" uv_snapshot!(context.filters(), context.compile() .arg("requirements.in"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.[X] + × No solution found when resolving dependencies: + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -6548,11 +6541,13 @@ requires-python = "<=3.8" .arg("requirements.in") .arg("--python-version=3.11"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.8, but resolution targets Python 3.11 + × No solution found when resolving dependencies: + ╰─▶ Because the requested Python version (3.11) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -6600,7 +6595,6 @@ dev = [ # via anyio ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] "### ); @@ -6651,7 +6645,6 @@ dev = ["setuptools"] # via example ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] "### ); @@ -6810,7 +6803,6 @@ fn compile_root_uri_editable() -> Result<()> { # via root-editable ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] "### ); @@ -7943,7 +7935,7 @@ requires-python = ">3.8" # via -r requirements.in idna==3.6 # via anyio - lib @ file://[TEMP_DIR]/lib + lib @ file://[TEMP_DIR]/lib/ # via example ----- stderr ----- @@ -8055,7 +8047,6 @@ requires-python = ">3.8" # via flask ----- stderr ----- - Built 1 editable in [TIME] Resolved 7 packages in [TIME] "### ); @@ -9260,7 +9251,6 @@ fn tool_uv_sources() -> Result<()> { # via project (some_dir/pyproject.toml) ----- stderr ----- - Built 1 editable in [TIME] Resolved 8 packages in [TIME] "### ); diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 665600775..883eb1f89 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -848,9 +848,8 @@ fn install_editable() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 @@ -928,8 +927,8 @@ fn install_editable_and_registry() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - black==24.3.0 @@ -993,8 +992,8 @@ fn install_editable_no_binary() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable) "### @@ -1019,8 +1018,8 @@ fn install_editable_compatible_constraint() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable) "### @@ -1047,9 +1046,8 @@ fn install_editable_incompatible_constraint_version() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] × No solution found when resolving dependencies: - ╰─▶ Because you require black==0.1.0 and black>0.1.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because only black<=0.1.0 is available and you require black>0.1.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -1074,7 +1072,6 @@ fn install_editable_incompatible_constraint_url() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] error: Requirements contain conflicting URLs for package `black`: - [WORKSPACE]/scripts/packages/black_editable - https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl @@ -1725,8 +1722,8 @@ fn only_binary_editable() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + anyio==4.3.0+foo (from file://[WORKSPACE]/scripts/packages/anyio_local) "### @@ -1739,26 +1736,26 @@ fn only_binary_dependent_editables() { let context = TestContext::new("3.12"); let root_path = context .workspace_root - .join("scripts/packages/dependent_editables"); + .join("scripts/packages/dependent_locals"); // Install the editable package. uv_snapshot!(context.filters(), context.install() .arg("--only-binary") .arg(":all:") .arg("-e") - .arg(root_path.join("first_editable")) + .arg(root_path.join("first_local")) .arg("-e") - .arg(root_path.join("second_editable")), @r###" + .arg(root_path.join("second_local")), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Built 2 editables in [TIME] Resolved 2 packages in [TIME] + Downloaded 2 packages in [TIME] Installed 2 packages in [TIME] - + first-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/first_editable) - + second-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/second_editable) + + first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local) + + second-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/second_local) "### ); } @@ -1779,9 +1776,8 @@ fn only_binary_editable_setup_py() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 8 packages in [TIME] - Downloaded 7 packages in [TIME] + Downloaded 8 packages in [TIME] Installed 8 packages in [TIME] + anyio==4.3.0 + certifi==2024.2.2 @@ -1949,8 +1945,8 @@ fn no_deps_editable() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable) "### @@ -2408,9 +2404,8 @@ fn config_settings() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Installed 2 packages in [TIME] + iniconfig==2.0.0 + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) @@ -2437,9 +2432,8 @@ fn config_settings() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Installed 2 packages in [TIME] + iniconfig==2.0.0 + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) @@ -2575,9 +2569,8 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.0.0 + example==0.0.0 (from file://[TEMP_DIR]/editable) @@ -2620,9 +2613,8 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Uninstalled 2 packages in [TIME] Installed 2 packages in [TIME] - anyio==4.0.0 @@ -2667,9 +2659,8 @@ dependencies = {file = ["requirements.txt"]} ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.0.0 + example==0.1.0 (from file://[TEMP_DIR]/editable) @@ -2687,8 +2678,8 @@ dependencies = {file = ["requirements.txt"]} ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - example==0.1.0 (from file://[TEMP_DIR]/editable) @@ -2708,9 +2699,8 @@ dependencies = {file = ["requirements.txt"]} ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Uninstalled 2 packages in [TIME] Installed 2 packages in [TIME] - anyio==4.0.0 @@ -2837,9 +2827,8 @@ requires-python = ">=3.11,<3.13" ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.0.0 + example==0.1.0 (from file://[TEMP_DIR]/editable) @@ -2875,11 +2864,13 @@ requires-python = "<=3.8" .arg("--editable") .arg(editable_dir.path()), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.8, but 3.12.[X] is installed + × No solution found when resolving dependencies: + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.8 and example==0.0.0 depends on Python<=3.8, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -3889,11 +3880,12 @@ fn already_installed_dependent_editable() { let context = TestContext::new("3.12"); let root_path = context .workspace_root - .join("scripts/packages/dependent_editables"); + .join("scripts/packages/dependent_locals"); // Install the first editable uv_snapshot!(context.filters(), context.install() - .arg(root_path.join("first_editable")), @r###" + .arg("-e") + .arg(root_path.join("first_local")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3902,14 +3894,15 @@ fn already_installed_dependent_editable() { Resolved 1 package in [TIME] Downloaded 1 package in [TIME] Installed 1 package in [TIME] - + first-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/first_editable) + + first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local) "### ); // Install the second editable which depends on the first editable // The already installed first editable package should satisfy the requirement uv_snapshot!(context.filters(), context.install() - .arg(root_path.join("second_editable")) + .arg("-e") + .arg(root_path.join("second_local")) // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") @@ -3922,14 +3915,15 @@ fn already_installed_dependent_editable() { Resolved 2 packages in [TIME] Downloaded 1 package in [TIME] Installed 1 package in [TIME] - + second-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/second_editable) + + second-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/second_local) "### ); // Request install of the first editable by full path again // We should audit the installed package uv_snapshot!(context.filters(), context.install() - .arg(root_path.join("first_editable")), @r###" + .arg("-e") + .arg(root_path.join("first_local")), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3940,11 +3934,12 @@ fn already_installed_dependent_editable() { ); // Request reinstallation of the first package during install of the second - // It's not available on an index and the user has not specified the path so we fail + // It's not available on an index and the user has not specified the path so we fail. uv_snapshot!(context.filters(), context.install() - .arg(root_path.join("second_editable")) + .arg("-e") + .arg(root_path.join("second_local")) .arg("--reinstall-package") - .arg("first-editable") + .arg("first-local") // Disable the index to guard this test against dependency confusion attacks .arg("--no-index") .arg("--find-links") @@ -3955,27 +3950,29 @@ fn already_installed_dependent_editable() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because first-editable was not found in the provided package locations and second-editable==0.0.1 depends on first-editable, we can conclude that second-editable==0.0.1 cannot be used. - And because only second-editable==0.0.1 is available and you require second-editable, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because first-local was not found in the provided package locations and second-local==0.1.0 depends on first-local, we can conclude that second-local==0.1.0 cannot be used. + And because only second-local==0.1.0 is available and you require second-local, we can conclude that the requirements are unsatisfiable. "### ); // Request reinstallation of the first package // We include it in the install command with a full path so we should succeed uv_snapshot!(context.filters(), context.install() - .arg(root_path.join("first_editable")) + .arg("-e") + .arg(root_path.join("first_local")) .arg("--reinstall-package") - .arg("first-editable"), @r###" + .arg("first-local"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - - first-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/first_editable) - + first-editable==0.0.1 (from file://[WORKSPACE]/scripts/packages/dependent_editables/first_editable) + - first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local) + + first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local) "### ); } @@ -4070,6 +4067,7 @@ fn already_installed_local_path_dependent() { ----- stderr ----- Resolved 2 packages in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local) @@ -4198,6 +4196,7 @@ fn already_installed_local_version_of_remote_package() { ----- stderr ----- Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - anyio==4.3.0+foo (from file://[WORKSPACE]/scripts/packages/anyio_local) @@ -4235,6 +4234,7 @@ fn already_installed_local_version_of_remote_package() { ----- stderr ----- Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - anyio==4.3.0 @@ -4688,8 +4688,7 @@ fn require_hashes_editable() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] - error: In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `aiohttp` + error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: file://[WORKSPACE]/scripts/packages/black_editable[d] "### ); @@ -4953,9 +4952,8 @@ fn tool_uv_sources() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 9 packages in [TIME] - Downloaded 8 packages in [TIME] + Downloaded 9 packages in [TIME] Installed 9 packages in [TIME] + anyio==4.3.0 + boltons==24.0.1.dev0 (from git+https://github.com/mahmoud/boltons@57fbaa9b673ed85b32458b31baeeae230520e4a0) diff --git a/crates/uv/tests/pip_list.rs b/crates/uv/tests/pip_list.rs index 73cb88c94..bf57d8c60 100644 --- a/crates/uv/tests/pip_list.rs +++ b/crates/uv/tests/pip_list.rs @@ -165,9 +165,8 @@ fn list_editable() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 @@ -218,9 +217,8 @@ fn list_editable_only() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 @@ -309,9 +307,8 @@ fn list_exclude() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 @@ -413,9 +410,8 @@ fn list_format_json() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 @@ -520,9 +516,8 @@ fn list_format_freeze() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + idna==3.6 diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 70b5af64a..43bb1ea94 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -2198,9 +2198,8 @@ fn sync_editable() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 3 packages in [TIME] - Downloaded 2 packages in [TIME] + Downloaded 3 packages in [TIME] Installed 3 packages in [TIME] + boltons==23.1.1 + numpy==1.26.2 @@ -2218,8 +2217,8 @@ fn sync_editable() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 3 packages in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable) @@ -2329,8 +2328,8 @@ fn sync_editable_and_registry() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - black==24.1.0 @@ -2416,8 +2415,8 @@ fn sync_editable_and_local() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + black==0.1.0 (from file://[TEMP_DIR]/black_editable) "### @@ -2460,8 +2459,8 @@ fn sync_editable_and_local() -> Result<()> { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - black==0.1.0 (from file://[TEMP_DIR]/black_editable) @@ -3099,8 +3098,8 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Installed 1 package in [TIME] + example==0.0.0 (from file://[TEMP_DIR]/editable) "### @@ -3139,8 +3138,8 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - example==0.0.0 (from file://[TEMP_DIR]/editable) @@ -3262,11 +3261,13 @@ requires-python = "<=3.5" uv_snapshot!(context.filters(), sync_without_exclude_newer(&context) .arg("requirements.in"), @r###" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Editable `example` requires Python <=3.5, but 3.12.[X] is installed + × No solution found when resolving dependencies: + ╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python<=3.5 and example==0.0.0 depends on Python<=3.5, we can conclude that example==0.0.0 cannot be used. + And because only example==0.0.0 is available and you require example, we can conclude that the requirements are unsatisfiable. "### ); @@ -4208,7 +4209,7 @@ fn require_hashes_unnamed() -> Result<()> { Ok(()) } -/// We allow `--require-hashes` for editables, as long as no dependencies are included. +/// We disallow `--require-hashes` for editables. #[test] fn require_hashes_editable() -> Result<()> { let context = TestContext::new("3.12"); @@ -4224,15 +4225,12 @@ fn require_hashes_editable() -> Result<()> { uv_snapshot!(context.filters(), sync_without_exclude_newer(&context) .arg(requirements_txt.path()) .arg("--require-hashes"), @r###" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] - Resolved 1 package in [TIME] - Installed 1 package in [TIME] - + black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable) + error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: file://[WORKSPACE]/scripts/packages/black_editable[d] "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 5d7685f8c..b35c90f94 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -59,9 +59,8 @@ fn test_albatross_in_examples_bird_feeder() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-in-example/examples/bird-feeder) @@ -95,9 +94,8 @@ fn test_albatross_in_examples() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Installed 2 packages in [TIME] + albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-in-example) + tqdm==4.66.2 @@ -129,9 +127,8 @@ fn test_albatross_just_project() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 2 packages in [TIME] - Downloaded 1 package in [TIME] + Downloaded 2 packages in [TIME] Installed 2 packages in [TIME] + albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-just-project) + tqdm==4.66.2 @@ -166,9 +163,8 @@ fn test_albatross_project_in_excluded() { ----- stdout ----- ----- stderr ----- - Built 1 editable in [TIME] Resolved 4 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 4 packages in [TIME] Installed 4 packages in [TIME] + anyio==4.3.0 + bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-project-in-excluded/excluded/bird-feeder) @@ -202,9 +198,8 @@ fn test_albatross_root_workspace() { ----- stdout ----- ----- stderr ----- - Built 3 editables in [TIME] Resolved 7 packages in [TIME] - Downloaded 4 packages in [TIME] + Downloaded 7 packages in [TIME] Installed 7 packages in [TIME] + albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace) + anyio==4.3.0 @@ -244,9 +239,8 @@ fn test_albatross_root_workspace_bird_feeder() { ----- stdout ----- ----- stderr ----- - Built 2 editables in [TIME] Resolved 5 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 5 packages in [TIME] Installed 5 packages in [TIME] + anyio==4.3.0 + bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace/packages/bird-feeder) @@ -284,9 +278,8 @@ fn test_albatross_root_workspace_albatross() { ----- stdout ----- ----- stderr ----- - Built 2 editables in [TIME] Resolved 5 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 5 packages in [TIME] Installed 5 packages in [TIME] + anyio==4.3.0 + bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace/packages/bird-feeder) @@ -324,9 +317,8 @@ fn test_albatross_virtual_workspace() { ----- stdout ----- ----- stderr ----- - Built 2 editables in [TIME] Resolved 5 packages in [TIME] - Downloaded 3 packages in [TIME] + Downloaded 5 packages in [TIME] Installed 5 packages in [TIME] + anyio==4.3.0 + bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-virtual-workspace/packages/bird-feeder) diff --git a/scripts/packages/dependent_editables/first_editable/.gitignore b/scripts/packages/dependent_editables/first_editable/.gitignore deleted file mode 100644 index eaa9f051b..000000000 --- a/scripts/packages/dependent_editables/first_editable/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Artifacts from the build process. -*.egg-info/ diff --git a/scripts/packages/dependent_editables/first_editable/build/lib/first_editable/__init__.py b/scripts/packages/dependent_editables/first_editable/build/lib/first_editable/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/packages/dependent_editables/first_editable/first_editable/__init__.py b/scripts/packages/dependent_editables/first_editable/first_editable/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/packages/dependent_editables/first_editable/setup.py b/scripts/packages/dependent_editables/first_editable/setup.py deleted file mode 100644 index 7c54dde9b..000000000 --- a/scripts/packages/dependent_editables/first_editable/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup(name="first-editable", version="0.0.1", install_requires=[]) diff --git a/scripts/packages/dependent_editables/second_editable/.gitignore b/scripts/packages/dependent_editables/second_editable/.gitignore deleted file mode 100644 index c847724a5..000000000 --- a/scripts/packages/dependent_editables/second_editable/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Artifacts from the build process. -*.egg-info/ -build/ diff --git a/scripts/packages/dependent_editables/second_editable/second_editable/__init__.py b/scripts/packages/dependent_editables/second_editable/second_editable/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/packages/dependent_editables/second_editable/setup.py b/scripts/packages/dependent_editables/second_editable/setup.py deleted file mode 100644 index 9df967f0b..000000000 --- a/scripts/packages/dependent_editables/second_editable/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from setuptools import setup - -setup( - name="second-editable", - version="0.0.1", - install_requires=[ - "first-editable", - ], -)