Support conflicting URL in separate forks (#4435)

Downstack PR: #4481

## Introduction

We support forking the dependency resolution to support conflicting
registry requirements for different platforms, say on package range is
required for an older python version while a newer is required for newer
python versions, or dependencies that are different per platform. We
need to extend this support to direct URL requirements.

```toml
dependencies = [
  "iniconfig @ 62565a6e1c/iniconfig-2.0.0-py3-none-any.whl ; python_version >= '3.12'",
  "iniconfig @ b3c12c6d70/iniconfig-1.1.1-py2.py3-none-any.whl ; python_version < '3.12'"
]
```

This did not work because `Urls` was built on the assumption that there
is a single allowed URL per package. We collect all allowed URL ahead of
resolution by following direct URL dependencies (including path
dependencies) transitively, i.e. a registry distribution can't require a
URL.

## The same package can have Registry and URL requirements

Consider the following two cases:

requirements.in:
```text
werkzeug==2.0.0
werkzeug @ 960bb4017c/Werkzeug-2.0.0-py3-none-any.whl
```
pyproject.toml:
```toml
dependencies = [
  "iniconfig == 1.1.1 ; python_version < '3.12'",
  "iniconfig @ git+https://github.com/pytest-dev/iniconfig@93f5930e668c0d1ddf4597e38dd0dea4e2665e7a ; python_version >= '3.12'",
]
```

In the first case, we want the URL to override the registry dependency,
in the second case we want to fork and have one branch use the registry
and the other the URL. We have to know about this in
`PubGrubRequirement::from_registry_requirement`, but we only fork after
the current method.

Consider the following case too:

a:
```
c==1.0.0
b @ https://b.zip
```
b:
```
c @ https://c_new.zip ; python_version >= '3.12'",
c @ https://c_old.zip ; python_version < '3.12'",
```

When we convert the requirements of `a`, we can't know the url of `c`
yet. The solution is to remove the `Url` from `PubGrubPackage`: The
`Url` is redundant with `PackageName`, there can be only one url per
package name per fork. We now do the following: We track the urls from
requirements in `PubGrubDependency`. After forking, we call
`add_package_version_dependencies` where we apply override URLs, check
if the URL is allowed and check if the url is unique in this fork. When
we request a distribution, we ask the fork urls for the real URL. Since
we prioritize url dependencies over registry dependencies and skip
packages with `Urls` entries in pre-visiting, we know that when fetching
a package, we know if it has a url or not.

## URL conflicts

pyproject.toml (invalid):
```toml
dependencies = [
  "iniconfig @ e96292c7f7/iniconfig-1.1.0.tar.gz",
  "iniconfig @ b3c12c6d70/iniconfig-1.1.1-py2.py3-none-any.whl ; python_version < '3.12'",
  "iniconfig @ 62565a6e1c/iniconfig-2.0.0-py3-none-any.whl ; python_version >= '3.12'",
]
```

On the fork state, we keep `ForkUrls` that check for conflicts after
forking, rejecting the third case because we added two packages of the
same name with different URLs.

We need to flatten out the requirements before transformation into
pubgrub requirements to get the full list of other requirements which
may contain a URL, which was changed in a previous PR: #4430.

## Complex Example

a:
```toml
dependencies = [
  # Force a split
  "anyio==4.3.0 ; python_version >= '3.12'",
  "anyio==4.2.0 ; python_version < '3.12'",
  # Include URLs transitively
  "b"
]
```
b:
```toml
dependencies = [
  # Only one is used in each split.
  "b1 ; python_version < '3.12'",
  "b2 ; python_version >= '3.12'",
  "b3 ; python_version >= '3.12'",
]
```
b1:
```toml
dependencies = [
  "iniconfig @ b3c12c6d70/iniconfig-1.1.1-py2.py3-none-any.whl",
]
```
b2:
```toml
dependencies = [
  "iniconfig @ 62565a6e1c/iniconfig-2.0.0-py3-none-any.whl",
]
```
b3:
```toml
dependencies = [
  "iniconfig @ e96292c7f7/iniconfig-1.1.0.tar.gz",
]
```

In this example, all packages are url requirements (directory
requirements) and the root package is `a`. We first split on `a`, `b`
being in each split. In the first fork, we reach `b1`, the fork URLs are
empty, we insert the iniconfig 1.1.1 URL, and then we skip over `b2` and
`b3` since the mark is disjoint with the fork markers. In the second
fork, we skip over `b1`, visit `b2`, insert the iniconfig 2.0.0 URL into
the again empty fork URLs, then visit `b3` and try to insert the
iniconfig 1.1.0 URL. At this point we find a conflict for the iniconfig
URL and error.

## Closing

The git tests are slow, but they make the best example for different URL
types i could find.

Part of #3927. This PR does not handle `Locals` or pre-releases yet.
This commit is contained in:
konsti 2024-06-26 13:58:23 +02:00 committed by GitHub
parent ca92b55605
commit d9dbb8a4af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1209 additions and 609 deletions

View file

@ -40,6 +40,7 @@ use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
use crate::candidate_selector::{CandidateDist, CandidateSelector};
use crate::dependency_provider::UvDependencyProvider;
use crate::error::ResolveError;
use crate::fork_urls::ForkUrls;
use crate::manifest::Manifest;
use crate::pins::FilePins;
use crate::preferences::Preferences;
@ -78,7 +79,7 @@ pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPack
}
/// State that is shared between the prefetcher and the PubGrub solver during
/// resolution.
/// resolution, across all forks.
struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
project: Option<PackageName>,
requirements: Vec<Requirement>,
@ -318,6 +319,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
pubgrub: State::init(root.clone(), MIN_VERSION.clone()),
next: root,
pins: FilePins::default(),
fork_urls: ForkUrls::default(),
priorities: PubGrubPriorities::default(),
added_dependencies: FxHashMap::default(),
markers: MarkerTree::And(vec![]),
@ -336,13 +338,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
'FORK: while let Some(mut state) = forked_states.pop() {
loop {
// Run unit propagation.
state.pubgrub.unit_propagation(state.next.clone())?;
state
.pubgrub
.unit_propagation(state.next.clone())
.map_err(|err| {
ResolveError::from_pubgrub_error(err, state.fork_urls.clone())
})?;
// Pre-visit all candidate packages, to allow metadata to be fetched in parallel. If
// the dependency mode is direct, we only need to visit the root package.
if self.dependency_mode.is_transitive() {
Self::pre_visit(
state.pubgrub.partial_solution.prioritized_packages(),
&self.urls,
&request_sink,
)?;
}
@ -360,6 +368,20 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
continue 'FORK;
};
state.next = highest_priority_pkg;
let url = state.next.name().and_then(|name| state.fork_urls.get(name));
// Consider:
// ```toml
// dependencies = [
// "iniconfig == 1.1.1 ; python_version < '3.12'",
// "iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl ; python_version >= '3.12'",
// ]
// ```
// In the `python_version < '3.12'` case, we haven't pre-visited `iniconfig` yet,
// since we weren't sure whether it might also be a URL requirement when
// transforming the requirements. For that case, we do another request here
// (idempotent due to caching).
self.request_package(&state.next, url, &request_sink)?;
prefetcher.version_tried(state.next.clone());
@ -368,14 +390,18 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.partial_solution
.term_intersection_for_package(&state.next)
.ok_or_else(|| {
PubGrubError::Failure(
"a package was chosen but we don't have a term.".into(),
ResolveError::from_pubgrub_error(
PubGrubError::Failure(
"a package was chosen but we don't have a term.".into(),
),
state.fork_urls.clone(),
)
})?;
let decision = self.choose_version(
&state.next,
term_intersection.unwrap_positive(),
&mut state.pins,
&state.fork_urls,
visited,
&request_sink,
)?;
@ -472,14 +498,17 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
};
prefetcher.prefetch_batches(
&state.next,
&version,
term_intersection.unwrap_positive(),
&request_sink,
&self.index,
&self.selector,
)?;
// Only consider registry packages for prefetch.
if url.is_none() {
prefetcher.prefetch_batches(
&state.next,
&version,
term_intersection.unwrap_positive(),
&request_sink,
&self.index,
&self.selector,
)?;
}
self.on_progress(&state.next, &version);
@ -489,13 +518,17 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.or_default()
.insert(version.clone())
{
let for_package = if let PubGrubPackageInner::Root(_) = &*state.next {
None
} else {
state.next.name().map(|name| format!("{name}=={version}"))
};
// Retrieve that package dependencies.
let forked_deps = self.get_dependencies_forking(
&state.next,
&version,
&state.fork_urls,
&state.markers,
&mut state.priorities,
&request_sink,
)?;
match forked_deps {
ForkedDependencies::Unavailable(reason) => {
@ -510,10 +543,23 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
ForkedDependencies::Unforked(dependencies) => {
state.add_package_version_dependencies(
for_package.as_deref(),
&version,
dependencies,
&self.urls,
dependencies.clone(),
&self.git,
&prefetcher,
)?;
// Emit a request to fetch the metadata for each registry package.
for dependency in &dependencies {
let PubGrubDependency {
package,
version: _,
url: _,
} = dependency;
let url = package.name().and_then(|name| state.fork_urls.get(name));
self.visit_package(package, url, &request_sink)?;
}
forked_states.push(state);
}
ForkedDependencies::Forked {
@ -545,10 +591,25 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
forked_state.markers.and(fork.markers);
forked_state.add_package_version_dependencies(
for_package.as_deref(),
&version,
fork.dependencies,
&self.urls,
fork.dependencies.clone(),
&self.git,
&prefetcher,
)?;
// Emit a request to fetch the metadata for each registry package.
for dependency in &fork.dependencies {
let PubGrubDependency {
package,
version: _,
url: _,
} = dependency;
let url = package
.name()
.and_then(|name| forked_state.fork_urls.get(name));
self.visit_package(package, url, &request_sink)?;
}
forked_states.push(forked_state);
}
}
@ -584,23 +645,38 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
fn visit_package(
&self,
package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>,
request_sink: &Sender<Request>,
) -> Result<(), ResolveError> {
match &**package {
PubGrubPackageInner::Root(_) => {}
PubGrubPackageInner::Python(_) => {}
PubGrubPackageInner::Marker {
name, url: None, ..
}
| PubGrubPackageInner::Extra {
name, url: None, ..
}
| PubGrubPackageInner::Dev {
name, url: None, ..
}
| PubGrubPackageInner::Package {
name, url: None, ..
} => {
// Ignore unresolved URL packages.
if url.is_none()
&& package
.name()
.map(|name| self.urls.any_url(name))
.unwrap_or(true)
{
return Ok(());
}
self.request_package(package, url, request_sink)
}
fn request_package(
&self,
package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>,
request_sink: &Sender<Request>,
) -> Result<(), ResolveError> {
match (&**package, url) {
(PubGrubPackageInner::Root(_), _) => {}
(PubGrubPackageInner::Python(_), _) => {}
(
PubGrubPackageInner::Marker { name, .. }
| PubGrubPackageInner::Extra { name, .. }
| PubGrubPackageInner::Dev { name, .. }
| PubGrubPackageInner::Package { name, .. },
None,
) => {
// Verify that the package is allowed under the hash-checking policy.
if !self.hasher.allows_package(name) {
return Err(ResolveError::UnhashedPackage(name.clone()));
@ -611,26 +687,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
request_sink.blocking_send(Request::Package(name.clone()))?;
}
}
PubGrubPackageInner::Marker {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Extra {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Dev {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Package {
name,
url: Some(url),
..
} => {
(
PubGrubPackageInner::Marker { name, .. }
| PubGrubPackageInner::Extra { name, .. }
| PubGrubPackageInner::Dev { name, .. }
| PubGrubPackageInner::Package { name, .. },
Some(url),
) => {
// Verify that the package is allowed under the hash-checking policy.
if !self.hasher.allows_url(&url.verbatim) {
return Err(ResolveError::UnhashedPackage(name.clone()));
@ -650,6 +713,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
/// metadata for all packages in parallel.
fn pre_visit<'data>(
packages: impl Iterator<Item = (&'data PubGrubPackage, &'data Range<Version>)>,
urls: &Urls,
request_sink: &Sender<Request>,
) -> Result<(), ResolveError> {
// Iterate over the potential packages, and fetch file metadata for any of them. These
@ -660,11 +724,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra: None,
dev: None,
marker: None,
url: None,
} = &**package
else {
continue;
};
// Avoid pre-visiting packages that have any URLs in any fork. At this point we can't
// tell whether they are registry distributions or which url they use.
if urls.any_url(name) {
continue;
}
request_sink.blocking_send(Request::Prefetch(name.clone(), range.clone()))?;
}
Ok(())
@ -680,40 +748,30 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
package: &PubGrubPackage,
range: &Range<Version>,
pins: &mut FilePins,
fork_urls: &ForkUrls,
visited: &mut FxHashSet<PackageName>,
request_sink: &Sender<Request>,
) -> Result<Option<ResolverVersion>, ResolveError> {
match &**package {
PubGrubPackageInner::Root(_) => {
let url = package.name().and_then(|name| fork_urls.get(name));
match (&**package, url) {
(PubGrubPackageInner::Root(_), _) => {
Ok(Some(ResolverVersion::Available(MIN_VERSION.clone())))
}
PubGrubPackageInner::Python(_) => {
(PubGrubPackageInner::Python(_), _) => {
// Dependencies on Python are only added when a package is incompatible; as such,
// we don't need to do anything here.
// we don't need to do anything here.
Ok(None)
}
PubGrubPackageInner::Marker {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Extra {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Dev {
name,
url: Some(url),
..
}
| PubGrubPackageInner::Package {
name,
url: Some(url),
..
} => {
(
PubGrubPackageInner::Marker { name, .. }
| PubGrubPackageInner::Extra { name, .. }
| PubGrubPackageInner::Dev { name, .. }
| PubGrubPackageInner::Package { name, .. },
Some(url),
) => {
debug!(
"Searching for a compatible version of {package} @ {} ({range})",
url.verbatim
@ -800,18 +858,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Ok(Some(ResolverVersion::Available(version.clone())))
}
PubGrubPackageInner::Marker {
name, url: None, ..
}
| PubGrubPackageInner::Extra {
name, url: None, ..
}
| PubGrubPackageInner::Dev {
name, url: None, ..
}
| PubGrubPackageInner::Package {
name, url: None, ..
} => {
(
PubGrubPackageInner::Marker { name, .. }
| PubGrubPackageInner::Extra { name, .. }
| PubGrubPackageInner::Dev { name, .. }
| PubGrubPackageInner::Package { name, .. },
None,
) => {
// Wait for the metadata to be available.
let versions_response = self
.index
@ -907,11 +960,10 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self,
package: &PubGrubPackage,
version: &Version,
fork_urls: &ForkUrls,
markers: &MarkerTree,
priorities: &mut PubGrubPriorities,
request_sink: &Sender<Request>,
) -> Result<ForkedDependencies, ResolveError> {
let result = self.get_dependencies(package, version, markers, priorities, request_sink);
let result = self.get_dependencies(package, version, fork_urls, markers);
if self.markers.is_some() {
return result.map(|deps| match deps {
Dependencies::Available(deps) => ForkedDependencies::Unforked(deps),
@ -927,11 +979,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self,
package: &PubGrubPackage,
version: &Version,
fork_urls: &ForkUrls,
markers: &MarkerTree,
priorities: &mut PubGrubPriorities,
request_sink: &Sender<Request>,
) -> Result<Dependencies, ResolveError> {
let (dependencies, name) = match &**package {
let url = package.name().and_then(|name| fork_urls.get(name));
let dependencies = match &**package {
PubGrubPackageInner::Root(_) => {
let no_dev_deps = BTreeMap::default();
let requirements = self.flatten_requirements(
@ -943,27 +995,18 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
markers,
);
let dependencies = requirements
requirements
.iter()
.flat_map(|requirement| {
PubGrubDependency::from_requirement(
requirement,
None,
&self.urls,
&self.locals,
&self.git,
)
PubGrubDependency::from_requirement(requirement, None, &self.locals)
})
.collect::<Result<Vec<_>, _>>()?;
(dependencies, None)
.collect::<Result<Vec<_>, _>>()?
}
PubGrubPackageInner::Package {
name,
extra,
dev,
marker,
url,
} => {
// If we're excluding transitive dependencies, short-circuit.
if self.dependency_mode.is_direct() {
@ -1083,18 +1126,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let mut dependencies = requirements
.iter()
.flat_map(|requirement| {
PubGrubDependency::from_requirement(
requirement,
Some(name),
&self.urls,
&self.locals,
&self.git,
)
PubGrubDependency::from_requirement(requirement, Some(name), &self.locals)
})
.collect::<Result<Vec<_>, _>>()?;
// If a package has metadata for an enabled dependency group,
// add a dependency from it to the same package with the group
// enabled.
if extra.is_none() && dev.is_none() {
for group in &self.dev {
if !metadata.dev_dependencies.contains_key(group) {
@ -1105,19 +1143,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
name: name.clone(),
dev: group.clone(),
marker: marker.clone(),
url: url.clone(),
}),
version: Range::singleton(version.clone()),
url: None,
});
}
}
(dependencies, Some(name))
dependencies
}
PubGrubPackageInner::Python(_) => return Ok(Dependencies::Available(Vec::default())),
// Add a dependency on both the marker and base package.
PubGrubPackageInner::Marker { name, marker, url } => {
PubGrubPackageInner::Marker { name, marker } => {
return Ok(Dependencies::Available(
[None, Some(marker)]
.into_iter()
@ -1127,9 +1165,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra: None,
dev: None,
marker: marker.cloned(),
url: url.clone(),
}),
version: Range::singleton(version.clone()),
url: None,
})
.collect(),
))
@ -1140,7 +1178,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
name,
extra,
marker,
url,
} => {
return Ok(Dependencies::Available(
[None, marker.as_ref()]
@ -1155,9 +1192,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra: extra.cloned(),
dev: None,
marker: marker.cloned(),
url: url.clone(),
}),
version: Range::singleton(version.clone()),
url: None,
})
})
.collect(),
@ -1166,12 +1203,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// Add a dependency on both the development dependency group and base package, with and
// without the marker.
PubGrubPackageInner::Dev {
name,
dev,
marker,
url,
} => {
PubGrubPackageInner::Dev { name, dev, marker } => {
return Ok(Dependencies::Available(
[None, marker.as_ref()]
.into_iter()
@ -1185,9 +1217,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra: None,
dev: dev.cloned(),
marker: marker.cloned(),
url: url.clone(),
}),
version: Range::singleton(version.clone()),
url: None,
})
})
.collect(),
@ -1195,22 +1227,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
};
for dependency in &dependencies {
let PubGrubDependency { package, version } = dependency;
if let Some(name) = name {
debug!("Adding transitive dependency for {name}=={version}: {package}{version}");
} else {
// A dependency from the root package or requirements.txt.
debug!("Adding direct dependency: {package}{version}");
}
// Update the package priorities.
priorities.insert(package, version);
// Emit a request to fetch the metadata for this package.
self.visit_package(package, request_sink)?;
}
Ok(Dependencies::Available(dependencies))
}
@ -1580,16 +1596,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
PubGrubPackageInner::Marker { .. } => {}
PubGrubPackageInner::Extra { .. } => {}
PubGrubPackageInner::Dev { .. } => {}
PubGrubPackageInner::Package {
name,
url: Some(url),
..
} => {
reporter.on_progress(name, &VersionOrUrlRef::Url(&url.verbatim));
}
PubGrubPackageInner::Package {
name, url: None, ..
} => {
PubGrubPackageInner::Package { name, .. } => {
reporter.on_progress(name, &VersionOrUrlRef::Version(version));
}
}
@ -1603,7 +1610,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
}
/// State that is used during unit propagation in the resolver.
/// State that is used during unit propagation in the resolver, one instance per fork.
#[derive(Clone)]
struct SolveState {
/// The internal state used by the resolver.
@ -1620,12 +1627,18 @@ struct SolveState {
///
/// The key of this map is a package name, and each package name maps to
/// a set of versions for that package. Each version in turn is mapped
/// to a single `ResolvedDist`. That `ResolvedDist` represents, at time
/// to a single [`ResolvedDist`]. That [`ResolvedDist`] represents, at time
/// of writing (2024/05/09), at most one wheel. The idea here is that
/// `FilePins` tracks precisely which wheel was selected during resolution.
/// [`FilePins`] tracks precisely which wheel was selected during resolution.
/// After resolution is finished, this maps is consulted in order to select
/// the wheel chosen during resolution.
pins: FilePins,
/// Ensure we don't have duplicate urls in any branch.
///
/// Unlike [`Urls`], we add only the URLs we have seen in this branch, and there can be only
/// one URL per package. By prioritizing direct URL dependencies over registry dependencies,
/// this map is populated for all direct URL packages before we look at any registry packages.
fork_urls: ForkUrls,
/// When dependencies for a package are retrieved, this map of priorities
/// is updated based on how each dependency was specified. Certain types
/// of dependencies have more "priority" than others (like direct URL
@ -1653,13 +1666,18 @@ struct SolveState {
}
impl SolveState {
/// Add the dependencies for the selected version of the current package.
/// Add the dependencies for the selected version of the current package, checking for
/// self-dependencies, and handling URLs.
fn add_package_version_dependencies(
&mut self,
for_package: Option<&str>,
version: &Version,
urls: &Urls,
dependencies: Vec<PubGrubDependency>,
git: &GitResolver,
prefetcher: &BatchPrefetcher,
) -> Result<(), ResolveError> {
// Check for self-dependencies.
if dependencies
.iter()
.any(|dependency| dependency.package == self.next)
@ -1667,17 +1685,59 @@ impl SolveState {
if enabled!(Level::DEBUG) {
prefetcher.log_tried_versions();
}
return Err(PubGrubError::SelfDependency {
package: self.next.clone(),
version: version.clone(),
}
.into());
return Err(ResolveError::from_pubgrub_error(
PubGrubError::SelfDependency {
package: self.next.clone(),
version: version.clone(),
},
self.fork_urls.clone(),
));
}
for dependency in &dependencies {
let PubGrubDependency {
package,
version,
url,
} = dependency;
// From the [`Requirement`] to [`PubGrubDependency`] conversion, we get a URL if the
// requirement was a URL requirement. `Urls` applies canonicalization to this and
// override URLs to both URL and registry requirements, which we then check for
// conflicts using [`ForkUrl`].
if let Some(name) = package.name() {
if let Some(url) = urls.get_url(name, url.as_ref(), git)? {
self.fork_urls.insert(name, url, &self.markers)?;
};
}
if let Some(for_package) = for_package {
if self.markers == MarkerTree::And(Vec::new()) {
debug!("Adding transitive dependency for {for_package}: {package}{version}",);
} else {
debug!(
"Adding transitive dependency for {for_package}{{{}}}: {package}{version}",
self.markers
);
}
} else {
// A dependency from the root package or requirements.txt.
debug!("Adding direct dependency: {package}{version}");
}
// Update the package priorities.
self.priorities.insert(package, version, &self.fork_urls);
}
self.pubgrub.add_package_version_dependencies(
self.next.clone(),
version.clone(),
dependencies.into_iter().map(|dependency| {
let PubGrubDependency { package, version } = dependency;
let PubGrubDependency {
package,
version,
url: _,
} = dependency;
(package, version)
}),
);
@ -1839,7 +1899,6 @@ impl SolveState {
name,
extra,
dev,
url,
marker: None,
} = &*package
{
@ -1848,7 +1907,7 @@ impl SolveState {
name: name.clone(),
extra: extra.clone(),
dev: dev.clone(),
url: url.clone(),
url: self.fork_urls.get(name).cloned(),
},
FxHashSet::from_iter([version]),
))