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