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::rc::Rc;
9use std::sync::Arc;
10use std::time::Instant;
11use std::{mem, thread};
12
13use either::Either;
14use futures::{FutureExt, StreamExt};
15use itertools::Itertools;
16use papaya::{HashMap, ResizeMode};
17use pubgrub::{ConflictId, Id, IncompatibilityArena, Ranges, State, Term};
18use rustc_hash::{FxHashMap, FxHashSet};
19use tokio::sync::mpsc::{self, Receiver};
20use tokio::sync::oneshot;
21use tokio_stream::wrappers::ReceiverStream;
22use tracing::{Level, debug, info, instrument, trace, warn};
23
24use uv_configuration::{Constraints, DependencyModifiers, IndexStrategy};
25use uv_distribution::{ArchiveMetadata, DistributionDatabase};
26use uv_distribution_types::{
27 BuiltDist, DerivationChain, Dist, DistErrorKind, Identifier, IndexCapabilities, IndexLocations,
28 IndexMetadata, IndexUrl, InstalledDist, Name, RemoteSource, Requirement, RequiresPython,
29 ResolutionRecorder, ResolvedDist, SourceDist, VariantsJsonContent, VersionOrUrlRef,
30};
31use uv_git::GitResolver;
32use uv_normalize::PackageName;
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_preview::PreviewFeature;
39use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
40use uv_resolver_types::{PackageNodeKind, VariantsJsonMetadataError};
41use uv_static::EnvVars;
42use uv_torch::TorchStrategy;
43use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
44use uv_warnings::warn_user_once;
45
46use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
47use crate::dependency_provider::UvDependencyProvider;
48use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
49use crate::fork_indexes::ForkIndexes;
50use crate::fork_urls::ForkUrls;
51use crate::manifest::Manifest;
52use crate::pins::FilePins;
53use crate::preferences::{PreferenceSource, Preferences};
54use crate::prioritized_distribution::{
55 CompatibleDist, IncompatibleDist, IncompatibleSource, IncompatibleWheel, PrioritizedDist,
56 PythonRequirementKind, RegistryVariantsJson, implied_markers,
57};
58use crate::pubgrub::{
59 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
60 PubGrubPython, Range,
61};
62use crate::python_requirement::PythonRequirement;
63use crate::resolution::{ResolutionGraphBuilder, ResolverOutput};
64use crate::resolution_mode::ResolutionStrategy;
65use crate::resolved::ResolvedDistRef;
66pub(crate) use crate::resolver::availability::{
67 ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion,
68 UnsatisfiableRequirement,
69};
70use crate::resolver::batch_prefetch::BatchPrefetcher;
71use crate::resolver::derivation::DerivationChainBuilder;
72pub use crate::resolver::environment::ResolverEnvironment;
73use crate::resolver::environment::{
74 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
75};
76pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
77pub use crate::resolver::index::InMemoryIndex;
78use crate::resolver::index::ParsedVariantsJsonResponse;
79use crate::resolver::indexes::Indexes;
80use crate::resolver::package_source::PackageSource;
81pub use crate::resolver::provider::{
82 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
83 VariantsJsonResponse, VersionsResponse, WheelMetadataResult,
84};
85pub use crate::resolver::reporter::Reporter;
86use crate::resolver::requests::{MetadataRequest, MetadataRequests};
87pub(crate) use crate::resolver::requests::{RegisteredMetadata, RegisteredVariantsJson};
88use crate::resolver::requirements::{RequirementContext, RequirementExpander};
89use crate::resolver::system::SystemDependency;
90pub(crate) use crate::resolver::urls::Urls;
91use crate::universal_marker::UniversalMarker;
92use crate::yanks::AllowedYanks;
93use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
94pub(crate) use provider::MetadataUnavailable;
95pub(crate) use resolution::{
96 Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionNodeId, ResolutionPackage,
97 SelectedDistribution,
98};
99use uv_configuration::ForkStrategy;
100
101mod availability;
102mod batch_prefetch;
103mod derivation;
104mod environment;
105mod fork_map;
106mod index;
107mod indexes;
108mod package_source;
109mod provider;
110mod reporter;
111mod requests;
112mod requirements;
113mod resolution;
114mod system;
115mod urls;
116
117const CONFLICT_THRESHOLD: usize = 5;
119
120pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
121 state: ResolverState<InstalledPackages>,
122 provider: Provider,
123}
124
125struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
128 recorder: Option<ResolutionRecorder>,
129 project: Option<PackageName>,
130 requirements: Vec<Requirement>,
131 constraints: Constraints,
132 modifiers: DependencyModifiers,
133 preferences: Preferences,
134 git: GitResolver,
135 capabilities: IndexCapabilities,
136 locations: IndexLocations,
137 exclusions: Exclusions,
138 urls: Urls,
139 indexes: Indexes,
140 dependency_mode: DependencyMode,
141 hasher: HashStrategy,
142 env: ResolverEnvironment,
143 current_environment: MarkerEnvironment,
145 tags: Option<Tags>,
146 python_requirement: PythonRequirement,
147 conflicts: Conflicts,
148 workspace_members: BTreeSet<PackageName>,
149 selector: CandidateSelector,
150 index: InMemoryIndex,
151 installed_packages: InstalledPackages,
152 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
155 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
157 options: Options,
159 reporter: Option<Arc<dyn Reporter>>,
161}
162
163#[derive(Default)]
165struct WheelVariantSelection<'a> {
166 distribution: Option<Cow<'a, PrioritizedDist>>,
168 unavailable: Option<MetadataUnavailable>,
170}
171
172impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
173 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
174{
175 pub fn new(
194 manifest: Manifest,
195 options: Options,
196 python_requirement: &'a PythonRequirement,
197 env: ResolverEnvironment,
198 current_environment: &MarkerEnvironment,
199 conflicts: Conflicts,
200 tags: Option<&'a Tags>,
201 flat_index: &'a FlatIndex,
202 index: &'a InMemoryIndex,
203 hasher: &'a HashStrategy,
204 build_context: &'a Context,
205 installed_packages: InstalledPackages,
206 database: DistributionDatabase<'a, Context>,
207 ) -> Result<Self, ResolveError> {
208 let provider = DefaultResolverProvider::new(
209 database,
210 flat_index,
211 tags,
212 python_requirement.target(),
213 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
214 hasher,
215 options.exclude_newer.clone(),
216 build_context.locations(),
217 build_context.build_options(),
218 build_context.capabilities(),
219 options.minimum_libc_version,
220 );
221
222 Ok(Self::new_custom_io(
223 manifest,
224 options,
225 hasher,
226 env,
227 current_environment,
228 tags.cloned(),
229 python_requirement,
230 conflicts,
231 index,
232 build_context.git(),
233 build_context.capabilities(),
234 build_context.locations(),
235 provider,
236 installed_packages,
237 ))
238 }
239}
240
241impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
242 Resolver<Provider, InstalledPackages>
243{
244 pub fn new_custom_io(
246 manifest: Manifest,
247 options: Options,
248 hasher: &HashStrategy,
249 env: ResolverEnvironment,
250 current_environment: &MarkerEnvironment,
251 tags: Option<Tags>,
252 python_requirement: &PythonRequirement,
253 conflicts: Conflicts,
254 index: &InMemoryIndex,
255 git: &GitResolver,
256 capabilities: &IndexCapabilities,
257 locations: &IndexLocations,
258 provider: Provider,
259 installed_packages: InstalledPackages,
260 ) -> Self {
261 let state = ResolverState {
262 index: index.clone(),
263 git: git.clone(),
264 capabilities: capabilities.clone(),
265 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
266 dependency_mode: options.dependency_mode,
267 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
268 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
269 recorder: manifest.recorder.clone(),
270 project: manifest.project,
271 workspace_members: manifest.workspace_members.into_keys().collect(),
272 requirements: manifest.requirements,
273 constraints: manifest
274 .constraints
275 .with_recorder(manifest.recorder.clone()),
276 modifiers: manifest.modifiers.with_recorder(manifest.recorder.clone()),
277 preferences: manifest.preferences,
278 exclusions: manifest.exclusions,
279 hasher: hasher.clone(),
280 locations: locations.clone(),
281 env,
282 current_environment: current_environment.clone(),
283 tags,
284 python_requirement: python_requirement.clone(),
285 conflicts,
286 installed_packages,
287 unavailable_packages: Box::default(),
288 incomplete_packages: Box::default(),
289 options,
290 reporter: None,
291 };
292 Self { state, provider }
293 }
294
295 #[must_use]
297 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
298 Self {
299 state: ResolverState {
300 reporter: Some(reporter.clone()),
301 ..self.state
302 },
303 provider: self
304 .provider
305 .with_reporter(reporter.into_distribution_reporter()),
306 }
307 }
308
309 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
311 let state = Arc::new(self.state);
312 let provider = Arc::new(self.provider);
313
314 let (request_sink, request_stream) = mpsc::channel(300);
318 let requests =
319 MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
320
321 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
323
324 let solver = state.clone();
326 let (tx, rx) = oneshot::channel();
327 thread::Builder::new()
328 .name("uv-resolver".into())
329 .spawn(move || {
330 let result = solver.solve(&requests);
331
332 let _ = tx.send(result);
334 })
335 .unwrap();
336
337 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
338
339 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
341
342 state.on_complete();
343 resolution
344 }
345}
346
347impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
348 #[instrument(skip_all)]
349 fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
350 debug!(
351 "Solving with installed Python version: {}",
352 self.python_requirement.exact()
353 );
354 debug!(
355 "Solving with target Python version: {}",
356 self.python_requirement.target()
357 );
358 if !self.options.exclude_newer.is_empty() {
359 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
360 }
361
362 let mut visited = FxHashSet::default();
363
364 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
365 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
366 let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
367 let mut state = ForkState::new(
368 pubgrub,
369 self.env.clone(),
370 self.python_requirement.clone(),
371 prefetcher,
372 );
373 let mut incompatibility_arena = state.pubgrub.take_incompatibility_arena();
375 let mut preferences = self.preferences.clone();
376 let mut forked_states = self.env.initial_forked_states(state)?;
377
378 match (self.options.fork_strategy, self.options.resolution_mode) {
384 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
385 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
386 }
387 (ForkStrategy::RequiresPython, _) => {
388 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
389 }
390 }
391 let mut forked_states: Vec<_> = forked_states
392 .into_iter()
393 .map(|state| PendingFork {
394 state: Rc::new(state),
395 action: ForkAction::Initial,
396 })
397 .collect();
398 let mut resolutions = ResolutionGraphBuilder::new(
399 self.project.as_ref(),
400 &self.workspace_members,
401 &self.preferences,
402 &self.hasher,
403 &self.index,
404 &self.git,
405 );
406
407 'FORK: while let Some(fork) = forked_states.pop() {
408 let mut state = self.materialize_fork(fork, incompatibility_arena, requests)?;
409 if let Some(split) = state.env.end_user_fork_display() {
410 let requires_python = state.python_requirement.target();
411 debug!("Solving {split} (requires-python: {requires_python:?})");
412 }
413 let start = Instant::now();
414 loop {
415 let continuation = mem::take(&mut state.continuation);
416 let (highest_priority_pkg, initial_version) = match continuation {
417 ForkContinuation::SelectVersion { package } => (package, None),
418 ForkContinuation::UseVersion { package, version } => (package, Some(version)),
419 ForkContinuation::Propagate => {
420 let result = state.pubgrub.unit_propagation(state.next);
422 match result {
423 Err(err) => {
424 return Err(self.convert_no_solution_err(
426 err,
427 state.fork_urls,
428 state.fork_indexes,
429 &state.known_versions.0,
430 state.env,
431 self.current_environment.clone(),
432 &visited,
433 ));
434 }
435 Ok(conflicts) => {
436 for (affected, incompatibility) in conflicts {
437 state.record_conflict(affected, None, &incompatibility);
440 }
441 }
442 }
443
444 if self.dependency_mode.is_transitive() {
446 Self::pre_visit(
447 state.pubgrub.partial_solution.prioritized_packages().map(
448 |(id, range)| (id, &state.pubgrub.package_store[id], range),
449 ),
450 &mut state.pre_visited,
451 &self.urls,
452 &self.indexes,
453 &state.python_requirement,
454 requests,
455 )?;
456 }
457
458 state.reprioritize_conflicts();
459
460 trace!(
461 "Assigned packages: {}",
462 state
463 .pubgrub
464 .partial_solution
465 .extract_solution()
466 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
467 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
468 .join(", ")
469 );
470 let Some((highest_priority_pkg, _)) =
474 state.pubgrub.partial_solution.pick_highest_priority_pkg(
475 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
476 )
477 else {
478 if tracing::enabled!(Level::DEBUG) {
480 state.prefetcher.log_tried_versions();
481 }
482 debug!(
483 "{} resolution took {:.3}s",
484 state.env,
485 start.elapsed().as_secs_f32()
486 );
487
488 let (resolution, mut arena) = state.into_resolution();
489 if let Some(next) = forked_states.last() {
490 arena.rewind_to(&next.state.pubgrub);
491 } else {
492 arena = IncompatibilityArena::default();
494 }
495 incompatibility_arena = arena;
496
497 if matches!(
506 self.options.resolution_mode,
507 ResolutionMode::Lowest | ResolutionMode::Highest
508 ) {
509 let marker = resolution
510 .env
511 .try_universal_markers()
512 .unwrap_or(UniversalMarker::TRUE);
513 for (package, version) in &resolution.nodes {
514 preferences.insert(
515 package.name.clone(),
516 package.index.clone(),
517 marker,
518 version.clone(),
519 PreferenceSource::Resolver,
520 );
521 }
522 }
523
524 if tracing::enabled!(Level::DEBUG)
525 && let Some(env) = resolution.env.end_user_fork_display()
526 {
527 let packages: FxHashSet<_> = resolution
528 .nodes
529 .iter()
530 .map(|(package, _)| &package.name)
531 .collect();
532 debug!(
533 "Distinct solution for {env} with {} package(s)",
534 packages.len()
535 );
536 }
537 resolution.trace_resolution();
538 resolutions.push(resolution)?;
539 continue 'FORK;
540 };
541 trace!(
542 "Chose package for decision: {}. remaining choices: {}",
543 state.pubgrub.package_store[highest_priority_pkg],
544 state
545 .pubgrub
546 .partial_solution
547 .undecided_packages()
548 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
549 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
550 .join(", ")
551 );
552
553 (highest_priority_pkg, None)
554 }
555 };
556
557 state.next = highest_priority_pkg;
558
559 let next_id = state.next;
561 let next_package = &state.pubgrub.package_store[state.next];
562
563 let source =
564 PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
565
566 self.request_package(next_package, source, requests)?;
578
579 let version = if let Some(version) = initial_version {
580 version
581 } else {
582 let range = state
583 .pubgrub
584 .partial_solution
585 .term_intersection_for_package(next_id)
586 .and_then(|term| match term {
587 Term::Positive(range) => Some(range),
588 Term::Negative(_) => None,
589 })
590 .expect("a package was chosen but we don't have a positive term");
591
592 let cache_selected_version = match source {
596 PackageSource::Registry(None) => true,
597 PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
598 };
599 let decision = if cache_selected_version
600 && let Some((selected_range, version)) =
601 state.selected_versions.get(&next_id)
602 && selected_range == range
603 {
604 Some(ResolverVersion::Unforked(version.clone()))
605 } else {
606 let decision = self.choose_version(
607 next_package,
608 next_id,
609 source,
610 range,
611 &mut state.pins,
612 &preferences,
613 &state.env,
614 &state.python_requirement,
615 &state.pubgrub,
616 &mut visited,
617 requests,
618 )?;
619
620 if cache_selected_version
621 && let Some(ResolverVersion::Unforked(version)) = &decision
622 {
623 state
624 .selected_versions
625 .insert(next_id, (range.clone(), version.clone()));
626 }
627
628 decision
629 };
630
631 let Some(version) = decision else {
633 debug!("No compatible version found for: {next_package}");
634
635 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
636 if let Some(reason) = self.unavailable_packages.pin().get(name) {
638 state.pubgrub.add_unavailable(
639 next_id,
640 range.clone(),
641 UnavailableReason::Package(reason.clone()),
642 );
643 continue;
644 }
645 }
646
647 state.pubgrub.add_no_versions(next_id, range.clone());
648 continue;
649 };
650
651 let version = match version {
652 ResolverVersion::Unforked(version) => version,
653 ResolverVersion::Forked(forks) => {
654 incompatibility_arena = state.pubgrub.take_incompatibility_arena();
655 forked_states.extend(Self::version_forks_to_fork_states(state, forks));
656 continue 'FORK;
657 }
658 ResolverVersion::Unavailable(version, reason) => {
659 state.add_unavailable_version(
660 version,
661 reason,
662 &self.index,
663 &self.installed_packages,
664 );
665 continue;
666 }
667 };
668
669 if let PackageSource::Registry(index) = source {
671 state.prefetcher.prefetch_batches(
672 next_package,
673 index,
674 &version,
675 range,
676 state
677 .pubgrub
678 .partial_solution
679 .unchanging_term_for_package(next_id),
680 &state.python_requirement,
681 &self.selector,
682 &state.env,
683 )?;
684 }
685
686 version
687 };
688
689 state.prefetcher.version_tried(next_package, &version);
690
691 self.on_progress(next_package, &version);
692
693 if state
694 .added_dependencies
695 .get(&next_id)
696 .is_some_and(|versions| versions.contains(&version))
697 {
698 state
701 .pubgrub
702 .partial_solution
703 .add_decision(next_id, version);
704 continue;
705 }
706
707 let forked_deps = self.get_dependencies_forking(
709 next_id,
710 next_package,
711 &version,
712 &state.pins,
713 &state.env,
714 &state.python_requirement,
715 &state.pubgrub,
716 )?;
717
718 match forked_deps {
719 ForkedDependencies::Unavailable(reason) => {
720 let versions = state.widen_version_to_gap(
723 &version,
724 &self.index,
725 &self.installed_packages,
726 );
727 state.pubgrub.add_unavailable(
728 next_id,
729 versions,
730 UnavailableReason::Version(reason),
731 );
732 }
733 ForkedDependencies::Unforked(dependencies) => {
734 state
735 .added_dependencies
736 .entry(next_id)
737 .or_default()
738 .insert(version.clone());
739
740 state
742 .visit_package_version_dependencies(
743 next_id,
744 &version,
745 &self.urls,
746 &self.indexes,
747 &dependencies,
748 &self.git,
749 &self.workspace_members,
750 self.selector.resolution_strategy(),
751 )
752 .map_err(|err| {
753 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
754 })?;
755
756 self.visit_dependencies(
758 &dependencies,
759 &state.fork_urls,
760 &state.fork_indexes,
761 requests,
762 )
763 .map_err(|err| {
764 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
765 })?;
766
767 state.add_package_version_dependencies(
769 next_id,
770 &version,
771 dependencies,
772 &self.index,
773 &self.installed_packages,
774 );
775 }
776 ForkedDependencies::Forked {
777 mut forks,
778 diverging_packages,
779 } => {
780 state
781 .added_dependencies
782 .entry(next_id)
783 .or_default()
784 .insert(version.clone());
785
786 debug!(
787 "Pre-fork {} took {:.3}s",
788 state.env,
789 start.elapsed().as_secs_f32()
790 );
791
792 match (self.options.fork_strategy, self.options.resolution_mode) {
794 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
795 forks.sort_by(|a, b| {
799 a.cmp_requires_python(b)
800 .reverse()
801 .then_with(|| a.cmp_upper_bounds(b))
802 });
803 }
804 (ForkStrategy::RequiresPython, _) => {
805 forks.sort_by(|a, b| {
809 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
810 });
811 }
812 }
813
814 self.prefetch_forks(&state, &forks, requests)
815 .map_err(|err| {
816 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
817 })?;
818 incompatibility_arena = state.pubgrub.take_incompatibility_arena();
819 forked_states.extend(Self::forks_to_fork_states(
820 state,
821 &version,
822 forks,
823 &diverging_packages,
824 ));
825 continue 'FORK;
826 }
827 ForkedDependencies::RequiresPython(requires_python) => {
828 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
829 && state.env.marker_environment().is_none()
830 {
831 let forks = fork_version_by_python_requirement(
832 &requires_python,
833 &state.python_requirement,
834 &state.env,
835 );
836 if !forks.is_empty() {
837 debug!(
838 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
839 state.python_requirement.target(),
840 &requires_python,
841 next_package,
842 version,
843 forks
844 .iter()
845 .map(ToString::to_string)
846 .collect::<Vec<_>>()
847 .join(", ")
848 );
849
850 let forks = forks
853 .into_iter()
854 .map(|env| VersionFork {
855 env,
856 id: next_id,
857 version: None,
858 })
859 .collect();
860 incompatibility_arena = state.pubgrub.take_incompatibility_arena();
861 forked_states
862 .extend(Self::version_forks_to_fork_states(state, forks));
863 continue 'FORK;
864 }
865 }
866
867 let versions = state.widen_version_to_gap(
868 &version,
869 &self.index,
870 &self.installed_packages,
871 );
872 state.pubgrub.add_unavailable(
873 next_id,
874 versions,
875 UnavailableReason::Version(UnavailableVersion::RequiresPython(
876 requires_python,
877 )),
878 );
879 }
880 }
881 }
882 }
883 drop(incompatibility_arena);
884
885 if resolutions.num_forks() > 1 {
886 info!(
887 "Solved your requirements for {} environments",
888 resolutions.num_forks()
889 );
890 }
891 resolutions.finish(
892 self.requirements.clone(),
893 self.constraints.clone(),
894 self.modifiers.clone(),
895 self.python_requirement.target().clone(),
896 &self.conflicts,
897 self.selector.resolution_strategy(),
898 self.options.clone(),
899 )
900 }
901
902 fn forks_to_fork_states<'index>(
904 current_state: ForkState<'index>,
905 version: &Version,
906 forks: Vec<Fork>,
907 diverging_packages: &BTreeSet<PackageName>,
908 ) -> impl Iterator<Item = PendingFork<'index>> {
909 debug!(
910 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
911 current_state.pubgrub.package_store[current_state.next],
912 version,
913 diverging_packages
914 .iter()
915 .map(ToString::to_string)
916 .join(", "),
917 forks.len(),
918 if forks.len() == 1 { "" } else { "s" }
919 );
920 assert!(forks.len() >= 2);
921 let state = Rc::new(current_state);
922 let version = version.clone();
923 forks.into_iter().map(move |fork| PendingFork {
924 state: Rc::clone(&state),
925 action: ForkAction::Dependencies {
926 fork,
927 version: version.clone(),
928 },
929 })
930 }
931
932 fn version_forks_to_fork_states(
934 current_state: ForkState<'_>,
935 forks: Vec<VersionFork>,
936 ) -> impl Iterator<Item = PendingFork<'_>> {
937 let state = Rc::new(current_state);
938 forks.into_iter().map(move |fork| PendingFork {
939 state: Rc::clone(&state),
940 action: ForkAction::Version(fork),
941 })
942 }
943
944 fn materialize_fork<'index>(
946 &self,
947 pending: PendingFork<'index>,
948 incompatibility_arena: IncompatibilityArena<UvDependencyProvider>,
949 requests: &MetadataRequests,
950 ) -> Result<ForkState<'index>, ResolveError> {
951 let mut state = Rc::unwrap_or_clone(pending.state);
952 state
953 .pubgrub
954 .restore_incompatibility_arena(incompatibility_arena);
955 match pending.action {
956 ForkAction::Initial => Ok(state),
957 ForkAction::Version(fork) => {
958 let continuation = match fork.version {
959 Some(version) => ForkContinuation::UseVersion {
960 package: fork.id,
961 version,
962 },
963 None => ForkContinuation::SelectVersion { package: fork.id },
964 };
965 Ok(state.with_env(fork.env).with_continuation(continuation))
966 }
967 ForkAction::Dependencies { fork, version } => {
968 let mut state = state.with_env(fork.env);
969 let package = state.next;
970
971 state
973 .visit_package_version_dependencies(
974 package,
975 &version,
976 &self.urls,
977 &self.indexes,
978 &fork.dependencies,
979 &self.git,
980 &self.workspace_members,
981 self.selector.resolution_strategy(),
982 )
983 .map_err(|err| {
984 enrich_dependency_error(err, package, &version, &state.pubgrub)
985 })?;
986
987 self.visit_dependencies(
989 &fork.dependencies,
990 &state.fork_urls,
991 &state.fork_indexes,
992 requests,
993 )
994 .map_err(|err| enrich_dependency_error(err, package, &version, &state.pubgrub))?;
995
996 state.add_package_version_dependencies(
997 package,
998 &version,
999 fork.dependencies,
1000 &self.index,
1001 &self.installed_packages,
1002 );
1003 Ok(state)
1004 }
1005 }
1006 }
1007
1008 fn prefetch_forks(
1010 &self,
1011 state: &ForkState,
1012 forks: &[Fork],
1013 requests: &MetadataRequests,
1014 ) -> Result<(), ResolveError> {
1015 for fork in forks {
1016 let mut fork_urls = state.fork_urls.clone();
1019 let mut fork_indexes = state.fork_indexes.clone();
1020 for dependency in &fork.dependencies {
1021 apply_dependency_source(
1022 dependency,
1023 &fork.env,
1024 &mut fork_urls,
1025 &mut fork_indexes,
1026 &self.urls,
1027 &self.indexes,
1028 &self.git,
1029 )?;
1030 }
1031 self.visit_dependencies(&fork.dependencies, &fork_urls, &fork_indexes, requests)?;
1032 }
1033 Ok(())
1034 }
1035
1036 fn visit_dependencies(
1038 &self,
1039 dependencies: &[PubGrubDependency],
1040 fork_urls: &ForkUrls,
1041 fork_indexes: &ForkIndexes,
1042 requests: &MetadataRequests,
1043 ) -> Result<(), ResolveError> {
1044 for dependency in dependencies {
1045 let PubGrubDependency {
1046 package,
1047 version: _,
1048 parent: _,
1049 source: _,
1050 } = dependency;
1051 let Some(source) =
1052 PackageSource::for_prefetch(package, fork_urls, fork_indexes, &self.urls)
1053 else {
1054 continue;
1055 };
1056 self.request_package(package, source, requests)?;
1057 }
1058 Ok(())
1059 }
1060
1061 fn request_package(
1062 &self,
1063 package: &PubGrubPackage,
1064 source: PackageSource<'_>,
1065 requests: &MetadataRequests,
1066 ) -> Result<(), ResolveError> {
1067 let Some(name) = package.name_no_root() else {
1069 return Ok(());
1070 };
1071
1072 match source {
1073 PackageSource::Url(url) => {
1074 if !self.hasher.allows_url(&url.verbatim) {
1076 return Err(ResolveError::UnhashedPackage(name.clone()));
1077 }
1078
1079 let dist = Dist::from_url(name.clone(), url.clone())?;
1081 requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1082 }
1083 PackageSource::Registry(index) => {
1084 requests.enqueue_package(name, index)?;
1085 }
1086 }
1087 Ok(())
1088 }
1089
1090 fn pre_visit<'data>(
1093 packages: impl Iterator<
1094 Item = (
1095 Id<PubGrubPackage>,
1096 &'data PubGrubPackage,
1097 &'data Range<Version>,
1098 ),
1099 >,
1100 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1101 urls: &Urls,
1102 indexes: &Indexes,
1103 python_requirement: &PythonRequirement,
1104 requests: &MetadataRequests,
1105 ) -> Result<(), ResolveError> {
1106 for (id, package, range) in packages {
1109 let PubGrubPackageInner::Package {
1110 name,
1111 kind: PackageNodeKind::Base,
1112 marker: MarkerTree::TRUE,
1113 } = &**package
1114 else {
1115 continue;
1116 };
1117 if urls.any_url(name) {
1120 continue;
1121 }
1122 if indexes.contains_key(name) {
1124 continue;
1125 }
1126 if pre_visited.get(&id) == Some(range) {
1129 continue;
1130 }
1131 pre_visited.insert(id, range.clone());
1132 requests.prefetch(name, range, python_requirement)?;
1133 }
1134 Ok(())
1135 }
1136
1137 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1145 fn choose_version<'index>(
1146 &self,
1147 package: &PubGrubPackage,
1148 id: Id<PubGrubPackage>,
1149 source: PackageSource<'_>,
1150 range: &Range<Version>,
1151 pins: &mut FilePins<'index>,
1152 preferences: &Preferences,
1153 env: &ResolverEnvironment,
1154 python_requirement: &PythonRequirement,
1155 pubgrub: &State<UvDependencyProvider>,
1156 visited: &mut FxHashSet<PackageName>,
1157 requests: &'index MetadataRequests,
1158 ) -> Result<Option<ResolverVersion>, ResolveError> {
1159 match &**package {
1160 PubGrubPackageInner::Root(_) => {
1161 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1162 }
1163
1164 PubGrubPackageInner::Python(_) => {
1165 Ok(None)
1168 }
1169
1170 PubGrubPackageInner::System(_) => {
1171 let Some(version) = range.as_singleton() else {
1174 return Ok(None);
1175 };
1176 Ok(Some(ResolverVersion::Unforked(version.clone())))
1177 }
1178
1179 PubGrubPackageInner::Marker { name, .. }
1180 | PubGrubPackageInner::Extra { name, .. }
1181 | PubGrubPackageInner::Group { name, .. }
1182 | PubGrubPackageInner::Package { name, .. } => match source {
1183 PackageSource::Url(url) => self.choose_version_url(
1184 id,
1185 name,
1186 range,
1187 url,
1188 env,
1189 python_requirement,
1190 pubgrub,
1191 pins,
1192 requests,
1193 ),
1194 PackageSource::Registry(index) => self.choose_version_registry(
1195 package,
1196 id,
1197 name,
1198 index,
1199 range,
1200 preferences,
1201 env,
1202 python_requirement,
1203 pubgrub,
1204 pins,
1205 visited,
1206 requests,
1207 ),
1208 },
1209 }
1210 }
1211
1212 fn choose_version_url<'index>(
1215 &self,
1216 id: Id<PubGrubPackage>,
1217 name: &PackageName,
1218 range: &Range<Version>,
1219 url: &VerbatimParsedUrl,
1220 env: &ResolverEnvironment,
1221 python_requirement: &PythonRequirement,
1222 pubgrub: &State<UvDependencyProvider>,
1223 pins: &mut FilePins<'index>,
1224 requests: &'index MetadataRequests,
1225 ) -> Result<Option<ResolverVersion>, ResolveError> {
1226 debug!(
1227 "Searching for a compatible version of {name} @ `{}` ({range})",
1228 url.verbatim
1229 );
1230
1231 let dist = Dist::from_url(name.clone(), url.clone())?;
1232 let registered = requests.metadata(&dist)?;
1233 let response = registered.wait();
1234
1235 let metadata = match &*response {
1237 MetadataResponse::Found(archive) => &archive.metadata,
1238 MetadataResponse::Unavailable(reason) => {
1239 self.unavailable_packages
1240 .pin()
1241 .insert(name.clone(), reason.into());
1242 return Ok(None);
1243 }
1244 MetadataResponse::Error(dist, err) => {
1247 return Err(ResolveError::Dist(
1248 DistErrorKind::from_requested_dist(dist, &**err),
1249 dist.clone(),
1250 DerivationChain::default(),
1251 err.clone(),
1252 ));
1253 }
1254 };
1255
1256 let version = &metadata.version;
1257
1258 if !range.contains(version) {
1260 return Ok(None);
1261 }
1262
1263 if let Dist::Built(dist) = &dist {
1265 let filename = dist.wheel_filename();
1266
1267 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1269 {
1270 let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1271 for environment_marker in self.options.artifact_environments.iter().copied() {
1274 if env.included_by_marker(environment_marker)
1276 && env.included_by_marker(
1277 find_environments(id, pubgrub).and(environment_marker),
1278 )
1279 {
1280 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1282 return Ok(Some(ResolverVersion::Unavailable(
1283 version.clone(),
1284 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1285 IncompatibleWheel::MissingPlatform(environment_marker),
1286 )),
1287 )));
1288 }
1289 }
1290 }
1291 }
1292
1293 let incompatible_tag = if filename.variant().is_some() {
1294 Some(IncompatibleTag::Variant)
1296 } else if !python_requirement.target().matches_wheel_tag(filename) {
1297 Some(IncompatibleTag::AbiPythonVersion)
1298 } else {
1299 None
1300 };
1301 if let Some(tag) = incompatible_tag {
1302 return Ok(Some(ResolverVersion::Unavailable(
1303 filename.version.clone(),
1304 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1305 IncompatibleWheel::Tag(tag),
1306 )),
1307 )));
1308 }
1309 }
1310
1311 if let Some(requires_python) = metadata.requires_python.as_ref() {
1313 if !python_requirement.target().is_contained_by(requires_python) {
1314 let kind = if python_requirement.installed() == python_requirement.target() {
1315 PythonRequirementKind::Installed
1316 } else {
1317 PythonRequirementKind::Target
1318 };
1319 return Ok(Some(ResolverVersion::Unavailable(
1320 version.clone(),
1321 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1322 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1323 )),
1324 )));
1325 }
1326 }
1327
1328 pins.insert_url(name, version, registered);
1329 Ok(Some(ResolverVersion::Unforked(version.clone())))
1330 }
1331
1332 fn choose_version_registry<'index>(
1335 &self,
1336 package: &PubGrubPackage,
1337 id: Id<PubGrubPackage>,
1338 name: &PackageName,
1339 index: Option<&IndexMetadata>,
1340 range: &Range<Version>,
1341 preferences: &Preferences,
1342 env: &ResolverEnvironment,
1343 python_requirement: &PythonRequirement,
1344 pubgrub: &State<UvDependencyProvider>,
1345 pins: &mut FilePins<'index>,
1346 visited: &mut FxHashSet<PackageName>,
1347 requests: &'index MetadataRequests,
1348 ) -> Result<Option<ResolverVersion>, ResolveError> {
1349 let versions_response = requests.request_package(name, index)?.wait();
1351 let index = index.map(IndexMetadata::url);
1352 visited.insert(name.clone());
1353
1354 let version_maps = match *versions_response {
1355 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1356 VersionsResponse::NoIndex => {
1357 self.unavailable_packages
1358 .pin()
1359 .insert(name.clone(), UnavailablePackage::NoIndex);
1360 &[]
1361 }
1362 VersionsResponse::Offline => {
1363 self.unavailable_packages
1364 .pin()
1365 .insert(name.clone(), UnavailablePackage::Offline);
1366 &[]
1367 }
1368 VersionsResponse::NotFound => {
1369 self.unavailable_packages
1370 .pin()
1371 .insert(name.clone(), UnavailablePackage::NotFound);
1372 &[]
1373 }
1374 };
1375
1376 debug!("Searching for a compatible version of {package} ({range})");
1377
1378 let Some(candidate) = self.selector.select(
1380 name,
1381 range,
1382 version_maps,
1383 preferences,
1384 &self.installed_packages,
1385 &self.exclusions,
1386 index,
1387 env,
1388 self.tags.as_ref(),
1389 ) else {
1390 return Ok(None);
1392 };
1393
1394 let variant = if env.marker_environment().is_some()
1397 && uv_preview::is_enabled(PreviewFeature::WheelVariants)
1398 && let Some(prioritized) = candidate.prioritized()
1399 {
1400 self.select_wheel_variant(candidate.version(), prioritized, version_maps, requests)?
1401 } else {
1402 WheelVariantSelection::default()
1403 };
1404 let candidate = if let Some(prioritized) = &variant.distribution {
1405 candidate.with_prioritized_dist(prioritized)
1406 } else if let Some(reason) = &variant.unavailable {
1407 return Ok(Some(ResolverVersion::Unavailable(
1408 candidate.version().clone(),
1409 self.record_unavailable_metadata(name, candidate.version(), reason),
1410 )));
1411 } else {
1412 candidate
1413 };
1414
1415 let dist = match candidate.dist() {
1416 CandidateDist::Compatible(dist) => dist,
1417 CandidateDist::Incompatible {
1418 incompatible_dist: incompatibility,
1419 prioritized_dist: _,
1420 } => {
1421 return Ok(Some(ResolverVersion::Unavailable(
1423 candidate.version().clone(),
1424 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1427 )));
1428 }
1429 };
1430
1431 if let Some((requires_python, incompatibility)) =
1433 Self::check_requires_python(dist, python_requirement)
1434 {
1435 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1436 if env.marker_environment().is_none() {
1437 let forks = fork_version_by_python_requirement(
1438 requires_python,
1439 python_requirement,
1440 env,
1441 );
1442 if !forks.is_empty() {
1443 debug!(
1444 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1445 python_requirement.target(),
1446 requires_python,
1447 name,
1448 candidate.version(),
1449 forks
1450 .iter()
1451 .map(ToString::to_string)
1452 .collect::<Vec<_>>()
1453 .join(", ")
1454 );
1455 let forks = forks
1456 .into_iter()
1457 .map(|env| VersionFork {
1458 env,
1459 id,
1460 version: None,
1461 })
1462 .collect();
1463 return Ok(Some(ResolverVersion::Forked(forks)));
1464 }
1465 }
1466 }
1467
1468 return Ok(Some(ResolverVersion::Unavailable(
1469 candidate.version().clone(),
1470 UnavailableVersion::IncompatibleDist(incompatibility),
1471 )));
1472 }
1473
1474 if env.marker_environment().is_none()
1478 && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1479 && !dist
1480 .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1481 {
1482 return Ok(Some(ResolverVersion::Unavailable(
1483 candidate.version().clone(),
1484 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1485 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1486 )),
1487 )));
1488 }
1489
1490 if let Some(forked) = self.fork_version_registry(
1492 &candidate,
1493 dist,
1494 version_maps,
1495 package,
1496 id,
1497 name,
1498 index,
1499 range,
1500 preferences,
1501 env,
1502 pubgrub,
1503 pins,
1504 requests,
1505 )? {
1506 return Ok(Some(forked));
1507 }
1508
1509 let filename = match dist.for_installation() {
1510 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1511 .filename()
1512 .unwrap_or(Cow::Borrowed("unknown filename")),
1513 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1514 .filename()
1515 .unwrap_or(Cow::Borrowed("unknown filename")),
1516 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1517 };
1518
1519 debug!(
1520 "Selecting: {}=={} [{}] ({})",
1521 name,
1522 candidate.version(),
1523 candidate.choice_kind(),
1524 filename,
1525 );
1526 self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1527
1528 let version = candidate.version().clone();
1529 Ok(Some(ResolverVersion::Unforked(version)))
1530 }
1531
1532 fn select_wheel_variant<'a>(
1534 &self,
1535 version: &Version,
1536 prioritized: &'a PrioritizedDist,
1537 version_maps: &'a [VersionMap],
1538 requests: &MetadataRequests,
1539 ) -> Result<WheelVariantSelection<'a>, ResolveError> {
1540 let candidates = if self.selector.index_strategy() == &IndexStrategy::UnsafeBestMatch {
1544 Either::Left(version_maps.iter().filter_map(|map| map.get(version)))
1545 } else {
1546 Either::Right(std::iter::once(prioritized))
1547 };
1548
1549 let mut selection = WheelVariantSelection::default();
1550 for dist in candidates {
1551 let mut pending = Vec::new();
1557 for variants_json in dist.variants_json_for_selection() {
1558 let request = requests.request_variants_json(variants_json)?;
1559 pending.push((&variants_json.index, request));
1560 }
1561 let responses: Vec<_> = pending
1562 .into_iter()
1563 .map(|(index, request)| (index, request.wait()))
1564 .collect();
1565 let mut metadata = Vec::with_capacity(responses.len());
1566 for (index, response) in &responses {
1567 match response.as_ref() {
1568 Ok(content) => metadata.push((*index, content)),
1569 Err(reason) => {
1570 selection.unavailable.get_or_insert_with(|| reason.clone());
1572 }
1573 }
1574 }
1575 if let Some(variant) = dist.prioritize_variants(&metadata) {
1578 selection.distribution = Some(Cow::Owned(variant));
1579 break;
1580 }
1581 if dist.get().is_some() {
1582 selection.distribution = Some(Cow::Borrowed(dist));
1583 break;
1584 }
1585 }
1586 Ok(selection)
1587 }
1588
1589 fn fork_version_registry<'index>(
1602 &self,
1603 candidate: &Candidate,
1604 dist: &CompatibleDist,
1605 version_maps: &[VersionMap],
1606 package: &PubGrubPackage,
1607 id: Id<PubGrubPackage>,
1608 name: &PackageName,
1609 index: Option<&IndexUrl>,
1610 range: &Range<Version>,
1611 preferences: &Preferences,
1612 env: &ResolverEnvironment,
1613 pubgrub: &State<UvDependencyProvider>,
1614 pins: &mut FilePins<'index>,
1615 requests: &'index MetadataRequests,
1616 ) -> Result<Option<ResolverVersion>, ResolveError> {
1617 let Some(fork_markers) = env.fork_markers() else {
1619 return Ok(None);
1620 };
1621
1622 let artifact_markers = dist.implied_markers();
1623 if artifact_markers.is_true() {
1624 return Ok(None);
1625 }
1626
1627 for marker in self.options.artifact_environments.iter().copied() {
1630 if env.included_by_marker(marker) {
1632 if !env.included_by_marker(artifact_markers.and(marker))
1634 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1635 {
1636 let coverage = dist.implied_markers().and(fork_markers);
1641 let split = marker.restrict(marker.or(coverage));
1642 let Some((left, right)) = fork_version_by_marker(env, split) else {
1643 return Ok(Some(ResolverVersion::Unavailable(
1644 candidate.version().clone(),
1645 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1646 IncompatibleWheel::MissingPlatform(marker),
1647 )),
1648 )));
1649 };
1650
1651 debug!(
1652 "Forking on required platform `{}` for {}=={} ({})",
1653 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1654 name,
1655 candidate.version(),
1656 [&left, &right]
1657 .iter()
1658 .map(ToString::to_string)
1659 .collect::<Vec<_>>()
1660 .join(", ")
1661 );
1662 let forks = vec![
1663 VersionFork {
1664 env: left,
1665 id,
1666 version: None,
1667 },
1668 VersionFork {
1669 env: right,
1670 id,
1671 version: None,
1672 },
1673 ];
1674 return Ok(Some(ResolverVersion::Forked(forks)));
1675 }
1676 }
1677 }
1678
1679 if !candidate.version().is_local() {
1681 return Ok(None);
1682 }
1683
1684 debug!(
1685 "Looking at local version: {}=={}",
1686 name,
1687 candidate.version()
1688 );
1689
1690 let range = range.clone().intersection(&Range::singleton(
1692 candidate.version().clone().without_local(),
1693 ));
1694
1695 let Some(base_candidate) = self.selector.select(
1696 name,
1697 &range,
1698 version_maps,
1699 preferences,
1700 &self.installed_packages,
1701 &self.exclusions,
1702 index,
1703 env,
1704 self.tags.as_ref(),
1705 ) else {
1706 return Ok(None);
1707 };
1708 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1709 return Ok(None);
1710 };
1711
1712 let mut remainder = {
1714 let mut remainder = base_dist.implied_markers();
1715 remainder = remainder.and(artifact_markers.negate());
1716 remainder
1717 };
1718 if remainder.is_false() {
1719 return Ok(None);
1720 }
1721
1722 if !env.included_by_marker(remainder) {
1726 return Ok(None);
1727 }
1728
1729 if !env.included_by_marker(artifact_markers) {
1732 let filename = match dist.for_installation() {
1733 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1734 .filename()
1735 .unwrap_or(Cow::Borrowed("unknown filename")),
1736 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1737 .filename()
1738 .unwrap_or(Cow::Borrowed("unknown filename")),
1739 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1740 };
1741
1742 debug!(
1743 "Preferring non-local candidate: {}=={} [{}] ({})",
1744 name,
1745 base_candidate.version(),
1746 base_candidate.choice_kind(),
1747 filename,
1748 );
1749 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1750
1751 return Ok(Some(ResolverVersion::Unforked(
1752 base_candidate.version().clone(),
1753 )));
1754 }
1755
1756 for value in [
1765 arcstr::literal!("darwin"),
1766 arcstr::literal!("linux"),
1767 arcstr::literal!("win32"),
1768 ] {
1769 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1770 key: MarkerValueString::SysPlatform,
1771 operator: MarkerOperator::Equal,
1772 value,
1773 });
1774 if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1775 remainder = remainder.or(sys_platform);
1776 }
1777 }
1778
1779 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1781 return Ok(None);
1782 };
1783
1784 debug!(
1785 "Forking platform for {}=={} ({})",
1786 name,
1787 candidate.version(),
1788 [&base_env, &local_env]
1789 .iter()
1790 .map(ToString::to_string)
1791 .collect::<Vec<_>>()
1792 .join(", ")
1793 );
1794 self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1795 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1796
1797 let forks = vec![
1798 VersionFork {
1799 env: base_env.clone(),
1800 id,
1801 version: Some(base_candidate.version().clone()),
1802 },
1803 VersionFork {
1804 env: local_env.clone(),
1805 id,
1806 version: Some(candidate.version().clone()),
1807 },
1808 ];
1809 Ok(Some(ResolverVersion::Forked(forks)))
1810 }
1811
1812 fn visit_candidate<'index>(
1814 &self,
1815 candidate: &Candidate,
1816 dist: &CompatibleDist,
1817 package: &PubGrubPackage,
1818 name: &PackageName,
1819 pins: &mut FilePins<'index>,
1820 requests: &'index MetadataRequests,
1821 ) -> Result<(), ResolveError> {
1822 let index = match dist {
1823 CompatibleDist::InstalledDist(_) => None,
1824 CompatibleDist::SourceDist { sdist, .. }
1825 | CompatibleDist::IncompatibleWheel { sdist, .. } => Some(&sdist.index),
1826 CompatibleDist::CompatibleWheel { wheel, .. } => Some(&wheel.index),
1827 };
1828 let variants_json = if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1829 && let Some(index) = index
1830 && let Some(variants_json) = candidate.prioritized().and_then(|prioritized| {
1831 prioritized
1832 .variants_json()
1833 .iter()
1834 .find(|variants_json| &variants_json.index == index)
1835 }) {
1836 Some(requests.request_variants_json(variants_json)?)
1837 } else {
1838 None
1839 };
1840
1841 let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1842 && self.dependency_mode.is_transitive()
1843 {
1844 Some(|| {
1845 requests.request_metadata(
1846 MetadataRequest::Resolved(dist.for_resolution()),
1847 |request| {
1848 if name != request.name() {
1849 return Err(ResolveError::MismatchedPackageName {
1850 request: "distribution",
1851 expected: name.clone(),
1852 actual: request.name().clone(),
1853 });
1854 }
1855 if !self
1857 .hasher
1858 .allows_package(candidate.name(), candidate.version())
1859 {
1860 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1861 }
1862 Ok(())
1863 },
1864 )
1865 })
1866 } else {
1867 None
1868 };
1869 pins.insert(candidate, dist, variants_json, request)
1870 }
1871
1872 fn check_requires_python<'dist>(
1875 dist: &'dist CompatibleDist,
1876 python_requirement: &PythonRequirement,
1877 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1878 let requires_python = dist.requires_python()?;
1879 if python_requirement.target().is_contained_by(requires_python) {
1880 None
1881 } else {
1882 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1883 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1884 requires_python.clone(),
1885 if python_requirement.installed() == python_requirement.target() {
1886 PythonRequirementKind::Installed
1887 } else {
1888 PythonRequirementKind::Target
1889 },
1890 ))
1891 } else {
1892 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1893 requires_python.clone(),
1894 if python_requirement.installed() == python_requirement.target() {
1895 PythonRequirementKind::Installed
1896 } else {
1897 PythonRequirementKind::Target
1898 },
1899 ))
1900 };
1901 Some((requires_python, incompatibility))
1902 }
1903 }
1904
1905 fn get_dependencies_forking(
1907 &self,
1908 id: Id<PubGrubPackage>,
1909 package: &PubGrubPackage,
1910 version: &Version,
1911 pins: &FilePins,
1912 env: &ResolverEnvironment,
1913 python_requirement: &PythonRequirement,
1914 pubgrub: &State<UvDependencyProvider>,
1915 ) -> Result<ForkedDependencies, ResolveError> {
1916 let dependencies =
1917 self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1918 if env.marker_environment().is_some() {
1919 Ok(ForkedDependencies::from_dependencies_platform_specific(
1920 dependencies,
1921 ))
1922 } else {
1923 Ok(ForkedDependencies::from_dependencies_universal(
1924 dependencies,
1925 env,
1926 python_requirement,
1927 &self.conflicts,
1928 ))
1929 }
1930 }
1931
1932 #[instrument(skip_all, fields(%package, %version))]
1934 fn get_dependencies(
1935 &self,
1936 id: Id<PubGrubPackage>,
1937 package: &PubGrubPackage,
1938 version: &Version,
1939 pins: &FilePins,
1940 env: &ResolverEnvironment,
1941 python_requirement: &PythonRequirement,
1942 pubgrub: &State<UvDependencyProvider>,
1943 ) -> Result<Dependencies, ResolveError> {
1944 let expander =
1945 RequirementExpander::new(&self.constraints, &self.modifiers, env, python_requirement);
1946 let dependencies = match &**package {
1947 PubGrubPackageInner::Root(_) => {
1948 let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1949
1950 PubGrubDependency::from_requirements(
1951 &self.conflicts,
1952 requirements,
1953 None,
1954 Some(package),
1955 )
1956 }
1957
1958 PubGrubPackageInner::Package {
1959 name,
1960 kind,
1961 marker: _,
1962 } => {
1963 if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1964 && let Some(variants_json) = pins.variants_json(name, version)
1965 && let Err(reason) = &*variants_json.wait()
1966 {
1967 return Ok(Dependencies::Unavailable(
1968 self.record_unavailable_metadata(name, version, reason),
1969 ));
1970 }
1971
1972 if self.dependency_mode.is_direct() {
1974 return Ok(Dependencies::Unforkable(Vec::default()));
1975 }
1976
1977 let registered = pins
1978 .metadata(name, version)
1979 .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1980
1981 if self.dependency_mode.is_transitive()
1983 && self.unavailable_packages.pin().contains_key(name)
1984 && self.installed_packages.get_packages(name).is_empty()
1985 {
1986 debug_assert!(
1987 false,
1988 "Dependencies were requested for a package that is not available"
1989 );
1990 return Err(ResolveError::PackageUnavailable(name.clone()));
1991 }
1992
1993 let response = registered.wait();
1995
1996 let metadata = match &*response {
1997 MetadataResponse::Found(archive) => &archive.metadata,
1998 MetadataResponse::Unavailable(reason) => {
1999 return Ok(Dependencies::Unavailable(
2000 self.record_unavailable_metadata(name, version, reason),
2001 ));
2002 }
2003 MetadataResponse::Error(dist, err) => {
2004 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
2005 .unwrap_or_default();
2006 return Err(ResolveError::Dist(
2007 DistErrorKind::from_requested_dist(dist, &**err),
2008 dist.clone(),
2009 chain,
2010 err.clone(),
2011 ));
2012 }
2013 };
2014
2015 if let Some(requires_python) = &metadata.requires_python {
2018 if !python_requirement.target().is_contained_by(requires_python) {
2019 return Ok(Dependencies::RequiresPython(requires_python.clone()));
2020 }
2021 }
2022
2023 let system_dependencies = self
2025 .options
2026 .torch_backend
2027 .as_ref()
2028 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
2029 .filter(|torch_backend| torch_backend.has_system_dependency(name))
2030 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
2031 .map(IndexUrl::url)
2032 .and_then(SystemDependency::from_index)
2033 .into_iter()
2034 .inspect(|system_dependency| {
2035 debug!(
2036 "Adding system dependency `{}` for `{package}@{version}`",
2037 system_dependency
2038 );
2039 })
2040 .map(PubGrubDependency::from);
2041
2042 let (requirements, context) = match kind {
2043 PackageNodeKind::Group(group) => (
2044 metadata
2045 .dependency_groups
2046 .get(group)
2047 .map_or(&[][..], AsRef::as_ref),
2048 RequirementContext::Group { name, version },
2049 ),
2050 PackageNodeKind::Extra(extra) => (
2051 metadata.requires_dist.as_ref(),
2052 RequirementContext::Extra {
2053 name,
2054 version,
2055 extra,
2056 },
2057 ),
2058 PackageNodeKind::Base => (
2059 metadata.requires_dist.as_ref(),
2060 RequirementContext::Package { name, version },
2061 ),
2062 };
2063 let requirements = expander.expand(requirements, context);
2064
2065 PubGrubDependency::from_requirements(
2066 &self.conflicts,
2067 requirements,
2068 kind.group(),
2069 Some(package),
2070 )
2071 .map(|mut dependencies| {
2072 dependencies.extend(system_dependencies);
2073 dependencies
2074 })
2075 }
2076
2077 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2078
2079 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2080
2081 PubGrubPackageInner::Marker { name, marker } => {
2083 return Ok(Dependencies::Unforkable(
2084 [MarkerTree::TRUE, *marker]
2085 .into_iter()
2086 .map(move |marker| PubGrubDependency {
2087 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2088 name: name.clone(),
2089 kind: PackageNodeKind::Base,
2090 marker,
2091 }),
2092 version: Range::singleton(version.clone()),
2093 parent: None,
2094 source: DependencySource::Unspecified,
2095 })
2096 .collect(),
2097 ));
2098 }
2099
2100 PubGrubPackageInner::Extra {
2102 name,
2103 extra,
2104 marker,
2105 } => {
2106 return Ok(Dependencies::Unforkable(
2107 [MarkerTree::TRUE, *marker]
2108 .into_iter()
2109 .dedup()
2110 .flat_map(move |marker| {
2111 [None, Some(extra)]
2112 .into_iter()
2113 .map(move |extra| PubGrubDependency {
2114 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2115 name: name.clone(),
2116 kind: extra
2117 .cloned()
2118 .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
2119 marker,
2120 }),
2121 version: Range::singleton(version.clone()),
2122 parent: None,
2123 source: DependencySource::Unspecified,
2124 })
2125 })
2126 .collect(),
2127 ));
2128 }
2129
2130 PubGrubPackageInner::Group {
2132 name,
2133 group,
2134 marker,
2135 } => {
2136 return Ok(Dependencies::Unforkable(
2137 [MarkerTree::TRUE, *marker]
2138 .into_iter()
2139 .dedup()
2140 .map(|marker| PubGrubDependency {
2141 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2142 name: name.clone(),
2143 kind: PackageNodeKind::Group(group.clone()),
2144 marker,
2145 }),
2146 version: Range::singleton(version.clone()),
2147 parent: None,
2148 source: DependencySource::Unspecified,
2149 })
2150 .collect(),
2151 ));
2152 }
2153 };
2154 Ok(match dependencies {
2155 Ok(dependencies) => Dependencies::Available(dependencies),
2156 Err(requirement) => {
2157 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2158 }
2159 })
2160 }
2161
2162 fn record_unavailable_metadata(
2163 &self,
2164 name: &PackageName,
2165 version: &Version,
2166 reason: &MetadataUnavailable,
2167 ) -> UnavailableVersion {
2168 let unavailable_version = UnavailableVersion::from(reason);
2169 let message = unavailable_version.singular_message();
2170 if let Some(error) = reason.source() {
2171 warn!("{name} {message}: {error}");
2172 } else {
2173 warn!("{name} {message}");
2174 }
2175 let incomplete_packages = self.incomplete_packages.pin();
2176 let versions = incomplete_packages.get_or_insert(
2177 name.clone(),
2178 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
2179 );
2180 versions.pin().insert(version.clone(), reason.clone());
2181 unavailable_version
2182 }
2183
2184 async fn fetch<Provider: ResolverProvider>(
2186 self: Arc<Self>,
2187 provider: Arc<Provider>,
2188 request_stream: Receiver<Request>,
2189 ) -> Result<(), ResolveError> {
2190 let mut response_stream = ReceiverStream::new(request_stream)
2191 .map(|request| self.process_request(request, &*provider).boxed_local())
2192 .buffer_unordered(usize::MAX);
2196
2197 while let Some(response) = response_stream.next().await {
2198 match response? {
2199 Some(Response::Package(name, index, version_map)) => {
2200 trace!("Received package metadata for: {name}");
2201 if let Some(index) = index {
2202 self.index
2203 .explicit()
2204 .done((name, index), Arc::new(version_map));
2205 } else {
2206 self.index.implicit().done(name, Arc::new(version_map));
2207 }
2208 }
2209 Some(Response::Installed { dist, metadata }) => {
2210 trace!("Received installed distribution metadata for: {dist}");
2211 self.index
2212 .distributions()
2213 .done(dist.distribution_id(), Arc::new(metadata));
2214 }
2215 Some(Response::Dist { dist, metadata }) => {
2216 let dist_kind = match dist {
2217 Dist::Built(_) => "built",
2218 Dist::Source(_) => "source",
2219 };
2220 trace!("Received {dist_kind} distribution metadata for: {dist}");
2221 if let MetadataResponse::Unavailable(reason) = &metadata {
2222 let message = UnavailableVersion::from(reason).singular_message();
2223 if let Some(err) = reason.source() {
2224 warn!("`{dist}` {message}: {err}");
2226 } else {
2227 warn!("`{dist}` {message}");
2228 }
2229 }
2230 self.index
2231 .distributions()
2232 .done(dist.distribution_id(), Arc::new(metadata));
2233 }
2234 Some(Response::VariantsJson {
2235 variants_json,
2236 response,
2237 }) => {
2238 trace!("Received variant metadata for: {}", variants_json.filename);
2239 self.index
2240 .variants_json()
2241 .done(variants_json, Arc::new(response));
2242 }
2243 None => {}
2244 }
2245 }
2246
2247 Ok::<(), ResolveError>(())
2248 }
2249
2250 #[instrument(skip_all, fields(%request))]
2251 async fn process_request<Provider: ResolverProvider>(
2252 &self,
2253 request: Request,
2254 provider: &Provider,
2255 ) -> Result<Option<Response>, ResolveError> {
2256 match request {
2257 Request::VariantsJson(variants_json) => {
2258 let response = provider
2259 .fetch_variants_json(
2260 &variants_json.filename.name,
2261 &variants_json.file,
2262 &variants_json.index,
2263 )
2264 .boxed_local()
2265 .await
2266 .map_err(ResolveError::Client)?;
2267 let response = match response {
2268 VariantsJsonResponse::Found(contents) => {
2269 VariantsJsonContent::from_slice(&contents).map_err(|source| {
2270 MetadataUnavailable::InvalidVariantMetadata(Arc::new(
2271 VariantsJsonMetadataError {
2272 filename: variants_json.filename.clone(),
2273 source,
2274 },
2275 ))
2276 })
2277 }
2278 VariantsJsonResponse::Unavailable(reason) => Err(reason),
2279 };
2280 Ok(Some(Response::VariantsJson {
2281 variants_json,
2282 response,
2283 }))
2284 }
2285 Request::Package(package_name, index) => {
2287 let package_versions = provider
2288 .get_package_versions(&package_name, index.as_ref())
2289 .boxed_local()
2290 .await
2291 .map_err(ResolveError::Client)?;
2292
2293 Ok(Some(Response::Package(
2294 package_name,
2295 index.map(IndexMetadata::into_url),
2296 package_versions,
2297 )))
2298 }
2299
2300 Request::Dist(dist) => {
2302 if let Some(version) = dist.version() {
2303 if let Some(index) = dist.index() {
2304 let versions_response = self.index.implicit().get(dist.name());
2306 if let Some(VersionsResponse::Found(version_maps)) =
2307 versions_response.as_deref()
2308 {
2309 for version_map in version_maps {
2310 if version_map.index() == Some(index) {
2311 let Some(metadata) = version_map.get_metadata(version) else {
2312 continue;
2313 };
2314 debug!("Found registry-provided metadata for: {dist}");
2315 return Ok(Some(Response::Dist {
2316 dist,
2317 metadata: MetadataResponse::Found(
2318 ArchiveMetadata::from_metadata23(metadata),
2319 ),
2320 }));
2321 }
2322 }
2323 }
2324
2325 let versions_response = self
2327 .index
2328 .explicit()
2329 .get(&(dist.name().clone(), index.clone()));
2330 if let Some(VersionsResponse::Found(version_maps)) =
2331 versions_response.as_deref()
2332 {
2333 for version_map in version_maps {
2334 let Some(metadata) = version_map.get_metadata(version) else {
2335 continue;
2336 };
2337 debug!("Found registry-provided metadata for: {dist}");
2338 return Ok(Some(Response::Dist {
2339 dist,
2340 metadata: MetadataResponse::Found(
2341 ArchiveMetadata::from_metadata23(metadata),
2342 ),
2343 }));
2344 }
2345 }
2346 }
2347 }
2348
2349 let metadata = provider
2350 .get_or_build_wheel_metadata(&dist)
2351 .boxed_local()
2352 .await?;
2353
2354 if let MetadataResponse::Found(metadata) = &metadata {
2355 if &metadata.metadata.name != dist.name() {
2356 return Err(ResolveError::MismatchedPackageName {
2357 request: "distribution metadata",
2358 expected: dist.name().clone(),
2359 actual: metadata.metadata.name.clone(),
2360 });
2361 }
2362 }
2363
2364 Ok(Some(Response::Dist { dist, metadata }))
2365 }
2366
2367 Request::Installed(dist) => {
2368 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2369
2370 if let MetadataResponse::Found(metadata) = &metadata {
2371 if &metadata.metadata.name != dist.name() {
2372 return Err(ResolveError::MismatchedPackageName {
2373 request: "installed metadata",
2374 expected: dist.name().clone(),
2375 actual: metadata.metadata.name.clone(),
2376 });
2377 }
2378 }
2379
2380 Ok(Some(Response::Installed { dist, metadata }))
2381 }
2382
2383 Request::Prefetch(package_name, range, python_requirement) => {
2385 let versions_response = self
2387 .index
2388 .implicit()
2389 .get_registered(package_name.clone())
2390 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2391 .wait()
2392 .await;
2393
2394 let version_map = match *versions_response {
2395 VersionsResponse::Found(ref version_map) => version_map,
2396 VersionsResponse::NoIndex => {
2398 self.unavailable_packages
2399 .pin()
2400 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2401
2402 return Ok(None);
2403 }
2404 VersionsResponse::Offline => {
2405 self.unavailable_packages
2406 .pin()
2407 .insert(package_name.clone(), UnavailablePackage::Offline);
2408
2409 return Ok(None);
2410 }
2411 VersionsResponse::NotFound => {
2412 self.unavailable_packages
2413 .pin()
2414 .insert(package_name.clone(), UnavailablePackage::NotFound);
2415
2416 return Ok(None);
2417 }
2418 };
2419
2420 let env = ResolverEnvironment::universal(vec![]);
2422
2423 let Some(candidate) = self.selector.select(
2426 &package_name,
2427 &range,
2428 version_map,
2429 &self.preferences,
2430 &self.installed_packages,
2431 &self.exclusions,
2432 None,
2433 &env,
2434 self.tags.as_ref(),
2435 ) else {
2436 return Ok(None);
2437 };
2438
2439 let Some(dist) = candidate.compatible() else {
2441 return Ok(None);
2442 };
2443
2444 for version_map in version_map {
2446 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2447 let dist = dist.for_resolution();
2448 if version_map.index() == dist.index() {
2449 debug!("Found registry-provided metadata for: {dist}");
2450
2451 let metadata =
2452 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2453
2454 let dist = dist.to_owned();
2455 if &package_name != dist.name() {
2456 return Err(ResolveError::MismatchedPackageName {
2457 request: "distribution",
2458 expected: package_name,
2459 actual: dist.name().clone(),
2460 });
2461 }
2462
2463 let response = match dist {
2464 ResolvedDist::Installable { dist, .. } => Response::Dist {
2465 dist: (*dist).clone(),
2466 metadata,
2467 },
2468 ResolvedDist::Installed { dist } => Response::Installed {
2469 dist: (*dist).clone(),
2470 metadata,
2471 },
2472 };
2473
2474 return Ok(Some(response));
2475 }
2476 }
2477 }
2478
2479 if dist.wheel().is_none() {
2483 if !self.selector.use_highest_version(&package_name, &env) {
2484 if let Some((lower, _)) = range.iter().next() {
2485 if lower == Bound::Unbounded {
2486 debug!(
2487 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2488 );
2489 return Ok(None);
2490 }
2491 }
2492 }
2493 }
2494
2495 if let Some(requires_python) = dist.requires_python() {
2497 if !python_requirement.target().is_contained_by(requires_python) {
2498 return Ok(None);
2499 }
2500 }
2501
2502 if !self
2504 .hasher
2505 .allows_package(candidate.name(), candidate.version())
2506 {
2507 return Ok(None);
2508 }
2509
2510 let dist = dist.for_resolution();
2512 if self.index.distributions().register(dist.distribution_id()) {
2513 let dist = dist.to_owned();
2514 if &package_name != dist.name() {
2515 return Err(ResolveError::MismatchedPackageName {
2516 request: "distribution",
2517 expected: package_name,
2518 actual: dist.name().clone(),
2519 });
2520 }
2521
2522 let response = match dist {
2523 ResolvedDist::Installable { dist, .. } => {
2524 let metadata = provider
2525 .get_or_build_wheel_metadata(&dist)
2526 .boxed_local()
2527 .await?;
2528
2529 Response::Dist {
2530 dist: (*dist).clone(),
2531 metadata,
2532 }
2533 }
2534 ResolvedDist::Installed { dist } => {
2535 let metadata =
2536 provider.get_installed_metadata(&dist).boxed_local().await?;
2537
2538 Response::Installed {
2539 dist: (*dist).clone(),
2540 metadata,
2541 }
2542 }
2543 };
2544
2545 Ok(Some(response))
2546 } else {
2547 Ok(None)
2548 }
2549 }
2550 }
2551 }
2552
2553 fn convert_no_solution_err(
2554 &self,
2555 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2556 fork_urls: ForkUrls,
2557 fork_indexes: ForkIndexes,
2558 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2559 env: ResolverEnvironment,
2560 current_environment: MarkerEnvironment,
2561 visited: &FxHashSet<PackageName>,
2562 ) -> ResolveError {
2563 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2564 err,
2565 ));
2566 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2567
2568 let mut unavailable_packages = FxHashMap::default();
2569 for package in derivation_tree_packages(&err) {
2570 if let PubGrubPackageInner::Package { name, .. } = &**package {
2571 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2572 unavailable_packages.insert(name.clone(), reason.clone());
2573 }
2574 }
2575 }
2576
2577 let mut incomplete_packages = FxHashMap::default();
2578 let incomplete_packages_cache = self.incomplete_packages.pin();
2579 for package in derivation_tree_packages(&err) {
2580 if let PubGrubPackageInner::Package { name, .. } = &**package
2581 && let Some(versions) = incomplete_packages_cache.get(name)
2582 {
2583 for (version, reason) in &versions.pin() {
2584 incomplete_packages
2585 .entry(name.clone())
2586 .or_insert_with(BTreeMap::default)
2587 .insert(version.clone(), reason.clone());
2588 }
2589 }
2590 }
2591
2592 let mut available_indexes = FxHashMap::default();
2593 let mut included_versions = FxHashMap::default();
2594 let mut available_versions = FxHashMap::default();
2595
2596 let available_version_cutoff: Option<jiff::Timestamp> =
2597 std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2598 .ok()
2599 .and_then(|s| s.parse().ok());
2600
2601 for package in derivation_tree_packages(&err) {
2602 let Some(name) = package.name() else { continue };
2603 if !visited.contains(name) {
2604 continue;
2609 }
2610 let versions_response = if let Some(index) = fork_indexes.get(name) {
2611 self.index
2612 .explicit()
2613 .get(&(name.clone(), index.url().clone()))
2614 } else {
2615 self.index.implicit().get(name)
2616 };
2617 if let Some(response) = versions_response {
2618 if let VersionsResponse::Found(ref version_maps) = *response {
2619 for version_map in version_maps {
2621 let package_included_versions = included_versions
2622 .entry(name.clone())
2623 .or_insert_with(BTreeSet::new);
2624 let package_available_versions = available_versions
2625 .entry(name.clone())
2626 .or_insert_with(BTreeSet::new);
2627
2628 for (version, dists) in version_map.iter(&Ranges::full()) {
2629 let excluded_from_included = || {
2634 let Some(included_version_cutoff) =
2635 version_map.included_version_cutoff()
2636 else {
2637 return false;
2638 };
2639 let Some(prioritized_dist) = dists.prioritized_dist() else {
2640 return true;
2641 };
2642 prioritized_dist.files().all(|file| {
2643 file.upload_time_utc_ms.is_none_or(|upload_time| {
2644 upload_time >= included_version_cutoff.as_millisecond()
2645 })
2646 })
2647 };
2648
2649 if !excluded_from_included() {
2650 package_included_versions.insert(version.clone());
2651 }
2652
2653 let excluded_from_available = || {
2659 let Some(ref exclude_newer) = available_version_cutoff else {
2660 return false;
2661 };
2662 let Some(prioritized_dist) = dists.prioritized_dist() else {
2663 return false;
2664 };
2665 prioritized_dist.files().all(|file| {
2666 file.upload_time_utc_ms.is_some_and(|upload_time| {
2667 upload_time >= exclude_newer.as_millisecond()
2668 })
2669 })
2670 };
2671
2672 if !excluded_from_available() {
2673 package_available_versions.insert(version.clone());
2674 }
2675 }
2676 }
2677
2678 available_indexes
2680 .entry(name.clone())
2681 .or_insert(BTreeSet::new())
2682 .extend(
2683 version_maps
2684 .iter()
2685 .filter_map(|version_map| version_map.index().cloned()),
2686 );
2687 }
2688 }
2689 }
2690
2691 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2692 err,
2693 self.index.clone(),
2694 included_versions,
2695 available_versions,
2696 available_indexes,
2697 self.selector.clone(),
2698 self.python_requirement.clone(),
2699 self.locations.clone(),
2700 self.capabilities.clone(),
2701 unavailable_packages,
2702 incomplete_packages,
2703 fork_urls,
2704 fork_indexes,
2705 env,
2706 current_environment,
2707 self.tags.clone(),
2708 self.workspace_members.clone(),
2709 self.options.clone(),
2710 )))
2711 }
2712
2713 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2714 if let Some(reporter) = self.reporter.as_ref() {
2715 match &**package {
2716 PubGrubPackageInner::Root(_) => {}
2717 PubGrubPackageInner::Python(_) => {}
2718 PubGrubPackageInner::System(_) => {}
2719 PubGrubPackageInner::Marker { .. } => {}
2720 PubGrubPackageInner::Extra { .. } => {}
2721 PubGrubPackageInner::Group { .. } => {}
2722 PubGrubPackageInner::Package { name, .. } => {
2723 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2724 }
2725 }
2726 }
2727 }
2728
2729 fn on_complete(&self) {
2730 if let Some(reporter) = self.reporter.as_ref() {
2731 reporter.on_complete();
2732 }
2733 }
2734}
2735
2736#[derive(Clone, Default)]
2739struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2740
2741impl KnownVersions {
2742 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2755 &'a mut self,
2756 index: &InMemoryIndex,
2757 installed_packages: &InstalledPackages,
2758 source: PackageSource<'_>,
2759 package: &PubGrubPackage,
2760 ) -> Option<&'a [Version]> {
2761 let name = package.name_no_root()?;
2762 let PackageSource::Registry(index_metadata) = source else {
2764 return None;
2765 };
2766 if !self.0.contains_key(name) {
2767 let response = if let Some(index_metadata) = index_metadata {
2768 index
2769 .explicit()
2770 .get(&(name.clone(), index_metadata.url().clone()))?
2771 } else {
2772 index.implicit().get(name)?
2773 };
2774 let VersionsResponse::Found(ref version_maps) = *response else {
2775 return None;
2776 };
2777 let mut versions: Vec<Version> = version_maps
2778 .iter()
2779 .flat_map(|version_map| version_map.included_versions().cloned())
2780 .chain(
2781 installed_packages
2782 .get_packages(name)
2783 .iter()
2784 .map(|dist| dist.version().clone()),
2785 )
2786 .collect();
2787 versions.sort_unstable();
2788 versions.dedup();
2789 self.0.insert(name.clone(), versions.into());
2790 }
2791 Some(&self.0[name][..])
2792 }
2793}
2794
2795#[derive(Clone, Default)]
2797enum ForkContinuation {
2798 #[default]
2800 Propagate,
2801 SelectVersion { package: Id<PubGrubPackage> },
2803 UseVersion {
2805 package: Id<PubGrubPackage>,
2806 version: Version,
2807 },
2808}
2809
2810struct PendingFork<'index> {
2815 state: Rc<ForkState<'index>>,
2816 action: ForkAction,
2817}
2818
2819enum ForkAction {
2820 Initial,
2821 Dependencies { fork: Fork, version: Version },
2822 Version(VersionFork),
2823}
2824
2825#[derive(Clone)]
2827pub(crate) struct ForkState<'index> {
2828 pubgrub: State<UvDependencyProvider>,
2836 continuation: ForkContinuation,
2838 next: Id<PubGrubPackage>,
2840 pins: FilePins<'index>,
2849 fork_urls: ForkUrls,
2855 fork_indexes: ForkIndexes,
2860 priorities: PubGrubPriorities,
2866 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2869 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2871 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2873 known_versions: KnownVersions,
2877 env: ResolverEnvironment,
2892 python_requirement: PythonRequirement,
2905 conflict_tracker: ConflictTracker,
2906 prefetcher: BatchPrefetcher,
2910}
2911
2912impl<'index> ForkState<'index> {
2913 fn new(
2914 pubgrub: State<UvDependencyProvider>,
2915 env: ResolverEnvironment,
2916 python_requirement: PythonRequirement,
2917 prefetcher: BatchPrefetcher,
2918 ) -> Self {
2919 Self {
2920 continuation: ForkContinuation::Propagate,
2921 next: pubgrub.root_package,
2922 pubgrub,
2923 pins: FilePins::default(),
2924 fork_urls: ForkUrls::default(),
2925 fork_indexes: ForkIndexes::default(),
2926 priorities: PubGrubPriorities::default(),
2927 added_dependencies: FxHashMap::default(),
2928 pre_visited: FxHashMap::default(),
2929 selected_versions: FxHashMap::default(),
2930 known_versions: KnownVersions::default(),
2931 env,
2932 python_requirement,
2933 conflict_tracker: ConflictTracker::default(),
2934 prefetcher,
2935 }
2936 }
2937
2938 fn visit_package_version_dependencies(
2941 &mut self,
2942 for_package: Id<PubGrubPackage>,
2943 for_version: &Version,
2944 urls: &Urls,
2945 indexes: &Indexes,
2946 dependencies: &[PubGrubDependency],
2947 git: &GitResolver,
2948 workspace_members: &BTreeSet<PackageName>,
2949 resolution_strategy: &ResolutionStrategy,
2950 ) -> Result<(), ResolveError> {
2951 for dependency in dependencies {
2952 let PubGrubDependency {
2953 package,
2954 version,
2955 parent: _,
2956 source: _,
2957 } = dependency;
2958
2959 let has_url = apply_dependency_source(
2960 dependency,
2961 &self.env,
2962 &mut self.fork_urls,
2963 &mut self.fork_indexes,
2964 urls,
2965 indexes,
2966 git,
2967 )?;
2968
2969 if let Some(name) = self.pubgrub.package_store[for_package]
2970 .name_no_root()
2971 .filter(|name| !workspace_members.contains(name))
2972 {
2973 debug!(
2974 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2975 );
2976 } else {
2977 debug!("Adding direct dependency: {package}{version}");
2979
2980 let missing_lower_bound = version
2982 .bounding_range()
2983 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2984 let strategy_lowest = matches!(
2985 resolution_strategy,
2986 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2987 );
2988
2989 if !has_url && missing_lower_bound && strategy_lowest {
2990 let name = package.name_no_root().unwrap();
2991 let bound_on_other_package = dependencies.iter().any(|other| {
2998 Some(name) == other.package.name()
2999 && !other
3000 .version
3001 .bounding_range()
3002 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3003 });
3004
3005 if !bound_on_other_package {
3006 warn_user_once!(
3007 "The direct dependency `{name}` is unpinned. \
3008 Consider setting a lower bound when using `--resolution lowest` \
3009 or `--resolution lowest-direct` to avoid using outdated versions.",
3010 );
3011 }
3012 }
3013 }
3014
3015 self.priorities.insert(package, version, &self.fork_urls);
3017 if let Some(base_package) = package.base_package() {
3020 self.priorities
3021 .insert(&base_package, version, &self.fork_urls);
3022 }
3023 }
3024
3025 Ok(())
3026 }
3027
3028 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3034 &mut self,
3035 for_package: Id<PubGrubPackage>,
3036 for_version: &Version,
3037 dependencies: Vec<PubGrubDependency>,
3038 index: &InMemoryIndex,
3039 installed_packages: &InstalledPackages,
3040 ) {
3041 for dependency in &dependencies {
3042 let PubGrubDependency {
3043 package,
3044 version,
3045 parent: _,
3046 source: _,
3047 } = dependency;
3048
3049 let Some(base_package) = package.base_package() else {
3050 continue;
3051 };
3052
3053 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3054 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3055 self.pubgrub.add_dependency(
3058 proxy_package,
3059 version.clone(),
3060 (base_package_id, version.clone()),
3061 );
3062 }
3063
3064 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
3067 let conflict = self.pubgrub.add_package_version_dependencies(
3068 self.next,
3069 for_version.clone(),
3070 versions,
3071 dependencies.into_iter().map(|dependency| {
3072 let PubGrubDependency {
3073 package,
3074 version,
3075 parent: _,
3076 source: _,
3077 } = dependency;
3078 (package, version)
3079 }),
3080 );
3081
3082 if let Some(incompatibility) = conflict {
3085 self.record_conflict(for_package, Some(for_version), &incompatibility);
3086 }
3087 }
3088
3089 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
3091 &mut self,
3092 version: &Version,
3093 index: &InMemoryIndex,
3094 installed_packages: &InstalledPackages,
3095 ) -> Range<Version> {
3096 widen_to_gap(
3097 version,
3098 self.known_versions.get_or_update(
3099 index,
3100 installed_packages,
3101 PackageSource::from_fork(
3102 &self.pubgrub.package_store[self.next],
3103 &self.fork_urls,
3104 &self.fork_indexes,
3105 ),
3106 &self.pubgrub.package_store[self.next],
3107 ),
3108 )
3109 }
3110
3111 fn record_conflict(
3112 &mut self,
3113 affected: Id<PubGrubPackage>,
3114 version: Option<&Version>,
3115 conflict: &ConflictId<UvDependencyProvider>,
3116 ) {
3117 let mut culprit_is_real = false;
3118 for incompatible in self.pubgrub.conflict_packages(conflict) {
3119 if incompatible == affected {
3120 continue;
3121 }
3122 if self.pubgrub.package_store[affected].name()
3123 == self.pubgrub.package_store[incompatible].name()
3124 {
3125 continue;
3128 }
3129 culprit_is_real = true;
3130 let culprit_count = self
3131 .conflict_tracker
3132 .culprit
3133 .entry(incompatible)
3134 .or_default();
3135 *culprit_count += 1;
3136 if *culprit_count == CONFLICT_THRESHOLD {
3137 self.conflict_tracker.deprioritize.push(incompatible);
3138 }
3139 }
3140 if culprit_is_real {
3143 if tracing::enabled!(Level::DEBUG) {
3144 let incompatibility = self
3145 .pubgrub
3146 .conflict_packages(conflict)
3147 .map(|package| &self.pubgrub.package_store[package])
3148 .join(", ");
3149 if let Some(version) = version {
3150 debug!(
3151 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3152 self.pubgrub.package_store[affected], version, incompatibility
3153 );
3154 } else {
3155 debug!(
3156 "Recording unit propagation conflict of {} from incompatibility of ({})",
3157 self.pubgrub.package_store[affected], incompatibility
3158 );
3159 }
3160 }
3161
3162 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3163 *affected_count += 1;
3164 if *affected_count == CONFLICT_THRESHOLD {
3165 self.conflict_tracker.prioritize.push(self.next);
3166 }
3167 }
3168 }
3169
3170 fn reprioritize_conflicts(&mut self) {
3174 for package in self.conflict_tracker.prioritize.drain(..) {
3175 let changed = self
3176 .priorities
3177 .mark_conflict_early(&self.pubgrub.package_store[package]);
3178 if changed {
3179 debug!(
3180 "Package {} has too many conflicts (affected), prioritizing",
3181 &self.pubgrub.package_store[package]
3182 );
3183 } else {
3184 debug!(
3185 "Package {} has too many conflicts (affected), already {:?}",
3186 self.pubgrub.package_store[package],
3187 self.priorities.get(&self.pubgrub.package_store[package])
3188 );
3189 }
3190 }
3191
3192 for package in self.conflict_tracker.deprioritize.drain(..) {
3193 let changed = self
3194 .priorities
3195 .mark_conflict_late(&self.pubgrub.package_store[package]);
3196 if changed {
3197 debug!(
3198 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3199 self.pubgrub.package_store[package],
3200 );
3201 let backtrack_level = self.pubgrub.backtrack_package(package);
3202 if let Some(backtrack_level) = backtrack_level {
3203 debug!("Backtracked {backtrack_level} decisions");
3204 } else {
3205 debug!(
3206 "Package {} is not decided, cannot backtrack",
3207 self.pubgrub.package_store[package]
3208 );
3209 }
3210 } else {
3211 debug!(
3212 "Package {} has too many conflicts (culprit), already {:?}",
3213 self.pubgrub.package_store[package],
3214 self.priorities.get(&self.pubgrub.package_store[package])
3215 );
3216 }
3217 }
3218 }
3219
3220 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3228 &mut self,
3229 version: Version,
3230 reason: UnavailableVersion,
3231 index: &InMemoryIndex,
3232 installed_packages: &InstalledPackages,
3233 ) {
3234 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3235
3236 if let UnavailableVersion::IncompatibleDist(
3240 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3241 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3242 ) = reason
3243 {
3244 let package = &self.next;
3245 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3246 PubGrubPackageInner::Python(match kind {
3247 PythonRequirementKind::Installed => PubGrubPython::Installed,
3248 PythonRequirementKind::Target => PubGrubPython::Target,
3249 }),
3250 ));
3251 self.pubgrub.add_dependency(
3252 *package,
3253 versions,
3254 (
3255 python,
3256 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3257 ),
3258 );
3259 self.pubgrub
3260 .partial_solution
3261 .add_decision(self.next, version);
3262 return;
3263 }
3264 self.pubgrub
3265 .add_unavailable(self.next, versions, UnavailableReason::Version(reason));
3266 }
3267
3268 fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3269 self.continuation = continuation;
3270 self
3271 }
3272
3273 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3275 self.selected_versions.clear();
3276 self.env = env;
3277 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3279 debug!("Narrowed `requires-python` bound to: {}", req.target());
3280 self.python_requirement = req;
3281 }
3282 self
3283 }
3284
3285 fn source(
3289 &self,
3290 name: &PackageName,
3291 version: &Version,
3292 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3293 let url = self.fork_urls.get(name);
3294 let index = url
3295 .is_none()
3296 .then(|| {
3297 self.pins
3298 .get(name, version)
3299 .expect("Every package should be pinned")
3300 .index()
3301 })
3302 .flatten();
3303 (url, index)
3304 }
3305
3306 fn into_resolution(
3307 mut self,
3308 ) -> (
3309 Resolution<'index>,
3310 IncompatibilityArena<UvDependencyProvider>,
3311 ) {
3312 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3313 let nodes: FxHashMap<_, _> = solution
3316 .iter()
3317 .filter_map(|(&package, version)| {
3318 if let PubGrubPackageInner::Package {
3319 name,
3320 kind,
3321 marker: MarkerTree::TRUE,
3322 } = &*self.pubgrub.package_store[package]
3323 {
3324 let (url, index) = self.source(name, version);
3325 Some((
3326 ResolutionPackage {
3327 name: name.clone(),
3328 kind: kind.clone(),
3329 url: url.cloned(),
3330 index: index.cloned(),
3331 },
3332 version.clone(),
3333 ))
3334 } else {
3335 None
3336 }
3337 })
3338 .collect();
3339 let nodes: Vec<_> = nodes.into_iter().collect();
3340 let node_indices: FxHashMap<_, _> = nodes
3341 .iter()
3342 .enumerate()
3343 .map(|(index, (package, version))| {
3344 (
3345 (
3346 &package.name,
3347 &package.kind,
3348 package.url.as_ref(),
3349 package.index.as_ref(),
3350 version,
3351 ),
3352 ResolutionNodeId(index),
3353 )
3354 })
3355 .collect();
3356
3357 let edge_count: usize = solution
3358 .keys()
3359 .filter_map(|package| self.pubgrub.dependencies(*package).size_hint().1)
3360 .sum();
3361 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3362 for (package, self_version) in &solution {
3363 let (self_name, self_kind) = match &*self.pubgrub.package_store[*package] {
3364 PubGrubPackageInner::Package { name, kind, .. } => (Some(name), kind),
3365 PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3366 PubGrubPackageInner::Python(_)
3367 | PubGrubPackageInner::System(_)
3368 | PubGrubPackageInner::Marker { .. }
3369 | PubGrubPackageInner::Extra { .. }
3370 | PubGrubPackageInner::Group { .. } => continue,
3371 };
3372 let from = self_name.map(|name| {
3373 let (url, index) = self.source(name, self_version);
3374 node_indices[&(name, self_kind, url, index, self_version)]
3375 });
3376 for dependency in self.pubgrub.dependencies(*package) {
3377 if *package != dependency.dependent {
3378 continue;
3379 }
3380 if !dependency.dependent_versions.contains(self_version) {
3381 continue;
3382 }
3383 let Some(dependency_range) = dependency.dependency_versions else {
3384 continue;
3385 };
3386 let Some(dependency_version) = solution.get(&dependency.dependency) else {
3387 continue;
3388 };
3389 if !dependency_range.contains(dependency_version) {
3390 continue;
3391 }
3392
3393 let dependency_package = &self.pubgrub.package_store[dependency.dependency];
3394
3395 let (name, kind, marker) = match &**dependency_package {
3396 PubGrubPackageInner::Package { name, kind, marker } => {
3397 debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3398
3399 if self_kind.group().is_none() && self_name == Some(name) {
3402 continue;
3403 }
3404 (name, kind.clone(), *marker)
3405 }
3406 PubGrubPackageInner::Marker { name, marker } => {
3407 if self_kind.group().is_none() && self_name == Some(name) {
3408 continue;
3409 }
3410 (name, PackageNodeKind::Base, *marker)
3411 }
3412 PubGrubPackageInner::Extra {
3413 name,
3414 extra,
3415 marker,
3416 } => {
3417 if self_kind.group().is_none() {
3418 debug_assert!(self_name != Some(name), "Extras should be flattened");
3419 }
3420 (name, PackageNodeKind::Extra(extra.clone()), *marker)
3421 }
3422 PubGrubPackageInner::Group {
3423 name,
3424 group,
3425 marker,
3426 } => {
3427 debug_assert!(self_name != Some(name), "Groups should be flattened");
3428 (name, PackageNodeKind::Group(group.clone()), *marker)
3429 }
3430 PubGrubPackageInner::Root(_)
3431 | PubGrubPackageInner::Python(_)
3432 | PubGrubPackageInner::System(_) => continue,
3433 };
3434 let (url, index) = self.source(name, dependency_version);
3435 edges.push(ResolutionDependencyEdge {
3436 from,
3437 to: node_indices[&(name, &kind, url, index, dependency_version)],
3438 marker,
3439 });
3440
3441 if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3444 edges.push(ResolutionDependencyEdge {
3445 from,
3446 to: node_indices
3447 [&(name, &PackageNodeKind::Base, url, index, dependency_version)],
3448 marker,
3449 });
3450 }
3451 }
3452 }
3453
3454 let arena = self.pubgrub.take_incompatibility_arena();
3456 (
3457 Resolution {
3458 nodes,
3459 edges,
3460 pins: self.pins,
3461 env: self.env,
3462 },
3463 arena,
3464 )
3465 }
3466}
3467
3468fn apply_dependency_source(
3472 dependency: &PubGrubDependency,
3473 env: &ResolverEnvironment,
3474 fork_urls: &mut ForkUrls,
3475 fork_indexes: &mut ForkIndexes,
3476 urls: &Urls,
3477 indexes: &Indexes,
3478 git: &GitResolver,
3479) -> Result<bool, ResolveError> {
3480 let Some(name) = dependency.package.name() else {
3481 return Ok(false);
3482 };
3483 let mut has_url = false;
3484 for url in urls.get_url(env, name, dependency.source.verbatim_url(), git)? {
3486 fork_urls.insert(name, url, env)?;
3487 has_url = true;
3488 }
3489 if let Some(index) = dependency.source.explicit_index() {
3490 fork_indexes.insert(name, index, env)?;
3491 }
3492 for index in indexes.get(name, env) {
3493 fork_indexes.insert(name, index, env)?;
3494 }
3495 Ok(has_url)
3496}
3497
3498fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3507 let versions = Range::singleton(version.clone());
3508 match known_versions {
3509 Some(known_versions) if !known_versions.is_empty() => {
3510 versions.widen_versions(known_versions)
3511 }
3512 _ => versions,
3513 }
3514}
3515
3516#[derive(Debug)]
3518#[expect(clippy::large_enum_variant)]
3519pub(crate) enum Request {
3520 Package(PackageName, Option<IndexMetadata>),
3522 Dist(Dist),
3524 Installed(InstalledDist),
3526 VariantsJson(RegistryVariantsJson),
3528 Prefetch(PackageName, Range<Version>, PythonRequirement),
3530}
3531
3532impl<'a> From<ResolvedDistRef<'a>> for Request {
3533 fn from(dist: ResolvedDistRef<'a>) -> Self {
3534 match dist {
3540 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3541 let source = prioritized.source_dist().expect("a source distribution");
3544 assert_eq!(
3545 (&sdist.name, &sdist.version),
3546 (&source.name, &source.version),
3547 "expected chosen sdist to match prioritized sdist"
3548 );
3549 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3550 }
3551 ResolvedDistRef::InstallableRegistryBuiltDist {
3552 wheel, prioritized, ..
3553 } => {
3554 assert_eq!(
3555 Some(&wheel.filename),
3556 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3557 "expected chosen wheel to match best wheel"
3558 );
3559 let built = prioritized.built_dist().expect("at least one wheel");
3562 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3563 }
3564 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3565 }
3566 }
3567}
3568
3569impl Display for Request {
3570 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3571 match self {
3572 Self::Package(package_name, _) => {
3573 write!(f, "Versions {package_name}")
3574 }
3575 Self::Dist(dist) => {
3576 write!(f, "Metadata `{dist}`")
3577 }
3578 Self::Installed(dist) => {
3579 write!(f, "Installed metadata `{dist}`")
3580 }
3581 Self::VariantsJson(variants_json) => {
3582 write!(f, "Variant metadata `{}`", variants_json.filename)
3583 }
3584 Self::Prefetch(package_name, range, _) => {
3585 write!(f, "Prefetch {package_name} {range}")
3586 }
3587 }
3588 }
3589}
3590
3591#[derive(Debug)]
3592#[expect(clippy::large_enum_variant)]
3593enum Response {
3594 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3596 Dist {
3598 dist: Dist,
3599 metadata: MetadataResponse,
3600 },
3601 Installed {
3603 dist: InstalledDist,
3604 metadata: MetadataResponse,
3605 },
3606 VariantsJson {
3608 variants_json: RegistryVariantsJson,
3609 response: ParsedVariantsJsonResponse,
3610 },
3611}
3612
3613enum Dependencies {
3619 Unavailable(UnavailableVersion),
3621 Available(Vec<PubGrubDependency>),
3627 RequiresPython(VersionSpecifiers),
3629 Unforkable(Vec<PubGrubDependency>),
3635}
3636
3637#[derive(Debug)]
3644enum ForkedDependencies {
3645 Unavailable(UnavailableVersion),
3647 Unforked(Vec<PubGrubDependency>),
3651 Forked {
3657 forks: Vec<Fork>,
3658 diverging_packages: BTreeSet<PackageName>,
3660 },
3661 RequiresPython(VersionSpecifiers),
3663}
3664
3665impl ForkedDependencies {
3666 fn from_dependencies_universal(
3673 dependencies: Dependencies,
3674 env: &ResolverEnvironment,
3675 python_requirement: &PythonRequirement,
3676 conflicts: &Conflicts,
3677 ) -> Self {
3678 let deps = match dependencies {
3679 Dependencies::Available(deps) => deps,
3680 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3681 Dependencies::RequiresPython(requires_python) => {
3682 return Self::RequiresPython(requires_python);
3683 }
3684 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3685 };
3686 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3687 for dep in deps {
3688 let name = dep
3689 .package
3690 .name()
3691 .expect("dependency always has a name")
3692 .clone();
3693 name_to_deps.entry(name).or_default().push(dep);
3694 }
3695 let (mut forks, diverging_packages) =
3696 Self::fork(name_to_deps, env, python_requirement, conflicts);
3697 if forks.is_empty() {
3698 Self::Unforked(vec![])
3699 } else if forks.len() == 1 {
3700 Self::Unforked(forks.pop().unwrap().dependencies)
3701 } else {
3702 Self::Forked {
3703 forks,
3704 diverging_packages,
3705 }
3706 }
3707 }
3708
3709 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3712 match dependencies {
3713 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3714 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3715 Dependencies::Unavailable(err) => Self::Unavailable(err),
3716 }
3717 }
3718
3719 fn fork(
3727 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3728 env: &ResolverEnvironment,
3729 python_requirement: &PythonRequirement,
3730 conflicts: &Conflicts,
3731 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3732 let python_marker = python_requirement.to_marker_tree();
3733
3734 let mut forks = vec![Fork::new(env.clone())];
3735 let mut diverging_packages = BTreeSet::new();
3736 for (name, mut deps) in name_to_deps {
3737 assert!(!deps.is_empty(), "every name has at least one dependency");
3738 if let [dep] = deps.as_slice() {
3749 if marker::requires_python(dep.package.marker())
3757 .is_none_or(|bound| !python_requirement.raises(&bound))
3758 {
3759 let dep = deps.pop().unwrap();
3760 let marker = dep.package.marker();
3761 for fork in &mut forks {
3762 if fork.env.included_by_marker(marker) {
3763 fork.add_dependency(dep.clone());
3764 }
3765 }
3766 continue;
3767 }
3768 } else {
3769 if let Some(dep) = deps.first() {
3771 let marker = dep.package.marker();
3772 if deps.iter().all(|dep| marker == dep.package.marker()) {
3773 if marker::requires_python(marker)
3777 .is_none_or(|bound| !python_requirement.raises(&bound))
3778 {
3779 for dep in deps {
3780 for fork in &mut forks {
3781 if fork.env.included_by_marker(marker) {
3782 fork.add_dependency(dep.clone());
3783 }
3784 }
3785 }
3786 continue;
3787 }
3788 }
3789 }
3790 }
3791 for dep in deps {
3792 let mut forker = match ForkingPossibility::new(env, &dep) {
3793 ForkingPossibility::Possible(forker) => forker,
3794 ForkingPossibility::DependencyAlwaysExcluded => {
3795 continue;
3798 }
3799 ForkingPossibility::NoForkingPossible => {
3800 for fork in &mut forks {
3803 fork.add_dependency(dep.clone());
3804 }
3805 continue;
3806 }
3807 };
3808 diverging_packages.insert(name.clone());
3810
3811 let mut new = vec![];
3812 for fork in std::mem::take(&mut forks) {
3813 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3814 new.push(fork);
3815 continue;
3816 };
3817 forker = remaining_forker;
3818
3819 for fork_env in envs {
3820 let mut new_fork = fork.clone();
3821 new_fork.set_env(fork_env);
3822 if forker.included(&new_fork.env) {
3827 new_fork.add_dependency(dep.clone());
3828 }
3829 if new_fork.env.included_by_marker(python_marker) {
3832 new.push(new_fork);
3833 }
3834 }
3835 }
3836 forks = new;
3837 }
3838 }
3839 for set in conflicts.iter() {
3854 let mut new = vec![];
3855 for fork in std::mem::take(&mut forks) {
3856 let mut has_conflicting_dependency = false;
3864 for item in set.iter() {
3865 if fork.contains_conflicting_item(item.as_ref()) {
3866 has_conflicting_dependency = true;
3867 diverging_packages.insert(item.package().clone());
3868 break;
3869 }
3870 }
3871 if !has_conflicting_dependency {
3872 new.push(fork);
3873 continue;
3874 }
3875
3876 let non_excluded: Vec<_> = set
3882 .iter()
3883 .filter(|item| fork.env.included_by_group(item.as_ref()))
3884 .collect();
3885 if non_excluded.len() < 2 {
3886 let dominated = non_excluded.iter().all(|item| {
3891 !conflicts.iter().any(|other_set| {
3892 !std::ptr::eq(set, other_set)
3893 && other_set.contains(item.package(), item.kind().as_ref())
3894 && other_set
3895 .iter()
3896 .filter(|other_item| {
3897 other_item.package() != item.package()
3898 || other_item.kind() != item.kind()
3899 })
3900 .any(|other_item| {
3901 fork.env.included_by_group(other_item.as_ref())
3902 })
3903 })
3904 });
3905 if dominated {
3906 let rules: Vec<_> = set
3911 .iter()
3912 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3913 .cloned()
3914 .map(Err)
3915 .collect();
3916 if let Some(filtered) = fork.filter(rules) {
3917 new.push(filtered);
3918 }
3919 continue;
3920 }
3921 }
3922
3923 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3925 new.push(fork_none);
3926 }
3927
3928 for (i, _) in set.iter().enumerate() {
3936 let fork_allows_group = fork.clone().filter(
3937 set.iter()
3938 .cloned()
3939 .enumerate()
3940 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3941 );
3942 if let Some(fork_allows_group) = fork_allows_group {
3943 new.push(fork_allows_group);
3944 }
3945 }
3946 }
3947 forks = new;
3948 }
3949 (forks, diverging_packages)
3950 }
3951}
3952
3953#[derive(Clone, Debug)]
3963struct Fork {
3964 dependencies: Vec<PubGrubDependency>,
3973 conflicts: crate::FxHashbrownSet<ConflictItem>,
3979 env: ResolverEnvironment,
3991}
3992
3993impl Fork {
3994 fn new(env: ResolverEnvironment) -> Self {
3997 Self {
3998 dependencies: vec![],
3999 conflicts: crate::FxHashbrownSet::default(),
4000 env,
4001 }
4002 }
4003
4004 fn add_dependency(&mut self, dep: PubGrubDependency) {
4006 if let Some(conflicting_item) = dep.conflicting_item() {
4007 self.conflicts.insert(conflicting_item.to_owned());
4008 }
4009 self.dependencies.push(dep);
4010 }
4011
4012 fn set_env(&mut self, env: ResolverEnvironment) {
4017 self.env = env;
4018 self.dependencies.retain(|dep| {
4019 let marker = dep.package.marker();
4020 if self.env.included_by_marker(marker) {
4021 return true;
4022 }
4023 if let Some(conflicting_item) = dep.conflicting_item() {
4024 self.conflicts.remove(&conflicting_item);
4025 }
4026 false
4027 });
4028 }
4029
4030 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4033 self.conflicts.contains(&item)
4034 }
4035
4036 fn filter(
4043 mut self,
4044 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4045 ) -> Option<Self> {
4046 self.env = self.env.filter_by_group(rules)?;
4047 self.dependencies.retain(|dep| {
4048 let Some(conflicting_item) = dep.conflicting_item() else {
4049 return true;
4050 };
4051 if self.env.included_by_group(conflicting_item) {
4052 return true;
4053 }
4054 match conflicting_item.kind() {
4055 ConflictKindRef::Project => {
4058 if dep.parent.is_some() {
4059 return true;
4060 }
4061 }
4062 ConflictKindRef::Group(_) => {}
4063 ConflictKindRef::Extra(_) => {}
4064 }
4065 self.conflicts.remove(&conflicting_item);
4066 false
4067 });
4068 Some(self)
4069 }
4070
4071 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4073 cmp_requires_python(&self.env, &other.env)
4074 }
4075
4076 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4078 let self_upper_bounds = self
4083 .dependencies
4084 .iter()
4085 .filter(|dep| {
4086 dep.version
4087 .bounding_range()
4088 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4089 })
4090 .count();
4091 let other_upper_bounds = other
4092 .dependencies
4093 .iter()
4094 .filter(|dep| {
4095 dep.version
4096 .bounding_range()
4097 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4098 })
4099 .count();
4100
4101 self_upper_bounds.cmp(&other_upper_bounds)
4102 }
4103}
4104
4105fn cmp_requires_python(
4107 self_env: &ResolverEnvironment,
4108 other_env: &ResolverEnvironment,
4109) -> Ordering {
4110 let self_bound = self_env.requires_python().unwrap_or_default();
4120 let other_bound = other_env.requires_python().unwrap_or_default();
4121 self_bound.lower().cmp(other_bound.lower())
4122}
4123
4124impl Eq for Fork {}
4125
4126impl PartialEq for Fork {
4127 fn eq(&self, other: &Self) -> bool {
4128 self.dependencies == other.dependencies && self.env == other.env
4129 }
4130}
4131
4132#[derive(Debug, Clone)]
4133pub(crate) struct VersionFork {
4134 env: ResolverEnvironment,
4136 id: Id<PubGrubPackage>,
4138 version: Option<Version>,
4140}
4141
4142fn enrich_dependency_error(
4144 error: ResolveError,
4145 id: Id<PubGrubPackage>,
4146 version: &Version,
4147 pubgrub: &State<UvDependencyProvider>,
4148) -> ResolveError {
4149 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4150 return error;
4151 };
4152 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4153 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4154}
4155
4156fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4158 let package = &state.package_store[id];
4159 if package.is_root() {
4160 return MarkerTree::TRUE;
4161 }
4162
4163 let mut ancestors = FxHashSet::default();
4166 let mut stack = vec![id];
4167 let mut root = None;
4168 ancestors.insert(id);
4169
4170 while let Some(current) = stack.pop() {
4171 for dependency in state.dependencies(current) {
4172 let parent = dependency.dependent;
4173 if current != dependency.dependency {
4174 continue;
4175 }
4176 if ancestors.insert(parent) {
4177 if state.package_store[parent].is_root() {
4178 root = Some(parent);
4179 }
4180 stack.push(parent);
4181 }
4182 }
4183 }
4184
4185 let Some(root) = root else {
4186 return MarkerTree::FALSE;
4187 };
4188
4189 let mut environments = FxHashMap::default();
4192 let mut queue = VecDeque::from([root]);
4193 environments.insert(root, MarkerTree::TRUE);
4194
4195 while let Some(current) = queue.pop_front() {
4196 let Some(current_environment) = environments.get(¤t).copied() else {
4197 continue;
4198 };
4199 for dependency in state.dependencies(current) {
4200 let parent = dependency.dependent;
4201 let child = dependency.dependency;
4202 if current != parent || !ancestors.contains(&child) {
4203 continue;
4204 }
4205
4206 let mut next_environment = state.package_store[child].marker();
4207 next_environment = next_environment.and(current_environment);
4208
4209 let entry = environments.entry(child).or_insert(MarkerTree::FALSE);
4210 let mut combined = *entry;
4211 combined = combined.or(next_environment);
4212 if combined != *entry {
4213 *entry = combined;
4214 queue.push_back(child);
4215 }
4216 }
4217 }
4218
4219 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4220}
4221
4222#[derive(Debug, Default, Clone)]
4223struct ConflictTracker {
4224 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4226 prioritize: Vec<Id<PubGrubPackage>>,
4230 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4232 deprioritize: Vec<Id<PubGrubPackage>>,
4236}
4237
4238#[cfg(test)]
4239mod tests {
4240 use super::*;
4241
4242 fn versions(versions: &[&str]) -> Vec<Version> {
4243 versions
4244 .iter()
4245 .map(|version| version.parse().expect("valid version"))
4246 .collect()
4247 }
4248
4249 #[test]
4250 fn widens_a_version_to_its_gap() {
4251 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4252 let version: Version = "2.0".parse().expect("valid version");
4253
4254 assert_eq!(
4256 widen_to_gap(&version, Some(&known_versions)).to_string(),
4257 ">1.0, <3.0"
4258 );
4259
4260 let version: Version = "1.0".parse().expect("valid version");
4262 assert_eq!(
4263 widen_to_gap(&version, Some(&known_versions)).to_string(),
4264 "<2.0"
4265 );
4266 let version: Version = "3.0".parse().expect("valid version");
4267 assert_eq!(
4268 widen_to_gap(&version, Some(&known_versions)).to_string(),
4269 ">2.0"
4270 );
4271 }
4272
4273 #[test]
4274 fn widens_a_version_without_known_versions_to_itself() {
4275 let version: Version = "2.0".parse().expect("valid version");
4276
4277 assert_eq!(
4279 widen_to_gap(&version, None),
4280 Range::singleton(version.clone())
4281 );
4282
4283 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4285 }
4286}