mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
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:
parent
ca92b55605
commit
d9dbb8a4af
15 changed files with 1209 additions and 609 deletions
|
@ -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]),
|
||||
))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue