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, DerivationChain, Dist, DistErrorKind, Identifier, IndexCapabilities, IndexLocations,
26 IndexMetadata, IndexUrl, InstalledDist, Name, RemoteSource, Requirement, RequiresPython,
27 ResolutionRecorder, ResolvedDist, SourceDist, VariantsJsonContent, VersionOrUrlRef,
28};
29use uv_git::GitResolver;
30use uv_normalize::PackageName;
31use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
32use uv_pep508::{
33 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
34};
35use uv_platform_tags::{IncompatibleTag, Tags};
36use uv_preview::PreviewFeature;
37use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
38use uv_resolver_types::{PackageNodeKind, VariantsJsonMetadataError};
39use uv_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_urls::ForkUrls;
49use crate::manifest::Manifest;
50use crate::pins::FilePins;
51use crate::preferences::{PreferenceSource, Preferences};
52use crate::prioritized_distribution::{
53 CompatibleDist, IncompatibleDist, IncompatibleSource, IncompatibleWheel, PythonRequirementKind,
54 RegistryVariantsJson, implied_markers,
55};
56use crate::pubgrub::{
57 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
58 PubGrubPython, Range,
59};
60use crate::python_requirement::PythonRequirement;
61use crate::resolution::ResolverOutput;
62use crate::resolution_mode::ResolutionStrategy;
63use crate::resolved::ResolvedDistRef;
64pub(crate) use crate::resolver::availability::{
65 ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion,
66 UnsatisfiableRequirement,
67};
68use crate::resolver::batch_prefetch::BatchPrefetcher;
69use crate::resolver::derivation::DerivationChainBuilder;
70pub use crate::resolver::environment::ResolverEnvironment;
71use crate::resolver::environment::{
72 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
73};
74pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
75pub use crate::resolver::index::InMemoryIndex;
76use crate::resolver::index::ParsedVariantsJsonResponse;
77use crate::resolver::indexes::Indexes;
78use crate::resolver::package_source::PackageSource;
79pub use crate::resolver::provider::{
80 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
81 VariantsJsonResponse, VersionsResponse, WheelMetadataResult,
82};
83pub use crate::resolver::reporter::Reporter;
84use crate::resolver::requests::{MetadataRequest, MetadataRequests};
85pub(crate) use crate::resolver::requests::{RegisteredMetadata, RegisteredVariantsJson};
86use crate::resolver::requirements::{RequirementContext, RequirementExpander};
87use crate::resolver::system::SystemDependency;
88pub(crate) use crate::resolver::urls::Urls;
89use crate::universal_marker::UniversalMarker;
90use crate::yanks::AllowedYanks;
91use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
92pub(crate) use provider::MetadataUnavailable;
93pub(crate) use resolution::{
94 Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionNodeId, ResolutionPackage,
95 ResolvedFork, SelectedDistribution,
96};
97use uv_configuration::ForkStrategy;
98
99mod availability;
100mod batch_prefetch;
101mod derivation;
102mod environment;
103mod fork_map;
104mod index;
105mod indexes;
106mod package_source;
107mod provider;
108mod reporter;
109mod requests;
110mod requirements;
111mod resolution;
112mod system;
113mod urls;
114
115const CONFLICT_THRESHOLD: usize = 5;
117
118pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
119 state: ResolverState<InstalledPackages>,
120 provider: Provider,
121}
122
123struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
126 recorder: Option<ResolutionRecorder>,
127 project: Option<PackageName>,
128 requirements: Vec<Requirement>,
129 constraints: Constraints,
130 modifiers: DependencyModifiers,
131 preferences: Preferences,
132 git: GitResolver,
133 capabilities: IndexCapabilities,
134 locations: IndexLocations,
135 exclusions: Exclusions,
136 urls: Urls,
137 indexes: Indexes,
138 dependency_mode: DependencyMode,
139 hasher: HashStrategy,
140 env: ResolverEnvironment,
141 current_environment: MarkerEnvironment,
143 tags: Option<Tags>,
144 python_requirement: PythonRequirement,
145 conflicts: Conflicts,
146 workspace_members: BTreeSet<PackageName>,
147 selector: CandidateSelector,
148 index: InMemoryIndex,
149 installed_packages: InstalledPackages,
150 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
153 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
155 options: Options,
157 reporter: Option<Arc<dyn Reporter>>,
159}
160
161impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
162 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
163{
164 pub fn new(
183 manifest: Manifest,
184 options: Options,
185 python_requirement: &'a PythonRequirement,
186 env: ResolverEnvironment,
187 current_environment: &MarkerEnvironment,
188 conflicts: Conflicts,
189 tags: Option<&'a Tags>,
190 flat_index: &'a FlatIndex,
191 index: &'a InMemoryIndex,
192 hasher: &'a HashStrategy,
193 build_context: &'a Context,
194 installed_packages: InstalledPackages,
195 database: DistributionDatabase<'a, Context>,
196 ) -> Result<Self, ResolveError> {
197 let provider = DefaultResolverProvider::new(
198 database,
199 flat_index,
200 tags,
201 python_requirement.target(),
202 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
203 hasher,
204 options.exclude_newer.clone(),
205 build_context.locations(),
206 build_context.build_options(),
207 build_context.capabilities(),
208 options.minimum_libc_version,
209 );
210
211 Ok(Self::new_custom_io(
212 manifest,
213 options,
214 hasher,
215 env,
216 current_environment,
217 tags.cloned(),
218 python_requirement,
219 conflicts,
220 index,
221 build_context.git(),
222 build_context.capabilities(),
223 build_context.locations(),
224 provider,
225 installed_packages,
226 ))
227 }
228}
229
230impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
231 Resolver<Provider, InstalledPackages>
232{
233 pub fn new_custom_io(
235 manifest: Manifest,
236 options: Options,
237 hasher: &HashStrategy,
238 env: ResolverEnvironment,
239 current_environment: &MarkerEnvironment,
240 tags: Option<Tags>,
241 python_requirement: &PythonRequirement,
242 conflicts: Conflicts,
243 index: &InMemoryIndex,
244 git: &GitResolver,
245 capabilities: &IndexCapabilities,
246 locations: &IndexLocations,
247 provider: Provider,
248 installed_packages: InstalledPackages,
249 ) -> Self {
250 let state = ResolverState {
251 index: index.clone(),
252 git: git.clone(),
253 capabilities: capabilities.clone(),
254 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
255 dependency_mode: options.dependency_mode,
256 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
257 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
258 recorder: manifest.recorder.clone(),
259 project: manifest.project,
260 workspace_members: manifest.workspace_members.into_keys().collect(),
261 requirements: manifest.requirements,
262 constraints: manifest
263 .constraints
264 .with_recorder(manifest.recorder.clone()),
265 modifiers: manifest.modifiers.with_recorder(manifest.recorder.clone()),
266 preferences: manifest.preferences,
267 exclusions: manifest.exclusions,
268 hasher: hasher.clone(),
269 locations: locations.clone(),
270 env,
271 current_environment: current_environment.clone(),
272 tags,
273 python_requirement: python_requirement.clone(),
274 conflicts,
275 installed_packages,
276 unavailable_packages: Box::default(),
277 incomplete_packages: Box::default(),
278 options,
279 reporter: None,
280 };
281 Self { state, provider }
282 }
283
284 #[must_use]
286 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
287 Self {
288 state: ResolverState {
289 reporter: Some(reporter.clone()),
290 ..self.state
291 },
292 provider: self
293 .provider
294 .with_reporter(reporter.into_distribution_reporter()),
295 }
296 }
297
298 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
300 let state = Arc::new(self.state);
301 let provider = Arc::new(self.provider);
302
303 let (request_sink, request_stream) = mpsc::channel(300);
307 let requests =
308 MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
309
310 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
312
313 let solver = state.clone();
315 let (tx, rx) = oneshot::channel();
316 thread::Builder::new()
317 .name("uv-resolver".into())
318 .spawn(move || {
319 let result = solver.solve(&requests);
320
321 let _ = tx.send(result);
323 })
324 .unwrap();
325
326 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
327
328 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
330
331 state.on_complete();
332 resolution
333 }
334}
335
336impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
337 #[instrument(skip_all)]
338 fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
339 debug!(
340 "Solving with installed Python version: {}",
341 self.python_requirement.exact()
342 );
343 debug!(
344 "Solving with target Python version: {}",
345 self.python_requirement.target()
346 );
347 if !self.options.exclude_newer.is_empty() {
348 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
349 }
350
351 let mut visited = FxHashSet::default();
352
353 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
354 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
355 let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
356 let state = ForkState::new(
357 pubgrub,
358 self.env.clone(),
359 self.python_requirement.clone(),
360 prefetcher,
361 );
362 let mut preferences = self.preferences.clone();
363 let mut forked_states = self.env.initial_forked_states(state)?;
364
365 match (self.options.fork_strategy, self.options.resolution_mode) {
371 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
372 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
373 }
374 (ForkStrategy::RequiresPython, _) => {
375 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
376 }
377 }
378 let mut resolutions = vec![];
379
380 'FORK: while let Some(mut state) = forked_states.pop() {
381 if let Some(split) = state.env.end_user_fork_display() {
382 let requires_python = state.python_requirement.target();
383 debug!("Solving {split} (requires-python: {requires_python:?})");
384 }
385 let start = Instant::now();
386 loop {
387 let continuation = mem::take(&mut state.continuation);
388 let (highest_priority_pkg, initial_version) = match continuation {
389 ForkContinuation::SelectVersion { package } => (package, None),
390 ForkContinuation::UseVersion { package, version } => (package, Some(version)),
391 ForkContinuation::Propagate => {
392 let result = state.pubgrub.unit_propagation(state.next);
394 match result {
395 Err(err) => {
396 return Err(self.convert_no_solution_err(
398 err,
399 state.fork_urls,
400 state.fork_indexes,
401 &state.known_versions.0,
402 state.env,
403 self.current_environment.clone(),
404 &visited,
405 ));
406 }
407 Ok(conflicts) => {
408 for (affected, incompatibility) in conflicts {
409 state.record_conflict(affected, None, incompatibility);
412 }
413 }
414 }
415
416 if self.dependency_mode.is_transitive() {
418 Self::pre_visit(
419 state.pubgrub.partial_solution.prioritized_packages().map(
420 |(id, range)| (id, &state.pubgrub.package_store[id], range),
421 ),
422 &mut state.pre_visited,
423 &self.urls,
424 &self.indexes,
425 &state.python_requirement,
426 requests,
427 )?;
428 }
429
430 state.reprioritize_conflicts();
431
432 trace!(
433 "Assigned packages: {}",
434 state
435 .pubgrub
436 .partial_solution
437 .extract_solution()
438 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
439 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
440 .join(", ")
441 );
442 let Some((highest_priority_pkg, _)) =
446 state.pubgrub.partial_solution.pick_highest_priority_pkg(
447 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
448 )
449 else {
450 if tracing::enabled!(Level::DEBUG) {
452 state.prefetcher.log_tried_versions();
453 }
454 debug!(
455 "{} resolution took {:.3}s",
456 state.env,
457 start.elapsed().as_secs_f32()
458 );
459
460 let resolution = state.into_resolution();
461
462 if matches!(
471 self.options.resolution_mode,
472 ResolutionMode::Lowest | ResolutionMode::Highest
473 ) {
474 let marker = resolution
475 .env
476 .try_universal_markers()
477 .unwrap_or(UniversalMarker::TRUE);
478 for (package, version) in &resolution.nodes {
479 preferences.insert(
480 package.name.clone(),
481 package.index.clone(),
482 marker,
483 version.clone(),
484 PreferenceSource::Resolver,
485 );
486 }
487 }
488
489 resolutions.push(resolution);
490 continue 'FORK;
491 };
492 trace!(
493 "Chose package for decision: {}. remaining choices: {}",
494 state.pubgrub.package_store[highest_priority_pkg],
495 state
496 .pubgrub
497 .partial_solution
498 .undecided_packages()
499 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
500 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
501 .join(", ")
502 );
503
504 (highest_priority_pkg, None)
505 }
506 };
507
508 state.next = highest_priority_pkg;
509
510 let next_id = state.next;
512 let next_package = &state.pubgrub.package_store[state.next];
513
514 let source =
515 PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
516
517 self.request_package(next_package, source, requests)?;
529
530 let version = if let Some(version) = initial_version {
531 version
532 } else {
533 let term_intersection = state
534 .pubgrub
535 .partial_solution
536 .term_intersection_for_package(next_id)
537 .expect("a package was chosen but we don't have a term");
538 let range = term_intersection.unwrap_positive();
539
540 let cache_selected_version = match source {
544 PackageSource::Registry(None) => true,
545 PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
546 };
547 let decision = if cache_selected_version
548 && let Some((selected_range, version)) =
549 state.selected_versions.get(&next_id)
550 && selected_range == range
551 {
552 Some(ResolverVersion::Unforked(version.clone()))
553 } else {
554 let decision = self.choose_version(
555 next_package,
556 next_id,
557 source,
558 range,
559 &mut state.pins,
560 &preferences,
561 &state.env,
562 &state.python_requirement,
563 &state.pubgrub,
564 &mut visited,
565 requests,
566 )?;
567
568 if cache_selected_version
569 && let Some(ResolverVersion::Unforked(version)) = &decision
570 {
571 state
572 .selected_versions
573 .insert(next_id, (range.clone(), version.clone()));
574 }
575
576 decision
577 };
578
579 let Some(version) = decision else {
581 debug!("No compatible version found for: {next_package}");
582
583 let term_intersection = state
584 .pubgrub
585 .partial_solution
586 .term_intersection_for_package(next_id)
587 .expect("a package was chosen but we don't have a term");
588
589 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
590 if let Some(reason) = self.unavailable_packages.pin().get(name) {
592 state
593 .pubgrub
594 .add_incompatibility(Incompatibility::custom_term(
595 next_id,
596 term_intersection.clone(),
597 UnavailableReason::Package(reason.clone()),
598 ));
599 continue;
600 }
601 }
602
603 state
604 .pubgrub
605 .add_incompatibility(Incompatibility::no_versions(
606 next_id,
607 term_intersection.clone(),
608 ));
609 continue;
610 };
611
612 let version = match version {
613 ResolverVersion::Unforked(version) => version,
614 ResolverVersion::Forked(forks) => {
615 forked_states.extend(self.version_forks_to_fork_states(state, forks));
616 continue 'FORK;
617 }
618 ResolverVersion::Unavailable(version, reason) => {
619 state.add_unavailable_version(
620 version,
621 reason,
622 &self.index,
623 &self.installed_packages,
624 );
625 continue;
626 }
627 };
628
629 if let PackageSource::Registry(index) = source {
631 state.prefetcher.prefetch_batches(
632 next_package,
633 index,
634 &version,
635 term_intersection.unwrap_positive(),
636 state
637 .pubgrub
638 .partial_solution
639 .unchanging_term_for_package(next_id),
640 &state.python_requirement,
641 &self.selector,
642 &state.env,
643 )?;
644 }
645
646 version
647 };
648
649 state.prefetcher.version_tried(next_package, &version);
650
651 self.on_progress(next_package, &version);
652
653 if state
654 .added_dependencies
655 .get(&next_id)
656 .is_some_and(|versions| versions.contains(&version))
657 {
658 state
661 .pubgrub
662 .partial_solution
663 .add_decision(next_id, version);
664 continue;
665 }
666
667 let forked_deps = self.get_dependencies_forking(
669 next_id,
670 next_package,
671 &version,
672 &state.pins,
673 &state.env,
674 &state.python_requirement,
675 &state.pubgrub,
676 )?;
677
678 match forked_deps {
679 ForkedDependencies::Unavailable(reason) => {
680 let versions = state.widen_version_to_gap(
683 &version,
684 &self.index,
685 &self.installed_packages,
686 );
687 state
688 .pubgrub
689 .add_incompatibility(Incompatibility::custom_term(
690 next_id,
691 Term::Positive(versions),
692 UnavailableReason::Version(reason),
693 ));
694 }
695 ForkedDependencies::Unforked(dependencies) => {
696 state
697 .added_dependencies
698 .entry(next_id)
699 .or_default()
700 .insert(version.clone());
701
702 state
704 .visit_package_version_dependencies(
705 next_id,
706 &version,
707 &self.urls,
708 &self.indexes,
709 &dependencies,
710 &self.git,
711 &self.workspace_members,
712 self.selector.resolution_strategy(),
713 )
714 .map_err(|err| {
715 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
716 })?;
717
718 self.visit_dependencies(&dependencies, &state, requests)
720 .map_err(|err| {
721 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
722 })?;
723
724 state.add_package_version_dependencies(
726 next_id,
727 &version,
728 dependencies,
729 &self.index,
730 &self.installed_packages,
731 );
732 }
733 ForkedDependencies::Forked {
734 mut forks,
735 diverging_packages,
736 } => {
737 state
738 .added_dependencies
739 .entry(next_id)
740 .or_default()
741 .insert(version.clone());
742
743 debug!(
744 "Pre-fork {} took {:.3}s",
745 state.env,
746 start.elapsed().as_secs_f32()
747 );
748
749 match (self.options.fork_strategy, self.options.resolution_mode) {
751 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
752 forks.sort_by(|a, b| {
756 a.cmp_requires_python(b)
757 .reverse()
758 .then_with(|| a.cmp_upper_bounds(b))
759 });
760 }
761 (ForkStrategy::RequiresPython, _) => {
762 forks.sort_by(|a, b| {
766 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
767 });
768 }
769 }
770
771 for new_fork_state in self.forks_to_fork_states(
772 state,
773 &version,
774 forks,
775 requests,
776 &diverging_packages,
777 ) {
778 forked_states.push(new_fork_state?);
779 }
780 continue 'FORK;
781 }
782 ForkedDependencies::RequiresPython(requires_python) => {
783 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
784 && state.env.marker_environment().is_none()
785 {
786 let forks = fork_version_by_python_requirement(
787 &requires_python,
788 &state.python_requirement,
789 &state.env,
790 );
791 if !forks.is_empty() {
792 debug!(
793 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
794 state.python_requirement.target(),
795 &requires_python,
796 next_package,
797 version,
798 forks
799 .iter()
800 .map(ToString::to_string)
801 .collect::<Vec<_>>()
802 .join(", ")
803 );
804
805 let forks = forks
808 .into_iter()
809 .map(|env| VersionFork {
810 env,
811 id: next_id,
812 version: None,
813 })
814 .collect();
815 forked_states
816 .extend(self.version_forks_to_fork_states(state, forks));
817 continue 'FORK;
818 }
819 }
820
821 let versions = state.widen_version_to_gap(
822 &version,
823 &self.index,
824 &self.installed_packages,
825 );
826 state
827 .pubgrub
828 .add_incompatibility(Incompatibility::custom_term(
829 next_id,
830 Term::Positive(versions),
831 UnavailableReason::Version(UnavailableVersion::RequiresPython(
832 requires_python,
833 )),
834 ));
835 }
836 }
837 }
838 }
839 if resolutions.len() > 1 {
840 info!(
841 "Solved your requirements for {} environments",
842 resolutions.len()
843 );
844 }
845 if tracing::enabled!(Level::DEBUG) {
846 for resolution in &resolutions {
847 if let Some(env) = resolution.env.end_user_fork_display() {
848 let packages: FxHashSet<_> = resolution
849 .nodes
850 .iter()
851 .map(|(package, _)| &package.name)
852 .collect();
853 debug!(
854 "Distinct solution for {env} with {} package(s)",
855 packages.len()
856 );
857 }
858 }
859 }
860 for resolution in &resolutions {
861 resolution.trace_resolution();
862 }
863 let resolutions = resolutions
864 .into_iter()
865 .map(|resolution| resolution.finalize(&self.index, &self.git))
866 .collect::<Result<Vec<_>, _>>()?;
867 crate::resolution::from_state(
868 resolutions,
869 self.project.as_ref(),
870 &self.workspace_members,
871 self.requirements.clone(),
872 self.constraints.clone(),
873 self.modifiers.clone(),
874 &self.preferences,
875 &self.hasher,
876 &self.index,
877 self.python_requirement.target().clone(),
878 &self.conflicts,
879 self.selector.resolution_strategy(),
880 self.options.clone(),
881 )
882 }
883
884 fn forks_to_fork_states<'a, 'index: 'a>(
886 &'a self,
887 current_state: ForkState<'index>,
888 version: &'a Version,
889 forks: Vec<Fork>,
890 requests: &'a MetadataRequests,
891 diverging_packages: &'a BTreeSet<PackageName>,
892 ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
893 debug!(
894 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
895 current_state.pubgrub.package_store[current_state.next],
896 version,
897 diverging_packages
898 .iter()
899 .map(ToString::to_string)
900 .join(", "),
901 forks.len(),
902 if forks.len() == 1 { "" } else { "s" }
903 );
904 assert!(forks.len() >= 2);
905 let package = current_state.next;
911 let mut cur_state = Some(current_state);
912 let forks_len = forks.len();
913 forks
914 .into_iter()
915 .enumerate()
916 .map(move |(i, fork)| {
917 let is_last = i == forks_len - 1;
918 let forked_state = cur_state.take().unwrap();
919 if !is_last {
920 cur_state = Some(forked_state.clone());
921 }
922
923 let env = fork.env.clone();
924 (fork, forked_state.with_env(env))
925 })
926 .map(move |(fork, mut forked_state)| {
927 forked_state
929 .visit_package_version_dependencies(
930 package,
931 version,
932 &self.urls,
933 &self.indexes,
934 &fork.dependencies,
935 &self.git,
936 &self.workspace_members,
937 self.selector.resolution_strategy(),
938 )
939 .map_err(|err| {
940 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
941 })?;
942
943 self.visit_dependencies(&fork.dependencies, &forked_state, requests)
945 .map_err(|err| {
946 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
947 })?;
948
949 forked_state.add_package_version_dependencies(
951 package,
952 version,
953 fork.dependencies,
954 &self.index,
955 &self.installed_packages,
956 );
957
958 Ok(forked_state)
959 })
960 }
961
962 #[expect(clippy::unused_self)]
964 fn version_forks_to_fork_states<'index>(
965 &self,
966 current_state: ForkState<'index>,
967 forks: Vec<VersionFork>,
968 ) -> impl Iterator<Item = ForkState<'index>> {
969 let mut cur_state = Some(current_state);
975 let forks_len = forks.len();
976 forks.into_iter().enumerate().map(move |(i, fork)| {
977 let is_last = i == forks_len - 1;
978 let forked_state = cur_state.take().unwrap();
979 if !is_last {
980 cur_state = Some(forked_state.clone());
981 }
982 let continuation = match fork.version {
983 Some(version) => ForkContinuation::UseVersion {
984 package: fork.id,
985 version,
986 },
987 None => ForkContinuation::SelectVersion { package: fork.id },
988 };
989 forked_state
990 .with_env(fork.env)
991 .with_continuation(continuation)
992 })
993 }
994
995 fn visit_dependencies(
997 &self,
998 dependencies: &[PubGrubDependency],
999 state: &ForkState,
1000 requests: &MetadataRequests,
1001 ) -> Result<(), ResolveError> {
1002 for dependency in dependencies {
1003 let PubGrubDependency {
1004 package,
1005 version: _,
1006 parent: _,
1007 source: _,
1008 } = dependency;
1009 let Some(source) = PackageSource::for_prefetch(
1010 package,
1011 &state.fork_urls,
1012 &state.fork_indexes,
1013 &self.urls,
1014 ) else {
1015 continue;
1016 };
1017 self.request_package(package, source, requests)?;
1018 }
1019 Ok(())
1020 }
1021
1022 fn request_package(
1023 &self,
1024 package: &PubGrubPackage,
1025 source: PackageSource<'_>,
1026 requests: &MetadataRequests,
1027 ) -> Result<(), ResolveError> {
1028 let Some(name) = package.name_no_root() else {
1030 return Ok(());
1031 };
1032
1033 match source {
1034 PackageSource::Url(url) => {
1035 if !self.hasher.allows_url(&url.verbatim) {
1037 return Err(ResolveError::UnhashedPackage(name.clone()));
1038 }
1039
1040 let dist = Dist::from_url(name.clone(), url.clone())?;
1042 requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1043 }
1044 PackageSource::Registry(index) => {
1045 requests.enqueue_package(name, index)?;
1046 }
1047 }
1048 Ok(())
1049 }
1050
1051 fn pre_visit<'data>(
1054 packages: impl Iterator<
1055 Item = (
1056 Id<PubGrubPackage>,
1057 &'data PubGrubPackage,
1058 &'data Range<Version>,
1059 ),
1060 >,
1061 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1062 urls: &Urls,
1063 indexes: &Indexes,
1064 python_requirement: &PythonRequirement,
1065 requests: &MetadataRequests,
1066 ) -> Result<(), ResolveError> {
1067 for (id, package, range) in packages {
1070 let PubGrubPackageInner::Package {
1071 name,
1072 kind: PackageNodeKind::Base,
1073 marker: MarkerTree::TRUE,
1074 } = &**package
1075 else {
1076 continue;
1077 };
1078 if urls.any_url(name) {
1081 continue;
1082 }
1083 if indexes.contains_key(name) {
1085 continue;
1086 }
1087 if pre_visited.get(&id) == Some(range) {
1090 continue;
1091 }
1092 pre_visited.insert(id, range.clone());
1093 requests.prefetch(name, range, python_requirement)?;
1094 }
1095 Ok(())
1096 }
1097
1098 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1106 fn choose_version<'index>(
1107 &self,
1108 package: &PubGrubPackage,
1109 id: Id<PubGrubPackage>,
1110 source: PackageSource<'_>,
1111 range: &Range<Version>,
1112 pins: &mut FilePins<'index>,
1113 preferences: &Preferences,
1114 env: &ResolverEnvironment,
1115 python_requirement: &PythonRequirement,
1116 pubgrub: &State<UvDependencyProvider>,
1117 visited: &mut FxHashSet<PackageName>,
1118 requests: &'index MetadataRequests,
1119 ) -> Result<Option<ResolverVersion>, ResolveError> {
1120 match &**package {
1121 PubGrubPackageInner::Root(_) => {
1122 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1123 }
1124
1125 PubGrubPackageInner::Python(_) => {
1126 Ok(None)
1129 }
1130
1131 PubGrubPackageInner::System(_) => {
1132 let Some(version) = range.as_singleton() else {
1135 return Ok(None);
1136 };
1137 Ok(Some(ResolverVersion::Unforked(version.clone())))
1138 }
1139
1140 PubGrubPackageInner::Marker { name, .. }
1141 | PubGrubPackageInner::Extra { name, .. }
1142 | PubGrubPackageInner::Group { name, .. }
1143 | PubGrubPackageInner::Package { name, .. } => match source {
1144 PackageSource::Url(url) => self.choose_version_url(
1145 id,
1146 name,
1147 range,
1148 url,
1149 env,
1150 python_requirement,
1151 pubgrub,
1152 pins,
1153 requests,
1154 ),
1155 PackageSource::Registry(index) => self.choose_version_registry(
1156 package,
1157 id,
1158 name,
1159 index,
1160 range,
1161 preferences,
1162 env,
1163 python_requirement,
1164 pubgrub,
1165 pins,
1166 visited,
1167 requests,
1168 ),
1169 },
1170 }
1171 }
1172
1173 fn choose_version_url<'index>(
1176 &self,
1177 id: Id<PubGrubPackage>,
1178 name: &PackageName,
1179 range: &Range<Version>,
1180 url: &VerbatimParsedUrl,
1181 env: &ResolverEnvironment,
1182 python_requirement: &PythonRequirement,
1183 pubgrub: &State<UvDependencyProvider>,
1184 pins: &mut FilePins<'index>,
1185 requests: &'index MetadataRequests,
1186 ) -> Result<Option<ResolverVersion>, ResolveError> {
1187 debug!(
1188 "Searching for a compatible version of {name} @ `{}` ({range})",
1189 url.verbatim
1190 );
1191
1192 let dist = Dist::from_url(name.clone(), url.clone())?;
1193 let registered = requests.metadata(&dist)?;
1194 let response = registered.wait();
1195
1196 let metadata = match &*response {
1198 MetadataResponse::Found(archive) => &archive.metadata,
1199 MetadataResponse::Unavailable(reason) => {
1200 self.unavailable_packages
1201 .pin()
1202 .insert(name.clone(), reason.into());
1203 return Ok(None);
1204 }
1205 MetadataResponse::Error(dist, err) => {
1208 return Err(ResolveError::Dist(
1209 DistErrorKind::from_requested_dist(dist, &**err),
1210 dist.clone(),
1211 DerivationChain::default(),
1212 err.clone(),
1213 ));
1214 }
1215 };
1216
1217 let version = &metadata.version;
1218
1219 if !range.contains(version) {
1221 return Ok(None);
1222 }
1223
1224 if let Dist::Built(dist) = &dist {
1227 let filename = match &dist {
1228 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1229 BuiltDist::DirectUrl(dist) => &dist.filename,
1230 BuiltDist::GitPath(dist) => &dist.filename,
1231 BuiltDist::Path(dist) => &dist.filename,
1232 };
1233
1234 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1236 {
1237 let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1238 for environment_marker in self.options.artifact_environments.iter().copied() {
1241 if env.included_by_marker(environment_marker)
1243 && env.included_by_marker(
1244 find_environments(id, pubgrub).and(environment_marker),
1245 )
1246 {
1247 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1249 return Ok(Some(ResolverVersion::Unavailable(
1250 version.clone(),
1251 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1252 IncompatibleWheel::MissingPlatform(environment_marker),
1253 )),
1254 )));
1255 }
1256 }
1257 }
1258 }
1259
1260 if !python_requirement.target().matches_wheel_tag(filename) {
1262 return Ok(Some(ResolverVersion::Unavailable(
1263 filename.version.clone(),
1264 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1265 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1266 )),
1267 )));
1268 }
1269 }
1270
1271 if let Some(requires_python) = metadata.requires_python.as_ref() {
1273 if !python_requirement.target().is_contained_by(requires_python) {
1274 let kind = if python_requirement.installed() == python_requirement.target() {
1275 PythonRequirementKind::Installed
1276 } else {
1277 PythonRequirementKind::Target
1278 };
1279 return Ok(Some(ResolverVersion::Unavailable(
1280 version.clone(),
1281 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1282 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1283 )),
1284 )));
1285 }
1286 }
1287
1288 pins.insert_url(name, version, registered);
1289 Ok(Some(ResolverVersion::Unforked(version.clone())))
1290 }
1291
1292 fn choose_version_registry<'index>(
1295 &self,
1296 package: &PubGrubPackage,
1297 id: Id<PubGrubPackage>,
1298 name: &PackageName,
1299 index: Option<&IndexMetadata>,
1300 range: &Range<Version>,
1301 preferences: &Preferences,
1302 env: &ResolverEnvironment,
1303 python_requirement: &PythonRequirement,
1304 pubgrub: &State<UvDependencyProvider>,
1305 pins: &mut FilePins<'index>,
1306 visited: &mut FxHashSet<PackageName>,
1307 requests: &'index MetadataRequests,
1308 ) -> Result<Option<ResolverVersion>, ResolveError> {
1309 let versions_response = requests.request_package(name, index)?.wait();
1311 let index = index.map(IndexMetadata::url);
1312 visited.insert(name.clone());
1313
1314 let version_maps = match *versions_response {
1315 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1316 VersionsResponse::NoIndex => {
1317 self.unavailable_packages
1318 .pin()
1319 .insert(name.clone(), UnavailablePackage::NoIndex);
1320 &[]
1321 }
1322 VersionsResponse::Offline => {
1323 self.unavailable_packages
1324 .pin()
1325 .insert(name.clone(), UnavailablePackage::Offline);
1326 &[]
1327 }
1328 VersionsResponse::NotFound => {
1329 self.unavailable_packages
1330 .pin()
1331 .insert(name.clone(), UnavailablePackage::NotFound);
1332 &[]
1333 }
1334 };
1335
1336 debug!("Searching for a compatible version of {package} ({range})");
1337
1338 let Some(candidate) = self.selector.select(
1340 name,
1341 range,
1342 version_maps,
1343 preferences,
1344 &self.installed_packages,
1345 &self.exclusions,
1346 index,
1347 env,
1348 self.tags.as_ref(),
1349 ) else {
1350 return Ok(None);
1352 };
1353
1354 let dist = match candidate.dist() {
1355 CandidateDist::Compatible(dist) => dist,
1356 CandidateDist::Incompatible {
1357 incompatible_dist: incompatibility,
1358 prioritized_dist: _,
1359 } => {
1360 return Ok(Some(ResolverVersion::Unavailable(
1362 candidate.version().clone(),
1363 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1366 )));
1367 }
1368 };
1369
1370 if let Some((requires_python, incompatibility)) =
1372 Self::check_requires_python(dist, python_requirement)
1373 {
1374 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1375 if env.marker_environment().is_none() {
1376 let forks = fork_version_by_python_requirement(
1377 requires_python,
1378 python_requirement,
1379 env,
1380 );
1381 if !forks.is_empty() {
1382 debug!(
1383 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1384 python_requirement.target(),
1385 requires_python,
1386 name,
1387 candidate.version(),
1388 forks
1389 .iter()
1390 .map(ToString::to_string)
1391 .collect::<Vec<_>>()
1392 .join(", ")
1393 );
1394 let forks = forks
1395 .into_iter()
1396 .map(|env| VersionFork {
1397 env,
1398 id,
1399 version: None,
1400 })
1401 .collect();
1402 return Ok(Some(ResolverVersion::Forked(forks)));
1403 }
1404 }
1405 }
1406
1407 return Ok(Some(ResolverVersion::Unavailable(
1408 candidate.version().clone(),
1409 UnavailableVersion::IncompatibleDist(incompatibility),
1410 )));
1411 }
1412
1413 if env.marker_environment().is_none()
1417 && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1418 && !dist
1419 .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1420 {
1421 return Ok(Some(ResolverVersion::Unavailable(
1422 candidate.version().clone(),
1423 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1424 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1425 )),
1426 )));
1427 }
1428
1429 if let Some(forked) = self.fork_version_registry(
1431 &candidate,
1432 dist,
1433 version_maps,
1434 package,
1435 id,
1436 name,
1437 index,
1438 range,
1439 preferences,
1440 env,
1441 pubgrub,
1442 pins,
1443 requests,
1444 )? {
1445 return Ok(Some(forked));
1446 }
1447
1448 let filename = match dist.for_installation() {
1449 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1450 .filename()
1451 .unwrap_or(Cow::Borrowed("unknown filename")),
1452 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1453 .filename()
1454 .unwrap_or(Cow::Borrowed("unknown filename")),
1455 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1456 };
1457
1458 debug!(
1459 "Selecting: {}=={} [{}] ({})",
1460 name,
1461 candidate.version(),
1462 candidate.choice_kind(),
1463 filename,
1464 );
1465 self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1466
1467 let version = candidate.version().clone();
1468 Ok(Some(ResolverVersion::Unforked(version)))
1469 }
1470
1471 fn fork_version_registry<'index>(
1484 &self,
1485 candidate: &Candidate,
1486 dist: &CompatibleDist,
1487 version_maps: &[VersionMap],
1488 package: &PubGrubPackage,
1489 id: Id<PubGrubPackage>,
1490 name: &PackageName,
1491 index: Option<&IndexUrl>,
1492 range: &Range<Version>,
1493 preferences: &Preferences,
1494 env: &ResolverEnvironment,
1495 pubgrub: &State<UvDependencyProvider>,
1496 pins: &mut FilePins<'index>,
1497 requests: &'index MetadataRequests,
1498 ) -> Result<Option<ResolverVersion>, ResolveError> {
1499 let Some(fork_markers) = env.fork_markers() else {
1501 return Ok(None);
1502 };
1503
1504 let artifact_markers = dist.implied_markers();
1505 if artifact_markers.is_true() {
1506 return Ok(None);
1507 }
1508
1509 for marker in self.options.artifact_environments.iter().copied() {
1512 if env.included_by_marker(marker) {
1514 if !env.included_by_marker(artifact_markers.and(marker))
1516 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1517 {
1518 let coverage = dist.implied_markers().and(fork_markers);
1523 let split = marker.restrict(marker.or(coverage));
1524 let Some((left, right)) = fork_version_by_marker(env, split) else {
1525 return Ok(Some(ResolverVersion::Unavailable(
1526 candidate.version().clone(),
1527 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1528 IncompatibleWheel::MissingPlatform(marker),
1529 )),
1530 )));
1531 };
1532
1533 debug!(
1534 "Forking on required platform `{}` for {}=={} ({})",
1535 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1536 name,
1537 candidate.version(),
1538 [&left, &right]
1539 .iter()
1540 .map(ToString::to_string)
1541 .collect::<Vec<_>>()
1542 .join(", ")
1543 );
1544 let forks = vec![
1545 VersionFork {
1546 env: left,
1547 id,
1548 version: None,
1549 },
1550 VersionFork {
1551 env: right,
1552 id,
1553 version: None,
1554 },
1555 ];
1556 return Ok(Some(ResolverVersion::Forked(forks)));
1557 }
1558 }
1559 }
1560
1561 if !candidate.version().is_local() {
1563 return Ok(None);
1564 }
1565
1566 debug!(
1567 "Looking at local version: {}=={}",
1568 name,
1569 candidate.version()
1570 );
1571
1572 let range = range.clone().intersection(&Range::singleton(
1574 candidate.version().clone().without_local(),
1575 ));
1576
1577 let Some(base_candidate) = self.selector.select(
1578 name,
1579 &range,
1580 version_maps,
1581 preferences,
1582 &self.installed_packages,
1583 &self.exclusions,
1584 index,
1585 env,
1586 self.tags.as_ref(),
1587 ) else {
1588 return Ok(None);
1589 };
1590 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1591 return Ok(None);
1592 };
1593
1594 let mut remainder = {
1596 let mut remainder = base_dist.implied_markers();
1597 remainder = remainder.and(artifact_markers.negate());
1598 remainder
1599 };
1600 if remainder.is_false() {
1601 return Ok(None);
1602 }
1603
1604 if !env.included_by_marker(remainder) {
1608 return Ok(None);
1609 }
1610
1611 if !env.included_by_marker(artifact_markers) {
1614 let filename = match dist.for_installation() {
1615 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1616 .filename()
1617 .unwrap_or(Cow::Borrowed("unknown filename")),
1618 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1619 .filename()
1620 .unwrap_or(Cow::Borrowed("unknown filename")),
1621 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1622 };
1623
1624 debug!(
1625 "Preferring non-local candidate: {}=={} [{}] ({})",
1626 name,
1627 base_candidate.version(),
1628 base_candidate.choice_kind(),
1629 filename,
1630 );
1631 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1632
1633 return Ok(Some(ResolverVersion::Unforked(
1634 base_candidate.version().clone(),
1635 )));
1636 }
1637
1638 for value in [
1647 arcstr::literal!("darwin"),
1648 arcstr::literal!("linux"),
1649 arcstr::literal!("win32"),
1650 ] {
1651 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1652 key: MarkerValueString::SysPlatform,
1653 operator: MarkerOperator::Equal,
1654 value,
1655 });
1656 if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1657 remainder = remainder.or(sys_platform);
1658 }
1659 }
1660
1661 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1663 return Ok(None);
1664 };
1665
1666 debug!(
1667 "Forking platform for {}=={} ({})",
1668 name,
1669 candidate.version(),
1670 [&base_env, &local_env]
1671 .iter()
1672 .map(ToString::to_string)
1673 .collect::<Vec<_>>()
1674 .join(", ")
1675 );
1676 self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1677 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1678
1679 let forks = vec![
1680 VersionFork {
1681 env: base_env.clone(),
1682 id,
1683 version: Some(base_candidate.version().clone()),
1684 },
1685 VersionFork {
1686 env: local_env.clone(),
1687 id,
1688 version: Some(candidate.version().clone()),
1689 },
1690 ];
1691 Ok(Some(ResolverVersion::Forked(forks)))
1692 }
1693
1694 fn visit_candidate<'index>(
1696 &self,
1697 candidate: &Candidate,
1698 dist: &CompatibleDist,
1699 package: &PubGrubPackage,
1700 name: &PackageName,
1701 pins: &mut FilePins<'index>,
1702 requests: &'index MetadataRequests,
1703 ) -> Result<(), ResolveError> {
1704 let index = match dist {
1705 CompatibleDist::InstalledDist(_) => None,
1706 CompatibleDist::SourceDist { sdist, .. }
1707 | CompatibleDist::IncompatibleWheel { sdist, .. } => Some(&sdist.index),
1708 CompatibleDist::CompatibleWheel { wheel, .. } => Some(&wheel.index),
1709 };
1710 let variants_json = if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1711 && let Some(index) = index
1712 && let Some(variants_json) = candidate.prioritized().and_then(|prioritized| {
1713 prioritized
1714 .variants_json()
1715 .iter()
1716 .find(|variants_json| &variants_json.index == index)
1717 }) {
1718 Some(requests.request_variants_json(variants_json)?)
1719 } else {
1720 None
1721 };
1722
1723 let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1724 && self.dependency_mode.is_transitive()
1725 {
1726 Some(|| {
1727 requests.request_metadata(
1728 MetadataRequest::Resolved(dist.for_resolution()),
1729 |request| {
1730 if name != request.name() {
1731 return Err(ResolveError::MismatchedPackageName {
1732 request: "distribution",
1733 expected: name.clone(),
1734 actual: request.name().clone(),
1735 });
1736 }
1737 if !self
1739 .hasher
1740 .allows_package(candidate.name(), candidate.version())
1741 {
1742 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1743 }
1744 Ok(())
1745 },
1746 )
1747 })
1748 } else {
1749 None
1750 };
1751 pins.insert(candidate, dist, variants_json, request)
1752 }
1753
1754 fn check_requires_python<'dist>(
1757 dist: &'dist CompatibleDist,
1758 python_requirement: &PythonRequirement,
1759 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1760 let requires_python = dist.requires_python()?;
1761 if python_requirement.target().is_contained_by(requires_python) {
1762 None
1763 } else {
1764 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1765 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1766 requires_python.clone(),
1767 if python_requirement.installed() == python_requirement.target() {
1768 PythonRequirementKind::Installed
1769 } else {
1770 PythonRequirementKind::Target
1771 },
1772 ))
1773 } else {
1774 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1775 requires_python.clone(),
1776 if python_requirement.installed() == python_requirement.target() {
1777 PythonRequirementKind::Installed
1778 } else {
1779 PythonRequirementKind::Target
1780 },
1781 ))
1782 };
1783 Some((requires_python, incompatibility))
1784 }
1785 }
1786
1787 fn get_dependencies_forking(
1789 &self,
1790 id: Id<PubGrubPackage>,
1791 package: &PubGrubPackage,
1792 version: &Version,
1793 pins: &FilePins,
1794 env: &ResolverEnvironment,
1795 python_requirement: &PythonRequirement,
1796 pubgrub: &State<UvDependencyProvider>,
1797 ) -> Result<ForkedDependencies, ResolveError> {
1798 let dependencies =
1799 self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1800 if env.marker_environment().is_some() {
1801 Ok(ForkedDependencies::from_dependencies_platform_specific(
1802 dependencies,
1803 ))
1804 } else {
1805 Ok(ForkedDependencies::from_dependencies_universal(
1806 dependencies,
1807 env,
1808 python_requirement,
1809 &self.conflicts,
1810 ))
1811 }
1812 }
1813
1814 #[instrument(skip_all, fields(%package, %version))]
1816 fn get_dependencies(
1817 &self,
1818 id: Id<PubGrubPackage>,
1819 package: &PubGrubPackage,
1820 version: &Version,
1821 pins: &FilePins,
1822 env: &ResolverEnvironment,
1823 python_requirement: &PythonRequirement,
1824 pubgrub: &State<UvDependencyProvider>,
1825 ) -> Result<Dependencies, ResolveError> {
1826 let expander =
1827 RequirementExpander::new(&self.constraints, &self.modifiers, env, python_requirement);
1828 let dependencies = match &**package {
1829 PubGrubPackageInner::Root(_) => {
1830 let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1831
1832 PubGrubDependency::from_requirements(
1833 &self.conflicts,
1834 requirements,
1835 None,
1836 Some(package),
1837 )
1838 }
1839
1840 PubGrubPackageInner::Package {
1841 name,
1842 kind,
1843 marker: _,
1844 } => {
1845 if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1846 && let Some(variants_json) = pins.variants_json(name, version)
1847 && let Err(reason) = &*variants_json.wait()
1848 {
1849 return Ok(Dependencies::Unavailable(
1850 self.record_unavailable_metadata(name, version, reason),
1851 ));
1852 }
1853
1854 if self.dependency_mode.is_direct() {
1856 return Ok(Dependencies::Unforkable(Vec::default()));
1857 }
1858
1859 let registered = pins
1860 .metadata(name, version)
1861 .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1862
1863 if self.dependency_mode.is_transitive()
1865 && self.unavailable_packages.pin().contains_key(name)
1866 && self.installed_packages.get_packages(name).is_empty()
1867 {
1868 debug_assert!(
1869 false,
1870 "Dependencies were requested for a package that is not available"
1871 );
1872 return Err(ResolveError::PackageUnavailable(name.clone()));
1873 }
1874
1875 let response = registered.wait();
1877
1878 let metadata = match &*response {
1879 MetadataResponse::Found(archive) => &archive.metadata,
1880 MetadataResponse::Unavailable(reason) => {
1881 return Ok(Dependencies::Unavailable(
1882 self.record_unavailable_metadata(name, version, reason),
1883 ));
1884 }
1885 MetadataResponse::Error(dist, err) => {
1886 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1887 .unwrap_or_default();
1888 return Err(ResolveError::Dist(
1889 DistErrorKind::from_requested_dist(dist, &**err),
1890 dist.clone(),
1891 chain,
1892 err.clone(),
1893 ));
1894 }
1895 };
1896
1897 if let Some(requires_python) = &metadata.requires_python {
1900 if !python_requirement.target().is_contained_by(requires_python) {
1901 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1902 }
1903 }
1904
1905 let system_dependencies = self
1907 .options
1908 .torch_backend
1909 .as_ref()
1910 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1911 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1912 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1913 .map(IndexUrl::url)
1914 .and_then(SystemDependency::from_index)
1915 .into_iter()
1916 .inspect(|system_dependency| {
1917 debug!(
1918 "Adding system dependency `{}` for `{package}@{version}`",
1919 system_dependency
1920 );
1921 })
1922 .map(PubGrubDependency::from);
1923
1924 let (requirements, context) = match kind {
1925 PackageNodeKind::Group(group) => (
1926 metadata
1927 .dependency_groups
1928 .get(group)
1929 .map_or(&[][..], AsRef::as_ref),
1930 RequirementContext::Group { name, version },
1931 ),
1932 PackageNodeKind::Extra(extra) => (
1933 metadata.requires_dist.as_ref(),
1934 RequirementContext::Extra {
1935 name,
1936 version,
1937 extra,
1938 },
1939 ),
1940 PackageNodeKind::Base => (
1941 metadata.requires_dist.as_ref(),
1942 RequirementContext::Package { name, version },
1943 ),
1944 };
1945 let requirements = expander.expand(requirements, context);
1946
1947 PubGrubDependency::from_requirements(
1948 &self.conflicts,
1949 requirements,
1950 kind.group(),
1951 Some(package),
1952 )
1953 .map(|mut dependencies| {
1954 dependencies.extend(system_dependencies);
1955 dependencies
1956 })
1957 }
1958
1959 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1960
1961 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1962
1963 PubGrubPackageInner::Marker { name, marker } => {
1965 return Ok(Dependencies::Unforkable(
1966 [MarkerTree::TRUE, *marker]
1967 .into_iter()
1968 .map(move |marker| PubGrubDependency {
1969 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1970 name: name.clone(),
1971 kind: PackageNodeKind::Base,
1972 marker,
1973 }),
1974 version: Range::singleton(version.clone()),
1975 parent: None,
1976 source: DependencySource::Unspecified,
1977 })
1978 .collect(),
1979 ));
1980 }
1981
1982 PubGrubPackageInner::Extra {
1984 name,
1985 extra,
1986 marker,
1987 } => {
1988 return Ok(Dependencies::Unforkable(
1989 [MarkerTree::TRUE, *marker]
1990 .into_iter()
1991 .dedup()
1992 .flat_map(move |marker| {
1993 [None, Some(extra)]
1994 .into_iter()
1995 .map(move |extra| PubGrubDependency {
1996 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1997 name: name.clone(),
1998 kind: extra
1999 .cloned()
2000 .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
2001 marker,
2002 }),
2003 version: Range::singleton(version.clone()),
2004 parent: None,
2005 source: DependencySource::Unspecified,
2006 })
2007 })
2008 .collect(),
2009 ));
2010 }
2011
2012 PubGrubPackageInner::Group {
2014 name,
2015 group,
2016 marker,
2017 } => {
2018 return Ok(Dependencies::Unforkable(
2019 [MarkerTree::TRUE, *marker]
2020 .into_iter()
2021 .dedup()
2022 .map(|marker| PubGrubDependency {
2023 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2024 name: name.clone(),
2025 kind: PackageNodeKind::Group(group.clone()),
2026 marker,
2027 }),
2028 version: Range::singleton(version.clone()),
2029 parent: None,
2030 source: DependencySource::Unspecified,
2031 })
2032 .collect(),
2033 ));
2034 }
2035 };
2036 Ok(match dependencies {
2037 Ok(dependencies) => Dependencies::Available(dependencies),
2038 Err(requirement) => {
2039 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2040 }
2041 })
2042 }
2043
2044 fn record_unavailable_metadata(
2045 &self,
2046 name: &PackageName,
2047 version: &Version,
2048 reason: &MetadataUnavailable,
2049 ) -> UnavailableVersion {
2050 let unavailable_version = UnavailableVersion::from(reason);
2051 let message = unavailable_version.singular_message();
2052 if let Some(error) = reason.source() {
2053 warn!("{name} {message}: {error}");
2054 } else {
2055 warn!("{name} {message}");
2056 }
2057 let incomplete_packages = self.incomplete_packages.pin();
2058 let versions = incomplete_packages.get_or_insert(
2059 name.clone(),
2060 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
2061 );
2062 versions.pin().insert(version.clone(), reason.clone());
2063 unavailable_version
2064 }
2065
2066 async fn fetch<Provider: ResolverProvider>(
2068 self: Arc<Self>,
2069 provider: Arc<Provider>,
2070 request_stream: Receiver<Request>,
2071 ) -> Result<(), ResolveError> {
2072 let mut response_stream = ReceiverStream::new(request_stream)
2073 .map(|request| self.process_request(request, &*provider).boxed_local())
2074 .buffer_unordered(usize::MAX);
2078
2079 while let Some(response) = response_stream.next().await {
2080 match response? {
2081 Some(Response::Package(name, index, version_map)) => {
2082 trace!("Received package metadata for: {name}");
2083 if let Some(index) = index {
2084 self.index
2085 .explicit()
2086 .done((name, index), Arc::new(version_map));
2087 } else {
2088 self.index.implicit().done(name, Arc::new(version_map));
2089 }
2090 }
2091 Some(Response::Installed { dist, metadata }) => {
2092 trace!("Received installed distribution metadata for: {dist}");
2093 self.index
2094 .distributions()
2095 .done(dist.distribution_id(), Arc::new(metadata));
2096 }
2097 Some(Response::Dist { dist, metadata }) => {
2098 let dist_kind = match dist {
2099 Dist::Built(_) => "built",
2100 Dist::Source(_) => "source",
2101 };
2102 trace!("Received {dist_kind} distribution metadata for: {dist}");
2103 if let MetadataResponse::Unavailable(reason) = &metadata {
2104 let message = UnavailableVersion::from(reason).singular_message();
2105 if let Some(err) = reason.source() {
2106 warn!("`{dist}` {message}: {err}");
2108 } else {
2109 warn!("`{dist}` {message}");
2110 }
2111 }
2112 self.index
2113 .distributions()
2114 .done(dist.distribution_id(), Arc::new(metadata));
2115 }
2116 Some(Response::VariantsJson {
2117 variants_json,
2118 response,
2119 }) => {
2120 trace!("Received variant metadata for: {}", variants_json.filename);
2121 self.index
2122 .variants_json()
2123 .done(variants_json, Arc::new(response));
2124 }
2125 None => {}
2126 }
2127 }
2128
2129 Ok::<(), ResolveError>(())
2130 }
2131
2132 #[instrument(skip_all, fields(%request))]
2133 async fn process_request<Provider: ResolverProvider>(
2134 &self,
2135 request: Request,
2136 provider: &Provider,
2137 ) -> Result<Option<Response>, ResolveError> {
2138 match request {
2139 Request::VariantsJson(variants_json) => {
2140 let response = provider
2141 .fetch_variants_json(
2142 &variants_json.filename.name,
2143 &variants_json.file,
2144 &variants_json.index,
2145 )
2146 .boxed_local()
2147 .await
2148 .map_err(ResolveError::Client)?;
2149 let response = match response {
2150 VariantsJsonResponse::Found(contents) => {
2151 VariantsJsonContent::from_slice(&contents).map_err(|source| {
2152 MetadataUnavailable::InvalidVariantMetadata(Arc::new(
2153 VariantsJsonMetadataError {
2154 filename: variants_json.filename.clone(),
2155 source,
2156 },
2157 ))
2158 })
2159 }
2160 VariantsJsonResponse::Unavailable(reason) => Err(reason),
2161 };
2162 Ok(Some(Response::VariantsJson {
2163 variants_json,
2164 response,
2165 }))
2166 }
2167 Request::Package(package_name, index) => {
2169 let package_versions = provider
2170 .get_package_versions(&package_name, index.as_ref())
2171 .boxed_local()
2172 .await
2173 .map_err(ResolveError::Client)?;
2174
2175 Ok(Some(Response::Package(
2176 package_name,
2177 index.map(IndexMetadata::into_url),
2178 package_versions,
2179 )))
2180 }
2181
2182 Request::Dist(dist) => {
2184 if let Some(version) = dist.version() {
2185 if let Some(index) = dist.index() {
2186 let versions_response = self.index.implicit().get(dist.name());
2188 if let Some(VersionsResponse::Found(version_maps)) =
2189 versions_response.as_deref()
2190 {
2191 for version_map in version_maps {
2192 if version_map.index() == Some(index) {
2193 let Some(metadata) = version_map.get_metadata(version) else {
2194 continue;
2195 };
2196 debug!("Found registry-provided metadata for: {dist}");
2197 return Ok(Some(Response::Dist {
2198 dist,
2199 metadata: MetadataResponse::Found(
2200 ArchiveMetadata::from_metadata23(metadata),
2201 ),
2202 }));
2203 }
2204 }
2205 }
2206
2207 let versions_response = self
2209 .index
2210 .explicit()
2211 .get(&(dist.name().clone(), index.clone()));
2212 if let Some(VersionsResponse::Found(version_maps)) =
2213 versions_response.as_deref()
2214 {
2215 for version_map in version_maps {
2216 let Some(metadata) = version_map.get_metadata(version) else {
2217 continue;
2218 };
2219 debug!("Found registry-provided metadata for: {dist}");
2220 return Ok(Some(Response::Dist {
2221 dist,
2222 metadata: MetadataResponse::Found(
2223 ArchiveMetadata::from_metadata23(metadata),
2224 ),
2225 }));
2226 }
2227 }
2228 }
2229 }
2230
2231 let metadata = provider
2232 .get_or_build_wheel_metadata(&dist)
2233 .boxed_local()
2234 .await?;
2235
2236 if let MetadataResponse::Found(metadata) = &metadata {
2237 if &metadata.metadata.name != dist.name() {
2238 return Err(ResolveError::MismatchedPackageName {
2239 request: "distribution metadata",
2240 expected: dist.name().clone(),
2241 actual: metadata.metadata.name.clone(),
2242 });
2243 }
2244 }
2245
2246 Ok(Some(Response::Dist { dist, metadata }))
2247 }
2248
2249 Request::Installed(dist) => {
2250 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2251
2252 if let MetadataResponse::Found(metadata) = &metadata {
2253 if &metadata.metadata.name != dist.name() {
2254 return Err(ResolveError::MismatchedPackageName {
2255 request: "installed metadata",
2256 expected: dist.name().clone(),
2257 actual: metadata.metadata.name.clone(),
2258 });
2259 }
2260 }
2261
2262 Ok(Some(Response::Installed { dist, metadata }))
2263 }
2264
2265 Request::Prefetch(package_name, range, python_requirement) => {
2267 let versions_response = self
2269 .index
2270 .implicit()
2271 .get_registered(package_name.clone())
2272 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2273 .wait()
2274 .await;
2275
2276 let version_map = match *versions_response {
2277 VersionsResponse::Found(ref version_map) => version_map,
2278 VersionsResponse::NoIndex => {
2280 self.unavailable_packages
2281 .pin()
2282 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2283
2284 return Ok(None);
2285 }
2286 VersionsResponse::Offline => {
2287 self.unavailable_packages
2288 .pin()
2289 .insert(package_name.clone(), UnavailablePackage::Offline);
2290
2291 return Ok(None);
2292 }
2293 VersionsResponse::NotFound => {
2294 self.unavailable_packages
2295 .pin()
2296 .insert(package_name.clone(), UnavailablePackage::NotFound);
2297
2298 return Ok(None);
2299 }
2300 };
2301
2302 let env = ResolverEnvironment::universal(vec![]);
2304
2305 let Some(candidate) = self.selector.select(
2308 &package_name,
2309 &range,
2310 version_map,
2311 &self.preferences,
2312 &self.installed_packages,
2313 &self.exclusions,
2314 None,
2315 &env,
2316 self.tags.as_ref(),
2317 ) else {
2318 return Ok(None);
2319 };
2320
2321 let Some(dist) = candidate.compatible() else {
2323 return Ok(None);
2324 };
2325
2326 for version_map in version_map {
2328 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2329 let dist = dist.for_resolution();
2330 if version_map.index() == dist.index() {
2331 debug!("Found registry-provided metadata for: {dist}");
2332
2333 let metadata =
2334 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2335
2336 let dist = dist.to_owned();
2337 if &package_name != dist.name() {
2338 return Err(ResolveError::MismatchedPackageName {
2339 request: "distribution",
2340 expected: package_name,
2341 actual: dist.name().clone(),
2342 });
2343 }
2344
2345 let response = match dist {
2346 ResolvedDist::Installable { dist, .. } => Response::Dist {
2347 dist: (*dist).clone(),
2348 metadata,
2349 },
2350 ResolvedDist::Installed { dist } => Response::Installed {
2351 dist: (*dist).clone(),
2352 metadata,
2353 },
2354 };
2355
2356 return Ok(Some(response));
2357 }
2358 }
2359 }
2360
2361 if dist.wheel().is_none() {
2365 if !self.selector.use_highest_version(&package_name, &env) {
2366 if let Some((lower, _)) = range.iter().next() {
2367 if lower == Bound::Unbounded {
2368 debug!(
2369 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2370 );
2371 return Ok(None);
2372 }
2373 }
2374 }
2375 }
2376
2377 if let Some(requires_python) = dist.requires_python() {
2379 if !python_requirement.target().is_contained_by(requires_python) {
2380 return Ok(None);
2381 }
2382 }
2383
2384 if !self
2386 .hasher
2387 .allows_package(candidate.name(), candidate.version())
2388 {
2389 return Ok(None);
2390 }
2391
2392 let dist = dist.for_resolution();
2394 if self.index.distributions().register(dist.distribution_id()) {
2395 let dist = dist.to_owned();
2396 if &package_name != dist.name() {
2397 return Err(ResolveError::MismatchedPackageName {
2398 request: "distribution",
2399 expected: package_name,
2400 actual: dist.name().clone(),
2401 });
2402 }
2403
2404 let response = match dist {
2405 ResolvedDist::Installable { dist, .. } => {
2406 let metadata = provider
2407 .get_or_build_wheel_metadata(&dist)
2408 .boxed_local()
2409 .await?;
2410
2411 Response::Dist {
2412 dist: (*dist).clone(),
2413 metadata,
2414 }
2415 }
2416 ResolvedDist::Installed { dist } => {
2417 let metadata =
2418 provider.get_installed_metadata(&dist).boxed_local().await?;
2419
2420 Response::Installed {
2421 dist: (*dist).clone(),
2422 metadata,
2423 }
2424 }
2425 };
2426
2427 Ok(Some(response))
2428 } else {
2429 Ok(None)
2430 }
2431 }
2432 }
2433 }
2434
2435 fn convert_no_solution_err(
2436 &self,
2437 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2438 fork_urls: ForkUrls,
2439 fork_indexes: ForkIndexes,
2440 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2441 env: ResolverEnvironment,
2442 current_environment: MarkerEnvironment,
2443 visited: &FxHashSet<PackageName>,
2444 ) -> ResolveError {
2445 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2446 err,
2447 ));
2448 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2449
2450 let mut unavailable_packages = FxHashMap::default();
2451 for package in derivation_tree_packages(&err) {
2452 if let PubGrubPackageInner::Package { name, .. } = &**package {
2453 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2454 unavailable_packages.insert(name.clone(), reason.clone());
2455 }
2456 }
2457 }
2458
2459 let mut incomplete_packages = FxHashMap::default();
2460 let incomplete_packages_cache = self.incomplete_packages.pin();
2461 for package in derivation_tree_packages(&err) {
2462 if let PubGrubPackageInner::Package { name, .. } = &**package
2463 && let Some(versions) = incomplete_packages_cache.get(name)
2464 {
2465 for (version, reason) in &versions.pin() {
2466 incomplete_packages
2467 .entry(name.clone())
2468 .or_insert_with(BTreeMap::default)
2469 .insert(version.clone(), reason.clone());
2470 }
2471 }
2472 }
2473
2474 let mut available_indexes = FxHashMap::default();
2475 let mut included_versions = FxHashMap::default();
2476 let mut available_versions = FxHashMap::default();
2477
2478 let available_version_cutoff: Option<jiff::Timestamp> =
2479 std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2480 .ok()
2481 .and_then(|s| s.parse().ok());
2482
2483 for package in derivation_tree_packages(&err) {
2484 let Some(name) = package.name() else { continue };
2485 if !visited.contains(name) {
2486 continue;
2491 }
2492 let versions_response = if let Some(index) = fork_indexes.get(name) {
2493 self.index
2494 .explicit()
2495 .get(&(name.clone(), index.url().clone()))
2496 } else {
2497 self.index.implicit().get(name)
2498 };
2499 if let Some(response) = versions_response {
2500 if let VersionsResponse::Found(ref version_maps) = *response {
2501 for version_map in version_maps {
2503 let package_included_versions = included_versions
2504 .entry(name.clone())
2505 .or_insert_with(BTreeSet::new);
2506 let package_available_versions = available_versions
2507 .entry(name.clone())
2508 .or_insert_with(BTreeSet::new);
2509
2510 for (version, dists) in version_map.iter(&Ranges::full()) {
2511 let excluded_from_included = || {
2516 let Some(included_version_cutoff) =
2517 version_map.included_version_cutoff()
2518 else {
2519 return false;
2520 };
2521 let Some(prioritized_dist) = dists.prioritized_dist() else {
2522 return true;
2523 };
2524 prioritized_dist.files().all(|file| {
2525 file.upload_time_utc_ms.is_none_or(|upload_time| {
2526 upload_time >= included_version_cutoff.as_millisecond()
2527 })
2528 })
2529 };
2530
2531 if !excluded_from_included() {
2532 package_included_versions.insert(version.clone());
2533 }
2534
2535 let excluded_from_available = || {
2541 let Some(ref exclude_newer) = available_version_cutoff else {
2542 return false;
2543 };
2544 let Some(prioritized_dist) = dists.prioritized_dist() else {
2545 return false;
2546 };
2547 prioritized_dist.files().all(|file| {
2548 file.upload_time_utc_ms.is_some_and(|upload_time| {
2549 upload_time >= exclude_newer.as_millisecond()
2550 })
2551 })
2552 };
2553
2554 if !excluded_from_available() {
2555 package_available_versions.insert(version.clone());
2556 }
2557 }
2558 }
2559
2560 available_indexes
2562 .entry(name.clone())
2563 .or_insert(BTreeSet::new())
2564 .extend(
2565 version_maps
2566 .iter()
2567 .filter_map(|version_map| version_map.index().cloned()),
2568 );
2569 }
2570 }
2571 }
2572
2573 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2574 err,
2575 self.index.clone(),
2576 included_versions,
2577 available_versions,
2578 available_indexes,
2579 self.selector.clone(),
2580 self.python_requirement.clone(),
2581 self.locations.clone(),
2582 self.capabilities.clone(),
2583 unavailable_packages,
2584 incomplete_packages,
2585 fork_urls,
2586 fork_indexes,
2587 env,
2588 current_environment,
2589 self.tags.clone(),
2590 self.workspace_members.clone(),
2591 self.options.clone(),
2592 )))
2593 }
2594
2595 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2596 if let Some(reporter) = self.reporter.as_ref() {
2597 match &**package {
2598 PubGrubPackageInner::Root(_) => {}
2599 PubGrubPackageInner::Python(_) => {}
2600 PubGrubPackageInner::System(_) => {}
2601 PubGrubPackageInner::Marker { .. } => {}
2602 PubGrubPackageInner::Extra { .. } => {}
2603 PubGrubPackageInner::Group { .. } => {}
2604 PubGrubPackageInner::Package { name, .. } => {
2605 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2606 }
2607 }
2608 }
2609 }
2610
2611 fn on_complete(&self) {
2612 if let Some(reporter) = self.reporter.as_ref() {
2613 reporter.on_complete();
2614 }
2615 }
2616}
2617
2618#[derive(Clone, Default)]
2621struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2622
2623impl KnownVersions {
2624 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2637 &'a mut self,
2638 index: &InMemoryIndex,
2639 installed_packages: &InstalledPackages,
2640 source: PackageSource<'_>,
2641 package: &PubGrubPackage,
2642 ) -> Option<&'a [Version]> {
2643 let name = package.name_no_root()?;
2644 let PackageSource::Registry(index_metadata) = source else {
2646 return None;
2647 };
2648 if !self.0.contains_key(name) {
2649 let response = if let Some(index_metadata) = index_metadata {
2650 index
2651 .explicit()
2652 .get(&(name.clone(), index_metadata.url().clone()))?
2653 } else {
2654 index.implicit().get(name)?
2655 };
2656 let VersionsResponse::Found(ref version_maps) = *response else {
2657 return None;
2658 };
2659 let mut versions: Vec<Version> = version_maps
2660 .iter()
2661 .flat_map(|version_map| version_map.included_versions().cloned())
2662 .chain(
2663 installed_packages
2664 .get_packages(name)
2665 .iter()
2666 .map(|dist| dist.version().clone()),
2667 )
2668 .collect();
2669 versions.sort_unstable();
2670 versions.dedup();
2671 self.0.insert(name.clone(), versions.into());
2672 }
2673 Some(&self.0[name][..])
2674 }
2675}
2676
2677#[derive(Clone, Default)]
2679enum ForkContinuation {
2680 #[default]
2682 Propagate,
2683 SelectVersion { package: Id<PubGrubPackage> },
2685 UseVersion {
2687 package: Id<PubGrubPackage>,
2688 version: Version,
2689 },
2690}
2691
2692#[derive(Clone)]
2694pub(crate) struct ForkState<'index> {
2695 pubgrub: State<UvDependencyProvider>,
2703 continuation: ForkContinuation,
2705 next: Id<PubGrubPackage>,
2707 pins: FilePins<'index>,
2716 fork_urls: ForkUrls,
2722 fork_indexes: ForkIndexes,
2727 priorities: PubGrubPriorities,
2733 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2736 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2738 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2740 known_versions: KnownVersions,
2744 env: ResolverEnvironment,
2759 python_requirement: PythonRequirement,
2772 conflict_tracker: ConflictTracker,
2773 prefetcher: BatchPrefetcher,
2777}
2778
2779impl<'index> ForkState<'index> {
2780 fn new(
2781 pubgrub: State<UvDependencyProvider>,
2782 env: ResolverEnvironment,
2783 python_requirement: PythonRequirement,
2784 prefetcher: BatchPrefetcher,
2785 ) -> Self {
2786 Self {
2787 continuation: ForkContinuation::Propagate,
2788 next: pubgrub.root_package,
2789 pubgrub,
2790 pins: FilePins::default(),
2791 fork_urls: ForkUrls::default(),
2792 fork_indexes: ForkIndexes::default(),
2793 priorities: PubGrubPriorities::default(),
2794 added_dependencies: FxHashMap::default(),
2795 pre_visited: FxHashMap::default(),
2796 selected_versions: FxHashMap::default(),
2797 known_versions: KnownVersions::default(),
2798 env,
2799 python_requirement,
2800 conflict_tracker: ConflictTracker::default(),
2801 prefetcher,
2802 }
2803 }
2804
2805 fn visit_package_version_dependencies(
2808 &mut self,
2809 for_package: Id<PubGrubPackage>,
2810 for_version: &Version,
2811 urls: &Urls,
2812 indexes: &Indexes,
2813 dependencies: &[PubGrubDependency],
2814 git: &GitResolver,
2815 workspace_members: &BTreeSet<PackageName>,
2816 resolution_strategy: &ResolutionStrategy,
2817 ) -> Result<(), ResolveError> {
2818 for dependency in dependencies {
2819 let PubGrubDependency {
2820 package,
2821 version,
2822 parent: _,
2823 source,
2824 } = dependency;
2825
2826 let mut has_url = false;
2827 if let Some(name) = package.name() {
2828 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2833 self.fork_urls.insert(name, url, &self.env)?;
2834 has_url = true;
2835 }
2836
2837 if let Some(index) = source.explicit_index() {
2838 self.fork_indexes.insert(name, index, &self.env)?;
2839 }
2840
2841 for index in indexes.get(name, &self.env) {
2843 self.fork_indexes.insert(name, index, &self.env)?;
2844 }
2845 }
2846
2847 if let Some(name) = self.pubgrub.package_store[for_package]
2848 .name_no_root()
2849 .filter(|name| !workspace_members.contains(name))
2850 {
2851 debug!(
2852 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2853 );
2854 } else {
2855 debug!("Adding direct dependency: {package}{version}");
2857
2858 let missing_lower_bound = version
2860 .bounding_range()
2861 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2862 let strategy_lowest = matches!(
2863 resolution_strategy,
2864 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2865 );
2866
2867 if !has_url && missing_lower_bound && strategy_lowest {
2868 let name = package.name_no_root().unwrap();
2869 let bound_on_other_package = dependencies.iter().any(|other| {
2876 Some(name) == other.package.name()
2877 && !other
2878 .version
2879 .bounding_range()
2880 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2881 });
2882
2883 if !bound_on_other_package {
2884 warn_user_once!(
2885 "The direct dependency `{name}` is unpinned. \
2886 Consider setting a lower bound when using `--resolution lowest` \
2887 or `--resolution lowest-direct` to avoid using outdated versions.",
2888 );
2889 }
2890 }
2891 }
2892
2893 self.priorities.insert(package, version, &self.fork_urls);
2895 if let Some(base_package) = package.base_package() {
2898 self.priorities
2899 .insert(&base_package, version, &self.fork_urls);
2900 }
2901 }
2902
2903 Ok(())
2904 }
2905
2906 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2912 &mut self,
2913 for_package: Id<PubGrubPackage>,
2914 for_version: &Version,
2915 dependencies: Vec<PubGrubDependency>,
2916 index: &InMemoryIndex,
2917 installed_packages: &InstalledPackages,
2918 ) {
2919 for dependency in &dependencies {
2920 let PubGrubDependency {
2921 package,
2922 version,
2923 parent: _,
2924 source: _,
2925 } = dependency;
2926
2927 let Some(base_package) = package.base_package() else {
2928 continue;
2929 };
2930
2931 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2932 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2933 self.pubgrub.add_proxy_package_incompatibility(
2934 proxy_package,
2935 base_package_id,
2936 version.clone(),
2937 );
2938 }
2939
2940 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2943 let conflict = self.pubgrub.add_package_version_dependencies(
2944 self.next,
2945 for_version.clone(),
2946 versions,
2947 dependencies.into_iter().map(|dependency| {
2948 let PubGrubDependency {
2949 package,
2950 version,
2951 parent: _,
2952 source: _,
2953 } = dependency;
2954 (package, version)
2955 }),
2956 );
2957
2958 if let Some(incompatibility) = conflict {
2961 self.record_conflict(for_package, Some(for_version), incompatibility);
2962 }
2963 }
2964
2965 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2967 &mut self,
2968 version: &Version,
2969 index: &InMemoryIndex,
2970 installed_packages: &InstalledPackages,
2971 ) -> Range<Version> {
2972 widen_to_gap(
2973 version,
2974 self.known_versions.get_or_update(
2975 index,
2976 installed_packages,
2977 PackageSource::from_fork(
2978 &self.pubgrub.package_store[self.next],
2979 &self.fork_urls,
2980 &self.fork_indexes,
2981 ),
2982 &self.pubgrub.package_store[self.next],
2983 ),
2984 )
2985 }
2986
2987 fn record_conflict(
2988 &mut self,
2989 affected: Id<PubGrubPackage>,
2990 version: Option<&Version>,
2991 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2992 ) {
2993 let mut culprit_is_real = false;
2994 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2995 if incompatible == affected {
2996 continue;
2997 }
2998 if self.pubgrub.package_store[affected].name()
2999 == self.pubgrub.package_store[incompatible].name()
3000 {
3001 continue;
3004 }
3005 culprit_is_real = true;
3006 let culprit_count = self
3007 .conflict_tracker
3008 .culprit
3009 .entry(incompatible)
3010 .or_default();
3011 *culprit_count += 1;
3012 if *culprit_count == CONFLICT_THRESHOLD {
3013 self.conflict_tracker.deprioritize.push(incompatible);
3014 }
3015 }
3016 if culprit_is_real {
3019 if tracing::enabled!(Level::DEBUG) {
3020 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3021 .iter()
3022 .map(|(package, _term)| &self.pubgrub.package_store[package])
3023 .join(", ");
3024 if let Some(version) = version {
3025 debug!(
3026 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3027 self.pubgrub.package_store[affected], version, incompatibility
3028 );
3029 } else {
3030 debug!(
3031 "Recording unit propagation conflict of {} from incompatibility of ({})",
3032 self.pubgrub.package_store[affected], incompatibility
3033 );
3034 }
3035 }
3036
3037 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3038 *affected_count += 1;
3039 if *affected_count == CONFLICT_THRESHOLD {
3040 self.conflict_tracker.prioritize.push(self.next);
3041 }
3042 }
3043 }
3044
3045 fn reprioritize_conflicts(&mut self) {
3049 for package in self.conflict_tracker.prioritize.drain(..) {
3050 let changed = self
3051 .priorities
3052 .mark_conflict_early(&self.pubgrub.package_store[package]);
3053 if changed {
3054 debug!(
3055 "Package {} has too many conflicts (affected), prioritizing",
3056 &self.pubgrub.package_store[package]
3057 );
3058 } else {
3059 debug!(
3060 "Package {} has too many conflicts (affected), already {:?}",
3061 self.pubgrub.package_store[package],
3062 self.priorities.get(&self.pubgrub.package_store[package])
3063 );
3064 }
3065 }
3066
3067 for package in self.conflict_tracker.deprioritize.drain(..) {
3068 let changed = self
3069 .priorities
3070 .mark_conflict_late(&self.pubgrub.package_store[package]);
3071 if changed {
3072 debug!(
3073 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3074 self.pubgrub.package_store[package],
3075 );
3076 let backtrack_level = self.pubgrub.backtrack_package(package);
3077 if let Some(backtrack_level) = backtrack_level {
3078 debug!("Backtracked {backtrack_level} decisions");
3079 } else {
3080 debug!(
3081 "Package {} is not decided, cannot backtrack",
3082 self.pubgrub.package_store[package]
3083 );
3084 }
3085 } else {
3086 debug!(
3087 "Package {} has too many conflicts (culprit), already {:?}",
3088 self.pubgrub.package_store[package],
3089 self.priorities.get(&self.pubgrub.package_store[package])
3090 );
3091 }
3092 }
3093 }
3094
3095 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3103 &mut self,
3104 version: Version,
3105 reason: UnavailableVersion,
3106 index: &InMemoryIndex,
3107 installed_packages: &InstalledPackages,
3108 ) {
3109 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3110
3111 if let UnavailableVersion::IncompatibleDist(
3115 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3116 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3117 ) = reason
3118 {
3119 let package = &self.next;
3120 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3121 PubGrubPackageInner::Python(match kind {
3122 PythonRequirementKind::Installed => PubGrubPython::Installed,
3123 PythonRequirementKind::Target => PubGrubPython::Target,
3124 }),
3125 ));
3126 self.pubgrub
3127 .add_incompatibility(Incompatibility::from_dependency(
3128 *package,
3129 versions,
3130 (
3131 python,
3132 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3133 ),
3134 ));
3135 self.pubgrub
3136 .partial_solution
3137 .add_decision(self.next, version);
3138 return;
3139 }
3140 self.pubgrub
3141 .add_incompatibility(Incompatibility::custom_term(
3142 self.next,
3143 Term::Positive(versions),
3144 UnavailableReason::Version(reason),
3145 ));
3146 }
3147
3148 fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3149 self.continuation = continuation;
3150 self
3151 }
3152
3153 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3155 self.selected_versions.clear();
3156 self.env = env;
3157 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3159 debug!("Narrowed `requires-python` bound to: {}", req.target());
3160 self.python_requirement = req;
3161 }
3162 self
3163 }
3164
3165 fn source(
3169 &self,
3170 name: &PackageName,
3171 version: &Version,
3172 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3173 let url = self.fork_urls.get(name);
3174 let index = url
3175 .is_none()
3176 .then(|| {
3177 self.pins
3178 .get(name, version)
3179 .expect("Every package should be pinned")
3180 .index()
3181 })
3182 .flatten();
3183 (url, index)
3184 }
3185
3186 fn into_resolution(self) -> Resolution<'index> {
3187 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3188 let nodes: FxHashMap<_, _> = solution
3191 .iter()
3192 .filter_map(|(&package, version)| {
3193 if let PubGrubPackageInner::Package {
3194 name,
3195 kind,
3196 marker: MarkerTree::TRUE,
3197 } = &*self.pubgrub.package_store[package]
3198 {
3199 let (url, index) = self.source(name, version);
3200 Some((
3201 ResolutionPackage {
3202 name: name.clone(),
3203 kind: kind.clone(),
3204 url: url.cloned(),
3205 index: index.cloned(),
3206 },
3207 version.clone(),
3208 ))
3209 } else {
3210 None
3211 }
3212 })
3213 .collect();
3214 let nodes: Vec<_> = nodes.into_iter().collect();
3215 let node_indices: FxHashMap<_, _> = nodes
3216 .iter()
3217 .enumerate()
3218 .map(|(index, (package, version))| {
3219 (
3220 (
3221 &package.name,
3222 &package.kind,
3223 package.url.as_ref(),
3224 package.index.as_ref(),
3225 version,
3226 ),
3227 ResolutionNodeId(index),
3228 )
3229 })
3230 .collect();
3231
3232 let edge_count: usize = solution
3233 .keys()
3234 .map(|package| self.pubgrub.incompatibilities[package].len())
3235 .sum();
3236 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3237 for (package, self_version) in &solution {
3238 let (self_name, self_kind) = match &*self.pubgrub.package_store[*package] {
3239 PubGrubPackageInner::Package { name, kind, .. } => (Some(name), kind),
3240 PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3241 PubGrubPackageInner::Python(_)
3242 | PubGrubPackageInner::System(_)
3243 | PubGrubPackageInner::Marker { .. }
3244 | PubGrubPackageInner::Extra { .. }
3245 | PubGrubPackageInner::Group { .. } => continue,
3246 };
3247 let from = self_name.map(|name| {
3248 let (url, index) = self.source(name, self_version);
3249 node_indices[&(name, self_kind, url, index, self_version)]
3250 });
3251 for id in &self.pubgrub.incompatibilities[package] {
3252 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3253 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3254 &incompatibility.kind
3255 else {
3256 continue;
3257 };
3258 let (self_package, dependency_package) = (*self_package, *dependency_package);
3259 let Some((self_range, dependency_range)) =
3260 incompatibility.dependency_version_sets()
3261 else {
3262 continue;
3263 };
3264 let dependency_range =
3265 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3266 if *package != self_package {
3267 continue;
3268 }
3269 if !self_range.contains(self_version) {
3270 continue;
3271 }
3272 let Some(dependency_version) = solution.get(&dependency_package) else {
3273 continue;
3274 };
3275 if !dependency_range.contains(dependency_version) {
3276 continue;
3277 }
3278
3279 let dependency_package = &self.pubgrub.package_store[dependency_package];
3280
3281 let (name, kind, marker) = match &**dependency_package {
3282 PubGrubPackageInner::Package { name, kind, marker } => {
3283 debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3284
3285 if self_kind.group().is_none() && self_name == Some(name) {
3288 continue;
3289 }
3290 (name, kind.clone(), *marker)
3291 }
3292 PubGrubPackageInner::Marker { name, marker } => {
3293 if self_kind.group().is_none() && self_name == Some(name) {
3294 continue;
3295 }
3296 (name, PackageNodeKind::Base, *marker)
3297 }
3298 PubGrubPackageInner::Extra {
3299 name,
3300 extra,
3301 marker,
3302 } => {
3303 if self_kind.group().is_none() {
3304 debug_assert!(self_name != Some(name), "Extras should be flattened");
3305 }
3306 (name, PackageNodeKind::Extra(extra.clone()), *marker)
3307 }
3308 PubGrubPackageInner::Group {
3309 name,
3310 group,
3311 marker,
3312 } => {
3313 debug_assert!(self_name != Some(name), "Groups should be flattened");
3314 (name, PackageNodeKind::Group(group.clone()), *marker)
3315 }
3316 PubGrubPackageInner::Root(_)
3317 | PubGrubPackageInner::Python(_)
3318 | PubGrubPackageInner::System(_) => continue,
3319 };
3320 let (url, index) = self.source(name, dependency_version);
3321 edges.push(ResolutionDependencyEdge {
3322 from,
3323 to: node_indices[&(name, &kind, url, index, dependency_version)],
3324 marker,
3325 });
3326
3327 if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3330 edges.push(ResolutionDependencyEdge {
3331 from,
3332 to: node_indices
3333 [&(name, &PackageNodeKind::Base, url, index, dependency_version)],
3334 marker,
3335 });
3336 }
3337 }
3338 }
3339
3340 Resolution {
3341 nodes,
3342 edges,
3343 pins: self.pins,
3344 env: self.env,
3345 }
3346 }
3347}
3348
3349fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3358 let versions = Range::singleton(version.clone());
3359 match known_versions {
3360 Some(known_versions) if !known_versions.is_empty() => {
3361 versions.widen_versions(known_versions)
3362 }
3363 _ => versions,
3364 }
3365}
3366
3367#[derive(Debug)]
3369#[expect(clippy::large_enum_variant)]
3370pub(crate) enum Request {
3371 Package(PackageName, Option<IndexMetadata>),
3373 Dist(Dist),
3375 Installed(InstalledDist),
3377 VariantsJson(RegistryVariantsJson),
3379 Prefetch(PackageName, Range<Version>, PythonRequirement),
3381}
3382
3383impl<'a> From<ResolvedDistRef<'a>> for Request {
3384 fn from(dist: ResolvedDistRef<'a>) -> Self {
3385 match dist {
3391 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3392 let source = prioritized.source_dist().expect("a source distribution");
3395 assert_eq!(
3396 (&sdist.name, &sdist.version),
3397 (&source.name, &source.version),
3398 "expected chosen sdist to match prioritized sdist"
3399 );
3400 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3401 }
3402 ResolvedDistRef::InstallableRegistryBuiltDist {
3403 wheel, prioritized, ..
3404 } => {
3405 assert_eq!(
3406 Some(&wheel.filename),
3407 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3408 "expected chosen wheel to match best wheel"
3409 );
3410 let built = prioritized.built_dist().expect("at least one wheel");
3413 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3414 }
3415 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3416 }
3417 }
3418}
3419
3420impl Display for Request {
3421 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3422 match self {
3423 Self::Package(package_name, _) => {
3424 write!(f, "Versions {package_name}")
3425 }
3426 Self::Dist(dist) => {
3427 write!(f, "Metadata `{dist}`")
3428 }
3429 Self::Installed(dist) => {
3430 write!(f, "Installed metadata `{dist}`")
3431 }
3432 Self::VariantsJson(variants_json) => {
3433 write!(f, "Variant metadata `{}`", variants_json.filename)
3434 }
3435 Self::Prefetch(package_name, range, _) => {
3436 write!(f, "Prefetch {package_name} {range}")
3437 }
3438 }
3439 }
3440}
3441
3442#[derive(Debug)]
3443#[expect(clippy::large_enum_variant)]
3444enum Response {
3445 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3447 Dist {
3449 dist: Dist,
3450 metadata: MetadataResponse,
3451 },
3452 Installed {
3454 dist: InstalledDist,
3455 metadata: MetadataResponse,
3456 },
3457 VariantsJson {
3459 variants_json: RegistryVariantsJson,
3460 response: ParsedVariantsJsonResponse,
3461 },
3462}
3463
3464enum Dependencies {
3470 Unavailable(UnavailableVersion),
3472 Available(Vec<PubGrubDependency>),
3478 RequiresPython(VersionSpecifiers),
3480 Unforkable(Vec<PubGrubDependency>),
3486}
3487
3488#[derive(Debug)]
3495enum ForkedDependencies {
3496 Unavailable(UnavailableVersion),
3498 Unforked(Vec<PubGrubDependency>),
3502 Forked {
3508 forks: Vec<Fork>,
3509 diverging_packages: BTreeSet<PackageName>,
3511 },
3512 RequiresPython(VersionSpecifiers),
3514}
3515
3516impl ForkedDependencies {
3517 fn from_dependencies_universal(
3524 dependencies: Dependencies,
3525 env: &ResolverEnvironment,
3526 python_requirement: &PythonRequirement,
3527 conflicts: &Conflicts,
3528 ) -> Self {
3529 let deps = match dependencies {
3530 Dependencies::Available(deps) => deps,
3531 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3532 Dependencies::RequiresPython(requires_python) => {
3533 return Self::RequiresPython(requires_python);
3534 }
3535 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3536 };
3537 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3538 for dep in deps {
3539 let name = dep
3540 .package
3541 .name()
3542 .expect("dependency always has a name")
3543 .clone();
3544 name_to_deps.entry(name).or_default().push(dep);
3545 }
3546 let (mut forks, diverging_packages) =
3547 Self::fork(name_to_deps, env, python_requirement, conflicts);
3548 if forks.is_empty() {
3549 Self::Unforked(vec![])
3550 } else if forks.len() == 1 {
3551 Self::Unforked(forks.pop().unwrap().dependencies)
3552 } else {
3553 Self::Forked {
3554 forks,
3555 diverging_packages,
3556 }
3557 }
3558 }
3559
3560 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3563 match dependencies {
3564 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3565 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3566 Dependencies::Unavailable(err) => Self::Unavailable(err),
3567 }
3568 }
3569
3570 fn fork(
3578 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3579 env: &ResolverEnvironment,
3580 python_requirement: &PythonRequirement,
3581 conflicts: &Conflicts,
3582 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3583 let python_marker = python_requirement.to_marker_tree();
3584
3585 let mut forks = vec![Fork::new(env.clone())];
3586 let mut diverging_packages = BTreeSet::new();
3587 for (name, mut deps) in name_to_deps {
3588 assert!(!deps.is_empty(), "every name has at least one dependency");
3589 if let [dep] = deps.as_slice() {
3600 if marker::requires_python(dep.package.marker())
3608 .is_none_or(|bound| !python_requirement.raises(&bound))
3609 {
3610 let dep = deps.pop().unwrap();
3611 let marker = dep.package.marker();
3612 for fork in &mut forks {
3613 if fork.env.included_by_marker(marker) {
3614 fork.add_dependency(dep.clone());
3615 }
3616 }
3617 continue;
3618 }
3619 } else {
3620 if let Some(dep) = deps.first() {
3622 let marker = dep.package.marker();
3623 if deps.iter().all(|dep| marker == dep.package.marker()) {
3624 if marker::requires_python(marker)
3628 .is_none_or(|bound| !python_requirement.raises(&bound))
3629 {
3630 for dep in deps {
3631 for fork in &mut forks {
3632 if fork.env.included_by_marker(marker) {
3633 fork.add_dependency(dep.clone());
3634 }
3635 }
3636 }
3637 continue;
3638 }
3639 }
3640 }
3641 }
3642 for dep in deps {
3643 let mut forker = match ForkingPossibility::new(env, &dep) {
3644 ForkingPossibility::Possible(forker) => forker,
3645 ForkingPossibility::DependencyAlwaysExcluded => {
3646 continue;
3649 }
3650 ForkingPossibility::NoForkingPossible => {
3651 for fork in &mut forks {
3654 fork.add_dependency(dep.clone());
3655 }
3656 continue;
3657 }
3658 };
3659 diverging_packages.insert(name.clone());
3661
3662 let mut new = vec![];
3663 for fork in std::mem::take(&mut forks) {
3664 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3665 new.push(fork);
3666 continue;
3667 };
3668 forker = remaining_forker;
3669
3670 for fork_env in envs {
3671 let mut new_fork = fork.clone();
3672 new_fork.set_env(fork_env);
3673 if forker.included(&new_fork.env) {
3678 new_fork.add_dependency(dep.clone());
3679 }
3680 if new_fork.env.included_by_marker(python_marker) {
3683 new.push(new_fork);
3684 }
3685 }
3686 }
3687 forks = new;
3688 }
3689 }
3690 for set in conflicts.iter() {
3705 let mut new = vec![];
3706 for fork in std::mem::take(&mut forks) {
3707 let mut has_conflicting_dependency = false;
3715 for item in set.iter() {
3716 if fork.contains_conflicting_item(item.as_ref()) {
3717 has_conflicting_dependency = true;
3718 diverging_packages.insert(item.package().clone());
3719 break;
3720 }
3721 }
3722 if !has_conflicting_dependency {
3723 new.push(fork);
3724 continue;
3725 }
3726
3727 let non_excluded: Vec<_> = set
3733 .iter()
3734 .filter(|item| fork.env.included_by_group(item.as_ref()))
3735 .collect();
3736 if non_excluded.len() < 2 {
3737 let dominated = non_excluded.iter().all(|item| {
3742 !conflicts.iter().any(|other_set| {
3743 !std::ptr::eq(set, other_set)
3744 && other_set.contains(item.package(), item.kind().as_ref())
3745 && other_set
3746 .iter()
3747 .filter(|other_item| {
3748 other_item.package() != item.package()
3749 || other_item.kind() != item.kind()
3750 })
3751 .any(|other_item| {
3752 fork.env.included_by_group(other_item.as_ref())
3753 })
3754 })
3755 });
3756 if dominated {
3757 let rules: Vec<_> = set
3762 .iter()
3763 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3764 .cloned()
3765 .map(Err)
3766 .collect();
3767 if let Some(filtered) = fork.filter(rules) {
3768 new.push(filtered);
3769 }
3770 continue;
3771 }
3772 }
3773
3774 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3776 new.push(fork_none);
3777 }
3778
3779 for (i, _) in set.iter().enumerate() {
3787 let fork_allows_group = fork.clone().filter(
3788 set.iter()
3789 .cloned()
3790 .enumerate()
3791 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3792 );
3793 if let Some(fork_allows_group) = fork_allows_group {
3794 new.push(fork_allows_group);
3795 }
3796 }
3797 }
3798 forks = new;
3799 }
3800 (forks, diverging_packages)
3801 }
3802}
3803
3804#[derive(Clone, Debug)]
3814struct Fork {
3815 dependencies: Vec<PubGrubDependency>,
3824 conflicts: crate::FxHashbrownSet<ConflictItem>,
3830 env: ResolverEnvironment,
3842}
3843
3844impl Fork {
3845 fn new(env: ResolverEnvironment) -> Self {
3848 Self {
3849 dependencies: vec![],
3850 conflicts: crate::FxHashbrownSet::default(),
3851 env,
3852 }
3853 }
3854
3855 fn add_dependency(&mut self, dep: PubGrubDependency) {
3857 if let Some(conflicting_item) = dep.conflicting_item() {
3858 self.conflicts.insert(conflicting_item.to_owned());
3859 }
3860 self.dependencies.push(dep);
3861 }
3862
3863 fn set_env(&mut self, env: ResolverEnvironment) {
3868 self.env = env;
3869 self.dependencies.retain(|dep| {
3870 let marker = dep.package.marker();
3871 if self.env.included_by_marker(marker) {
3872 return true;
3873 }
3874 if let Some(conflicting_item) = dep.conflicting_item() {
3875 self.conflicts.remove(&conflicting_item);
3876 }
3877 false
3878 });
3879 }
3880
3881 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3884 self.conflicts.contains(&item)
3885 }
3886
3887 fn filter(
3894 mut self,
3895 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3896 ) -> Option<Self> {
3897 self.env = self.env.filter_by_group(rules)?;
3898 self.dependencies.retain(|dep| {
3899 let Some(conflicting_item) = dep.conflicting_item() else {
3900 return true;
3901 };
3902 if self.env.included_by_group(conflicting_item) {
3903 return true;
3904 }
3905 match conflicting_item.kind() {
3906 ConflictKindRef::Project => {
3909 if dep.parent.is_some() {
3910 return true;
3911 }
3912 }
3913 ConflictKindRef::Group(_) => {}
3914 ConflictKindRef::Extra(_) => {}
3915 }
3916 self.conflicts.remove(&conflicting_item);
3917 false
3918 });
3919 Some(self)
3920 }
3921
3922 fn cmp_requires_python(&self, other: &Self) -> Ordering {
3924 cmp_requires_python(&self.env, &other.env)
3925 }
3926
3927 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3929 let self_upper_bounds = self
3934 .dependencies
3935 .iter()
3936 .filter(|dep| {
3937 dep.version
3938 .bounding_range()
3939 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3940 })
3941 .count();
3942 let other_upper_bounds = other
3943 .dependencies
3944 .iter()
3945 .filter(|dep| {
3946 dep.version
3947 .bounding_range()
3948 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3949 })
3950 .count();
3951
3952 self_upper_bounds.cmp(&other_upper_bounds)
3953 }
3954}
3955
3956fn cmp_requires_python(
3958 self_env: &ResolverEnvironment,
3959 other_env: &ResolverEnvironment,
3960) -> Ordering {
3961 let self_bound = self_env.requires_python().unwrap_or_default();
3971 let other_bound = other_env.requires_python().unwrap_or_default();
3972 self_bound.lower().cmp(other_bound.lower())
3973}
3974
3975impl Eq for Fork {}
3976
3977impl PartialEq for Fork {
3978 fn eq(&self, other: &Self) -> bool {
3979 self.dependencies == other.dependencies && self.env == other.env
3980 }
3981}
3982
3983#[derive(Debug, Clone)]
3984pub(crate) struct VersionFork {
3985 env: ResolverEnvironment,
3987 id: Id<PubGrubPackage>,
3989 version: Option<Version>,
3991}
3992
3993fn enrich_dependency_error(
3995 error: ResolveError,
3996 id: Id<PubGrubPackage>,
3997 version: &Version,
3998 pubgrub: &State<UvDependencyProvider>,
3999) -> ResolveError {
4000 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4001 return error;
4002 };
4003 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4004 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4005}
4006
4007fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4009 let package = &state.package_store[id];
4010 if package.is_root() {
4011 return MarkerTree::TRUE;
4012 }
4013
4014 let mut ancestors = FxHashSet::default();
4017 let mut stack = vec![id];
4018 let mut root = None;
4019 ancestors.insert(id);
4020
4021 while let Some(current) = stack.pop() {
4022 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4023 continue;
4024 };
4025
4026 for index in incompatibilities {
4027 let incompat = &state.incompatibility_store[*index];
4028 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4029 if current != *child {
4030 continue;
4031 }
4032 if ancestors.insert(*parent) {
4033 if state.package_store[*parent].is_root() {
4034 root = Some(*parent);
4035 }
4036 stack.push(*parent);
4037 }
4038 }
4039 }
4040 }
4041
4042 let Some(root) = root else {
4043 return MarkerTree::FALSE;
4044 };
4045
4046 let mut environments = FxHashMap::default();
4049 let mut queue = VecDeque::from([root]);
4050 environments.insert(root, MarkerTree::TRUE);
4051
4052 while let Some(current) = queue.pop_front() {
4053 let Some(current_environment) = environments.get(¤t).copied() else {
4054 continue;
4055 };
4056 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4057 continue;
4058 };
4059
4060 for index in incompatibilities {
4061 let incompat = &state.incompatibility_store[*index];
4062 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4063 continue;
4064 };
4065 if current != *parent || !ancestors.contains(child) {
4066 continue;
4067 }
4068
4069 let mut next_environment = state.package_store[*child].marker();
4070 next_environment = next_environment.and(current_environment);
4071
4072 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4073 let mut combined = *entry;
4074 combined = combined.or(next_environment);
4075 if combined != *entry {
4076 *entry = combined;
4077 queue.push_back(*child);
4078 }
4079 }
4080 }
4081
4082 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4083}
4084
4085#[derive(Debug, Default, Clone)]
4086struct ConflictTracker {
4087 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4089 prioritize: Vec<Id<PubGrubPackage>>,
4093 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4095 deprioritize: Vec<Id<PubGrubPackage>>,
4099}
4100
4101#[cfg(test)]
4102mod tests {
4103 use super::*;
4104
4105 fn versions(versions: &[&str]) -> Vec<Version> {
4106 versions
4107 .iter()
4108 .map(|version| version.parse().expect("valid version"))
4109 .collect()
4110 }
4111
4112 #[test]
4113 fn widens_a_version_to_its_gap() {
4114 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4115 let version: Version = "2.0".parse().expect("valid version");
4116
4117 assert_eq!(
4119 widen_to_gap(&version, Some(&known_versions)).to_string(),
4120 ">1.0, <3.0"
4121 );
4122
4123 let version: Version = "1.0".parse().expect("valid version");
4125 assert_eq!(
4126 widen_to_gap(&version, Some(&known_versions)).to_string(),
4127 "<2.0"
4128 );
4129 let version: Version = "3.0".parse().expect("valid version");
4130 assert_eq!(
4131 widen_to_gap(&version, Some(&known_versions)).to_string(),
4132 ">2.0"
4133 );
4134 }
4135
4136 #[test]
4137 fn widens_a_version_without_known_versions_to_itself() {
4138 let version: Version = "2.0".parse().expect("valid version");
4139
4140 assert_eq!(
4142 widen_to_gap(&version, None),
4143 Range::singleton(version.clone())
4144 );
4145
4146 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4148 }
4149}