mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Remove special-casing for editable requirements (#3869)
## Summary There are a few behavior changes in here: - We now enforce `--require-hashes` for editables, like pip. So if you use `--require-hashes` with an editable requirement, we'll reject it. I could change this if it seems off. - We now treat source tree requirements, editable or not (e.g., both `-e ./black` and `./black`) as if `--refresh` is always enabled. This doesn't mean that we _always_ rebuild them; but if you pass `--reinstall`, then yes, we always rebuild them. I think this is an improvement and is close to how editables work today. Closes #3844. Closes #2695.
This commit is contained in:
parent
063a0a4384
commit
1fc6a59707
64 changed files with 583 additions and 1813 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use url::Url;
|
||||
|
||||
use pep508_rs::{RequirementOrigin, VerbatimUrl};
|
||||
use pep508_rs::RequirementOrigin;
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
|
@ -40,35 +38,19 @@ impl std::fmt::Display for SourceAnnotation {
|
|||
|
||||
/// A collection of source annotations.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SourceAnnotations {
|
||||
packages: BTreeMap<PackageName, BTreeSet<SourceAnnotation>>,
|
||||
editables: BTreeMap<Url, BTreeSet<SourceAnnotation>>,
|
||||
}
|
||||
pub struct SourceAnnotations(BTreeMap<PackageName, BTreeSet<SourceAnnotation>>);
|
||||
|
||||
impl SourceAnnotations {
|
||||
/// Add a source annotation to the collection for the given package.
|
||||
pub fn add(&mut self, package: &PackageName, annotation: SourceAnnotation) {
|
||||
self.packages
|
||||
self.0
|
||||
.entry(package.clone())
|
||||
.or_default()
|
||||
.insert(annotation);
|
||||
}
|
||||
|
||||
/// Add an source annotation to the collection for the given editable.
|
||||
pub fn add_editable(&mut self, url: &VerbatimUrl, annotation: SourceAnnotation) {
|
||||
self.editables
|
||||
.entry(url.to_url())
|
||||
.or_default()
|
||||
.insert(annotation);
|
||||
}
|
||||
|
||||
/// Return the source annotations for a given package.
|
||||
pub fn get(&self, package: &PackageName) -> Option<&BTreeSet<SourceAnnotation>> {
|
||||
self.packages.get(package)
|
||||
}
|
||||
|
||||
/// Return the source annotations for a given editable.
|
||||
pub fn get_editable(&self, url: &VerbatimUrl) -> Option<&BTreeSet<SourceAnnotation>> {
|
||||
self.editables.get(url.raw())
|
||||
self.0.get(package)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,14 @@ impl BuildableSource<'_> {
|
|||
Self::Url(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the source is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
match self {
|
||||
Self::Dist(dist) => dist.is_editable(),
|
||||
Self::Url(url) => url.is_editable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BuildableSource<'_> {
|
||||
|
@ -77,6 +85,14 @@ impl<'a> SourceUrl<'a> {
|
|||
Self::Directory(dist) => dist.url,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the source is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Directory(DirectorySourceUrl { editable: true, .. })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SourceUrl<'_> {
|
||||
|
@ -151,6 +167,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
|||
pub struct DirectorySourceUrl<'a> {
|
||||
pub url: &'a Url,
|
||||
pub path: Cow<'a, Path>,
|
||||
pub editable: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DirectorySourceUrl<'_> {
|
||||
|
@ -164,6 +181,7 @@ impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
|
|||
Self {
|
||||
url: &dist.url,
|
||||
path: Cow::Borrowed(&dist.path),
|
||||
editable: dist.editable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,14 +131,6 @@ impl CachedDist {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the distribution is editable.
|
||||
pub fn editable(&self) -> bool {
|
||||
match self {
|
||||
Self::Registry(_) => false,
|
||||
Self::Url(dist) => dist.editable,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`WheelFilename`] of the distribution.
|
||||
pub fn filename(&self) -> &WheelFilename {
|
||||
match self {
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use uv_normalize::ExtraName;
|
||||
|
||||
use crate::Verbatim;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalEditable {
|
||||
/// The underlying [`EditableRequirement`] from the `requirements.txt` file.
|
||||
pub url: VerbatimUrl,
|
||||
/// Either the path to the editable or its checkout.
|
||||
pub path: PathBuf,
|
||||
/// The extras that should be installed.
|
||||
pub extras: Vec<ExtraName>,
|
||||
}
|
||||
|
||||
impl LocalEditable {
|
||||
/// Return the editable as a [`Url`].
|
||||
pub fn url(&self) -> &VerbatimUrl {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Return the resolved path to the editable.
|
||||
pub fn raw(&self) -> &Url {
|
||||
self.url.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl Verbatim for LocalEditable {
|
||||
fn verbatim(&self) -> Cow<'_, str> {
|
||||
self.url.verbatim()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocalEditable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.url, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`LocalEditable`]s.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalEditables(Vec<LocalEditable>);
|
||||
|
||||
impl LocalEditables {
|
||||
/// Merge and dedupe a list of [`LocalEditable`]s.
|
||||
///
|
||||
/// This function will deduplicate any editables that point to identical paths, merging their
|
||||
/// extras.
|
||||
pub fn from_editables(editables: impl Iterator<Item = LocalEditable>) -> Self {
|
||||
let mut map = BTreeMap::new();
|
||||
for editable in editables {
|
||||
match map.entry(editable.path.clone()) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(editable);
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
let existing = entry.get_mut();
|
||||
existing.extras.extend(editable.extras);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(map.into_values().collect())
|
||||
}
|
||||
|
||||
/// Return the number of editables.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Return whether the editables are empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Return the editables as a vector.
|
||||
pub fn into_vec(self) -> Vec<LocalEditable> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for LocalEditables {
|
||||
type Item = LocalEditable;
|
||||
type IntoIter = std::vec::IntoIter<LocalEditable>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
|
@ -267,12 +267,10 @@ impl InstalledDist {
|
|||
|
||||
/// Return true if the distribution is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
match self {
|
||||
Self::Registry(_) => false,
|
||||
Self::Url(dist) => dist.editable,
|
||||
Self::EggInfo(_) => false,
|
||||
Self::LegacyEditable(_) => true,
|
||||
}
|
||||
matches!(
|
||||
self,
|
||||
Self::LegacyEditable(_) | Self::Url(InstalledDirectUrlDist { editable: true, .. })
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the [`Url`] of the distribution, if it is editable.
|
||||
|
|
|
@ -51,7 +51,6 @@ pub use crate::any::*;
|
|||
pub use crate::buildable::*;
|
||||
pub use crate::cached::*;
|
||||
pub use crate::diagnostic::*;
|
||||
pub use crate::editable::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::file::*;
|
||||
pub use crate::hash::*;
|
||||
|
@ -70,7 +69,6 @@ mod any;
|
|||
mod buildable;
|
||||
mod cached;
|
||||
mod diagnostic;
|
||||
mod editable;
|
||||
mod error;
|
||||
mod file;
|
||||
mod hash;
|
||||
|
@ -401,24 +399,15 @@ impl Dist {
|
|||
ParsedUrl::Archive(archive) => {
|
||||
Self::from_http_url(name, url.verbatim, archive.url, archive.subdirectory)
|
||||
}
|
||||
ParsedUrl::Path(file) => Self::from_file_url(name, url.verbatim, &file.path, false),
|
||||
ParsedUrl::Path(file) => {
|
||||
Self::from_file_url(name, url.verbatim, &file.path, file.editable)
|
||||
}
|
||||
ParsedUrl::Git(git) => {
|
||||
Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Dist`] for a local editable distribution.
|
||||
pub fn from_editable(name: PackageName, editable: LocalEditable) -> Result<Self, Error> {
|
||||
let LocalEditable { url, path, .. } = editable;
|
||||
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
|
||||
name,
|
||||
url,
|
||||
path,
|
||||
editable: true,
|
||||
})))
|
||||
}
|
||||
|
||||
/// Return true if the distribution is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
match self {
|
||||
|
|
|
@ -44,6 +44,11 @@ impl Requirement {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the requirement is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
self.source.is_editable()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pep508_rs::Requirement<VerbatimParsedUrl>> for Requirement {
|
||||
|
@ -190,7 +195,7 @@ impl RequirementSource {
|
|||
ParsedUrl::Path(local_file) => RequirementSource::Path {
|
||||
path: local_file.path,
|
||||
url,
|
||||
editable: false,
|
||||
editable: local_file.editable,
|
||||
},
|
||||
ParsedUrl::Git(git) => RequirementSource::Git {
|
||||
url,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
||||
use crate::{
|
||||
BuiltDist, Diagnostic, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist,
|
||||
LocalEditable, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
||||
BuiltDist, Diagnostic, Dist, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
||||
};
|
||||
|
||||
/// A set of packages pinned at specific versions.
|
||||
|
@ -67,35 +65,6 @@ impl Resolution {
|
|||
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
/// Return an iterator over the [`LocalEditable`] entities in this resolution.
|
||||
pub fn editables(&self) -> impl Iterator<Item = LocalEditable> + '_ {
|
||||
self.packages.values().filter_map(|dist| match dist {
|
||||
ResolvedDist::Installable(Dist::Source(SourceDist::Directory(
|
||||
DirectorySourceDist {
|
||||
path,
|
||||
url,
|
||||
editable: true,
|
||||
..
|
||||
},
|
||||
))) => Some(LocalEditable {
|
||||
url: url.clone(),
|
||||
path: path.clone(),
|
||||
extras: vec![],
|
||||
}),
|
||||
ResolvedDist::Installed(InstalledDist::Url(InstalledDirectUrlDist {
|
||||
path,
|
||||
url,
|
||||
editable: true,
|
||||
..
|
||||
})) => Some(LocalEditable {
|
||||
url: VerbatimUrl::from_url(url.clone()),
|
||||
path: path.clone(),
|
||||
extras: vec![],
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -73,4 +73,21 @@ impl UnresolvedRequirement {
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the requirement is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
match self {
|
||||
Self::Named(requirement) => requirement.is_editable(),
|
||||
Self::Unnamed(requirement) => requirement.url.is_editable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Requirement> for UnresolvedRequirementSpecification {
|
||||
fn from(requirement: Requirement) -> Self {
|
||||
Self {
|
||||
requirement: UnresolvedRequirement::Named(requirement),
|
||||
hashes: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<RequirementEntry> for UnresolvedRequirementSpecification {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<EditableRequirement> 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 {
|
||||
|
|
|
@ -129,7 +129,7 @@ impl Cache {
|
|||
pub fn from_path(root: impl Into<PathBuf>) -> 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<Freshness> {
|
||||
// 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<bool> {
|
||||
// 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<PackageName>, 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<bool>, refresh_package: Vec<PackageName>) -> 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(_))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,11 @@ impl<'a> BuiltWheelIndex<'a> {
|
|||
) -> Result<Option<CachedWheel>, 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.
|
||||
|
|
|
@ -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<Revision, Error> {
|
||||
|
@ -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<reqwest::Request, reqwest::Error> {
|
||||
client
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<Vec<BuiltEditable>, 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::<FuturesUnordered<_>>();
|
||||
|
||||
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<CachedDist, Error> {
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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::<PyProjectToml>(&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<Project>,
|
||||
tool: Option<Tool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct Project {
|
||||
dynamic: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Tool {
|
||||
poetry: Option<ToolPoetry>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ToolPoetry {
|
||||
#[allow(dead_code)]
|
||||
name: Option<String>,
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::<PyProjectToml>(&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<Project>,
|
||||
tool: Option<Tool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct Project {
|
||||
dynamic: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Tool {
|
||||
poetry: Option<ToolPoetry>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ToolPoetry {
|
||||
#[allow(dead_code)]
|
||||
name: Option<String>,
|
||||
}
|
||||
|
|
|
@ -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<SatisfiesResult> {
|
||||
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(),
|
||||
))
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Requirement>,
|
||||
/// The overrides for the project.
|
||||
pub overrides: Vec<UnresolvedRequirementSpecification>,
|
||||
/// Package to install as editable installs
|
||||
pub editables: Vec<EditableRequirement>,
|
||||
/// The source trees from which to extract requirements.
|
||||
pub source_trees: Vec<PathBuf>,
|
||||
/// 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<RequirementsSpecification> {
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<PackageName, BuiltEditableMetadata>);
|
||||
|
||||
impl Editables {
|
||||
/// Create a new set of editables from a set of requirements.
|
||||
pub(crate) fn from_requirements(requirements: Vec<BuiltEditableMetadata>) -> 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<Item = &BuiltEditableMetadata> {
|
||||
self.0.values()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<PackageName>,
|
||||
|
||||
/// 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<BuiltEditableMetadata>,
|
||||
|
||||
/// 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<Preference>,
|
||||
project: Option<PackageName>,
|
||||
editables: Vec<BuiltEditableMetadata>,
|
||||
exclusions: Exclusions,
|
||||
lookaheads: Vec<RequestedRequirements>,
|
||||
) -> 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Version>) {
|
||||
self.0.push((package, version));
|
||||
}
|
||||
|
||||
/// Iterate over the dependencies.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &(PubGrubPackage, Range<Version>)> {
|
||||
self.0.iter()
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
|
||||
// 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())?;
|
||||
}
|
||||
|
|
|
@ -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<AnnotatedDist, Range<Version>, 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<ResolutionDiagnostic>,
|
||||
}
|
||||
|
@ -50,7 +47,6 @@ impl ResolutionGraph {
|
|||
distributions: &FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
||||
state: &State<UvDependencyProvider>,
|
||||
preferences: &Preferences,
|
||||
editables: Editables,
|
||||
) -> anyhow::Result<Self, ResolveError> {
|
||||
// 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;
|
||||
};
|
||||
|
|
|
@ -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<str> {
|
||||
// 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 {
|
||||
|
|
|
@ -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<InstalledPackages: InstalledPackagesProvider> {
|
|||
overrides: Overrides,
|
||||
preferences: Preferences,
|
||||
exclusions: Exclusions,
|
||||
editables: Editables,
|
||||
urls: Urls,
|
||||
locals: Locals,
|
||||
dependency_mode: DependencyMode,
|
||||
|
@ -192,7 +190,6 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
|||
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<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
self.index.distributions(),
|
||||
&state.pubgrub,
|
||||
&self.preferences,
|
||||
self.editables.clone(),
|
||||
);
|
||||
};
|
||||
state.next = highest_priority_pkg;
|
||||
|
@ -560,11 +556,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
return Err(ResolveError::UnhashedPackage(name.clone()));
|
||||
}
|
||||
|
||||
// If the package is an editable, we don't need to fetch metadata.
|
||||
if self.editables.contains(name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Emit a request to fetch the metadata for this distribution.
|
||||
let dist = Dist::from_url(name.clone(), url.clone())?;
|
||||
if self.index.distributions().register(dist.version_id()) {
|
||||
|
@ -649,31 +640,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
url.verbatim
|
||||
);
|
||||
|
||||
// If the dist is an editable, return the version from the editable metadata.
|
||||
if let Some(editable) = self.editables.get(name) {
|
||||
let version = &editable.metadata.version;
|
||||
|
||||
// The version is incompatible with the requirement.
|
||||
if !range.contains(version) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// The version is incompatible due to its Python requirement.
|
||||
if let Some(requires_python) = editable.metadata.requires_python.as_ref() {
|
||||
let target = self.python_requirement.target();
|
||||
if !requires_python.contains(target) {
|
||||
return Ok(Some(ResolverVersion::Unavailable(
|
||||
version.clone(),
|
||||
UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
|
||||
IncompatibleSource::RequiresPython(requires_python.clone()),
|
||||
)),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(ResolverVersion::Available(version.clone())));
|
||||
}
|
||||
|
||||
let dist = PubGrubDistribution::from_url(name, url);
|
||||
let response = self
|
||||
.index
|
||||
|
@ -853,7 +819,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
self.markers.as_ref(),
|
||||
);
|
||||
|
||||
let mut dependencies = match dependencies {
|
||||
let dependencies = match dependencies {
|
||||
Ok(dependencies) => dependencies,
|
||||
Err(err) => {
|
||||
return Ok(Dependencies::Unavailable(
|
||||
|
@ -872,54 +838,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
self.visit_package(package, request_sink)?;
|
||||
}
|
||||
|
||||
// Add a dependency on each editable.
|
||||
for editable in self.editables.iter() {
|
||||
let package = PubGrubPackage::from_package(
|
||||
editable.metadata.name.clone(),
|
||||
None,
|
||||
None,
|
||||
&self.urls,
|
||||
);
|
||||
let version = Range::singleton(editable.metadata.version.clone());
|
||||
|
||||
// Update the package priorities.
|
||||
priorities.insert(&package, &version);
|
||||
|
||||
// Add the editable as a direct dependency.
|
||||
dependencies.push(package, version);
|
||||
|
||||
// Add a dependency on each extra.
|
||||
for extra in &editable.built.extras {
|
||||
dependencies.push(
|
||||
PubGrubPackage::from_package(
|
||||
editable.metadata.name.clone(),
|
||||
Some(extra.clone()),
|
||||
None,
|
||||
&self.urls,
|
||||
),
|
||||
Range::singleton(editable.metadata.version.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
// Add any constraints.
|
||||
for constraint in self
|
||||
.constraints
|
||||
.get(&editable.metadata.name)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if constraint.evaluate_markers(self.markers.as_ref(), &[]) {
|
||||
let PubGrubRequirement { package, version } =
|
||||
PubGrubRequirement::from_constraint(
|
||||
constraint,
|
||||
&self.urls,
|
||||
&self.locals,
|
||||
)?;
|
||||
dependencies.push(package, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Dependencies::Available(dependencies.into()))
|
||||
}
|
||||
|
||||
|
@ -935,57 +853,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
if self.dependency_mode.is_direct() {
|
||||
// If an extra is provided, wait for the metadata to be available, since it's
|
||||
// still required for generating the lock file.
|
||||
if !self.editables.contains(name) {
|
||||
// Determine the distribution to lookup.
|
||||
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)?;
|
||||
}
|
||||
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<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Some(Response::Dist { dist, metadata }))
|
||||
}
|
||||
|
||||
|
@ -1320,6 +1204,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
||||
}
|
||||
})?;
|
||||
|
||||
Response::Dist { dist, metadata }
|
||||
}
|
||||
ResolvedDist::Installed(dist) => {
|
||||
|
|
|
@ -22,36 +22,6 @@ impl Urls {
|
|||
) -> Result<Self, ResolveError> {
|
||||
let mut urls: FxHashMap<PackageName, VerbatimParsedUrl> = 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 {
|
||||
|
|
|
@ -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![],
|
||||
);
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<BuiltEditableMetadata> = 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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<InstalledPackages: InstalledPackagesProvider>(
|
|||
source_trees: Vec<PathBuf>,
|
||||
project: Option<PackageName>,
|
||||
extras: &ExtrasSpecification,
|
||||
editables: &ResolvedEditables,
|
||||
installed_packages: InstalledPackages,
|
||||
hasher: &HashStrategy,
|
||||
reinstall: &Reinstall,
|
||||
|
@ -176,9 +174,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
|||
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<InstalledPackages: InstalledPackagesProvider>(
|
|||
&requirements,
|
||||
&constraints,
|
||||
&overrides,
|
||||
&editables,
|
||||
hasher,
|
||||
index,
|
||||
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
|
||||
|
@ -215,7 +209,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
|||
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::<Vec<_>>();
|
||||
// Extract the requirements from the resolution.
|
||||
let requirements = resolution.requirements().collect::<Vec<_>>();
|
||||
|
||||
// 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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<u64>) -> 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<u64>) -> 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<u64>) -> usize {
|
||||
|
@ -406,9 +401,3 @@ impl ColorDisplay for BuildableSource<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDisplay for LocalEditable {
|
||||
fn to_color_string(&self) -> String {
|
||||
format!("{}", self.to_string().dimmed())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ResolvedEditable>,
|
||||
/// The temporary directory in which the built editables were stored.
|
||||
#[allow(dead_code)]
|
||||
temp_dir: Option<tempfile::TempDir>,
|
||||
}
|
||||
|
||||
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<Item = LocalEditable>,
|
||||
installed_packages: &impl InstalledPackagesProvider,
|
||||
reinstall: &Reinstall,
|
||||
hasher: &HashStrategy,
|
||||
interpreter: &Interpreter,
|
||||
tags: &Tags,
|
||||
cache: &Cache,
|
||||
client: &RegistryClient,
|
||||
build_dispatch: &BuildDispatch<'_>,
|
||||
concurrency: Concurrency,
|
||||
printer: Printer,
|
||||
) -> Result<Self> {
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
Ok(Self {
|
||||
editables,
|
||||
temp_dir,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn as_metadata(&self) -> Vec<BuiltEditableMetadata> {
|
||||
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<Option<InstalledEditable>> {
|
||||
// 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,
|
||||
}))
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
"###
|
||||
);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# Artifacts from the build process.
|
||||
*.egg-info/
|
|
@ -1,3 +0,0 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(name="first-editable", version="0.0.1", install_requires=[])
|
|
@ -1,3 +0,0 @@
|
|||
# Artifacts from the build process.
|
||||
*.egg-info/
|
||||
build/
|
|
@ -1,9 +0,0 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="second-editable",
|
||||
version="0.0.1",
|
||||
install_requires=[
|
||||
"first-editable",
|
||||
],
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue