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