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