mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 20:31:12 +00:00
Allow prereleases, locals, and URLs in non-editable path requirements (#2671)
## Summary This PR enables the resolver to "accept" URLs, prereleases, and local version specifiers for direct dependencies of path dependencies. As a result, `uv pip install .` and `uv pip install -e .` now behave identically, in that neither has a restriction on URL dependencies and the like. Closes https://github.com/astral-sh/uv/issues/2643. Closes https://github.com/astral-sh/uv/issues/1853.
This commit is contained in:
parent
4b69ad4281
commit
cf30932831
17 changed files with 484 additions and 77 deletions
|
|
@ -425,6 +425,14 @@ impl SourceDist {
|
|||
dist => dist,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the source distribution, if if it's a local distribution.
|
||||
pub fn as_path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Path(dist) => Some(&dist.path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Name for RegistryBuiltDist {
|
||||
|
|
|
|||
35
crates/distribution-types/src/requirements.rs
Normal file
35
crates/distribution-types/src/requirements.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use pep508_rs::Requirement;
|
||||
use uv_normalize::ExtraName;
|
||||
|
||||
/// A set of requirements as requested by a parent requirement.
|
||||
///
|
||||
/// For example, given `flask[dotenv]`, the `RequestedRequirements` would include the `dotenv`
|
||||
/// extra, along with all of the requirements that are included in the `flask` distribution
|
||||
/// including their unevaluated markers.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestedRequirements {
|
||||
/// The set of extras included on the originating requirement.
|
||||
extras: Vec<ExtraName>,
|
||||
/// The set of requirements that were requested by the originating requirement.
|
||||
requirements: Vec<Requirement>,
|
||||
}
|
||||
|
||||
impl RequestedRequirements {
|
||||
/// Instantiate a [`RequestedRequirements`] with the given `extras` and `requirements`.
|
||||
pub fn new(extras: Vec<ExtraName>, requirements: Vec<Requirement>) -> Self {
|
||||
Self {
|
||||
extras,
|
||||
requirements,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the extras that were included on the originating requirement.
|
||||
pub fn extras(&self) -> &[ExtraName] {
|
||||
&self.extras
|
||||
}
|
||||
|
||||
/// Return the requirements that were included on the originating requirement.
|
||||
pub fn requirements(&self) -> &[Requirement] {
|
||||
&self.requirements
|
||||
}
|
||||
}
|
||||
|
|
@ -385,6 +385,11 @@ impl Scheme {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the scheme is a file scheme.
|
||||
pub fn is_file(self) -> bool {
|
||||
matches!(self, Self::File)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Scheme {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
pub use crate::lookahead::*;
|
||||
pub use crate::resolver::*;
|
||||
pub use crate::source_tree::*;
|
||||
pub use crate::sources::*;
|
||||
pub use crate::specification::*;
|
||||
|
||||
mod confirm;
|
||||
mod lookahead;
|
||||
mod pyproject;
|
||||
mod resolver;
|
||||
mod source_tree;
|
||||
|
|
|
|||
104
crates/uv-requirements/src/lookahead.rs
Normal file
104
crates/uv-requirements/src/lookahead.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
|
||||
use distribution_types::{BuildableSource, Dist};
|
||||
use pep508_rs::{Requirement, VersionOrUrl};
|
||||
use uv_client::RegistryClient;
|
||||
use uv_distribution::{Reporter, SourceDistCachedBuilder};
|
||||
use uv_types::{BuildContext, RequestedRequirements};
|
||||
|
||||
/// A resolver for resolving lookahead requirements from local dependencies.
|
||||
///
|
||||
/// The resolver extends certain privileges to "first-party" requirements. For example, first-party
|
||||
/// requirements are allowed to contain direct URL references, local version specifiers, and more.
|
||||
///
|
||||
/// We make an exception for transitive requirements of _local_ dependencies. For example,
|
||||
/// `pip install .` should treat the dependencies of `.` as if they were first-party dependencies.
|
||||
/// This matches our treatment of editable installs (`pip install -e .`).
|
||||
///
|
||||
/// The lookahead resolver resolves requirements for local dependencies, so that the resolver can
|
||||
/// treat them as first-party dependencies for the purpose of analyzing their specifiers.
|
||||
pub struct LookaheadResolver<'a> {
|
||||
/// The requirements for the project.
|
||||
requirements: &'a [Requirement],
|
||||
/// The reporter to use when building source distributions.
|
||||
reporter: Option<Arc<dyn Reporter>>,
|
||||
}
|
||||
|
||||
impl<'a> LookaheadResolver<'a> {
|
||||
/// Instantiate a new [`LookaheadResolver`] for a given set of `source_trees`.
|
||||
pub fn new(requirements: &'a [Requirement]) -> Self {
|
||||
Self {
|
||||
requirements,
|
||||
reporter: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the [`Reporter`] to use for this resolver.
|
||||
#[must_use]
|
||||
pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self {
|
||||
let reporter: Arc<dyn Reporter> = Arc::new(reporter);
|
||||
Self {
|
||||
reporter: Some(reporter),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the requirements from the provided source trees.
|
||||
pub async fn resolve<T: BuildContext>(
|
||||
self,
|
||||
context: &T,
|
||||
client: &RegistryClient,
|
||||
) -> Result<Vec<RequestedRequirements>> {
|
||||
let requirements: Vec<_> = futures::stream::iter(self.requirements.iter())
|
||||
.map(|requirement| async { self.lookahead(requirement, context, client).await })
|
||||
.buffered(50)
|
||||
.try_collect()
|
||||
.await?;
|
||||
Ok(requirements.into_iter().flatten().collect())
|
||||
}
|
||||
|
||||
/// Infer the package name for a given "unnamed" requirement.
|
||||
async fn lookahead<T: BuildContext>(
|
||||
&self,
|
||||
requirement: &Requirement,
|
||||
context: &T,
|
||||
client: &RegistryClient,
|
||||
) -> Result<Option<RequestedRequirements>> {
|
||||
// Determine whether the requirement represents a local distribution.
|
||||
let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Convert to a buildable distribution.
|
||||
let dist = Dist::from_url(requirement.name.clone(), url.clone())?;
|
||||
|
||||
// Only support source trees (and not, e.g., wheels).
|
||||
let Dist::Source(source_dist) = &dist else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !source_dist.as_path().is_some_and(std::path::Path::is_dir) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Run the PEP 517 build process to extract metadata from the source distribution.
|
||||
let builder = if let Some(reporter) = self.reporter.clone() {
|
||||
SourceDistCachedBuilder::new(context, client).with_reporter(reporter)
|
||||
} else {
|
||||
SourceDistCachedBuilder::new(context, client)
|
||||
};
|
||||
|
||||
let metadata = builder
|
||||
.download_and_build_metadata(&BuildableSource::Dist(source_dist))
|
||||
.await
|
||||
.context("Failed to build source distribution")?;
|
||||
|
||||
// Return the requirements from the metadata.
|
||||
Ok(Some(RequestedRequirements::new(
|
||||
requirement.extras.clone(),
|
||||
metadata.requires_dist,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,44 @@ use distribution_types::LocalEditable;
|
|||
use pep508_rs::Requirement;
|
||||
use pypi_types::Metadata23;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_types::RequestedRequirements;
|
||||
|
||||
use crate::preferences::Preference;
|
||||
|
||||
/// A manifest of requirements, constraints, and preferences.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Manifest {
|
||||
/// The direct requirements for the project.
|
||||
pub(crate) requirements: Vec<Requirement>,
|
||||
|
||||
/// The constraints for the project.
|
||||
pub(crate) constraints: Vec<Requirement>,
|
||||
|
||||
/// The overrides for the project.
|
||||
pub(crate) overrides: Vec<Requirement>,
|
||||
|
||||
/// The preferences for the project.
|
||||
///
|
||||
/// These represent "preferred" versions of a given package. For example, they may be the
|
||||
/// versions that are already installed in the environment, or already pinned in an existing
|
||||
/// lockfile.
|
||||
pub(crate) preferences: Vec<Preference>,
|
||||
|
||||
/// The name of the project.
|
||||
pub(crate) project: Option<PackageName>,
|
||||
|
||||
/// The editable requirements for the project, which are built in advance.
|
||||
///
|
||||
/// The requirements of the editables should be included in resolution as if they were
|
||||
/// direct requirements in their own right.
|
||||
pub(crate) editables: Vec<(LocalEditable, Metadata23)>,
|
||||
|
||||
/// The lookahead requirements for the project.
|
||||
///
|
||||
/// These represent transitive dependencies that should be incorporated when making
|
||||
/// determinations around "allowed" versions (for example, "allowed" URLs or "allowed"
|
||||
/// pre-release versions).
|
||||
pub(crate) lookaheads: Vec<RequestedRequirements>,
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
|
|
@ -24,6 +50,7 @@ impl Manifest {
|
|||
preferences: Vec<Preference>,
|
||||
project: Option<PackageName>,
|
||||
editables: Vec<(LocalEditable, Metadata23)>,
|
||||
lookaheads: Vec<RequestedRequirements>,
|
||||
) -> Self {
|
||||
Self {
|
||||
requirements,
|
||||
|
|
@ -32,6 +59,7 @@ impl Manifest {
|
|||
preferences,
|
||||
project,
|
||||
editables,
|
||||
lookaheads,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +71,7 @@ impl Manifest {
|
|||
preferences: Vec::new(),
|
||||
project: None,
|
||||
editables: Vec::new(),
|
||||
lookaheads: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ impl PreReleaseStrategy {
|
|||
.chain(manifest.constraints.iter())
|
||||
.chain(manifest.overrides.iter())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.chain(manifest.lookaheads.iter().flat_map(|lookahead| {
|
||||
lookahead.requirements().iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
}))
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata.requires_dist.iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
|
|
@ -95,6 +100,11 @@ impl PreReleaseStrategy {
|
|||
.chain(manifest.constraints.iter())
|
||||
.chain(manifest.overrides.iter())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.chain(manifest.lookaheads.iter().flat_map(|lookahead| {
|
||||
lookahead.requirements().iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
}))
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata.requires_dist.iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
|
|
|
|||
|
|
@ -41,11 +41,16 @@ impl ResolutionStrategy {
|
|||
ResolutionMode::Highest => Self::Highest,
|
||||
ResolutionMode::Lowest => Self::Lowest,
|
||||
ResolutionMode::LowestDirect => Self::LowestDirect(
|
||||
// Consider `requirements` and dependencies of `editables` to be "direct" dependencies.
|
||||
// Consider `requirements` and dependencies of any local requirements to be "direct" dependencies.
|
||||
manifest
|
||||
.requirements
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.chain(manifest.lookaheads.iter().flat_map(|lookahead| {
|
||||
lookahead.requirements().iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
}))
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata.requires_dist.iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
|
|
|
|||
|
|
@ -24,28 +24,23 @@ impl Locals {
|
|||
|
||||
// Add all direct requirements and constraints. There's no need to look for conflicts,
|
||||
// since conflicting versions will be tracked upstream.
|
||||
for requirement in manifest
|
||||
for requirement in
|
||||
manifest
|
||||
.requirements
|
||||
.iter()
|
||||
.chain(manifest.constraints.iter())
|
||||
.chain(manifest.overrides.iter())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.chain(
|
||||
manifest
|
||||
.constraints
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata
|
||||
.requires_dist
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &editable.extras))
|
||||
.chain(manifest.lookaheads.iter().flat_map(|lookahead| {
|
||||
lookahead.requirements().iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
}))
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata.requires_dist.iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
})
|
||||
}))
|
||||
.chain(
|
||||
manifest
|
||||
.overrides
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
{
|
||||
if let Some(version_or_url) = requirement.version_or_url.as_ref() {
|
||||
for local in iter_locals(version_or_url) {
|
||||
|
|
|
|||
|
|
@ -23,16 +23,39 @@ impl Urls {
|
|||
let mut required: FxHashMap<PackageName, VerbatimUrl> = FxHashMap::default();
|
||||
let mut allowed: FxHashMap<VerbatimUrl, VerbatimUrl> = FxHashMap::default();
|
||||
|
||||
// Add any lookahead requirements. If there are any conflicts, return an error.
|
||||
for lookahead in &manifest.lookaheads {
|
||||
for requirement in lookahead
|
||||
.requirements()
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, lookahead.extras()))
|
||||
{
|
||||
if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url {
|
||||
if let Some(previous) = required.insert(requirement.name.clone(), url.clone()) {
|
||||
if !is_equal(&previous, url) {
|
||||
if is_precise(&previous, url) {
|
||||
debug!("Assuming {url} is a precise variant of {previous}");
|
||||
allowed.insert(url.clone(), previous);
|
||||
} else {
|
||||
return Err(ResolveError::ConflictingUrlsDirect(
|
||||
requirement.name.clone(),
|
||||
previous.verbatim().to_string(),
|
||||
url.verbatim().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all direct requirements and constraints. If there are any conflicts, return an error.
|
||||
for requirement in manifest
|
||||
.requirements
|
||||
.iter()
|
||||
.chain(manifest.constraints.iter())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
{
|
||||
if !requirement.evaluate_markers(markers, &[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url {
|
||||
if let Some(previous) = required.insert(requirement.name.clone(), url.clone()) {
|
||||
if is_equal(&previous, url) {
|
||||
|
|
@ -74,11 +97,11 @@ impl Urls {
|
|||
}
|
||||
}
|
||||
|
||||
for requirement in &metadata.requires_dist {
|
||||
if !requirement.evaluate_markers(markers, &editable.extras) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for requirement in metadata
|
||||
.requires_dist
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &editable.extras))
|
||||
{
|
||||
if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url {
|
||||
if let Some(previous) = required.insert(requirement.name.clone(), url.clone()) {
|
||||
if !is_equal(&previous, url) {
|
||||
|
|
@ -100,11 +123,11 @@ impl Urls {
|
|||
|
||||
// Add any overrides. Conflicts here are fine, as the overrides are meant to be
|
||||
// authoritative.
|
||||
for requirement in &manifest.overrides {
|
||||
if !requirement.evaluate_markers(markers, &[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for requirement in manifest
|
||||
.overrides
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
{
|
||||
if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url {
|
||||
required.insert(requirement.name.clone(), url.clone());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pep508_rs::{MarkerEnvironment, VersionOrUrl};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::preferences::Preference;
|
||||
|
|
@ -15,22 +15,26 @@ pub struct AllowedYanks(FxHashMap<PackageName, FxHashSet<Version>>);
|
|||
impl AllowedYanks {
|
||||
pub fn from_manifest(manifest: &Manifest, markers: &MarkerEnvironment) -> Self {
|
||||
let mut allowed_yanks = FxHashMap::<PackageName, FxHashSet<Version>>::default();
|
||||
for requirement in manifest
|
||||
for requirement in
|
||||
manifest
|
||||
.requirements
|
||||
.iter()
|
||||
.chain(manifest.constraints.iter())
|
||||
.chain(manifest.overrides.iter())
|
||||
.chain(manifest.preferences.iter().map(Preference::requirement))
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.chain(manifest.lookaheads.iter().flat_map(|lookahead| {
|
||||
lookahead.requirements().iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
}))
|
||||
.chain(manifest.editables.iter().flat_map(|(editable, metadata)| {
|
||||
metadata
|
||||
.requires_dist
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &editable.extras))
|
||||
metadata.requires_dist.iter().filter(|requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
})
|
||||
}))
|
||||
{
|
||||
let Some(pep508_rs::VersionOrUrl::VersionSpecifier(specifiers)) =
|
||||
&requirement.version_or_url
|
||||
let Some(VersionOrUrl::VersionSpecifier(specifiers)) = &requirement.version_or_url
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@ async fn black_mypy_extensions() -> Result<()> {
|
|||
vec![],
|
||||
None,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
|
@ -306,6 +307,7 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
|||
vec![],
|
||||
None,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
|
@ -341,6 +343,7 @@ async fn black_flake8() -> Result<()> {
|
|||
vec![],
|
||||
None,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
|
@ -430,6 +433,7 @@ async fn black_respect_preference() -> Result<()> {
|
|||
)?)],
|
||||
None,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
|
@ -465,6 +469,7 @@ async fn black_ignore_preference() -> Result<()> {
|
|||
)?)],
|
||||
None,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ pub use config_settings::*;
|
|||
pub use downloads::*;
|
||||
pub use name_specifiers::*;
|
||||
pub use package_options::*;
|
||||
pub use requirements::*;
|
||||
pub use traits::*;
|
||||
|
||||
mod build_options;
|
||||
|
|
@ -11,4 +12,5 @@ mod config_settings;
|
|||
mod downloads;
|
||||
mod name_specifiers;
|
||||
mod package_options;
|
||||
mod requirements;
|
||||
mod traits;
|
||||
|
|
|
|||
35
crates/uv-types/src/requirements.rs
Normal file
35
crates/uv-types/src/requirements.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use pep508_rs::Requirement;
|
||||
use uv_normalize::ExtraName;
|
||||
|
||||
/// A set of requirements as requested by a parent requirement.
|
||||
///
|
||||
/// For example, given `flask[dotenv]`, the `RequestedRequirements` would include the `dotenv`
|
||||
/// extra, along with all of the requirements that are included in the `flask` distribution
|
||||
/// including their unevaluated markers.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestedRequirements {
|
||||
/// The set of extras included on the originating requirement.
|
||||
extras: Vec<ExtraName>,
|
||||
/// The set of requirements that were requested by the originating requirement.
|
||||
requirements: Vec<Requirement>,
|
||||
}
|
||||
|
||||
impl RequestedRequirements {
|
||||
/// Instantiate a [`RequestedRequirements`] with the given `extras` and `requirements`.
|
||||
pub fn new(extras: Vec<ExtraName>, requirements: Vec<Requirement>) -> Self {
|
||||
Self {
|
||||
extras,
|
||||
requirements,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the extras that were included on the originating requirement.
|
||||
pub fn extras(&self) -> &[ExtraName] {
|
||||
&self.extras
|
||||
}
|
||||
|
||||
/// Return the requirements that were included on the originating requirement.
|
||||
pub fn requirements(&self) -> &[Requirement] {
|
||||
&self.requirements
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,8 @@ use uv_installer::{Downloader, NoBinary};
|
|||
use uv_interpreter::{find_best_python, PythonEnvironment, PythonVersion};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_requirements::{
|
||||
upgrade::read_lockfile, ExtrasSpecification, NamedRequirementsResolver, RequirementsSource,
|
||||
RequirementsSpecification, SourceTreeResolver,
|
||||
upgrade::read_lockfile, ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver,
|
||||
RequirementsSource, RequirementsSpecification, SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
||||
|
|
@ -274,6 +274,12 @@ pub(crate) async fn pip_compile(
|
|||
requirements
|
||||
};
|
||||
|
||||
// Determine any lookahead requirements.
|
||||
let lookaheads = LookaheadResolver::new(&requirements)
|
||||
.with_reporter(ResolverReporter::from(printer))
|
||||
.resolve(&build_dispatch, &client)
|
||||
.await?;
|
||||
|
||||
// Build the editables and add their requirements
|
||||
let editable_metadata = if editables.is_empty() {
|
||||
Vec::new()
|
||||
|
|
@ -338,6 +344,7 @@ pub(crate) async fn pip_compile(
|
|||
preferences,
|
||||
project,
|
||||
editable_metadata,
|
||||
lookaheads,
|
||||
);
|
||||
|
||||
let options = OptionsBuilder::new()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
use anstream::eprint;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
|
@ -34,8 +33,8 @@ use uv_installer::{
|
|||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_requirements::{
|
||||
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
|
||||
SourceTreeResolver,
|
||||
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource,
|
||||
RequirementsSpecification, SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
DependencyMode, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode, Preference,
|
||||
|
|
@ -82,7 +81,7 @@ pub(crate) async fn pip_install(
|
|||
dry_run: bool,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
let start = Instant::now();
|
||||
let start = std::time::Instant::now();
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls)
|
||||
|
|
@ -437,7 +436,7 @@ async fn build_editables(
|
|||
build_dispatch: &BuildDispatch<'_>,
|
||||
printer: Printer,
|
||||
) -> Result<Vec<BuiltEditable>, Error> {
|
||||
let start = Instant::now();
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
||||
|
|
@ -547,6 +546,12 @@ async fn resolve(
|
|||
})
|
||||
.collect();
|
||||
|
||||
// Determine any lookahead requirements.
|
||||
let lookaheads = LookaheadResolver::new(&requirements)
|
||||
.with_reporter(ResolverReporter::from(printer))
|
||||
.resolve(build_dispatch, client)
|
||||
.await?;
|
||||
|
||||
// Create a manifest of the requirements.
|
||||
let manifest = Manifest::new(
|
||||
requirements,
|
||||
|
|
@ -555,6 +560,7 @@ async fn resolve(
|
|||
preferences,
|
||||
project,
|
||||
editables,
|
||||
lookaheads,
|
||||
);
|
||||
|
||||
// Resolve the dependencies.
|
||||
|
|
@ -674,7 +680,7 @@ async fn install(
|
|||
let wheels = if remote.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
let start = Instant::now();
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
|
||||
|
|
@ -796,7 +802,7 @@ async fn install(
|
|||
fn report_dry_run(
|
||||
resolution: &Resolution,
|
||||
plan: Plan,
|
||||
start: Instant,
|
||||
start: std::time::Instant,
|
||||
printer: Printer,
|
||||
) -> Result<(), Error> {
|
||||
let Plan {
|
||||
|
|
|
|||
|
|
@ -1691,7 +1691,7 @@ fn incompatible_narrowed_url_dependency() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Request `transitive_url_dependency`, which depends on `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`.
|
||||
/// Request `hatchling_editable`, which depends on `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`.
|
||||
/// Since this URL isn't declared upfront, we should reject it.
|
||||
#[test]
|
||||
#[cfg(feature = "git")]
|
||||
|
|
@ -1699,12 +1699,10 @@ fn disallowed_transitive_url_dependency() -> Result<()> {
|
|||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("hatchling_editable @ ${HATCHLING}")?;
|
||||
requirements_in.write_str("hatchling_editable @ https://github.com/astral-sh/uv/files/14762645/hatchling_editable.zip")?;
|
||||
|
||||
let hatchling_path = current_dir()?.join("../../scripts/packages/hatchling_editable");
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.env("HATCHLING", hatchling_path.as_os_str()), @r###"
|
||||
.arg("requirements.in"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
|
@ -1725,23 +1723,21 @@ fn allowed_transitive_url_dependency() -> Result<()> {
|
|||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("hatchling_editable @ ${HATCHLING}")?;
|
||||
requirements_in.write_str("hatchling_editable @ https://github.com/astral-sh/uv/files/14762645/hatchling_editable.zip")?;
|
||||
|
||||
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("iniconfig @ git+https://github.com/pytest-dev/iniconfig@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4")?;
|
||||
|
||||
let hatchling_path = current_dir()?.join("../../scripts/packages/hatchling_editable");
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--constraint")
|
||||
.arg("constraints.txt")
|
||||
.env("HATCHLING", hatchling_path.as_os_str()), @r###"
|
||||
.arg("constraints.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --constraint constraints.txt
|
||||
hatchling-editable @ ${HATCHLING}
|
||||
hatchling-editable @ https://github.com/astral-sh/uv/files/14762645/hatchling_editable.zip
|
||||
iniconfig @ git+https://github.com/pytest-dev/iniconfig@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4
|
||||
# via hatchling-editable
|
||||
|
||||
|
|
@ -1762,23 +1758,21 @@ fn allowed_transitive_canonical_url_dependency() -> Result<()> {
|
|||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("hatchling_editable @ ${HATCHLING}")?;
|
||||
requirements_in.write_str("hatchling_editable @ https://github.com/astral-sh/uv/files/14762645/hatchling_editable.zip")?;
|
||||
|
||||
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("iniconfig @ git+https://github.com/pytest-dev/iniconfig.git@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4")?;
|
||||
|
||||
let hatchling_path = current_dir()?.join("../../scripts/packages/hatchling_editable");
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--constraint")
|
||||
.arg("constraints.txt")
|
||||
.env("HATCHLING", hatchling_path.as_os_str()), @r###"
|
||||
.arg("constraints.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --constraint constraints.txt
|
||||
hatchling-editable @ ${HATCHLING}
|
||||
hatchling-editable @ https://github.com/astral-sh/uv/files/14762645/hatchling_editable.zip
|
||||
iniconfig @ git+https://github.com/pytest-dev/iniconfig.git@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4
|
||||
# via hatchling-editable
|
||||
|
||||
|
|
@ -1790,6 +1784,37 @@ fn allowed_transitive_canonical_url_dependency() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Request `hatchling_editable`, which depends on `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`.
|
||||
/// Since `hatchling_editable` is a path (local) dependency, we should accept it.
|
||||
#[test]
|
||||
#[cfg(feature = "git")]
|
||||
fn allowed_transitive_url_path_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("hatchling_editable @ ${HATCH_PATH}")?;
|
||||
|
||||
let hatchling_path = current_dir()?.join("../../scripts/packages/hatchling_editable");
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.env("HATCH_PATH", hatchling_path.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
hatchling-editable @ ${HATCH_PATH}
|
||||
iniconfig @ git+https://github.com/pytest-dev/iniconfig@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4
|
||||
# via hatchling-editable
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve packages from all optional dependency groups in a `pyproject.toml` file.
|
||||
#[test]
|
||||
fn compile_pyproject_toml_all_extras() -> Result<()> {
|
||||
|
|
@ -6328,3 +6353,110 @@ fn pendulum_no_tzdata_on_windows() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allow pre-releases for dependencies of source path requirements.
|
||||
#[test]
|
||||
fn pre_release_path_requirement() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Create an a package that requires a pre-release version of `flask`.
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"flask==2.0.0rc1"
|
||||
]
|
||||
requires-python = ">3.8"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Write to a requirements file.
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(".")?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
click==8.1.7
|
||||
# via flask
|
||||
example @ .
|
||||
flask==2.0.0rc1
|
||||
# via example
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.3
|
||||
# via flask
|
||||
markupsafe==2.1.5
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
werkzeug==3.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allow pre-releases for dependencies of editable requirements.
|
||||
#[test]
|
||||
fn pre_release_editable_requirement() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Create an a package that requires a pre-release version of `flask`.r
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"flask==2.0.0rc1"
|
||||
]
|
||||
requires-python = ">3.8"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Write to a requirements file.
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("-e .")?;
|
||||
|
||||
uv_snapshot!( context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
-e .
|
||||
click==8.1.7
|
||||
# via flask
|
||||
flask==2.0.0rc1
|
||||
# via example
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.3
|
||||
# via flask
|
||||
markupsafe==2.1.5
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
werkzeug==3.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Built 1 editable in [TIME]
|
||||
Resolved 7 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue