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, Range, 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,
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,
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 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.env,
374 self.current_environment.clone(),
375 &visited,
376 ));
377 }
378 Ok(conflicts) => {
379 for (affected, incompatibility) in conflicts {
380 state.record_conflict(affected, None, incompatibility);
383 }
384 }
385 }
386
387 if self.dependency_mode.is_transitive() {
389 Self::pre_visit(
390 state.pubgrub.partial_solution.prioritized_packages().map(
391 |(id, range)| (id, &state.pubgrub.package_store[id], range),
392 ),
393 &mut state.pre_visited,
394 &self.urls,
395 &self.indexes,
396 &state.python_requirement,
397 request_sink,
398 )?;
399 }
400
401 Self::reprioritize_conflicts(&mut state);
402
403 trace!(
404 "Assigned packages: {}",
405 state
406 .pubgrub
407 .partial_solution
408 .extract_solution()
409 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
410 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
411 .join(", ")
412 );
413 let Some((highest_priority_pkg, _)) =
417 state.pubgrub.partial_solution.pick_highest_priority_pkg(
418 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
419 )
420 else {
421 if tracing::enabled!(Level::DEBUG) {
423 state.prefetcher.log_tried_versions();
424 }
425 debug!(
426 "{} resolution took {:.3}s",
427 state.env,
428 start.elapsed().as_secs_f32()
429 );
430
431 let resolution = state.into_resolution();
432
433 if matches!(
442 self.options.resolution_mode,
443 ResolutionMode::Lowest | ResolutionMode::Highest
444 ) {
445 for (package, version) in &resolution.nodes {
446 preferences.insert(
447 package.name.clone(),
448 package.index.clone(),
449 resolution
450 .env
451 .try_universal_markers()
452 .unwrap_or(UniversalMarker::TRUE),
453 version.clone(),
454 PreferenceSource::Resolver,
455 );
456 }
457 }
458
459 resolutions.push(resolution);
460 continue 'FORK;
461 };
462 trace!(
463 "Chose package for decision: {}. remaining choices: {}",
464 state.pubgrub.package_store[highest_priority_pkg],
465 state
466 .pubgrub
467 .partial_solution
468 .undecided_packages()
469 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
470 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
471 .join(", ")
472 );
473
474 highest_priority_pkg
475 };
476
477 state.next = highest_priority_pkg;
478
479 let next_id = state.next;
481 let next_package = &state.pubgrub.package_store[state.next];
482
483 let url = next_package
484 .name()
485 .and_then(|name| state.fork_urls.get(name));
486 let index = next_package
487 .name()
488 .and_then(|name| state.fork_indexes.get(name));
489
490 self.request_package(next_package, url, index, request_sink)?;
502
503 let version = if let Some(version) = state.initial_version.take() {
504 version
508 } else {
509 let term_intersection = state
510 .pubgrub
511 .partial_solution
512 .term_intersection_for_package(next_id)
513 .expect("a package was chosen but we don't have a term");
514 let range = term_intersection.unwrap_positive();
515
516 let cache_selected_version = state.env.marker_environment().is_some()
520 && url.is_none()
521 && index.is_none();
522 let decision = if cache_selected_version
523 && let Some((selected_range, version)) =
524 state.selected_versions.get(&next_id)
525 && selected_range == range
526 {
527 Some(ResolverVersion::Unforked(version.clone()))
528 } else {
529 let decision = self.choose_version(
530 next_package,
531 next_id,
532 index.map(IndexMetadata::url),
533 range,
534 &mut state.pins,
535 &preferences,
536 &state.fork_urls,
537 &state.env,
538 &state.python_requirement,
539 &state.pubgrub,
540 &mut visited,
541 request_sink,
542 )?;
543
544 if cache_selected_version
545 && let Some(ResolverVersion::Unforked(version)) = &decision
546 {
547 state
548 .selected_versions
549 .insert(next_id, (range.clone(), version.clone()));
550 }
551
552 decision
553 };
554
555 let Some(version) = decision else {
557 debug!("No compatible version found for: {next_package}");
558
559 let term_intersection = state
560 .pubgrub
561 .partial_solution
562 .term_intersection_for_package(next_id)
563 .expect("a package was chosen but we don't have a term");
564
565 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
566 if let Some(reason) = self.unavailable_packages.pin().get(name) {
568 state
569 .pubgrub
570 .add_incompatibility(Incompatibility::custom_term(
571 next_id,
572 term_intersection.clone(),
573 UnavailableReason::Package(reason.clone()),
574 ));
575 continue;
576 }
577 }
578
579 state
580 .pubgrub
581 .add_incompatibility(Incompatibility::no_versions(
582 next_id,
583 term_intersection.clone(),
584 ));
585 continue;
586 };
587
588 let version = match version {
589 ResolverVersion::Unforked(version) => version,
590 ResolverVersion::Forked(forks) => {
591 forked_states.extend(self.version_forks_to_fork_states(state, forks));
592 continue 'FORK;
593 }
594 ResolverVersion::Unavailable(version, reason) => {
595 state.add_unavailable_version(version, reason);
596 continue;
597 }
598 };
599
600 if url.is_none() {
602 state.prefetcher.prefetch_batches(
603 next_package,
604 index,
605 &version,
606 term_intersection.unwrap_positive(),
607 state
608 .pubgrub
609 .partial_solution
610 .unchanging_term_for_package(next_id),
611 &state.python_requirement,
612 &self.selector,
613 &state.env,
614 )?;
615 }
616
617 version
618 };
619
620 state.prefetcher.version_tried(next_package, &version);
621
622 self.on_progress(next_package, &version);
623
624 if !state
625 .added_dependencies
626 .entry(next_id)
627 .or_default()
628 .insert(version.clone())
629 {
630 state
633 .pubgrub
634 .partial_solution
635 .add_decision(next_id, version);
636 continue;
637 }
638
639 let forked_deps = self.get_dependencies_forking(
641 next_id,
642 next_package,
643 &version,
644 &state.pins,
645 &state.fork_urls,
646 &state.env,
647 &state.python_requirement,
648 &state.pubgrub,
649 )?;
650
651 match forked_deps {
652 ForkedDependencies::Unavailable(reason) => {
653 state
656 .pubgrub
657 .add_incompatibility(Incompatibility::custom_version(
658 next_id,
659 version.clone(),
660 UnavailableReason::Version(reason),
661 ));
662 }
663 ForkedDependencies::Unforked(dependencies) => {
664 state
666 .visit_package_version_dependencies(
667 next_id,
668 &version,
669 &self.urls,
670 &self.indexes,
671 &dependencies,
672 &self.git,
673 &self.workspace_members,
674 self.selector.resolution_strategy(),
675 )
676 .map_err(|err| {
677 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
678 })?;
679
680 self.visit_dependencies(&dependencies, &state, request_sink)
682 .map_err(|err| {
683 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
684 })?;
685
686 state.add_package_version_dependencies(next_id, &version, dependencies);
688 }
689 ForkedDependencies::Forked {
690 mut forks,
691 diverging_packages,
692 } => {
693 debug!(
694 "Pre-fork {} took {:.3}s",
695 state.env,
696 start.elapsed().as_secs_f32()
697 );
698
699 match (self.options.fork_strategy, self.options.resolution_mode) {
701 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
702 forks.sort_by(|a, b| {
706 a.cmp_requires_python(b)
707 .reverse()
708 .then_with(|| a.cmp_upper_bounds(b))
709 });
710 }
711 (ForkStrategy::RequiresPython, _) => {
712 forks.sort_by(|a, b| {
716 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
717 });
718 }
719 }
720
721 for new_fork_state in self.forks_to_fork_states(
722 state,
723 &version,
724 forks,
725 request_sink,
726 &diverging_packages,
727 ) {
728 forked_states.push(new_fork_state?);
729 }
730 continue 'FORK;
731 }
732 }
733 }
734 }
735 if resolutions.len() > 1 {
736 info!(
737 "Solved your requirements for {} environments",
738 resolutions.len()
739 );
740 }
741 if tracing::enabled!(Level::DEBUG) {
742 for resolution in &resolutions {
743 if let Some(env) = resolution.env.end_user_fork_display() {
744 let packages: FxHashSet<_> = resolution
745 .nodes
746 .keys()
747 .map(|package| &package.name)
748 .collect();
749 debug!(
750 "Distinct solution for {env} with {} package(s)",
751 packages.len()
752 );
753 }
754 }
755 }
756 for resolution in &resolutions {
757 Self::trace_resolution(resolution);
758 }
759 ResolverOutput::from_state(
760 &resolutions,
761 &self.requirements,
762 &self.constraints,
763 &self.overrides,
764 &self.preferences,
765 &self.index,
766 &self.git,
767 &self.python_requirement,
768 &self.conflicts,
769 self.selector.resolution_strategy(),
770 self.options.clone(),
771 )
772 }
773
774 fn reprioritize_conflicts(state: &mut ForkState) {
778 for package in state.conflict_tracker.prioritize.drain(..) {
779 let changed = state
780 .priorities
781 .mark_conflict_early(&state.pubgrub.package_store[package]);
782 if changed {
783 debug!(
784 "Package {} has too many conflicts (affected), prioritizing",
785 &state.pubgrub.package_store[package]
786 );
787 } else {
788 debug!(
789 "Package {} has too many conflicts (affected), already {:?}",
790 state.pubgrub.package_store[package],
791 state.priorities.get(&state.pubgrub.package_store[package])
792 );
793 }
794 }
795
796 for package in state.conflict_tracker.deprioritize.drain(..) {
797 let changed = state
798 .priorities
799 .mark_conflict_late(&state.pubgrub.package_store[package]);
800 if changed {
801 debug!(
802 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
803 state.pubgrub.package_store[package],
804 );
805 let backtrack_level = state.pubgrub.backtrack_package(package);
806 if let Some(backtrack_level) = backtrack_level {
807 debug!("Backtracked {backtrack_level} decisions");
808 } else {
809 debug!(
810 "Package {} is not decided, cannot backtrack",
811 state.pubgrub.package_store[package]
812 );
813 }
814 } else {
815 debug!(
816 "Package {} has too many conflicts (culprit), already {:?}",
817 state.pubgrub.package_store[package],
818 state.priorities.get(&state.pubgrub.package_store[package])
819 );
820 }
821 }
822 }
823
824 fn trace_resolution(combined: &Resolution) {
830 if !tracing::enabled!(Level::TRACE) {
831 return;
832 }
833 trace!("Resolution: {:?}", combined.env);
834 for edge in &combined.edges {
835 trace!(
836 "Resolution edge: {} -> {}",
837 edge.from
838 .as_ref()
839 .map(PackageName::as_str)
840 .unwrap_or("ROOT"),
841 edge.to,
842 );
843 let mut msg = String::new();
846 write!(msg, "{}", edge.from_version).unwrap();
847 if let Some(ref extra) = edge.from_extra {
848 write!(msg, " (extra: {extra})").unwrap();
849 }
850 if let Some(ref dev) = edge.from_group {
851 write!(msg, " (group: {dev})").unwrap();
852 }
853
854 write!(msg, " -> ").unwrap();
855
856 write!(msg, "{}", edge.to_version).unwrap();
857 if let Some(ref extra) = edge.to_extra {
858 write!(msg, " (extra: {extra})").unwrap();
859 }
860 if let Some(ref dev) = edge.to_group {
861 write!(msg, " (group: {dev})").unwrap();
862 }
863 if let Some(marker) = edge.marker.contents() {
864 write!(msg, " ; {marker}").unwrap();
865 }
866 trace!("Resolution edge: {msg}");
867 }
868 }
869
870 fn forks_to_fork_states<'a>(
872 &'a self,
873 current_state: ForkState,
874 version: &'a Version,
875 forks: Vec<Fork>,
876 request_sink: &'a Sender<Request>,
877 diverging_packages: &'a [PackageName],
878 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
879 debug!(
880 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
881 current_state.pubgrub.package_store[current_state.next],
882 version,
883 diverging_packages
884 .iter()
885 .map(ToString::to_string)
886 .join(", "),
887 forks.len(),
888 if forks.len() == 1 { "" } else { "s" }
889 );
890 assert!(forks.len() >= 2);
891 let package = current_state.next;
897 let mut cur_state = Some(current_state);
898 let forks_len = forks.len();
899 forks
900 .into_iter()
901 .enumerate()
902 .map(move |(i, fork)| {
903 let is_last = i == forks_len - 1;
904 let forked_state = cur_state.take().unwrap();
905 if !is_last {
906 cur_state = Some(forked_state.clone());
907 }
908
909 let env = fork.env.clone();
910 (fork, forked_state.with_env(env))
911 })
912 .map(move |(fork, mut forked_state)| {
913 forked_state
915 .visit_package_version_dependencies(
916 package,
917 version,
918 &self.urls,
919 &self.indexes,
920 &fork.dependencies,
921 &self.git,
922 &self.workspace_members,
923 self.selector.resolution_strategy(),
924 )
925 .map_err(|err| {
926 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
927 })?;
928
929 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
931 .map_err(|err| {
932 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
933 })?;
934
935 forked_state.add_package_version_dependencies(package, version, fork.dependencies);
937
938 Ok(forked_state)
939 })
940 }
941
942 #[expect(clippy::unused_self)]
944 fn version_forks_to_fork_states(
945 &self,
946 current_state: ForkState,
947 forks: Vec<VersionFork>,
948 ) -> impl Iterator<Item = ForkState> + '_ {
949 let mut cur_state = Some(current_state);
955 let forks_len = forks.len();
956 forks.into_iter().enumerate().map(move |(i, fork)| {
957 let is_last = i == forks_len - 1;
958 let mut forked_state = cur_state.take().unwrap();
959 if !is_last {
960 cur_state = Some(forked_state.clone());
961 }
962 forked_state.initial_id = Some(fork.id);
963 forked_state.initial_version = fork.version;
964 forked_state.with_env(fork.env)
965 })
966 }
967
968 fn visit_dependencies(
970 &self,
971 dependencies: &[PubGrubDependency],
972 state: &ForkState,
973 request_sink: &Sender<Request>,
974 ) -> Result<(), ResolveError> {
975 for dependency in dependencies {
976 let PubGrubDependency {
977 package,
978 version: _,
979 parent: _,
980 source: _,
981 } = dependency;
982 let url = package.name().and_then(|name| state.fork_urls.get(name));
983 let index = package.name().and_then(|name| state.fork_indexes.get(name));
984 self.visit_package(package, url, index, request_sink)?;
985 }
986 Ok(())
987 }
988
989 fn visit_package(
992 &self,
993 package: &PubGrubPackage,
994 url: Option<&VerbatimParsedUrl>,
995 index: Option<&IndexMetadata>,
996 request_sink: &Sender<Request>,
997 ) -> Result<(), ResolveError> {
998 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1000 return Ok(());
1001 }
1002
1003 self.request_package(package, url, index, request_sink)
1004 }
1005
1006 fn request_package(
1007 &self,
1008 package: &PubGrubPackage,
1009 url: Option<&VerbatimParsedUrl>,
1010 index: Option<&IndexMetadata>,
1011 request_sink: &Sender<Request>,
1012 ) -> Result<(), ResolveError> {
1013 let Some(name) = package.name_no_root() else {
1015 return Ok(());
1016 };
1017
1018 if let Some(url) = url {
1019 if !self.hasher.allows_url(&url.verbatim) {
1021 return Err(ResolveError::UnhashedPackage(name.clone()));
1022 }
1023
1024 let dist = Dist::from_url(name.clone(), url.clone())?;
1026 if self.index.distributions().register(dist.distribution_id()) {
1027 request_sink.blocking_send(Request::Dist(dist))?;
1028 }
1029 } else if let Some(index) = index {
1030 if self
1032 .index
1033 .explicit()
1034 .register((name.clone(), index.url().clone()))
1035 {
1036 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1037 }
1038 } else {
1039 if self.index.implicit().register(name.clone()) {
1041 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1042 }
1043 }
1044 Ok(())
1045 }
1046
1047 fn pre_visit<'data>(
1050 packages: impl Iterator<
1051 Item = (
1052 Id<PubGrubPackage>,
1053 &'data PubGrubPackage,
1054 &'data Range<Version>,
1055 ),
1056 >,
1057 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1058 urls: &Urls,
1059 indexes: &Indexes,
1060 python_requirement: &PythonRequirement,
1061 request_sink: &Sender<Request>,
1062 ) -> Result<(), ResolveError> {
1063 for (id, package, range) in packages {
1066 let PubGrubPackageInner::Package {
1067 name,
1068 extra: None,
1069 group: None,
1070 marker: MarkerTree::TRUE,
1071 } = &**package
1072 else {
1073 continue;
1074 };
1075 if urls.any_url(name) {
1078 continue;
1079 }
1080 if indexes.contains_key(name) {
1082 continue;
1083 }
1084 if pre_visited.get(&id) == Some(range) {
1087 continue;
1088 }
1089 pre_visited.insert(id, range.clone());
1090 request_sink.blocking_send(Request::Prefetch(
1091 name.clone(),
1092 range.clone(),
1093 python_requirement.clone(),
1094 ))?;
1095 }
1096 Ok(())
1097 }
1098
1099 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1107 fn choose_version(
1108 &self,
1109 package: &PubGrubPackage,
1110 id: Id<PubGrubPackage>,
1111 index: Option<&IndexUrl>,
1112 range: &Range<Version>,
1113 pins: &mut FilePins,
1114 preferences: &Preferences,
1115 fork_urls: &ForkUrls,
1116 env: &ResolverEnvironment,
1117 python_requirement: &PythonRequirement,
1118 pubgrub: &State<UvDependencyProvider>,
1119 visited: &mut FxHashSet<PackageName>,
1120 request_sink: &Sender<Request>,
1121 ) -> Result<Option<ResolverVersion>, ResolveError> {
1122 match &**package {
1123 PubGrubPackageInner::Root(_) => {
1124 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1125 }
1126
1127 PubGrubPackageInner::Python(_) => {
1128 Ok(None)
1131 }
1132
1133 PubGrubPackageInner::System(_) => {
1134 let Some(version) = range.as_singleton() else {
1137 return Ok(None);
1138 };
1139 Ok(Some(ResolverVersion::Unforked(version.clone())))
1140 }
1141
1142 PubGrubPackageInner::Marker { name, .. }
1143 | PubGrubPackageInner::Extra { name, .. }
1144 | PubGrubPackageInner::Group { name, .. }
1145 | PubGrubPackageInner::Package { name, .. } => {
1146 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1147 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1148 } else {
1149 self.choose_version_registry(
1150 package,
1151 id,
1152 name,
1153 index,
1154 range,
1155 preferences,
1156 env,
1157 python_requirement,
1158 pubgrub,
1159 pins,
1160 visited,
1161 request_sink,
1162 )
1163 }
1164 }
1165 }
1166 }
1167
1168 fn choose_version_url(
1171 &self,
1172 id: Id<PubGrubPackage>,
1173 name: &PackageName,
1174 range: &Range<Version>,
1175 url: &VerbatimParsedUrl,
1176 env: &ResolverEnvironment,
1177 python_requirement: &PythonRequirement,
1178 pubgrub: &State<UvDependencyProvider>,
1179 ) -> Result<Option<ResolverVersion>, ResolveError> {
1180 debug!(
1181 "Searching for a compatible version of {name} @ {} ({range})",
1182 url.verbatim
1183 );
1184
1185 let dist = Dist::from_url(name.clone(), url.clone())?;
1186 let distribution_id = dist.distribution_id();
1187 let response = self
1188 .index
1189 .distributions()
1190 .wait_blocking(&distribution_id)
1191 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1192
1193 let metadata = match &*response {
1195 MetadataResponse::Found(archive) => &archive.metadata,
1196 MetadataResponse::Unavailable(reason) => {
1197 self.unavailable_packages
1198 .pin()
1199 .insert(name.clone(), reason.into());
1200 return Ok(None);
1201 }
1202 MetadataResponse::Error(dist, err) => {
1205 return Err(ResolveError::Dist(
1206 DistErrorKind::from_requested_dist(dist, &**err),
1207 dist.clone(),
1208 DerivationChain::default(),
1209 err.clone(),
1210 ));
1211 }
1212 };
1213
1214 let version = &metadata.version;
1215
1216 if !range.contains(version) {
1218 return Ok(None);
1219 }
1220
1221 if let Dist::Built(dist) = &dist {
1224 let filename = match &dist {
1225 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1226 BuiltDist::DirectUrl(dist) => &dist.filename,
1227 BuiltDist::GitPath(dist) => &dist.filename,
1228 BuiltDist::Path(dist) => &dist.filename,
1229 };
1230
1231 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1234 {
1235 let wheel_marker = implied_markers(filename);
1236 for environment_marker in self.options.artifact_environments.iter().copied() {
1239 if env.included_by_marker(environment_marker)
1241 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1242 {
1243 if wheel_marker.is_disjoint(environment_marker) {
1245 return Ok(Some(ResolverVersion::Unavailable(
1246 version.clone(),
1247 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1248 IncompatibleWheel::MissingPlatform(environment_marker),
1249 )),
1250 )));
1251 }
1252 }
1253 }
1254 }
1255
1256 if !python_requirement.target().matches_wheel_tag(filename) {
1258 return Ok(Some(ResolverVersion::Unavailable(
1259 filename.version.clone(),
1260 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1261 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1262 )),
1263 )));
1264 }
1265 }
1266
1267 if let Some(requires_python) = metadata.requires_python.as_ref() {
1269 if !python_requirement.target().is_contained_by(requires_python) {
1270 let kind = if python_requirement.installed() == python_requirement.target() {
1271 PythonRequirementKind::Installed
1272 } else {
1273 PythonRequirementKind::Target
1274 };
1275 return Ok(Some(ResolverVersion::Unavailable(
1276 version.clone(),
1277 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1278 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1279 )),
1280 )));
1281 }
1282 }
1283
1284 Ok(Some(ResolverVersion::Unforked(version.clone())))
1285 }
1286
1287 fn choose_version_registry(
1290 &self,
1291 package: &PubGrubPackage,
1292 id: Id<PubGrubPackage>,
1293 name: &PackageName,
1294 index: Option<&IndexUrl>,
1295 range: &Range<Version>,
1296 preferences: &Preferences,
1297 env: &ResolverEnvironment,
1298 python_requirement: &PythonRequirement,
1299 pubgrub: &State<UvDependencyProvider>,
1300 pins: &mut FilePins,
1301 visited: &mut FxHashSet<PackageName>,
1302 request_sink: &Sender<Request>,
1303 ) -> Result<Option<ResolverVersion>, ResolveError> {
1304 let versions_response = if let Some(index) = index {
1306 self.index
1307 .explicit()
1308 .wait_blocking(&(name.clone(), index.clone()))
1309 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1310 } else {
1311 self.index
1312 .implicit()
1313 .wait_blocking(name)
1314 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1315 };
1316 visited.insert(name.clone());
1317
1318 let version_maps = match *versions_response {
1319 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1320 VersionsResponse::NoIndex => {
1321 self.unavailable_packages
1322 .pin()
1323 .insert(name.clone(), UnavailablePackage::NoIndex);
1324 &[]
1325 }
1326 VersionsResponse::Offline => {
1327 self.unavailable_packages
1328 .pin()
1329 .insert(name.clone(), UnavailablePackage::Offline);
1330 &[]
1331 }
1332 VersionsResponse::NotFound => {
1333 self.unavailable_packages
1334 .pin()
1335 .insert(name.clone(), UnavailablePackage::NotFound);
1336 &[]
1337 }
1338 };
1339
1340 debug!("Searching for a compatible version of {package} ({range})");
1341
1342 let Some(candidate) = self.selector.select(
1344 name,
1345 range,
1346 version_maps,
1347 preferences,
1348 &self.installed_packages,
1349 &self.exclusions,
1350 index,
1351 env,
1352 self.tags.as_ref(),
1353 ) else {
1354 return Ok(None);
1356 };
1357
1358 let dist = match candidate.dist() {
1359 CandidateDist::Compatible(dist) => dist,
1360 CandidateDist::Incompatible {
1361 incompatible_dist: incompatibility,
1362 prioritized_dist: _,
1363 } => {
1364 return Ok(Some(ResolverVersion::Unavailable(
1366 candidate.version().clone(),
1367 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1370 )));
1371 }
1372 };
1373
1374 if let Some((requires_python, incompatibility)) =
1376 Self::check_requires_python(dist, python_requirement)
1377 {
1378 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1379 if env.marker_environment().is_none() {
1380 let forks = fork_version_by_python_requirement(
1381 requires_python,
1382 python_requirement,
1383 env,
1384 );
1385 if !forks.is_empty() {
1386 debug!(
1387 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1388 python_requirement.target(),
1389 requires_python,
1390 name,
1391 candidate.version(),
1392 forks
1393 .iter()
1394 .map(ToString::to_string)
1395 .collect::<Vec<_>>()
1396 .join(", ")
1397 );
1398 let forks = forks
1399 .into_iter()
1400 .map(|env| VersionFork {
1401 env,
1402 id,
1403 version: None,
1404 })
1405 .collect();
1406 return Ok(Some(ResolverVersion::Forked(forks)));
1407 }
1408 }
1409 }
1410
1411 return Ok(Some(ResolverVersion::Unavailable(
1412 candidate.version().clone(),
1413 UnavailableVersion::IncompatibleDist(incompatibility),
1414 )));
1415 }
1416
1417 if let Some(forked) = self.fork_version_registry(
1419 &candidate,
1420 dist,
1421 version_maps,
1422 package,
1423 id,
1424 name,
1425 index,
1426 range,
1427 preferences,
1428 env,
1429 pubgrub,
1430 pins,
1431 request_sink,
1432 )? {
1433 return Ok(Some(forked));
1434 }
1435
1436 let filename = match dist.for_installation() {
1437 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1438 .filename()
1439 .unwrap_or(Cow::Borrowed("unknown filename")),
1440 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1441 .filename()
1442 .unwrap_or(Cow::Borrowed("unknown filename")),
1443 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1444 };
1445
1446 debug!(
1447 "Selecting: {}=={} [{}] ({})",
1448 name,
1449 candidate.version(),
1450 candidate.choice_kind(),
1451 filename,
1452 );
1453 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1454
1455 let version = candidate.version().clone();
1456 Ok(Some(ResolverVersion::Unforked(version)))
1457 }
1458
1459 fn fork_version_registry(
1472 &self,
1473 candidate: &Candidate,
1474 dist: &CompatibleDist,
1475 version_maps: &[VersionMap],
1476 package: &PubGrubPackage,
1477 id: Id<PubGrubPackage>,
1478 name: &PackageName,
1479 index: Option<&IndexUrl>,
1480 range: &Range<Version>,
1481 preferences: &Preferences,
1482 env: &ResolverEnvironment,
1483 pubgrub: &State<UvDependencyProvider>,
1484 pins: &mut FilePins,
1485 request_sink: &Sender<Request>,
1486 ) -> Result<Option<ResolverVersion>, ResolveError> {
1487 if env.marker_environment().is_some() {
1489 return Ok(None);
1490 }
1491
1492 if dist.implied_markers().is_true() {
1495 return Ok(None);
1496 }
1497
1498 for marker in self.options.artifact_environments.iter().copied() {
1501 if env.included_by_marker(marker) {
1503 if dist.implied_markers().is_disjoint(marker)
1505 && !find_environments(id, pubgrub).is_disjoint(marker)
1506 {
1507 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1509 return Ok(Some(ResolverVersion::Unavailable(
1510 candidate.version().clone(),
1511 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1512 IncompatibleWheel::MissingPlatform(marker),
1513 )),
1514 )));
1515 };
1516
1517 debug!(
1518 "Forking on required platform `{}` for {}=={} ({})",
1519 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1520 name,
1521 candidate.version(),
1522 [&left, &right]
1523 .iter()
1524 .map(ToString::to_string)
1525 .collect::<Vec<_>>()
1526 .join(", ")
1527 );
1528 let forks = vec![
1529 VersionFork {
1530 env: left,
1531 id,
1532 version: None,
1533 },
1534 VersionFork {
1535 env: right,
1536 id,
1537 version: None,
1538 },
1539 ];
1540 return Ok(Some(ResolverVersion::Forked(forks)));
1541 }
1542 }
1543 }
1544
1545 if !candidate.version().is_local() {
1547 return Ok(None);
1548 }
1549
1550 debug!(
1551 "Looking at local version: {}=={}",
1552 name,
1553 candidate.version()
1554 );
1555
1556 let range = range.clone().intersection(&Range::singleton(
1558 candidate.version().clone().without_local(),
1559 ));
1560
1561 let Some(base_candidate) = self.selector.select(
1562 name,
1563 &range,
1564 version_maps,
1565 preferences,
1566 &self.installed_packages,
1567 &self.exclusions,
1568 index,
1569 env,
1570 self.tags.as_ref(),
1571 ) else {
1572 return Ok(None);
1573 };
1574 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1575 return Ok(None);
1576 };
1577
1578 let mut remainder = {
1580 let mut remainder = base_dist.implied_markers();
1581 remainder.and(dist.implied_markers().negate());
1582 remainder
1583 };
1584 if remainder.is_false() {
1585 return Ok(None);
1586 }
1587
1588 if !env.included_by_marker(remainder) {
1592 return Ok(None);
1593 }
1594
1595 if !env.included_by_marker(dist.implied_markers()) {
1598 let filename = match dist.for_installation() {
1599 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1600 .filename()
1601 .unwrap_or(Cow::Borrowed("unknown filename")),
1602 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1603 .filename()
1604 .unwrap_or(Cow::Borrowed("unknown filename")),
1605 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1606 };
1607
1608 debug!(
1609 "Preferring non-local candidate: {}=={} [{}] ({})",
1610 name,
1611 base_candidate.version(),
1612 base_candidate.choice_kind(),
1613 filename,
1614 );
1615 self.visit_candidate(
1616 &base_candidate,
1617 base_dist,
1618 package,
1619 name,
1620 pins,
1621 request_sink,
1622 )?;
1623
1624 return Ok(Some(ResolverVersion::Unforked(
1625 base_candidate.version().clone(),
1626 )));
1627 }
1628
1629 for value in [
1638 arcstr::literal!("darwin"),
1639 arcstr::literal!("linux"),
1640 arcstr::literal!("win32"),
1641 ] {
1642 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1643 key: MarkerValueString::SysPlatform,
1644 operator: MarkerOperator::Equal,
1645 value,
1646 });
1647 if dist.implied_markers().is_disjoint(sys_platform)
1648 && !remainder.is_disjoint(sys_platform)
1649 {
1650 remainder.or(sys_platform);
1651 }
1652 }
1653
1654 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1656 return Ok(None);
1657 };
1658
1659 debug!(
1660 "Forking platform for {}=={} ({})",
1661 name,
1662 candidate.version(),
1663 [&base_env, &local_env]
1664 .iter()
1665 .map(ToString::to_string)
1666 .collect::<Vec<_>>()
1667 .join(", ")
1668 );
1669 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1670 self.visit_candidate(
1671 &base_candidate,
1672 base_dist,
1673 package,
1674 name,
1675 pins,
1676 request_sink,
1677 )?;
1678
1679 let forks = vec![
1680 VersionFork {
1681 env: base_env.clone(),
1682 id,
1683 version: Some(base_candidate.version().clone()),
1684 },
1685 VersionFork {
1686 env: local_env.clone(),
1687 id,
1688 version: Some(candidate.version().clone()),
1689 },
1690 ];
1691 Ok(Some(ResolverVersion::Forked(forks)))
1692 }
1693
1694 fn visit_candidate(
1696 &self,
1697 candidate: &Candidate,
1698 dist: &CompatibleDist,
1699 package: &PubGrubPackage,
1700 name: &PackageName,
1701 pins: &mut FilePins,
1702 request_sink: &Sender<Request>,
1703 ) -> Result<(), ResolveError> {
1704 pins.insert(candidate, dist);
1707
1708 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1710 if self.dependency_mode.is_transitive() {
1711 let dist = dist.for_resolution();
1712 if self.index.distributions().register(dist.distribution_id()) {
1713 if name != dist.name() {
1714 return Err(ResolveError::MismatchedPackageName {
1715 request: "distribution",
1716 expected: name.clone(),
1717 actual: dist.name().clone(),
1718 });
1719 }
1720 if !self
1722 .hasher
1723 .allows_package(candidate.name(), candidate.version())
1724 {
1725 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1726 }
1727
1728 let request = Request::from(dist);
1729 request_sink.blocking_send(request)?;
1730 }
1731 }
1732 }
1733
1734 Ok(())
1735 }
1736
1737 fn check_requires_python<'dist>(
1740 dist: &'dist CompatibleDist,
1741 python_requirement: &PythonRequirement,
1742 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1743 let requires_python = dist.requires_python()?;
1744 if python_requirement.target().is_contained_by(requires_python) {
1745 None
1746 } else {
1747 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1748 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1749 requires_python.clone(),
1750 if python_requirement.installed() == python_requirement.target() {
1751 PythonRequirementKind::Installed
1752 } else {
1753 PythonRequirementKind::Target
1754 },
1755 ))
1756 } else {
1757 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1758 requires_python.clone(),
1759 if python_requirement.installed() == python_requirement.target() {
1760 PythonRequirementKind::Installed
1761 } else {
1762 PythonRequirementKind::Target
1763 },
1764 ))
1765 };
1766 Some((requires_python, incompatibility))
1767 }
1768 }
1769
1770 #[instrument(skip_all, fields(%package, %version))]
1772 fn get_dependencies_forking(
1773 &self,
1774 id: Id<PubGrubPackage>,
1775 package: &PubGrubPackage,
1776 version: &Version,
1777 pins: &FilePins,
1778 fork_urls: &ForkUrls,
1779 env: &ResolverEnvironment,
1780 python_requirement: &PythonRequirement,
1781 pubgrub: &State<UvDependencyProvider>,
1782 ) -> Result<ForkedDependencies, ResolveError> {
1783 let result = self.get_dependencies(
1784 id,
1785 package,
1786 version,
1787 pins,
1788 fork_urls,
1789 env,
1790 python_requirement,
1791 pubgrub,
1792 );
1793 if env.marker_environment().is_some() {
1794 result.map(|deps| match deps {
1795 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1796 ForkedDependencies::Unforked(deps)
1797 }
1798 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1799 })
1800 } else {
1801 Ok(result?.fork(env, python_requirement, &self.conflicts))
1802 }
1803 }
1804
1805 #[instrument(skip_all, fields(%package, %version))]
1807 fn get_dependencies(
1808 &self,
1809 id: Id<PubGrubPackage>,
1810 package: &PubGrubPackage,
1811 version: &Version,
1812 pins: &FilePins,
1813 fork_urls: &ForkUrls,
1814 env: &ResolverEnvironment,
1815 python_requirement: &PythonRequirement,
1816 pubgrub: &State<UvDependencyProvider>,
1817 ) -> Result<Dependencies, ResolveError> {
1818 let dependencies = match &**package {
1819 PubGrubPackageInner::Root(_) => {
1820 let no_dev_deps = BTreeMap::default();
1821 let requirements = self.flatten_requirements(
1822 &self.requirements,
1823 &no_dev_deps,
1824 None,
1825 None,
1826 None,
1827 None,
1828 env,
1829 python_requirement,
1830 );
1831
1832 requirements
1833 .flat_map(move |requirement| {
1834 PubGrubDependency::from_requirement(
1835 &self.conflicts,
1836 requirement,
1837 None,
1838 Some(package),
1839 )
1840 })
1841 .collect()
1842 }
1843
1844 PubGrubPackageInner::Package {
1845 name,
1846 extra,
1847 group,
1848 marker: _,
1849 } => {
1850 if self.dependency_mode.is_direct() {
1852 return Ok(Dependencies::Unforkable(Vec::default()));
1853 }
1854
1855 let owned_id;
1857 let distribution_id = if let Some((_, metadata_id)) =
1858 pins.dist_and_id(name, version)
1859 {
1860 metadata_id
1861 } else if let Some(url) = fork_urls.get(name) {
1862 let dist = Dist::from_url(name.clone(), url.clone())?;
1863 owned_id = dist.distribution_id();
1864 &owned_id
1865 } else {
1866 debug_assert!(
1867 false,
1868 "Dependencies were requested for a package without a pinned distribution"
1869 );
1870 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1871 };
1872
1873 if self.dependency_mode.is_transitive()
1875 && self.unavailable_packages.pin().contains_key(name)
1876 && self.installed_packages.get_packages(name).is_empty()
1877 {
1878 debug_assert!(
1879 false,
1880 "Dependencies were requested for a package that is not available"
1881 );
1882 return Err(ResolveError::PackageUnavailable(name.clone()));
1883 }
1884
1885 let response = self
1887 .index
1888 .distributions()
1889 .wait_blocking(distribution_id)
1890 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1891
1892 let metadata = match &*response {
1893 MetadataResponse::Found(archive) => &archive.metadata,
1894 MetadataResponse::Unavailable(reason) => {
1895 let unavailable_version = UnavailableVersion::from(reason);
1896 let message = unavailable_version.singular_message();
1897 if let Some(err) = reason.source() {
1898 warn!("{name} {message}: {err}");
1900 } else {
1901 warn!("{name} {message}");
1902 }
1903 let incomplete_packages = self.incomplete_packages.pin();
1904 let versions = incomplete_packages.get_or_insert(
1905 name.clone(),
1906 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1907 );
1908 versions.pin().insert(version.clone(), reason.clone());
1909 return Ok(Dependencies::Unavailable(unavailable_version));
1910 }
1911 MetadataResponse::Error(dist, err) => {
1912 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1913 .unwrap_or_default();
1914 return Err(ResolveError::Dist(
1915 DistErrorKind::from_requested_dist(dist, &**err),
1916 dist.clone(),
1917 chain,
1918 err.clone(),
1919 ));
1920 }
1921 };
1922
1923 if let Some(requires_python) = &metadata.requires_python {
1926 if !python_requirement.target().is_contained_by(requires_python) {
1927 return Ok(Dependencies::Unavailable(
1928 UnavailableVersion::RequiresPython(requires_python.clone()),
1929 ));
1930 }
1931 }
1932
1933 let system_dependencies = self
1935 .options
1936 .torch_backend
1937 .as_ref()
1938 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1939 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1940 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1941 .map(IndexUrl::url)
1942 .and_then(SystemDependency::from_index)
1943 .into_iter()
1944 .inspect(|system_dependency| {
1945 debug!(
1946 "Adding system dependency `{}` for `{package}@{version}`",
1947 system_dependency
1948 );
1949 })
1950 .map(PubGrubDependency::from);
1951
1952 let requirements = self.flatten_requirements(
1953 &metadata.requires_dist,
1954 &metadata.dependency_groups,
1955 extra.as_ref(),
1956 group.as_ref(),
1957 Some(name),
1958 Some(version),
1959 env,
1960 python_requirement,
1961 );
1962
1963 requirements
1964 .flat_map(|requirement| {
1965 PubGrubDependency::from_requirement(
1966 &self.conflicts,
1967 requirement,
1968 group.as_ref(),
1969 Some(package),
1970 )
1971 })
1972 .chain(system_dependencies)
1973 .collect()
1974 }
1975
1976 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1977
1978 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1979
1980 PubGrubPackageInner::Marker { name, marker } => {
1982 return Ok(Dependencies::Unforkable(
1983 [MarkerTree::TRUE, *marker]
1984 .into_iter()
1985 .map(move |marker| PubGrubDependency {
1986 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1987 name: name.clone(),
1988 extra: None,
1989 group: None,
1990 marker,
1991 }),
1992 version: Range::singleton(version.clone()),
1993 parent: None,
1994 source: DependencySource::Unspecified,
1995 })
1996 .collect(),
1997 ));
1998 }
1999
2000 PubGrubPackageInner::Extra {
2002 name,
2003 extra,
2004 marker,
2005 } => {
2006 return Ok(Dependencies::Unforkable(
2007 [MarkerTree::TRUE, *marker]
2008 .into_iter()
2009 .dedup()
2010 .flat_map(move |marker| {
2011 [None, Some(extra)]
2012 .into_iter()
2013 .map(move |extra| PubGrubDependency {
2014 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2015 name: name.clone(),
2016 extra: extra.cloned(),
2017 group: None,
2018 marker,
2019 }),
2020 version: Range::singleton(version.clone()),
2021 parent: None,
2022 source: DependencySource::Unspecified,
2023 })
2024 })
2025 .collect(),
2026 ));
2027 }
2028
2029 PubGrubPackageInner::Group {
2031 name,
2032 group,
2033 marker,
2034 } => {
2035 return Ok(Dependencies::Unforkable(
2036 [MarkerTree::TRUE, *marker]
2037 .into_iter()
2038 .dedup()
2039 .map(|marker| PubGrubDependency {
2040 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2041 name: name.clone(),
2042 extra: None,
2043 group: Some(group.clone()),
2044 marker,
2045 }),
2046 version: Range::singleton(version.clone()),
2047 parent: None,
2048 source: DependencySource::Unspecified,
2049 })
2050 .collect(),
2051 ));
2052 }
2053 };
2054 Ok(Dependencies::Available(dependencies))
2055 }
2056
2057 fn flatten_requirements<'a>(
2061 &'a self,
2062 dependencies: &'a [Requirement],
2063 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2064 extra: Option<&'a ExtraName>,
2065 dev: Option<&'a GroupName>,
2066 name: Option<&'a PackageName>,
2067 version: Option<&'a Version>,
2068 env: &'a ResolverEnvironment,
2069 python_requirement: &'a PythonRequirement,
2070 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2071 let python_marker = python_requirement.to_marker_tree();
2072
2073 if let Some(dev) = dev {
2074 Either::Left(Either::Left(self.requirements_for_extra(
2077 dev_dependencies.get(dev).into_iter().flatten(),
2078 extra,
2079 None,
2080 name.zip(version),
2081 env,
2082 python_marker,
2083 python_requirement,
2084 )))
2085 } else if !dependencies
2086 .iter()
2087 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2088 {
2089 Either::Left(Either::Right(self.requirements_for_extra(
2091 dependencies.iter(),
2092 extra,
2093 name.zip(version),
2094 name.zip(version),
2095 env,
2096 python_marker,
2097 python_requirement,
2098 )))
2099 } else {
2100 let mut requirements = self
2101 .requirements_for_extra(
2102 dependencies.iter(),
2103 extra,
2104 name.zip(version),
2105 name.zip(version),
2106 env,
2107 python_marker,
2108 python_requirement,
2109 )
2110 .collect::<Vec<_>>();
2111
2112 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2115 let mut queue: VecDeque<_> = requirements
2116 .iter()
2117 .filter(|req| name == Some(&req.name))
2118 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2119 .collect();
2120 while let Some((extra, marker)) = queue.pop_front() {
2121 if !seen.insert((extra.clone(), marker)) {
2122 continue;
2123 }
2124 for requirement in self.requirements_for_extra(
2125 dependencies,
2126 Some(&extra),
2127 name.zip(version),
2128 name.zip(version),
2129 env,
2130 python_marker,
2131 python_requirement,
2132 ) {
2133 let requirement = match requirement {
2134 Cow::Owned(mut requirement) => {
2135 requirement.marker.and(marker);
2136 requirement
2137 }
2138 Cow::Borrowed(requirement) => {
2139 let mut marker = marker;
2140 marker.and(requirement.marker);
2141 Requirement {
2142 name: requirement.name.clone(),
2143 extras: requirement.extras.clone(),
2144 groups: requirement.groups.clone(),
2145 source: requirement.source.clone(),
2146 origin: requirement.origin.clone(),
2147 marker: marker.simplify_extras(slice::from_ref(&extra)),
2148 }
2149 }
2150 };
2151 if name == Some(&requirement.name) {
2152 queue.extend(
2154 requirement
2155 .extras
2156 .iter()
2157 .cloned()
2158 .map(|extra| (extra, requirement.marker)),
2159 );
2160 } else {
2161 requirements.push(Cow::Owned(requirement));
2163 }
2164 }
2165 }
2166
2167 let mut self_constraints = vec![];
2171 for req in &requirements {
2172 if name == Some(&req.name) && !req.source.is_empty() {
2173 self_constraints.push(Requirement {
2174 name: req.name.clone(),
2175 extras: Box::new([]),
2176 groups: req.groups.clone(),
2177 source: req.source.clone(),
2178 origin: req.origin.clone(),
2179 marker: req.marker,
2180 });
2181 }
2182 }
2183
2184 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2186 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2187
2188 Either::Right(requirements.into_iter())
2189 }
2190 }
2191
2192 fn requirements_for_extra<'data, 'parameters>(
2195 &'data self,
2196 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2197 extra: Option<&'parameters ExtraName>,
2198 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2199 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2200 env: &'parameters ResolverEnvironment,
2201 python_marker: MarkerTree,
2202 python_requirement: &'parameters PythonRequirement,
2203 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2204 where
2205 'data: 'parameters,
2206 {
2207 self.overrides
2208 .apply_for_package(override_package, dependencies)
2209 .filter(move |requirement| {
2210 !self
2211 .excludes
2212 .contains_for_package(exclusion_package, &requirement.name)
2213 })
2214 .filter(move |requirement| {
2215 Self::is_requirement_applicable(
2216 requirement,
2217 extra,
2218 env,
2219 python_marker,
2220 python_requirement,
2221 )
2222 })
2223 .flat_map(move |requirement| {
2224 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2225 requirement,
2226 extra,
2227 env,
2228 python_marker,
2229 python_requirement,
2230 ))
2231 })
2232 }
2233
2234 fn is_requirement_applicable(
2237 requirement: &Requirement,
2238 extra: Option<&ExtraName>,
2239 env: &ResolverEnvironment,
2240 python_marker: MarkerTree,
2241 python_requirement: &PythonRequirement,
2242 ) -> bool {
2243 match extra {
2245 Some(source_extra) => {
2246 if requirement.evaluate_markers(env.marker_environment(), &[]) {
2248 return false;
2249 }
2250 if !requirement
2251 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2252 {
2253 return false;
2254 }
2255 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2256 {
2257 return false;
2258 }
2259 }
2260 None => {
2261 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2262 return false;
2263 }
2264 }
2265 }
2266
2267 if python_marker.is_disjoint(requirement.marker) {
2270 trace!(
2271 "Skipping {requirement} because of Requires-Python: {requires_python}",
2272 requires_python = python_requirement.target(),
2273 );
2274 return false;
2275 }
2276
2277 if !env.included_by_marker(requirement.marker) {
2280 trace!("Skipping {requirement} because of {env}");
2281 return false;
2282 }
2283
2284 true
2285 }
2286
2287 fn constraints_for_requirement<'data, 'parameters>(
2290 &'data self,
2291 requirement: Cow<'data, Requirement>,
2292 extra: Option<&'parameters ExtraName>,
2293 env: &'parameters ResolverEnvironment,
2294 python_marker: MarkerTree,
2295 python_requirement: &'parameters PythonRequirement,
2296 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2297 where
2298 'data: 'parameters,
2299 {
2300 self.constraints
2301 .get(&requirement.name)
2302 .into_iter()
2303 .flatten()
2304 .filter_map(move |constraint| {
2305 let constraint = if constraint.marker.is_true() {
2308 if requirement.marker.is_true() {
2312 Cow::Borrowed(constraint)
2313 } else {
2314 let mut marker = constraint.marker;
2315 marker.and(requirement.marker);
2316
2317 if marker.is_false() {
2318 trace!(
2319 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2320 constraint.marker.try_to_string().unwrap(),
2321 requirement.marker.try_to_string().unwrap(),
2322 );
2323 return None;
2324 }
2325
2326 Cow::Owned(Requirement {
2327 name: constraint.name.clone(),
2328 extras: constraint.extras.clone(),
2329 groups: constraint.groups.clone(),
2330 source: constraint.source.clone(),
2331 origin: constraint.origin.clone(),
2332 marker,
2333 })
2334 }
2335 } else {
2336 let requires_python = python_requirement.target();
2337
2338 let mut marker = constraint.marker;
2339 marker.and(requirement.marker);
2340
2341 if marker.is_false() {
2342 trace!(
2343 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2344 constraint.marker.try_to_string().unwrap(),
2345 requirement.marker.try_to_string().unwrap(),
2346 );
2347 return None;
2348 }
2349
2350 if python_marker.is_disjoint(marker) {
2354 trace!(
2355 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2356 );
2357 return None;
2358 }
2359
2360 if marker == constraint.marker {
2361 Cow::Borrowed(constraint)
2362 } else {
2363 Cow::Owned(Requirement {
2364 name: constraint.name.clone(),
2365 extras: constraint.extras.clone(),
2366 groups: constraint.groups.clone(),
2367 source: constraint.source.clone(),
2368 origin: constraint.origin.clone(),
2369 marker,
2370 })
2371 }
2372 };
2373
2374 if !env.included_by_marker(constraint.marker) {
2377 trace!("Skipping {constraint} because of {env}");
2378 return None;
2379 }
2380
2381 match extra {
2383 Some(source_extra) => {
2384 if !constraint
2385 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2386 {
2387 return None;
2388 }
2389 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2390 {
2391 return None;
2392 }
2393 }
2394 None => {
2395 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2396 return None;
2397 }
2398 }
2399 }
2400
2401 Some(constraint)
2402 })
2403 }
2404
2405 async fn fetch<Provider: ResolverProvider>(
2407 self: Arc<Self>,
2408 provider: Arc<Provider>,
2409 request_stream: Receiver<Request>,
2410 ) -> Result<(), ResolveError> {
2411 let mut response_stream = ReceiverStream::new(request_stream)
2412 .map(|request| self.process_request(request, &*provider).boxed_local())
2413 .buffer_unordered(usize::MAX);
2417
2418 while let Some(response) = response_stream.next().await {
2419 match response? {
2420 Some(Response::Package(name, index, version_map)) => {
2421 trace!("Received package metadata for: {name}");
2422 if let Some(index) = index {
2423 self.index
2424 .explicit()
2425 .done((name, index), Arc::new(version_map));
2426 } else {
2427 self.index.implicit().done(name, Arc::new(version_map));
2428 }
2429 }
2430 Some(Response::Installed { dist, metadata }) => {
2431 trace!("Received installed distribution metadata for: {dist}");
2432 self.index
2433 .distributions()
2434 .done(dist.distribution_id(), Arc::new(metadata));
2435 }
2436 Some(Response::Dist { dist, metadata }) => {
2437 let dist_kind = match dist {
2438 Dist::Built(_) => "built",
2439 Dist::Source(_) => "source",
2440 };
2441 trace!("Received {dist_kind} distribution metadata for: {dist}");
2442 if let MetadataResponse::Unavailable(reason) = &metadata {
2443 let message = UnavailableVersion::from(reason).singular_message();
2444 if let Some(err) = reason.source() {
2445 warn!("{dist} {message}: {err}");
2447 } else {
2448 warn!("{dist} {message}");
2449 }
2450 }
2451 self.index
2452 .distributions()
2453 .done(dist.distribution_id(), Arc::new(metadata));
2454 }
2455 None => {}
2456 }
2457 }
2458
2459 Ok::<(), ResolveError>(())
2460 }
2461
2462 #[instrument(skip_all, fields(%request))]
2463 async fn process_request<Provider: ResolverProvider>(
2464 &self,
2465 request: Request,
2466 provider: &Provider,
2467 ) -> Result<Option<Response>, ResolveError> {
2468 match request {
2469 Request::Package(package_name, index) => {
2471 let package_versions = provider
2472 .get_package_versions(&package_name, index.as_ref())
2473 .boxed_local()
2474 .await
2475 .map_err(ResolveError::Client)?;
2476
2477 Ok(Some(Response::Package(
2478 package_name,
2479 index.map(IndexMetadata::into_url),
2480 package_versions,
2481 )))
2482 }
2483
2484 Request::Dist(dist) => {
2486 if let Some(version) = dist.version() {
2487 if let Some(index) = dist.index() {
2488 let versions_response = self.index.implicit().get(dist.name());
2490 if let Some(VersionsResponse::Found(version_maps)) =
2491 versions_response.as_deref()
2492 {
2493 for version_map in version_maps {
2494 if version_map.index() == Some(index) {
2495 let Some(metadata) = version_map.get_metadata(version) else {
2496 continue;
2497 };
2498 debug!("Found registry-provided metadata for: {dist}");
2499 return Ok(Some(Response::Dist {
2500 dist,
2501 metadata: MetadataResponse::Found(
2502 ArchiveMetadata::from_metadata23(metadata),
2503 ),
2504 }));
2505 }
2506 }
2507 }
2508
2509 let versions_response = self
2511 .index
2512 .explicit()
2513 .get(&(dist.name().clone(), index.clone()));
2514 if let Some(VersionsResponse::Found(version_maps)) =
2515 versions_response.as_deref()
2516 {
2517 for version_map in version_maps {
2518 let Some(metadata) = version_map.get_metadata(version) else {
2519 continue;
2520 };
2521 debug!("Found registry-provided metadata for: {dist}");
2522 return Ok(Some(Response::Dist {
2523 dist,
2524 metadata: MetadataResponse::Found(
2525 ArchiveMetadata::from_metadata23(metadata),
2526 ),
2527 }));
2528 }
2529 }
2530 }
2531 }
2532
2533 let metadata = provider
2534 .get_or_build_wheel_metadata(&dist)
2535 .boxed_local()
2536 .await?;
2537
2538 if let MetadataResponse::Found(metadata) = &metadata {
2539 if &metadata.metadata.name != dist.name() {
2540 return Err(ResolveError::MismatchedPackageName {
2541 request: "distribution metadata",
2542 expected: dist.name().clone(),
2543 actual: metadata.metadata.name.clone(),
2544 });
2545 }
2546 }
2547
2548 Ok(Some(Response::Dist { dist, metadata }))
2549 }
2550
2551 Request::Installed(dist) => {
2552 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2553
2554 if let MetadataResponse::Found(metadata) = &metadata {
2555 if &metadata.metadata.name != dist.name() {
2556 return Err(ResolveError::MismatchedPackageName {
2557 request: "installed metadata",
2558 expected: dist.name().clone(),
2559 actual: metadata.metadata.name.clone(),
2560 });
2561 }
2562 }
2563
2564 Ok(Some(Response::Installed { dist, metadata }))
2565 }
2566
2567 Request::Prefetch(package_name, range, python_requirement) => {
2569 let versions_response = self
2571 .index
2572 .implicit()
2573 .wait(&package_name)
2574 .await
2575 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2576
2577 let version_map = match *versions_response {
2578 VersionsResponse::Found(ref version_map) => version_map,
2579 VersionsResponse::NoIndex => {
2581 self.unavailable_packages
2582 .pin()
2583 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2584
2585 return Ok(None);
2586 }
2587 VersionsResponse::Offline => {
2588 self.unavailable_packages
2589 .pin()
2590 .insert(package_name.clone(), UnavailablePackage::Offline);
2591
2592 return Ok(None);
2593 }
2594 VersionsResponse::NotFound => {
2595 self.unavailable_packages
2596 .pin()
2597 .insert(package_name.clone(), UnavailablePackage::NotFound);
2598
2599 return Ok(None);
2600 }
2601 };
2602
2603 let env = ResolverEnvironment::universal(vec![]);
2606
2607 let Some(candidate) = self.selector.select(
2610 &package_name,
2611 &range,
2612 version_map,
2613 &self.preferences,
2614 &self.installed_packages,
2615 &self.exclusions,
2616 None,
2617 &env,
2618 self.tags.as_ref(),
2619 ) else {
2620 return Ok(None);
2621 };
2622
2623 let Some(dist) = candidate.compatible() else {
2625 return Ok(None);
2626 };
2627
2628 for version_map in version_map {
2630 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2631 let dist = dist.for_resolution();
2632 if version_map.index() == dist.index() {
2633 debug!("Found registry-provided metadata for: {dist}");
2634
2635 let metadata =
2636 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2637
2638 let dist = dist.to_owned();
2639 if &package_name != dist.name() {
2640 return Err(ResolveError::MismatchedPackageName {
2641 request: "distribution",
2642 expected: package_name,
2643 actual: dist.name().clone(),
2644 });
2645 }
2646
2647 let response = match dist {
2648 ResolvedDist::Installable { dist, .. } => Response::Dist {
2649 dist: (*dist).clone(),
2650 metadata,
2651 },
2652 ResolvedDist::Installed { dist } => Response::Installed {
2653 dist: (*dist).clone(),
2654 metadata,
2655 },
2656 };
2657
2658 return Ok(Some(response));
2659 }
2660 }
2661 }
2662
2663 if dist.wheel().is_none() {
2667 if !self.selector.use_highest_version(&package_name, &env) {
2668 if let Some((lower, _)) = range.iter().next() {
2669 if lower == Bound::Unbounded {
2670 debug!(
2671 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2672 );
2673 return Ok(None);
2674 }
2675 }
2676 }
2677 }
2678
2679 let requires_python = match dist {
2681 CompatibleDist::InstalledDist(_) => None,
2682 CompatibleDist::SourceDist { sdist, .. }
2683 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2684 sdist.file.requires_python.as_ref()
2685 }
2686 CompatibleDist::CompatibleWheel { wheel, .. } => {
2687 wheel.file.requires_python.as_ref()
2688 }
2689 };
2690 if let Some(requires_python) = requires_python.as_ref() {
2691 if !python_requirement.target().is_contained_by(requires_python) {
2692 return Ok(None);
2693 }
2694 }
2695
2696 if !self
2698 .hasher
2699 .allows_package(candidate.name(), candidate.version())
2700 {
2701 return Ok(None);
2702 }
2703
2704 let dist = dist.for_resolution();
2706 if self.index.distributions().register(dist.distribution_id()) {
2707 let dist = dist.to_owned();
2708 if &package_name != dist.name() {
2709 return Err(ResolveError::MismatchedPackageName {
2710 request: "distribution",
2711 expected: package_name,
2712 actual: dist.name().clone(),
2713 });
2714 }
2715
2716 let response = match dist {
2717 ResolvedDist::Installable { dist, .. } => {
2718 let metadata = provider
2719 .get_or_build_wheel_metadata(&dist)
2720 .boxed_local()
2721 .await?;
2722
2723 Response::Dist {
2724 dist: (*dist).clone(),
2725 metadata,
2726 }
2727 }
2728 ResolvedDist::Installed { dist } => {
2729 let metadata =
2730 provider.get_installed_metadata(&dist).boxed_local().await?;
2731
2732 Response::Installed {
2733 dist: (*dist).clone(),
2734 metadata,
2735 }
2736 }
2737 };
2738
2739 Ok(Some(response))
2740 } else {
2741 Ok(None)
2742 }
2743 }
2744 }
2745 }
2746
2747 fn convert_no_solution_err(
2748 &self,
2749 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2750 fork_urls: ForkUrls,
2751 fork_indexes: ForkIndexes,
2752 env: ResolverEnvironment,
2753 current_environment: MarkerEnvironment,
2754 visited: &FxHashSet<PackageName>,
2755 ) -> ResolveError {
2756 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2757 err,
2758 ));
2759
2760 let mut unavailable_packages = FxHashMap::default();
2761 for package in derivation_tree_packages(&err) {
2762 if let PubGrubPackageInner::Package { name, .. } = &**package {
2763 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2764 unavailable_packages.insert(name.clone(), reason.clone());
2765 }
2766 }
2767 }
2768
2769 let mut incomplete_packages = FxHashMap::default();
2770 let incomplete_packages_cache = self.incomplete_packages.pin();
2771 for package in derivation_tree_packages(&err) {
2772 if let PubGrubPackageInner::Package { name, .. } = &**package
2773 && let Some(versions) = incomplete_packages_cache.get(name)
2774 {
2775 for (version, reason) in &versions.pin() {
2776 incomplete_packages
2777 .entry(name.clone())
2778 .or_insert_with(BTreeMap::default)
2779 .insert(version.clone(), reason.clone());
2780 }
2781 }
2782 }
2783
2784 let mut available_indexes = FxHashMap::default();
2785 let mut included_versions = FxHashMap::default();
2786 let mut available_versions = FxHashMap::default();
2787
2788 let available_version_cutoff: Option<jiff::Timestamp> =
2789 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2790 .ok()
2791 .and_then(|s| s.parse().ok());
2792
2793 for package in derivation_tree_packages(&err) {
2794 let Some(name) = package.name() else { continue };
2795 if !visited.contains(name) {
2796 continue;
2801 }
2802 let versions_response = if let Some(index) = fork_indexes.get(name) {
2803 self.index
2804 .explicit()
2805 .get(&(name.clone(), index.url().clone()))
2806 } else {
2807 self.index.implicit().get(name)
2808 };
2809 if let Some(response) = versions_response {
2810 if let VersionsResponse::Found(ref version_maps) = *response {
2811 for version_map in version_maps {
2813 let package_included_versions = included_versions
2814 .entry(name.clone())
2815 .or_insert_with(BTreeSet::new);
2816 let package_available_versions = available_versions
2817 .entry(name.clone())
2818 .or_insert_with(BTreeSet::new);
2819
2820 for (version, dists) in version_map.iter(&Ranges::full()) {
2821 let excluded_from_included = || {
2826 let Some(included_version_cutoff) =
2827 version_map.included_version_cutoff()
2828 else {
2829 return false;
2830 };
2831 let Some(prioritized_dist) = dists.prioritized_dist() else {
2832 return true;
2833 };
2834 prioritized_dist.files().all(|file| {
2835 file.upload_time_utc_ms.is_none_or(|upload_time| {
2836 upload_time >= included_version_cutoff.as_millisecond()
2837 })
2838 })
2839 };
2840
2841 if !excluded_from_included() {
2842 package_included_versions.insert(version.clone());
2843 }
2844
2845 let excluded_from_available = || {
2851 let Some(ref exclude_newer) = available_version_cutoff else {
2852 return false;
2853 };
2854 let Some(prioritized_dist) = dists.prioritized_dist() else {
2855 return false;
2856 };
2857 prioritized_dist.files().all(|file| {
2858 file.upload_time_utc_ms.is_some_and(|upload_time| {
2859 upload_time >= exclude_newer.as_millisecond()
2860 })
2861 })
2862 };
2863
2864 if !excluded_from_available() {
2865 package_available_versions.insert(version.clone());
2866 }
2867 }
2868 }
2869
2870 available_indexes
2872 .entry(name.clone())
2873 .or_insert(BTreeSet::new())
2874 .extend(
2875 version_maps
2876 .iter()
2877 .filter_map(|version_map| version_map.index().cloned()),
2878 );
2879 }
2880 }
2881 }
2882
2883 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2884 err,
2885 self.index.clone(),
2886 included_versions,
2887 available_versions,
2888 available_indexes,
2889 self.selector.clone(),
2890 self.python_requirement.clone(),
2891 self.locations.clone(),
2892 self.capabilities.clone(),
2893 unavailable_packages,
2894 incomplete_packages,
2895 fork_urls,
2896 fork_indexes,
2897 env,
2898 current_environment,
2899 self.tags.clone(),
2900 self.workspace_members.clone(),
2901 self.options.clone(),
2902 )))
2903 }
2904
2905 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2906 if let Some(reporter) = self.reporter.as_ref() {
2907 match &**package {
2908 PubGrubPackageInner::Root(_) => {}
2909 PubGrubPackageInner::Python(_) => {}
2910 PubGrubPackageInner::System(_) => {}
2911 PubGrubPackageInner::Marker { .. } => {}
2912 PubGrubPackageInner::Extra { .. } => {}
2913 PubGrubPackageInner::Group { .. } => {}
2914 PubGrubPackageInner::Package { name, .. } => {
2915 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2916 }
2917 }
2918 }
2919 }
2920
2921 fn on_complete(&self) {
2922 if let Some(reporter) = self.reporter.as_ref() {
2923 reporter.on_complete();
2924 }
2925 }
2926}
2927
2928#[derive(Clone)]
2930pub(crate) struct ForkState {
2931 pubgrub: State<UvDependencyProvider>,
2939 initial_id: Option<Id<PubGrubPackage>>,
2942 initial_version: Option<Version>,
2945 next: Id<PubGrubPackage>,
2947 pins: FilePins,
2956 fork_urls: ForkUrls,
2962 fork_indexes: ForkIndexes,
2967 priorities: PubGrubPriorities,
2973 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2976 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2978 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2980 env: ResolverEnvironment,
2995 python_requirement: PythonRequirement,
3008 conflict_tracker: ConflictTracker,
3009 prefetcher: BatchPrefetcher,
3013}
3014
3015impl ForkState {
3016 fn new(
3017 pubgrub: State<UvDependencyProvider>,
3018 env: ResolverEnvironment,
3019 python_requirement: PythonRequirement,
3020 prefetcher: BatchPrefetcher,
3021 ) -> Self {
3022 Self {
3023 initial_id: None,
3024 initial_version: None,
3025 next: pubgrub.root_package,
3026 pubgrub,
3027 pins: FilePins::default(),
3028 fork_urls: ForkUrls::default(),
3029 fork_indexes: ForkIndexes::default(),
3030 priorities: PubGrubPriorities::default(),
3031 added_dependencies: FxHashMap::default(),
3032 pre_visited: FxHashMap::default(),
3033 selected_versions: FxHashMap::default(),
3034 env,
3035 python_requirement,
3036 conflict_tracker: ConflictTracker::default(),
3037 prefetcher,
3038 }
3039 }
3040
3041 fn visit_package_version_dependencies(
3044 &mut self,
3045 for_package: Id<PubGrubPackage>,
3046 for_version: &Version,
3047 urls: &Urls,
3048 indexes: &Indexes,
3049 dependencies: &[PubGrubDependency],
3050 git: &GitResolver,
3051 workspace_members: &BTreeSet<PackageName>,
3052 resolution_strategy: &ResolutionStrategy,
3053 ) -> Result<(), ResolveError> {
3054 for dependency in dependencies {
3055 let PubGrubDependency {
3056 package,
3057 version,
3058 parent: _,
3059 source,
3060 } = dependency;
3061
3062 let mut has_url = false;
3063 if let Some(name) = package.name() {
3064 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3069 self.fork_urls.insert(name, url, &self.env)?;
3070 has_url = true;
3071 }
3072
3073 if let Some(index) = source.explicit_index() {
3074 self.fork_indexes.insert(name, index, &self.env)?;
3075 }
3076
3077 for index in indexes.get(name, &self.env) {
3079 self.fork_indexes.insert(name, index, &self.env)?;
3080 }
3081 }
3082
3083 if let Some(name) = self.pubgrub.package_store[for_package]
3084 .name_no_root()
3085 .filter(|name| !workspace_members.contains(name))
3086 {
3087 debug!(
3088 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3089 );
3090 } else {
3091 debug!("Adding direct dependency: {package}{version}");
3093
3094 let missing_lower_bound = version
3096 .bounding_range()
3097 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3098 let strategy_lowest = matches!(
3099 resolution_strategy,
3100 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3101 );
3102
3103 if !has_url && missing_lower_bound && strategy_lowest {
3104 let name = package.name_no_root().unwrap();
3105 let bound_on_other_package = dependencies.iter().any(|other| {
3112 Some(name) == other.package.name()
3113 && !other
3114 .version
3115 .bounding_range()
3116 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3117 });
3118
3119 if !bound_on_other_package {
3120 warn_user_once!(
3121 "The direct dependency `{name}` is unpinned. \
3122 Consider setting a lower bound when using `--resolution lowest` \
3123 or `--resolution lowest-direct` to avoid using outdated versions.",
3124 );
3125 }
3126 }
3127 }
3128
3129 self.priorities.insert(package, version, &self.fork_urls);
3131 if let Some(base_package) = package.base_package() {
3134 self.priorities
3135 .insert(&base_package, version, &self.fork_urls);
3136 }
3137 }
3138
3139 Ok(())
3140 }
3141
3142 fn add_package_version_dependencies(
3144 &mut self,
3145 for_package: Id<PubGrubPackage>,
3146 for_version: &Version,
3147 dependencies: Vec<PubGrubDependency>,
3148 ) {
3149 for dependency in &dependencies {
3150 let PubGrubDependency {
3151 package,
3152 version,
3153 parent: _,
3154 source: _,
3155 } = dependency;
3156
3157 let Some(base_package) = package.base_package() else {
3158 continue;
3159 };
3160
3161 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3162 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3163 self.pubgrub.add_proxy_package_incompatibility(
3164 proxy_package,
3165 base_package_id,
3166 version.clone(),
3167 );
3168 }
3169
3170 let conflict = self.pubgrub.add_package_version_dependencies(
3171 self.next,
3172 for_version.clone(),
3173 dependencies.into_iter().map(|dependency| {
3174 let PubGrubDependency {
3175 package,
3176 version,
3177 parent: _,
3178 source: _,
3179 } = dependency;
3180 (package, version)
3181 }),
3182 );
3183
3184 if let Some(incompatibility) = conflict {
3187 self.record_conflict(for_package, Some(for_version), incompatibility);
3188 }
3189 }
3190
3191 fn record_conflict(
3192 &mut self,
3193 affected: Id<PubGrubPackage>,
3194 version: Option<&Version>,
3195 incompatibility: IncompId<PubGrubPackage, Ranges<Version>, UnavailableReason>,
3196 ) {
3197 let mut culprit_is_real = false;
3198 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3199 if incompatible == affected {
3200 continue;
3201 }
3202 if self.pubgrub.package_store[affected].name()
3203 == self.pubgrub.package_store[incompatible].name()
3204 {
3205 continue;
3208 }
3209 culprit_is_real = true;
3210 let culprit_count = self
3211 .conflict_tracker
3212 .culprit
3213 .entry(incompatible)
3214 .or_default();
3215 *culprit_count += 1;
3216 if *culprit_count == CONFLICT_THRESHOLD {
3217 self.conflict_tracker.deprioritize.push(incompatible);
3218 }
3219 }
3220 if culprit_is_real {
3223 if tracing::enabled!(Level::DEBUG) {
3224 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3225 .iter()
3226 .map(|(package, _term)| &self.pubgrub.package_store[package])
3227 .join(", ");
3228 if let Some(version) = version {
3229 debug!(
3230 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3231 self.pubgrub.package_store[affected], version, incompatibility
3232 );
3233 } else {
3234 debug!(
3235 "Recording unit propagation conflict of {} from incompatibility of ({})",
3236 self.pubgrub.package_store[affected], incompatibility
3237 );
3238 }
3239 }
3240
3241 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3242 *affected_count += 1;
3243 if *affected_count == CONFLICT_THRESHOLD {
3244 self.conflict_tracker.prioritize.push(self.next);
3245 }
3246 }
3247 }
3248
3249 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3250 if let UnavailableVersion::IncompatibleDist(
3254 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3255 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3256 ) = reason
3257 {
3258 let package = &self.next;
3259 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3260 PubGrubPackageInner::Python(match kind {
3261 PythonRequirementKind::Installed => PubGrubPython::Installed,
3262 PythonRequirementKind::Target => PubGrubPython::Target,
3263 }),
3264 ));
3265 self.pubgrub
3266 .add_incompatibility(Incompatibility::from_dependency(
3267 *package,
3268 Range::singleton(version.clone()),
3269 (python, release_specifiers_to_ranges(requires_python)),
3270 ));
3271 self.pubgrub
3272 .partial_solution
3273 .add_decision(self.next, version);
3274 return;
3275 }
3276 self.pubgrub
3277 .add_incompatibility(Incompatibility::custom_version(
3278 self.next,
3279 version.clone(),
3280 UnavailableReason::Version(reason),
3281 ));
3282 }
3283
3284 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3290 self.env = env;
3291 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3293 debug!("Narrowed `requires-python` bound to: {}", req.target());
3294 self.python_requirement = req;
3295 }
3296 self
3297 }
3298
3299 fn source(
3303 &self,
3304 name: &PackageName,
3305 version: &Version,
3306 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3307 let url = self.fork_urls.get(name);
3308 let index = url
3309 .is_none()
3310 .then(|| {
3311 self.pins
3312 .get(name, version)
3313 .expect("Every package should be pinned")
3314 .index()
3315 })
3316 .flatten();
3317 (url, index)
3318 }
3319
3320 fn into_resolution(self) -> Resolution {
3321 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3322 let edge_count: usize = solution
3323 .keys()
3324 .map(|package| self.pubgrub.incompatibilities[package].len())
3325 .sum();
3326 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3327 for (package, self_version) in &solution {
3328 for id in &self.pubgrub.incompatibilities[package] {
3329 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3330 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3331 &incompatibility.kind
3332 else {
3333 continue;
3334 };
3335 let (self_package, dependency_package) = (*self_package, *dependency_package);
3336 let Some((self_range, dependency_range)) =
3337 incompatibility.dependency_version_sets()
3338 else {
3339 continue;
3340 };
3341 let dependency_range =
3342 dependency_range.map_or_else(|| Cow::Owned(Ranges::empty()), Cow::Borrowed);
3343 if *package != self_package {
3344 continue;
3345 }
3346 if !self_range.contains(self_version) {
3347 continue;
3348 }
3349 let Some(dependency_version) = solution.get(&dependency_package) else {
3350 continue;
3351 };
3352 if !dependency_range.contains(dependency_version) {
3353 continue;
3354 }
3355
3356 let self_package = &self.pubgrub.package_store[self_package];
3357 let dependency_package = &self.pubgrub.package_store[dependency_package];
3358
3359 let (self_name, self_extra, self_group) = match &**self_package {
3360 PubGrubPackageInner::Package {
3361 name: self_name,
3362 extra: self_extra,
3363 group: self_group,
3364 marker: _,
3365 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3366
3367 PubGrubPackageInner::Root(_) => (None, None, None),
3368
3369 _ => continue,
3370 };
3371
3372 let (self_url, self_index) = self_name
3373 .map(|self_name| self.source(self_name, self_version))
3374 .unwrap_or((None, None));
3375
3376 match **dependency_package {
3377 PubGrubPackageInner::Package {
3378 name: ref dependency_name,
3379 extra: ref dependency_extra,
3380 group: ref dependency_dev,
3381 marker: ref dependency_marker,
3382 } => {
3383 debug_assert!(
3384 dependency_extra.is_none(),
3385 "Packages should depend on an extra proxy"
3386 );
3387 debug_assert!(
3388 dependency_dev.is_none(),
3389 "Packages should depend on a group proxy"
3390 );
3391
3392 if self_group.is_none() {
3395 if self_name == Some(dependency_name) {
3396 continue;
3397 }
3398 }
3399
3400 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3401
3402 let edge = ResolutionDependencyEdge {
3403 from: self_name.cloned(),
3404 from_version: self_version.clone(),
3405 from_url: self_url.cloned(),
3406 from_index: self_index.cloned(),
3407 from_extra: self_extra.cloned(),
3408 from_group: self_group.cloned(),
3409 to: dependency_name.clone(),
3410 to_version: dependency_version.clone(),
3411 to_url: to_url.cloned(),
3412 to_index: to_index.cloned(),
3413 to_extra: dependency_extra.clone(),
3414 to_group: dependency_dev.clone(),
3415 marker: *dependency_marker,
3416 };
3417 edges.push(edge);
3418 }
3419
3420 PubGrubPackageInner::Marker {
3421 name: ref dependency_name,
3422 marker: ref dependency_marker,
3423 } => {
3424 if self_group.is_none() {
3427 if self_name == Some(dependency_name) {
3428 continue;
3429 }
3430 }
3431
3432 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3433
3434 let edge = ResolutionDependencyEdge {
3435 from: self_name.cloned(),
3436 from_version: self_version.clone(),
3437 from_url: self_url.cloned(),
3438 from_index: self_index.cloned(),
3439 from_extra: self_extra.cloned(),
3440 from_group: self_group.cloned(),
3441 to: dependency_name.clone(),
3442 to_version: dependency_version.clone(),
3443 to_url: to_url.cloned(),
3444 to_index: to_index.cloned(),
3445 to_extra: None,
3446 to_group: None,
3447 marker: *dependency_marker,
3448 };
3449 edges.push(edge);
3450 }
3451
3452 PubGrubPackageInner::Extra {
3453 name: ref dependency_name,
3454 extra: ref dependency_extra,
3455 marker: ref dependency_marker,
3456 } => {
3457 if self_group.is_none() {
3458 debug_assert!(
3459 self_name != Some(dependency_name),
3460 "Extras should be flattened"
3461 );
3462 }
3463 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3464
3465 let edge = ResolutionDependencyEdge {
3467 from: self_name.cloned(),
3468 from_version: self_version.clone(),
3469 from_url: self_url.cloned(),
3470 from_index: self_index.cloned(),
3471 from_extra: self_extra.cloned(),
3472 from_group: self_group.cloned(),
3473 to: dependency_name.clone(),
3474 to_version: dependency_version.clone(),
3475 to_url: to_url.cloned(),
3476 to_index: to_index.cloned(),
3477 to_extra: Some(dependency_extra.clone()),
3478 to_group: None,
3479 marker: *dependency_marker,
3480 };
3481 edges.push(edge);
3482
3483 let edge = ResolutionDependencyEdge {
3485 from: self_name.cloned(),
3486 from_version: self_version.clone(),
3487 from_url: self_url.cloned(),
3488 from_index: self_index.cloned(),
3489 from_extra: self_extra.cloned(),
3490 from_group: self_group.cloned(),
3491 to: dependency_name.clone(),
3492 to_version: dependency_version.clone(),
3493 to_url: to_url.cloned(),
3494 to_index: to_index.cloned(),
3495 to_extra: None,
3496 to_group: None,
3497 marker: *dependency_marker,
3498 };
3499 edges.push(edge);
3500 }
3501
3502 PubGrubPackageInner::Group {
3503 name: ref dependency_name,
3504 group: ref dependency_group,
3505 marker: ref dependency_marker,
3506 } => {
3507 debug_assert!(
3508 self_name != Some(dependency_name),
3509 "Groups should be flattened"
3510 );
3511
3512 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3513
3514 let edge = ResolutionDependencyEdge {
3517 from: self_name.cloned(),
3518 from_version: self_version.clone(),
3519 from_url: self_url.cloned(),
3520 from_index: self_index.cloned(),
3521 from_extra: self_extra.cloned(),
3522 from_group: self_group.cloned(),
3523 to: dependency_name.clone(),
3524 to_version: dependency_version.clone(),
3525 to_url: to_url.cloned(),
3526 to_index: to_index.cloned(),
3527 to_extra: None,
3528 to_group: Some(dependency_group.clone()),
3529 marker: *dependency_marker,
3530 };
3531 edges.push(edge);
3532 }
3533
3534 _ => {}
3535 }
3536 }
3537 }
3538
3539 let nodes = solution
3540 .into_iter()
3541 .filter_map(|(package, version)| {
3542 if let PubGrubPackageInner::Package {
3543 name,
3544 extra,
3545 group,
3546 marker: MarkerTree::TRUE,
3547 } = &*self.pubgrub.package_store[package]
3548 {
3549 let (url, index) = self.source(name, &version);
3550 Some((
3551 ResolutionPackage {
3552 name: name.clone(),
3553 extra: extra.clone(),
3554 dev: group.clone(),
3555 url: url.cloned(),
3556 index: index.cloned(),
3557 },
3558 version,
3559 ))
3560 } else {
3561 None
3562 }
3563 })
3564 .collect();
3565
3566 Resolution {
3567 nodes,
3568 edges,
3569 pins: self.pins,
3570 env: self.env,
3571 }
3572 }
3573}
3574
3575#[derive(Debug)]
3577pub(crate) struct Resolution {
3578 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3579 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3582 pub(crate) pins: FilePins,
3584 pub(crate) env: ResolverEnvironment,
3586}
3587
3588#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3591pub(crate) struct ResolutionPackage {
3592 pub(crate) name: PackageName,
3593 pub(crate) extra: Option<ExtraName>,
3594 pub(crate) dev: Option<GroupName>,
3595 pub(crate) url: Option<VerbatimParsedUrl>,
3597 pub(crate) index: Option<IndexUrl>,
3599}
3600
3601#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3604pub(crate) struct ResolutionDependencyEdge {
3605 pub(crate) from: Option<PackageName>,
3607 pub(crate) from_version: Version,
3608 pub(crate) from_url: Option<VerbatimParsedUrl>,
3609 pub(crate) from_index: Option<IndexUrl>,
3610 pub(crate) from_extra: Option<ExtraName>,
3611 pub(crate) from_group: Option<GroupName>,
3612 pub(crate) to: PackageName,
3613 pub(crate) to_version: Version,
3614 pub(crate) to_url: Option<VerbatimParsedUrl>,
3615 pub(crate) to_index: Option<IndexUrl>,
3616 pub(crate) to_extra: Option<ExtraName>,
3617 pub(crate) to_group: Option<GroupName>,
3618 pub(crate) marker: MarkerTree,
3619}
3620
3621impl ResolutionDependencyEdge {
3622 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3623 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3627 }
3628}
3629
3630#[derive(Debug)]
3632#[expect(clippy::large_enum_variant)]
3633pub(crate) enum Request {
3634 Package(PackageName, Option<IndexMetadata>),
3636 Dist(Dist),
3638 Installed(InstalledDist),
3640 Prefetch(PackageName, Range<Version>, PythonRequirement),
3642}
3643
3644impl<'a> From<ResolvedDistRef<'a>> for Request {
3645 fn from(dist: ResolvedDistRef<'a>) -> Self {
3646 match dist {
3652 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3653 let source = prioritized.source_dist().expect("a source distribution");
3656 assert_eq!(
3657 (&sdist.name, &sdist.version),
3658 (&source.name, &source.version),
3659 "expected chosen sdist to match prioritized sdist"
3660 );
3661 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3662 }
3663 ResolvedDistRef::InstallableRegistryBuiltDist {
3664 wheel, prioritized, ..
3665 } => {
3666 assert_eq!(
3667 Some(&wheel.filename),
3668 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3669 "expected chosen wheel to match best wheel"
3670 );
3671 let built = prioritized.built_dist().expect("at least one wheel");
3674 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3675 }
3676 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3677 }
3678 }
3679}
3680
3681impl Display for Request {
3682 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3683 match self {
3684 Self::Package(package_name, _) => {
3685 write!(f, "Versions {package_name}")
3686 }
3687 Self::Dist(dist) => {
3688 write!(f, "Metadata {dist}")
3689 }
3690 Self::Installed(dist) => {
3691 write!(f, "Installed metadata {dist}")
3692 }
3693 Self::Prefetch(package_name, range, _) => {
3694 write!(f, "Prefetch {package_name} {range}")
3695 }
3696 }
3697 }
3698}
3699
3700#[derive(Debug)]
3701#[expect(clippy::large_enum_variant)]
3702enum Response {
3703 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3705 Dist {
3707 dist: Dist,
3708 metadata: MetadataResponse,
3709 },
3710 Installed {
3712 dist: InstalledDist,
3713 metadata: MetadataResponse,
3714 },
3715}
3716
3717enum Dependencies {
3723 Unavailable(UnavailableVersion),
3725 Available(Vec<PubGrubDependency>),
3731 Unforkable(Vec<PubGrubDependency>),
3737}
3738
3739impl Dependencies {
3740 fn fork(
3747 self,
3748 env: &ResolverEnvironment,
3749 python_requirement: &PythonRequirement,
3750 conflicts: &Conflicts,
3751 ) -> ForkedDependencies {
3752 let deps = match self {
3753 Self::Available(deps) => deps,
3754 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3755 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3756 };
3757 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3758 for dep in deps {
3759 let name = dep
3760 .package
3761 .name()
3762 .expect("dependency always has a name")
3763 .clone();
3764 name_to_deps.entry(name).or_default().push(dep);
3765 }
3766 let Forks {
3767 mut forks,
3768 diverging_packages,
3769 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3770 if forks.is_empty() {
3771 ForkedDependencies::Unforked(vec![])
3772 } else if forks.len() == 1 {
3773 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3774 } else {
3775 ForkedDependencies::Forked {
3776 forks,
3777 diverging_packages: diverging_packages.into_iter().collect(),
3778 }
3779 }
3780 }
3781}
3782
3783#[derive(Debug)]
3790enum ForkedDependencies {
3791 Unavailable(UnavailableVersion),
3793 Unforked(Vec<PubGrubDependency>),
3797 Forked {
3803 forks: Vec<Fork>,
3804 diverging_packages: Vec<PackageName>,
3806 },
3807}
3808
3809#[derive(Debug, Default)]
3814struct Forks {
3815 forks: Vec<Fork>,
3817 diverging_packages: BTreeSet<PackageName>,
3819}
3820
3821impl Forks {
3822 fn new(
3823 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3824 env: &ResolverEnvironment,
3825 python_requirement: &PythonRequirement,
3826 conflicts: &Conflicts,
3827 ) -> Self {
3828 let python_marker = python_requirement.to_marker_tree();
3829
3830 let mut forks = vec![Fork::new(env.clone())];
3831 let mut diverging_packages = BTreeSet::new();
3832 for (name, mut deps) in name_to_deps {
3833 assert!(!deps.is_empty(), "every name has at least one dependency");
3834 if let [dep] = deps.as_slice() {
3845 if marker::requires_python(dep.package.marker())
3853 .is_none_or(|bound| !python_requirement.raises(&bound))
3854 {
3855 let dep = deps.pop().unwrap();
3856 let marker = dep.package.marker();
3857 for fork in &mut forks {
3858 if fork.env.included_by_marker(marker) {
3859 fork.add_dependency(dep.clone());
3860 }
3861 }
3862 continue;
3863 }
3864 } else {
3865 if let Some(dep) = deps.first() {
3867 let marker = dep.package.marker();
3868 if deps.iter().all(|dep| marker == dep.package.marker()) {
3869 if marker::requires_python(marker)
3873 .is_none_or(|bound| !python_requirement.raises(&bound))
3874 {
3875 for dep in deps {
3876 for fork in &mut forks {
3877 if fork.env.included_by_marker(marker) {
3878 fork.add_dependency(dep.clone());
3879 }
3880 }
3881 }
3882 continue;
3883 }
3884 }
3885 }
3886 }
3887 for dep in deps {
3888 let mut forker = match ForkingPossibility::new(env, &dep) {
3889 ForkingPossibility::Possible(forker) => forker,
3890 ForkingPossibility::DependencyAlwaysExcluded => {
3891 continue;
3894 }
3895 ForkingPossibility::NoForkingPossible => {
3896 for fork in &mut forks {
3899 fork.add_dependency(dep.clone());
3900 }
3901 continue;
3902 }
3903 };
3904 diverging_packages.insert(name.clone());
3906
3907 let mut new = vec![];
3908 for fork in std::mem::take(&mut forks) {
3909 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3910 new.push(fork);
3911 continue;
3912 };
3913 forker = remaining_forker;
3914
3915 for fork_env in envs {
3916 let mut new_fork = fork.clone();
3917 new_fork.set_env(fork_env);
3918 if forker.included(&new_fork.env) {
3923 new_fork.add_dependency(dep.clone());
3924 }
3925 if new_fork.env.included_by_marker(python_marker) {
3928 new.push(new_fork);
3929 }
3930 }
3931 }
3932 forks = new;
3933 }
3934 }
3935 for set in conflicts.iter() {
3950 let mut new = vec![];
3951 for fork in std::mem::take(&mut forks) {
3952 let mut has_conflicting_dependency = false;
3960 for item in set.iter() {
3961 if fork.contains_conflicting_item(item.as_ref()) {
3962 has_conflicting_dependency = true;
3963 diverging_packages.insert(item.package().clone());
3964 break;
3965 }
3966 }
3967 if !has_conflicting_dependency {
3968 new.push(fork);
3969 continue;
3970 }
3971
3972 let non_excluded: Vec<_> = set
3978 .iter()
3979 .filter(|item| fork.env.included_by_group(item.as_ref()))
3980 .collect();
3981 if non_excluded.len() < 2 {
3982 let dominated = non_excluded.iter().all(|item| {
3987 !conflicts.iter().any(|other_set| {
3988 !std::ptr::eq(set, other_set)
3989 && other_set.contains(item.package(), item.kind().as_ref())
3990 && other_set
3991 .iter()
3992 .filter(|other_item| {
3993 other_item.package() != item.package()
3994 || other_item.kind() != item.kind()
3995 })
3996 .any(|other_item| {
3997 fork.env.included_by_group(other_item.as_ref())
3998 })
3999 })
4000 });
4001 if dominated {
4002 let rules: Vec<_> = set
4007 .iter()
4008 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4009 .cloned()
4010 .map(Err)
4011 .collect();
4012 if let Some(filtered) = fork.filter(rules) {
4013 new.push(filtered);
4014 }
4015 continue;
4016 }
4017 }
4018
4019 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4021 new.push(fork_none);
4022 }
4023
4024 for (i, _) in set.iter().enumerate() {
4032 let fork_allows_group = fork.clone().filter(
4033 set.iter()
4034 .cloned()
4035 .enumerate()
4036 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4037 );
4038 if let Some(fork_allows_group) = fork_allows_group {
4039 new.push(fork_allows_group);
4040 }
4041 }
4042 }
4043 forks = new;
4044 }
4045 Self {
4046 forks,
4047 diverging_packages,
4048 }
4049 }
4050}
4051
4052#[derive(Clone, Debug)]
4062struct Fork {
4063 dependencies: Vec<PubGrubDependency>,
4072 conflicts: crate::FxHashbrownSet<ConflictItem>,
4078 env: ResolverEnvironment,
4090}
4091
4092impl Fork {
4093 fn new(env: ResolverEnvironment) -> Self {
4096 Self {
4097 dependencies: vec![],
4098 conflicts: crate::FxHashbrownSet::default(),
4099 env,
4100 }
4101 }
4102
4103 fn add_dependency(&mut self, dep: PubGrubDependency) {
4105 if let Some(conflicting_item) = dep.conflicting_item() {
4106 self.conflicts.insert(conflicting_item.to_owned());
4107 }
4108 self.dependencies.push(dep);
4109 }
4110
4111 fn set_env(&mut self, env: ResolverEnvironment) {
4116 self.env = env;
4117 self.dependencies.retain(|dep| {
4118 let marker = dep.package.marker();
4119 if self.env.included_by_marker(marker) {
4120 return true;
4121 }
4122 if let Some(conflicting_item) = dep.conflicting_item() {
4123 self.conflicts.remove(&conflicting_item);
4124 }
4125 false
4126 });
4127 }
4128
4129 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4132 self.conflicts.contains(&item)
4133 }
4134
4135 fn filter(
4142 mut self,
4143 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4144 ) -> Option<Self> {
4145 self.env = self.env.filter_by_group(rules)?;
4146 self.dependencies.retain(|dep| {
4147 let Some(conflicting_item) = dep.conflicting_item() else {
4148 return true;
4149 };
4150 if self.env.included_by_group(conflicting_item) {
4151 return true;
4152 }
4153 match conflicting_item.kind() {
4154 ConflictKindRef::Project => {
4157 if dep.parent.is_some() {
4158 return true;
4159 }
4160 }
4161 ConflictKindRef::Group(_) => {}
4162 ConflictKindRef::Extra(_) => {}
4163 }
4164 self.conflicts.remove(&conflicting_item);
4165 false
4166 });
4167 Some(self)
4168 }
4169
4170 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4172 let self_bound = self.env.requires_python().unwrap_or_default();
4182 let other_bound = other.env.requires_python().unwrap_or_default();
4183 self_bound.lower().cmp(other_bound.lower())
4184 }
4185
4186 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4188 let self_upper_bounds = self
4193 .dependencies
4194 .iter()
4195 .filter(|dep| {
4196 dep.version
4197 .bounding_range()
4198 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4199 })
4200 .count();
4201 let other_upper_bounds = other
4202 .dependencies
4203 .iter()
4204 .filter(|dep| {
4205 dep.version
4206 .bounding_range()
4207 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4208 })
4209 .count();
4210
4211 self_upper_bounds.cmp(&other_upper_bounds)
4212 }
4213}
4214
4215impl Eq for Fork {}
4216
4217impl PartialEq for Fork {
4218 fn eq(&self, other: &Self) -> bool {
4219 self.dependencies == other.dependencies && self.env == other.env
4220 }
4221}
4222
4223#[derive(Debug, Clone)]
4224pub(crate) struct VersionFork {
4225 env: ResolverEnvironment,
4227 id: Id<PubGrubPackage>,
4229 version: Option<Version>,
4231}
4232
4233fn enrich_dependency_error(
4235 error: ResolveError,
4236 id: Id<PubGrubPackage>,
4237 version: &Version,
4238 pubgrub: &State<UvDependencyProvider>,
4239) -> ResolveError {
4240 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4241 return error;
4242 };
4243 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4244 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4245}
4246
4247fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4249 let package = &state.package_store[id];
4250 if package.is_root() {
4251 return MarkerTree::TRUE;
4252 }
4253
4254 let mut ancestors = FxHashSet::default();
4257 let mut stack = vec![id];
4258 let mut root = None;
4259 ancestors.insert(id);
4260
4261 while let Some(current) = stack.pop() {
4262 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4263 continue;
4264 };
4265
4266 for index in incompatibilities {
4267 let incompat = &state.incompatibility_store[*index];
4268 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4269 if current != *child {
4270 continue;
4271 }
4272 if ancestors.insert(*parent) {
4273 if state.package_store[*parent].is_root() {
4274 root = Some(*parent);
4275 }
4276 stack.push(*parent);
4277 }
4278 }
4279 }
4280 }
4281
4282 let Some(root) = root else {
4283 return MarkerTree::FALSE;
4284 };
4285
4286 let mut environments = FxHashMap::default();
4289 let mut queue = VecDeque::from([root]);
4290 environments.insert(root, MarkerTree::TRUE);
4291
4292 while let Some(current) = queue.pop_front() {
4293 let Some(current_environment) = environments.get(¤t).copied() else {
4294 continue;
4295 };
4296 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4297 continue;
4298 };
4299
4300 for index in incompatibilities {
4301 let incompat = &state.incompatibility_store[*index];
4302 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4303 continue;
4304 };
4305 if current != *parent || !ancestors.contains(child) {
4306 continue;
4307 }
4308
4309 let mut next_environment = state.package_store[*child].marker();
4310 next_environment.and(current_environment);
4311
4312 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4313 let mut combined = *entry;
4314 combined.or(next_environment);
4315 if combined != *entry {
4316 *entry = combined;
4317 queue.push_back(*child);
4318 }
4319 }
4320 }
4321
4322 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4323}
4324
4325#[derive(Debug, Default, Clone)]
4326struct ConflictTracker {
4327 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4329 prioritize: Vec<Id<PubGrubPackage>>,
4333 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4335 deprioritize: Vec<Id<PubGrubPackage>>,
4339}