1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter, Write};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{iter, slice, thread};
11
12use either::Either;
13use futures::{FutureExt, StreamExt};
14use itertools::Itertools;
15use papaya::{HashMap, ResizeMode};
16use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State};
17use rustc_hash::{FxHashMap, FxHashSet};
18use tokio::sync::mpsc::{self, Receiver, Sender};
19use tokio::sync::oneshot;
20use tokio_stream::wrappers::ReceiverStream;
21use tracing::{Level, debug, info, instrument, trace, warn};
22
23use uv_configuration::{Constraints, Excludes, Overrides};
24use uv_distribution::{ArchiveMetadata, DistributionDatabase};
25use uv_distribution_types::{
26 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
27 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
28 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist,
29 ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::{ExtraName, GroupName, 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_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_strategy::ForkStrategy;
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;
73pub use crate::resolver::provider::{
74 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
75 VersionsResponse, WheelMetadataResult,
76};
77pub use crate::resolver::reporter::Reporter;
78use crate::resolver::system::SystemDependency;
79pub(crate) use crate::resolver::urls::Urls;
80use crate::universal_marker::{ConflictMarker, UniversalMarker};
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84
85mod availability;
86mod batch_prefetch;
87mod derivation;
88mod environment;
89mod fork_map;
90mod index;
91mod indexes;
92mod provider;
93mod reporter;
94mod system;
95mod urls;
96
97const CONFLICT_THRESHOLD: usize = 5;
99
100pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
101 state: ResolverState<InstalledPackages>,
102 provider: Provider,
103}
104
105struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
108 project: Option<PackageName>,
109 requirements: Vec<Requirement>,
110 constraints: Constraints,
111 overrides: Overrides,
112 excludes: Excludes,
113 preferences: Preferences,
114 git: GitResolver,
115 capabilities: IndexCapabilities,
116 locations: IndexLocations,
117 exclusions: Exclusions,
118 urls: Urls,
119 indexes: Indexes,
120 dependency_mode: DependencyMode,
121 hasher: HashStrategy,
122 env: ResolverEnvironment,
123 current_environment: MarkerEnvironment,
125 tags: Option<Tags>,
126 python_requirement: PythonRequirement,
127 conflicts: Conflicts,
128 workspace_members: BTreeSet<PackageName>,
129 selector: CandidateSelector,
130 index: InMemoryIndex,
131 installed_packages: InstalledPackages,
132 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
135 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
137 options: Options,
139 reporter: Option<Arc<dyn Reporter>>,
141}
142
143impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
144 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
145{
146 pub fn new(
165 manifest: Manifest,
166 options: Options,
167 python_requirement: &'a PythonRequirement,
168 env: ResolverEnvironment,
169 current_environment: &MarkerEnvironment,
170 conflicts: Conflicts,
171 tags: Option<&'a Tags>,
172 flat_index: &'a FlatIndex,
173 index: &'a InMemoryIndex,
174 hasher: &'a HashStrategy,
175 build_context: &'a Context,
176 installed_packages: InstalledPackages,
177 database: DistributionDatabase<'a, Context>,
178 ) -> Result<Self, ResolveError> {
179 let provider = DefaultResolverProvider::new(
180 database,
181 flat_index,
182 tags,
183 python_requirement.target(),
184 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
185 hasher,
186 options.exclude_newer.clone(),
187 build_context.locations(),
188 build_context.build_options(),
189 build_context.capabilities(),
190 );
191
192 Ok(Self::new_custom_io(
193 manifest,
194 options,
195 hasher,
196 env,
197 current_environment,
198 tags.cloned(),
199 python_requirement,
200 conflicts,
201 index,
202 build_context.git(),
203 build_context.capabilities(),
204 build_context.locations(),
205 provider,
206 installed_packages,
207 ))
208 }
209}
210
211impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
212 Resolver<Provider, InstalledPackages>
213{
214 pub fn new_custom_io(
216 manifest: Manifest,
217 options: Options,
218 hasher: &HashStrategy,
219 env: ResolverEnvironment,
220 current_environment: &MarkerEnvironment,
221 tags: Option<Tags>,
222 python_requirement: &PythonRequirement,
223 conflicts: Conflicts,
224 index: &InMemoryIndex,
225 git: &GitResolver,
226 capabilities: &IndexCapabilities,
227 locations: &IndexLocations,
228 provider: Provider,
229 installed_packages: InstalledPackages,
230 ) -> Self {
231 let state = ResolverState {
232 index: index.clone(),
233 git: git.clone(),
234 capabilities: capabilities.clone(),
235 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
236 dependency_mode: options.dependency_mode,
237 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
238 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
239 project: manifest.project,
240 workspace_members: manifest.workspace_members,
241 requirements: manifest.requirements,
242 constraints: manifest.constraints,
243 overrides: manifest.overrides,
244 excludes: manifest.excludes,
245 preferences: manifest.preferences,
246 exclusions: manifest.exclusions,
247 hasher: hasher.clone(),
248 locations: locations.clone(),
249 env,
250 current_environment: current_environment.clone(),
251 tags,
252 python_requirement: python_requirement.clone(),
253 conflicts,
254 installed_packages,
255 unavailable_packages: Box::default(),
256 incomplete_packages: Box::default(),
257 options,
258 reporter: None,
259 };
260 Self { state, provider }
261 }
262
263 #[must_use]
265 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
266 Self {
267 state: ResolverState {
268 reporter: Some(reporter.clone()),
269 ..self.state
270 },
271 provider: self
272 .provider
273 .with_reporter(reporter.into_distribution_reporter()),
274 }
275 }
276
277 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
279 let state = Arc::new(self.state);
280 let provider = Arc::new(self.provider);
281
282 let (request_sink, request_stream) = mpsc::channel(300);
286
287 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
289
290 let solver = state.clone();
292 let (tx, rx) = oneshot::channel();
293 thread::Builder::new()
294 .name("uv-resolver".into())
295 .spawn(move || {
296 let result = solver.solve(&request_sink);
297
298 let _ = tx.send(result);
300 })
301 .unwrap();
302
303 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
304
305 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
307
308 state.on_complete();
309 resolution
310 }
311}
312
313impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
314 #[instrument(skip_all)]
315 fn solve(
316 self: Arc<Self>,
317 request_sink: &Sender<Request>,
318 ) -> Result<ResolverOutput, ResolveError> {
319 debug!(
320 "Solving with installed Python version: {}",
321 self.python_requirement.exact()
322 );
323 debug!(
324 "Solving with target Python version: {}",
325 self.python_requirement.target()
326 );
327 if !self.options.exclude_newer.is_empty() {
328 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
329 }
330
331 let mut visited = FxHashSet::default();
332
333 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
334 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
335 let prefetcher = BatchPrefetcher::new(
336 self.capabilities.clone(),
337 self.index.clone(),
338 request_sink.clone(),
339 );
340 let state = ForkState::new(
341 pubgrub,
342 self.env.clone(),
343 self.python_requirement.clone(),
344 prefetcher,
345 );
346 let mut preferences = self.preferences.clone();
347 let mut forked_states = self.env.initial_forked_states(state)?;
348 let mut resolutions = vec![];
349
350 'FORK: while let Some(mut state) = forked_states.pop() {
351 if let Some(split) = state.env.end_user_fork_display() {
352 let requires_python = state.python_requirement.target();
353 debug!("Solving {split} (requires-python: {requires_python:?})");
354 }
355 let start = Instant::now();
356 loop {
357 let highest_priority_pkg =
358 if let Some(initial) = state.initial_id.take() {
359 initial
363 } else {
364 let result = state.pubgrub.unit_propagation(state.next);
366 match result {
367 Err(err) => {
368 return Err(self.convert_no_solution_err(
370 err,
371 state.fork_urls,
372 state.fork_indexes,
373 &state.known_versions,
374 state.env,
375 self.current_environment.clone(),
376 &visited,
377 ));
378 }
379 Ok(conflicts) => {
380 for (affected, incompatibility) in conflicts {
381 state.record_conflict(affected, None, incompatibility);
384 }
385 }
386 }
387
388 if self.dependency_mode.is_transitive() {
390 Self::pre_visit(
391 state.pubgrub.partial_solution.prioritized_packages().map(
392 |(id, range)| (id, &state.pubgrub.package_store[id], range),
393 ),
394 &mut state.pre_visited,
395 &self.urls,
396 &self.indexes,
397 &state.python_requirement,
398 request_sink,
399 )?;
400 }
401
402 Self::reprioritize_conflicts(&mut state);
403
404 trace!(
405 "Assigned packages: {}",
406 state
407 .pubgrub
408 .partial_solution
409 .extract_solution()
410 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
411 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
412 .join(", ")
413 );
414 let Some((highest_priority_pkg, _)) =
418 state.pubgrub.partial_solution.pick_highest_priority_pkg(
419 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
420 )
421 else {
422 if tracing::enabled!(Level::DEBUG) {
424 state.prefetcher.log_tried_versions();
425 }
426 debug!(
427 "{} resolution took {:.3}s",
428 state.env,
429 start.elapsed().as_secs_f32()
430 );
431
432 let resolution = state.into_resolution();
433
434 if matches!(
443 self.options.resolution_mode,
444 ResolutionMode::Lowest | ResolutionMode::Highest
445 ) {
446 for (package, version) in &resolution.nodes {
447 preferences.insert(
448 package.name.clone(),
449 package.index.clone(),
450 resolution
451 .env
452 .try_universal_markers()
453 .unwrap_or(UniversalMarker::TRUE),
454 version.clone(),
455 PreferenceSource::Resolver,
456 );
457 }
458 }
459
460 resolutions.push(resolution);
461 continue 'FORK;
462 };
463 trace!(
464 "Chose package for decision: {}. remaining choices: {}",
465 state.pubgrub.package_store[highest_priority_pkg],
466 state
467 .pubgrub
468 .partial_solution
469 .undecided_packages()
470 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
471 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
472 .join(", ")
473 );
474
475 highest_priority_pkg
476 };
477
478 state.next = highest_priority_pkg;
479
480 let next_id = state.next;
482 let next_package = &state.pubgrub.package_store[state.next];
483
484 let url = next_package
485 .name()
486 .and_then(|name| state.fork_urls.get(name));
487 let index = next_package
488 .name()
489 .and_then(|name| state.fork_indexes.get(name));
490
491 self.request_package(next_package, url, index, request_sink)?;
503
504 let version = if let Some(version) = state.initial_version.take() {
505 version
509 } else {
510 let term_intersection = state
511 .pubgrub
512 .partial_solution
513 .term_intersection_for_package(next_id)
514 .expect("a package was chosen but we don't have a term");
515 let range = term_intersection.unwrap_positive();
516
517 let cache_selected_version = state.env.marker_environment().is_some()
521 && url.is_none()
522 && index.is_none();
523 let decision = if cache_selected_version
524 && let Some((selected_range, version)) =
525 state.selected_versions.get(&next_id)
526 && selected_range == range
527 {
528 Some(ResolverVersion::Unforked(version.clone()))
529 } else {
530 let decision = self.choose_version(
531 next_package,
532 next_id,
533 index.map(IndexMetadata::url),
534 range,
535 &mut state.pins,
536 &preferences,
537 &state.fork_urls,
538 &state.env,
539 &state.python_requirement,
540 &state.pubgrub,
541 &mut visited,
542 request_sink,
543 )?;
544
545 if cache_selected_version
546 && let Some(ResolverVersion::Unforked(version)) = &decision
547 {
548 state
549 .selected_versions
550 .insert(next_id, (range.clone(), version.clone()));
551 }
552
553 decision
554 };
555
556 let Some(version) = decision else {
558 debug!("No compatible version found for: {next_package}");
559
560 let term_intersection = state
561 .pubgrub
562 .partial_solution
563 .term_intersection_for_package(next_id)
564 .expect("a package was chosen but we don't have a term");
565
566 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
567 if let Some(reason) = self.unavailable_packages.pin().get(name) {
569 state
570 .pubgrub
571 .add_incompatibility(Incompatibility::custom_term(
572 next_id,
573 term_intersection.clone(),
574 UnavailableReason::Package(reason.clone()),
575 ));
576 continue;
577 }
578 }
579
580 state
581 .pubgrub
582 .add_incompatibility(Incompatibility::no_versions(
583 next_id,
584 term_intersection.clone(),
585 ));
586 continue;
587 };
588
589 let version = match version {
590 ResolverVersion::Unforked(version) => version,
591 ResolverVersion::Forked(forks) => {
592 forked_states.extend(self.version_forks_to_fork_states(state, forks));
593 continue 'FORK;
594 }
595 ResolverVersion::Unavailable(version, reason) => {
596 state.add_unavailable_version(version, reason);
597 continue;
598 }
599 };
600
601 if url.is_none() {
603 state.prefetcher.prefetch_batches(
604 next_package,
605 index,
606 &version,
607 term_intersection.unwrap_positive(),
608 state
609 .pubgrub
610 .partial_solution
611 .unchanging_term_for_package(next_id),
612 &state.python_requirement,
613 &self.selector,
614 &state.env,
615 )?;
616 }
617
618 version
619 };
620
621 state.prefetcher.version_tried(next_package, &version);
622
623 self.on_progress(next_package, &version);
624
625 if !state
626 .added_dependencies
627 .entry(next_id)
628 .or_default()
629 .insert(version.clone())
630 {
631 state
634 .pubgrub
635 .partial_solution
636 .add_decision(next_id, version);
637 continue;
638 }
639
640 let forked_deps = self.get_dependencies_forking(
642 next_id,
643 next_package,
644 &version,
645 &state.pins,
646 &state.fork_urls,
647 &state.env,
648 &state.python_requirement,
649 &state.pubgrub,
650 )?;
651
652 match forked_deps {
653 ForkedDependencies::Unavailable(reason) => {
654 state
657 .pubgrub
658 .add_incompatibility(Incompatibility::custom_version(
659 next_id,
660 version.clone(),
661 UnavailableReason::Version(reason),
662 ));
663 }
664 ForkedDependencies::Unforked(dependencies) => {
665 state
667 .visit_package_version_dependencies(
668 next_id,
669 &version,
670 &self.urls,
671 &self.indexes,
672 &dependencies,
673 &self.git,
674 &self.workspace_members,
675 self.selector.resolution_strategy(),
676 )
677 .map_err(|err| {
678 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
679 })?;
680
681 self.visit_dependencies(&dependencies, &state, request_sink)
683 .map_err(|err| {
684 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
685 })?;
686
687 state.add_package_version_dependencies(
689 next_id,
690 &version,
691 dependencies,
692 &self.index,
693 &self.installed_packages,
694 );
695 }
696 ForkedDependencies::Forked {
697 mut forks,
698 diverging_packages,
699 } => {
700 debug!(
701 "Pre-fork {} took {:.3}s",
702 state.env,
703 start.elapsed().as_secs_f32()
704 );
705
706 match (self.options.fork_strategy, self.options.resolution_mode) {
708 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
709 forks.sort_by(|a, b| {
713 a.cmp_requires_python(b)
714 .reverse()
715 .then_with(|| a.cmp_upper_bounds(b))
716 });
717 }
718 (ForkStrategy::RequiresPython, _) => {
719 forks.sort_by(|a, b| {
723 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
724 });
725 }
726 }
727
728 for new_fork_state in self.forks_to_fork_states(
729 state,
730 &version,
731 forks,
732 request_sink,
733 &diverging_packages,
734 ) {
735 forked_states.push(new_fork_state?);
736 }
737 continue 'FORK;
738 }
739 }
740 }
741 }
742 if resolutions.len() > 1 {
743 info!(
744 "Solved your requirements for {} environments",
745 resolutions.len()
746 );
747 }
748 if tracing::enabled!(Level::DEBUG) {
749 for resolution in &resolutions {
750 if let Some(env) = resolution.env.end_user_fork_display() {
751 let packages: FxHashSet<_> = resolution
752 .nodes
753 .keys()
754 .map(|package| &package.name)
755 .collect();
756 debug!(
757 "Distinct solution for {env} with {} package(s)",
758 packages.len()
759 );
760 }
761 }
762 }
763 for resolution in &resolutions {
764 Self::trace_resolution(resolution);
765 }
766 ResolverOutput::from_state(
767 &resolutions,
768 self.requirements.clone(),
769 self.constraints.clone(),
770 self.overrides.clone(),
771 &self.preferences,
772 &self.index,
773 &self.git,
774 self.python_requirement.target().clone(),
775 &self.conflicts,
776 self.selector.resolution_strategy(),
777 self.options.clone(),
778 )
779 }
780
781 fn reprioritize_conflicts(state: &mut ForkState) {
785 for package in state.conflict_tracker.prioritize.drain(..) {
786 let changed = state
787 .priorities
788 .mark_conflict_early(&state.pubgrub.package_store[package]);
789 if changed {
790 debug!(
791 "Package {} has too many conflicts (affected), prioritizing",
792 &state.pubgrub.package_store[package]
793 );
794 } else {
795 debug!(
796 "Package {} has too many conflicts (affected), already {:?}",
797 state.pubgrub.package_store[package],
798 state.priorities.get(&state.pubgrub.package_store[package])
799 );
800 }
801 }
802
803 for package in state.conflict_tracker.deprioritize.drain(..) {
804 let changed = state
805 .priorities
806 .mark_conflict_late(&state.pubgrub.package_store[package]);
807 if changed {
808 debug!(
809 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
810 state.pubgrub.package_store[package],
811 );
812 let backtrack_level = state.pubgrub.backtrack_package(package);
813 if let Some(backtrack_level) = backtrack_level {
814 debug!("Backtracked {backtrack_level} decisions");
815 } else {
816 debug!(
817 "Package {} is not decided, cannot backtrack",
818 state.pubgrub.package_store[package]
819 );
820 }
821 } else {
822 debug!(
823 "Package {} has too many conflicts (culprit), already {:?}",
824 state.pubgrub.package_store[package],
825 state.priorities.get(&state.pubgrub.package_store[package])
826 );
827 }
828 }
829 }
830
831 fn trace_resolution(combined: &Resolution) {
837 if !tracing::enabled!(Level::TRACE) {
838 return;
839 }
840 trace!("Resolution: {:?}", combined.env);
841 for edge in &combined.edges {
842 trace!(
843 "Resolution edge: {} -> {}",
844 edge.from
845 .as_ref()
846 .map(PackageName::as_str)
847 .unwrap_or("ROOT"),
848 edge.to,
849 );
850 let mut msg = String::new();
853 write!(msg, "{}", edge.from_version).unwrap();
854 if let Some(ref extra) = edge.from_extra {
855 write!(msg, " (extra: {extra})").unwrap();
856 }
857 if let Some(ref dev) = edge.from_group {
858 write!(msg, " (group: {dev})").unwrap();
859 }
860
861 write!(msg, " -> ").unwrap();
862
863 write!(msg, "{}", edge.to_version).unwrap();
864 if let Some(ref extra) = edge.to_extra {
865 write!(msg, " (extra: {extra})").unwrap();
866 }
867 if let Some(ref dev) = edge.to_group {
868 write!(msg, " (group: {dev})").unwrap();
869 }
870 if let Some(marker) = edge.marker.contents() {
871 write!(msg, " ; {marker}").unwrap();
872 }
873 trace!("Resolution edge: {msg}");
874 }
875 }
876
877 fn forks_to_fork_states<'a>(
879 &'a self,
880 current_state: ForkState,
881 version: &'a Version,
882 forks: Vec<Fork>,
883 request_sink: &'a Sender<Request>,
884 diverging_packages: &'a [PackageName],
885 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
886 debug!(
887 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
888 current_state.pubgrub.package_store[current_state.next],
889 version,
890 diverging_packages
891 .iter()
892 .map(ToString::to_string)
893 .join(", "),
894 forks.len(),
895 if forks.len() == 1 { "" } else { "s" }
896 );
897 assert!(forks.len() >= 2);
898 let package = current_state.next;
904 let mut cur_state = Some(current_state);
905 let forks_len = forks.len();
906 forks
907 .into_iter()
908 .enumerate()
909 .map(move |(i, fork)| {
910 let is_last = i == forks_len - 1;
911 let forked_state = cur_state.take().unwrap();
912 if !is_last {
913 cur_state = Some(forked_state.clone());
914 }
915
916 let env = fork.env.clone();
917 (fork, forked_state.with_env(env))
918 })
919 .map(move |(fork, mut forked_state)| {
920 forked_state
922 .visit_package_version_dependencies(
923 package,
924 version,
925 &self.urls,
926 &self.indexes,
927 &fork.dependencies,
928 &self.git,
929 &self.workspace_members,
930 self.selector.resolution_strategy(),
931 )
932 .map_err(|err| {
933 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
934 })?;
935
936 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
938 .map_err(|err| {
939 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
940 })?;
941
942 forked_state.add_package_version_dependencies(
944 package,
945 version,
946 fork.dependencies,
947 &self.index,
948 &self.installed_packages,
949 );
950
951 Ok(forked_state)
952 })
953 }
954
955 #[expect(clippy::unused_self)]
957 fn version_forks_to_fork_states(
958 &self,
959 current_state: ForkState,
960 forks: Vec<VersionFork>,
961 ) -> impl Iterator<Item = ForkState> + '_ {
962 let mut cur_state = Some(current_state);
968 let forks_len = forks.len();
969 forks.into_iter().enumerate().map(move |(i, fork)| {
970 let is_last = i == forks_len - 1;
971 let mut forked_state = cur_state.take().unwrap();
972 if !is_last {
973 cur_state = Some(forked_state.clone());
974 }
975 forked_state.initial_id = Some(fork.id);
976 forked_state.initial_version = fork.version;
977 forked_state.with_env(fork.env)
978 })
979 }
980
981 fn visit_dependencies(
983 &self,
984 dependencies: &[PubGrubDependency],
985 state: &ForkState,
986 request_sink: &Sender<Request>,
987 ) -> Result<(), ResolveError> {
988 for dependency in dependencies {
989 let PubGrubDependency {
990 package,
991 version: _,
992 parent: _,
993 source: _,
994 } = dependency;
995 let url = package.name().and_then(|name| state.fork_urls.get(name));
996 let index = package.name().and_then(|name| state.fork_indexes.get(name));
997 self.visit_package(package, url, index, request_sink)?;
998 }
999 Ok(())
1000 }
1001
1002 fn visit_package(
1005 &self,
1006 package: &PubGrubPackage,
1007 url: Option<&VerbatimParsedUrl>,
1008 index: Option<&IndexMetadata>,
1009 request_sink: &Sender<Request>,
1010 ) -> Result<(), ResolveError> {
1011 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1013 return Ok(());
1014 }
1015
1016 self.request_package(package, url, index, request_sink)
1017 }
1018
1019 fn request_package(
1020 &self,
1021 package: &PubGrubPackage,
1022 url: Option<&VerbatimParsedUrl>,
1023 index: Option<&IndexMetadata>,
1024 request_sink: &Sender<Request>,
1025 ) -> Result<(), ResolveError> {
1026 let Some(name) = package.name_no_root() else {
1028 return Ok(());
1029 };
1030
1031 if let Some(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 if self.index.distributions().register(dist.distribution_id()) {
1040 request_sink.blocking_send(Request::Dist(dist))?;
1041 }
1042 } else if let Some(index) = index {
1043 if self
1045 .index
1046 .explicit()
1047 .register((name.clone(), index.url().clone()))
1048 {
1049 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1050 }
1051 } else {
1052 if self.index.implicit().register(name.clone()) {
1054 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1055 }
1056 }
1057 Ok(())
1058 }
1059
1060 fn pre_visit<'data>(
1063 packages: impl Iterator<
1064 Item = (
1065 Id<PubGrubPackage>,
1066 &'data PubGrubPackage,
1067 &'data Range<Version>,
1068 ),
1069 >,
1070 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1071 urls: &Urls,
1072 indexes: &Indexes,
1073 python_requirement: &PythonRequirement,
1074 request_sink: &Sender<Request>,
1075 ) -> Result<(), ResolveError> {
1076 for (id, package, range) in packages {
1079 let PubGrubPackageInner::Package {
1080 name,
1081 extra: None,
1082 group: None,
1083 marker: MarkerTree::TRUE,
1084 } = &**package
1085 else {
1086 continue;
1087 };
1088 if urls.any_url(name) {
1091 continue;
1092 }
1093 if indexes.contains_key(name) {
1095 continue;
1096 }
1097 if pre_visited.get(&id) == Some(range) {
1100 continue;
1101 }
1102 pre_visited.insert(id, range.clone());
1103 request_sink.blocking_send(Request::Prefetch(
1104 name.clone(),
1105 range.clone(),
1106 python_requirement.clone(),
1107 ))?;
1108 }
1109 Ok(())
1110 }
1111
1112 fn known_versions<'a>(
1124 index: &InMemoryIndex,
1125 installed_packages: &InstalledPackages,
1126 fork_urls: &ForkUrls,
1127 fork_indexes: &ForkIndexes,
1128 known_versions: &'a mut FxHashMap<PackageName, Arc<[Version]>>,
1129 package: &PubGrubPackage,
1130 ) -> Option<&'a [Version]> {
1131 let name = package.name_no_root()?;
1132 if fork_urls.get(name).is_some() {
1134 return None;
1135 }
1136 if !known_versions.contains_key(name) {
1137 let response = if let Some(index_metadata) = fork_indexes.get(name) {
1138 index
1139 .explicit()
1140 .get(&(name.clone(), index_metadata.url().clone()))?
1141 } else {
1142 index.implicit().get(name)?
1143 };
1144 let VersionsResponse::Found(ref version_maps) = *response else {
1145 return None;
1146 };
1147 let mut versions: Vec<Version> = version_maps
1148 .iter()
1149 .flat_map(|version_map| version_map.included_versions().cloned())
1150 .chain(
1151 installed_packages
1152 .get_packages(name)
1153 .iter()
1154 .map(|dist| dist.version().clone()),
1155 )
1156 .collect();
1157 versions.sort_unstable();
1158 versions.dedup();
1159 known_versions.insert(name.clone(), versions.into());
1160 }
1161 Some(&known_versions[name][..])
1162 }
1163
1164 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1172 fn choose_version(
1173 &self,
1174 package: &PubGrubPackage,
1175 id: Id<PubGrubPackage>,
1176 index: Option<&IndexUrl>,
1177 range: &Range<Version>,
1178 pins: &mut FilePins,
1179 preferences: &Preferences,
1180 fork_urls: &ForkUrls,
1181 env: &ResolverEnvironment,
1182 python_requirement: &PythonRequirement,
1183 pubgrub: &State<UvDependencyProvider>,
1184 visited: &mut FxHashSet<PackageName>,
1185 request_sink: &Sender<Request>,
1186 ) -> Result<Option<ResolverVersion>, ResolveError> {
1187 match &**package {
1188 PubGrubPackageInner::Root(_) => {
1189 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1190 }
1191
1192 PubGrubPackageInner::Python(_) => {
1193 Ok(None)
1196 }
1197
1198 PubGrubPackageInner::System(_) => {
1199 let Some(version) = range.as_singleton() else {
1202 return Ok(None);
1203 };
1204 Ok(Some(ResolverVersion::Unforked(version.clone())))
1205 }
1206
1207 PubGrubPackageInner::Marker { name, .. }
1208 | PubGrubPackageInner::Extra { name, .. }
1209 | PubGrubPackageInner::Group { name, .. }
1210 | PubGrubPackageInner::Package { name, .. } => {
1211 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1212 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1213 } else {
1214 self.choose_version_registry(
1215 package,
1216 id,
1217 name,
1218 index,
1219 range,
1220 preferences,
1221 env,
1222 python_requirement,
1223 pubgrub,
1224 pins,
1225 visited,
1226 request_sink,
1227 )
1228 }
1229 }
1230 }
1231 }
1232
1233 fn choose_version_url(
1236 &self,
1237 id: Id<PubGrubPackage>,
1238 name: &PackageName,
1239 range: &Range<Version>,
1240 url: &VerbatimParsedUrl,
1241 env: &ResolverEnvironment,
1242 python_requirement: &PythonRequirement,
1243 pubgrub: &State<UvDependencyProvider>,
1244 ) -> Result<Option<ResolverVersion>, ResolveError> {
1245 debug!(
1246 "Searching for a compatible version of {name} @ {} ({range})",
1247 url.verbatim
1248 );
1249
1250 let dist = Dist::from_url(name.clone(), url.clone())?;
1251 let distribution_id = dist.distribution_id();
1252 let response = self
1253 .index
1254 .distributions()
1255 .wait_blocking(&distribution_id)
1256 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1257
1258 let metadata = match &*response {
1260 MetadataResponse::Found(archive) => &archive.metadata,
1261 MetadataResponse::Unavailable(reason) => {
1262 self.unavailable_packages
1263 .pin()
1264 .insert(name.clone(), reason.into());
1265 return Ok(None);
1266 }
1267 MetadataResponse::Error(dist, err) => {
1270 return Err(ResolveError::Dist(
1271 DistErrorKind::from_requested_dist(dist, &**err),
1272 dist.clone(),
1273 DerivationChain::default(),
1274 err.clone(),
1275 ));
1276 }
1277 };
1278
1279 let version = &metadata.version;
1280
1281 if !range.contains(version) {
1283 return Ok(None);
1284 }
1285
1286 if let Dist::Built(dist) = &dist {
1289 let filename = match &dist {
1290 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1291 BuiltDist::DirectUrl(dist) => &dist.filename,
1292 BuiltDist::GitPath(dist) => &dist.filename,
1293 BuiltDist::Path(dist) => &dist.filename,
1294 };
1295
1296 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1299 {
1300 let wheel_marker = implied_markers(filename);
1301 for environment_marker in self.options.artifact_environments.iter().copied() {
1304 if env.included_by_marker(environment_marker)
1306 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1307 {
1308 if wheel_marker.is_disjoint(environment_marker) {
1310 return Ok(Some(ResolverVersion::Unavailable(
1311 version.clone(),
1312 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1313 IncompatibleWheel::MissingPlatform(environment_marker),
1314 )),
1315 )));
1316 }
1317 }
1318 }
1319 }
1320
1321 if !python_requirement.target().matches_wheel_tag(filename) {
1323 return Ok(Some(ResolverVersion::Unavailable(
1324 filename.version.clone(),
1325 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1326 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1327 )),
1328 )));
1329 }
1330 }
1331
1332 if let Some(requires_python) = metadata.requires_python.as_ref() {
1334 if !python_requirement.target().is_contained_by(requires_python) {
1335 let kind = if python_requirement.installed() == python_requirement.target() {
1336 PythonRequirementKind::Installed
1337 } else {
1338 PythonRequirementKind::Target
1339 };
1340 return Ok(Some(ResolverVersion::Unavailable(
1341 version.clone(),
1342 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1343 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1344 )),
1345 )));
1346 }
1347 }
1348
1349 Ok(Some(ResolverVersion::Unforked(version.clone())))
1350 }
1351
1352 fn choose_version_registry(
1355 &self,
1356 package: &PubGrubPackage,
1357 id: Id<PubGrubPackage>,
1358 name: &PackageName,
1359 index: Option<&IndexUrl>,
1360 range: &Range<Version>,
1361 preferences: &Preferences,
1362 env: &ResolverEnvironment,
1363 python_requirement: &PythonRequirement,
1364 pubgrub: &State<UvDependencyProvider>,
1365 pins: &mut FilePins,
1366 visited: &mut FxHashSet<PackageName>,
1367 request_sink: &Sender<Request>,
1368 ) -> Result<Option<ResolverVersion>, ResolveError> {
1369 let versions_response = if let Some(index) = index {
1371 self.index
1372 .explicit()
1373 .wait_blocking(&(name.clone(), index.clone()))
1374 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1375 } else {
1376 self.index
1377 .implicit()
1378 .wait_blocking(name)
1379 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1380 };
1381 visited.insert(name.clone());
1382
1383 let version_maps = match *versions_response {
1384 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1385 VersionsResponse::NoIndex => {
1386 self.unavailable_packages
1387 .pin()
1388 .insert(name.clone(), UnavailablePackage::NoIndex);
1389 &[]
1390 }
1391 VersionsResponse::Offline => {
1392 self.unavailable_packages
1393 .pin()
1394 .insert(name.clone(), UnavailablePackage::Offline);
1395 &[]
1396 }
1397 VersionsResponse::NotFound => {
1398 self.unavailable_packages
1399 .pin()
1400 .insert(name.clone(), UnavailablePackage::NotFound);
1401 &[]
1402 }
1403 };
1404
1405 debug!("Searching for a compatible version of {package} ({range})");
1406
1407 let Some(candidate) = self.selector.select(
1409 name,
1410 range,
1411 version_maps,
1412 preferences,
1413 &self.installed_packages,
1414 &self.exclusions,
1415 index,
1416 env,
1417 self.tags.as_ref(),
1418 ) else {
1419 return Ok(None);
1421 };
1422
1423 let dist = match candidate.dist() {
1424 CandidateDist::Compatible(dist) => dist,
1425 CandidateDist::Incompatible {
1426 incompatible_dist: incompatibility,
1427 prioritized_dist: _,
1428 } => {
1429 return Ok(Some(ResolverVersion::Unavailable(
1431 candidate.version().clone(),
1432 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1435 )));
1436 }
1437 };
1438
1439 if let Some((requires_python, incompatibility)) =
1441 Self::check_requires_python(dist, python_requirement)
1442 {
1443 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1444 if env.marker_environment().is_none() {
1445 let forks = fork_version_by_python_requirement(
1446 requires_python,
1447 python_requirement,
1448 env,
1449 );
1450 if !forks.is_empty() {
1451 debug!(
1452 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1453 python_requirement.target(),
1454 requires_python,
1455 name,
1456 candidate.version(),
1457 forks
1458 .iter()
1459 .map(ToString::to_string)
1460 .collect::<Vec<_>>()
1461 .join(", ")
1462 );
1463 let forks = forks
1464 .into_iter()
1465 .map(|env| VersionFork {
1466 env,
1467 id,
1468 version: None,
1469 })
1470 .collect();
1471 return Ok(Some(ResolverVersion::Forked(forks)));
1472 }
1473 }
1474 }
1475
1476 return Ok(Some(ResolverVersion::Unavailable(
1477 candidate.version().clone(),
1478 UnavailableVersion::IncompatibleDist(incompatibility),
1479 )));
1480 }
1481
1482 if let Some(forked) = self.fork_version_registry(
1484 &candidate,
1485 dist,
1486 version_maps,
1487 package,
1488 id,
1489 name,
1490 index,
1491 range,
1492 preferences,
1493 env,
1494 pubgrub,
1495 pins,
1496 request_sink,
1497 )? {
1498 return Ok(Some(forked));
1499 }
1500
1501 let filename = match dist.for_installation() {
1502 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1503 .filename()
1504 .unwrap_or(Cow::Borrowed("unknown filename")),
1505 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1506 .filename()
1507 .unwrap_or(Cow::Borrowed("unknown filename")),
1508 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1509 };
1510
1511 debug!(
1512 "Selecting: {}=={} [{}] ({})",
1513 name,
1514 candidate.version(),
1515 candidate.choice_kind(),
1516 filename,
1517 );
1518 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1519
1520 let version = candidate.version().clone();
1521 Ok(Some(ResolverVersion::Unforked(version)))
1522 }
1523
1524 fn fork_version_registry(
1537 &self,
1538 candidate: &Candidate,
1539 dist: &CompatibleDist,
1540 version_maps: &[VersionMap],
1541 package: &PubGrubPackage,
1542 id: Id<PubGrubPackage>,
1543 name: &PackageName,
1544 index: Option<&IndexUrl>,
1545 range: &Range<Version>,
1546 preferences: &Preferences,
1547 env: &ResolverEnvironment,
1548 pubgrub: &State<UvDependencyProvider>,
1549 pins: &mut FilePins,
1550 request_sink: &Sender<Request>,
1551 ) -> Result<Option<ResolverVersion>, ResolveError> {
1552 if env.marker_environment().is_some() {
1554 return Ok(None);
1555 }
1556
1557 if dist.implied_markers().is_true() {
1560 return Ok(None);
1561 }
1562
1563 for marker in self.options.artifact_environments.iter().copied() {
1566 if env.included_by_marker(marker) {
1568 if dist.implied_markers().is_disjoint(marker)
1570 && !find_environments(id, pubgrub).is_disjoint(marker)
1571 {
1572 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1574 return Ok(Some(ResolverVersion::Unavailable(
1575 candidate.version().clone(),
1576 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1577 IncompatibleWheel::MissingPlatform(marker),
1578 )),
1579 )));
1580 };
1581
1582 debug!(
1583 "Forking on required platform `{}` for {}=={} ({})",
1584 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1585 name,
1586 candidate.version(),
1587 [&left, &right]
1588 .iter()
1589 .map(ToString::to_string)
1590 .collect::<Vec<_>>()
1591 .join(", ")
1592 );
1593 let forks = vec![
1594 VersionFork {
1595 env: left,
1596 id,
1597 version: None,
1598 },
1599 VersionFork {
1600 env: right,
1601 id,
1602 version: None,
1603 },
1604 ];
1605 return Ok(Some(ResolverVersion::Forked(forks)));
1606 }
1607 }
1608 }
1609
1610 if !candidate.version().is_local() {
1612 return Ok(None);
1613 }
1614
1615 debug!(
1616 "Looking at local version: {}=={}",
1617 name,
1618 candidate.version()
1619 );
1620
1621 let range = range.clone().intersection(&Range::singleton(
1623 candidate.version().clone().without_local(),
1624 ));
1625
1626 let Some(base_candidate) = self.selector.select(
1627 name,
1628 &range,
1629 version_maps,
1630 preferences,
1631 &self.installed_packages,
1632 &self.exclusions,
1633 index,
1634 env,
1635 self.tags.as_ref(),
1636 ) else {
1637 return Ok(None);
1638 };
1639 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1640 return Ok(None);
1641 };
1642
1643 let mut remainder = {
1645 let mut remainder = base_dist.implied_markers();
1646 remainder.and(dist.implied_markers().negate());
1647 remainder
1648 };
1649 if remainder.is_false() {
1650 return Ok(None);
1651 }
1652
1653 if !env.included_by_marker(remainder) {
1657 return Ok(None);
1658 }
1659
1660 if !env.included_by_marker(dist.implied_markers()) {
1663 let filename = match dist.for_installation() {
1664 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1665 .filename()
1666 .unwrap_or(Cow::Borrowed("unknown filename")),
1667 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1668 .filename()
1669 .unwrap_or(Cow::Borrowed("unknown filename")),
1670 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1671 };
1672
1673 debug!(
1674 "Preferring non-local candidate: {}=={} [{}] ({})",
1675 name,
1676 base_candidate.version(),
1677 base_candidate.choice_kind(),
1678 filename,
1679 );
1680 self.visit_candidate(
1681 &base_candidate,
1682 base_dist,
1683 package,
1684 name,
1685 pins,
1686 request_sink,
1687 )?;
1688
1689 return Ok(Some(ResolverVersion::Unforked(
1690 base_candidate.version().clone(),
1691 )));
1692 }
1693
1694 for value in [
1703 arcstr::literal!("darwin"),
1704 arcstr::literal!("linux"),
1705 arcstr::literal!("win32"),
1706 ] {
1707 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1708 key: MarkerValueString::SysPlatform,
1709 operator: MarkerOperator::Equal,
1710 value,
1711 });
1712 if dist.implied_markers().is_disjoint(sys_platform)
1713 && !remainder.is_disjoint(sys_platform)
1714 {
1715 remainder.or(sys_platform);
1716 }
1717 }
1718
1719 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1721 return Ok(None);
1722 };
1723
1724 debug!(
1725 "Forking platform for {}=={} ({})",
1726 name,
1727 candidate.version(),
1728 [&base_env, &local_env]
1729 .iter()
1730 .map(ToString::to_string)
1731 .collect::<Vec<_>>()
1732 .join(", ")
1733 );
1734 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1735 self.visit_candidate(
1736 &base_candidate,
1737 base_dist,
1738 package,
1739 name,
1740 pins,
1741 request_sink,
1742 )?;
1743
1744 let forks = vec![
1745 VersionFork {
1746 env: base_env.clone(),
1747 id,
1748 version: Some(base_candidate.version().clone()),
1749 },
1750 VersionFork {
1751 env: local_env.clone(),
1752 id,
1753 version: Some(candidate.version().clone()),
1754 },
1755 ];
1756 Ok(Some(ResolverVersion::Forked(forks)))
1757 }
1758
1759 fn visit_candidate(
1761 &self,
1762 candidate: &Candidate,
1763 dist: &CompatibleDist,
1764 package: &PubGrubPackage,
1765 name: &PackageName,
1766 pins: &mut FilePins,
1767 request_sink: &Sender<Request>,
1768 ) -> Result<(), ResolveError> {
1769 pins.insert(candidate, dist);
1772
1773 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1775 if self.dependency_mode.is_transitive() {
1776 let dist = dist.for_resolution();
1777 if self.index.distributions().register(dist.distribution_id()) {
1778 if name != dist.name() {
1779 return Err(ResolveError::MismatchedPackageName {
1780 request: "distribution",
1781 expected: name.clone(),
1782 actual: dist.name().clone(),
1783 });
1784 }
1785 if !self
1787 .hasher
1788 .allows_package(candidate.name(), candidate.version())
1789 {
1790 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1791 }
1792
1793 let request = Request::from(dist);
1794 request_sink.blocking_send(request)?;
1795 }
1796 }
1797 }
1798
1799 Ok(())
1800 }
1801
1802 fn check_requires_python<'dist>(
1805 dist: &'dist CompatibleDist,
1806 python_requirement: &PythonRequirement,
1807 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1808 let requires_python = dist.requires_python()?;
1809 if python_requirement.target().is_contained_by(requires_python) {
1810 None
1811 } else {
1812 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1813 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1814 requires_python.clone(),
1815 if python_requirement.installed() == python_requirement.target() {
1816 PythonRequirementKind::Installed
1817 } else {
1818 PythonRequirementKind::Target
1819 },
1820 ))
1821 } else {
1822 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1823 requires_python.clone(),
1824 if python_requirement.installed() == python_requirement.target() {
1825 PythonRequirementKind::Installed
1826 } else {
1827 PythonRequirementKind::Target
1828 },
1829 ))
1830 };
1831 Some((requires_python, incompatibility))
1832 }
1833 }
1834
1835 #[instrument(skip_all, fields(%package, %version))]
1837 fn get_dependencies_forking(
1838 &self,
1839 id: Id<PubGrubPackage>,
1840 package: &PubGrubPackage,
1841 version: &Version,
1842 pins: &FilePins,
1843 fork_urls: &ForkUrls,
1844 env: &ResolverEnvironment,
1845 python_requirement: &PythonRequirement,
1846 pubgrub: &State<UvDependencyProvider>,
1847 ) -> Result<ForkedDependencies, ResolveError> {
1848 let result = self.get_dependencies(
1849 id,
1850 package,
1851 version,
1852 pins,
1853 fork_urls,
1854 env,
1855 python_requirement,
1856 pubgrub,
1857 );
1858 if env.marker_environment().is_some() {
1859 result.map(|deps| match deps {
1860 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1861 ForkedDependencies::Unforked(deps)
1862 }
1863 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1864 })
1865 } else {
1866 Ok(result?.fork(env, python_requirement, &self.conflicts))
1867 }
1868 }
1869
1870 #[instrument(skip_all, fields(%package, %version))]
1872 fn get_dependencies(
1873 &self,
1874 id: Id<PubGrubPackage>,
1875 package: &PubGrubPackage,
1876 version: &Version,
1877 pins: &FilePins,
1878 fork_urls: &ForkUrls,
1879 env: &ResolverEnvironment,
1880 python_requirement: &PythonRequirement,
1881 pubgrub: &State<UvDependencyProvider>,
1882 ) -> Result<Dependencies, ResolveError> {
1883 let dependencies = match &**package {
1884 PubGrubPackageInner::Root(_) => {
1885 let no_dev_deps = BTreeMap::default();
1886 let requirements = self.flatten_requirements(
1887 &self.requirements,
1888 &no_dev_deps,
1889 None,
1890 None,
1891 None,
1892 None,
1893 env,
1894 python_requirement,
1895 );
1896
1897 PubGrubDependency::from_requirements(
1898 &self.conflicts,
1899 requirements,
1900 None,
1901 Some(package),
1902 )
1903 }
1904
1905 PubGrubPackageInner::Package {
1906 name,
1907 extra,
1908 group,
1909 marker: _,
1910 } => {
1911 if self.dependency_mode.is_direct() {
1913 return Ok(Dependencies::Unforkable(Vec::default()));
1914 }
1915
1916 let owned_id;
1918 let distribution_id = if let Some((_, metadata_id)) =
1919 pins.dist_and_id(name, version)
1920 {
1921 metadata_id
1922 } else if let Some(url) = fork_urls.get(name) {
1923 let dist = Dist::from_url(name.clone(), url.clone())?;
1924 owned_id = dist.distribution_id();
1925 &owned_id
1926 } else {
1927 debug_assert!(
1928 false,
1929 "Dependencies were requested for a package without a pinned distribution"
1930 );
1931 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1932 };
1933
1934 if self.dependency_mode.is_transitive()
1936 && self.unavailable_packages.pin().contains_key(name)
1937 && self.installed_packages.get_packages(name).is_empty()
1938 {
1939 debug_assert!(
1940 false,
1941 "Dependencies were requested for a package that is not available"
1942 );
1943 return Err(ResolveError::PackageUnavailable(name.clone()));
1944 }
1945
1946 let response = self
1948 .index
1949 .distributions()
1950 .wait_blocking(distribution_id)
1951 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1952
1953 let metadata = match &*response {
1954 MetadataResponse::Found(archive) => &archive.metadata,
1955 MetadataResponse::Unavailable(reason) => {
1956 let unavailable_version = UnavailableVersion::from(reason);
1957 let message = unavailable_version.singular_message();
1958 if let Some(err) = reason.source() {
1959 warn!("{name} {message}: {err}");
1961 } else {
1962 warn!("{name} {message}");
1963 }
1964 let incomplete_packages = self.incomplete_packages.pin();
1965 let versions = incomplete_packages.get_or_insert(
1966 name.clone(),
1967 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1968 );
1969 versions.pin().insert(version.clone(), reason.clone());
1970 return Ok(Dependencies::Unavailable(unavailable_version));
1971 }
1972 MetadataResponse::Error(dist, err) => {
1973 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1974 .unwrap_or_default();
1975 return Err(ResolveError::Dist(
1976 DistErrorKind::from_requested_dist(dist, &**err),
1977 dist.clone(),
1978 chain,
1979 err.clone(),
1980 ));
1981 }
1982 };
1983
1984 if let Some(requires_python) = &metadata.requires_python {
1987 if !python_requirement.target().is_contained_by(requires_python) {
1988 return Ok(Dependencies::Unavailable(
1989 UnavailableVersion::RequiresPython(requires_python.clone()),
1990 ));
1991 }
1992 }
1993
1994 let system_dependencies = self
1996 .options
1997 .torch_backend
1998 .as_ref()
1999 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
2000 .filter(|torch_backend| torch_backend.has_system_dependency(name))
2001 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
2002 .map(IndexUrl::url)
2003 .and_then(SystemDependency::from_index)
2004 .into_iter()
2005 .inspect(|system_dependency| {
2006 debug!(
2007 "Adding system dependency `{}` for `{package}@{version}`",
2008 system_dependency
2009 );
2010 })
2011 .map(PubGrubDependency::from);
2012
2013 let requirements = self.flatten_requirements(
2014 &metadata.requires_dist,
2015 &metadata.dependency_groups,
2016 extra.as_ref(),
2017 group.as_ref(),
2018 Some(name),
2019 Some(version),
2020 env,
2021 python_requirement,
2022 );
2023
2024 PubGrubDependency::from_requirements(
2025 &self.conflicts,
2026 requirements,
2027 group.as_ref(),
2028 Some(package),
2029 )
2030 .map(|mut dependencies| {
2031 dependencies.extend(system_dependencies);
2032 dependencies
2033 })
2034 }
2035
2036 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2037
2038 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2039
2040 PubGrubPackageInner::Marker { name, marker } => {
2042 return Ok(Dependencies::Unforkable(
2043 [MarkerTree::TRUE, *marker]
2044 .into_iter()
2045 .map(move |marker| PubGrubDependency {
2046 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2047 name: name.clone(),
2048 extra: None,
2049 group: None,
2050 marker,
2051 }),
2052 version: Range::singleton(version.clone()),
2053 parent: None,
2054 source: DependencySource::Unspecified,
2055 })
2056 .collect(),
2057 ));
2058 }
2059
2060 PubGrubPackageInner::Extra {
2062 name,
2063 extra,
2064 marker,
2065 } => {
2066 return Ok(Dependencies::Unforkable(
2067 [MarkerTree::TRUE, *marker]
2068 .into_iter()
2069 .dedup()
2070 .flat_map(move |marker| {
2071 [None, Some(extra)]
2072 .into_iter()
2073 .map(move |extra| PubGrubDependency {
2074 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2075 name: name.clone(),
2076 extra: extra.cloned(),
2077 group: None,
2078 marker,
2079 }),
2080 version: Range::singleton(version.clone()),
2081 parent: None,
2082 source: DependencySource::Unspecified,
2083 })
2084 })
2085 .collect(),
2086 ));
2087 }
2088
2089 PubGrubPackageInner::Group {
2091 name,
2092 group,
2093 marker,
2094 } => {
2095 return Ok(Dependencies::Unforkable(
2096 [MarkerTree::TRUE, *marker]
2097 .into_iter()
2098 .dedup()
2099 .map(|marker| PubGrubDependency {
2100 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2101 name: name.clone(),
2102 extra: None,
2103 group: Some(group.clone()),
2104 marker,
2105 }),
2106 version: Range::singleton(version.clone()),
2107 parent: None,
2108 source: DependencySource::Unspecified,
2109 })
2110 .collect(),
2111 ));
2112 }
2113 };
2114 Ok(match dependencies {
2115 Ok(dependencies) => Dependencies::Available(dependencies),
2116 Err(requirement) => {
2117 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2118 }
2119 })
2120 }
2121
2122 fn flatten_requirements<'a>(
2126 &'a self,
2127 dependencies: &'a [Requirement],
2128 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2129 extra: Option<&'a ExtraName>,
2130 dev: Option<&'a GroupName>,
2131 name: Option<&'a PackageName>,
2132 version: Option<&'a Version>,
2133 env: &'a ResolverEnvironment,
2134 python_requirement: &'a PythonRequirement,
2135 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2136 let python_marker = python_requirement.to_marker_tree();
2137
2138 if let Some(dev) = dev {
2139 Either::Left(Either::Left(self.requirements_for_extra(
2142 dev_dependencies.get(dev).into_iter().flatten(),
2143 extra,
2144 None,
2145 name.zip(version),
2146 env,
2147 python_marker,
2148 python_requirement,
2149 )))
2150 } else if !dependencies
2151 .iter()
2152 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2153 {
2154 Either::Left(Either::Right(self.requirements_for_extra(
2156 dependencies.iter(),
2157 extra,
2158 name.zip(version),
2159 name.zip(version),
2160 env,
2161 python_marker,
2162 python_requirement,
2163 )))
2164 } else {
2165 let mut requirements = self
2166 .requirements_for_extra(
2167 dependencies.iter(),
2168 extra,
2169 name.zip(version),
2170 name.zip(version),
2171 env,
2172 python_marker,
2173 python_requirement,
2174 )
2175 .collect::<Vec<_>>();
2176
2177 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2180 let mut queue: VecDeque<_> = requirements
2181 .iter()
2182 .filter(|req| name == Some(&req.name))
2183 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2184 .collect();
2185 while let Some((extra, marker)) = queue.pop_front() {
2186 if !seen.insert((extra.clone(), marker)) {
2187 continue;
2188 }
2189 for requirement in self.requirements_for_extra(
2190 dependencies,
2191 Some(&extra),
2192 name.zip(version),
2193 name.zip(version),
2194 env,
2195 python_marker,
2196 python_requirement,
2197 ) {
2198 let requirement = match requirement {
2199 Cow::Owned(mut requirement) => {
2200 requirement.marker.and(marker);
2201 requirement
2202 }
2203 Cow::Borrowed(requirement) => {
2204 let mut marker = marker;
2205 marker.and(requirement.marker);
2206 Requirement {
2207 name: requirement.name.clone(),
2208 extras: requirement.extras.clone(),
2209 groups: requirement.groups.clone(),
2210 source: requirement.source.clone(),
2211 origin: requirement.origin.clone(),
2212 marker: marker.simplify_extras(slice::from_ref(&extra)),
2213 }
2214 }
2215 };
2216 if name == Some(&requirement.name) {
2217 queue.extend(
2219 requirement
2220 .extras
2221 .iter()
2222 .cloned()
2223 .map(|extra| (extra, requirement.marker)),
2224 );
2225 } else {
2226 requirements.push(Cow::Owned(requirement));
2228 }
2229 }
2230 }
2231
2232 let mut self_constraints = vec![];
2236 for req in &requirements {
2237 if name == Some(&req.name) && !req.source.is_empty() {
2238 self_constraints.push(Requirement {
2239 name: req.name.clone(),
2240 extras: Box::new([]),
2241 groups: req.groups.clone(),
2242 source: req.source.clone(),
2243 origin: req.origin.clone(),
2244 marker: req.marker,
2245 });
2246 }
2247 }
2248
2249 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2251 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2252
2253 Either::Right(requirements.into_iter())
2254 }
2255 }
2256
2257 fn requirements_for_extra<'data, 'parameters>(
2260 &'data self,
2261 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2262 extra: Option<&'parameters ExtraName>,
2263 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2264 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2265 env: &'parameters ResolverEnvironment,
2266 python_marker: MarkerTree,
2267 python_requirement: &'parameters PythonRequirement,
2268 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2269 where
2270 'data: 'parameters,
2271 {
2272 self.overrides
2273 .apply_for_package(override_package, dependencies)
2274 .filter(move |requirement| {
2275 !self
2276 .excludes
2277 .contains_for_package(exclusion_package, &requirement.name)
2278 })
2279 .filter(move |requirement| {
2280 Self::is_requirement_applicable(
2281 requirement,
2282 extra,
2283 env,
2284 python_marker,
2285 python_requirement,
2286 )
2287 })
2288 .flat_map(move |requirement| {
2289 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2290 requirement,
2291 extra,
2292 env,
2293 python_marker,
2294 python_requirement,
2295 ))
2296 })
2297 }
2298
2299 fn is_requirement_applicable(
2302 requirement: &Requirement,
2303 extra: Option<&ExtraName>,
2304 env: &ResolverEnvironment,
2305 python_marker: MarkerTree,
2306 python_requirement: &PythonRequirement,
2307 ) -> bool {
2308 match extra {
2310 Some(source_extra) => {
2311 if requirement.evaluate_markers(env.marker_environment(), &[]) {
2313 return false;
2314 }
2315 if !requirement
2316 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2317 {
2318 return false;
2319 }
2320 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2321 {
2322 return false;
2323 }
2324 }
2325 None => {
2326 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2327 return false;
2328 }
2329 }
2330 }
2331
2332 if python_marker.is_disjoint(requirement.marker) {
2335 trace!(
2336 "Skipping {requirement} because of Requires-Python: {requires_python}",
2337 requires_python = python_requirement.target(),
2338 );
2339 return false;
2340 }
2341
2342 if !env.included_by_marker(requirement.marker) {
2345 trace!("Skipping {requirement} because of {env}");
2346 return false;
2347 }
2348
2349 true
2350 }
2351
2352 fn constraints_for_requirement<'data, 'parameters>(
2355 &'data self,
2356 requirement: Cow<'data, Requirement>,
2357 extra: Option<&'parameters ExtraName>,
2358 env: &'parameters ResolverEnvironment,
2359 python_marker: MarkerTree,
2360 python_requirement: &'parameters PythonRequirement,
2361 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2362 where
2363 'data: 'parameters,
2364 {
2365 self.constraints
2366 .get(&requirement.name)
2367 .into_iter()
2368 .flatten()
2369 .filter_map(move |constraint| {
2370 let constraint = if constraint.marker.is_true() {
2373 if requirement.marker.is_true() {
2377 Cow::Borrowed(constraint)
2378 } else {
2379 let mut marker = constraint.marker;
2380 marker.and(requirement.marker);
2381
2382 if marker.is_false() {
2383 trace!(
2384 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2385 constraint.marker.try_to_string().unwrap(),
2386 requirement.marker.try_to_string().unwrap(),
2387 );
2388 return None;
2389 }
2390
2391 Cow::Owned(Requirement {
2392 name: constraint.name.clone(),
2393 extras: constraint.extras.clone(),
2394 groups: constraint.groups.clone(),
2395 source: constraint.source.clone(),
2396 origin: constraint.origin.clone(),
2397 marker,
2398 })
2399 }
2400 } else {
2401 let requires_python = python_requirement.target();
2402
2403 let mut marker = constraint.marker;
2404 marker.and(requirement.marker);
2405
2406 if marker.is_false() {
2407 trace!(
2408 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2409 constraint.marker.try_to_string().unwrap(),
2410 requirement.marker.try_to_string().unwrap(),
2411 );
2412 return None;
2413 }
2414
2415 if python_marker.is_disjoint(marker) {
2419 trace!(
2420 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2421 );
2422 return None;
2423 }
2424
2425 if marker == constraint.marker {
2426 Cow::Borrowed(constraint)
2427 } else {
2428 Cow::Owned(Requirement {
2429 name: constraint.name.clone(),
2430 extras: constraint.extras.clone(),
2431 groups: constraint.groups.clone(),
2432 source: constraint.source.clone(),
2433 origin: constraint.origin.clone(),
2434 marker,
2435 })
2436 }
2437 };
2438
2439 if !env.included_by_marker(constraint.marker) {
2442 trace!("Skipping {constraint} because of {env}");
2443 return None;
2444 }
2445
2446 match extra {
2448 Some(source_extra) => {
2449 if !constraint
2450 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2451 {
2452 return None;
2453 }
2454 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2455 {
2456 return None;
2457 }
2458 }
2459 None => {
2460 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2461 return None;
2462 }
2463 }
2464 }
2465
2466 Some(constraint)
2467 })
2468 }
2469
2470 async fn fetch<Provider: ResolverProvider>(
2472 self: Arc<Self>,
2473 provider: Arc<Provider>,
2474 request_stream: Receiver<Request>,
2475 ) -> Result<(), ResolveError> {
2476 let mut response_stream = ReceiverStream::new(request_stream)
2477 .map(|request| self.process_request(request, &*provider).boxed_local())
2478 .buffer_unordered(usize::MAX);
2482
2483 while let Some(response) = response_stream.next().await {
2484 match response? {
2485 Some(Response::Package(name, index, version_map)) => {
2486 trace!("Received package metadata for: {name}");
2487 if let Some(index) = index {
2488 self.index
2489 .explicit()
2490 .done((name, index), Arc::new(version_map));
2491 } else {
2492 self.index.implicit().done(name, Arc::new(version_map));
2493 }
2494 }
2495 Some(Response::Installed { dist, metadata }) => {
2496 trace!("Received installed distribution metadata for: {dist}");
2497 self.index
2498 .distributions()
2499 .done(dist.distribution_id(), Arc::new(metadata));
2500 }
2501 Some(Response::Dist { dist, metadata }) => {
2502 let dist_kind = match dist {
2503 Dist::Built(_) => "built",
2504 Dist::Source(_) => "source",
2505 };
2506 trace!("Received {dist_kind} distribution metadata for: {dist}");
2507 if let MetadataResponse::Unavailable(reason) = &metadata {
2508 let message = UnavailableVersion::from(reason).singular_message();
2509 if let Some(err) = reason.source() {
2510 warn!("{dist} {message}: {err}");
2512 } else {
2513 warn!("{dist} {message}");
2514 }
2515 }
2516 self.index
2517 .distributions()
2518 .done(dist.distribution_id(), Arc::new(metadata));
2519 }
2520 None => {}
2521 }
2522 }
2523
2524 Ok::<(), ResolveError>(())
2525 }
2526
2527 #[instrument(skip_all, fields(%request))]
2528 async fn process_request<Provider: ResolverProvider>(
2529 &self,
2530 request: Request,
2531 provider: &Provider,
2532 ) -> Result<Option<Response>, ResolveError> {
2533 match request {
2534 Request::Package(package_name, index) => {
2536 let package_versions = provider
2537 .get_package_versions(&package_name, index.as_ref())
2538 .boxed_local()
2539 .await
2540 .map_err(ResolveError::Client)?;
2541
2542 Ok(Some(Response::Package(
2543 package_name,
2544 index.map(IndexMetadata::into_url),
2545 package_versions,
2546 )))
2547 }
2548
2549 Request::Dist(dist) => {
2551 if let Some(version) = dist.version() {
2552 if let Some(index) = dist.index() {
2553 let versions_response = self.index.implicit().get(dist.name());
2555 if let Some(VersionsResponse::Found(version_maps)) =
2556 versions_response.as_deref()
2557 {
2558 for version_map in version_maps {
2559 if version_map.index() == Some(index) {
2560 let Some(metadata) = version_map.get_metadata(version) else {
2561 continue;
2562 };
2563 debug!("Found registry-provided metadata for: {dist}");
2564 return Ok(Some(Response::Dist {
2565 dist,
2566 metadata: MetadataResponse::Found(
2567 ArchiveMetadata::from_metadata23(metadata),
2568 ),
2569 }));
2570 }
2571 }
2572 }
2573
2574 let versions_response = self
2576 .index
2577 .explicit()
2578 .get(&(dist.name().clone(), index.clone()));
2579 if let Some(VersionsResponse::Found(version_maps)) =
2580 versions_response.as_deref()
2581 {
2582 for version_map in version_maps {
2583 let Some(metadata) = version_map.get_metadata(version) else {
2584 continue;
2585 };
2586 debug!("Found registry-provided metadata for: {dist}");
2587 return Ok(Some(Response::Dist {
2588 dist,
2589 metadata: MetadataResponse::Found(
2590 ArchiveMetadata::from_metadata23(metadata),
2591 ),
2592 }));
2593 }
2594 }
2595 }
2596 }
2597
2598 let metadata = provider
2599 .get_or_build_wheel_metadata(&dist)
2600 .boxed_local()
2601 .await?;
2602
2603 if let MetadataResponse::Found(metadata) = &metadata {
2604 if &metadata.metadata.name != dist.name() {
2605 return Err(ResolveError::MismatchedPackageName {
2606 request: "distribution metadata",
2607 expected: dist.name().clone(),
2608 actual: metadata.metadata.name.clone(),
2609 });
2610 }
2611 }
2612
2613 Ok(Some(Response::Dist { dist, metadata }))
2614 }
2615
2616 Request::Installed(dist) => {
2617 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2618
2619 if let MetadataResponse::Found(metadata) = &metadata {
2620 if &metadata.metadata.name != dist.name() {
2621 return Err(ResolveError::MismatchedPackageName {
2622 request: "installed metadata",
2623 expected: dist.name().clone(),
2624 actual: metadata.metadata.name.clone(),
2625 });
2626 }
2627 }
2628
2629 Ok(Some(Response::Installed { dist, metadata }))
2630 }
2631
2632 Request::Prefetch(package_name, range, python_requirement) => {
2634 let versions_response = self
2636 .index
2637 .implicit()
2638 .wait(&package_name)
2639 .await
2640 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2641
2642 let version_map = match *versions_response {
2643 VersionsResponse::Found(ref version_map) => version_map,
2644 VersionsResponse::NoIndex => {
2646 self.unavailable_packages
2647 .pin()
2648 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2649
2650 return Ok(None);
2651 }
2652 VersionsResponse::Offline => {
2653 self.unavailable_packages
2654 .pin()
2655 .insert(package_name.clone(), UnavailablePackage::Offline);
2656
2657 return Ok(None);
2658 }
2659 VersionsResponse::NotFound => {
2660 self.unavailable_packages
2661 .pin()
2662 .insert(package_name.clone(), UnavailablePackage::NotFound);
2663
2664 return Ok(None);
2665 }
2666 };
2667
2668 let env = ResolverEnvironment::universal(vec![]);
2671
2672 let Some(candidate) = self.selector.select(
2675 &package_name,
2676 &range,
2677 version_map,
2678 &self.preferences,
2679 &self.installed_packages,
2680 &self.exclusions,
2681 None,
2682 &env,
2683 self.tags.as_ref(),
2684 ) else {
2685 return Ok(None);
2686 };
2687
2688 let Some(dist) = candidate.compatible() else {
2690 return Ok(None);
2691 };
2692
2693 for version_map in version_map {
2695 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2696 let dist = dist.for_resolution();
2697 if version_map.index() == dist.index() {
2698 debug!("Found registry-provided metadata for: {dist}");
2699
2700 let metadata =
2701 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2702
2703 let dist = dist.to_owned();
2704 if &package_name != dist.name() {
2705 return Err(ResolveError::MismatchedPackageName {
2706 request: "distribution",
2707 expected: package_name,
2708 actual: dist.name().clone(),
2709 });
2710 }
2711
2712 let response = match dist {
2713 ResolvedDist::Installable { dist, .. } => Response::Dist {
2714 dist: (*dist).clone(),
2715 metadata,
2716 },
2717 ResolvedDist::Installed { dist } => Response::Installed {
2718 dist: (*dist).clone(),
2719 metadata,
2720 },
2721 };
2722
2723 return Ok(Some(response));
2724 }
2725 }
2726 }
2727
2728 if dist.wheel().is_none() {
2732 if !self.selector.use_highest_version(&package_name, &env) {
2733 if let Some((lower, _)) = range.iter().next() {
2734 if lower == Bound::Unbounded {
2735 debug!(
2736 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2737 );
2738 return Ok(None);
2739 }
2740 }
2741 }
2742 }
2743
2744 let requires_python = match dist {
2746 CompatibleDist::InstalledDist(_) => None,
2747 CompatibleDist::SourceDist { sdist, .. }
2748 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2749 sdist.file.requires_python.as_ref()
2750 }
2751 CompatibleDist::CompatibleWheel { wheel, .. } => {
2752 wheel.file.requires_python.as_ref()
2753 }
2754 };
2755 if let Some(requires_python) = requires_python.as_ref() {
2756 if !python_requirement.target().is_contained_by(requires_python) {
2757 return Ok(None);
2758 }
2759 }
2760
2761 if !self
2763 .hasher
2764 .allows_package(candidate.name(), candidate.version())
2765 {
2766 return Ok(None);
2767 }
2768
2769 let dist = dist.for_resolution();
2771 if self.index.distributions().register(dist.distribution_id()) {
2772 let dist = dist.to_owned();
2773 if &package_name != dist.name() {
2774 return Err(ResolveError::MismatchedPackageName {
2775 request: "distribution",
2776 expected: package_name,
2777 actual: dist.name().clone(),
2778 });
2779 }
2780
2781 let response = match dist {
2782 ResolvedDist::Installable { dist, .. } => {
2783 let metadata = provider
2784 .get_or_build_wheel_metadata(&dist)
2785 .boxed_local()
2786 .await?;
2787
2788 Response::Dist {
2789 dist: (*dist).clone(),
2790 metadata,
2791 }
2792 }
2793 ResolvedDist::Installed { dist } => {
2794 let metadata =
2795 provider.get_installed_metadata(&dist).boxed_local().await?;
2796
2797 Response::Installed {
2798 dist: (*dist).clone(),
2799 metadata,
2800 }
2801 }
2802 };
2803
2804 Ok(Some(response))
2805 } else {
2806 Ok(None)
2807 }
2808 }
2809 }
2810 }
2811
2812 fn convert_no_solution_err(
2813 &self,
2814 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2815 fork_urls: ForkUrls,
2816 fork_indexes: ForkIndexes,
2817 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2818 env: ResolverEnvironment,
2819 current_environment: MarkerEnvironment,
2820 visited: &FxHashSet<PackageName>,
2821 ) -> ResolveError {
2822 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2823 err,
2824 ));
2825 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2826
2827 let mut unavailable_packages = FxHashMap::default();
2828 for package in derivation_tree_packages(&err) {
2829 if let PubGrubPackageInner::Package { name, .. } = &**package {
2830 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2831 unavailable_packages.insert(name.clone(), reason.clone());
2832 }
2833 }
2834 }
2835
2836 let mut incomplete_packages = FxHashMap::default();
2837 let incomplete_packages_cache = self.incomplete_packages.pin();
2838 for package in derivation_tree_packages(&err) {
2839 if let PubGrubPackageInner::Package { name, .. } = &**package
2840 && let Some(versions) = incomplete_packages_cache.get(name)
2841 {
2842 for (version, reason) in &versions.pin() {
2843 incomplete_packages
2844 .entry(name.clone())
2845 .or_insert_with(BTreeMap::default)
2846 .insert(version.clone(), reason.clone());
2847 }
2848 }
2849 }
2850
2851 let mut available_indexes = FxHashMap::default();
2852 let mut included_versions = FxHashMap::default();
2853 let mut available_versions = FxHashMap::default();
2854
2855 let available_version_cutoff: Option<jiff::Timestamp> =
2856 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2857 .ok()
2858 .and_then(|s| s.parse().ok());
2859
2860 for package in derivation_tree_packages(&err) {
2861 let Some(name) = package.name() else { continue };
2862 if !visited.contains(name) {
2863 continue;
2868 }
2869 let versions_response = if let Some(index) = fork_indexes.get(name) {
2870 self.index
2871 .explicit()
2872 .get(&(name.clone(), index.url().clone()))
2873 } else {
2874 self.index.implicit().get(name)
2875 };
2876 if let Some(response) = versions_response {
2877 if let VersionsResponse::Found(ref version_maps) = *response {
2878 for version_map in version_maps {
2880 let package_included_versions = included_versions
2881 .entry(name.clone())
2882 .or_insert_with(BTreeSet::new);
2883 let package_available_versions = available_versions
2884 .entry(name.clone())
2885 .or_insert_with(BTreeSet::new);
2886
2887 for (version, dists) in version_map.iter(&Ranges::full()) {
2888 let excluded_from_included = || {
2893 let Some(included_version_cutoff) =
2894 version_map.included_version_cutoff()
2895 else {
2896 return false;
2897 };
2898 let Some(prioritized_dist) = dists.prioritized_dist() else {
2899 return true;
2900 };
2901 prioritized_dist.files().all(|file| {
2902 file.upload_time_utc_ms.is_none_or(|upload_time| {
2903 upload_time >= included_version_cutoff.as_millisecond()
2904 })
2905 })
2906 };
2907
2908 if !excluded_from_included() {
2909 package_included_versions.insert(version.clone());
2910 }
2911
2912 let excluded_from_available = || {
2918 let Some(ref exclude_newer) = available_version_cutoff else {
2919 return false;
2920 };
2921 let Some(prioritized_dist) = dists.prioritized_dist() else {
2922 return false;
2923 };
2924 prioritized_dist.files().all(|file| {
2925 file.upload_time_utc_ms.is_some_and(|upload_time| {
2926 upload_time >= exclude_newer.as_millisecond()
2927 })
2928 })
2929 };
2930
2931 if !excluded_from_available() {
2932 package_available_versions.insert(version.clone());
2933 }
2934 }
2935 }
2936
2937 available_indexes
2939 .entry(name.clone())
2940 .or_insert(BTreeSet::new())
2941 .extend(
2942 version_maps
2943 .iter()
2944 .filter_map(|version_map| version_map.index().cloned()),
2945 );
2946 }
2947 }
2948 }
2949
2950 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2951 err,
2952 self.index.clone(),
2953 included_versions,
2954 available_versions,
2955 available_indexes,
2956 self.selector.clone(),
2957 self.python_requirement.clone(),
2958 self.locations.clone(),
2959 self.capabilities.clone(),
2960 unavailable_packages,
2961 incomplete_packages,
2962 fork_urls,
2963 fork_indexes,
2964 env,
2965 current_environment,
2966 self.tags.clone(),
2967 self.workspace_members.clone(),
2968 self.options.clone(),
2969 )))
2970 }
2971
2972 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2973 if let Some(reporter) = self.reporter.as_ref() {
2974 match &**package {
2975 PubGrubPackageInner::Root(_) => {}
2976 PubGrubPackageInner::Python(_) => {}
2977 PubGrubPackageInner::System(_) => {}
2978 PubGrubPackageInner::Marker { .. } => {}
2979 PubGrubPackageInner::Extra { .. } => {}
2980 PubGrubPackageInner::Group { .. } => {}
2981 PubGrubPackageInner::Package { name, .. } => {
2982 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2983 }
2984 }
2985 }
2986 }
2987
2988 fn on_complete(&self) {
2989 if let Some(reporter) = self.reporter.as_ref() {
2990 reporter.on_complete();
2991 }
2992 }
2993}
2994
2995#[derive(Clone)]
2997pub(crate) struct ForkState {
2998 pubgrub: State<UvDependencyProvider>,
3006 initial_id: Option<Id<PubGrubPackage>>,
3009 initial_version: Option<Version>,
3012 next: Id<PubGrubPackage>,
3014 pins: FilePins,
3023 fork_urls: ForkUrls,
3029 fork_indexes: ForkIndexes,
3034 priorities: PubGrubPriorities,
3040 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3043 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3045 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3047 known_versions: FxHashMap<PackageName, Arc<[Version]>>,
3052 env: ResolverEnvironment,
3067 python_requirement: PythonRequirement,
3080 conflict_tracker: ConflictTracker,
3081 prefetcher: BatchPrefetcher,
3085}
3086
3087impl ForkState {
3088 fn new(
3089 pubgrub: State<UvDependencyProvider>,
3090 env: ResolverEnvironment,
3091 python_requirement: PythonRequirement,
3092 prefetcher: BatchPrefetcher,
3093 ) -> Self {
3094 Self {
3095 initial_id: None,
3096 initial_version: None,
3097 next: pubgrub.root_package,
3098 pubgrub,
3099 pins: FilePins::default(),
3100 fork_urls: ForkUrls::default(),
3101 fork_indexes: ForkIndexes::default(),
3102 priorities: PubGrubPriorities::default(),
3103 added_dependencies: FxHashMap::default(),
3104 pre_visited: FxHashMap::default(),
3105 selected_versions: FxHashMap::default(),
3106 known_versions: FxHashMap::default(),
3107 env,
3108 python_requirement,
3109 conflict_tracker: ConflictTracker::default(),
3110 prefetcher,
3111 }
3112 }
3113
3114 fn visit_package_version_dependencies(
3117 &mut self,
3118 for_package: Id<PubGrubPackage>,
3119 for_version: &Version,
3120 urls: &Urls,
3121 indexes: &Indexes,
3122 dependencies: &[PubGrubDependency],
3123 git: &GitResolver,
3124 workspace_members: &BTreeSet<PackageName>,
3125 resolution_strategy: &ResolutionStrategy,
3126 ) -> Result<(), ResolveError> {
3127 for dependency in dependencies {
3128 let PubGrubDependency {
3129 package,
3130 version,
3131 parent: _,
3132 source,
3133 } = dependency;
3134
3135 let mut has_url = false;
3136 if let Some(name) = package.name() {
3137 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3142 self.fork_urls.insert(name, url, &self.env)?;
3143 has_url = true;
3144 }
3145
3146 if let Some(index) = source.explicit_index() {
3147 self.fork_indexes.insert(name, index, &self.env)?;
3148 }
3149
3150 for index in indexes.get(name, &self.env) {
3152 self.fork_indexes.insert(name, index, &self.env)?;
3153 }
3154 }
3155
3156 if let Some(name) = self.pubgrub.package_store[for_package]
3157 .name_no_root()
3158 .filter(|name| !workspace_members.contains(name))
3159 {
3160 debug!(
3161 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3162 );
3163 } else {
3164 debug!("Adding direct dependency: {package}{version}");
3166
3167 let missing_lower_bound = version
3169 .bounding_range()
3170 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3171 let strategy_lowest = matches!(
3172 resolution_strategy,
3173 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3174 );
3175
3176 if !has_url && missing_lower_bound && strategy_lowest {
3177 let name = package.name_no_root().unwrap();
3178 let bound_on_other_package = dependencies.iter().any(|other| {
3185 Some(name) == other.package.name()
3186 && !other
3187 .version
3188 .bounding_range()
3189 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3190 });
3191
3192 if !bound_on_other_package {
3193 warn_user_once!(
3194 "The direct dependency `{name}` is unpinned. \
3195 Consider setting a lower bound when using `--resolution lowest` \
3196 or `--resolution lowest-direct` to avoid using outdated versions.",
3197 );
3198 }
3199 }
3200 }
3201
3202 self.priorities.insert(package, version, &self.fork_urls);
3204 if let Some(base_package) = package.base_package() {
3207 self.priorities
3208 .insert(&base_package, version, &self.fork_urls);
3209 }
3210 }
3211
3212 Ok(())
3213 }
3214
3215 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3221 &mut self,
3222 for_package: Id<PubGrubPackage>,
3223 for_version: &Version,
3224 dependencies: Vec<PubGrubDependency>,
3225 index: &InMemoryIndex,
3226 installed_packages: &InstalledPackages,
3227 ) {
3228 for dependency in &dependencies {
3229 let PubGrubDependency {
3230 package,
3231 version,
3232 parent: _,
3233 source: _,
3234 } = dependency;
3235
3236 let Some(base_package) = package.base_package() else {
3237 continue;
3238 };
3239
3240 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3241 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3242 self.pubgrub.add_proxy_package_incompatibility(
3243 proxy_package,
3244 base_package_id,
3245 version.clone(),
3246 );
3247 }
3248
3249 let versions = Range::singleton(for_version.clone());
3252 let versions = if let Some(known_versions) =
3253 ResolverState::<InstalledPackages>::known_versions(
3254 index,
3255 installed_packages,
3256 &self.fork_urls,
3257 &self.fork_indexes,
3258 &mut self.known_versions,
3259 &self.pubgrub.package_store[self.next],
3260 )
3261 .filter(|versions| !versions.is_empty())
3262 {
3263 versions.widen_versions(known_versions)
3264 } else {
3265 versions
3268 };
3269 let conflict = self.pubgrub.add_package_version_dependencies(
3270 self.next,
3271 for_version.clone(),
3272 versions,
3273 dependencies.into_iter().map(|dependency| {
3274 let PubGrubDependency {
3275 package,
3276 version,
3277 parent: _,
3278 source: _,
3279 } = dependency;
3280 (package, version)
3281 }),
3282 );
3283
3284 if let Some(incompatibility) = conflict {
3287 self.record_conflict(for_package, Some(for_version), incompatibility);
3288 }
3289 }
3290
3291 fn record_conflict(
3292 &mut self,
3293 affected: Id<PubGrubPackage>,
3294 version: Option<&Version>,
3295 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3296 ) {
3297 let mut culprit_is_real = false;
3298 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3299 if incompatible == affected {
3300 continue;
3301 }
3302 if self.pubgrub.package_store[affected].name()
3303 == self.pubgrub.package_store[incompatible].name()
3304 {
3305 continue;
3308 }
3309 culprit_is_real = true;
3310 let culprit_count = self
3311 .conflict_tracker
3312 .culprit
3313 .entry(incompatible)
3314 .or_default();
3315 *culprit_count += 1;
3316 if *culprit_count == CONFLICT_THRESHOLD {
3317 self.conflict_tracker.deprioritize.push(incompatible);
3318 }
3319 }
3320 if culprit_is_real {
3323 if tracing::enabled!(Level::DEBUG) {
3324 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3325 .iter()
3326 .map(|(package, _term)| &self.pubgrub.package_store[package])
3327 .join(", ");
3328 if let Some(version) = version {
3329 debug!(
3330 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3331 self.pubgrub.package_store[affected], version, incompatibility
3332 );
3333 } else {
3334 debug!(
3335 "Recording unit propagation conflict of {} from incompatibility of ({})",
3336 self.pubgrub.package_store[affected], incompatibility
3337 );
3338 }
3339 }
3340
3341 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3342 *affected_count += 1;
3343 if *affected_count == CONFLICT_THRESHOLD {
3344 self.conflict_tracker.prioritize.push(self.next);
3345 }
3346 }
3347 }
3348
3349 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3350 if let UnavailableVersion::IncompatibleDist(
3354 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3355 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3356 ) = reason
3357 {
3358 let package = &self.next;
3359 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3360 PubGrubPackageInner::Python(match kind {
3361 PythonRequirementKind::Installed => PubGrubPython::Installed,
3362 PythonRequirementKind::Target => PubGrubPython::Target,
3363 }),
3364 ));
3365 self.pubgrub
3366 .add_incompatibility(Incompatibility::from_dependency(
3367 *package,
3368 Range::singleton(version.clone()),
3369 (
3370 python,
3371 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3372 ),
3373 ));
3374 self.pubgrub
3375 .partial_solution
3376 .add_decision(self.next, version);
3377 return;
3378 }
3379 self.pubgrub
3380 .add_incompatibility(Incompatibility::custom_version(
3381 self.next,
3382 version.clone(),
3383 UnavailableReason::Version(reason),
3384 ));
3385 }
3386
3387 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3393 self.env = env;
3394 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3396 debug!("Narrowed `requires-python` bound to: {}", req.target());
3397 self.python_requirement = req;
3398 }
3399 self
3400 }
3401
3402 fn source(
3406 &self,
3407 name: &PackageName,
3408 version: &Version,
3409 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3410 let url = self.fork_urls.get(name);
3411 let index = url
3412 .is_none()
3413 .then(|| {
3414 self.pins
3415 .get(name, version)
3416 .expect("Every package should be pinned")
3417 .index()
3418 })
3419 .flatten();
3420 (url, index)
3421 }
3422
3423 fn into_resolution(self) -> Resolution {
3424 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3425 let edge_count: usize = solution
3426 .keys()
3427 .map(|package| self.pubgrub.incompatibilities[package].len())
3428 .sum();
3429 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3430 for (package, self_version) in &solution {
3431 for id in &self.pubgrub.incompatibilities[package] {
3432 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3433 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3434 &incompatibility.kind
3435 else {
3436 continue;
3437 };
3438 let (self_package, dependency_package) = (*self_package, *dependency_package);
3439 let Some((self_range, dependency_range)) =
3440 incompatibility.dependency_version_sets()
3441 else {
3442 continue;
3443 };
3444 let dependency_range =
3445 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3446 if *package != self_package {
3447 continue;
3448 }
3449 if !self_range.contains(self_version) {
3450 continue;
3451 }
3452 let Some(dependency_version) = solution.get(&dependency_package) else {
3453 continue;
3454 };
3455 if !dependency_range.contains(dependency_version) {
3456 continue;
3457 }
3458
3459 let self_package = &self.pubgrub.package_store[self_package];
3460 let dependency_package = &self.pubgrub.package_store[dependency_package];
3461
3462 let (self_name, self_extra, self_group) = match &**self_package {
3463 PubGrubPackageInner::Package {
3464 name: self_name,
3465 extra: self_extra,
3466 group: self_group,
3467 marker: _,
3468 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3469
3470 PubGrubPackageInner::Root(_) => (None, None, None),
3471
3472 _ => continue,
3473 };
3474
3475 let (self_url, self_index) = self_name
3476 .map(|self_name| self.source(self_name, self_version))
3477 .unwrap_or((None, None));
3478
3479 match **dependency_package {
3480 PubGrubPackageInner::Package {
3481 name: ref dependency_name,
3482 extra: ref dependency_extra,
3483 group: ref dependency_dev,
3484 marker: ref dependency_marker,
3485 } => {
3486 debug_assert!(
3487 dependency_extra.is_none(),
3488 "Packages should depend on an extra proxy"
3489 );
3490 debug_assert!(
3491 dependency_dev.is_none(),
3492 "Packages should depend on a group proxy"
3493 );
3494
3495 if self_group.is_none() {
3498 if self_name == Some(dependency_name) {
3499 continue;
3500 }
3501 }
3502
3503 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3504
3505 let edge = ResolutionDependencyEdge {
3506 from: self_name.cloned(),
3507 from_version: self_version.clone(),
3508 from_url: self_url.cloned(),
3509 from_index: self_index.cloned(),
3510 from_extra: self_extra.cloned(),
3511 from_group: self_group.cloned(),
3512 to: dependency_name.clone(),
3513 to_version: dependency_version.clone(),
3514 to_url: to_url.cloned(),
3515 to_index: to_index.cloned(),
3516 to_extra: dependency_extra.clone(),
3517 to_group: dependency_dev.clone(),
3518 marker: *dependency_marker,
3519 };
3520 edges.push(edge);
3521 }
3522
3523 PubGrubPackageInner::Marker {
3524 name: ref dependency_name,
3525 marker: ref dependency_marker,
3526 } => {
3527 if self_group.is_none() {
3530 if self_name == Some(dependency_name) {
3531 continue;
3532 }
3533 }
3534
3535 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3536
3537 let edge = ResolutionDependencyEdge {
3538 from: self_name.cloned(),
3539 from_version: self_version.clone(),
3540 from_url: self_url.cloned(),
3541 from_index: self_index.cloned(),
3542 from_extra: self_extra.cloned(),
3543 from_group: self_group.cloned(),
3544 to: dependency_name.clone(),
3545 to_version: dependency_version.clone(),
3546 to_url: to_url.cloned(),
3547 to_index: to_index.cloned(),
3548 to_extra: None,
3549 to_group: None,
3550 marker: *dependency_marker,
3551 };
3552 edges.push(edge);
3553 }
3554
3555 PubGrubPackageInner::Extra {
3556 name: ref dependency_name,
3557 extra: ref dependency_extra,
3558 marker: ref dependency_marker,
3559 } => {
3560 if self_group.is_none() {
3561 debug_assert!(
3562 self_name != Some(dependency_name),
3563 "Extras should be flattened"
3564 );
3565 }
3566 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3567
3568 let edge = ResolutionDependencyEdge {
3570 from: self_name.cloned(),
3571 from_version: self_version.clone(),
3572 from_url: self_url.cloned(),
3573 from_index: self_index.cloned(),
3574 from_extra: self_extra.cloned(),
3575 from_group: self_group.cloned(),
3576 to: dependency_name.clone(),
3577 to_version: dependency_version.clone(),
3578 to_url: to_url.cloned(),
3579 to_index: to_index.cloned(),
3580 to_extra: Some(dependency_extra.clone()),
3581 to_group: None,
3582 marker: *dependency_marker,
3583 };
3584 edges.push(edge);
3585
3586 let edge = ResolutionDependencyEdge {
3588 from: self_name.cloned(),
3589 from_version: self_version.clone(),
3590 from_url: self_url.cloned(),
3591 from_index: self_index.cloned(),
3592 from_extra: self_extra.cloned(),
3593 from_group: self_group.cloned(),
3594 to: dependency_name.clone(),
3595 to_version: dependency_version.clone(),
3596 to_url: to_url.cloned(),
3597 to_index: to_index.cloned(),
3598 to_extra: None,
3599 to_group: None,
3600 marker: *dependency_marker,
3601 };
3602 edges.push(edge);
3603 }
3604
3605 PubGrubPackageInner::Group {
3606 name: ref dependency_name,
3607 group: ref dependency_group,
3608 marker: ref dependency_marker,
3609 } => {
3610 debug_assert!(
3611 self_name != Some(dependency_name),
3612 "Groups should be flattened"
3613 );
3614
3615 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3616
3617 let edge = ResolutionDependencyEdge {
3620 from: self_name.cloned(),
3621 from_version: self_version.clone(),
3622 from_url: self_url.cloned(),
3623 from_index: self_index.cloned(),
3624 from_extra: self_extra.cloned(),
3625 from_group: self_group.cloned(),
3626 to: dependency_name.clone(),
3627 to_version: dependency_version.clone(),
3628 to_url: to_url.cloned(),
3629 to_index: to_index.cloned(),
3630 to_extra: None,
3631 to_group: Some(dependency_group.clone()),
3632 marker: *dependency_marker,
3633 };
3634 edges.push(edge);
3635 }
3636
3637 _ => {}
3638 }
3639 }
3640 }
3641
3642 let nodes = solution
3643 .into_iter()
3644 .filter_map(|(package, version)| {
3645 if let PubGrubPackageInner::Package {
3646 name,
3647 extra,
3648 group,
3649 marker: MarkerTree::TRUE,
3650 } = &*self.pubgrub.package_store[package]
3651 {
3652 let (url, index) = self.source(name, &version);
3653 Some((
3654 ResolutionPackage {
3655 name: name.clone(),
3656 extra: extra.clone(),
3657 dev: group.clone(),
3658 url: url.cloned(),
3659 index: index.cloned(),
3660 },
3661 version,
3662 ))
3663 } else {
3664 None
3665 }
3666 })
3667 .collect();
3668
3669 Resolution {
3670 nodes,
3671 edges,
3672 pins: self.pins,
3673 env: self.env,
3674 }
3675 }
3676}
3677
3678#[derive(Debug)]
3680pub(crate) struct Resolution {
3681 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3682 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3685 pub(crate) pins: FilePins,
3687 pub(crate) env: ResolverEnvironment,
3689}
3690
3691#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3694pub(crate) struct ResolutionPackage {
3695 pub(crate) name: PackageName,
3696 pub(crate) extra: Option<ExtraName>,
3697 pub(crate) dev: Option<GroupName>,
3698 pub(crate) url: Option<VerbatimParsedUrl>,
3700 pub(crate) index: Option<IndexUrl>,
3702}
3703
3704#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3707pub(crate) struct ResolutionDependencyEdge {
3708 pub(super) from: Option<PackageName>,
3710 pub(super) from_version: Version,
3711 pub(super) from_url: Option<VerbatimParsedUrl>,
3712 pub(super) from_index: Option<IndexUrl>,
3713 pub(super) from_extra: Option<ExtraName>,
3714 pub(super) from_group: Option<GroupName>,
3715 pub(super) to: PackageName,
3716 pub(super) to_version: Version,
3717 pub(super) to_url: Option<VerbatimParsedUrl>,
3718 pub(super) to_index: Option<IndexUrl>,
3719 pub(super) to_extra: Option<ExtraName>,
3720 pub(super) to_group: Option<GroupName>,
3721 pub(super) marker: MarkerTree,
3722}
3723
3724impl ResolutionDependencyEdge {
3725 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3726 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3730 }
3731}
3732
3733#[derive(Debug)]
3735#[expect(clippy::large_enum_variant)]
3736pub(crate) enum Request {
3737 Package(PackageName, Option<IndexMetadata>),
3739 Dist(Dist),
3741 Installed(InstalledDist),
3743 Prefetch(PackageName, Range<Version>, PythonRequirement),
3745}
3746
3747impl<'a> From<ResolvedDistRef<'a>> for Request {
3748 fn from(dist: ResolvedDistRef<'a>) -> Self {
3749 match dist {
3755 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3756 let source = prioritized.source_dist().expect("a source distribution");
3759 assert_eq!(
3760 (&sdist.name, &sdist.version),
3761 (&source.name, &source.version),
3762 "expected chosen sdist to match prioritized sdist"
3763 );
3764 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3765 }
3766 ResolvedDistRef::InstallableRegistryBuiltDist {
3767 wheel, prioritized, ..
3768 } => {
3769 assert_eq!(
3770 Some(&wheel.filename),
3771 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3772 "expected chosen wheel to match best wheel"
3773 );
3774 let built = prioritized.built_dist().expect("at least one wheel");
3777 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3778 }
3779 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3780 }
3781 }
3782}
3783
3784impl Display for Request {
3785 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3786 match self {
3787 Self::Package(package_name, _) => {
3788 write!(f, "Versions {package_name}")
3789 }
3790 Self::Dist(dist) => {
3791 write!(f, "Metadata {dist}")
3792 }
3793 Self::Installed(dist) => {
3794 write!(f, "Installed metadata {dist}")
3795 }
3796 Self::Prefetch(package_name, range, _) => {
3797 write!(f, "Prefetch {package_name} {range}")
3798 }
3799 }
3800 }
3801}
3802
3803#[derive(Debug)]
3804#[expect(clippy::large_enum_variant)]
3805enum Response {
3806 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3808 Dist {
3810 dist: Dist,
3811 metadata: MetadataResponse,
3812 },
3813 Installed {
3815 dist: InstalledDist,
3816 metadata: MetadataResponse,
3817 },
3818}
3819
3820enum Dependencies {
3826 Unavailable(UnavailableVersion),
3828 Available(Vec<PubGrubDependency>),
3834 Unforkable(Vec<PubGrubDependency>),
3840}
3841
3842impl Dependencies {
3843 fn fork(
3850 self,
3851 env: &ResolverEnvironment,
3852 python_requirement: &PythonRequirement,
3853 conflicts: &Conflicts,
3854 ) -> ForkedDependencies {
3855 let deps = match self {
3856 Self::Available(deps) => deps,
3857 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3858 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3859 };
3860 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3861 for dep in deps {
3862 let name = dep
3863 .package
3864 .name()
3865 .expect("dependency always has a name")
3866 .clone();
3867 name_to_deps.entry(name).or_default().push(dep);
3868 }
3869 let Forks {
3870 mut forks,
3871 diverging_packages,
3872 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3873 if forks.is_empty() {
3874 ForkedDependencies::Unforked(vec![])
3875 } else if forks.len() == 1 {
3876 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3877 } else {
3878 ForkedDependencies::Forked {
3879 forks,
3880 diverging_packages: diverging_packages.into_iter().collect(),
3881 }
3882 }
3883 }
3884}
3885
3886#[derive(Debug)]
3893enum ForkedDependencies {
3894 Unavailable(UnavailableVersion),
3896 Unforked(Vec<PubGrubDependency>),
3900 Forked {
3906 forks: Vec<Fork>,
3907 diverging_packages: Vec<PackageName>,
3909 },
3910}
3911
3912#[derive(Debug, Default)]
3917struct Forks {
3918 forks: Vec<Fork>,
3920 diverging_packages: BTreeSet<PackageName>,
3922}
3923
3924impl Forks {
3925 fn new(
3926 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3927 env: &ResolverEnvironment,
3928 python_requirement: &PythonRequirement,
3929 conflicts: &Conflicts,
3930 ) -> Self {
3931 let python_marker = python_requirement.to_marker_tree();
3932
3933 let mut forks = vec![Fork::new(env.clone())];
3934 let mut diverging_packages = BTreeSet::new();
3935 for (name, mut deps) in name_to_deps {
3936 assert!(!deps.is_empty(), "every name has at least one dependency");
3937 if let [dep] = deps.as_slice() {
3948 if marker::requires_python(dep.package.marker())
3956 .is_none_or(|bound| !python_requirement.raises(&bound))
3957 {
3958 let dep = deps.pop().unwrap();
3959 let marker = dep.package.marker();
3960 for fork in &mut forks {
3961 if fork.env.included_by_marker(marker) {
3962 fork.add_dependency(dep.clone());
3963 }
3964 }
3965 continue;
3966 }
3967 } else {
3968 if let Some(dep) = deps.first() {
3970 let marker = dep.package.marker();
3971 if deps.iter().all(|dep| marker == dep.package.marker()) {
3972 if marker::requires_python(marker)
3976 .is_none_or(|bound| !python_requirement.raises(&bound))
3977 {
3978 for dep in deps {
3979 for fork in &mut forks {
3980 if fork.env.included_by_marker(marker) {
3981 fork.add_dependency(dep.clone());
3982 }
3983 }
3984 }
3985 continue;
3986 }
3987 }
3988 }
3989 }
3990 for dep in deps {
3991 let mut forker = match ForkingPossibility::new(env, &dep) {
3992 ForkingPossibility::Possible(forker) => forker,
3993 ForkingPossibility::DependencyAlwaysExcluded => {
3994 continue;
3997 }
3998 ForkingPossibility::NoForkingPossible => {
3999 for fork in &mut forks {
4002 fork.add_dependency(dep.clone());
4003 }
4004 continue;
4005 }
4006 };
4007 diverging_packages.insert(name.clone());
4009
4010 let mut new = vec![];
4011 for fork in std::mem::take(&mut forks) {
4012 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4013 new.push(fork);
4014 continue;
4015 };
4016 forker = remaining_forker;
4017
4018 for fork_env in envs {
4019 let mut new_fork = fork.clone();
4020 new_fork.set_env(fork_env);
4021 if forker.included(&new_fork.env) {
4026 new_fork.add_dependency(dep.clone());
4027 }
4028 if new_fork.env.included_by_marker(python_marker) {
4031 new.push(new_fork);
4032 }
4033 }
4034 }
4035 forks = new;
4036 }
4037 }
4038 for set in conflicts.iter() {
4053 let mut new = vec![];
4054 for fork in std::mem::take(&mut forks) {
4055 let mut has_conflicting_dependency = false;
4063 for item in set.iter() {
4064 if fork.contains_conflicting_item(item.as_ref()) {
4065 has_conflicting_dependency = true;
4066 diverging_packages.insert(item.package().clone());
4067 break;
4068 }
4069 }
4070 if !has_conflicting_dependency {
4071 new.push(fork);
4072 continue;
4073 }
4074
4075 let non_excluded: Vec<_> = set
4081 .iter()
4082 .filter(|item| fork.env.included_by_group(item.as_ref()))
4083 .collect();
4084 if non_excluded.len() < 2 {
4085 let dominated = non_excluded.iter().all(|item| {
4090 !conflicts.iter().any(|other_set| {
4091 !std::ptr::eq(set, other_set)
4092 && other_set.contains(item.package(), item.kind().as_ref())
4093 && other_set
4094 .iter()
4095 .filter(|other_item| {
4096 other_item.package() != item.package()
4097 || other_item.kind() != item.kind()
4098 })
4099 .any(|other_item| {
4100 fork.env.included_by_group(other_item.as_ref())
4101 })
4102 })
4103 });
4104 if dominated {
4105 let rules: Vec<_> = set
4110 .iter()
4111 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4112 .cloned()
4113 .map(Err)
4114 .collect();
4115 if let Some(filtered) = fork.filter(rules) {
4116 new.push(filtered);
4117 }
4118 continue;
4119 }
4120 }
4121
4122 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4124 new.push(fork_none);
4125 }
4126
4127 for (i, _) in set.iter().enumerate() {
4135 let fork_allows_group = fork.clone().filter(
4136 set.iter()
4137 .cloned()
4138 .enumerate()
4139 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4140 );
4141 if let Some(fork_allows_group) = fork_allows_group {
4142 new.push(fork_allows_group);
4143 }
4144 }
4145 }
4146 forks = new;
4147 }
4148 Self {
4149 forks,
4150 diverging_packages,
4151 }
4152 }
4153}
4154
4155#[derive(Clone, Debug)]
4165struct Fork {
4166 dependencies: Vec<PubGrubDependency>,
4175 conflicts: crate::FxHashbrownSet<ConflictItem>,
4181 env: ResolverEnvironment,
4193}
4194
4195impl Fork {
4196 fn new(env: ResolverEnvironment) -> Self {
4199 Self {
4200 dependencies: vec![],
4201 conflicts: crate::FxHashbrownSet::default(),
4202 env,
4203 }
4204 }
4205
4206 fn add_dependency(&mut self, dep: PubGrubDependency) {
4208 if let Some(conflicting_item) = dep.conflicting_item() {
4209 self.conflicts.insert(conflicting_item.to_owned());
4210 }
4211 self.dependencies.push(dep);
4212 }
4213
4214 fn set_env(&mut self, env: ResolverEnvironment) {
4219 self.env = env;
4220 self.dependencies.retain(|dep| {
4221 let marker = dep.package.marker();
4222 if self.env.included_by_marker(marker) {
4223 return true;
4224 }
4225 if let Some(conflicting_item) = dep.conflicting_item() {
4226 self.conflicts.remove(&conflicting_item);
4227 }
4228 false
4229 });
4230 }
4231
4232 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4235 self.conflicts.contains(&item)
4236 }
4237
4238 fn filter(
4245 mut self,
4246 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4247 ) -> Option<Self> {
4248 self.env = self.env.filter_by_group(rules)?;
4249 self.dependencies.retain(|dep| {
4250 let Some(conflicting_item) = dep.conflicting_item() else {
4251 return true;
4252 };
4253 if self.env.included_by_group(conflicting_item) {
4254 return true;
4255 }
4256 match conflicting_item.kind() {
4257 ConflictKindRef::Project => {
4260 if dep.parent.is_some() {
4261 return true;
4262 }
4263 }
4264 ConflictKindRef::Group(_) => {}
4265 ConflictKindRef::Extra(_) => {}
4266 }
4267 self.conflicts.remove(&conflicting_item);
4268 false
4269 });
4270 Some(self)
4271 }
4272
4273 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4275 let self_bound = self.env.requires_python().unwrap_or_default();
4285 let other_bound = other.env.requires_python().unwrap_or_default();
4286 self_bound.lower().cmp(other_bound.lower())
4287 }
4288
4289 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4291 let self_upper_bounds = self
4296 .dependencies
4297 .iter()
4298 .filter(|dep| {
4299 dep.version
4300 .bounding_range()
4301 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4302 })
4303 .count();
4304 let other_upper_bounds = other
4305 .dependencies
4306 .iter()
4307 .filter(|dep| {
4308 dep.version
4309 .bounding_range()
4310 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4311 })
4312 .count();
4313
4314 self_upper_bounds.cmp(&other_upper_bounds)
4315 }
4316}
4317
4318impl Eq for Fork {}
4319
4320impl PartialEq for Fork {
4321 fn eq(&self, other: &Self) -> bool {
4322 self.dependencies == other.dependencies && self.env == other.env
4323 }
4324}
4325
4326#[derive(Debug, Clone)]
4327pub(crate) struct VersionFork {
4328 env: ResolverEnvironment,
4330 id: Id<PubGrubPackage>,
4332 version: Option<Version>,
4334}
4335
4336fn enrich_dependency_error(
4338 error: ResolveError,
4339 id: Id<PubGrubPackage>,
4340 version: &Version,
4341 pubgrub: &State<UvDependencyProvider>,
4342) -> ResolveError {
4343 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4344 return error;
4345 };
4346 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4347 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4348}
4349
4350fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4352 let package = &state.package_store[id];
4353 if package.is_root() {
4354 return MarkerTree::TRUE;
4355 }
4356
4357 let mut ancestors = FxHashSet::default();
4360 let mut stack = vec![id];
4361 let mut root = None;
4362 ancestors.insert(id);
4363
4364 while let Some(current) = stack.pop() {
4365 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4366 continue;
4367 };
4368
4369 for index in incompatibilities {
4370 let incompat = &state.incompatibility_store[*index];
4371 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4372 if current != *child {
4373 continue;
4374 }
4375 if ancestors.insert(*parent) {
4376 if state.package_store[*parent].is_root() {
4377 root = Some(*parent);
4378 }
4379 stack.push(*parent);
4380 }
4381 }
4382 }
4383 }
4384
4385 let Some(root) = root else {
4386 return MarkerTree::FALSE;
4387 };
4388
4389 let mut environments = FxHashMap::default();
4392 let mut queue = VecDeque::from([root]);
4393 environments.insert(root, MarkerTree::TRUE);
4394
4395 while let Some(current) = queue.pop_front() {
4396 let Some(current_environment) = environments.get(¤t).copied() else {
4397 continue;
4398 };
4399 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4400 continue;
4401 };
4402
4403 for index in incompatibilities {
4404 let incompat = &state.incompatibility_store[*index];
4405 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4406 continue;
4407 };
4408 if current != *parent || !ancestors.contains(child) {
4409 continue;
4410 }
4411
4412 let mut next_environment = state.package_store[*child].marker();
4413 next_environment.and(current_environment);
4414
4415 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4416 let mut combined = *entry;
4417 combined.or(next_environment);
4418 if combined != *entry {
4419 *entry = combined;
4420 queue.push_back(*child);
4421 }
4422 }
4423 }
4424
4425 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4426}
4427
4428#[derive(Debug, Default, Clone)]
4429struct ConflictTracker {
4430 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4432 prioritize: Vec<Id<PubGrubPackage>>,
4436 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4438 deprioritize: Vec<Id<PubGrubPackage>>,
4442}