mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Remove special-casing for editable requirements (#3869)
## Summary There are a few behavior changes in here: - We now enforce `--require-hashes` for editables, like pip. So if you use `--require-hashes` with an editable requirement, we'll reject it. I could change this if it seems off. - We now treat source tree requirements, editable or not (e.g., both `-e ./black` and `./black`) as if `--refresh` is always enabled. This doesn't mean that we _always_ rebuild them; but if you pass `--reinstall`, then yes, we always rebuild them. I think this is an improvement and is close to how editables work today. Closes #3844. Closes #2695.
This commit is contained in:
parent
063a0a4384
commit
1fc6a59707
64 changed files with 583 additions and 1813 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4486,7 +4486,6 @@ dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"indexmap",
|
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"indoc",
|
"indoc",
|
||||||
"insta",
|
"insta",
|
||||||
|
@ -4501,7 +4500,6 @@ dependencies = [
|
||||||
"pypi-types",
|
"pypi-types",
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"requirements-txt",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4868,7 +4866,6 @@ dependencies = [
|
||||||
"platform-tags",
|
"platform-tags",
|
||||||
"pypi-types",
|
"pypi-types",
|
||||||
"rayon",
|
"rayon",
|
||||||
"requirements-txt",
|
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use url::Url;
|
use pep508_rs::RequirementOrigin;
|
||||||
|
|
||||||
use pep508_rs::{RequirementOrigin, VerbatimUrl};
|
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
|
@ -40,35 +38,19 @@ impl std::fmt::Display for SourceAnnotation {
|
||||||
|
|
||||||
/// A collection of source annotations.
|
/// A collection of source annotations.
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct SourceAnnotations {
|
pub struct SourceAnnotations(BTreeMap<PackageName, BTreeSet<SourceAnnotation>>);
|
||||||
packages: BTreeMap<PackageName, BTreeSet<SourceAnnotation>>,
|
|
||||||
editables: BTreeMap<Url, BTreeSet<SourceAnnotation>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceAnnotations {
|
impl SourceAnnotations {
|
||||||
/// Add a source annotation to the collection for the given package.
|
/// Add a source annotation to the collection for the given package.
|
||||||
pub fn add(&mut self, package: &PackageName, annotation: SourceAnnotation) {
|
pub fn add(&mut self, package: &PackageName, annotation: SourceAnnotation) {
|
||||||
self.packages
|
self.0
|
||||||
.entry(package.clone())
|
.entry(package.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(annotation);
|
.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.
|
/// Return the source annotations for a given package.
|
||||||
pub fn get(&self, package: &PackageName) -> Option<&BTreeSet<SourceAnnotation>> {
|
pub fn get(&self, package: &PackageName) -> Option<&BTreeSet<SourceAnnotation>> {
|
||||||
self.packages.get(package)
|
self.0.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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,14 @@ impl BuildableSource<'_> {
|
||||||
Self::Url(_) => None,
|
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<'_> {
|
impl std::fmt::Display for BuildableSource<'_> {
|
||||||
|
@ -77,6 +85,14 @@ impl<'a> SourceUrl<'a> {
|
||||||
Self::Directory(dist) => dist.url,
|
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<'_> {
|
impl std::fmt::Display for SourceUrl<'_> {
|
||||||
|
@ -151,6 +167,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
||||||
pub struct DirectorySourceUrl<'a> {
|
pub struct DirectorySourceUrl<'a> {
|
||||||
pub url: &'a Url,
|
pub url: &'a Url,
|
||||||
pub path: Cow<'a, Path>,
|
pub path: Cow<'a, Path>,
|
||||||
|
pub editable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for DirectorySourceUrl<'_> {
|
impl std::fmt::Display for DirectorySourceUrl<'_> {
|
||||||
|
@ -164,6 +181,7 @@ impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
|
||||||
Self {
|
Self {
|
||||||
url: &dist.url,
|
url: &dist.url,
|
||||||
path: Cow::Borrowed(&dist.path),
|
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.
|
/// Returns the [`WheelFilename`] of the distribution.
|
||||||
pub fn filename(&self) -> &WheelFilename {
|
pub fn filename(&self) -> &WheelFilename {
|
||||||
match self {
|
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.
|
/// Return true if the distribution is editable.
|
||||||
pub fn is_editable(&self) -> bool {
|
pub fn is_editable(&self) -> bool {
|
||||||
match self {
|
matches!(
|
||||||
Self::Registry(_) => false,
|
self,
|
||||||
Self::Url(dist) => dist.editable,
|
Self::LegacyEditable(_) | Self::Url(InstalledDirectUrlDist { editable: true, .. })
|
||||||
Self::EggInfo(_) => false,
|
)
|
||||||
Self::LegacyEditable(_) => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`Url`] of the distribution, if it is editable.
|
/// 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::buildable::*;
|
||||||
pub use crate::cached::*;
|
pub use crate::cached::*;
|
||||||
pub use crate::diagnostic::*;
|
pub use crate::diagnostic::*;
|
||||||
pub use crate::editable::*;
|
|
||||||
pub use crate::error::*;
|
pub use crate::error::*;
|
||||||
pub use crate::file::*;
|
pub use crate::file::*;
|
||||||
pub use crate::hash::*;
|
pub use crate::hash::*;
|
||||||
|
@ -70,7 +69,6 @@ mod any;
|
||||||
mod buildable;
|
mod buildable;
|
||||||
mod cached;
|
mod cached;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
mod editable;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod file;
|
mod file;
|
||||||
mod hash;
|
mod hash;
|
||||||
|
@ -401,24 +399,15 @@ impl Dist {
|
||||||
ParsedUrl::Archive(archive) => {
|
ParsedUrl::Archive(archive) => {
|
||||||
Self::from_http_url(name, url.verbatim, archive.url, archive.subdirectory)
|
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) => {
|
ParsedUrl::Git(git) => {
|
||||||
Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
|
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.
|
/// Return true if the distribution is editable.
|
||||||
pub fn is_editable(&self) -> bool {
|
pub fn is_editable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -44,6 +44,11 @@ impl Requirement {
|
||||||
true
|
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 {
|
impl From<pep508_rs::Requirement<VerbatimParsedUrl>> for Requirement {
|
||||||
|
@ -190,7 +195,7 @@ impl RequirementSource {
|
||||||
ParsedUrl::Path(local_file) => RequirementSource::Path {
|
ParsedUrl::Path(local_file) => RequirementSource::Path {
|
||||||
path: local_file.path,
|
path: local_file.path,
|
||||||
url,
|
url,
|
||||||
editable: false,
|
editable: local_file.editable,
|
||||||
},
|
},
|
||||||
ParsedUrl::Git(git) => RequirementSource::Git {
|
ParsedUrl::Git(git) => RequirementSource::Git {
|
||||||
url,
|
url,
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use pep508_rs::VerbatimUrl;
|
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BuiltDist, Diagnostic, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist,
|
BuiltDist, Diagnostic, Dist, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
||||||
LocalEditable, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A set of packages pinned at specific versions.
|
/// A set of packages pinned at specific versions.
|
||||||
|
@ -67,35 +65,6 @@ impl Resolution {
|
||||||
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
|
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
|
||||||
&self.diagnostics
|
&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)]
|
#[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,
|
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 {
|
impl Pep508Url for VerbatimParsedUrl {
|
||||||
type Err = ParsedUrlError;
|
type Err = ParsedUrlError;
|
||||||
|
|
||||||
|
@ -150,6 +157,13 @@ pub enum ParsedUrl {
|
||||||
Archive(ParsedArchiveUrl),
|
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
|
/// A local path url
|
||||||
///
|
///
|
||||||
/// Examples:
|
/// Examples:
|
||||||
|
|
|
@ -47,9 +47,9 @@ use url::Url;
|
||||||
use distribution_types::{Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification};
|
use distribution_types::{Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification};
|
||||||
use pep508_rs::{
|
use pep508_rs::{
|
||||||
expand_env_vars, split_scheme, strip_host, Extras, MarkerTree, Pep508Error, Pep508ErrorSource,
|
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")]
|
#[cfg(feature = "http")]
|
||||||
use uv_client::BaseClient;
|
use uv_client::BaseClient;
|
||||||
use uv_client::BaseClientBuilder;
|
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
|
/// Parsed and flattened requirements.txt with requirements and constraints
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct RequirementsTxt {
|
pub struct RequirementsTxt {
|
||||||
|
|
|
@ -129,7 +129,7 @@ impl Cache {
|
||||||
pub fn from_path(root: impl Into<PathBuf>) -> Self {
|
pub fn from_path(root: impl Into<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: root.into(),
|
root: root.into(),
|
||||||
refresh: Refresh::None,
|
refresh: Refresh::None(Timestamp::now()),
|
||||||
_temp_dir_drop: None,
|
_temp_dir_drop: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ impl Cache {
|
||||||
let temp_dir = tempdir()?;
|
let temp_dir = tempdir()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
root: temp_dir.path().to_path_buf(),
|
root: temp_dir.path().to_path_buf(),
|
||||||
refresh: Refresh::None,
|
refresh: Refresh::None(Timestamp::now()),
|
||||||
_temp_dir_drop: Some(Arc::new(temp_dir)),
|
_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.
|
/// Returns `true` if a cache entry must be revalidated given the [`Refresh`] policy.
|
||||||
pub fn must_revalidate(&self, package: &PackageName) -> bool {
|
pub fn must_revalidate(&self, package: &PackageName) -> bool {
|
||||||
match &self.refresh {
|
match &self.refresh {
|
||||||
Refresh::None => false,
|
Refresh::None(_) => false,
|
||||||
Refresh::All(_) => true,
|
Refresh::All(_) => true,
|
||||||
Refresh::Packages(packages, _) => packages.contains(package),
|
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(
|
pub fn freshness(
|
||||||
&self,
|
&self,
|
||||||
entry: &CacheEntry,
|
entry: &CacheEntry,
|
||||||
|
@ -197,7 +200,7 @@ impl Cache {
|
||||||
) -> io::Result<Freshness> {
|
) -> io::Result<Freshness> {
|
||||||
// Grab the cutoff timestamp, if it's relevant.
|
// Grab the cutoff timestamp, if it's relevant.
|
||||||
let timestamp = match &self.refresh {
|
let timestamp = match &self.refresh {
|
||||||
Refresh::None => return Ok(Freshness::Fresh),
|
Refresh::None(_) => return Ok(Freshness::Fresh),
|
||||||
Refresh::All(timestamp) => timestamp,
|
Refresh::All(timestamp) => timestamp,
|
||||||
Refresh::Packages(packages, timestamp) => {
|
Refresh::Packages(packages, timestamp) => {
|
||||||
if package.map_or(true, |package| packages.contains(package)) {
|
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.
|
/// Persist a temporary directory to the artifact store, returning its unique ID.
|
||||||
pub async fn persist(
|
pub async fn persist(
|
||||||
&self,
|
&self,
|
||||||
|
@ -897,10 +920,13 @@ impl Freshness {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A refresh policy for cache entries.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Refresh {
|
pub enum Refresh {
|
||||||
/// Don't refresh any entries.
|
/// Don't refresh any entries.
|
||||||
None,
|
None(Timestamp),
|
||||||
/// Refresh entries linked to the given packages, if created before the given timestamp.
|
/// Refresh entries linked to the given packages, if created before the given timestamp.
|
||||||
Packages(Vec<PackageName>, Timestamp),
|
Packages(Vec<PackageName>, Timestamp),
|
||||||
/// Refresh all entries created before the given timestamp.
|
/// Refresh all entries created before the given timestamp.
|
||||||
|
@ -910,14 +936,15 @@ pub enum Refresh {
|
||||||
impl Refresh {
|
impl Refresh {
|
||||||
/// Determine the refresh strategy to use based on the command-line arguments.
|
/// Determine the refresh strategy to use based on the command-line arguments.
|
||||||
pub fn from_args(refresh: Option<bool>, refresh_package: Vec<PackageName>) -> Self {
|
pub fn from_args(refresh: Option<bool>, refresh_package: Vec<PackageName>) -> Self {
|
||||||
|
let timestamp = Timestamp::now();
|
||||||
match refresh {
|
match refresh {
|
||||||
Some(true) => Self::All(Timestamp::now()),
|
Some(true) => Self::All(timestamp),
|
||||||
Some(false) => Self::None,
|
Some(false) => Self::None(timestamp),
|
||||||
None => {
|
None => {
|
||||||
if refresh_package.is_empty() {
|
if refresh_package.is_empty() {
|
||||||
Self::None
|
Self::None(timestamp)
|
||||||
} else {
|
} 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.
|
/// Returns `true` if no packages should be reinstalled.
|
||||||
pub fn is_none(&self) -> bool {
|
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),
|
Url(&'a Url),
|
||||||
/// A path dependency, which we key by URL.
|
/// A path dependency, which we key by URL.
|
||||||
Path(&'a 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.
|
/// A Git dependency, which we key by URL and SHA.
|
||||||
///
|
///
|
||||||
/// Note that this variant only exists for source distributions; wheels can't be delivered
|
/// 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
|
WheelCache::Path(url) => WheelCacheKind::Path
|
||||||
.root()
|
.root()
|
||||||
.join(digest(&CanonicalUrl::new(url))),
|
.join(digest(&CanonicalUrl::new(url))),
|
||||||
|
WheelCache::Editable(url) => WheelCacheKind::Editable
|
||||||
|
.root()
|
||||||
|
.join(digest(&CanonicalUrl::new(url))),
|
||||||
WheelCache::Git(url, sha) => WheelCacheKind::Git
|
WheelCache::Git(url, sha) => WheelCacheKind::Git
|
||||||
.root()
|
.root()
|
||||||
.join(digest(&CanonicalUrl::new(url)))
|
.join(digest(&CanonicalUrl::new(url)))
|
||||||
|
@ -58,6 +63,8 @@ pub(crate) enum WheelCacheKind {
|
||||||
Url,
|
Url,
|
||||||
/// A cache of data from a local path.
|
/// A cache of data from a local path.
|
||||||
Path,
|
Path,
|
||||||
|
/// A cache of data from an editable URL.
|
||||||
|
Editable,
|
||||||
/// A cache of data from a Git repository.
|
/// A cache of data from a Git repository.
|
||||||
Git,
|
Git,
|
||||||
}
|
}
|
||||||
|
@ -69,6 +76,7 @@ impl WheelCacheKind {
|
||||||
Self::Index => "index",
|
Self::Index => "index",
|
||||||
Self::Url => "url",
|
Self::Url => "url",
|
||||||
Self::Path => "path",
|
Self::Path => "path",
|
||||||
|
Self::Editable => "editable",
|
||||||
Self::Git => "git",
|
Self::Git => "git",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
||||||
remote,
|
remote,
|
||||||
reinstalls,
|
reinstalls,
|
||||||
extraneous: _,
|
extraneous: _,
|
||||||
} = Planner::with_requirements(&requirements).build(
|
} = Planner::new(&requirements).build(
|
||||||
site_packages,
|
site_packages,
|
||||||
&Reinstall::None,
|
&Reinstall::None,
|
||||||
&NoBinary::None,
|
&NoBinary::None,
|
||||||
|
|
|
@ -11,16 +11,16 @@ use tempfile::TempDir;
|
||||||
use tokio::io::{AsyncRead, AsyncSeekExt, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncSeekExt, ReadBuf};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||||
use tracing::{info_span, instrument, warn, Instrument};
|
use tracing::{debug, info_span, instrument, warn, Instrument};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, IndexLocations,
|
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, IndexLocations, Name,
|
||||||
LocalEditable, Name, SourceDist,
|
SourceDist,
|
||||||
};
|
};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::{HashDigest, Metadata23};
|
use pypi_types::HashDigest;
|
||||||
use uv_cache::{ArchiveId, ArchiveTimestamp, CacheBucket, CacheEntry, Timestamp, WheelCache};
|
use uv_cache::{ArchiveId, ArchiveTimestamp, CacheBucket, CacheEntry, Timestamp, WheelCache};
|
||||||
use uv_client::{
|
use uv_client::{
|
||||||
CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient,
|
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.
|
/// 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
|
/// 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.
|
// Optimization: Skip source dist download when we must not build them anyway.
|
||||||
if no_build {
|
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;
|
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)
|
.download_and_build_metadata(source, hashes, &self.client)
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,11 @@ impl<'a> BuiltWheelIndex<'a> {
|
||||||
) -> Result<Option<CachedWheel>, Error> {
|
) -> Result<Option<CachedWheel>, Error> {
|
||||||
let cache_shard = self.cache.shard(
|
let cache_shard = self.cache.shard(
|
||||||
CacheBucket::BuiltWheels,
|
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.
|
// Read the revision from the cache.
|
||||||
|
|
|
@ -16,8 +16,8 @@ use zip::ZipArchive;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, DirectorySourceDist, DirectorySourceUrl, Dist, FileLocation, GitSourceUrl,
|
BuildableSource, DirectorySourceUrl, FileLocation, GitSourceUrl, HashPolicy, Hashed,
|
||||||
HashPolicy, Hashed, LocalEditable, PathSourceUrl, RemoteSource, SourceDist, SourceUrl,
|
PathSourceUrl, RemoteSource, SourceDist, SourceUrl,
|
||||||
};
|
};
|
||||||
use install_wheel_rs::metadata::read_archive_metadata;
|
use install_wheel_rs::metadata::read_archive_metadata;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
|
@ -369,7 +369,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
||||||
let cache_shard = self.build_context.cache().shard(
|
let cache_shard = self.build_context.cache().shard(
|
||||||
CacheBucket::BuiltWheels,
|
CacheBucket::BuiltWheels,
|
||||||
|
@ -825,7 +824,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
Ok(revision)
|
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(
|
async fn source_tree(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
|
@ -840,15 +840,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
|
|
||||||
let cache_shard = self.build_context.cache().shard(
|
let cache_shard = self.build_context.cache().shard(
|
||||||
CacheBucket::BuiltWheels,
|
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?;
|
let _lock = lock_shard(&cache_shard).await?;
|
||||||
|
|
||||||
// Fetch the revision for the source distribution.
|
// Fetch the revision for the source distribution.
|
||||||
let revision = self
|
let revision = self.source_tree_revision(resource, &cache_shard).await?;
|
||||||
.source_tree_revision(source, resource, &cache_shard)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Scope all operations to the revision. Within the revision, there's no need to check for
|
// 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.
|
// 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
|
/// If the build backend supports `prepare_metadata_for_build_wheel`, this method will avoid
|
||||||
/// building the wheel.
|
/// building the wheel.
|
||||||
|
@ -906,15 +909,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
|
|
||||||
let cache_shard = self.build_context.cache().shard(
|
let cache_shard = self.build_context.cache().shard(
|
||||||
CacheBucket::BuiltWheels,
|
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?;
|
let _lock = lock_shard(&cache_shard).await?;
|
||||||
|
|
||||||
// Fetch the revision for the source distribution.
|
// Fetch the revision for the source distribution.
|
||||||
let revision = self
|
let revision = self.source_tree_revision(resource, &cache_shard).await?;
|
||||||
.source_tree_revision(source, resource, &cache_shard)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Scope all operations to the revision. Within the revision, there's no need to check for
|
// 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.
|
// 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.
|
/// Return the [`Revision`] for a local source tree, refreshing it if necessary.
|
||||||
async fn source_tree_revision(
|
async fn source_tree_revision(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
|
||||||
resource: &DirectorySourceUrl<'_>,
|
resource: &DirectorySourceUrl<'_>,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
) -> Result<Revision, Error> {
|
) -> Result<Revision, Error> {
|
||||||
|
@ -982,16 +986,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
return Err(Error::DirWithoutEntrypoint(resource.path.to_path_buf()));
|
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 entry = cache_shard.entry(LOCAL_REVISION);
|
||||||
let freshness = self
|
let is_fresh = self
|
||||||
.build_context
|
.build_context
|
||||||
.cache()
|
.cache()
|
||||||
.freshness(&entry, source.name())
|
.is_fresh(&entry)
|
||||||
.map_err(Error::CacheRead)?;
|
.map_err(Error::CacheRead)?;
|
||||||
|
|
||||||
// If the revision is fresh, return it.
|
// If the revision is fresh, return it.
|
||||||
if freshness.is_fresh() {
|
if is_fresh {
|
||||||
if let Some(pointer) = LocalRevisionPointer::read_from(&entry)? {
|
if let Some(pointer) = LocalRevisionPointer::read_from(&entry)? {
|
||||||
if pointer.timestamp == modified.timestamp() {
|
if pointer.timestamp == modified.timestamp() {
|
||||||
return Ok(pointer.into_revision());
|
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))
|
source.name().is_some_and(|name| packages.contains(name))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if no_build {
|
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.
|
// Build the wheel.
|
||||||
|
@ -1314,7 +1324,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
subdirectory,
|
subdirectory,
|
||||||
&source.to_string(),
|
&source.to_string(),
|
||||||
source.as_dist(),
|
source.as_dist(),
|
||||||
BuildKind::Wheel,
|
if source.is_editable() {
|
||||||
|
BuildKind::Editable
|
||||||
|
} else {
|
||||||
|
BuildKind::Wheel
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| Error::Build(source.to_string(), err))?
|
.map_err(|err| Error::Build(source.to_string(), err))?
|
||||||
|
@ -1383,7 +1397,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
subdirectory,
|
subdirectory,
|
||||||
&source.to_string(),
|
&source.to_string(),
|
||||||
source.as_dist(),
|
source.as_dist(),
|
||||||
BuildKind::Wheel,
|
if source.is_editable() {
|
||||||
|
BuildKind::Editable
|
||||||
|
} else {
|
||||||
|
BuildKind::Wheel
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| Error::Build(source.to_string(), err))?;
|
.map_err(|err| Error::Build(source.to_string(), err))?;
|
||||||
|
@ -1410,49 +1428,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
Ok(Some(metadata))
|
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.
|
/// Returns a GET [`reqwest::Request`] for the given URL.
|
||||||
fn request(url: Url, client: &RegistryClient) -> Result<reqwest::Request, reqwest::Error> {
|
fn request(url: Url, client: &RegistryClient) -> Result<reqwest::Request, reqwest::Error> {
|
||||||
client
|
client
|
||||||
|
|
|
@ -21,7 +21,6 @@ pep440_rs = { workspace = true }
|
||||||
pep508_rs = { workspace = true }
|
pep508_rs = { workspace = true }
|
||||||
platform-tags = { workspace = true }
|
platform-tags = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
requirements-txt = { workspace = true }
|
|
||||||
uv-cache = { workspace = true }
|
uv-cache = { workspace = true }
|
||||||
uv-configuration = { workspace = true }
|
uv-configuration = { workspace = true }
|
||||||
uv-distribution = { workspace = true }
|
uv-distribution = { workspace = true }
|
||||||
|
|
|
@ -1,24 +1,18 @@
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
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 pep508_rs::PackageName;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{BuildableSource, CachedDist, Dist, Hashed, Identifier, RemoteSource};
|
||||||
BuildableSource, CachedDist, Dist, Hashed, Identifier, LocalEditable, LocalEditables,
|
|
||||||
RemoteSource,
|
|
||||||
};
|
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_distribution::{DistributionDatabase, LocalWheel};
|
use uv_distribution::{DistributionDatabase, LocalWheel};
|
||||||
use uv_types::{BuildContext, HashStrategy, InFlight};
|
use uv_types::{BuildContext, HashStrategy, InFlight};
|
||||||
|
|
||||||
use crate::editable::BuiltEditable;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to unzip wheel: {0}")]
|
#[error("Failed to unzip wheel: {0}")]
|
||||||
|
@ -115,55 +109,6 @@ impl<'a, Context: BuildContext> Downloader<'a, Context> {
|
||||||
Ok(wheels)
|
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.
|
/// 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()))]
|
#[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> {
|
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.
|
/// Callback to invoke when a source distribution build is complete.
|
||||||
fn on_build_complete(&self, source: &BuildableSource, id: usize);
|
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.
|
/// Callback to invoke when a repository checkout begins.
|
||||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
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 compile::{compile_tree, CompileError};
|
||||||
pub use downloader::{Downloader, Reporter as DownloadReporter};
|
pub use downloader::{Downloader, Reporter as DownloadReporter};
|
||||||
pub use editable::{is_dynamic, BuiltEditable, InstalledEditable, ResolvedEditable};
|
|
||||||
pub use installer::{Installer, Reporter as InstallReporter};
|
pub use installer::{Installer, Reporter as InstallReporter};
|
||||||
pub use plan::{Plan, Planner};
|
pub use plan::{Plan, Planner};
|
||||||
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic};
|
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic};
|
||||||
|
@ -8,7 +7,7 @@ pub use uninstall::{uninstall, UninstallError};
|
||||||
|
|
||||||
mod compile;
|
mod compile;
|
||||||
mod downloader;
|
mod downloader;
|
||||||
mod editable;
|
|
||||||
mod installer;
|
mod installer;
|
||||||
mod plan;
|
mod plan;
|
||||||
mod satisfies;
|
mod satisfies;
|
||||||
|
|
|
@ -5,14 +5,13 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tracing::{debug, warn};
|
use tracing::debug;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
CachedDirectUrlDist, CachedDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
CachedDirectUrlDist, CachedDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
||||||
Error, GitSourceDist, Hashed, IndexLocations, InstalledDist, InstalledMetadata,
|
Error, GitSourceDist, Hashed, IndexLocations, InstalledDist, Name, PathBuiltDist,
|
||||||
InstalledVersion, Name, PathBuiltDist, PathSourceDist, RemoteSource, Requirement,
|
PathSourceDist, RemoteSource, Requirement, RequirementSource, Verbatim,
|
||||||
RequirementSource, Verbatim,
|
|
||||||
};
|
};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
||||||
|
@ -26,32 +25,18 @@ use uv_interpreter::PythonEnvironment;
|
||||||
use uv_types::HashStrategy;
|
use uv_types::HashStrategy;
|
||||||
|
|
||||||
use crate::satisfies::RequirementSatisfaction;
|
use crate::satisfies::RequirementSatisfaction;
|
||||||
use crate::{ResolvedEditable, SitePackages};
|
use crate::SitePackages;
|
||||||
|
|
||||||
/// A planner to generate an [`Plan`] based on a set of requirements.
|
/// A planner to generate an [`Plan`] based on a set of requirements.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Planner<'a> {
|
pub struct Planner<'a> {
|
||||||
requirements: &'a [Requirement],
|
requirements: &'a [Requirement],
|
||||||
editable_requirements: &'a [ResolvedEditable],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Planner<'a> {
|
impl<'a> Planner<'a> {
|
||||||
/// Set the requirements use in the [`Plan`].
|
/// Set the requirements use in the [`Plan`].
|
||||||
#[must_use]
|
pub fn new(requirements: &'a [Requirement]) -> Self {
|
||||||
pub fn with_requirements(requirements: &'a [Requirement]) -> Self {
|
Self { requirements }
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Partition a set of requirements into those that should be linked from the cache, those that
|
/// 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(),
|
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 {
|
for requirement in self.requirements {
|
||||||
// Filter out incompatible requirements.
|
// Filter out incompatible requirements.
|
||||||
if !requirement.evaluate_markers(Some(venv.interpreter().markers()), &[]) {
|
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.
|
// If we see the same requirement twice, then we have a conflict.
|
||||||
let specifier = Specifier::NonEditable(&requirement.source);
|
|
||||||
match seen.entry(requirement.name.clone()) {
|
match seen.entry(requirement.name.clone()) {
|
||||||
Entry::Occupied(value) => {
|
Entry::Occupied(value) => {
|
||||||
if value.get() == &specifier {
|
if value.get() == &&requirement.source {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bail!(
|
bail!(
|
||||||
|
@ -159,7 +93,7 @@ impl<'a> Planner<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(specifier);
|
entry.insert(&requirement.source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +129,9 @@ impl<'a> Planner<'a> {
|
||||||
RequirementSatisfaction::OutOfDate => {
|
RequirementSatisfaction::OutOfDate => {
|
||||||
debug!("Requirement installed, but not fresh: {distribution}");
|
debug!("Requirement installed, but not fresh: {distribution}");
|
||||||
}
|
}
|
||||||
|
RequirementSatisfaction::Dynamic => {
|
||||||
|
debug!("Requirement installed, but dynamic: {distribution}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reinstalls.push(distribution.clone());
|
reinstalls.push(distribution.clone());
|
||||||
}
|
}
|
||||||
|
@ -332,7 +269,7 @@ impl<'a> Planner<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequirementSource::Path { url, .. } => {
|
RequirementSource::Path { url, editable, .. } => {
|
||||||
// Store the canonicalized path, which also serves to validate that it exists.
|
// Store the canonicalized path, which also serves to validate that it exists.
|
||||||
let path = match url
|
let path = match url
|
||||||
.to_file_path()
|
.to_file_path()
|
||||||
|
@ -352,7 +289,7 @@ impl<'a> Planner<'a> {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
path,
|
path,
|
||||||
editable: false,
|
editable: *editable,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find the most-compatible wheel from the cache, since we don't know
|
// 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)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Plan {
|
pub struct Plan {
|
||||||
/// The distributions that are not already installed in the current environment, but are
|
/// 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::fmt::Debug;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
use cache_key::{CanonicalUrl, RepositoryUrl};
|
||||||
use distribution_types::{InstalledDirectUrlDist, InstalledDist, RequirementSource};
|
use distribution_types::{InstalledDirectUrlDist, InstalledDist, RequirementSource};
|
||||||
use pypi_types::{DirInfo, DirectUrl, VcsInfo, VcsKind};
|
use pypi_types::{DirInfo, DirectUrl, VcsInfo, VcsKind};
|
||||||
use uv_cache::{ArchiveTarget, ArchiveTimestamp};
|
use uv_cache::{ArchiveTarget, ArchiveTimestamp};
|
||||||
|
@ -12,6 +15,7 @@ pub(crate) enum RequirementSatisfaction {
|
||||||
Mismatch,
|
Mismatch,
|
||||||
Satisfied,
|
Satisfied,
|
||||||
OutOfDate,
|
OutOfDate,
|
||||||
|
Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequirementSatisfaction {
|
impl RequirementSatisfaction {
|
||||||
|
@ -187,9 +191,59 @@ impl RequirementSatisfaction {
|
||||||
return Ok(Self::OutOfDate);
|
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.
|
// Otherwise, assume the requirement is up-to-date.
|
||||||
Ok(Self::Satisfied)
|
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 pep440_rs::{Version, VersionSpecifiers};
|
||||||
use pypi_types::VerbatimParsedUrl;
|
use pypi_types::VerbatimParsedUrl;
|
||||||
use requirements_txt::EditableRequirement;
|
|
||||||
use uv_cache::{ArchiveTarget, ArchiveTimestamp};
|
|
||||||
use uv_interpreter::PythonEnvironment;
|
use uv_interpreter::PythonEnvironment;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_types::InstalledPackagesProvider;
|
use uv_types::InstalledPackagesProvider;
|
||||||
|
|
||||||
use crate::is_dynamic;
|
|
||||||
use crate::satisfies::RequirementSatisfaction;
|
use crate::satisfies::RequirementSatisfaction;
|
||||||
|
|
||||||
/// An index over the packages installed in an environment.
|
/// An index over the packages installed in an environment.
|
||||||
|
@ -289,7 +286,6 @@ impl SitePackages {
|
||||||
pub fn satisfies(
|
pub fn satisfies(
|
||||||
&self,
|
&self,
|
||||||
requirements: &[UnresolvedRequirementSpecification],
|
requirements: &[UnresolvedRequirementSpecification],
|
||||||
editables: &[EditableRequirement],
|
|
||||||
constraints: &[Requirement],
|
constraints: &[Requirement],
|
||||||
) -> Result<SatisfiesResult> {
|
) -> Result<SatisfiesResult> {
|
||||||
let mut stack = Vec::with_capacity(requirements.len());
|
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.
|
// Verify that all non-editable requirements are met.
|
||||||
while let Some(entry) = stack.pop() {
|
while let Some(entry) = stack.pop() {
|
||||||
let installed = match &entry.requirement {
|
let installed = match &entry.requirement {
|
||||||
|
@ -378,7 +322,9 @@ impl SitePackages {
|
||||||
distribution,
|
distribution,
|
||||||
entry.requirement.source().as_ref(),
|
entry.requirement.source().as_ref(),
|
||||||
)? {
|
)? {
|
||||||
RequirementSatisfaction::Mismatch | RequirementSatisfaction::OutOfDate => {
|
RequirementSatisfaction::Mismatch
|
||||||
|
| RequirementSatisfaction::OutOfDate
|
||||||
|
| RequirementSatisfaction::Dynamic => {
|
||||||
return Ok(SatisfiesResult::Unsatisfied(entry.requirement.to_string()))
|
return Ok(SatisfiesResult::Unsatisfied(entry.requirement.to_string()))
|
||||||
}
|
}
|
||||||
RequirementSatisfaction::Satisfied => {}
|
RequirementSatisfaction::Satisfied => {}
|
||||||
|
@ -387,7 +333,8 @@ impl SitePackages {
|
||||||
for constraint in constraints {
|
for constraint in constraints {
|
||||||
match RequirementSatisfaction::check(distribution, &constraint.source)? {
|
match RequirementSatisfaction::check(distribution, &constraint.source)? {
|
||||||
RequirementSatisfaction::Mismatch
|
RequirementSatisfaction::Mismatch
|
||||||
| RequirementSatisfaction::OutOfDate => {
|
| RequirementSatisfaction::OutOfDate
|
||||||
|
| RequirementSatisfaction::Dynamic => {
|
||||||
return Ok(SatisfiesResult::Unsatisfied(
|
return Ok(SatisfiesResult::Unsatisfied(
|
||||||
entry.requirement.to_string(),
|
entry.requirement.to_string(),
|
||||||
))
|
))
|
||||||
|
|
|
@ -16,7 +16,7 @@ distribution-types = { workspace = true }
|
||||||
pep440_rs = { workspace = true }
|
pep440_rs = { workspace = true }
|
||||||
pep508_rs = { workspace = true }
|
pep508_rs = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
requirements-txt = { workspace = true, features = ["reqwest"] }
|
requirements-txt = { workspace = true, features = ["http"] }
|
||||||
uv-client = { workspace = true }
|
uv-client = { workspace = true }
|
||||||
uv-configuration = { workspace = true }
|
uv-configuration = { workspace = true }
|
||||||
uv-distribution = { workspace = true }
|
uv-distribution = { workspace = true }
|
||||||
|
|
|
@ -14,7 +14,7 @@ use pep508_rs::MarkerEnvironment;
|
||||||
use uv_configuration::{Constraints, Overrides};
|
use uv_configuration::{Constraints, Overrides};
|
||||||
use uv_distribution::{DistributionDatabase, Reporter};
|
use uv_distribution::{DistributionDatabase, Reporter};
|
||||||
use uv_git::GitUrl;
|
use uv_git::GitUrl;
|
||||||
use uv_resolver::{BuiltEditableMetadata, InMemoryIndex, MetadataResponse};
|
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||||
use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
|
use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -50,8 +50,6 @@ pub struct LookaheadResolver<'a, Context: BuildContext> {
|
||||||
constraints: &'a Constraints,
|
constraints: &'a Constraints,
|
||||||
/// The overrides for the project.
|
/// The overrides for the project.
|
||||||
overrides: &'a Overrides,
|
overrides: &'a Overrides,
|
||||||
/// The editable requirements for the project.
|
|
||||||
editables: &'a [BuiltEditableMetadata],
|
|
||||||
/// The required hashes for the project.
|
/// The required hashes for the project.
|
||||||
hasher: &'a HashStrategy,
|
hasher: &'a HashStrategy,
|
||||||
/// The in-memory index for resolving dependencies.
|
/// The in-memory index for resolving dependencies.
|
||||||
|
@ -67,7 +65,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
||||||
requirements: &'a [Requirement],
|
requirements: &'a [Requirement],
|
||||||
constraints: &'a Constraints,
|
constraints: &'a Constraints,
|
||||||
overrides: &'a Overrides,
|
overrides: &'a Overrides,
|
||||||
editables: &'a [BuiltEditableMetadata],
|
|
||||||
hasher: &'a HashStrategy,
|
hasher: &'a HashStrategy,
|
||||||
index: &'a InMemoryIndex,
|
index: &'a InMemoryIndex,
|
||||||
database: DistributionDatabase<'a, Context>,
|
database: DistributionDatabase<'a, Context>,
|
||||||
|
@ -76,7 +73,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
editables,
|
|
||||||
hasher,
|
hasher,
|
||||||
index,
|
index,
|
||||||
database,
|
database,
|
||||||
|
@ -111,13 +107,6 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
||||||
.constraints
|
.constraints
|
||||||
.apply(self.overrides.apply(self.requirements))
|
.apply(self.overrides.apply(self.requirements))
|
||||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
.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()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -184,9 +173,8 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
||||||
RequirementSource::Path {
|
RequirementSource::Path {
|
||||||
path,
|
path,
|
||||||
url,
|
url,
|
||||||
// TODO(konsti): Figure out why we lose the editable here (does it matter?)
|
editable,
|
||||||
editable: _,
|
} => Dist::from_file_url(requirement.name, url, &path, editable)?,
|
||||||
} => Dist::from_file_url(requirement.name, url, &path, false)?,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch the metadata for the distribution.
|
// Fetch the metadata for the distribution.
|
||||||
|
|
|
@ -104,6 +104,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||||
let source = SourceUrl::Directory(DirectorySourceUrl {
|
let source = SourceUrl::Directory(DirectorySourceUrl {
|
||||||
url: &url,
|
url: &url,
|
||||||
path: Cow::Borrowed(source_tree),
|
path: Cow::Borrowed(source_tree),
|
||||||
|
editable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Determine the hash policy. Since we don't have a package name, we perform a
|
// Determine the hash policy. Since we don't have a package name, we perform a
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
//! `source_trees`.
|
//! `source_trees`.
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::iter;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
@ -66,8 +65,6 @@ pub struct RequirementsSpecification {
|
||||||
pub constraints: Vec<Requirement>,
|
pub constraints: Vec<Requirement>,
|
||||||
/// The overrides for the project.
|
/// The overrides for the project.
|
||||||
pub overrides: Vec<UnresolvedRequirementSpecification>,
|
pub overrides: Vec<UnresolvedRequirementSpecification>,
|
||||||
/// Package to install as editable installs
|
|
||||||
pub editables: Vec<EditableRequirement>,
|
|
||||||
/// The source trees from which to extract requirements.
|
/// The source trees from which to extract requirements.
|
||||||
pub source_trees: Vec<PathBuf>,
|
pub source_trees: Vec<PathBuf>,
|
||||||
/// The extras used to collect requirements.
|
/// The extras used to collect requirements.
|
||||||
|
@ -121,13 +118,18 @@ impl RequirementsSpecification {
|
||||||
.requirements
|
.requirements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(UnresolvedRequirementSpecification::from)
|
.map(UnresolvedRequirementSpecification::from)
|
||||||
|
.chain(
|
||||||
|
requirements_txt
|
||||||
|
.editables
|
||||||
|
.into_iter()
|
||||||
|
.map(UnresolvedRequirementSpecification::from),
|
||||||
|
)
|
||||||
.collect(),
|
.collect(),
|
||||||
constraints: requirements_txt
|
constraints: requirements_txt
|
||||||
.constraints
|
.constraints
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Requirement::from)
|
.map(Requirement::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
editables: requirements_txt.editables,
|
|
||||||
index_url: requirements_txt.index_url.map(IndexUrl::from),
|
index_url: requirements_txt.index_url.map(IndexUrl::from),
|
||||||
extra_index_urls: requirements_txt
|
extra_index_urls: requirements_txt
|
||||||
.extra_index_urls
|
.extra_index_urls
|
||||||
|
@ -191,6 +193,7 @@ impl RequirementsSpecification {
|
||||||
.find(|member| is_same_file(member.root(), &requirement.path).unwrap_or(false))
|
.find(|member| is_same_file(member.root(), &requirement.path).unwrap_or(false))
|
||||||
.map(|member| (member.pyproject_toml(), workspace))
|
.map(|member| (member.pyproject_toml(), workspace))
|
||||||
});
|
});
|
||||||
|
|
||||||
let editable_spec = if let Some((pyproject_toml, workspace)) = project_in_exiting_workspace
|
let editable_spec = if let Some((pyproject_toml, workspace)) = project_in_exiting_workspace
|
||||||
{
|
{
|
||||||
Self::parse_direct_pyproject_toml(
|
Self::parse_direct_pyproject_toml(
|
||||||
|
@ -222,22 +225,23 @@ impl RequirementsSpecification {
|
||||||
requirement.path.user_display()
|
requirement.path.user_display()
|
||||||
);
|
);
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
editables: vec![requirement],
|
requirements: vec![UnresolvedRequirementSpecification::from(requirement)],
|
||||||
..Self::default()
|
..Self::default()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(editable_spec) = editable_spec {
|
if let Some(editable_spec) = editable_spec {
|
||||||
// We only collect the editables here to keep the count of root packages
|
// We only collect the editables here to keep the count of root packages correct.
|
||||||
// correct.
|
|
||||||
// TODO(konsti): Collect all workspace packages, even the non-editable ones.
|
// 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 {
|
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()
|
..Self::default()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -246,7 +250,7 @@ impl RequirementsSpecification {
|
||||||
requirement.path.user_display()
|
requirement.path.user_display()
|
||||||
);
|
);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
editables: vec![requirement],
|
requirements: vec![UnresolvedRequirementSpecification::from(requirement)],
|
||||||
..Self::default()
|
..Self::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -361,14 +365,13 @@ impl RequirementsSpecification {
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
project: Pep621Metadata,
|
project: Pep621Metadata,
|
||||||
) -> Result<RequirementsSpecification> {
|
) -> 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 queue = VecDeque::from([project.name.clone()]);
|
||||||
let mut editables = Vec::new();
|
|
||||||
let mut requirements = Vec::new();
|
let mut requirements = Vec::new();
|
||||||
let mut used_extras = FxHashSet::default();
|
let mut used_extras = FxHashSet::default();
|
||||||
|
|
||||||
while let Some(project_name) = queue.pop_front() {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
trace!("Processing metadata for workspace package {project_name}");
|
trace!("Processing metadata for workspace package {project_name}");
|
||||||
|
@ -396,40 +399,30 @@ impl RequirementsSpecification {
|
||||||
current.root().user_display()
|
current.root().user_display()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
used_extras.extend(project.used_extras);
|
|
||||||
|
|
||||||
// Partition into editable and non-editable requirements.
|
// Recurse into any editables.
|
||||||
for requirement in project.requirements {
|
for requirement in &project.requirements {
|
||||||
if let RequirementSource::Path {
|
if matches!(
|
||||||
path,
|
requirement.source,
|
||||||
editable: true,
|
RequirementSource::Path { editable: true, .. }
|
||||||
url,
|
) {
|
||||||
} = requirement.source
|
if seen.insert(requirement.name.clone()) {
|
||||||
{
|
|
||||||
editables.push(EditableRequirement {
|
|
||||||
url,
|
|
||||||
path,
|
|
||||||
marker: requirement.marker,
|
|
||||||
extras: requirement.extras,
|
|
||||||
origin: requirement.origin,
|
|
||||||
});
|
|
||||||
|
|
||||||
if seen_editables.insert(requirement.name.clone()) {
|
|
||||||
queue.push_back(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 {
|
let spec = Self {
|
||||||
project: Some(project.name),
|
project: Some(project.name),
|
||||||
editables,
|
requirements: requirements
|
||||||
requirements,
|
.into_iter()
|
||||||
|
.map(UnresolvedRequirementSpecification::from)
|
||||||
|
.collect(),
|
||||||
extras: used_extras,
|
extras: used_extras,
|
||||||
..Self::default()
|
..Self::default()
|
||||||
};
|
};
|
||||||
|
@ -459,7 +452,6 @@ impl RequirementsSpecification {
|
||||||
spec.constraints.extend(source.constraints);
|
spec.constraints.extend(source.constraints);
|
||||||
spec.overrides.extend(source.overrides);
|
spec.overrides.extend(source.overrides);
|
||||||
spec.extras.extend(source.extras);
|
spec.extras.extend(source.extras);
|
||||||
spec.editables.extend(source.editables);
|
|
||||||
spec.source_trees.extend(source.source_trees);
|
spec.source_trees.extend(source.source_trees);
|
||||||
|
|
||||||
// Use the first project name discovered.
|
// Use the first project name discovered.
|
||||||
|
|
|
@ -224,6 +224,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
||||||
SourceUrl::Directory(DirectorySourceUrl {
|
SourceUrl::Directory(DirectorySourceUrl {
|
||||||
url: &requirement.url.verbatim,
|
url: &requirement.url.verbatim,
|
||||||
path: Cow::Borrowed(&parsed_path_url.path),
|
path: Cow::Borrowed(&parsed_path_url.path),
|
||||||
|
editable: parsed_path_url.editable,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// If it's not a directory, assume it's a file.
|
// 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 dependency_mode::DependencyMode;
|
||||||
pub use editables::BuiltEditableMetadata;
|
|
||||||
pub use error::ResolveError;
|
pub use error::ResolveError;
|
||||||
pub use exclude_newer::ExcludeNewer;
|
pub use exclude_newer::ExcludeNewer;
|
||||||
pub use exclusions::Exclusions;
|
pub use exclusions::Exclusions;
|
||||||
|
@ -25,7 +24,6 @@ mod candidate_selector;
|
||||||
|
|
||||||
mod dependency_mode;
|
mod dependency_mode;
|
||||||
mod dependency_provider;
|
mod dependency_provider;
|
||||||
mod editables;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod exclude_newer;
|
mod exclude_newer;
|
||||||
mod exclusions;
|
mod exclusions;
|
||||||
|
|
|
@ -6,7 +6,6 @@ use uv_configuration::{Constraints, Overrides};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_types::RequestedRequirements;
|
use uv_types::RequestedRequirements;
|
||||||
|
|
||||||
use crate::editables::BuiltEditableMetadata;
|
|
||||||
use crate::{preferences::Preference, DependencyMode, Exclusions};
|
use crate::{preferences::Preference, DependencyMode, Exclusions};
|
||||||
|
|
||||||
/// A manifest of requirements, constraints, and preferences.
|
/// A manifest of requirements, constraints, and preferences.
|
||||||
|
@ -31,12 +30,6 @@ pub struct Manifest {
|
||||||
/// The name of the project.
|
/// The name of the project.
|
||||||
pub(crate) project: Option<PackageName>,
|
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.
|
/// The installed packages to exclude from consideration during resolution.
|
||||||
///
|
///
|
||||||
/// These typically represent packages that are being upgraded or reinstalled
|
/// These typically represent packages that are being upgraded or reinstalled
|
||||||
|
@ -59,7 +52,6 @@ impl Manifest {
|
||||||
overrides: Overrides,
|
overrides: Overrides,
|
||||||
preferences: Vec<Preference>,
|
preferences: Vec<Preference>,
|
||||||
project: Option<PackageName>,
|
project: Option<PackageName>,
|
||||||
editables: Vec<BuiltEditableMetadata>,
|
|
||||||
exclusions: Exclusions,
|
exclusions: Exclusions,
|
||||||
lookaheads: Vec<RequestedRequirements>,
|
lookaheads: Vec<RequestedRequirements>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -69,7 +61,6 @@ impl Manifest {
|
||||||
overrides,
|
overrides,
|
||||||
preferences,
|
preferences,
|
||||||
project,
|
project,
|
||||||
editables,
|
|
||||||
exclusions,
|
exclusions,
|
||||||
lookaheads,
|
lookaheads,
|
||||||
}
|
}
|
||||||
|
@ -82,7 +73,6 @@ impl Manifest {
|
||||||
overrides: Overrides::default(),
|
overrides: Overrides::default(),
|
||||||
preferences: Vec::new(),
|
preferences: Vec::new(),
|
||||||
project: None,
|
project: None,
|
||||||
editables: Vec::new(),
|
|
||||||
exclusions: Exclusions::default(),
|
exclusions: Exclusions::default(),
|
||||||
lookaheads: Vec::new(),
|
lookaheads: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -113,13 +103,6 @@ impl Manifest {
|
||||||
requirement.evaluate_markers(markers, lookahead.extras())
|
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(
|
.chain(
|
||||||
self.overrides
|
self.overrides
|
||||||
.apply(&self.requirements)
|
.apply(&self.requirements)
|
||||||
|
@ -175,13 +158,6 @@ impl Manifest {
|
||||||
requirement.evaluate_markers(markers, lookahead.extras())
|
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(
|
.chain(
|
||||||
self.overrides
|
self.overrides
|
||||||
.apply(&self.requirements)
|
.apply(&self.requirements)
|
||||||
|
@ -213,6 +189,6 @@ impl Manifest {
|
||||||
|
|
||||||
/// Returns the number of input requirements.
|
/// Returns the number of input requirements.
|
||||||
pub fn num_requirements(&self) -> usize {
|
pub fn num_requirements(&self) -> usize {
|
||||||
self.requirements.len() + self.editables.len()
|
self.requirements.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,11 +49,6 @@ impl PubGrubDependencies {
|
||||||
Ok(Self(dependencies))
|
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.
|
/// Iterate over the dependencies.
|
||||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &(PubGrubPackage, Range<Version>)> {
|
pub(crate) fn iter(&self) -> impl Iterator<Item = &(PubGrubPackage, Range<Version>)> {
|
||||||
self.0.iter()
|
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::distribution::PubGrubDistribution;
|
||||||
pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||||
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};
|
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use petgraph::visit::EdgeRef;
|
use petgraph::visit::EdgeRef;
|
||||||
use petgraph::Direction;
|
use petgraph::Direction;
|
||||||
|
|
||||||
use distribution_types::{IndexUrl, LocalEditable, Name, SourceAnnotations, Verbatim};
|
use distribution_types::{Name, SourceAnnotations};
|
||||||
use pypi_types::HashDigest;
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
use crate::resolution::AnnotatedDist;
|
|
||||||
use crate::ResolutionGraph;
|
use crate::ResolutionGraph;
|
||||||
|
|
||||||
/// A [`std::fmt::Display`] implementation for the resolution graph.
|
/// 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.
|
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
||||||
impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
@ -134,32 +89,22 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = if let Some(editable) = self.resolution.editables.get(name) {
|
Some((index, dist))
|
||||||
Node::Editable(&editable.built)
|
|
||||||
} else {
|
|
||||||
Node::Distribution(dist)
|
|
||||||
};
|
|
||||||
Some((index, node))
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Sort the nodes by name, but with editable packages first.
|
// 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.
|
// Print out the dependency graph.
|
||||||
for (index, node) in nodes {
|
for (index, node) in nodes {
|
||||||
// Display the node itself.
|
// Display the node itself.
|
||||||
let mut line = match node {
|
let mut line = node.to_requirements_txt(self.include_extras).to_string();
|
||||||
Node::Editable(editable) => format!("-e {}", editable.verbatim()),
|
|
||||||
Node::Distribution(dist) => {
|
|
||||||
dist.to_requirements_txt(self.include_extras).to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Display the distribution hashes, if any.
|
// Display the distribution hashes, if any.
|
||||||
let mut has_hashes = false;
|
let mut has_hashes = false;
|
||||||
if self.show_hashes {
|
if self.show_hashes {
|
||||||
for hash in node.hashes() {
|
for hash in &node.hashes {
|
||||||
has_hashes = true;
|
has_hashes = true;
|
||||||
line.push_str(" \\\n");
|
line.push_str(" \\\n");
|
||||||
line.push_str(" --hash=");
|
line.push_str(" --hash=");
|
||||||
|
@ -184,12 +129,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
|
|
||||||
// Include all external sources (e.g., requirements files).
|
// Include all external sources (e.g., requirements files).
|
||||||
let default = BTreeSet::default();
|
let default = BTreeSet::default();
|
||||||
let source = match node {
|
let source = self.sources.get(node.name()).unwrap_or(&default);
|
||||||
Node::Editable(editable) => {
|
|
||||||
self.sources.get_editable(&editable.url).unwrap_or(&default)
|
|
||||||
}
|
|
||||||
Node::Distribution(dist) => self.sources.get(dist.name()).unwrap_or(&default),
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.annotation_style {
|
match self.annotation_style {
|
||||||
AnnotationStyle::Line => match edges.as_slice() {
|
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.,
|
// If enabled, include indexes to indicate which index was used for each package (e.g.,
|
||||||
// `# from https://pypi.org/simple`).
|
// `# from https://pypi.org/simple`).
|
||||||
if self.include_index_annotation {
|
if self.include_index_annotation {
|
||||||
if let Some(index) = node.index() {
|
if let Some(index) = node.dist.index() {
|
||||||
let url = index.redacted();
|
let url = index.redacted();
|
||||||
writeln!(f, "{}", format!(" # from {url}").green())?;
|
writeln!(f, "{}", format!(" # from {url}").green())?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ use pypi_types::{ParsedUrlError, Yanked};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
use crate::editables::Editables;
|
|
||||||
use crate::pins::FilePins;
|
use crate::pins::FilePins;
|
||||||
use crate::preferences::Preferences;
|
use crate::preferences::Preferences;
|
||||||
use crate::pubgrub::{PubGrubDistribution, PubGrubPackageInner};
|
use crate::pubgrub::{PubGrubDistribution, PubGrubPackageInner};
|
||||||
|
@ -34,8 +33,6 @@ use crate::{
|
||||||
pub struct ResolutionGraph {
|
pub struct ResolutionGraph {
|
||||||
/// The underlying graph.
|
/// The underlying graph.
|
||||||
pub(crate) petgraph: petgraph::graph::Graph<AnnotatedDist, Range<Version>, petgraph::Directed>,
|
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.
|
/// Any diagnostics that were encountered while building the graph.
|
||||||
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
||||||
}
|
}
|
||||||
|
@ -50,7 +47,6 @@ impl ResolutionGraph {
|
||||||
distributions: &FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
distributions: &FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
||||||
state: &State<UvDependencyProvider>,
|
state: &State<UvDependencyProvider>,
|
||||||
preferences: &Preferences,
|
preferences: &Preferences,
|
||||||
editables: Editables,
|
|
||||||
) -> anyhow::Result<Self, ResolveError> {
|
) -> anyhow::Result<Self, ResolveError> {
|
||||||
// Collect and validate the extras.
|
// Collect and validate the extras.
|
||||||
let mut extras = FxHashMap::default();
|
let mut extras = FxHashMap::default();
|
||||||
|
@ -102,50 +98,34 @@ impl ResolutionGraph {
|
||||||
marker: None,
|
marker: None,
|
||||||
url: Some(url),
|
url: Some(url),
|
||||||
} => {
|
} => {
|
||||||
if let Some(editable) = editables.get(name) {
|
let dist = PubGrubDistribution::from_url(name, url);
|
||||||
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())?;
|
|
||||||
|
|
||||||
diagnostics.push(ResolutionDiagnostic::MissingExtra {
|
let response = distributions.get(&dist.version_id()).unwrap_or_else(|| {
|
||||||
dist: dist.into(),
|
panic!(
|
||||||
extra: extra.clone(),
|
"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 {
|
} 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(|| {
|
diagnostics.push(ResolutionDiagnostic::MissingExtra {
|
||||||
panic!(
|
dist: dist.into(),
|
||||||
"Every package should have metadata: {:?}",
|
extra: extra.clone(),
|
||||||
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 = 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),
|
url: Some(url),
|
||||||
} => {
|
} => {
|
||||||
// Create the distribution.
|
// 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 dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?;
|
||||||
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()))?;
|
|
||||||
|
|
||||||
// Extract the hashes, preserving those that were already present in the
|
// Extract the hashes, preserving those that were already present in the
|
||||||
// lockfile if necessary.
|
// lockfile if necessary.
|
||||||
let hashes = if let Some(digests) = preferences
|
let hashes = if let Some(digests) = preferences
|
||||||
.match_hashes(name, version)
|
.match_hashes(name, version)
|
||||||
.filter(|digests| !digests.is_empty())
|
.filter(|digests| !digests.is_empty())
|
||||||
{
|
{
|
||||||
digests.to_vec()
|
digests.to_vec()
|
||||||
} else if let Some(metadata_response) =
|
} else if let Some(metadata_response) = distributions.get(&dist.version_id()) {
|
||||||
distributions.get(&dist.version_id())
|
if let MetadataResponse::Found(ref archive) = *metadata_response {
|
||||||
{
|
let mut digests = archive.hashes.clone();
|
||||||
if let MetadataResponse::Found(ref archive) = *metadata_response {
|
digests.sort_unstable();
|
||||||
let mut digests = archive.hashes.clone();
|
digests
|
||||||
digests.sort_unstable();
|
|
||||||
digests
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
}
|
||||||
|
} else {
|
||||||
// Extract the metadata.
|
vec![]
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 {
|
Ok(Self {
|
||||||
petgraph,
|
petgraph,
|
||||||
editables,
|
|
||||||
diagnostics,
|
diagnostics,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -523,13 +487,7 @@ impl ResolutionGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we consider markers from direct dependencies.
|
// Ensure that we consider markers from direct dependencies.
|
||||||
let direct_reqs = manifest.requirements.iter().chain(
|
for direct_req in manifest.apply(manifest.requirements.iter()) {
|
||||||
manifest
|
|
||||||
.editables
|
|
||||||
.iter()
|
|
||||||
.flat_map(|editable| &editable.requirements.dependencies),
|
|
||||||
);
|
|
||||||
for direct_req in manifest.apply(direct_reqs) {
|
|
||||||
let Some(ref marker_tree) = direct_req.marker else {
|
let Some(ref marker_tree) = direct_req.marker else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,14 @@ impl AnnotatedDist {
|
||||||
/// unnamed requirement for relative paths, which can't be represented with PEP 508 (but are
|
/// unnamed requirement for relative paths, which can't be represented with PEP 508 (but are
|
||||||
/// supported in `requirements.txt`).
|
/// supported in `requirements.txt`).
|
||||||
pub(crate) fn to_requirements_txt(&self, include_extras: bool) -> Cow<str> {
|
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 the URL is not _definitively_ an absolute `file://` URL, write it as a relative path.
|
||||||
if self.dist.is_local() {
|
if self.dist.is_local() {
|
||||||
if let VersionOrUrlRef::Url(url) = self.dist.version_or_url() {
|
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 {
|
impl Name for AnnotatedDist {
|
||||||
|
|
|
@ -37,14 +37,13 @@ use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
|
||||||
|
|
||||||
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
use crate::editables::Editables;
|
|
||||||
use crate::error::ResolveError;
|
use crate::error::ResolveError;
|
||||||
use crate::manifest::Manifest;
|
use crate::manifest::Manifest;
|
||||||
use crate::pins::FilePins;
|
use crate::pins::FilePins;
|
||||||
use crate::preferences::Preferences;
|
use crate::preferences::Preferences;
|
||||||
use crate::pubgrub::{
|
use crate::pubgrub::{
|
||||||
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner,
|
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner,
|
||||||
PubGrubPriorities, PubGrubPython, PubGrubRequirement, PubGrubSpecifier,
|
PubGrubPriorities, PubGrubPython, PubGrubSpecifier,
|
||||||
};
|
};
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolution::ResolutionGraph;
|
use crate::resolution::ResolutionGraph;
|
||||||
|
@ -85,7 +84,6 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
|
||||||
overrides: Overrides,
|
overrides: Overrides,
|
||||||
preferences: Preferences,
|
preferences: Preferences,
|
||||||
exclusions: Exclusions,
|
exclusions: Exclusions,
|
||||||
editables: Editables,
|
|
||||||
urls: Urls,
|
urls: Urls,
|
||||||
locals: Locals,
|
locals: Locals,
|
||||||
dependency_mode: DependencyMode,
|
dependency_mode: DependencyMode,
|
||||||
|
@ -192,7 +190,6 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
overrides: manifest.overrides,
|
overrides: manifest.overrides,
|
||||||
preferences: Preferences::from_iter(manifest.preferences, markers),
|
preferences: Preferences::from_iter(manifest.preferences, markers),
|
||||||
exclusions: manifest.exclusions,
|
exclusions: manifest.exclusions,
|
||||||
editables: Editables::from_requirements(manifest.editables),
|
|
||||||
hasher: hasher.clone(),
|
hasher: hasher.clone(),
|
||||||
markers: markers.cloned(),
|
markers: markers.cloned(),
|
||||||
python_requirement: python_requirement.clone(),
|
python_requirement: python_requirement.clone(),
|
||||||
|
@ -343,7 +340,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
self.index.distributions(),
|
self.index.distributions(),
|
||||||
&state.pubgrub,
|
&state.pubgrub,
|
||||||
&self.preferences,
|
&self.preferences,
|
||||||
self.editables.clone(),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
state.next = highest_priority_pkg;
|
state.next = highest_priority_pkg;
|
||||||
|
@ -560,11 +556,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
return Err(ResolveError::UnhashedPackage(name.clone()));
|
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.
|
// Emit a request to fetch the metadata for this distribution.
|
||||||
let dist = Dist::from_url(name.clone(), url.clone())?;
|
let dist = Dist::from_url(name.clone(), url.clone())?;
|
||||||
if self.index.distributions().register(dist.version_id()) {
|
if self.index.distributions().register(dist.version_id()) {
|
||||||
|
@ -649,31 +640,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
url.verbatim
|
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 dist = PubGrubDistribution::from_url(name, url);
|
||||||
let response = self
|
let response = self
|
||||||
.index
|
.index
|
||||||
|
@ -853,7 +819,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
self.markers.as_ref(),
|
self.markers.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut dependencies = match dependencies {
|
let dependencies = match dependencies {
|
||||||
Ok(dependencies) => dependencies,
|
Ok(dependencies) => dependencies,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Ok(Dependencies::Unavailable(
|
return Ok(Dependencies::Unavailable(
|
||||||
|
@ -872,54 +838,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
self.visit_package(package, request_sink)?;
|
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()))
|
Ok(Dependencies::Available(dependencies.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -935,57 +853,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
if self.dependency_mode.is_direct() {
|
if self.dependency_mode.is_direct() {
|
||||||
// If an extra is provided, wait for the metadata to be available, since it's
|
// If an extra is provided, wait for the metadata to be available, since it's
|
||||||
// still required for generating the lock file.
|
// 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.
|
let dist = match url {
|
||||||
self.index
|
Some(url) => PubGrubDistribution::from_url(name, url),
|
||||||
.distributions()
|
None => PubGrubDistribution::from_registry(name, version),
|
||||||
.wait_blocking(&version_id)
|
};
|
||||||
.ok_or(ResolveError::Unregistered)?;
|
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()));
|
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.
|
// Determine the distribution to lookup.
|
||||||
let dist = match url {
|
let dist = match url {
|
||||||
Some(url) => PubGrubDistribution::from_url(name, url),
|
Some(url) => PubGrubDistribution::from_url(name, url),
|
||||||
|
@ -1232,6 +1115,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Some(Response::Dist { dist, metadata }))
|
Ok(Some(Response::Dist { dist, metadata }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1320,6 +1204,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
ResolveError::FetchAndBuild(Box::new(source_dist), err)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Response::Dist { dist, metadata }
|
Response::Dist { dist, metadata }
|
||||||
}
|
}
|
||||||
ResolvedDist::Installed(dist) => {
|
ResolvedDist::Installed(dist) => {
|
||||||
|
|
|
@ -22,36 +22,6 @@ impl Urls {
|
||||||
) -> Result<Self, ResolveError> {
|
) -> Result<Self, ResolveError> {
|
||||||
let mut urls: FxHashMap<PackageName, VerbatimParsedUrl> = FxHashMap::default();
|
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.
|
// Add all direct requirements and constraints. If there are any conflicts, return an error.
|
||||||
for requirement in manifest.requirements(markers, dependencies) {
|
for requirement in manifest.requirements(markers, dependencies) {
|
||||||
match &requirement.source {
|
match &requirement.source {
|
||||||
|
|
|
@ -300,7 +300,6 @@ async fn black_mypy_extensions() -> Result<()> {
|
||||||
Overrides::default(),
|
Overrides::default(),
|
||||||
vec![],
|
vec![],
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
Exclusions::default(),
|
Exclusions::default(),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
@ -341,7 +340,6 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
||||||
Overrides::default(),
|
Overrides::default(),
|
||||||
vec![],
|
vec![],
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
Exclusions::default(),
|
Exclusions::default(),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
@ -382,7 +380,6 @@ async fn black_flake8() -> Result<()> {
|
||||||
Overrides::default(),
|
Overrides::default(),
|
||||||
vec![],
|
vec![],
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
Exclusions::default(),
|
Exclusions::default(),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
@ -480,7 +477,6 @@ async fn black_respect_preference() -> Result<()> {
|
||||||
Version::from_str("23.9.0")?,
|
Version::from_str("23.9.0")?,
|
||||||
)],
|
)],
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
Exclusions::default(),
|
Exclusions::default(),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
@ -521,7 +517,6 @@ async fn black_ignore_preference() -> Result<()> {
|
||||||
Version::from_str("23.9.2")?,
|
Version::from_str("23.9.2")?,
|
||||||
)],
|
)],
|
||||||
None,
|
None,
|
||||||
vec![],
|
|
||||||
Exclusions::default(),
|
Exclusions::default(),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,6 @@ install-wheel-rs = { workspace = true, features = ["clap"], default-features = f
|
||||||
pep508_rs = { workspace = true }
|
pep508_rs = { workspace = true }
|
||||||
platform-tags = { workspace = true }
|
platform-tags = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
requirements-txt = { workspace = true, features = ["http"] }
|
|
||||||
uv-auth = { workspace = true }
|
uv-auth = { workspace = true }
|
||||||
uv-cache = { workspace = true, features = ["clap"] }
|
uv-cache = { workspace = true, features = ["clap"] }
|
||||||
uv-client = { workspace = true }
|
uv-client = { workspace = true }
|
||||||
|
@ -45,7 +44,6 @@ clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
|
||||||
clap_complete_command = { workspace = true }
|
clap_complete_command = { workspace = true }
|
||||||
flate2 = { workspace = true, default-features = false }
|
flate2 = { workspace = true, default-features = false }
|
||||||
fs-err = { workspace = true, features = ["tokio"] }
|
fs-err = { workspace = true, features = ["tokio"] }
|
||||||
indexmap = { workspace = true }
|
|
||||||
indicatif = { workspace = true }
|
indicatif = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy"] }
|
miette = { workspace = true, features = ["fancy"] }
|
||||||
|
|
|
@ -7,21 +7,15 @@ use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anstream::{eprint, AutoStream, StripStream};
|
use anstream::{eprint, AutoStream, StripStream};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tempfile::tempdir_in;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{IndexLocations, SourceAnnotation, SourceAnnotations, Verbatim};
|
||||||
IndexLocations, LocalEditable, LocalEditables, SourceAnnotation, SourceAnnotations, Verbatim,
|
|
||||||
};
|
|
||||||
use distribution_types::{Requirement, Requirements};
|
|
||||||
use install_wheel_rs::linker::LinkMode;
|
use install_wheel_rs::linker::LinkMode;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use requirements_txt::EditableRequirement;
|
|
||||||
use uv_auth::store_credentials_from_url;
|
use uv_auth::store_credentials_from_url;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||||
|
@ -33,7 +27,6 @@ use uv_configuration::{KeyringProviderType, TargetTriple};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_installer::Downloader;
|
|
||||||
use uv_interpreter::{
|
use uv_interpreter::{
|
||||||
find_best_interpreter, find_interpreter, InterpreterRequest, PythonEnvironment, SystemPython,
|
find_best_interpreter, find_interpreter, InterpreterRequest, PythonEnvironment, SystemPython,
|
||||||
VersionRequest,
|
VersionRequest,
|
||||||
|
@ -45,15 +38,15 @@ use uv_requirements::{
|
||||||
RequirementsSource, RequirementsSpecification, SourceTreeResolver,
|
RequirementsSource, RequirementsSpecification, SourceTreeResolver,
|
||||||
};
|
};
|
||||||
use uv_resolver::{
|
use uv_resolver::{
|
||||||
AnnotationStyle, BuiltEditableMetadata, DependencyMode, DisplayResolutionGraph, ExcludeNewer,
|
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex,
|
||||||
Exclusions, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode,
|
InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode,
|
||||||
PythonRequirement, ResolutionMode, Resolver,
|
Resolver,
|
||||||
};
|
};
|
||||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::commands::pip::operations;
|
use crate::commands::pip::operations;
|
||||||
use crate::commands::reporters::{DownloadReporter, ResolverReporter};
|
use crate::commands::reporters::ResolverReporter;
|
||||||
use crate::commands::{elapsed, ExitStatus};
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
|
@ -123,7 +116,6 @@ pub(crate) async fn pip_compile(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
editables,
|
|
||||||
source_trees,
|
source_trees,
|
||||||
extras: used_extras,
|
extras: used_extras,
|
||||||
index_url,
|
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.
|
// Collect constraints and overrides.
|
||||||
let constraints = Constraints::from_requirements(constraints);
|
let constraints = Constraints::from_requirements(constraints);
|
||||||
let overrides = Overrides::from_requirements(overrides);
|
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.
|
// Determine any lookahead requirements.
|
||||||
let lookaheads = match dependency_mode {
|
let lookaheads = match dependency_mode {
|
||||||
DependencyMode::Transitive => {
|
DependencyMode::Transitive => {
|
||||||
|
@ -519,7 +423,6 @@ pub(crate) async fn pip_compile(
|
||||||
&requirements,
|
&requirements,
|
||||||
&constraints,
|
&constraints,
|
||||||
&overrides,
|
&overrides,
|
||||||
&editables,
|
|
||||||
&hasher,
|
&hasher,
|
||||||
&top_level_index,
|
&top_level_index,
|
||||||
DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads),
|
DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads),
|
||||||
|
@ -538,7 +441,6 @@ pub(crate) async fn pip_compile(
|
||||||
overrides,
|
overrides,
|
||||||
preferences,
|
preferences,
|
||||||
project,
|
project,
|
||||||
editables,
|
|
||||||
// Do not consider any installed packages during resolution.
|
// Do not consider any installed packages during resolution.
|
||||||
Exclusions::All,
|
Exclusions::All,
|
||||||
lookaheads,
|
lookaheads,
|
||||||
|
|
|
@ -33,7 +33,6 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
use crate::commands::pip::operations;
|
use crate::commands::pip::operations;
|
||||||
use crate::commands::pip::operations::Modifications;
|
use crate::commands::pip::operations::Modifications;
|
||||||
use crate::commands::{elapsed, ExitStatus};
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Install packages into the current environment.
|
/// Install packages into the current environment.
|
||||||
|
@ -89,7 +88,6 @@ pub(crate) async fn pip_install(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
editables,
|
|
||||||
source_trees,
|
source_trees,
|
||||||
index_url,
|
index_url,
|
||||||
extra_index_urls,
|
extra_index_urls,
|
||||||
|
@ -169,7 +167,7 @@ pub(crate) async fn pip_install(
|
||||||
&& overrides.is_empty()
|
&& overrides.is_empty()
|
||||||
&& uv_lock.is_none()
|
&& 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.
|
// If the requirements are already satisfied, we're done.
|
||||||
SatisfiesResult::Fresh {
|
SatisfiesResult::Fresh {
|
||||||
recursive_requirements,
|
recursive_requirements,
|
||||||
|
@ -183,13 +181,7 @@ pub(crate) async fn pip_install(
|
||||||
debug!("Requirement satisfied: {requirement}");
|
debug!("Requirement satisfied: {requirement}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !editables.is_empty() {
|
let num_requirements = requirements.len();
|
||||||
debug!(
|
|
||||||
"All editables satisfied: {}",
|
|
||||||
editables.iter().map(ToString::to_string).join(" | ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let num_requirements = requirements.len() + editables.len();
|
|
||||||
let s = if num_requirements == 1 { "" } else { "s" };
|
let s = if num_requirements == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
|
@ -326,25 +318,6 @@ pub(crate) async fn pip_install(
|
||||||
)
|
)
|
||||||
.with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build());
|
.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.
|
// Resolve the requirements.
|
||||||
let resolution = if let Some(ref root) = uv_lock {
|
let resolution = if let Some(ref root) = uv_lock {
|
||||||
let root = PackageName::new(root.to_string())?;
|
let root = PackageName::new(root.to_string())?;
|
||||||
|
@ -367,7 +340,6 @@ pub(crate) async fn pip_install(
|
||||||
source_trees,
|
source_trees,
|
||||||
project,
|
project,
|
||||||
extras,
|
extras,
|
||||||
&editables,
|
|
||||||
site_packages.clone(),
|
site_packages.clone(),
|
||||||
&hasher,
|
&hasher,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
@ -426,7 +398,6 @@ pub(crate) async fn pip_install(
|
||||||
// Sync the environment.
|
// Sync the environment.
|
||||||
operations::install(
|
operations::install(
|
||||||
&resolution,
|
&resolution,
|
||||||
&editables,
|
|
||||||
site_packages,
|
site_packages,
|
||||||
Modifications::Sufficient,
|
Modifications::Sufficient,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
|
|
@ -26,7 +26,7 @@ use uv_configuration::{
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
use uv_fs::Simplified;
|
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_interpreter::{Interpreter, PythonEnvironment};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_requirements::{
|
use uv_requirements::{
|
||||||
|
@ -43,7 +43,6 @@ use uv_warnings::warn_user;
|
||||||
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
||||||
use crate::commands::DryRunEvent;
|
use crate::commands::DryRunEvent;
|
||||||
use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind};
|
use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind};
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Consolidate the requirements for an installation.
|
/// Consolidate the requirements for an installation.
|
||||||
|
@ -110,7 +109,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||||
source_trees: Vec<PathBuf>,
|
source_trees: Vec<PathBuf>,
|
||||||
project: Option<PackageName>,
|
project: Option<PackageName>,
|
||||||
extras: &ExtrasSpecification,
|
extras: &ExtrasSpecification,
|
||||||
editables: &ResolvedEditables,
|
|
||||||
installed_packages: InstalledPackages,
|
installed_packages: InstalledPackages,
|
||||||
hasher: &HashStrategy,
|
hasher: &HashStrategy,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
|
@ -176,9 +174,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||||
let overrides = Overrides::from_requirements(overrides);
|
let overrides = Overrides::from_requirements(overrides);
|
||||||
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
||||||
|
|
||||||
// Map the editables to their metadata.
|
|
||||||
let editables = editables.as_metadata();
|
|
||||||
|
|
||||||
// Determine any lookahead requirements.
|
// Determine any lookahead requirements.
|
||||||
let lookaheads = match options.dependency_mode {
|
let lookaheads = match options.dependency_mode {
|
||||||
DependencyMode::Transitive => {
|
DependencyMode::Transitive => {
|
||||||
|
@ -186,7 +181,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||||
&requirements,
|
&requirements,
|
||||||
&constraints,
|
&constraints,
|
||||||
&overrides,
|
&overrides,
|
||||||
&editables,
|
|
||||||
hasher,
|
hasher,
|
||||||
index,
|
index,
|
||||||
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
|
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
|
||||||
|
@ -215,7 +209,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||||
overrides,
|
overrides,
|
||||||
preferences,
|
preferences,
|
||||||
project,
|
project,
|
||||||
editables,
|
|
||||||
exclusions,
|
exclusions,
|
||||||
lookaheads,
|
lookaheads,
|
||||||
);
|
);
|
||||||
|
@ -282,7 +275,6 @@ pub(crate) enum Modifications {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn install(
|
pub(crate) async fn install(
|
||||||
resolution: &Resolution,
|
resolution: &Resolution,
|
||||||
editables: &[ResolvedEditable],
|
|
||||||
site_packages: SitePackages,
|
site_packages: SitePackages,
|
||||||
modifications: Modifications,
|
modifications: Modifications,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
|
@ -303,26 +295,12 @@ pub(crate) async fn install(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
// Extract the requirements from the resolution, filtering out any editables that were already
|
// Extract the requirements from the resolution.
|
||||||
// required. If a package is already installed as editable, it may appear in the resolution
|
let requirements = resolution.requirements().collect::<Vec<_>>();
|
||||||
// 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<_>>();
|
|
||||||
|
|
||||||
// Partition into those that should be linked from the cache (`local`), those that need to be
|
// 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`).
|
// downloaded (`remote`), and those that should be removed (`extraneous`).
|
||||||
let plan = Planner::with_requirements(&requirements)
|
let plan = Planner::new(&requirements)
|
||||||
.with_editable_requirements(editables)
|
|
||||||
.build(
|
.build(
|
||||||
site_packages,
|
site_packages,
|
||||||
reinstall,
|
reinstall,
|
||||||
|
|
|
@ -31,7 +31,6 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
use crate::commands::pip::operations;
|
use crate::commands::pip::operations;
|
||||||
use crate::commands::pip::operations::Modifications;
|
use crate::commands::pip::operations::Modifications;
|
||||||
use crate::commands::ExitStatus;
|
use crate::commands::ExitStatus;
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Install a set of locked requirements into the current Python environment.
|
/// Install a set of locked requirements into the current Python environment.
|
||||||
|
@ -86,7 +85,6 @@ pub(crate) async fn pip_sync(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
editables,
|
|
||||||
source_trees,
|
source_trees,
|
||||||
index_url,
|
index_url,
|
||||||
extra_index_urls,
|
extra_index_urls,
|
||||||
|
@ -107,7 +105,7 @@ pub(crate) async fn pip_sync(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Validate that the requirements are non-empty.
|
// 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 {
|
if num_requirements == 0 {
|
||||||
writeln!(printer.stderr(), "No requirements found")?;
|
writeln!(printer.stderr(), "No requirements found")?;
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
|
@ -277,25 +275,6 @@ pub(crate) async fn pip_sync(
|
||||||
// Determine the set of installed packages.
|
// Determine the set of installed packages.
|
||||||
let site_packages = SitePackages::from_executable(&venv)?;
|
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()
|
let options = OptionsBuilder::new()
|
||||||
.resolution_mode(resolution_mode)
|
.resolution_mode(resolution_mode)
|
||||||
.prerelease_mode(prerelease_mode)
|
.prerelease_mode(prerelease_mode)
|
||||||
|
@ -311,7 +290,6 @@ pub(crate) async fn pip_sync(
|
||||||
source_trees,
|
source_trees,
|
||||||
project,
|
project,
|
||||||
&extras,
|
&extras,
|
||||||
&editables,
|
|
||||||
site_packages.clone(),
|
site_packages.clone(),
|
||||||
&hasher,
|
&hasher,
|
||||||
reinstall,
|
reinstall,
|
||||||
|
@ -369,7 +347,6 @@ pub(crate) async fn pip_sync(
|
||||||
// Sync the environment.
|
// Sync the environment.
|
||||||
operations::install(
|
operations::install(
|
||||||
&resolution,
|
&resolution,
|
||||||
&editables,
|
|
||||||
site_packages,
|
site_packages,
|
||||||
Modifications::Exact,
|
Modifications::Exact,
|
||||||
reinstall,
|
reinstall,
|
||||||
|
|
|
@ -16,7 +16,6 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::commands::{pip, project, ExitStatus};
|
use crate::commands::{pip, project, ExitStatus};
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Resolve the project requirements into a lockfile.
|
/// Resolve the project requirements into a lockfile.
|
||||||
|
@ -104,26 +103,6 @@ pub(crate) async fn lock(
|
||||||
concurrency,
|
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.
|
// Resolve the requirements.
|
||||||
let resolution = pip::operations::resolve(
|
let resolution = pip::operations::resolve(
|
||||||
spec.requirements,
|
spec.requirements,
|
||||||
|
@ -132,7 +111,6 @@ pub(crate) async fn lock(
|
||||||
spec.source_trees,
|
spec.source_trees,
|
||||||
spec.project,
|
spec.project,
|
||||||
&extras,
|
&extras,
|
||||||
&editables,
|
|
||||||
EmptyInstalledPackages,
|
EmptyInstalledPackages,
|
||||||
&hasher,
|
&hasher,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
|
|
@ -24,7 +24,6 @@ use uv_requirements::{
|
||||||
use uv_resolver::{FlatIndex, InMemoryIndex, Options};
|
use uv_resolver::{FlatIndex, InMemoryIndex, Options};
|
||||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
|
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
pub(crate) mod lock;
|
pub(crate) mod lock;
|
||||||
|
@ -118,7 +117,7 @@ pub(crate) async fn update_environment(
|
||||||
// Check if the current environment satisfies the requirements
|
// Check if the current environment satisfies the requirements
|
||||||
let site_packages = SitePackages::from_executable(&venv)?;
|
let site_packages = SitePackages::from_executable(&venv)?;
|
||||||
if spec.source_trees.is_empty() {
|
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.
|
// If the requirements are already satisfied, we're done.
|
||||||
SatisfiesResult::Fresh {
|
SatisfiesResult::Fresh {
|
||||||
recursive_requirements,
|
recursive_requirements,
|
||||||
|
@ -131,12 +130,6 @@ pub(crate) async fn update_environment(
|
||||||
.sorted()
|
.sorted()
|
||||||
.join(" | ")
|
.join(" | ")
|
||||||
);
|
);
|
||||||
if !spec.editables.is_empty() {
|
|
||||||
debug!(
|
|
||||||
"All editables satisfied: {}",
|
|
||||||
spec.editables.iter().map(ToString::to_string).join(", ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Ok(venv);
|
return Ok(venv);
|
||||||
}
|
}
|
||||||
SatisfiesResult::Unsatisfied(requirement) => {
|
SatisfiesResult::Unsatisfied(requirement) => {
|
||||||
|
@ -196,26 +189,6 @@ pub(crate) async fn update_environment(
|
||||||
concurrency,
|
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.
|
// Resolve the requirements.
|
||||||
let resolution = match pip::operations::resolve(
|
let resolution = match pip::operations::resolve(
|
||||||
spec.requirements,
|
spec.requirements,
|
||||||
|
@ -224,7 +197,6 @@ pub(crate) async fn update_environment(
|
||||||
spec.source_trees,
|
spec.source_trees,
|
||||||
spec.project,
|
spec.project,
|
||||||
&extras,
|
&extras,
|
||||||
&editables,
|
|
||||||
site_packages.clone(),
|
site_packages.clone(),
|
||||||
&hasher,
|
&hasher,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
@ -275,7 +247,6 @@ pub(crate) async fn update_environment(
|
||||||
// Sync the environment.
|
// Sync the environment.
|
||||||
pip::operations::install(
|
pip::operations::install(
|
||||||
&resolution,
|
&resolution,
|
||||||
&editables,
|
|
||||||
site_packages,
|
site_packages,
|
||||||
pip::operations::Modifications::Sufficient,
|
pip::operations::Modifications::Sufficient,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
|
|
@ -16,7 +16,6 @@ use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::commands::pip::operations::Modifications;
|
use crate::commands::pip::operations::Modifications;
|
||||||
use crate::commands::{pip, project, ExitStatus};
|
use crate::commands::{pip, project, ExitStatus};
|
||||||
use crate::editables::ResolvedEditables;
|
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Sync the project environment.
|
/// Sync the project environment.
|
||||||
|
@ -90,28 +89,9 @@ pub(crate) async fn sync(
|
||||||
|
|
||||||
let site_packages = SitePackages::from_executable(&venv)?;
|
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.
|
// Sync the environment.
|
||||||
pip::operations::install(
|
pip::operations::install(
|
||||||
&resolution,
|
&resolution,
|
||||||
&editables,
|
|
||||||
site_packages,
|
site_packages,
|
||||||
Modifications::Sufficient,
|
Modifications::Sufficient,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
|
|
|
@ -7,8 +7,7 @@ use rustc_hash::FxHashMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, CachedDist, DistributionMetadata, LocalEditable, Name, SourceDist,
|
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
|
||||||
VersionOrUrlRef,
|
|
||||||
};
|
};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ impl BarState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressReporter {
|
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 mut state = self.state.lock().unwrap();
|
||||||
let id = state.id();
|
let id = state.id();
|
||||||
|
|
||||||
|
@ -53,21 +52,29 @@ impl ProgressReporter {
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
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.headers += 1;
|
||||||
state.bars.insert(id, progress);
|
state.bars.insert(id, progress);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_any_build_complete(&self, color_string: &str, id: usize) {
|
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||||
let progress = {
|
let progress = {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
state.headers -= 1;
|
state.headers -= 1;
|
||||||
state.bars.remove(&id).unwrap()
|
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 {
|
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 {
|
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) {
|
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||||
self.reporter
|
self.reporter.on_build_complete(source, id);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
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 {
|
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) {
|
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||||
self.reporter
|
self.reporter.on_build_complete(source, id);
|
||||||
.on_any_build_complete(&source.to_color_string(), id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
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 {
|
impl uv_distribution::Reporter for ResolverReporter {
|
||||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
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) {
|
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||||
self.reporter
|
self.reporter.on_build_complete(source, id);
|
||||||
.on_any_build_complete(&source.to_color_string(), id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
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 cli;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod compat;
|
mod compat;
|
||||||
mod editables;
|
|
||||||
mod logging;
|
mod logging;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
|
@ -3377,7 +3377,6 @@ fn compile_editable() -> Result<()> {
|
||||||
# via aiohttp
|
# via aiohttp
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 13 packages in [TIME]
|
Resolved 13 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -3428,7 +3427,6 @@ fn deduplicate_editable() -> Result<()> {
|
||||||
# via aiohttp
|
# via aiohttp
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 9 packages in [TIME]
|
Resolved 9 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -3507,7 +3505,6 @@ fn compile_editable_url_requirement() -> Result<()> {
|
||||||
# via hatchling-editable
|
# via hatchling-editable
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -4009,7 +4006,6 @@ fn generate_hashes_editable() -> Result<()> {
|
||||||
# via anyio
|
# via anyio
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -4219,7 +4215,6 @@ coverage = ["example[test]", "extras>=0.0.1,<=0.0.2"]
|
||||||
# via extras
|
# via extras
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -4436,8 +4431,7 @@ fn missing_editable_requirement() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Failed to build editables
|
error: Distribution not found at: file://[TEMP_DIR]/foo/anyio-3.7.0.tar.gz
|
||||||
Caused by: Source distribution not found at: [TEMP_DIR]/foo/anyio-3.7.0.tar.gz
|
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -5610,7 +5604,6 @@ dependencies = [
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -5637,7 +5630,6 @@ fn editable_invalid_extra() -> Result<()> {
|
||||||
# via -r [TEMP_DIR]/requirements.in
|
# via -r [TEMP_DIR]/requirements.in
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
warning: The package `black @ file://[WORKSPACE]/scripts/packages/black_editable` does not have an extra named `empty`.
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override a regular package with an editable.
|
/// Override a regular package with an editable. This should resolve to the editable package.
|
||||||
///
|
|
||||||
/// At present, this incorrectly resolves to the regular package.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn editable_override() -> Result<()> {
|
fn editable_override() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
@ -5895,41 +5885,32 @@ fn editable_override() -> Result<()> {
|
||||||
|
|
||||||
// Add an editable override.
|
// Add an editable override.
|
||||||
let overrides_txt = context.temp_dir.child("overrides.txt");
|
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()
|
uv_snapshot!(context.filters(), context.compile()
|
||||||
.arg("requirements.in")
|
.arg(requirements_in.path())
|
||||||
.arg("--override")
|
.arg("--override")
|
||||||
.arg("overrides.txt"), @r###"
|
.arg(overrides_txt.path())
|
||||||
|
.current_dir(current_dir()?), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# 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
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --override [TEMP_DIR]/overrides.txt
|
||||||
black==24.3.0
|
-e ../../scripts/packages/black_editable
|
||||||
# via -r requirements.in
|
# via
|
||||||
click==8.1.7
|
# --override [TEMP_DIR]/overrides.txt
|
||||||
# via black
|
# -r [TEMP_DIR]/requirements.in
|
||||||
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 -----
|
----- stderr -----
|
||||||
Resolved 6 packages in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override an editable with a regular package.
|
/// Override an editable with a regular package. This should resolve to the regular package.
|
||||||
///
|
|
||||||
/// At present, this incorrectly resolves to the editable.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn override_editable() -> Result<()> {
|
fn override_editable() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
@ -5949,12 +5930,23 @@ fn override_editable() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# 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
|
# 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
|
black==23.10.1
|
||||||
# via -r [TEMP_DIR]/requirements.in
|
# 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 -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
Resolved 6 packages in [TIME]
|
||||||
Resolved 1 package in [TIME]
|
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -6306,7 +6298,6 @@ fn editable_direct_dependency() -> Result<()> {
|
||||||
# via setuptools-editable
|
# via setuptools-editable
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -6498,11 +6489,13 @@ requires-python = "<=3.8"
|
||||||
uv_snapshot!(context.filters(), context.compile()
|
uv_snapshot!(context.filters(), context.compile()
|
||||||
.arg("requirements.in"), @r###"
|
.arg("requirements.in"), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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("requirements.in")
|
||||||
.arg("--python-version=3.11"), @r###"
|
.arg("--python-version=3.11"), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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
|
# via anyio
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -6651,7 +6645,6 @@ dev = ["setuptools"]
|
||||||
# via example
|
# via example
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -6810,7 +6803,6 @@ fn compile_root_uri_editable() -> Result<()> {
|
||||||
# via root-editable
|
# via root-editable
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -7943,7 +7935,7 @@ requires-python = ">3.8"
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.6
|
idna==3.6
|
||||||
# via anyio
|
# via anyio
|
||||||
lib @ file://[TEMP_DIR]/lib
|
lib @ file://[TEMP_DIR]/lib/
|
||||||
# via example
|
# via example
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
@ -8055,7 +8047,6 @@ requires-python = ">3.8"
|
||||||
# via flask
|
# via flask
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 7 packages in [TIME]
|
Resolved 7 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
@ -9260,7 +9251,6 @@ fn tool_uv_sources() -> Result<()> {
|
||||||
# via project (some_dir/pyproject.toml)
|
# via project (some_dir/pyproject.toml)
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
|
@ -848,9 +848,8 @@ fn install_editable() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
@ -928,8 +927,8 @@ fn install_editable_and_registry() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- black==24.3.0
|
- black==24.3.0
|
||||||
|
@ -993,8 +992,8 @@ fn install_editable_no_binary() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
||||||
"###
|
"###
|
||||||
|
@ -1019,8 +1018,8 @@ fn install_editable_compatible_constraint() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
||||||
"###
|
"###
|
||||||
|
@ -1047,9 +1046,8 @@ fn install_editable_incompatible_constraint_version() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
× No solution found when resolving dependencies:
|
× 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
error: Requirements contain conflicting URLs for package `black`:
|
error: Requirements contain conflicting URLs for package `black`:
|
||||||
- [WORKSPACE]/scripts/packages/black_editable
|
- [WORKSPACE]/scripts/packages/black_editable
|
||||||
- https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl
|
- 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ anyio==4.3.0+foo (from file://[WORKSPACE]/scripts/packages/anyio_local)
|
+ 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 context = TestContext::new("3.12");
|
||||||
let root_path = context
|
let root_path = context
|
||||||
.workspace_root
|
.workspace_root
|
||||||
.join("scripts/packages/dependent_editables");
|
.join("scripts/packages/dependent_locals");
|
||||||
|
|
||||||
// Install the editable package.
|
// Install the editable package.
|
||||||
uv_snapshot!(context.filters(), context.install()
|
uv_snapshot!(context.filters(), context.install()
|
||||||
.arg("--only-binary")
|
.arg("--only-binary")
|
||||||
.arg(":all:")
|
.arg(":all:")
|
||||||
.arg("-e")
|
.arg("-e")
|
||||||
.arg(root_path.join("first_editable"))
|
.arg(root_path.join("first_local"))
|
||||||
.arg("-e")
|
.arg("-e")
|
||||||
.arg(root_path.join("second_editable")), @r###"
|
.arg(root_path.join("second_local")), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
|
Downloaded 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages 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)
|
||||||
+ 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)
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1779,9 +1776,8 @@ fn only_binary_editable_setup_py() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
Downloaded 7 packages in [TIME]
|
Downloaded 8 packages in [TIME]
|
||||||
Installed 8 packages in [TIME]
|
Installed 8 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ certifi==2024.2.2
|
+ certifi==2024.2.2
|
||||||
|
@ -1949,8 +1945,8 @@ fn no_deps_editable() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
||||||
"###
|
"###
|
||||||
|
@ -2408,9 +2404,8 @@ fn config_settings() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ iniconfig==2.0.0
|
+ iniconfig==2.0.0
|
||||||
+ setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
|
+ setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
|
||||||
|
@ -2437,9 +2432,8 @@ fn config_settings() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ iniconfig==2.0.0
|
+ iniconfig==2.0.0
|
||||||
+ setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
|
+ setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
|
||||||
|
@ -2575,9 +2569,8 @@ requires-python = ">=3.8"
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.0.0
|
+ anyio==4.0.0
|
||||||
+ example==0.0.0 (from file://[TEMP_DIR]/editable)
|
+ example==0.0.0 (from file://[TEMP_DIR]/editable)
|
||||||
|
@ -2620,9 +2613,8 @@ requires-python = ">=3.8"
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Uninstalled 2 packages in [TIME]
|
Uninstalled 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
- anyio==4.0.0
|
- anyio==4.0.0
|
||||||
|
@ -2667,9 +2659,8 @@ dependencies = {file = ["requirements.txt"]}
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.0.0
|
+ anyio==4.0.0
|
||||||
+ example==0.1.0 (from file://[TEMP_DIR]/editable)
|
+ example==0.1.0 (from file://[TEMP_DIR]/editable)
|
||||||
|
@ -2687,8 +2678,8 @@ dependencies = {file = ["requirements.txt"]}
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- example==0.1.0 (from file://[TEMP_DIR]/editable)
|
- example==0.1.0 (from file://[TEMP_DIR]/editable)
|
||||||
|
@ -2708,9 +2699,8 @@ dependencies = {file = ["requirements.txt"]}
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Uninstalled 2 packages in [TIME]
|
Uninstalled 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
- anyio==4.0.0
|
- anyio==4.0.0
|
||||||
|
@ -2837,9 +2827,8 @@ requires-python = ">=3.11,<3.13"
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.0.0
|
+ anyio==4.0.0
|
||||||
+ example==0.1.0 (from file://[TEMP_DIR]/editable)
|
+ example==0.1.0 (from file://[TEMP_DIR]/editable)
|
||||||
|
@ -2875,11 +2864,13 @@ requires-python = "<=3.8"
|
||||||
.arg("--editable")
|
.arg("--editable")
|
||||||
.arg(editable_dir.path()), @r###"
|
.arg(editable_dir.path()), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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 context = TestContext::new("3.12");
|
||||||
let root_path = context
|
let root_path = context
|
||||||
.workspace_root
|
.workspace_root
|
||||||
.join("scripts/packages/dependent_editables");
|
.join("scripts/packages/dependent_locals");
|
||||||
|
|
||||||
// Install the first editable
|
// Install the first editable
|
||||||
uv_snapshot!(context.filters(), context.install()
|
uv_snapshot!(context.filters(), context.install()
|
||||||
.arg(root_path.join("first_editable")), @r###"
|
.arg("-e")
|
||||||
|
.arg(root_path.join("first_local")), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -3902,14 +3894,15 @@ fn already_installed_dependent_editable() {
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 1 package in [TIME]
|
||||||
Installed 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
|
// Install the second editable which depends on the first editable
|
||||||
// The already installed first editable package should satisfy the requirement
|
// The already installed first editable package should satisfy the requirement
|
||||||
uv_snapshot!(context.filters(), context.install()
|
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
|
// Disable the index to guard this test against dependency confusion attacks
|
||||||
.arg("--no-index")
|
.arg("--no-index")
|
||||||
.arg("--find-links")
|
.arg("--find-links")
|
||||||
|
@ -3922,14 +3915,15 @@ fn already_installed_dependent_editable() {
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 1 package in [TIME]
|
||||||
Installed 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
|
// Request install of the first editable by full path again
|
||||||
// We should audit the installed package
|
// We should audit the installed package
|
||||||
uv_snapshot!(context.filters(), context.install()
|
uv_snapshot!(context.filters(), context.install()
|
||||||
.arg(root_path.join("first_editable")), @r###"
|
.arg("-e")
|
||||||
|
.arg(root_path.join("first_local")), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -3940,11 +3934,12 @@ fn already_installed_dependent_editable() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Request reinstallation of the first package during install of the second
|
// 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()
|
uv_snapshot!(context.filters(), context.install()
|
||||||
.arg(root_path.join("second_editable"))
|
.arg("-e")
|
||||||
|
.arg(root_path.join("second_local"))
|
||||||
.arg("--reinstall-package")
|
.arg("--reinstall-package")
|
||||||
.arg("first-editable")
|
.arg("first-local")
|
||||||
// Disable the index to guard this test against dependency confusion attacks
|
// Disable the index to guard this test against dependency confusion attacks
|
||||||
.arg("--no-index")
|
.arg("--no-index")
|
||||||
.arg("--find-links")
|
.arg("--find-links")
|
||||||
|
@ -3955,27 +3950,29 @@ fn already_installed_dependent_editable() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× 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.
|
╰─▶ 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-editable==0.0.1 is available and you require second-editable, we can conclude that the requirements are unsatisfiable.
|
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
|
// Request reinstallation of the first package
|
||||||
// We include it in the install command with a full path so we should succeed
|
// We include it in the install command with a full path so we should succeed
|
||||||
uv_snapshot!(context.filters(), context.install()
|
uv_snapshot!(context.filters(), context.install()
|
||||||
.arg(root_path.join("first_editable"))
|
.arg("-e")
|
||||||
|
.arg(root_path.join("first_local"))
|
||||||
.arg("--reinstall-package")
|
.arg("--reinstall-package")
|
||||||
.arg("first-editable"), @r###"
|
.arg("first-local"), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 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)
|
||||||
+ 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)
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4070,6 +4067,7 @@ fn already_installed_local_path_dependent() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- 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)
|
||||||
|
@ -4198,6 +4196,7 @@ fn already_installed_local_version_of_remote_package() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- anyio==4.3.0+foo (from file://[WORKSPACE]/scripts/packages/anyio_local)
|
- 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 -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- anyio==4.3.0
|
- anyio==4.3.0
|
||||||
|
@ -4688,8 +4688,7 @@ fn require_hashes_editable() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: file://[WORKSPACE]/scripts/packages/black_editable[d]
|
||||||
error: In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `aiohttp`
|
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4953,9 +4952,8 @@ fn tool_uv_sources() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 9 packages in [TIME]
|
Resolved 9 packages in [TIME]
|
||||||
Downloaded 8 packages in [TIME]
|
Downloaded 9 packages in [TIME]
|
||||||
Installed 9 packages in [TIME]
|
Installed 9 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ boltons==24.0.1.dev0 (from git+https://github.com/mahmoud/boltons@57fbaa9b673ed85b32458b31baeeae230520e4a0)
|
+ boltons==24.0.1.dev0 (from git+https://github.com/mahmoud/boltons@57fbaa9b673ed85b32458b31baeeae230520e4a0)
|
||||||
|
|
|
@ -165,9 +165,8 @@ fn list_editable() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
@ -218,9 +217,8 @@ fn list_editable_only() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
@ -309,9 +307,8 @@ fn list_exclude() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
@ -413,9 +410,8 @@ fn list_format_json() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
@ -520,9 +516,8 @@ fn list_format_freeze() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
|
|
@ -2198,9 +2198,8 @@ fn sync_editable() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
Downloaded 2 packages in [TIME]
|
Downloaded 3 packages in [TIME]
|
||||||
Installed 3 packages in [TIME]
|
Installed 3 packages in [TIME]
|
||||||
+ boltons==23.1.1
|
+ boltons==23.1.1
|
||||||
+ numpy==1.26.2
|
+ numpy==1.26.2
|
||||||
|
@ -2218,8 +2217,8 @@ fn sync_editable() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable)
|
- poetry-editable==0.1.0 (from file://[TEMP_DIR]/poetry_editable)
|
||||||
|
@ -2329,8 +2328,8 @@ fn sync_editable_and_registry() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- black==24.1.0
|
- black==24.1.0
|
||||||
|
@ -2416,8 +2415,8 @@ fn sync_editable_and_local() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
|
+ black==0.1.0 (from file://[TEMP_DIR]/black_editable)
|
||||||
"###
|
"###
|
||||||
|
@ -2460,8 +2459,8 @@ fn sync_editable_and_local() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- black==0.1.0 (from file://[TEMP_DIR]/black_editable)
|
- black==0.1.0 (from file://[TEMP_DIR]/black_editable)
|
||||||
|
@ -3099,8 +3098,8 @@ requires-python = ">=3.8"
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ example==0.0.0 (from file://[TEMP_DIR]/editable)
|
+ example==0.0.0 (from file://[TEMP_DIR]/editable)
|
||||||
"###
|
"###
|
||||||
|
@ -3139,8 +3138,8 @@ requires-python = ">=3.8"
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
Uninstalled 1 package in [TIME]
|
Uninstalled 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
- example==0.0.0 (from file://[TEMP_DIR]/editable)
|
- 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)
|
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
|
||||||
.arg("requirements.in"), @r###"
|
.arg("requirements.in"), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We allow `--require-hashes` for editables, as long as no dependencies are included.
|
/// We disallow `--require-hashes` for editables.
|
||||||
#[test]
|
#[test]
|
||||||
fn require_hashes_editable() -> Result<()> {
|
fn require_hashes_editable() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
@ -4224,15 +4225,12 @@ fn require_hashes_editable() -> Result<()> {
|
||||||
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
|
uv_snapshot!(context.filters(), sync_without_exclude_newer(&context)
|
||||||
.arg(requirements_txt.path())
|
.arg(requirements_txt.path())
|
||||||
.arg("--require-hashes"), @r###"
|
.arg("--require-hashes"), @r###"
|
||||||
success: true
|
success: false
|
||||||
exit_code: 0
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
error: In `--require-hashes` mode, all requirement must have a hash, but none were provided for: file://[WORKSPACE]/scripts/packages/black_editable[d]
|
||||||
Resolved 1 package in [TIME]
|
|
||||||
Installed 1 package in [TIME]
|
|
||||||
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
|
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -59,9 +59,8 @@ fn test_albatross_in_examples_bird_feeder() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-in-example/examples/bird-feeder)
|
+ 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-in-example)
|
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-in-example)
|
||||||
+ tqdm==4.66.2
|
+ tqdm==4.66.2
|
||||||
|
@ -129,9 +127,8 @@ fn test_albatross_just_project() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-just-project)
|
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-just-project)
|
||||||
+ tqdm==4.66.2
|
+ tqdm==4.66.2
|
||||||
|
@ -166,9 +163,8 @@ fn test_albatross_project_in_excluded() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 1 editable in [TIME]
|
|
||||||
Resolved 4 packages in [TIME]
|
Resolved 4 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 4 packages in [TIME]
|
||||||
Installed 4 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-project-in-excluded/excluded/bird-feeder)
|
+ 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 3 editables in [TIME]
|
|
||||||
Resolved 7 packages in [TIME]
|
Resolved 7 packages in [TIME]
|
||||||
Downloaded 4 packages in [TIME]
|
Downloaded 7 packages in [TIME]
|
||||||
Installed 7 packages in [TIME]
|
Installed 7 packages in [TIME]
|
||||||
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace)
|
+ albatross==0.1.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace)
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
|
@ -244,9 +239,8 @@ fn test_albatross_root_workspace_bird_feeder() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 5 packages in [TIME]
|
||||||
Installed 5 packages in [TIME]
|
Installed 5 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace/packages/bird-feeder)
|
+ 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 5 packages in [TIME]
|
||||||
Installed 5 packages in [TIME]
|
Installed 5 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-root-workspace/packages/bird-feeder)
|
+ 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Built 2 editables in [TIME]
|
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Downloaded 3 packages in [TIME]
|
Downloaded 5 packages in [TIME]
|
||||||
Installed 5 packages in [TIME]
|
Installed 5 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ bird-feeder==1.0.0 (from file://[WORKSPACE]/scripts/workspaces/albatross-virtual-workspace/packages/bird-feeder)
|
+ 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