1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{mem, thread};
11
12use futures::{FutureExt, StreamExt};
13use itertools::Itertools;
14use papaya::{HashMap, ResizeMode};
15use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
16use rustc_hash::{FxHashMap, FxHashSet};
17use tokio::sync::mpsc::{self, Receiver};
18use tokio::sync::oneshot;
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{Level, debug, info, instrument, trace, warn};
21
22use uv_configuration::{Constraints, Excludes, Overrides};
23use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24use uv_distribution_types::{
25 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
26 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
27 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement,
28 RequiresPython, ResolutionRecorder, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
29 implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::PackageName;
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_resolver_types::PackageNodeKind;
40use uv_static::EnvVars;
41use uv_torch::TorchStrategy;
42use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
43use uv_warnings::warn_user_once;
44
45use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
46use crate::dependency_provider::UvDependencyProvider;
47use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
48use crate::fork_indexes::ForkIndexes;
49use crate::fork_urls::ForkUrls;
50use crate::manifest::Manifest;
51use crate::pins::FilePins;
52use crate::preferences::{PreferenceSource, Preferences};
53use crate::pubgrub::{
54 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
55 PubGrubPython, Range,
56};
57use crate::python_requirement::PythonRequirement;
58use crate::resolution::ResolverOutput;
59use crate::resolution_mode::ResolutionStrategy;
60pub(crate) use crate::resolver::availability::{
61 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
62 UnavailableVersion, UnsatisfiableRequirement,
63};
64use crate::resolver::batch_prefetch::BatchPrefetcher;
65use crate::resolver::derivation::DerivationChainBuilder;
66pub use crate::resolver::environment::ResolverEnvironment;
67use crate::resolver::environment::{
68 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
69};
70pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
71pub use crate::resolver::index::InMemoryIndex;
72use crate::resolver::indexes::Indexes;
73use crate::resolver::package_source::PackageSource;
74pub use crate::resolver::provider::{
75 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
76 VersionsResponse, WheelMetadataResult,
77};
78pub use crate::resolver::reporter::Reporter;
79pub(crate) use crate::resolver::requests::RegisteredMetadata;
80use crate::resolver::requests::{MetadataRequest, MetadataRequests};
81use crate::resolver::requirements::{RequirementContext, RequirementExpander};
82use crate::resolver::system::SystemDependency;
83pub(crate) use crate::resolver::urls::Urls;
84use crate::universal_marker::UniversalMarker;
85use crate::yanks::AllowedYanks;
86use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
87pub(crate) use provider::MetadataUnavailable;
88pub(crate) use resolution::{
89 Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionPackage, ResolvedFork,
90 SelectedDistribution,
91};
92use uv_configuration::ForkStrategy;
93
94mod availability;
95mod batch_prefetch;
96mod derivation;
97mod environment;
98mod fork_map;
99mod index;
100mod indexes;
101mod package_source;
102mod provider;
103mod reporter;
104mod requests;
105mod requirements;
106mod resolution;
107mod system;
108mod urls;
109
110const CONFLICT_THRESHOLD: usize = 5;
112
113pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
114 state: ResolverState<InstalledPackages>,
115 provider: Provider,
116}
117
118struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
121 recorder: Option<ResolutionRecorder>,
122 project: Option<PackageName>,
123 requirements: Vec<Requirement>,
124 constraints: Constraints,
125 overrides: Overrides,
126 excludes: Excludes,
127 preferences: Preferences,
128 git: GitResolver,
129 capabilities: IndexCapabilities,
130 locations: IndexLocations,
131 exclusions: Exclusions,
132 urls: Urls,
133 indexes: Indexes,
134 dependency_mode: DependencyMode,
135 hasher: HashStrategy,
136 env: ResolverEnvironment,
137 current_environment: MarkerEnvironment,
139 tags: Option<Tags>,
140 python_requirement: PythonRequirement,
141 conflicts: Conflicts,
142 workspace_members: BTreeSet<PackageName>,
143 selector: CandidateSelector,
144 index: InMemoryIndex,
145 installed_packages: InstalledPackages,
146 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
149 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
151 options: Options,
153 reporter: Option<Arc<dyn Reporter>>,
155}
156
157impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
158 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
159{
160 pub fn new(
179 manifest: Manifest,
180 options: Options,
181 python_requirement: &'a PythonRequirement,
182 env: ResolverEnvironment,
183 current_environment: &MarkerEnvironment,
184 conflicts: Conflicts,
185 tags: Option<&'a Tags>,
186 flat_index: &'a FlatIndex,
187 index: &'a InMemoryIndex,
188 hasher: &'a HashStrategy,
189 build_context: &'a Context,
190 installed_packages: InstalledPackages,
191 database: DistributionDatabase<'a, Context>,
192 ) -> Result<Self, ResolveError> {
193 let provider = DefaultResolverProvider::new(
194 database,
195 flat_index,
196 tags,
197 python_requirement.target(),
198 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
199 hasher,
200 options.exclude_newer.clone(),
201 build_context.locations(),
202 build_context.build_options(),
203 build_context.capabilities(),
204 options.minimum_libc_version,
205 );
206
207 Ok(Self::new_custom_io(
208 manifest,
209 options,
210 hasher,
211 env,
212 current_environment,
213 tags.cloned(),
214 python_requirement,
215 conflicts,
216 index,
217 build_context.git(),
218 build_context.capabilities(),
219 build_context.locations(),
220 provider,
221 installed_packages,
222 ))
223 }
224}
225
226impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
227 Resolver<Provider, InstalledPackages>
228{
229 pub fn new_custom_io(
231 manifest: Manifest,
232 options: Options,
233 hasher: &HashStrategy,
234 env: ResolverEnvironment,
235 current_environment: &MarkerEnvironment,
236 tags: Option<Tags>,
237 python_requirement: &PythonRequirement,
238 conflicts: Conflicts,
239 index: &InMemoryIndex,
240 git: &GitResolver,
241 capabilities: &IndexCapabilities,
242 locations: &IndexLocations,
243 provider: Provider,
244 installed_packages: InstalledPackages,
245 ) -> Self {
246 let state = ResolverState {
247 index: index.clone(),
248 git: git.clone(),
249 capabilities: capabilities.clone(),
250 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
251 dependency_mode: options.dependency_mode,
252 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
253 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
254 recorder: manifest.recorder.clone(),
255 project: manifest.project,
256 workspace_members: manifest.workspace_members,
257 requirements: manifest.requirements,
258 constraints: manifest
259 .constraints
260 .with_recorder(manifest.recorder.clone()),
261 overrides: manifest.overrides.with_recorder(manifest.recorder.clone()),
262 excludes: manifest.excludes.with_recorder(manifest.recorder.clone()),
263 preferences: manifest.preferences,
264 exclusions: manifest.exclusions,
265 hasher: hasher.clone(),
266 locations: locations.clone(),
267 env,
268 current_environment: current_environment.clone(),
269 tags,
270 python_requirement: python_requirement.clone(),
271 conflicts,
272 installed_packages,
273 unavailable_packages: Box::default(),
274 incomplete_packages: Box::default(),
275 options,
276 reporter: None,
277 };
278 Self { state, provider }
279 }
280
281 #[must_use]
283 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
284 Self {
285 state: ResolverState {
286 reporter: Some(reporter.clone()),
287 ..self.state
288 },
289 provider: self
290 .provider
291 .with_reporter(reporter.into_distribution_reporter()),
292 }
293 }
294
295 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
297 let state = Arc::new(self.state);
298 let provider = Arc::new(self.provider);
299
300 let (request_sink, request_stream) = mpsc::channel(300);
304 let requests =
305 MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
306
307 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
309
310 let solver = state.clone();
312 let (tx, rx) = oneshot::channel();
313 thread::Builder::new()
314 .name("uv-resolver".into())
315 .spawn(move || {
316 let result = solver.solve(&requests);
317
318 let _ = tx.send(result);
320 })
321 .unwrap();
322
323 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
324
325 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
327
328 state.on_complete();
329 resolution
330 }
331}
332
333impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
334 #[instrument(skip_all)]
335 fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
336 debug!(
337 "Solving with installed Python version: {}",
338 self.python_requirement.exact()
339 );
340 debug!(
341 "Solving with target Python version: {}",
342 self.python_requirement.target()
343 );
344 if !self.options.exclude_newer.is_empty() {
345 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
346 }
347
348 let mut visited = FxHashSet::default();
349
350 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
351 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
352 let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
353 let state = ForkState::new(
354 pubgrub,
355 self.env.clone(),
356 self.python_requirement.clone(),
357 prefetcher,
358 );
359 let mut preferences = self.preferences.clone();
360 let mut forked_states = self.env.initial_forked_states(state)?;
361
362 match (self.options.fork_strategy, self.options.resolution_mode) {
368 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
369 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
370 }
371 (ForkStrategy::RequiresPython, _) => {
372 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
373 }
374 }
375 let mut resolutions = vec![];
376
377 'FORK: while let Some(mut state) = forked_states.pop() {
378 if let Some(split) = state.env.end_user_fork_display() {
379 let requires_python = state.python_requirement.target();
380 debug!("Solving {split} (requires-python: {requires_python:?})");
381 }
382 let start = Instant::now();
383 loop {
384 let continuation = mem::take(&mut state.continuation);
385 let (highest_priority_pkg, initial_version) = match continuation {
386 ForkContinuation::SelectVersion { package } => (package, None),
387 ForkContinuation::UseVersion { package, version } => (package, Some(version)),
388 ForkContinuation::Propagate => {
389 let result = state.pubgrub.unit_propagation(state.next);
391 match result {
392 Err(err) => {
393 return Err(self.convert_no_solution_err(
395 err,
396 state.fork_urls,
397 state.fork_indexes,
398 &state.known_versions.0,
399 state.env,
400 self.current_environment.clone(),
401 &visited,
402 ));
403 }
404 Ok(conflicts) => {
405 for (affected, incompatibility) in conflicts {
406 state.record_conflict(affected, None, incompatibility);
409 }
410 }
411 }
412
413 if self.dependency_mode.is_transitive() {
415 Self::pre_visit(
416 state.pubgrub.partial_solution.prioritized_packages().map(
417 |(id, range)| (id, &state.pubgrub.package_store[id], range),
418 ),
419 &mut state.pre_visited,
420 &self.urls,
421 &self.indexes,
422 &state.python_requirement,
423 requests,
424 )?;
425 }
426
427 state.reprioritize_conflicts();
428
429 trace!(
430 "Assigned packages: {}",
431 state
432 .pubgrub
433 .partial_solution
434 .extract_solution()
435 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
436 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
437 .join(", ")
438 );
439 let Some((highest_priority_pkg, _)) =
443 state.pubgrub.partial_solution.pick_highest_priority_pkg(
444 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
445 )
446 else {
447 if tracing::enabled!(Level::DEBUG) {
449 state.prefetcher.log_tried_versions();
450 }
451 debug!(
452 "{} resolution took {:.3}s",
453 state.env,
454 start.elapsed().as_secs_f32()
455 );
456
457 let resolution = state.into_resolution();
458
459 if matches!(
468 self.options.resolution_mode,
469 ResolutionMode::Lowest | ResolutionMode::Highest
470 ) {
471 let marker = resolution
472 .env
473 .try_universal_markers()
474 .unwrap_or(UniversalMarker::TRUE);
475 for (package, version) in &resolution.nodes {
476 preferences.insert(
477 package.name.clone(),
478 package.index.clone(),
479 marker,
480 version.clone(),
481 PreferenceSource::Resolver,
482 );
483 }
484 }
485
486 resolutions.push(resolution);
487 continue 'FORK;
488 };
489 trace!(
490 "Chose package for decision: {}. remaining choices: {}",
491 state.pubgrub.package_store[highest_priority_pkg],
492 state
493 .pubgrub
494 .partial_solution
495 .undecided_packages()
496 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
497 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
498 .join(", ")
499 );
500
501 (highest_priority_pkg, None)
502 }
503 };
504
505 state.next = highest_priority_pkg;
506
507 let next_id = state.next;
509 let next_package = &state.pubgrub.package_store[state.next];
510
511 let source =
512 PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
513
514 self.request_package(next_package, source, requests)?;
526
527 let version = if let Some(version) = initial_version {
528 version
529 } else {
530 let term_intersection = state
531 .pubgrub
532 .partial_solution
533 .term_intersection_for_package(next_id)
534 .expect("a package was chosen but we don't have a term");
535 let range = term_intersection.unwrap_positive();
536
537 let cache_selected_version = match source {
541 PackageSource::Registry(None) => true,
542 PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
543 };
544 let decision = if cache_selected_version
545 && let Some((selected_range, version)) =
546 state.selected_versions.get(&next_id)
547 && selected_range == range
548 {
549 Some(ResolverVersion::Unforked(version.clone()))
550 } else {
551 let decision = self.choose_version(
552 next_package,
553 next_id,
554 source,
555 range,
556 &mut state.pins,
557 &preferences,
558 &state.env,
559 &state.python_requirement,
560 &state.pubgrub,
561 &mut visited,
562 requests,
563 )?;
564
565 if cache_selected_version
566 && let Some(ResolverVersion::Unforked(version)) = &decision
567 {
568 state
569 .selected_versions
570 .insert(next_id, (range.clone(), version.clone()));
571 }
572
573 decision
574 };
575
576 let Some(version) = decision else {
578 debug!("No compatible version found for: {next_package}");
579
580 let term_intersection = state
581 .pubgrub
582 .partial_solution
583 .term_intersection_for_package(next_id)
584 .expect("a package was chosen but we don't have a term");
585
586 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
587 if let Some(reason) = self.unavailable_packages.pin().get(name) {
589 state
590 .pubgrub
591 .add_incompatibility(Incompatibility::custom_term(
592 next_id,
593 term_intersection.clone(),
594 UnavailableReason::Package(reason.clone()),
595 ));
596 continue;
597 }
598 }
599
600 state
601 .pubgrub
602 .add_incompatibility(Incompatibility::no_versions(
603 next_id,
604 term_intersection.clone(),
605 ));
606 continue;
607 };
608
609 let version = match version {
610 ResolverVersion::Unforked(version) => version,
611 ResolverVersion::Forked(forks) => {
612 forked_states.extend(self.version_forks_to_fork_states(state, forks));
613 continue 'FORK;
614 }
615 ResolverVersion::Unavailable(version, reason) => {
616 state.add_unavailable_version(
617 version,
618 reason,
619 &self.index,
620 &self.installed_packages,
621 );
622 continue;
623 }
624 };
625
626 if let PackageSource::Registry(index) = source {
628 state.prefetcher.prefetch_batches(
629 next_package,
630 index,
631 &version,
632 term_intersection.unwrap_positive(),
633 state
634 .pubgrub
635 .partial_solution
636 .unchanging_term_for_package(next_id),
637 &state.python_requirement,
638 &self.selector,
639 &state.env,
640 )?;
641 }
642
643 version
644 };
645
646 state.prefetcher.version_tried(next_package, &version);
647
648 self.on_progress(next_package, &version);
649
650 if state
651 .added_dependencies
652 .get(&next_id)
653 .is_some_and(|versions| versions.contains(&version))
654 {
655 state
658 .pubgrub
659 .partial_solution
660 .add_decision(next_id, version);
661 continue;
662 }
663
664 let forked_deps = self.get_dependencies_forking(
666 next_id,
667 next_package,
668 &version,
669 &state.pins,
670 &state.env,
671 &state.python_requirement,
672 &state.pubgrub,
673 )?;
674
675 match forked_deps {
676 ForkedDependencies::Unavailable(reason) => {
677 let versions = state.widen_version_to_gap(
680 &version,
681 &self.index,
682 &self.installed_packages,
683 );
684 state
685 .pubgrub
686 .add_incompatibility(Incompatibility::custom_term(
687 next_id,
688 Term::Positive(versions),
689 UnavailableReason::Version(reason),
690 ));
691 }
692 ForkedDependencies::Unforked(dependencies) => {
693 state
694 .added_dependencies
695 .entry(next_id)
696 .or_default()
697 .insert(version.clone());
698
699 state
701 .visit_package_version_dependencies(
702 next_id,
703 &version,
704 &self.urls,
705 &self.indexes,
706 &dependencies,
707 &self.git,
708 &self.workspace_members,
709 self.selector.resolution_strategy(),
710 )
711 .map_err(|err| {
712 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
713 })?;
714
715 self.visit_dependencies(&dependencies, &state, requests)
717 .map_err(|err| {
718 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
719 })?;
720
721 state.add_package_version_dependencies(
723 next_id,
724 &version,
725 dependencies,
726 &self.index,
727 &self.installed_packages,
728 );
729 }
730 ForkedDependencies::Forked {
731 mut forks,
732 diverging_packages,
733 } => {
734 state
735 .added_dependencies
736 .entry(next_id)
737 .or_default()
738 .insert(version.clone());
739
740 debug!(
741 "Pre-fork {} took {:.3}s",
742 state.env,
743 start.elapsed().as_secs_f32()
744 );
745
746 match (self.options.fork_strategy, self.options.resolution_mode) {
748 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
749 forks.sort_by(|a, b| {
753 a.cmp_requires_python(b)
754 .reverse()
755 .then_with(|| a.cmp_upper_bounds(b))
756 });
757 }
758 (ForkStrategy::RequiresPython, _) => {
759 forks.sort_by(|a, b| {
763 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
764 });
765 }
766 }
767
768 for new_fork_state in self.forks_to_fork_states(
769 state,
770 &version,
771 forks,
772 requests,
773 &diverging_packages,
774 ) {
775 forked_states.push(new_fork_state?);
776 }
777 continue 'FORK;
778 }
779 ForkedDependencies::RequiresPython(requires_python) => {
780 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
781 && state.env.marker_environment().is_none()
782 {
783 let forks = fork_version_by_python_requirement(
784 &requires_python,
785 &state.python_requirement,
786 &state.env,
787 );
788 if !forks.is_empty() {
789 debug!(
790 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
791 state.python_requirement.target(),
792 &requires_python,
793 next_package,
794 version,
795 forks
796 .iter()
797 .map(ToString::to_string)
798 .collect::<Vec<_>>()
799 .join(", ")
800 );
801
802 let forks = forks
805 .into_iter()
806 .map(|env| VersionFork {
807 env,
808 id: next_id,
809 version: None,
810 })
811 .collect();
812 forked_states
813 .extend(self.version_forks_to_fork_states(state, forks));
814 continue 'FORK;
815 }
816 }
817
818 let versions = state.widen_version_to_gap(
819 &version,
820 &self.index,
821 &self.installed_packages,
822 );
823 state
824 .pubgrub
825 .add_incompatibility(Incompatibility::custom_term(
826 next_id,
827 Term::Positive(versions),
828 UnavailableReason::Version(UnavailableVersion::RequiresPython(
829 requires_python,
830 )),
831 ));
832 }
833 }
834 }
835 }
836 if resolutions.len() > 1 {
837 info!(
838 "Solved your requirements for {} environments",
839 resolutions.len()
840 );
841 }
842 if tracing::enabled!(Level::DEBUG) {
843 for resolution in &resolutions {
844 if let Some(env) = resolution.env.end_user_fork_display() {
845 let packages: FxHashSet<_> = resolution
846 .nodes
847 .keys()
848 .map(|package| &package.name)
849 .collect();
850 debug!(
851 "Distinct solution for {env} with {} package(s)",
852 packages.len()
853 );
854 }
855 }
856 }
857 for resolution in &resolutions {
858 resolution.trace_resolution();
859 }
860 let resolutions = resolutions
861 .into_iter()
862 .map(|resolution| resolution.finalize(&self.index, &self.git))
863 .collect::<Result<Vec<_>, _>>()?;
864 crate::resolution::from_state(
865 resolutions,
866 self.project.as_ref(),
867 &self.workspace_members,
868 self.requirements.clone(),
869 self.constraints.clone(),
870 self.overrides.clone(),
871 &self.preferences,
872 &self.hasher,
873 &self.index,
874 self.python_requirement.target().clone(),
875 &self.conflicts,
876 self.selector.resolution_strategy(),
877 self.options.clone(),
878 )
879 }
880
881 fn forks_to_fork_states<'a, 'index: 'a>(
883 &'a self,
884 current_state: ForkState<'index>,
885 version: &'a Version,
886 forks: Vec<Fork>,
887 requests: &'a MetadataRequests,
888 diverging_packages: &'a BTreeSet<PackageName>,
889 ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
890 debug!(
891 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
892 current_state.pubgrub.package_store[current_state.next],
893 version,
894 diverging_packages
895 .iter()
896 .map(ToString::to_string)
897 .join(", "),
898 forks.len(),
899 if forks.len() == 1 { "" } else { "s" }
900 );
901 assert!(forks.len() >= 2);
902 let package = current_state.next;
908 let mut cur_state = Some(current_state);
909 let forks_len = forks.len();
910 forks
911 .into_iter()
912 .enumerate()
913 .map(move |(i, fork)| {
914 let is_last = i == forks_len - 1;
915 let forked_state = cur_state.take().unwrap();
916 if !is_last {
917 cur_state = Some(forked_state.clone());
918 }
919
920 let env = fork.env.clone();
921 (fork, forked_state.with_env(env))
922 })
923 .map(move |(fork, mut forked_state)| {
924 forked_state
926 .visit_package_version_dependencies(
927 package,
928 version,
929 &self.urls,
930 &self.indexes,
931 &fork.dependencies,
932 &self.git,
933 &self.workspace_members,
934 self.selector.resolution_strategy(),
935 )
936 .map_err(|err| {
937 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
938 })?;
939
940 self.visit_dependencies(&fork.dependencies, &forked_state, requests)
942 .map_err(|err| {
943 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
944 })?;
945
946 forked_state.add_package_version_dependencies(
948 package,
949 version,
950 fork.dependencies,
951 &self.index,
952 &self.installed_packages,
953 );
954
955 Ok(forked_state)
956 })
957 }
958
959 #[expect(clippy::unused_self)]
961 fn version_forks_to_fork_states<'index>(
962 &self,
963 current_state: ForkState<'index>,
964 forks: Vec<VersionFork>,
965 ) -> impl Iterator<Item = ForkState<'index>> {
966 let mut cur_state = Some(current_state);
972 let forks_len = forks.len();
973 forks.into_iter().enumerate().map(move |(i, fork)| {
974 let is_last = i == forks_len - 1;
975 let forked_state = cur_state.take().unwrap();
976 if !is_last {
977 cur_state = Some(forked_state.clone());
978 }
979 let continuation = match fork.version {
980 Some(version) => ForkContinuation::UseVersion {
981 package: fork.id,
982 version,
983 },
984 None => ForkContinuation::SelectVersion { package: fork.id },
985 };
986 forked_state
987 .with_env(fork.env)
988 .with_continuation(continuation)
989 })
990 }
991
992 fn visit_dependencies(
994 &self,
995 dependencies: &[PubGrubDependency],
996 state: &ForkState,
997 requests: &MetadataRequests,
998 ) -> Result<(), ResolveError> {
999 for dependency in dependencies {
1000 let PubGrubDependency {
1001 package,
1002 version: _,
1003 parent: _,
1004 source: _,
1005 } = dependency;
1006 let Some(source) = PackageSource::for_prefetch(
1007 package,
1008 &state.fork_urls,
1009 &state.fork_indexes,
1010 &self.urls,
1011 ) else {
1012 continue;
1013 };
1014 self.request_package(package, source, requests)?;
1015 }
1016 Ok(())
1017 }
1018
1019 fn request_package(
1020 &self,
1021 package: &PubGrubPackage,
1022 source: PackageSource<'_>,
1023 requests: &MetadataRequests,
1024 ) -> Result<(), ResolveError> {
1025 let Some(name) = package.name_no_root() else {
1027 return Ok(());
1028 };
1029
1030 match source {
1031 PackageSource::Url(url) => {
1032 if !self.hasher.allows_url(&url.verbatim) {
1034 return Err(ResolveError::UnhashedPackage(name.clone()));
1035 }
1036
1037 let dist = Dist::from_url(name.clone(), url.clone())?;
1039 requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1040 }
1041 PackageSource::Registry(index) => {
1042 requests.enqueue_package(name, index)?;
1043 }
1044 }
1045 Ok(())
1046 }
1047
1048 fn pre_visit<'data>(
1051 packages: impl Iterator<
1052 Item = (
1053 Id<PubGrubPackage>,
1054 &'data PubGrubPackage,
1055 &'data Range<Version>,
1056 ),
1057 >,
1058 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1059 urls: &Urls,
1060 indexes: &Indexes,
1061 python_requirement: &PythonRequirement,
1062 requests: &MetadataRequests,
1063 ) -> Result<(), ResolveError> {
1064 for (id, package, range) in packages {
1067 let PubGrubPackageInner::Package {
1068 name,
1069 kind: PackageNodeKind::Base,
1070 marker: MarkerTree::TRUE,
1071 } = &**package
1072 else {
1073 continue;
1074 };
1075 if urls.any_url(name) {
1078 continue;
1079 }
1080 if indexes.contains_key(name) {
1082 continue;
1083 }
1084 if pre_visited.get(&id) == Some(range) {
1087 continue;
1088 }
1089 pre_visited.insert(id, range.clone());
1090 requests.prefetch(name, range, python_requirement)?;
1091 }
1092 Ok(())
1093 }
1094
1095 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1103 fn choose_version<'index>(
1104 &self,
1105 package: &PubGrubPackage,
1106 id: Id<PubGrubPackage>,
1107 source: PackageSource<'_>,
1108 range: &Range<Version>,
1109 pins: &mut FilePins<'index>,
1110 preferences: &Preferences,
1111 env: &ResolverEnvironment,
1112 python_requirement: &PythonRequirement,
1113 pubgrub: &State<UvDependencyProvider>,
1114 visited: &mut FxHashSet<PackageName>,
1115 requests: &'index MetadataRequests,
1116 ) -> Result<Option<ResolverVersion>, ResolveError> {
1117 match &**package {
1118 PubGrubPackageInner::Root(_) => {
1119 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1120 }
1121
1122 PubGrubPackageInner::Python(_) => {
1123 Ok(None)
1126 }
1127
1128 PubGrubPackageInner::System(_) => {
1129 let Some(version) = range.as_singleton() else {
1132 return Ok(None);
1133 };
1134 Ok(Some(ResolverVersion::Unforked(version.clone())))
1135 }
1136
1137 PubGrubPackageInner::Marker { name, .. }
1138 | PubGrubPackageInner::Extra { name, .. }
1139 | PubGrubPackageInner::Group { name, .. }
1140 | PubGrubPackageInner::Package { name, .. } => match source {
1141 PackageSource::Url(url) => self.choose_version_url(
1142 id,
1143 name,
1144 range,
1145 url,
1146 env,
1147 python_requirement,
1148 pubgrub,
1149 pins,
1150 requests,
1151 ),
1152 PackageSource::Registry(index) => self.choose_version_registry(
1153 package,
1154 id,
1155 name,
1156 index,
1157 range,
1158 preferences,
1159 env,
1160 python_requirement,
1161 pubgrub,
1162 pins,
1163 visited,
1164 requests,
1165 ),
1166 },
1167 }
1168 }
1169
1170 fn choose_version_url<'index>(
1173 &self,
1174 id: Id<PubGrubPackage>,
1175 name: &PackageName,
1176 range: &Range<Version>,
1177 url: &VerbatimParsedUrl,
1178 env: &ResolverEnvironment,
1179 python_requirement: &PythonRequirement,
1180 pubgrub: &State<UvDependencyProvider>,
1181 pins: &mut FilePins<'index>,
1182 requests: &'index MetadataRequests,
1183 ) -> Result<Option<ResolverVersion>, ResolveError> {
1184 debug!(
1185 "Searching for a compatible version of {name} @ {} ({range})",
1186 url.verbatim
1187 );
1188
1189 let dist = Dist::from_url(name.clone(), url.clone())?;
1190 let registered = requests.metadata(&dist)?;
1191 let response = registered.wait();
1192
1193 let metadata = match &*response {
1195 MetadataResponse::Found(archive) => &archive.metadata,
1196 MetadataResponse::Unavailable(reason) => {
1197 self.unavailable_packages
1198 .pin()
1199 .insert(name.clone(), reason.into());
1200 return Ok(None);
1201 }
1202 MetadataResponse::Error(dist, err) => {
1205 return Err(ResolveError::Dist(
1206 DistErrorKind::from_requested_dist(dist, &**err),
1207 dist.clone(),
1208 DerivationChain::default(),
1209 err.clone(),
1210 ));
1211 }
1212 };
1213
1214 let version = &metadata.version;
1215
1216 if !range.contains(version) {
1218 return Ok(None);
1219 }
1220
1221 if let Dist::Built(dist) = &dist {
1224 let filename = match &dist {
1225 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1226 BuiltDist::DirectUrl(dist) => &dist.filename,
1227 BuiltDist::GitPath(dist) => &dist.filename,
1228 BuiltDist::Path(dist) => &dist.filename,
1229 };
1230
1231 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1233 {
1234 let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1235 for environment_marker in self.options.artifact_environments.iter().copied() {
1238 if env.included_by_marker(environment_marker)
1240 && env.included_by_marker(
1241 find_environments(id, pubgrub).and(environment_marker),
1242 )
1243 {
1244 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1246 return Ok(Some(ResolverVersion::Unavailable(
1247 version.clone(),
1248 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1249 IncompatibleWheel::MissingPlatform(environment_marker),
1250 )),
1251 )));
1252 }
1253 }
1254 }
1255 }
1256
1257 if !python_requirement.target().matches_wheel_tag(filename) {
1259 return Ok(Some(ResolverVersion::Unavailable(
1260 filename.version.clone(),
1261 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1262 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1263 )),
1264 )));
1265 }
1266 }
1267
1268 if let Some(requires_python) = metadata.requires_python.as_ref() {
1270 if !python_requirement.target().is_contained_by(requires_python) {
1271 let kind = if python_requirement.installed() == python_requirement.target() {
1272 PythonRequirementKind::Installed
1273 } else {
1274 PythonRequirementKind::Target
1275 };
1276 return Ok(Some(ResolverVersion::Unavailable(
1277 version.clone(),
1278 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1279 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1280 )),
1281 )));
1282 }
1283 }
1284
1285 pins.insert_url(name, version, registered);
1286 Ok(Some(ResolverVersion::Unforked(version.clone())))
1287 }
1288
1289 fn choose_version_registry<'index>(
1292 &self,
1293 package: &PubGrubPackage,
1294 id: Id<PubGrubPackage>,
1295 name: &PackageName,
1296 index: Option<&IndexMetadata>,
1297 range: &Range<Version>,
1298 preferences: &Preferences,
1299 env: &ResolverEnvironment,
1300 python_requirement: &PythonRequirement,
1301 pubgrub: &State<UvDependencyProvider>,
1302 pins: &mut FilePins<'index>,
1303 visited: &mut FxHashSet<PackageName>,
1304 requests: &'index MetadataRequests,
1305 ) -> Result<Option<ResolverVersion>, ResolveError> {
1306 let versions_response = requests.request_package(name, index)?.wait();
1308 let index = index.map(IndexMetadata::url);
1309 visited.insert(name.clone());
1310
1311 let version_maps = match *versions_response {
1312 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1313 VersionsResponse::NoIndex => {
1314 self.unavailable_packages
1315 .pin()
1316 .insert(name.clone(), UnavailablePackage::NoIndex);
1317 &[]
1318 }
1319 VersionsResponse::Offline => {
1320 self.unavailable_packages
1321 .pin()
1322 .insert(name.clone(), UnavailablePackage::Offline);
1323 &[]
1324 }
1325 VersionsResponse::NotFound => {
1326 self.unavailable_packages
1327 .pin()
1328 .insert(name.clone(), UnavailablePackage::NotFound);
1329 &[]
1330 }
1331 };
1332
1333 debug!("Searching for a compatible version of {package} ({range})");
1334
1335 let Some(candidate) = self.selector.select(
1337 name,
1338 range,
1339 version_maps,
1340 preferences,
1341 &self.installed_packages,
1342 &self.exclusions,
1343 index,
1344 env,
1345 self.tags.as_ref(),
1346 ) else {
1347 return Ok(None);
1349 };
1350
1351 let dist = match candidate.dist() {
1352 CandidateDist::Compatible(dist) => dist,
1353 CandidateDist::Incompatible {
1354 incompatible_dist: incompatibility,
1355 prioritized_dist: _,
1356 } => {
1357 return Ok(Some(ResolverVersion::Unavailable(
1359 candidate.version().clone(),
1360 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1363 )));
1364 }
1365 };
1366
1367 if let Some((requires_python, incompatibility)) =
1369 Self::check_requires_python(dist, python_requirement)
1370 {
1371 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1372 if env.marker_environment().is_none() {
1373 let forks = fork_version_by_python_requirement(
1374 requires_python,
1375 python_requirement,
1376 env,
1377 );
1378 if !forks.is_empty() {
1379 debug!(
1380 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1381 python_requirement.target(),
1382 requires_python,
1383 name,
1384 candidate.version(),
1385 forks
1386 .iter()
1387 .map(ToString::to_string)
1388 .collect::<Vec<_>>()
1389 .join(", ")
1390 );
1391 let forks = forks
1392 .into_iter()
1393 .map(|env| VersionFork {
1394 env,
1395 id,
1396 version: None,
1397 })
1398 .collect();
1399 return Ok(Some(ResolverVersion::Forked(forks)));
1400 }
1401 }
1402 }
1403
1404 return Ok(Some(ResolverVersion::Unavailable(
1405 candidate.version().clone(),
1406 UnavailableVersion::IncompatibleDist(incompatibility),
1407 )));
1408 }
1409
1410 if env.marker_environment().is_none()
1414 && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1415 && !dist
1416 .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1417 {
1418 return Ok(Some(ResolverVersion::Unavailable(
1419 candidate.version().clone(),
1420 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1421 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1422 )),
1423 )));
1424 }
1425
1426 if let Some(forked) = self.fork_version_registry(
1428 &candidate,
1429 dist,
1430 version_maps,
1431 package,
1432 id,
1433 name,
1434 index,
1435 range,
1436 preferences,
1437 env,
1438 pubgrub,
1439 pins,
1440 requests,
1441 )? {
1442 return Ok(Some(forked));
1443 }
1444
1445 let filename = match dist.for_installation() {
1446 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1447 .filename()
1448 .unwrap_or(Cow::Borrowed("unknown filename")),
1449 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1450 .filename()
1451 .unwrap_or(Cow::Borrowed("unknown filename")),
1452 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1453 };
1454
1455 debug!(
1456 "Selecting: {}=={} [{}] ({})",
1457 name,
1458 candidate.version(),
1459 candidate.choice_kind(),
1460 filename,
1461 );
1462 self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1463
1464 let version = candidate.version().clone();
1465 Ok(Some(ResolverVersion::Unforked(version)))
1466 }
1467
1468 fn fork_version_registry<'index>(
1481 &self,
1482 candidate: &Candidate,
1483 dist: &CompatibleDist,
1484 version_maps: &[VersionMap],
1485 package: &PubGrubPackage,
1486 id: Id<PubGrubPackage>,
1487 name: &PackageName,
1488 index: Option<&IndexUrl>,
1489 range: &Range<Version>,
1490 preferences: &Preferences,
1491 env: &ResolverEnvironment,
1492 pubgrub: &State<UvDependencyProvider>,
1493 pins: &mut FilePins<'index>,
1494 requests: &'index MetadataRequests,
1495 ) -> Result<Option<ResolverVersion>, ResolveError> {
1496 let Some(fork_markers) = env.fork_markers() else {
1498 return Ok(None);
1499 };
1500
1501 let artifact_markers = dist.implied_markers();
1502 if artifact_markers.is_true() {
1503 return Ok(None);
1504 }
1505
1506 for marker in self.options.artifact_environments.iter().copied() {
1509 if env.included_by_marker(marker) {
1511 if !env.included_by_marker(artifact_markers.and(marker))
1513 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1514 {
1515 let coverage = dist.implied_markers().and(fork_markers);
1520 let split = marker.restrict(marker.or(coverage));
1521 let Some((left, right)) = fork_version_by_marker(env, split) else {
1522 return Ok(Some(ResolverVersion::Unavailable(
1523 candidate.version().clone(),
1524 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1525 IncompatibleWheel::MissingPlatform(marker),
1526 )),
1527 )));
1528 };
1529
1530 debug!(
1531 "Forking on required platform `{}` for {}=={} ({})",
1532 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1533 name,
1534 candidate.version(),
1535 [&left, &right]
1536 .iter()
1537 .map(ToString::to_string)
1538 .collect::<Vec<_>>()
1539 .join(", ")
1540 );
1541 let forks = vec![
1542 VersionFork {
1543 env: left,
1544 id,
1545 version: None,
1546 },
1547 VersionFork {
1548 env: right,
1549 id,
1550 version: None,
1551 },
1552 ];
1553 return Ok(Some(ResolverVersion::Forked(forks)));
1554 }
1555 }
1556 }
1557
1558 if !candidate.version().is_local() {
1560 return Ok(None);
1561 }
1562
1563 debug!(
1564 "Looking at local version: {}=={}",
1565 name,
1566 candidate.version()
1567 );
1568
1569 let range = range.clone().intersection(&Range::singleton(
1571 candidate.version().clone().without_local(),
1572 ));
1573
1574 let Some(base_candidate) = self.selector.select(
1575 name,
1576 &range,
1577 version_maps,
1578 preferences,
1579 &self.installed_packages,
1580 &self.exclusions,
1581 index,
1582 env,
1583 self.tags.as_ref(),
1584 ) else {
1585 return Ok(None);
1586 };
1587 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1588 return Ok(None);
1589 };
1590
1591 let mut remainder = {
1593 let mut remainder = base_dist.implied_markers();
1594 remainder = remainder.and(artifact_markers.negate());
1595 remainder
1596 };
1597 if remainder.is_false() {
1598 return Ok(None);
1599 }
1600
1601 if !env.included_by_marker(remainder) {
1605 return Ok(None);
1606 }
1607
1608 if !env.included_by_marker(artifact_markers) {
1611 let filename = match dist.for_installation() {
1612 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1613 .filename()
1614 .unwrap_or(Cow::Borrowed("unknown filename")),
1615 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1616 .filename()
1617 .unwrap_or(Cow::Borrowed("unknown filename")),
1618 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1619 };
1620
1621 debug!(
1622 "Preferring non-local candidate: {}=={} [{}] ({})",
1623 name,
1624 base_candidate.version(),
1625 base_candidate.choice_kind(),
1626 filename,
1627 );
1628 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1629
1630 return Ok(Some(ResolverVersion::Unforked(
1631 base_candidate.version().clone(),
1632 )));
1633 }
1634
1635 for value in [
1644 arcstr::literal!("darwin"),
1645 arcstr::literal!("linux"),
1646 arcstr::literal!("win32"),
1647 ] {
1648 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1649 key: MarkerValueString::SysPlatform,
1650 operator: MarkerOperator::Equal,
1651 value,
1652 });
1653 if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1654 remainder = remainder.or(sys_platform);
1655 }
1656 }
1657
1658 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1660 return Ok(None);
1661 };
1662
1663 debug!(
1664 "Forking platform for {}=={} ({})",
1665 name,
1666 candidate.version(),
1667 [&base_env, &local_env]
1668 .iter()
1669 .map(ToString::to_string)
1670 .collect::<Vec<_>>()
1671 .join(", ")
1672 );
1673 self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1674 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1675
1676 let forks = vec![
1677 VersionFork {
1678 env: base_env.clone(),
1679 id,
1680 version: Some(base_candidate.version().clone()),
1681 },
1682 VersionFork {
1683 env: local_env.clone(),
1684 id,
1685 version: Some(candidate.version().clone()),
1686 },
1687 ];
1688 Ok(Some(ResolverVersion::Forked(forks)))
1689 }
1690
1691 fn visit_candidate<'index>(
1693 &self,
1694 candidate: &Candidate,
1695 dist: &CompatibleDist,
1696 package: &PubGrubPackage,
1697 name: &PackageName,
1698 pins: &mut FilePins<'index>,
1699 requests: &'index MetadataRequests,
1700 ) -> Result<(), ResolveError> {
1701 let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1702 && self.dependency_mode.is_transitive()
1703 {
1704 Some(|| {
1705 requests.request_metadata(
1706 MetadataRequest::Resolved(dist.for_resolution()),
1707 |request| {
1708 if name != request.name() {
1709 return Err(ResolveError::MismatchedPackageName {
1710 request: "distribution",
1711 expected: name.clone(),
1712 actual: request.name().clone(),
1713 });
1714 }
1715 if !self
1717 .hasher
1718 .allows_package(candidate.name(), candidate.version())
1719 {
1720 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1721 }
1722 Ok(())
1723 },
1724 )
1725 })
1726 } else {
1727 None
1728 };
1729 pins.insert(candidate, dist, request)
1730 }
1731
1732 fn check_requires_python<'dist>(
1735 dist: &'dist CompatibleDist,
1736 python_requirement: &PythonRequirement,
1737 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1738 let requires_python = dist.requires_python()?;
1739 if python_requirement.target().is_contained_by(requires_python) {
1740 None
1741 } else {
1742 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1743 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1744 requires_python.clone(),
1745 if python_requirement.installed() == python_requirement.target() {
1746 PythonRequirementKind::Installed
1747 } else {
1748 PythonRequirementKind::Target
1749 },
1750 ))
1751 } else {
1752 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1753 requires_python.clone(),
1754 if python_requirement.installed() == python_requirement.target() {
1755 PythonRequirementKind::Installed
1756 } else {
1757 PythonRequirementKind::Target
1758 },
1759 ))
1760 };
1761 Some((requires_python, incompatibility))
1762 }
1763 }
1764
1765 fn get_dependencies_forking(
1767 &self,
1768 id: Id<PubGrubPackage>,
1769 package: &PubGrubPackage,
1770 version: &Version,
1771 pins: &FilePins,
1772 env: &ResolverEnvironment,
1773 python_requirement: &PythonRequirement,
1774 pubgrub: &State<UvDependencyProvider>,
1775 ) -> Result<ForkedDependencies, ResolveError> {
1776 let dependencies =
1777 self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1778 if env.marker_environment().is_some() {
1779 Ok(ForkedDependencies::from_dependencies_platform_specific(
1780 dependencies,
1781 ))
1782 } else {
1783 Ok(ForkedDependencies::from_dependencies_universal(
1784 dependencies,
1785 env,
1786 python_requirement,
1787 &self.conflicts,
1788 ))
1789 }
1790 }
1791
1792 #[instrument(skip_all, fields(%package, %version))]
1794 fn get_dependencies(
1795 &self,
1796 id: Id<PubGrubPackage>,
1797 package: &PubGrubPackage,
1798 version: &Version,
1799 pins: &FilePins,
1800 env: &ResolverEnvironment,
1801 python_requirement: &PythonRequirement,
1802 pubgrub: &State<UvDependencyProvider>,
1803 ) -> Result<Dependencies, ResolveError> {
1804 let expander = RequirementExpander::new(
1805 &self.constraints,
1806 &self.overrides,
1807 &self.excludes,
1808 env,
1809 python_requirement,
1810 );
1811 let dependencies = match &**package {
1812 PubGrubPackageInner::Root(_) => {
1813 let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1814
1815 PubGrubDependency::from_requirements(
1816 &self.conflicts,
1817 requirements,
1818 None,
1819 Some(package),
1820 )
1821 }
1822
1823 PubGrubPackageInner::Package {
1824 name,
1825 kind,
1826 marker: _,
1827 } => {
1828 if self.dependency_mode.is_direct() {
1830 return Ok(Dependencies::Unforkable(Vec::default()));
1831 }
1832
1833 let registered = pins
1834 .metadata(name, version)
1835 .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1836
1837 if self.dependency_mode.is_transitive()
1839 && self.unavailable_packages.pin().contains_key(name)
1840 && self.installed_packages.get_packages(name).is_empty()
1841 {
1842 debug_assert!(
1843 false,
1844 "Dependencies were requested for a package that is not available"
1845 );
1846 return Err(ResolveError::PackageUnavailable(name.clone()));
1847 }
1848
1849 let response = registered.wait();
1851
1852 let metadata = match &*response {
1853 MetadataResponse::Found(archive) => &archive.metadata,
1854 MetadataResponse::Unavailable(reason) => {
1855 let unavailable_version = UnavailableVersion::from(reason);
1856 let message = unavailable_version.singular_message();
1857 if let Some(err) = reason.source() {
1858 warn!("{name} {message}: {err}");
1860 } else {
1861 warn!("{name} {message}");
1862 }
1863 let incomplete_packages = self.incomplete_packages.pin();
1864 let versions = incomplete_packages.get_or_insert(
1865 name.clone(),
1866 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1867 );
1868 versions.pin().insert(version.clone(), reason.clone());
1869 return Ok(Dependencies::Unavailable(unavailable_version));
1870 }
1871 MetadataResponse::Error(dist, err) => {
1872 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1873 .unwrap_or_default();
1874 return Err(ResolveError::Dist(
1875 DistErrorKind::from_requested_dist(dist, &**err),
1876 dist.clone(),
1877 chain,
1878 err.clone(),
1879 ));
1880 }
1881 };
1882
1883 if let Some(requires_python) = &metadata.requires_python {
1886 if !python_requirement.target().is_contained_by(requires_python) {
1887 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1888 }
1889 }
1890
1891 let system_dependencies = self
1893 .options
1894 .torch_backend
1895 .as_ref()
1896 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1897 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1898 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1899 .map(IndexUrl::url)
1900 .and_then(SystemDependency::from_index)
1901 .into_iter()
1902 .inspect(|system_dependency| {
1903 debug!(
1904 "Adding system dependency `{}` for `{package}@{version}`",
1905 system_dependency
1906 );
1907 })
1908 .map(PubGrubDependency::from);
1909
1910 let (requirements, context) = match kind {
1911 PackageNodeKind::Group(group) => (
1912 metadata
1913 .dependency_groups
1914 .get(group)
1915 .map_or(&[][..], AsRef::as_ref),
1916 RequirementContext::Group { name, version },
1917 ),
1918 PackageNodeKind::Extra(extra) => (
1919 metadata.requires_dist.as_ref(),
1920 RequirementContext::Extra {
1921 name,
1922 version,
1923 extra,
1924 },
1925 ),
1926 PackageNodeKind::Base => (
1927 metadata.requires_dist.as_ref(),
1928 RequirementContext::Package { name, version },
1929 ),
1930 };
1931 let requirements = expander.expand(requirements, context);
1932
1933 PubGrubDependency::from_requirements(
1934 &self.conflicts,
1935 requirements,
1936 kind.group(),
1937 Some(package),
1938 )
1939 .map(|mut dependencies| {
1940 dependencies.extend(system_dependencies);
1941 dependencies
1942 })
1943 }
1944
1945 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1946
1947 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1948
1949 PubGrubPackageInner::Marker { name, marker } => {
1951 return Ok(Dependencies::Unforkable(
1952 [MarkerTree::TRUE, *marker]
1953 .into_iter()
1954 .map(move |marker| PubGrubDependency {
1955 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1956 name: name.clone(),
1957 kind: PackageNodeKind::Base,
1958 marker,
1959 }),
1960 version: Range::singleton(version.clone()),
1961 parent: None,
1962 source: DependencySource::Unspecified,
1963 })
1964 .collect(),
1965 ));
1966 }
1967
1968 PubGrubPackageInner::Extra {
1970 name,
1971 extra,
1972 marker,
1973 } => {
1974 return Ok(Dependencies::Unforkable(
1975 [MarkerTree::TRUE, *marker]
1976 .into_iter()
1977 .dedup()
1978 .flat_map(move |marker| {
1979 [None, Some(extra)]
1980 .into_iter()
1981 .map(move |extra| PubGrubDependency {
1982 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1983 name: name.clone(),
1984 kind: extra
1985 .cloned()
1986 .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
1987 marker,
1988 }),
1989 version: Range::singleton(version.clone()),
1990 parent: None,
1991 source: DependencySource::Unspecified,
1992 })
1993 })
1994 .collect(),
1995 ));
1996 }
1997
1998 PubGrubPackageInner::Group {
2000 name,
2001 group,
2002 marker,
2003 } => {
2004 return Ok(Dependencies::Unforkable(
2005 [MarkerTree::TRUE, *marker]
2006 .into_iter()
2007 .dedup()
2008 .map(|marker| PubGrubDependency {
2009 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2010 name: name.clone(),
2011 kind: PackageNodeKind::Group(group.clone()),
2012 marker,
2013 }),
2014 version: Range::singleton(version.clone()),
2015 parent: None,
2016 source: DependencySource::Unspecified,
2017 })
2018 .collect(),
2019 ));
2020 }
2021 };
2022 Ok(match dependencies {
2023 Ok(dependencies) => Dependencies::Available(dependencies),
2024 Err(requirement) => {
2025 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2026 }
2027 })
2028 }
2029
2030 async fn fetch<Provider: ResolverProvider>(
2032 self: Arc<Self>,
2033 provider: Arc<Provider>,
2034 request_stream: Receiver<Request>,
2035 ) -> Result<(), ResolveError> {
2036 let mut response_stream = ReceiverStream::new(request_stream)
2037 .map(|request| self.process_request(request, &*provider).boxed_local())
2038 .buffer_unordered(usize::MAX);
2042
2043 while let Some(response) = response_stream.next().await {
2044 match response? {
2045 Some(Response::Package(name, index, version_map)) => {
2046 trace!("Received package metadata for: {name}");
2047 if let Some(index) = index {
2048 self.index
2049 .explicit()
2050 .done((name, index), Arc::new(version_map));
2051 } else {
2052 self.index.implicit().done(name, Arc::new(version_map));
2053 }
2054 }
2055 Some(Response::Installed { dist, metadata }) => {
2056 trace!("Received installed distribution metadata for: {dist}");
2057 self.index
2058 .distributions()
2059 .done(dist.distribution_id(), Arc::new(metadata));
2060 }
2061 Some(Response::Dist { dist, metadata }) => {
2062 let dist_kind = match dist {
2063 Dist::Built(_) => "built",
2064 Dist::Source(_) => "source",
2065 };
2066 trace!("Received {dist_kind} distribution metadata for: {dist}");
2067 if let MetadataResponse::Unavailable(reason) = &metadata {
2068 let message = UnavailableVersion::from(reason).singular_message();
2069 if let Some(err) = reason.source() {
2070 warn!("{dist} {message}: {err}");
2072 } else {
2073 warn!("{dist} {message}");
2074 }
2075 }
2076 self.index
2077 .distributions()
2078 .done(dist.distribution_id(), Arc::new(metadata));
2079 }
2080 None => {}
2081 }
2082 }
2083
2084 Ok::<(), ResolveError>(())
2085 }
2086
2087 #[instrument(skip_all, fields(%request))]
2088 async fn process_request<Provider: ResolverProvider>(
2089 &self,
2090 request: Request,
2091 provider: &Provider,
2092 ) -> Result<Option<Response>, ResolveError> {
2093 match request {
2094 Request::Package(package_name, index) => {
2096 let package_versions = provider
2097 .get_package_versions(&package_name, index.as_ref())
2098 .boxed_local()
2099 .await
2100 .map_err(ResolveError::Client)?;
2101
2102 Ok(Some(Response::Package(
2103 package_name,
2104 index.map(IndexMetadata::into_url),
2105 package_versions,
2106 )))
2107 }
2108
2109 Request::Dist(dist) => {
2111 if let Some(version) = dist.version() {
2112 if let Some(index) = dist.index() {
2113 let versions_response = self.index.implicit().get(dist.name());
2115 if let Some(VersionsResponse::Found(version_maps)) =
2116 versions_response.as_deref()
2117 {
2118 for version_map in version_maps {
2119 if version_map.index() == Some(index) {
2120 let Some(metadata) = version_map.get_metadata(version) else {
2121 continue;
2122 };
2123 debug!("Found registry-provided metadata for: {dist}");
2124 return Ok(Some(Response::Dist {
2125 dist,
2126 metadata: MetadataResponse::Found(
2127 ArchiveMetadata::from_metadata23(metadata),
2128 ),
2129 }));
2130 }
2131 }
2132 }
2133
2134 let versions_response = self
2136 .index
2137 .explicit()
2138 .get(&(dist.name().clone(), index.clone()));
2139 if let Some(VersionsResponse::Found(version_maps)) =
2140 versions_response.as_deref()
2141 {
2142 for version_map in version_maps {
2143 let Some(metadata) = version_map.get_metadata(version) else {
2144 continue;
2145 };
2146 debug!("Found registry-provided metadata for: {dist}");
2147 return Ok(Some(Response::Dist {
2148 dist,
2149 metadata: MetadataResponse::Found(
2150 ArchiveMetadata::from_metadata23(metadata),
2151 ),
2152 }));
2153 }
2154 }
2155 }
2156 }
2157
2158 let metadata = provider
2159 .get_or_build_wheel_metadata(&dist)
2160 .boxed_local()
2161 .await?;
2162
2163 if let MetadataResponse::Found(metadata) = &metadata {
2164 if &metadata.metadata.name != dist.name() {
2165 return Err(ResolveError::MismatchedPackageName {
2166 request: "distribution metadata",
2167 expected: dist.name().clone(),
2168 actual: metadata.metadata.name.clone(),
2169 });
2170 }
2171 }
2172
2173 Ok(Some(Response::Dist { dist, metadata }))
2174 }
2175
2176 Request::Installed(dist) => {
2177 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2178
2179 if let MetadataResponse::Found(metadata) = &metadata {
2180 if &metadata.metadata.name != dist.name() {
2181 return Err(ResolveError::MismatchedPackageName {
2182 request: "installed metadata",
2183 expected: dist.name().clone(),
2184 actual: metadata.metadata.name.clone(),
2185 });
2186 }
2187 }
2188
2189 Ok(Some(Response::Installed { dist, metadata }))
2190 }
2191
2192 Request::Prefetch(package_name, range, python_requirement) => {
2194 let versions_response = self
2196 .index
2197 .implicit()
2198 .get_registered(package_name.clone())
2199 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2200 .wait()
2201 .await;
2202
2203 let version_map = match *versions_response {
2204 VersionsResponse::Found(ref version_map) => version_map,
2205 VersionsResponse::NoIndex => {
2207 self.unavailable_packages
2208 .pin()
2209 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2210
2211 return Ok(None);
2212 }
2213 VersionsResponse::Offline => {
2214 self.unavailable_packages
2215 .pin()
2216 .insert(package_name.clone(), UnavailablePackage::Offline);
2217
2218 return Ok(None);
2219 }
2220 VersionsResponse::NotFound => {
2221 self.unavailable_packages
2222 .pin()
2223 .insert(package_name.clone(), UnavailablePackage::NotFound);
2224
2225 return Ok(None);
2226 }
2227 };
2228
2229 let env = ResolverEnvironment::universal(vec![]);
2231
2232 let Some(candidate) = self.selector.select(
2235 &package_name,
2236 &range,
2237 version_map,
2238 &self.preferences,
2239 &self.installed_packages,
2240 &self.exclusions,
2241 None,
2242 &env,
2243 self.tags.as_ref(),
2244 ) else {
2245 return Ok(None);
2246 };
2247
2248 let Some(dist) = candidate.compatible() else {
2250 return Ok(None);
2251 };
2252
2253 for version_map in version_map {
2255 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2256 let dist = dist.for_resolution();
2257 if version_map.index() == dist.index() {
2258 debug!("Found registry-provided metadata for: {dist}");
2259
2260 let metadata =
2261 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2262
2263 let dist = dist.to_owned();
2264 if &package_name != dist.name() {
2265 return Err(ResolveError::MismatchedPackageName {
2266 request: "distribution",
2267 expected: package_name,
2268 actual: dist.name().clone(),
2269 });
2270 }
2271
2272 let response = match dist {
2273 ResolvedDist::Installable { dist, .. } => Response::Dist {
2274 dist: (*dist).clone(),
2275 metadata,
2276 },
2277 ResolvedDist::Installed { dist } => Response::Installed {
2278 dist: (*dist).clone(),
2279 metadata,
2280 },
2281 };
2282
2283 return Ok(Some(response));
2284 }
2285 }
2286 }
2287
2288 if dist.wheel().is_none() {
2292 if !self.selector.use_highest_version(&package_name, &env) {
2293 if let Some((lower, _)) = range.iter().next() {
2294 if lower == Bound::Unbounded {
2295 debug!(
2296 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2297 );
2298 return Ok(None);
2299 }
2300 }
2301 }
2302 }
2303
2304 if let Some(requires_python) = dist.requires_python() {
2306 if !python_requirement.target().is_contained_by(requires_python) {
2307 return Ok(None);
2308 }
2309 }
2310
2311 if !self
2313 .hasher
2314 .allows_package(candidate.name(), candidate.version())
2315 {
2316 return Ok(None);
2317 }
2318
2319 let dist = dist.for_resolution();
2321 if self.index.distributions().register(dist.distribution_id()) {
2322 let dist = dist.to_owned();
2323 if &package_name != dist.name() {
2324 return Err(ResolveError::MismatchedPackageName {
2325 request: "distribution",
2326 expected: package_name,
2327 actual: dist.name().clone(),
2328 });
2329 }
2330
2331 let response = match dist {
2332 ResolvedDist::Installable { dist, .. } => {
2333 let metadata = provider
2334 .get_or_build_wheel_metadata(&dist)
2335 .boxed_local()
2336 .await?;
2337
2338 Response::Dist {
2339 dist: (*dist).clone(),
2340 metadata,
2341 }
2342 }
2343 ResolvedDist::Installed { dist } => {
2344 let metadata =
2345 provider.get_installed_metadata(&dist).boxed_local().await?;
2346
2347 Response::Installed {
2348 dist: (*dist).clone(),
2349 metadata,
2350 }
2351 }
2352 };
2353
2354 Ok(Some(response))
2355 } else {
2356 Ok(None)
2357 }
2358 }
2359 }
2360 }
2361
2362 fn convert_no_solution_err(
2363 &self,
2364 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2365 fork_urls: ForkUrls,
2366 fork_indexes: ForkIndexes,
2367 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2368 env: ResolverEnvironment,
2369 current_environment: MarkerEnvironment,
2370 visited: &FxHashSet<PackageName>,
2371 ) -> ResolveError {
2372 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2373 err,
2374 ));
2375 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2376
2377 let mut unavailable_packages = FxHashMap::default();
2378 for package in derivation_tree_packages(&err) {
2379 if let PubGrubPackageInner::Package { name, .. } = &**package {
2380 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2381 unavailable_packages.insert(name.clone(), reason.clone());
2382 }
2383 }
2384 }
2385
2386 let mut incomplete_packages = FxHashMap::default();
2387 let incomplete_packages_cache = self.incomplete_packages.pin();
2388 for package in derivation_tree_packages(&err) {
2389 if let PubGrubPackageInner::Package { name, .. } = &**package
2390 && let Some(versions) = incomplete_packages_cache.get(name)
2391 {
2392 for (version, reason) in &versions.pin() {
2393 incomplete_packages
2394 .entry(name.clone())
2395 .or_insert_with(BTreeMap::default)
2396 .insert(version.clone(), reason.clone());
2397 }
2398 }
2399 }
2400
2401 let mut available_indexes = FxHashMap::default();
2402 let mut included_versions = FxHashMap::default();
2403 let mut available_versions = FxHashMap::default();
2404
2405 let available_version_cutoff: Option<jiff::Timestamp> =
2406 std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2407 .ok()
2408 .and_then(|s| s.parse().ok());
2409
2410 for package in derivation_tree_packages(&err) {
2411 let Some(name) = package.name() else { continue };
2412 if !visited.contains(name) {
2413 continue;
2418 }
2419 let versions_response = if let Some(index) = fork_indexes.get(name) {
2420 self.index
2421 .explicit()
2422 .get(&(name.clone(), index.url().clone()))
2423 } else {
2424 self.index.implicit().get(name)
2425 };
2426 if let Some(response) = versions_response {
2427 if let VersionsResponse::Found(ref version_maps) = *response {
2428 for version_map in version_maps {
2430 let package_included_versions = included_versions
2431 .entry(name.clone())
2432 .or_insert_with(BTreeSet::new);
2433 let package_available_versions = available_versions
2434 .entry(name.clone())
2435 .or_insert_with(BTreeSet::new);
2436
2437 for (version, dists) in version_map.iter(&Ranges::full()) {
2438 let excluded_from_included = || {
2443 let Some(included_version_cutoff) =
2444 version_map.included_version_cutoff()
2445 else {
2446 return false;
2447 };
2448 let Some(prioritized_dist) = dists.prioritized_dist() else {
2449 return true;
2450 };
2451 prioritized_dist.files().all(|file| {
2452 file.upload_time_utc_ms.is_none_or(|upload_time| {
2453 upload_time >= included_version_cutoff.as_millisecond()
2454 })
2455 })
2456 };
2457
2458 if !excluded_from_included() {
2459 package_included_versions.insert(version.clone());
2460 }
2461
2462 let excluded_from_available = || {
2468 let Some(ref exclude_newer) = available_version_cutoff else {
2469 return false;
2470 };
2471 let Some(prioritized_dist) = dists.prioritized_dist() else {
2472 return false;
2473 };
2474 prioritized_dist.files().all(|file| {
2475 file.upload_time_utc_ms.is_some_and(|upload_time| {
2476 upload_time >= exclude_newer.as_millisecond()
2477 })
2478 })
2479 };
2480
2481 if !excluded_from_available() {
2482 package_available_versions.insert(version.clone());
2483 }
2484 }
2485 }
2486
2487 available_indexes
2489 .entry(name.clone())
2490 .or_insert(BTreeSet::new())
2491 .extend(
2492 version_maps
2493 .iter()
2494 .filter_map(|version_map| version_map.index().cloned()),
2495 );
2496 }
2497 }
2498 }
2499
2500 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2501 err,
2502 self.index.clone(),
2503 included_versions,
2504 available_versions,
2505 available_indexes,
2506 self.selector.clone(),
2507 self.python_requirement.clone(),
2508 self.locations.clone(),
2509 self.capabilities.clone(),
2510 unavailable_packages,
2511 incomplete_packages,
2512 fork_urls,
2513 fork_indexes,
2514 env,
2515 current_environment,
2516 self.tags.clone(),
2517 self.workspace_members.clone(),
2518 self.options.clone(),
2519 )))
2520 }
2521
2522 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2523 if let Some(reporter) = self.reporter.as_ref() {
2524 match &**package {
2525 PubGrubPackageInner::Root(_) => {}
2526 PubGrubPackageInner::Python(_) => {}
2527 PubGrubPackageInner::System(_) => {}
2528 PubGrubPackageInner::Marker { .. } => {}
2529 PubGrubPackageInner::Extra { .. } => {}
2530 PubGrubPackageInner::Group { .. } => {}
2531 PubGrubPackageInner::Package { name, .. } => {
2532 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2533 }
2534 }
2535 }
2536 }
2537
2538 fn on_complete(&self) {
2539 if let Some(reporter) = self.reporter.as_ref() {
2540 reporter.on_complete();
2541 }
2542 }
2543}
2544
2545#[derive(Clone, Default)]
2548struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2549
2550impl KnownVersions {
2551 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2564 &'a mut self,
2565 index: &InMemoryIndex,
2566 installed_packages: &InstalledPackages,
2567 source: PackageSource<'_>,
2568 package: &PubGrubPackage,
2569 ) -> Option<&'a [Version]> {
2570 let name = package.name_no_root()?;
2571 let PackageSource::Registry(index_metadata) = source else {
2573 return None;
2574 };
2575 if !self.0.contains_key(name) {
2576 let response = if let Some(index_metadata) = index_metadata {
2577 index
2578 .explicit()
2579 .get(&(name.clone(), index_metadata.url().clone()))?
2580 } else {
2581 index.implicit().get(name)?
2582 };
2583 let VersionsResponse::Found(ref version_maps) = *response else {
2584 return None;
2585 };
2586 let mut versions: Vec<Version> = version_maps
2587 .iter()
2588 .flat_map(|version_map| version_map.included_versions().cloned())
2589 .chain(
2590 installed_packages
2591 .get_packages(name)
2592 .iter()
2593 .map(|dist| dist.version().clone()),
2594 )
2595 .collect();
2596 versions.sort_unstable();
2597 versions.dedup();
2598 self.0.insert(name.clone(), versions.into());
2599 }
2600 Some(&self.0[name][..])
2601 }
2602}
2603
2604#[derive(Clone, Default)]
2606enum ForkContinuation {
2607 #[default]
2609 Propagate,
2610 SelectVersion { package: Id<PubGrubPackage> },
2612 UseVersion {
2614 package: Id<PubGrubPackage>,
2615 version: Version,
2616 },
2617}
2618
2619#[derive(Clone)]
2621pub(crate) struct ForkState<'index> {
2622 pubgrub: State<UvDependencyProvider>,
2630 continuation: ForkContinuation,
2632 next: Id<PubGrubPackage>,
2634 pins: FilePins<'index>,
2643 fork_urls: ForkUrls,
2649 fork_indexes: ForkIndexes,
2654 priorities: PubGrubPriorities,
2660 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2663 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2665 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2667 known_versions: KnownVersions,
2671 env: ResolverEnvironment,
2686 python_requirement: PythonRequirement,
2699 conflict_tracker: ConflictTracker,
2700 prefetcher: BatchPrefetcher,
2704}
2705
2706impl<'index> ForkState<'index> {
2707 fn new(
2708 pubgrub: State<UvDependencyProvider>,
2709 env: ResolverEnvironment,
2710 python_requirement: PythonRequirement,
2711 prefetcher: BatchPrefetcher,
2712 ) -> Self {
2713 Self {
2714 continuation: ForkContinuation::Propagate,
2715 next: pubgrub.root_package,
2716 pubgrub,
2717 pins: FilePins::default(),
2718 fork_urls: ForkUrls::default(),
2719 fork_indexes: ForkIndexes::default(),
2720 priorities: PubGrubPriorities::default(),
2721 added_dependencies: FxHashMap::default(),
2722 pre_visited: FxHashMap::default(),
2723 selected_versions: FxHashMap::default(),
2724 known_versions: KnownVersions::default(),
2725 env,
2726 python_requirement,
2727 conflict_tracker: ConflictTracker::default(),
2728 prefetcher,
2729 }
2730 }
2731
2732 fn visit_package_version_dependencies(
2735 &mut self,
2736 for_package: Id<PubGrubPackage>,
2737 for_version: &Version,
2738 urls: &Urls,
2739 indexes: &Indexes,
2740 dependencies: &[PubGrubDependency],
2741 git: &GitResolver,
2742 workspace_members: &BTreeSet<PackageName>,
2743 resolution_strategy: &ResolutionStrategy,
2744 ) -> Result<(), ResolveError> {
2745 for dependency in dependencies {
2746 let PubGrubDependency {
2747 package,
2748 version,
2749 parent: _,
2750 source,
2751 } = dependency;
2752
2753 let mut has_url = false;
2754 if let Some(name) = package.name() {
2755 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2760 self.fork_urls.insert(name, url, &self.env)?;
2761 has_url = true;
2762 }
2763
2764 if let Some(index) = source.explicit_index() {
2765 self.fork_indexes.insert(name, index, &self.env)?;
2766 }
2767
2768 for index in indexes.get(name, &self.env) {
2770 self.fork_indexes.insert(name, index, &self.env)?;
2771 }
2772 }
2773
2774 if let Some(name) = self.pubgrub.package_store[for_package]
2775 .name_no_root()
2776 .filter(|name| !workspace_members.contains(name))
2777 {
2778 debug!(
2779 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2780 );
2781 } else {
2782 debug!("Adding direct dependency: {package}{version}");
2784
2785 let missing_lower_bound = version
2787 .bounding_range()
2788 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2789 let strategy_lowest = matches!(
2790 resolution_strategy,
2791 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2792 );
2793
2794 if !has_url && missing_lower_bound && strategy_lowest {
2795 let name = package.name_no_root().unwrap();
2796 let bound_on_other_package = dependencies.iter().any(|other| {
2803 Some(name) == other.package.name()
2804 && !other
2805 .version
2806 .bounding_range()
2807 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2808 });
2809
2810 if !bound_on_other_package {
2811 warn_user_once!(
2812 "The direct dependency `{name}` is unpinned. \
2813 Consider setting a lower bound when using `--resolution lowest` \
2814 or `--resolution lowest-direct` to avoid using outdated versions.",
2815 );
2816 }
2817 }
2818 }
2819
2820 self.priorities.insert(package, version, &self.fork_urls);
2822 if let Some(base_package) = package.base_package() {
2825 self.priorities
2826 .insert(&base_package, version, &self.fork_urls);
2827 }
2828 }
2829
2830 Ok(())
2831 }
2832
2833 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2839 &mut self,
2840 for_package: Id<PubGrubPackage>,
2841 for_version: &Version,
2842 dependencies: Vec<PubGrubDependency>,
2843 index: &InMemoryIndex,
2844 installed_packages: &InstalledPackages,
2845 ) {
2846 for dependency in &dependencies {
2847 let PubGrubDependency {
2848 package,
2849 version,
2850 parent: _,
2851 source: _,
2852 } = dependency;
2853
2854 let Some(base_package) = package.base_package() else {
2855 continue;
2856 };
2857
2858 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2859 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2860 self.pubgrub.add_proxy_package_incompatibility(
2861 proxy_package,
2862 base_package_id,
2863 version.clone(),
2864 );
2865 }
2866
2867 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2870 let conflict = self.pubgrub.add_package_version_dependencies(
2871 self.next,
2872 for_version.clone(),
2873 versions,
2874 dependencies.into_iter().map(|dependency| {
2875 let PubGrubDependency {
2876 package,
2877 version,
2878 parent: _,
2879 source: _,
2880 } = dependency;
2881 (package, version)
2882 }),
2883 );
2884
2885 if let Some(incompatibility) = conflict {
2888 self.record_conflict(for_package, Some(for_version), incompatibility);
2889 }
2890 }
2891
2892 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2894 &mut self,
2895 version: &Version,
2896 index: &InMemoryIndex,
2897 installed_packages: &InstalledPackages,
2898 ) -> Range<Version> {
2899 widen_to_gap(
2900 version,
2901 self.known_versions.get_or_update(
2902 index,
2903 installed_packages,
2904 PackageSource::from_fork(
2905 &self.pubgrub.package_store[self.next],
2906 &self.fork_urls,
2907 &self.fork_indexes,
2908 ),
2909 &self.pubgrub.package_store[self.next],
2910 ),
2911 )
2912 }
2913
2914 fn record_conflict(
2915 &mut self,
2916 affected: Id<PubGrubPackage>,
2917 version: Option<&Version>,
2918 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2919 ) {
2920 let mut culprit_is_real = false;
2921 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2922 if incompatible == affected {
2923 continue;
2924 }
2925 if self.pubgrub.package_store[affected].name()
2926 == self.pubgrub.package_store[incompatible].name()
2927 {
2928 continue;
2931 }
2932 culprit_is_real = true;
2933 let culprit_count = self
2934 .conflict_tracker
2935 .culprit
2936 .entry(incompatible)
2937 .or_default();
2938 *culprit_count += 1;
2939 if *culprit_count == CONFLICT_THRESHOLD {
2940 self.conflict_tracker.deprioritize.push(incompatible);
2941 }
2942 }
2943 if culprit_is_real {
2946 if tracing::enabled!(Level::DEBUG) {
2947 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
2948 .iter()
2949 .map(|(package, _term)| &self.pubgrub.package_store[package])
2950 .join(", ");
2951 if let Some(version) = version {
2952 debug!(
2953 "Recording dependency conflict of {}=={} from incompatibility of ({})",
2954 self.pubgrub.package_store[affected], version, incompatibility
2955 );
2956 } else {
2957 debug!(
2958 "Recording unit propagation conflict of {} from incompatibility of ({})",
2959 self.pubgrub.package_store[affected], incompatibility
2960 );
2961 }
2962 }
2963
2964 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
2965 *affected_count += 1;
2966 if *affected_count == CONFLICT_THRESHOLD {
2967 self.conflict_tracker.prioritize.push(self.next);
2968 }
2969 }
2970 }
2971
2972 fn reprioritize_conflicts(&mut self) {
2976 for package in self.conflict_tracker.prioritize.drain(..) {
2977 let changed = self
2978 .priorities
2979 .mark_conflict_early(&self.pubgrub.package_store[package]);
2980 if changed {
2981 debug!(
2982 "Package {} has too many conflicts (affected), prioritizing",
2983 &self.pubgrub.package_store[package]
2984 );
2985 } else {
2986 debug!(
2987 "Package {} has too many conflicts (affected), already {:?}",
2988 self.pubgrub.package_store[package],
2989 self.priorities.get(&self.pubgrub.package_store[package])
2990 );
2991 }
2992 }
2993
2994 for package in self.conflict_tracker.deprioritize.drain(..) {
2995 let changed = self
2996 .priorities
2997 .mark_conflict_late(&self.pubgrub.package_store[package]);
2998 if changed {
2999 debug!(
3000 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3001 self.pubgrub.package_store[package],
3002 );
3003 let backtrack_level = self.pubgrub.backtrack_package(package);
3004 if let Some(backtrack_level) = backtrack_level {
3005 debug!("Backtracked {backtrack_level} decisions");
3006 } else {
3007 debug!(
3008 "Package {} is not decided, cannot backtrack",
3009 self.pubgrub.package_store[package]
3010 );
3011 }
3012 } else {
3013 debug!(
3014 "Package {} has too many conflicts (culprit), already {:?}",
3015 self.pubgrub.package_store[package],
3016 self.priorities.get(&self.pubgrub.package_store[package])
3017 );
3018 }
3019 }
3020 }
3021
3022 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3030 &mut self,
3031 version: Version,
3032 reason: UnavailableVersion,
3033 index: &InMemoryIndex,
3034 installed_packages: &InstalledPackages,
3035 ) {
3036 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3037
3038 if let UnavailableVersion::IncompatibleDist(
3042 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3043 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3044 ) = reason
3045 {
3046 let package = &self.next;
3047 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3048 PubGrubPackageInner::Python(match kind {
3049 PythonRequirementKind::Installed => PubGrubPython::Installed,
3050 PythonRequirementKind::Target => PubGrubPython::Target,
3051 }),
3052 ));
3053 self.pubgrub
3054 .add_incompatibility(Incompatibility::from_dependency(
3055 *package,
3056 versions,
3057 (
3058 python,
3059 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3060 ),
3061 ));
3062 self.pubgrub
3063 .partial_solution
3064 .add_decision(self.next, version);
3065 return;
3066 }
3067 self.pubgrub
3068 .add_incompatibility(Incompatibility::custom_term(
3069 self.next,
3070 Term::Positive(versions),
3071 UnavailableReason::Version(reason),
3072 ));
3073 }
3074
3075 fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3076 self.continuation = continuation;
3077 self
3078 }
3079
3080 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3082 self.selected_versions.clear();
3083 self.env = env;
3084 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3086 debug!("Narrowed `requires-python` bound to: {}", req.target());
3087 self.python_requirement = req;
3088 }
3089 self
3090 }
3091
3092 fn source(
3096 &self,
3097 name: &PackageName,
3098 version: &Version,
3099 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3100 let url = self.fork_urls.get(name);
3101 let index = url
3102 .is_none()
3103 .then(|| {
3104 self.pins
3105 .get(name, version)
3106 .expect("Every package should be pinned")
3107 .index()
3108 })
3109 .flatten();
3110 (url, index)
3111 }
3112
3113 fn into_resolution(self) -> Resolution<'index> {
3114 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3115 let edge_count: usize = solution
3116 .keys()
3117 .map(|package| self.pubgrub.incompatibilities[package].len())
3118 .sum();
3119 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3120 for (package, self_version) in &solution {
3121 for id in &self.pubgrub.incompatibilities[package] {
3122 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3123 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3124 &incompatibility.kind
3125 else {
3126 continue;
3127 };
3128 let (self_package, dependency_package) = (*self_package, *dependency_package);
3129 let Some((self_range, dependency_range)) =
3130 incompatibility.dependency_version_sets()
3131 else {
3132 continue;
3133 };
3134 let dependency_range =
3135 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3136 if *package != self_package {
3137 continue;
3138 }
3139 if !self_range.contains(self_version) {
3140 continue;
3141 }
3142 let Some(dependency_version) = solution.get(&dependency_package) else {
3143 continue;
3144 };
3145 if !dependency_range.contains(dependency_version) {
3146 continue;
3147 }
3148
3149 let self_package = &self.pubgrub.package_store[self_package];
3150 let dependency_package = &self.pubgrub.package_store[dependency_package];
3151
3152 let (self_name, self_kind) = match &**self_package {
3153 PubGrubPackageInner::Package {
3154 name: self_name,
3155 kind: self_kind,
3156 marker: _,
3157 } => (Some(self_name), self_kind),
3158
3159 PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3160
3161 _ => continue,
3162 };
3163
3164 let (name, kind, marker) = match &**dependency_package {
3165 PubGrubPackageInner::Package { name, kind, marker } => {
3166 debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3167
3168 if self_kind.group().is_none() && self_name == Some(name) {
3171 continue;
3172 }
3173 (name, kind.clone(), *marker)
3174 }
3175 PubGrubPackageInner::Marker { name, marker } => {
3176 if self_kind.group().is_none() && self_name == Some(name) {
3177 continue;
3178 }
3179 (name, PackageNodeKind::Base, *marker)
3180 }
3181 PubGrubPackageInner::Extra {
3182 name,
3183 extra,
3184 marker,
3185 } => {
3186 if self_kind.group().is_none() {
3187 debug_assert!(self_name != Some(name), "Extras should be flattened");
3188 }
3189 (name, PackageNodeKind::Extra(extra.clone()), *marker)
3190 }
3191 PubGrubPackageInner::Group {
3192 name,
3193 group,
3194 marker,
3195 } => {
3196 debug_assert!(self_name != Some(name), "Groups should be flattened");
3197 (name, PackageNodeKind::Group(group.clone()), *marker)
3198 }
3199 PubGrubPackageInner::Root(_)
3200 | PubGrubPackageInner::Python(_)
3201 | PubGrubPackageInner::System(_) => continue,
3202 };
3203 let from = self_name.map(|name| {
3204 let (url, index) = self.source(name, self_version);
3205 ResolutionNode {
3206 package: ResolutionPackage {
3207 name: name.clone(),
3208 kind: self_kind.clone(),
3209 url: url.cloned(),
3210 index: index.cloned(),
3211 },
3212 version: self_version.clone(),
3213 }
3214 });
3215
3216 let (url, index) = self.source(name, dependency_version);
3217 let to = ResolutionNode {
3218 package: ResolutionPackage {
3219 name: name.clone(),
3220 kind,
3221 url: url.cloned(),
3222 index: index.cloned(),
3223 },
3224 version: dependency_version.clone(),
3225 };
3226 let edge = ResolutionDependencyEdge { from, to, marker };
3227
3228 if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3231 let mut base_edge = edge.clone();
3232 base_edge.to.package.kind = PackageNodeKind::Base;
3233 edges.push(edge);
3234 edges.push(base_edge);
3235 } else {
3236 edges.push(edge);
3237 }
3238 }
3239 }
3240
3241 let nodes = solution
3242 .into_iter()
3243 .filter_map(|(package, version)| {
3244 if let PubGrubPackageInner::Package {
3245 name,
3246 kind,
3247 marker: MarkerTree::TRUE,
3248 } = &*self.pubgrub.package_store[package]
3249 {
3250 let (url, index) = self.source(name, &version);
3251 Some((
3252 ResolutionPackage {
3253 name: name.clone(),
3254 kind: kind.clone(),
3255 url: url.cloned(),
3256 index: index.cloned(),
3257 },
3258 version,
3259 ))
3260 } else {
3261 None
3262 }
3263 })
3264 .collect();
3265
3266 Resolution {
3267 nodes,
3268 edges,
3269 pins: self.pins,
3270 env: self.env,
3271 }
3272 }
3273}
3274
3275fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3284 let versions = Range::singleton(version.clone());
3285 match known_versions {
3286 Some(known_versions) if !known_versions.is_empty() => {
3287 versions.widen_versions(known_versions)
3288 }
3289 _ => versions,
3290 }
3291}
3292
3293#[derive(Debug)]
3295#[expect(clippy::large_enum_variant)]
3296pub(crate) enum Request {
3297 Package(PackageName, Option<IndexMetadata>),
3299 Dist(Dist),
3301 Installed(InstalledDist),
3303 Prefetch(PackageName, Range<Version>, PythonRequirement),
3305}
3306
3307impl<'a> From<ResolvedDistRef<'a>> for Request {
3308 fn from(dist: ResolvedDistRef<'a>) -> Self {
3309 match dist {
3315 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3316 let source = prioritized.source_dist().expect("a source distribution");
3319 assert_eq!(
3320 (&sdist.name, &sdist.version),
3321 (&source.name, &source.version),
3322 "expected chosen sdist to match prioritized sdist"
3323 );
3324 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3325 }
3326 ResolvedDistRef::InstallableRegistryBuiltDist {
3327 wheel, prioritized, ..
3328 } => {
3329 assert_eq!(
3330 Some(&wheel.filename),
3331 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3332 "expected chosen wheel to match best wheel"
3333 );
3334 let built = prioritized.built_dist().expect("at least one wheel");
3337 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3338 }
3339 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3340 }
3341 }
3342}
3343
3344impl Display for Request {
3345 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3346 match self {
3347 Self::Package(package_name, _) => {
3348 write!(f, "Versions {package_name}")
3349 }
3350 Self::Dist(dist) => {
3351 write!(f, "Metadata {dist}")
3352 }
3353 Self::Installed(dist) => {
3354 write!(f, "Installed metadata {dist}")
3355 }
3356 Self::Prefetch(package_name, range, _) => {
3357 write!(f, "Prefetch {package_name} {range}")
3358 }
3359 }
3360 }
3361}
3362
3363#[derive(Debug)]
3364#[expect(clippy::large_enum_variant)]
3365enum Response {
3366 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3368 Dist {
3370 dist: Dist,
3371 metadata: MetadataResponse,
3372 },
3373 Installed {
3375 dist: InstalledDist,
3376 metadata: MetadataResponse,
3377 },
3378}
3379
3380enum Dependencies {
3386 Unavailable(UnavailableVersion),
3388 Available(Vec<PubGrubDependency>),
3394 RequiresPython(VersionSpecifiers),
3396 Unforkable(Vec<PubGrubDependency>),
3402}
3403
3404#[derive(Debug)]
3411enum ForkedDependencies {
3412 Unavailable(UnavailableVersion),
3414 Unforked(Vec<PubGrubDependency>),
3418 Forked {
3424 forks: Vec<Fork>,
3425 diverging_packages: BTreeSet<PackageName>,
3427 },
3428 RequiresPython(VersionSpecifiers),
3430}
3431
3432impl ForkedDependencies {
3433 fn from_dependencies_universal(
3440 dependencies: Dependencies,
3441 env: &ResolverEnvironment,
3442 python_requirement: &PythonRequirement,
3443 conflicts: &Conflicts,
3444 ) -> Self {
3445 let deps = match dependencies {
3446 Dependencies::Available(deps) => deps,
3447 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3448 Dependencies::RequiresPython(requires_python) => {
3449 return Self::RequiresPython(requires_python);
3450 }
3451 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3452 };
3453 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3454 for dep in deps {
3455 let name = dep
3456 .package
3457 .name()
3458 .expect("dependency always has a name")
3459 .clone();
3460 name_to_deps.entry(name).or_default().push(dep);
3461 }
3462 let (mut forks, diverging_packages) =
3463 Self::fork(name_to_deps, env, python_requirement, conflicts);
3464 if forks.is_empty() {
3465 Self::Unforked(vec![])
3466 } else if forks.len() == 1 {
3467 Self::Unforked(forks.pop().unwrap().dependencies)
3468 } else {
3469 Self::Forked {
3470 forks,
3471 diverging_packages,
3472 }
3473 }
3474 }
3475
3476 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3479 match dependencies {
3480 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3481 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3482 Dependencies::Unavailable(err) => Self::Unavailable(err),
3483 }
3484 }
3485
3486 fn fork(
3494 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3495 env: &ResolverEnvironment,
3496 python_requirement: &PythonRequirement,
3497 conflicts: &Conflicts,
3498 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3499 let python_marker = python_requirement.to_marker_tree();
3500
3501 let mut forks = vec![Fork::new(env.clone())];
3502 let mut diverging_packages = BTreeSet::new();
3503 for (name, mut deps) in name_to_deps {
3504 assert!(!deps.is_empty(), "every name has at least one dependency");
3505 if let [dep] = deps.as_slice() {
3516 if marker::requires_python(dep.package.marker())
3524 .is_none_or(|bound| !python_requirement.raises(&bound))
3525 {
3526 let dep = deps.pop().unwrap();
3527 let marker = dep.package.marker();
3528 for fork in &mut forks {
3529 if fork.env.included_by_marker(marker) {
3530 fork.add_dependency(dep.clone());
3531 }
3532 }
3533 continue;
3534 }
3535 } else {
3536 if let Some(dep) = deps.first() {
3538 let marker = dep.package.marker();
3539 if deps.iter().all(|dep| marker == dep.package.marker()) {
3540 if marker::requires_python(marker)
3544 .is_none_or(|bound| !python_requirement.raises(&bound))
3545 {
3546 for dep in deps {
3547 for fork in &mut forks {
3548 if fork.env.included_by_marker(marker) {
3549 fork.add_dependency(dep.clone());
3550 }
3551 }
3552 }
3553 continue;
3554 }
3555 }
3556 }
3557 }
3558 for dep in deps {
3559 let mut forker = match ForkingPossibility::new(env, &dep) {
3560 ForkingPossibility::Possible(forker) => forker,
3561 ForkingPossibility::DependencyAlwaysExcluded => {
3562 continue;
3565 }
3566 ForkingPossibility::NoForkingPossible => {
3567 for fork in &mut forks {
3570 fork.add_dependency(dep.clone());
3571 }
3572 continue;
3573 }
3574 };
3575 diverging_packages.insert(name.clone());
3577
3578 let mut new = vec![];
3579 for fork in std::mem::take(&mut forks) {
3580 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3581 new.push(fork);
3582 continue;
3583 };
3584 forker = remaining_forker;
3585
3586 for fork_env in envs {
3587 let mut new_fork = fork.clone();
3588 new_fork.set_env(fork_env);
3589 if forker.included(&new_fork.env) {
3594 new_fork.add_dependency(dep.clone());
3595 }
3596 if new_fork.env.included_by_marker(python_marker) {
3599 new.push(new_fork);
3600 }
3601 }
3602 }
3603 forks = new;
3604 }
3605 }
3606 for set in conflicts.iter() {
3621 let mut new = vec![];
3622 for fork in std::mem::take(&mut forks) {
3623 let mut has_conflicting_dependency = false;
3631 for item in set.iter() {
3632 if fork.contains_conflicting_item(item.as_ref()) {
3633 has_conflicting_dependency = true;
3634 diverging_packages.insert(item.package().clone());
3635 break;
3636 }
3637 }
3638 if !has_conflicting_dependency {
3639 new.push(fork);
3640 continue;
3641 }
3642
3643 let non_excluded: Vec<_> = set
3649 .iter()
3650 .filter(|item| fork.env.included_by_group(item.as_ref()))
3651 .collect();
3652 if non_excluded.len() < 2 {
3653 let dominated = non_excluded.iter().all(|item| {
3658 !conflicts.iter().any(|other_set| {
3659 !std::ptr::eq(set, other_set)
3660 && other_set.contains(item.package(), item.kind().as_ref())
3661 && other_set
3662 .iter()
3663 .filter(|other_item| {
3664 other_item.package() != item.package()
3665 || other_item.kind() != item.kind()
3666 })
3667 .any(|other_item| {
3668 fork.env.included_by_group(other_item.as_ref())
3669 })
3670 })
3671 });
3672 if dominated {
3673 let rules: Vec<_> = set
3678 .iter()
3679 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3680 .cloned()
3681 .map(Err)
3682 .collect();
3683 if let Some(filtered) = fork.filter(rules) {
3684 new.push(filtered);
3685 }
3686 continue;
3687 }
3688 }
3689
3690 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3692 new.push(fork_none);
3693 }
3694
3695 for (i, _) in set.iter().enumerate() {
3703 let fork_allows_group = fork.clone().filter(
3704 set.iter()
3705 .cloned()
3706 .enumerate()
3707 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3708 );
3709 if let Some(fork_allows_group) = fork_allows_group {
3710 new.push(fork_allows_group);
3711 }
3712 }
3713 }
3714 forks = new;
3715 }
3716 (forks, diverging_packages)
3717 }
3718}
3719
3720#[derive(Clone, Debug)]
3730struct Fork {
3731 dependencies: Vec<PubGrubDependency>,
3740 conflicts: crate::FxHashbrownSet<ConflictItem>,
3746 env: ResolverEnvironment,
3758}
3759
3760impl Fork {
3761 fn new(env: ResolverEnvironment) -> Self {
3764 Self {
3765 dependencies: vec![],
3766 conflicts: crate::FxHashbrownSet::default(),
3767 env,
3768 }
3769 }
3770
3771 fn add_dependency(&mut self, dep: PubGrubDependency) {
3773 if let Some(conflicting_item) = dep.conflicting_item() {
3774 self.conflicts.insert(conflicting_item.to_owned());
3775 }
3776 self.dependencies.push(dep);
3777 }
3778
3779 fn set_env(&mut self, env: ResolverEnvironment) {
3784 self.env = env;
3785 self.dependencies.retain(|dep| {
3786 let marker = dep.package.marker();
3787 if self.env.included_by_marker(marker) {
3788 return true;
3789 }
3790 if let Some(conflicting_item) = dep.conflicting_item() {
3791 self.conflicts.remove(&conflicting_item);
3792 }
3793 false
3794 });
3795 }
3796
3797 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3800 self.conflicts.contains(&item)
3801 }
3802
3803 fn filter(
3810 mut self,
3811 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3812 ) -> Option<Self> {
3813 self.env = self.env.filter_by_group(rules)?;
3814 self.dependencies.retain(|dep| {
3815 let Some(conflicting_item) = dep.conflicting_item() else {
3816 return true;
3817 };
3818 if self.env.included_by_group(conflicting_item) {
3819 return true;
3820 }
3821 match conflicting_item.kind() {
3822 ConflictKindRef::Project => {
3825 if dep.parent.is_some() {
3826 return true;
3827 }
3828 }
3829 ConflictKindRef::Group(_) => {}
3830 ConflictKindRef::Extra(_) => {}
3831 }
3832 self.conflicts.remove(&conflicting_item);
3833 false
3834 });
3835 Some(self)
3836 }
3837
3838 fn cmp_requires_python(&self, other: &Self) -> Ordering {
3840 cmp_requires_python(&self.env, &other.env)
3841 }
3842
3843 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3845 let self_upper_bounds = self
3850 .dependencies
3851 .iter()
3852 .filter(|dep| {
3853 dep.version
3854 .bounding_range()
3855 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3856 })
3857 .count();
3858 let other_upper_bounds = other
3859 .dependencies
3860 .iter()
3861 .filter(|dep| {
3862 dep.version
3863 .bounding_range()
3864 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3865 })
3866 .count();
3867
3868 self_upper_bounds.cmp(&other_upper_bounds)
3869 }
3870}
3871
3872fn cmp_requires_python(
3874 self_env: &ResolverEnvironment,
3875 other_env: &ResolverEnvironment,
3876) -> Ordering {
3877 let self_bound = self_env.requires_python().unwrap_or_default();
3887 let other_bound = other_env.requires_python().unwrap_or_default();
3888 self_bound.lower().cmp(other_bound.lower())
3889}
3890
3891impl Eq for Fork {}
3892
3893impl PartialEq for Fork {
3894 fn eq(&self, other: &Self) -> bool {
3895 self.dependencies == other.dependencies && self.env == other.env
3896 }
3897}
3898
3899#[derive(Debug, Clone)]
3900pub(crate) struct VersionFork {
3901 env: ResolverEnvironment,
3903 id: Id<PubGrubPackage>,
3905 version: Option<Version>,
3907}
3908
3909fn enrich_dependency_error(
3911 error: ResolveError,
3912 id: Id<PubGrubPackage>,
3913 version: &Version,
3914 pubgrub: &State<UvDependencyProvider>,
3915) -> ResolveError {
3916 let Some(name) = pubgrub.package_store[id].name_no_root() else {
3917 return error;
3918 };
3919 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
3920 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
3921}
3922
3923fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
3925 let package = &state.package_store[id];
3926 if package.is_root() {
3927 return MarkerTree::TRUE;
3928 }
3929
3930 let mut ancestors = FxHashSet::default();
3933 let mut stack = vec![id];
3934 let mut root = None;
3935 ancestors.insert(id);
3936
3937 while let Some(current) = stack.pop() {
3938 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3939 continue;
3940 };
3941
3942 for index in incompatibilities {
3943 let incompat = &state.incompatibility_store[*index];
3944 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
3945 if current != *child {
3946 continue;
3947 }
3948 if ancestors.insert(*parent) {
3949 if state.package_store[*parent].is_root() {
3950 root = Some(*parent);
3951 }
3952 stack.push(*parent);
3953 }
3954 }
3955 }
3956 }
3957
3958 let Some(root) = root else {
3959 return MarkerTree::FALSE;
3960 };
3961
3962 let mut environments = FxHashMap::default();
3965 let mut queue = VecDeque::from([root]);
3966 environments.insert(root, MarkerTree::TRUE);
3967
3968 while let Some(current) = queue.pop_front() {
3969 let Some(current_environment) = environments.get(¤t).copied() else {
3970 continue;
3971 };
3972 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3973 continue;
3974 };
3975
3976 for index in incompatibilities {
3977 let incompat = &state.incompatibility_store[*index];
3978 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
3979 continue;
3980 };
3981 if current != *parent || !ancestors.contains(child) {
3982 continue;
3983 }
3984
3985 let mut next_environment = state.package_store[*child].marker();
3986 next_environment = next_environment.and(current_environment);
3987
3988 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
3989 let mut combined = *entry;
3990 combined = combined.or(next_environment);
3991 if combined != *entry {
3992 *entry = combined;
3993 queue.push_back(*child);
3994 }
3995 }
3996 }
3997
3998 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
3999}
4000
4001#[derive(Debug, Default, Clone)]
4002struct ConflictTracker {
4003 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4005 prioritize: Vec<Id<PubGrubPackage>>,
4009 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4011 deprioritize: Vec<Id<PubGrubPackage>>,
4015}
4016
4017#[cfg(test)]
4018mod tests {
4019 use super::*;
4020
4021 fn versions(versions: &[&str]) -> Vec<Version> {
4022 versions
4023 .iter()
4024 .map(|version| version.parse().expect("valid version"))
4025 .collect()
4026 }
4027
4028 #[test]
4029 fn widens_a_version_to_its_gap() {
4030 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4031 let version: Version = "2.0".parse().expect("valid version");
4032
4033 assert_eq!(
4035 widen_to_gap(&version, Some(&known_versions)).to_string(),
4036 ">1.0, <3.0"
4037 );
4038
4039 let version: Version = "1.0".parse().expect("valid version");
4041 assert_eq!(
4042 widen_to_gap(&version, Some(&known_versions)).to_string(),
4043 "<2.0"
4044 );
4045 let version: Version = "3.0".parse().expect("valid version");
4046 assert_eq!(
4047 widen_to_gap(&version, Some(&known_versions)).to_string(),
4048 ">2.0"
4049 );
4050 }
4051
4052 #[test]
4053 fn widens_a_version_without_known_versions_to_itself() {
4054 let version: Version = "2.0".parse().expect("valid version");
4055
4056 assert_eq!(
4058 widen_to_gap(&version, None),
4059 Range::singleton(version.clone())
4060 );
4061
4062 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4064 }
4065}