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