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::{iter, slice, thread};
11
12use either::Either;
13use futures::{FutureExt, StreamExt};
14use itertools::Itertools;
15use papaya::{HashMap, ResizeMode};
16use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
17use rustc_hash::{FxHashMap, FxHashSet};
18use tokio::sync::mpsc::{self, Receiver, Sender};
19use tokio::sync::oneshot;
20use tokio_stream::wrappers::ReceiverStream;
21use tracing::{Level, debug, info, instrument, trace, warn};
22
23use uv_configuration::{Constraints, Excludes, Overrides};
24use uv_distribution::{ArchiveMetadata, DistributionDatabase};
25use uv_distribution_types::{
26 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
27 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
28 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist,
29 ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::{ExtraName, GroupName, PackageName};
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_strategy::ForkStrategy;
49use crate::fork_urls::ForkUrls;
50use crate::manifest::Manifest;
51use crate::pins::FilePins;
52use crate::preferences::{PreferenceSource, Preferences};
53use crate::pubgrub::{
54 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
55 PubGrubPython, Range,
56};
57use crate::python_requirement::PythonRequirement;
58use crate::resolution::ResolverOutput;
59use crate::resolution_mode::ResolutionStrategy;
60pub(crate) use crate::resolver::availability::{
61 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
62 UnavailableVersion, UnsatisfiableRequirement,
63};
64use crate::resolver::batch_prefetch::BatchPrefetcher;
65use crate::resolver::derivation::DerivationChainBuilder;
66pub use crate::resolver::environment::ResolverEnvironment;
67use crate::resolver::environment::{
68 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
69};
70pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
71pub use crate::resolver::index::InMemoryIndex;
72use crate::resolver::indexes::Indexes;
73pub use crate::resolver::provider::{
74 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
75 VersionsResponse, WheelMetadataResult,
76};
77pub use crate::resolver::reporter::Reporter;
78use crate::resolver::system::SystemDependency;
79pub(crate) use crate::resolver::urls::Urls;
80use crate::universal_marker::UniversalMarker;
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84pub(crate) use resolution::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
85
86mod availability;
87mod batch_prefetch;
88mod derivation;
89mod environment;
90mod fork_map;
91mod index;
92mod indexes;
93mod provider;
94mod reporter;
95mod resolution;
96mod system;
97mod urls;
98
99const CONFLICT_THRESHOLD: usize = 5;
101
102pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
103 state: ResolverState<InstalledPackages>,
104 provider: Provider,
105}
106
107struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
110 project: Option<PackageName>,
111 requirements: Vec<Requirement>,
112 constraints: Constraints,
113 overrides: Overrides,
114 excludes: Excludes,
115 preferences: Preferences,
116 git: GitResolver,
117 capabilities: IndexCapabilities,
118 locations: IndexLocations,
119 exclusions: Exclusions,
120 urls: Urls,
121 indexes: Indexes,
122 dependency_mode: DependencyMode,
123 hasher: HashStrategy,
124 env: ResolverEnvironment,
125 current_environment: MarkerEnvironment,
127 tags: Option<Tags>,
128 python_requirement: PythonRequirement,
129 conflicts: Conflicts,
130 workspace_members: BTreeSet<PackageName>,
131 selector: CandidateSelector,
132 index: InMemoryIndex,
133 installed_packages: InstalledPackages,
134 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
137 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
139 options: Options,
141 reporter: Option<Arc<dyn Reporter>>,
143}
144
145impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
146 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
147{
148 pub fn new(
167 manifest: Manifest,
168 options: Options,
169 python_requirement: &'a PythonRequirement,
170 env: ResolverEnvironment,
171 current_environment: &MarkerEnvironment,
172 conflicts: Conflicts,
173 tags: Option<&'a Tags>,
174 flat_index: &'a FlatIndex,
175 index: &'a InMemoryIndex,
176 hasher: &'a HashStrategy,
177 build_context: &'a Context,
178 installed_packages: InstalledPackages,
179 database: DistributionDatabase<'a, Context>,
180 ) -> Result<Self, ResolveError> {
181 let provider = DefaultResolverProvider::new(
182 database,
183 flat_index,
184 tags,
185 python_requirement.target(),
186 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
187 hasher,
188 options.exclude_newer.clone(),
189 build_context.locations(),
190 build_context.build_options(),
191 build_context.capabilities(),
192 );
193
194 Ok(Self::new_custom_io(
195 manifest,
196 options,
197 hasher,
198 env,
199 current_environment,
200 tags.cloned(),
201 python_requirement,
202 conflicts,
203 index,
204 build_context.git(),
205 build_context.capabilities(),
206 build_context.locations(),
207 provider,
208 installed_packages,
209 ))
210 }
211}
212
213impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
214 Resolver<Provider, InstalledPackages>
215{
216 pub fn new_custom_io(
218 manifest: Manifest,
219 options: Options,
220 hasher: &HashStrategy,
221 env: ResolverEnvironment,
222 current_environment: &MarkerEnvironment,
223 tags: Option<Tags>,
224 python_requirement: &PythonRequirement,
225 conflicts: Conflicts,
226 index: &InMemoryIndex,
227 git: &GitResolver,
228 capabilities: &IndexCapabilities,
229 locations: &IndexLocations,
230 provider: Provider,
231 installed_packages: InstalledPackages,
232 ) -> Self {
233 let state = ResolverState {
234 index: index.clone(),
235 git: git.clone(),
236 capabilities: capabilities.clone(),
237 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
238 dependency_mode: options.dependency_mode,
239 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
240 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
241 project: manifest.project,
242 workspace_members: manifest.workspace_members,
243 requirements: manifest.requirements,
244 constraints: manifest.constraints,
245 overrides: manifest.overrides,
246 excludes: manifest.excludes,
247 preferences: manifest.preferences,
248 exclusions: manifest.exclusions,
249 hasher: hasher.clone(),
250 locations: locations.clone(),
251 env,
252 current_environment: current_environment.clone(),
253 tags,
254 python_requirement: python_requirement.clone(),
255 conflicts,
256 installed_packages,
257 unavailable_packages: Box::default(),
258 incomplete_packages: Box::default(),
259 options,
260 reporter: None,
261 };
262 Self { state, provider }
263 }
264
265 #[must_use]
267 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
268 Self {
269 state: ResolverState {
270 reporter: Some(reporter.clone()),
271 ..self.state
272 },
273 provider: self
274 .provider
275 .with_reporter(reporter.into_distribution_reporter()),
276 }
277 }
278
279 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
281 let state = Arc::new(self.state);
282 let provider = Arc::new(self.provider);
283
284 let (request_sink, request_stream) = mpsc::channel(300);
288
289 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
291
292 let solver = state.clone();
294 let (tx, rx) = oneshot::channel();
295 thread::Builder::new()
296 .name("uv-resolver".into())
297 .spawn(move || {
298 let result = solver.solve(&request_sink);
299
300 let _ = tx.send(result);
302 })
303 .unwrap();
304
305 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
306
307 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
309
310 state.on_complete();
311 resolution
312 }
313}
314
315impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
316 #[instrument(skip_all)]
317 fn solve(
318 self: Arc<Self>,
319 request_sink: &Sender<Request>,
320 ) -> Result<ResolverOutput, ResolveError> {
321 debug!(
322 "Solving with installed Python version: {}",
323 self.python_requirement.exact()
324 );
325 debug!(
326 "Solving with target Python version: {}",
327 self.python_requirement.target()
328 );
329 if !self.options.exclude_newer.is_empty() {
330 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
331 }
332
333 let mut visited = FxHashSet::default();
334
335 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
336 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
337 let prefetcher = BatchPrefetcher::new(
338 self.capabilities.clone(),
339 self.index.clone(),
340 request_sink.clone(),
341 );
342 let state = ForkState::new(
343 pubgrub,
344 self.env.clone(),
345 self.python_requirement.clone(),
346 prefetcher,
347 );
348 let mut preferences = self.preferences.clone();
349 let mut forked_states = self.env.initial_forked_states(state)?;
350
351 match (self.options.fork_strategy, self.options.resolution_mode) {
357 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
358 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
359 }
360 (ForkStrategy::RequiresPython, _) => {
361 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
362 }
363 }
364 let mut resolutions = vec![];
365
366 'FORK: while let Some(mut state) = forked_states.pop() {
367 if let Some(split) = state.env.end_user_fork_display() {
368 let requires_python = state.python_requirement.target();
369 debug!("Solving {split} (requires-python: {requires_python:?})");
370 }
371 let start = Instant::now();
372 loop {
373 let highest_priority_pkg =
374 if let Some(initial) = state.initial_id.take() {
375 initial
379 } else {
380 let result = state.pubgrub.unit_propagation(state.next);
382 match result {
383 Err(err) => {
384 return Err(self.convert_no_solution_err(
386 err,
387 state.fork_urls,
388 state.fork_indexes,
389 &state.known_versions.0,
390 state.env,
391 self.current_environment.clone(),
392 &visited,
393 ));
394 }
395 Ok(conflicts) => {
396 for (affected, incompatibility) in conflicts {
397 state.record_conflict(affected, None, incompatibility);
400 }
401 }
402 }
403
404 if self.dependency_mode.is_transitive() {
406 Self::pre_visit(
407 state.pubgrub.partial_solution.prioritized_packages().map(
408 |(id, range)| (id, &state.pubgrub.package_store[id], range),
409 ),
410 &mut state.pre_visited,
411 &self.urls,
412 &self.indexes,
413 &state.python_requirement,
414 request_sink,
415 )?;
416 }
417
418 state.reprioritize_conflicts();
419
420 trace!(
421 "Assigned packages: {}",
422 state
423 .pubgrub
424 .partial_solution
425 .extract_solution()
426 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
427 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
428 .join(", ")
429 );
430 let Some((highest_priority_pkg, _)) =
434 state.pubgrub.partial_solution.pick_highest_priority_pkg(
435 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
436 )
437 else {
438 if tracing::enabled!(Level::DEBUG) {
440 state.prefetcher.log_tried_versions();
441 }
442 debug!(
443 "{} resolution took {:.3}s",
444 state.env,
445 start.elapsed().as_secs_f32()
446 );
447
448 let resolution = state.into_resolution();
449
450 if matches!(
459 self.options.resolution_mode,
460 ResolutionMode::Lowest | ResolutionMode::Highest
461 ) {
462 let marker = resolution
463 .env
464 .try_universal_markers()
465 .unwrap_or(UniversalMarker::TRUE);
466 for (package, version) in &resolution.nodes {
467 preferences.insert(
468 package.name.clone(),
469 package.index.clone(),
470 marker,
471 version.clone(),
472 PreferenceSource::Resolver,
473 );
474 }
475 }
476
477 resolutions.push(resolution);
478 continue 'FORK;
479 };
480 trace!(
481 "Chose package for decision: {}. remaining choices: {}",
482 state.pubgrub.package_store[highest_priority_pkg],
483 state
484 .pubgrub
485 .partial_solution
486 .undecided_packages()
487 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
488 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
489 .join(", ")
490 );
491
492 highest_priority_pkg
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 url = next_package
502 .name()
503 .and_then(|name| state.fork_urls.get(name));
504 let index = next_package
505 .name()
506 .and_then(|name| state.fork_indexes.get(name));
507
508 self.request_package(next_package, url, index, request_sink)?;
520
521 let version = if let Some(version) = state.initial_version.take() {
522 version
526 } else {
527 let term_intersection = state
528 .pubgrub
529 .partial_solution
530 .term_intersection_for_package(next_id)
531 .expect("a package was chosen but we don't have a term");
532 let range = term_intersection.unwrap_positive();
533
534 let cache_selected_version = url.is_none() && index.is_none();
538 let decision = if cache_selected_version
539 && let Some((selected_range, version)) =
540 state.selected_versions.get(&next_id)
541 && selected_range == range
542 {
543 Some(ResolverVersion::Unforked(version.clone()))
544 } else {
545 let decision = self.choose_version(
546 next_package,
547 next_id,
548 index.map(IndexMetadata::url),
549 range,
550 &mut state.pins,
551 &preferences,
552 &state.fork_urls,
553 &state.env,
554 &state.python_requirement,
555 &state.pubgrub,
556 &mut visited,
557 request_sink,
558 )?;
559
560 if cache_selected_version
561 && let Some(ResolverVersion::Unforked(version)) = &decision
562 {
563 state
564 .selected_versions
565 .insert(next_id, (range.clone(), version.clone()));
566 }
567
568 decision
569 };
570
571 let Some(version) = decision else {
573 debug!("No compatible version found for: {next_package}");
574
575 let term_intersection = state
576 .pubgrub
577 .partial_solution
578 .term_intersection_for_package(next_id)
579 .expect("a package was chosen but we don't have a term");
580
581 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
582 if let Some(reason) = self.unavailable_packages.pin().get(name) {
584 state
585 .pubgrub
586 .add_incompatibility(Incompatibility::custom_term(
587 next_id,
588 term_intersection.clone(),
589 UnavailableReason::Package(reason.clone()),
590 ));
591 continue;
592 }
593 }
594
595 state
596 .pubgrub
597 .add_incompatibility(Incompatibility::no_versions(
598 next_id,
599 term_intersection.clone(),
600 ));
601 continue;
602 };
603
604 let version = match version {
605 ResolverVersion::Unforked(version) => version,
606 ResolverVersion::Forked(forks) => {
607 forked_states.extend(self.version_forks_to_fork_states(state, forks));
608 continue 'FORK;
609 }
610 ResolverVersion::Unavailable(version, reason) => {
611 state.add_unavailable_version(
612 version,
613 reason,
614 &self.index,
615 &self.installed_packages,
616 );
617 continue;
618 }
619 };
620
621 if url.is_none() {
623 state.prefetcher.prefetch_batches(
624 next_package,
625 index,
626 &version,
627 term_intersection.unwrap_positive(),
628 state
629 .pubgrub
630 .partial_solution
631 .unchanging_term_for_package(next_id),
632 &state.python_requirement,
633 &self.selector,
634 &state.env,
635 )?;
636 }
637
638 version
639 };
640
641 state.prefetcher.version_tried(next_package, &version);
642
643 self.on_progress(next_package, &version);
644
645 if state
646 .added_dependencies
647 .get(&next_id)
648 .is_some_and(|versions| versions.contains(&version))
649 {
650 state
653 .pubgrub
654 .partial_solution
655 .add_decision(next_id, version);
656 continue;
657 }
658
659 let forked_deps = self.get_dependencies_forking(
661 next_id,
662 next_package,
663 &version,
664 &state.pins,
665 &state.fork_urls,
666 &state.env,
667 &state.python_requirement,
668 &state.pubgrub,
669 )?;
670
671 match forked_deps {
672 ForkedDependencies::Unavailable(reason) => {
673 let versions = state.widen_version_to_gap(
676 &version,
677 &self.index,
678 &self.installed_packages,
679 );
680 state
681 .pubgrub
682 .add_incompatibility(Incompatibility::custom_term(
683 next_id,
684 Term::Positive(versions),
685 UnavailableReason::Version(reason),
686 ));
687 }
688 ForkedDependencies::Unforked(dependencies) => {
689 state
690 .added_dependencies
691 .entry(next_id)
692 .or_default()
693 .insert(version.clone());
694
695 state
697 .visit_package_version_dependencies(
698 next_id,
699 &version,
700 &self.urls,
701 &self.indexes,
702 &dependencies,
703 &self.git,
704 &self.workspace_members,
705 self.selector.resolution_strategy(),
706 )
707 .map_err(|err| {
708 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
709 })?;
710
711 self.visit_dependencies(&dependencies, &state, request_sink)
713 .map_err(|err| {
714 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
715 })?;
716
717 state.add_package_version_dependencies(
719 next_id,
720 &version,
721 dependencies,
722 &self.index,
723 &self.installed_packages,
724 );
725 }
726 ForkedDependencies::Forked {
727 mut forks,
728 diverging_packages,
729 } => {
730 state
731 .added_dependencies
732 .entry(next_id)
733 .or_default()
734 .insert(version.clone());
735
736 debug!(
737 "Pre-fork {} took {:.3}s",
738 state.env,
739 start.elapsed().as_secs_f32()
740 );
741
742 match (self.options.fork_strategy, self.options.resolution_mode) {
744 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
745 forks.sort_by(|a, b| {
749 a.cmp_requires_python(b)
750 .reverse()
751 .then_with(|| a.cmp_upper_bounds(b))
752 });
753 }
754 (ForkStrategy::RequiresPython, _) => {
755 forks.sort_by(|a, b| {
759 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
760 });
761 }
762 }
763
764 for new_fork_state in self.forks_to_fork_states(
765 state,
766 &version,
767 forks,
768 request_sink,
769 &diverging_packages,
770 ) {
771 forked_states.push(new_fork_state?);
772 }
773 continue 'FORK;
774 }
775 ForkedDependencies::RequiresPython(requires_python) => {
776 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
777 && state.env.marker_environment().is_none()
778 {
779 let forks = fork_version_by_python_requirement(
780 &requires_python,
781 &state.python_requirement,
782 &state.env,
783 );
784 if !forks.is_empty() {
785 debug!(
786 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
787 state.python_requirement.target(),
788 &requires_python,
789 next_package,
790 version,
791 forks
792 .iter()
793 .map(ToString::to_string)
794 .collect::<Vec<_>>()
795 .join(", ")
796 );
797
798 let forks = forks
801 .into_iter()
802 .map(|env| VersionFork {
803 env,
804 id: next_id,
805 version: None,
806 })
807 .collect();
808 forked_states
809 .extend(self.version_forks_to_fork_states(state, forks));
810 continue 'FORK;
811 }
812 }
813
814 let versions = state.widen_version_to_gap(
815 &version,
816 &self.index,
817 &self.installed_packages,
818 );
819 state
820 .pubgrub
821 .add_incompatibility(Incompatibility::custom_term(
822 next_id,
823 Term::Positive(versions),
824 UnavailableReason::Version(UnavailableVersion::RequiresPython(
825 requires_python,
826 )),
827 ));
828 }
829 }
830 }
831 }
832 if resolutions.len() > 1 {
833 info!(
834 "Solved your requirements for {} environments",
835 resolutions.len()
836 );
837 }
838 if tracing::enabled!(Level::DEBUG) {
839 for resolution in &resolutions {
840 if let Some(env) = resolution.env.end_user_fork_display() {
841 let packages: FxHashSet<_> = resolution
842 .nodes
843 .keys()
844 .map(|package| &package.name)
845 .collect();
846 debug!(
847 "Distinct solution for {env} with {} package(s)",
848 packages.len()
849 );
850 }
851 }
852 }
853 for resolution in &resolutions {
854 resolution.trace_resolution();
855 }
856 ResolverOutput::from_state(
857 &resolutions,
858 self.requirements.clone(),
859 self.constraints.clone(),
860 self.overrides.clone(),
861 &self.preferences,
862 &self.hasher,
863 &self.index,
864 &self.git,
865 self.python_requirement.target().clone(),
866 &self.conflicts,
867 self.selector.resolution_strategy(),
868 self.options.clone(),
869 )
870 }
871
872 fn forks_to_fork_states<'a>(
874 &'a self,
875 current_state: ForkState,
876 version: &'a Version,
877 forks: Vec<Fork>,
878 request_sink: &'a Sender<Request>,
879 diverging_packages: &'a BTreeSet<PackageName>,
880 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
881 debug!(
882 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
883 current_state.pubgrub.package_store[current_state.next],
884 version,
885 diverging_packages
886 .iter()
887 .map(ToString::to_string)
888 .join(", "),
889 forks.len(),
890 if forks.len() == 1 { "" } else { "s" }
891 );
892 assert!(forks.len() >= 2);
893 let package = current_state.next;
899 let mut cur_state = Some(current_state);
900 let forks_len = forks.len();
901 forks
902 .into_iter()
903 .enumerate()
904 .map(move |(i, fork)| {
905 let is_last = i == forks_len - 1;
906 let forked_state = cur_state.take().unwrap();
907 if !is_last {
908 cur_state = Some(forked_state.clone());
909 }
910
911 let env = fork.env.clone();
912 (fork, forked_state.with_env(env))
913 })
914 .map(move |(fork, mut forked_state)| {
915 forked_state
917 .visit_package_version_dependencies(
918 package,
919 version,
920 &self.urls,
921 &self.indexes,
922 &fork.dependencies,
923 &self.git,
924 &self.workspace_members,
925 self.selector.resolution_strategy(),
926 )
927 .map_err(|err| {
928 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
929 })?;
930
931 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
933 .map_err(|err| {
934 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
935 })?;
936
937 forked_state.add_package_version_dependencies(
939 package,
940 version,
941 fork.dependencies,
942 &self.index,
943 &self.installed_packages,
944 );
945
946 Ok(forked_state)
947 })
948 }
949
950 #[expect(clippy::unused_self)]
952 fn version_forks_to_fork_states(
953 &self,
954 current_state: ForkState,
955 forks: Vec<VersionFork>,
956 ) -> impl Iterator<Item = ForkState> + '_ {
957 let mut cur_state = Some(current_state);
963 let forks_len = forks.len();
964 forks.into_iter().enumerate().map(move |(i, fork)| {
965 let is_last = i == forks_len - 1;
966 let mut forked_state = cur_state.take().unwrap();
967 if !is_last {
968 cur_state = Some(forked_state.clone());
969 }
970 forked_state.initial_id = Some(fork.id);
971 forked_state.initial_version = fork.version;
972 forked_state.with_env(fork.env)
973 })
974 }
975
976 fn visit_dependencies(
978 &self,
979 dependencies: &[PubGrubDependency],
980 state: &ForkState,
981 request_sink: &Sender<Request>,
982 ) -> Result<(), ResolveError> {
983 for dependency in dependencies {
984 let PubGrubDependency {
985 package,
986 version: _,
987 parent: _,
988 source: _,
989 } = dependency;
990 let url = package.name().and_then(|name| state.fork_urls.get(name));
991 let index = package.name().and_then(|name| state.fork_indexes.get(name));
992 self.visit_package(package, url, index, request_sink)?;
993 }
994 Ok(())
995 }
996
997 fn visit_package(
1000 &self,
1001 package: &PubGrubPackage,
1002 url: Option<&VerbatimParsedUrl>,
1003 index: Option<&IndexMetadata>,
1004 request_sink: &Sender<Request>,
1005 ) -> Result<(), ResolveError> {
1006 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1008 return Ok(());
1009 }
1010
1011 self.request_package(package, url, index, request_sink)
1012 }
1013
1014 fn request_package(
1015 &self,
1016 package: &PubGrubPackage,
1017 url: Option<&VerbatimParsedUrl>,
1018 index: Option<&IndexMetadata>,
1019 request_sink: &Sender<Request>,
1020 ) -> Result<(), ResolveError> {
1021 let Some(name) = package.name_no_root() else {
1023 return Ok(());
1024 };
1025
1026 if let Some(url) = url {
1027 if !self.hasher.allows_url(&url.verbatim) {
1029 return Err(ResolveError::UnhashedPackage(name.clone()));
1030 }
1031
1032 let dist = Dist::from_url(name.clone(), url.clone())?;
1034 if self.index.distributions().register(dist.distribution_id()) {
1035 request_sink.blocking_send(Request::Dist(dist))?;
1036 }
1037 } else if let Some(index) = index {
1038 if self
1040 .index
1041 .explicit()
1042 .register((name.clone(), index.url().clone()))
1043 {
1044 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1045 }
1046 } else {
1047 if self.index.implicit().register(name.clone()) {
1049 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1050 }
1051 }
1052 Ok(())
1053 }
1054
1055 fn pre_visit<'data>(
1058 packages: impl Iterator<
1059 Item = (
1060 Id<PubGrubPackage>,
1061 &'data PubGrubPackage,
1062 &'data Range<Version>,
1063 ),
1064 >,
1065 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1066 urls: &Urls,
1067 indexes: &Indexes,
1068 python_requirement: &PythonRequirement,
1069 request_sink: &Sender<Request>,
1070 ) -> Result<(), ResolveError> {
1071 for (id, package, range) in packages {
1074 let PubGrubPackageInner::Package {
1075 name,
1076 extra: None,
1077 group: None,
1078 marker: MarkerTree::TRUE,
1079 } = &**package
1080 else {
1081 continue;
1082 };
1083 if urls.any_url(name) {
1086 continue;
1087 }
1088 if indexes.contains_key(name) {
1090 continue;
1091 }
1092 if pre_visited.get(&id) == Some(range) {
1095 continue;
1096 }
1097 pre_visited.insert(id, range.clone());
1098 request_sink.blocking_send(Request::Prefetch(
1099 name.clone(),
1100 range.clone(),
1101 python_requirement.clone(),
1102 ))?;
1103 }
1104 Ok(())
1105 }
1106
1107 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1115 fn choose_version(
1116 &self,
1117 package: &PubGrubPackage,
1118 id: Id<PubGrubPackage>,
1119 index: Option<&IndexUrl>,
1120 range: &Range<Version>,
1121 pins: &mut FilePins,
1122 preferences: &Preferences,
1123 fork_urls: &ForkUrls,
1124 env: &ResolverEnvironment,
1125 python_requirement: &PythonRequirement,
1126 pubgrub: &State<UvDependencyProvider>,
1127 visited: &mut FxHashSet<PackageName>,
1128 request_sink: &Sender<Request>,
1129 ) -> Result<Option<ResolverVersion>, ResolveError> {
1130 match &**package {
1131 PubGrubPackageInner::Root(_) => {
1132 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1133 }
1134
1135 PubGrubPackageInner::Python(_) => {
1136 Ok(None)
1139 }
1140
1141 PubGrubPackageInner::System(_) => {
1142 let Some(version) = range.as_singleton() else {
1145 return Ok(None);
1146 };
1147 Ok(Some(ResolverVersion::Unforked(version.clone())))
1148 }
1149
1150 PubGrubPackageInner::Marker { name, .. }
1151 | PubGrubPackageInner::Extra { name, .. }
1152 | PubGrubPackageInner::Group { name, .. }
1153 | PubGrubPackageInner::Package { name, .. } => {
1154 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1155 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1156 } else {
1157 self.choose_version_registry(
1158 package,
1159 id,
1160 name,
1161 index,
1162 range,
1163 preferences,
1164 env,
1165 python_requirement,
1166 pubgrub,
1167 pins,
1168 visited,
1169 request_sink,
1170 )
1171 }
1172 }
1173 }
1174 }
1175
1176 fn choose_version_url(
1179 &self,
1180 id: Id<PubGrubPackage>,
1181 name: &PackageName,
1182 range: &Range<Version>,
1183 url: &VerbatimParsedUrl,
1184 env: &ResolverEnvironment,
1185 python_requirement: &PythonRequirement,
1186 pubgrub: &State<UvDependencyProvider>,
1187 ) -> Result<Option<ResolverVersion>, ResolveError> {
1188 debug!(
1189 "Searching for a compatible version of {name} @ {} ({range})",
1190 url.verbatim
1191 );
1192
1193 let dist = Dist::from_url(name.clone(), url.clone())?;
1194 let distribution_id = dist.distribution_id();
1195 let response = self
1196 .index
1197 .distributions()
1198 .wait_blocking(&distribution_id)
1199 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1200
1201 let metadata = match &*response {
1203 MetadataResponse::Found(archive) => &archive.metadata,
1204 MetadataResponse::Unavailable(reason) => {
1205 self.unavailable_packages
1206 .pin()
1207 .insert(name.clone(), reason.into());
1208 return Ok(None);
1209 }
1210 MetadataResponse::Error(dist, err) => {
1213 return Err(ResolveError::Dist(
1214 DistErrorKind::from_requested_dist(dist, &**err),
1215 dist.clone(),
1216 DerivationChain::default(),
1217 err.clone(),
1218 ));
1219 }
1220 };
1221
1222 let version = &metadata.version;
1223
1224 if !range.contains(version) {
1226 return Ok(None);
1227 }
1228
1229 if let Dist::Built(dist) = &dist {
1232 let filename = match &dist {
1233 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1234 BuiltDist::DirectUrl(dist) => &dist.filename,
1235 BuiltDist::GitPath(dist) => &dist.filename,
1236 BuiltDist::Path(dist) => &dist.filename,
1237 };
1238
1239 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1242 {
1243 let wheel_marker = implied_markers(filename);
1244 for environment_marker in self.options.artifact_environments.iter().copied() {
1247 if env.included_by_marker(environment_marker)
1249 && env.included_by_marker(
1250 find_environments(id, pubgrub).and(environment_marker),
1251 )
1252 {
1253 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1255 return Ok(Some(ResolverVersion::Unavailable(
1256 version.clone(),
1257 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1258 IncompatibleWheel::MissingPlatform(environment_marker),
1259 )),
1260 )));
1261 }
1262 }
1263 }
1264 }
1265
1266 if !python_requirement.target().matches_wheel_tag(filename) {
1268 return Ok(Some(ResolverVersion::Unavailable(
1269 filename.version.clone(),
1270 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1271 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1272 )),
1273 )));
1274 }
1275 }
1276
1277 if let Some(requires_python) = metadata.requires_python.as_ref() {
1279 if !python_requirement.target().is_contained_by(requires_python) {
1280 let kind = if python_requirement.installed() == python_requirement.target() {
1281 PythonRequirementKind::Installed
1282 } else {
1283 PythonRequirementKind::Target
1284 };
1285 return Ok(Some(ResolverVersion::Unavailable(
1286 version.clone(),
1287 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1288 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1289 )),
1290 )));
1291 }
1292 }
1293
1294 Ok(Some(ResolverVersion::Unforked(version.clone())))
1295 }
1296
1297 fn choose_version_registry(
1300 &self,
1301 package: &PubGrubPackage,
1302 id: Id<PubGrubPackage>,
1303 name: &PackageName,
1304 index: Option<&IndexUrl>,
1305 range: &Range<Version>,
1306 preferences: &Preferences,
1307 env: &ResolverEnvironment,
1308 python_requirement: &PythonRequirement,
1309 pubgrub: &State<UvDependencyProvider>,
1310 pins: &mut FilePins,
1311 visited: &mut FxHashSet<PackageName>,
1312 request_sink: &Sender<Request>,
1313 ) -> Result<Option<ResolverVersion>, ResolveError> {
1314 let versions_response = if let Some(index) = index {
1316 self.index
1317 .explicit()
1318 .wait_blocking(&(name.clone(), index.clone()))
1319 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1320 } else {
1321 self.index
1322 .implicit()
1323 .wait_blocking(name)
1324 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1325 };
1326 visited.insert(name.clone());
1327
1328 let version_maps = match *versions_response {
1329 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1330 VersionsResponse::NoIndex => {
1331 self.unavailable_packages
1332 .pin()
1333 .insert(name.clone(), UnavailablePackage::NoIndex);
1334 &[]
1335 }
1336 VersionsResponse::Offline => {
1337 self.unavailable_packages
1338 .pin()
1339 .insert(name.clone(), UnavailablePackage::Offline);
1340 &[]
1341 }
1342 VersionsResponse::NotFound => {
1343 self.unavailable_packages
1344 .pin()
1345 .insert(name.clone(), UnavailablePackage::NotFound);
1346 &[]
1347 }
1348 };
1349
1350 debug!("Searching for a compatible version of {package} ({range})");
1351
1352 let Some(candidate) = self.selector.select(
1354 name,
1355 range,
1356 version_maps,
1357 preferences,
1358 &self.installed_packages,
1359 &self.exclusions,
1360 index,
1361 env,
1362 self.tags.as_ref(),
1363 ) else {
1364 return Ok(None);
1366 };
1367
1368 let dist = match candidate.dist() {
1369 CandidateDist::Compatible(dist) => dist,
1370 CandidateDist::Incompatible {
1371 incompatible_dist: incompatibility,
1372 prioritized_dist: _,
1373 } => {
1374 return Ok(Some(ResolverVersion::Unavailable(
1376 candidate.version().clone(),
1377 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1380 )));
1381 }
1382 };
1383
1384 if let Some((requires_python, incompatibility)) =
1386 Self::check_requires_python(dist, python_requirement)
1387 {
1388 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1389 if env.marker_environment().is_none() {
1390 let forks = fork_version_by_python_requirement(
1391 requires_python,
1392 python_requirement,
1393 env,
1394 );
1395 if !forks.is_empty() {
1396 debug!(
1397 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1398 python_requirement.target(),
1399 requires_python,
1400 name,
1401 candidate.version(),
1402 forks
1403 .iter()
1404 .map(ToString::to_string)
1405 .collect::<Vec<_>>()
1406 .join(", ")
1407 );
1408 let forks = forks
1409 .into_iter()
1410 .map(|env| VersionFork {
1411 env,
1412 id,
1413 version: None,
1414 })
1415 .collect();
1416 return Ok(Some(ResolverVersion::Forked(forks)));
1417 }
1418 }
1419 }
1420
1421 return Ok(Some(ResolverVersion::Unavailable(
1422 candidate.version().clone(),
1423 UnavailableVersion::IncompatibleDist(incompatibility),
1424 )));
1425 }
1426
1427 if let Some(forked) = self.fork_version_registry(
1429 &candidate,
1430 dist,
1431 version_maps,
1432 package,
1433 id,
1434 name,
1435 index,
1436 range,
1437 preferences,
1438 env,
1439 pubgrub,
1440 pins,
1441 request_sink,
1442 )? {
1443 return Ok(Some(forked));
1444 }
1445
1446 let filename = match dist.for_installation() {
1447 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1448 .filename()
1449 .unwrap_or(Cow::Borrowed("unknown filename")),
1450 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1451 .filename()
1452 .unwrap_or(Cow::Borrowed("unknown filename")),
1453 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1454 };
1455
1456 debug!(
1457 "Selecting: {}=={} [{}] ({})",
1458 name,
1459 candidate.version(),
1460 candidate.choice_kind(),
1461 filename,
1462 );
1463 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1464
1465 let version = candidate.version().clone();
1466 Ok(Some(ResolverVersion::Unforked(version)))
1467 }
1468
1469 fn fork_version_registry(
1482 &self,
1483 candidate: &Candidate,
1484 dist: &CompatibleDist,
1485 version_maps: &[VersionMap],
1486 package: &PubGrubPackage,
1487 id: Id<PubGrubPackage>,
1488 name: &PackageName,
1489 index: Option<&IndexUrl>,
1490 range: &Range<Version>,
1491 preferences: &Preferences,
1492 env: &ResolverEnvironment,
1493 pubgrub: &State<UvDependencyProvider>,
1494 pins: &mut FilePins,
1495 request_sink: &Sender<Request>,
1496 ) -> Result<Option<ResolverVersion>, ResolveError> {
1497 if env.marker_environment().is_some() {
1499 return Ok(None);
1500 }
1501
1502 if dist.implied_markers().is_true() {
1505 return Ok(None);
1506 }
1507
1508 for marker in self.options.artifact_environments.iter().copied() {
1511 if env.included_by_marker(marker) {
1513 if !env.included_by_marker(dist.implied_markers().and(marker))
1515 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1516 {
1517 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1519 return Ok(Some(ResolverVersion::Unavailable(
1520 candidate.version().clone(),
1521 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1522 IncompatibleWheel::MissingPlatform(marker),
1523 )),
1524 )));
1525 };
1526
1527 debug!(
1528 "Forking on required platform `{}` for {}=={} ({})",
1529 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1530 name,
1531 candidate.version(),
1532 [&left, &right]
1533 .iter()
1534 .map(ToString::to_string)
1535 .collect::<Vec<_>>()
1536 .join(", ")
1537 );
1538 let forks = vec![
1539 VersionFork {
1540 env: left,
1541 id,
1542 version: None,
1543 },
1544 VersionFork {
1545 env: right,
1546 id,
1547 version: None,
1548 },
1549 ];
1550 return Ok(Some(ResolverVersion::Forked(forks)));
1551 }
1552 }
1553 }
1554
1555 if !candidate.version().is_local() {
1557 return Ok(None);
1558 }
1559
1560 debug!(
1561 "Looking at local version: {}=={}",
1562 name,
1563 candidate.version()
1564 );
1565
1566 let range = range.clone().intersection(&Range::singleton(
1568 candidate.version().clone().without_local(),
1569 ));
1570
1571 let Some(base_candidate) = self.selector.select(
1572 name,
1573 &range,
1574 version_maps,
1575 preferences,
1576 &self.installed_packages,
1577 &self.exclusions,
1578 index,
1579 env,
1580 self.tags.as_ref(),
1581 ) else {
1582 return Ok(None);
1583 };
1584 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1585 return Ok(None);
1586 };
1587
1588 let mut remainder = {
1590 let mut remainder = base_dist.implied_markers();
1591 remainder = remainder.and(dist.implied_markers().negate());
1592 remainder
1593 };
1594 if remainder.is_false() {
1595 return Ok(None);
1596 }
1597
1598 if !env.included_by_marker(remainder) {
1602 return Ok(None);
1603 }
1604
1605 if !env.included_by_marker(dist.implied_markers()) {
1608 let filename = match dist.for_installation() {
1609 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1610 .filename()
1611 .unwrap_or(Cow::Borrowed("unknown filename")),
1612 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1613 .filename()
1614 .unwrap_or(Cow::Borrowed("unknown filename")),
1615 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1616 };
1617
1618 debug!(
1619 "Preferring non-local candidate: {}=={} [{}] ({})",
1620 name,
1621 base_candidate.version(),
1622 base_candidate.choice_kind(),
1623 filename,
1624 );
1625 self.visit_candidate(
1626 &base_candidate,
1627 base_dist,
1628 package,
1629 name,
1630 pins,
1631 request_sink,
1632 )?;
1633
1634 return Ok(Some(ResolverVersion::Unforked(
1635 base_candidate.version().clone(),
1636 )));
1637 }
1638
1639 for value in [
1648 arcstr::literal!("darwin"),
1649 arcstr::literal!("linux"),
1650 arcstr::literal!("win32"),
1651 ] {
1652 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1653 key: MarkerValueString::SysPlatform,
1654 operator: MarkerOperator::Equal,
1655 value,
1656 });
1657 if dist.implied_markers().is_disjoint(sys_platform)
1658 && !remainder.is_disjoint(sys_platform)
1659 {
1660 remainder = remainder.or(sys_platform);
1661 }
1662 }
1663
1664 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1666 return Ok(None);
1667 };
1668
1669 debug!(
1670 "Forking platform for {}=={} ({})",
1671 name,
1672 candidate.version(),
1673 [&base_env, &local_env]
1674 .iter()
1675 .map(ToString::to_string)
1676 .collect::<Vec<_>>()
1677 .join(", ")
1678 );
1679 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1680 self.visit_candidate(
1681 &base_candidate,
1682 base_dist,
1683 package,
1684 name,
1685 pins,
1686 request_sink,
1687 )?;
1688
1689 let forks = vec![
1690 VersionFork {
1691 env: base_env.clone(),
1692 id,
1693 version: Some(base_candidate.version().clone()),
1694 },
1695 VersionFork {
1696 env: local_env.clone(),
1697 id,
1698 version: Some(candidate.version().clone()),
1699 },
1700 ];
1701 Ok(Some(ResolverVersion::Forked(forks)))
1702 }
1703
1704 fn visit_candidate(
1706 &self,
1707 candidate: &Candidate,
1708 dist: &CompatibleDist,
1709 package: &PubGrubPackage,
1710 name: &PackageName,
1711 pins: &mut FilePins,
1712 request_sink: &Sender<Request>,
1713 ) -> Result<(), ResolveError> {
1714 pins.insert(candidate, dist);
1717
1718 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1720 if self.dependency_mode.is_transitive() {
1721 let dist = dist.for_resolution();
1722 if self.index.distributions().register(dist.distribution_id()) {
1723 if name != dist.name() {
1724 return Err(ResolveError::MismatchedPackageName {
1725 request: "distribution",
1726 expected: name.clone(),
1727 actual: dist.name().clone(),
1728 });
1729 }
1730 if !self
1732 .hasher
1733 .allows_package(candidate.name(), candidate.version())
1734 {
1735 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1736 }
1737
1738 let request = Request::from(dist);
1739 request_sink.blocking_send(request)?;
1740 }
1741 }
1742 }
1743
1744 Ok(())
1745 }
1746
1747 fn check_requires_python<'dist>(
1750 dist: &'dist CompatibleDist,
1751 python_requirement: &PythonRequirement,
1752 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1753 let requires_python = dist.requires_python()?;
1754 if python_requirement.target().is_contained_by(requires_python) {
1755 None
1756 } else {
1757 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1758 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1759 requires_python.clone(),
1760 if python_requirement.installed() == python_requirement.target() {
1761 PythonRequirementKind::Installed
1762 } else {
1763 PythonRequirementKind::Target
1764 },
1765 ))
1766 } else {
1767 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1768 requires_python.clone(),
1769 if python_requirement.installed() == python_requirement.target() {
1770 PythonRequirementKind::Installed
1771 } else {
1772 PythonRequirementKind::Target
1773 },
1774 ))
1775 };
1776 Some((requires_python, incompatibility))
1777 }
1778 }
1779
1780 fn get_dependencies_forking(
1782 &self,
1783 id: Id<PubGrubPackage>,
1784 package: &PubGrubPackage,
1785 version: &Version,
1786 pins: &FilePins,
1787 fork_urls: &ForkUrls,
1788 env: &ResolverEnvironment,
1789 python_requirement: &PythonRequirement,
1790 pubgrub: &State<UvDependencyProvider>,
1791 ) -> Result<ForkedDependencies, ResolveError> {
1792 let dependencies = self.get_dependencies(
1793 id,
1794 package,
1795 version,
1796 pins,
1797 fork_urls,
1798 env,
1799 python_requirement,
1800 pubgrub,
1801 )?;
1802 if env.marker_environment().is_some() {
1803 Ok(ForkedDependencies::from_dependencies_platform_specific(
1804 dependencies,
1805 ))
1806 } else {
1807 Ok(ForkedDependencies::from_dependencies_universal(
1808 dependencies,
1809 env,
1810 python_requirement,
1811 &self.conflicts,
1812 ))
1813 }
1814 }
1815
1816 #[instrument(skip_all, fields(%package, %version))]
1818 fn get_dependencies(
1819 &self,
1820 id: Id<PubGrubPackage>,
1821 package: &PubGrubPackage,
1822 version: &Version,
1823 pins: &FilePins,
1824 fork_urls: &ForkUrls,
1825 env: &ResolverEnvironment,
1826 python_requirement: &PythonRequirement,
1827 pubgrub: &State<UvDependencyProvider>,
1828 ) -> Result<Dependencies, ResolveError> {
1829 let dependencies = match &**package {
1830 PubGrubPackageInner::Root(_) => {
1831 let no_dev_deps = BTreeMap::default();
1832 let requirements = self.flatten_requirements(
1833 &self.requirements,
1834 &no_dev_deps,
1835 None,
1836 None,
1837 None,
1838 None,
1839 env,
1840 python_requirement,
1841 );
1842
1843 PubGrubDependency::from_requirements(
1844 &self.conflicts,
1845 requirements,
1846 None,
1847 Some(package),
1848 )
1849 }
1850
1851 PubGrubPackageInner::Package {
1852 name,
1853 extra,
1854 group,
1855 marker: _,
1856 } => {
1857 if self.dependency_mode.is_direct() {
1859 return Ok(Dependencies::Unforkable(Vec::default()));
1860 }
1861
1862 let owned_id;
1864 let distribution_id = if let Some((_, metadata_id)) =
1865 pins.dist_and_id(name, version)
1866 {
1867 metadata_id
1868 } else if let Some(url) = fork_urls.get(name) {
1869 let dist = Dist::from_url(name.clone(), url.clone())?;
1870 owned_id = dist.distribution_id();
1871 &owned_id
1872 } else {
1873 debug_assert!(
1874 false,
1875 "Dependencies were requested for a package without a pinned distribution"
1876 );
1877 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1878 };
1879
1880 if self.dependency_mode.is_transitive()
1882 && self.unavailable_packages.pin().contains_key(name)
1883 && self.installed_packages.get_packages(name).is_empty()
1884 {
1885 debug_assert!(
1886 false,
1887 "Dependencies were requested for a package that is not available"
1888 );
1889 return Err(ResolveError::PackageUnavailable(name.clone()));
1890 }
1891
1892 let response = self
1894 .index
1895 .distributions()
1896 .wait_blocking(distribution_id)
1897 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1898
1899 let metadata = match &*response {
1900 MetadataResponse::Found(archive) => &archive.metadata,
1901 MetadataResponse::Unavailable(reason) => {
1902 let unavailable_version = UnavailableVersion::from(reason);
1903 let message = unavailable_version.singular_message();
1904 if let Some(err) = reason.source() {
1905 warn!("{name} {message}: {err}");
1907 } else {
1908 warn!("{name} {message}");
1909 }
1910 let incomplete_packages = self.incomplete_packages.pin();
1911 let versions = incomplete_packages.get_or_insert(
1912 name.clone(),
1913 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1914 );
1915 versions.pin().insert(version.clone(), reason.clone());
1916 return Ok(Dependencies::Unavailable(unavailable_version));
1917 }
1918 MetadataResponse::Error(dist, err) => {
1919 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1920 .unwrap_or_default();
1921 return Err(ResolveError::Dist(
1922 DistErrorKind::from_requested_dist(dist, &**err),
1923 dist.clone(),
1924 chain,
1925 err.clone(),
1926 ));
1927 }
1928 };
1929
1930 if let Some(requires_python) = &metadata.requires_python {
1933 if !python_requirement.target().is_contained_by(requires_python) {
1934 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1935 }
1936 }
1937
1938 let system_dependencies = self
1940 .options
1941 .torch_backend
1942 .as_ref()
1943 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1944 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1945 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1946 .map(IndexUrl::url)
1947 .and_then(SystemDependency::from_index)
1948 .into_iter()
1949 .inspect(|system_dependency| {
1950 debug!(
1951 "Adding system dependency `{}` for `{package}@{version}`",
1952 system_dependency
1953 );
1954 })
1955 .map(PubGrubDependency::from);
1956
1957 let requirements = self.flatten_requirements(
1958 &metadata.requires_dist,
1959 &metadata.dependency_groups,
1960 extra.as_ref(),
1961 group.as_ref(),
1962 Some(name),
1963 Some(version),
1964 env,
1965 python_requirement,
1966 );
1967
1968 PubGrubDependency::from_requirements(
1969 &self.conflicts,
1970 requirements,
1971 group.as_ref(),
1972 Some(package),
1973 )
1974 .map(|mut dependencies| {
1975 dependencies.extend(system_dependencies);
1976 dependencies
1977 })
1978 }
1979
1980 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1981
1982 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1983
1984 PubGrubPackageInner::Marker { name, marker } => {
1986 return Ok(Dependencies::Unforkable(
1987 [MarkerTree::TRUE, *marker]
1988 .into_iter()
1989 .map(move |marker| PubGrubDependency {
1990 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1991 name: name.clone(),
1992 extra: None,
1993 group: None,
1994 marker,
1995 }),
1996 version: Range::singleton(version.clone()),
1997 parent: None,
1998 source: DependencySource::Unspecified,
1999 })
2000 .collect(),
2001 ));
2002 }
2003
2004 PubGrubPackageInner::Extra {
2006 name,
2007 extra,
2008 marker,
2009 } => {
2010 return Ok(Dependencies::Unforkable(
2011 [MarkerTree::TRUE, *marker]
2012 .into_iter()
2013 .dedup()
2014 .flat_map(move |marker| {
2015 [None, Some(extra)]
2016 .into_iter()
2017 .map(move |extra| PubGrubDependency {
2018 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2019 name: name.clone(),
2020 extra: extra.cloned(),
2021 group: None,
2022 marker,
2023 }),
2024 version: Range::singleton(version.clone()),
2025 parent: None,
2026 source: DependencySource::Unspecified,
2027 })
2028 })
2029 .collect(),
2030 ));
2031 }
2032
2033 PubGrubPackageInner::Group {
2035 name,
2036 group,
2037 marker,
2038 } => {
2039 return Ok(Dependencies::Unforkable(
2040 [MarkerTree::TRUE, *marker]
2041 .into_iter()
2042 .dedup()
2043 .map(|marker| PubGrubDependency {
2044 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2045 name: name.clone(),
2046 extra: None,
2047 group: Some(group.clone()),
2048 marker,
2049 }),
2050 version: Range::singleton(version.clone()),
2051 parent: None,
2052 source: DependencySource::Unspecified,
2053 })
2054 .collect(),
2055 ));
2056 }
2057 };
2058 Ok(match dependencies {
2059 Ok(dependencies) => Dependencies::Available(dependencies),
2060 Err(requirement) => {
2061 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2062 }
2063 })
2064 }
2065
2066 fn flatten_requirements<'a>(
2070 &'a self,
2071 dependencies: &'a [Requirement],
2072 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2073 extra: Option<&'a ExtraName>,
2074 dev: Option<&'a GroupName>,
2075 name: Option<&'a PackageName>,
2076 version: Option<&'a Version>,
2077 env: &'a ResolverEnvironment,
2078 python_requirement: &'a PythonRequirement,
2079 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2080 let python_marker = python_requirement.to_marker_tree();
2081
2082 if let Some(dev) = dev {
2083 Either::Left(Either::Left(self.requirements_for_extra(
2086 dev_dependencies.get(dev).into_iter().flatten(),
2087 extra,
2088 None,
2089 name.zip(version),
2090 env,
2091 python_marker,
2092 python_requirement,
2093 )))
2094 } else if !dependencies
2095 .iter()
2096 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2097 {
2098 Either::Left(Either::Right(self.requirements_for_extra(
2100 dependencies.iter(),
2101 extra,
2102 name.zip(version),
2103 name.zip(version),
2104 env,
2105 python_marker,
2106 python_requirement,
2107 )))
2108 } else {
2109 let mut requirements = self
2110 .requirements_for_extra(
2111 dependencies.iter(),
2112 extra,
2113 name.zip(version),
2114 name.zip(version),
2115 env,
2116 python_marker,
2117 python_requirement,
2118 )
2119 .collect::<Vec<_>>();
2120
2121 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2124 let mut queue: VecDeque<_> = requirements
2125 .iter()
2126 .filter(|req| name == Some(&req.name))
2127 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2128 .collect();
2129 while let Some((extra, marker)) = queue.pop_front() {
2130 if !seen.insert((extra.clone(), marker)) {
2131 continue;
2132 }
2133 for requirement in self.requirements_for_extra(
2134 dependencies,
2135 Some(&extra),
2136 name.zip(version),
2137 name.zip(version),
2138 env,
2139 python_marker,
2140 python_requirement,
2141 ) {
2142 let requirement = match requirement {
2143 Cow::Owned(mut requirement) => {
2144 requirement.marker = requirement.marker.and(marker);
2145 requirement
2146 }
2147 Cow::Borrowed(requirement) => {
2148 let mut marker = marker;
2149 marker = marker.and(requirement.marker);
2150 Requirement {
2151 name: requirement.name.clone(),
2152 extras: requirement.extras.clone(),
2153 groups: requirement.groups.clone(),
2154 source: requirement.source.clone(),
2155 origin: requirement.origin.clone(),
2156 marker: marker.simplify_extras(slice::from_ref(&extra)),
2157 }
2158 }
2159 };
2160 let applicable_marker = requirement
2163 .marker
2164 .simplify_extras(slice::from_ref(&extra))
2165 .simplify_not_extras_with(|candidate| candidate != &extra);
2166 if python_marker.is_disjoint(applicable_marker)
2167 || !env.included_by_marker(applicable_marker)
2168 {
2169 continue;
2170 }
2171 if name == Some(&requirement.name) {
2172 queue.extend(
2174 requirement
2175 .extras
2176 .iter()
2177 .cloned()
2178 .map(|extra| (extra, requirement.marker)),
2179 );
2180 }
2181
2182 requirements.push(Cow::Owned(requirement));
2184 }
2185 }
2186
2187 let mut self_constraints = vec![];
2191 for req in &requirements {
2192 if name == Some(&req.name) && !req.extras.is_empty() && !req.source.is_empty() {
2193 self_constraints.push(Requirement {
2194 name: req.name.clone(),
2195 extras: Box::new([]),
2196 groups: req.groups.clone(),
2197 source: req.source.clone(),
2198 origin: req.origin.clone(),
2199 marker: req.marker,
2200 });
2201 }
2202 }
2203
2204 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2206 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2207
2208 Either::Right(requirements.into_iter())
2209 }
2210 }
2211
2212 fn requirements_for_extra<'data, 'parameters>(
2215 &'data self,
2216 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2217 extra: Option<&'parameters ExtraName>,
2218 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2219 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2220 env: &'parameters ResolverEnvironment,
2221 python_marker: MarkerTree,
2222 python_requirement: &'parameters PythonRequirement,
2223 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2224 where
2225 'data: 'parameters,
2226 {
2227 self.overrides
2228 .apply_for_package(override_package, dependencies)
2229 .filter(move |requirement| {
2230 !self
2231 .excludes
2232 .contains_for_package(exclusion_package, &requirement.name)
2233 })
2234 .map(move |mut requirement| {
2235 let marker = match extra {
2244 Some(extra) => requirement
2245 .marker
2246 .simplify_extras(slice::from_ref(extra))
2247 .simplify_not_extras_with(|candidate| candidate != extra)
2248 .and(
2249 requirement
2250 .marker
2251 .simplify_not_extras_with(|_| true)
2252 .negate(),
2253 ),
2254 None => requirement.marker.simplify_not_extras_with(|_| true),
2255 };
2256
2257 if requirement.marker != marker {
2258 requirement.to_mut().marker = marker;
2259 }
2260
2261 requirement
2262 })
2263 .filter(move |requirement| {
2264 Self::is_requirement_applicable(
2265 requirement,
2266 extra,
2267 env,
2268 python_marker,
2269 python_requirement,
2270 )
2271 })
2272 .flat_map(move |requirement| {
2273 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2274 requirement,
2275 extra,
2276 env,
2277 python_marker,
2278 python_requirement,
2279 ))
2280 })
2281 }
2282
2283 fn is_requirement_applicable(
2286 requirement: &Requirement,
2287 extra: Option<&ExtraName>,
2288 env: &ResolverEnvironment,
2289 python_marker: MarkerTree,
2290 python_requirement: &PythonRequirement,
2291 ) -> bool {
2292 match extra {
2294 Some(source_extra) => {
2295 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2296 return false;
2297 }
2298
2299 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2300 {
2301 return false;
2302 }
2303 }
2304 None => {
2305 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2306 return false;
2307 }
2308 }
2309 }
2310
2311 if python_marker.is_disjoint(requirement.marker) {
2314 trace!(
2315 "Skipping {requirement} because of Requires-Python: {requires_python}",
2316 requires_python = python_requirement.target(),
2317 );
2318 return false;
2319 }
2320
2321 if !env.included_by_marker(requirement.marker) {
2324 trace!("Skipping {requirement} because of {env}");
2325 return false;
2326 }
2327
2328 true
2329 }
2330
2331 fn constraints_for_requirement<'data, 'parameters>(
2334 &'data self,
2335 requirement: Cow<'data, Requirement>,
2336 extra: Option<&'parameters ExtraName>,
2337 env: &'parameters ResolverEnvironment,
2338 python_marker: MarkerTree,
2339 python_requirement: &'parameters PythonRequirement,
2340 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2341 where
2342 'data: 'parameters,
2343 {
2344 self.constraints
2345 .get(&requirement.name)
2346 .into_iter()
2347 .flatten()
2348 .filter_map(move |constraint| {
2349 let constraint = if constraint.marker.is_true() {
2352 if requirement.marker.is_true() {
2356 Cow::Borrowed(constraint)
2357 } else {
2358 let mut marker = constraint.marker;
2359 marker = marker.and(requirement.marker);
2360
2361 if marker.is_false() {
2362 trace!(
2363 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2364 constraint.marker.try_to_string().unwrap(),
2365 requirement.marker.try_to_string().unwrap(),
2366 );
2367 return None;
2368 }
2369
2370 Cow::Owned(Requirement {
2371 name: constraint.name.clone(),
2372 extras: constraint.extras.clone(),
2373 groups: constraint.groups.clone(),
2374 source: constraint.source.clone(),
2375 origin: constraint.origin.clone(),
2376 marker,
2377 })
2378 }
2379 } else {
2380 let requires_python = python_requirement.target();
2381
2382 let mut marker = constraint.marker;
2383 marker = marker.and(requirement.marker);
2384
2385 if marker.is_false() {
2386 trace!(
2387 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2388 constraint.marker.try_to_string().unwrap(),
2389 requirement.marker.try_to_string().unwrap(),
2390 );
2391 return None;
2392 }
2393
2394 if python_marker.is_disjoint(marker) {
2398 trace!(
2399 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2400 );
2401 return None;
2402 }
2403
2404 if marker == constraint.marker {
2405 Cow::Borrowed(constraint)
2406 } else {
2407 Cow::Owned(Requirement {
2408 name: constraint.name.clone(),
2409 extras: constraint.extras.clone(),
2410 groups: constraint.groups.clone(),
2411 source: constraint.source.clone(),
2412 origin: constraint.origin.clone(),
2413 marker,
2414 })
2415 }
2416 };
2417
2418 if !env.included_by_marker(constraint.marker) {
2421 trace!("Skipping {constraint} because of {env}");
2422 return None;
2423 }
2424
2425 match extra {
2427 Some(source_extra) => {
2428 if !constraint
2429 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2430 {
2431 return None;
2432 }
2433 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2434 {
2435 return None;
2436 }
2437 }
2438 None => {
2439 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2440 return None;
2441 }
2442 }
2443 }
2444
2445 Some(constraint)
2446 })
2447 }
2448
2449 async fn fetch<Provider: ResolverProvider>(
2451 self: Arc<Self>,
2452 provider: Arc<Provider>,
2453 request_stream: Receiver<Request>,
2454 ) -> Result<(), ResolveError> {
2455 let mut response_stream = ReceiverStream::new(request_stream)
2456 .map(|request| self.process_request(request, &*provider).boxed_local())
2457 .buffer_unordered(usize::MAX);
2461
2462 while let Some(response) = response_stream.next().await {
2463 match response? {
2464 Some(Response::Package(name, index, version_map)) => {
2465 trace!("Received package metadata for: {name}");
2466 if let Some(index) = index {
2467 self.index
2468 .explicit()
2469 .done((name, index), Arc::new(version_map));
2470 } else {
2471 self.index.implicit().done(name, Arc::new(version_map));
2472 }
2473 }
2474 Some(Response::Installed { dist, metadata }) => {
2475 trace!("Received installed distribution metadata for: {dist}");
2476 self.index
2477 .distributions()
2478 .done(dist.distribution_id(), Arc::new(metadata));
2479 }
2480 Some(Response::Dist { dist, metadata }) => {
2481 let dist_kind = match dist {
2482 Dist::Built(_) => "built",
2483 Dist::Source(_) => "source",
2484 };
2485 trace!("Received {dist_kind} distribution metadata for: {dist}");
2486 if let MetadataResponse::Unavailable(reason) = &metadata {
2487 let message = UnavailableVersion::from(reason).singular_message();
2488 if let Some(err) = reason.source() {
2489 warn!("{dist} {message}: {err}");
2491 } else {
2492 warn!("{dist} {message}");
2493 }
2494 }
2495 self.index
2496 .distributions()
2497 .done(dist.distribution_id(), Arc::new(metadata));
2498 }
2499 None => {}
2500 }
2501 }
2502
2503 Ok::<(), ResolveError>(())
2504 }
2505
2506 #[instrument(skip_all, fields(%request))]
2507 async fn process_request<Provider: ResolverProvider>(
2508 &self,
2509 request: Request,
2510 provider: &Provider,
2511 ) -> Result<Option<Response>, ResolveError> {
2512 match request {
2513 Request::Package(package_name, index) => {
2515 let package_versions = provider
2516 .get_package_versions(&package_name, index.as_ref())
2517 .boxed_local()
2518 .await
2519 .map_err(ResolveError::Client)?;
2520
2521 Ok(Some(Response::Package(
2522 package_name,
2523 index.map(IndexMetadata::into_url),
2524 package_versions,
2525 )))
2526 }
2527
2528 Request::Dist(dist) => {
2530 if let Some(version) = dist.version() {
2531 if let Some(index) = dist.index() {
2532 let versions_response = self.index.implicit().get(dist.name());
2534 if let Some(VersionsResponse::Found(version_maps)) =
2535 versions_response.as_deref()
2536 {
2537 for version_map in version_maps {
2538 if version_map.index() == Some(index) {
2539 let Some(metadata) = version_map.get_metadata(version) else {
2540 continue;
2541 };
2542 debug!("Found registry-provided metadata for: {dist}");
2543 return Ok(Some(Response::Dist {
2544 dist,
2545 metadata: MetadataResponse::Found(
2546 ArchiveMetadata::from_metadata23(metadata),
2547 ),
2548 }));
2549 }
2550 }
2551 }
2552
2553 let versions_response = self
2555 .index
2556 .explicit()
2557 .get(&(dist.name().clone(), index.clone()));
2558 if let Some(VersionsResponse::Found(version_maps)) =
2559 versions_response.as_deref()
2560 {
2561 for version_map in version_maps {
2562 let Some(metadata) = version_map.get_metadata(version) else {
2563 continue;
2564 };
2565 debug!("Found registry-provided metadata for: {dist}");
2566 return Ok(Some(Response::Dist {
2567 dist,
2568 metadata: MetadataResponse::Found(
2569 ArchiveMetadata::from_metadata23(metadata),
2570 ),
2571 }));
2572 }
2573 }
2574 }
2575 }
2576
2577 let metadata = provider
2578 .get_or_build_wheel_metadata(&dist)
2579 .boxed_local()
2580 .await?;
2581
2582 if let MetadataResponse::Found(metadata) = &metadata {
2583 if &metadata.metadata.name != dist.name() {
2584 return Err(ResolveError::MismatchedPackageName {
2585 request: "distribution metadata",
2586 expected: dist.name().clone(),
2587 actual: metadata.metadata.name.clone(),
2588 });
2589 }
2590 }
2591
2592 Ok(Some(Response::Dist { dist, metadata }))
2593 }
2594
2595 Request::Installed(dist) => {
2596 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2597
2598 if let MetadataResponse::Found(metadata) = &metadata {
2599 if &metadata.metadata.name != dist.name() {
2600 return Err(ResolveError::MismatchedPackageName {
2601 request: "installed metadata",
2602 expected: dist.name().clone(),
2603 actual: metadata.metadata.name.clone(),
2604 });
2605 }
2606 }
2607
2608 Ok(Some(Response::Installed { dist, metadata }))
2609 }
2610
2611 Request::Prefetch(package_name, range, python_requirement) => {
2613 let versions_response = self
2615 .index
2616 .implicit()
2617 .wait(&package_name)
2618 .await
2619 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2620
2621 let version_map = match *versions_response {
2622 VersionsResponse::Found(ref version_map) => version_map,
2623 VersionsResponse::NoIndex => {
2625 self.unavailable_packages
2626 .pin()
2627 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2628
2629 return Ok(None);
2630 }
2631 VersionsResponse::Offline => {
2632 self.unavailable_packages
2633 .pin()
2634 .insert(package_name.clone(), UnavailablePackage::Offline);
2635
2636 return Ok(None);
2637 }
2638 VersionsResponse::NotFound => {
2639 self.unavailable_packages
2640 .pin()
2641 .insert(package_name.clone(), UnavailablePackage::NotFound);
2642
2643 return Ok(None);
2644 }
2645 };
2646
2647 let env = ResolverEnvironment::universal(vec![]);
2649
2650 let Some(candidate) = self.selector.select(
2653 &package_name,
2654 &range,
2655 version_map,
2656 &self.preferences,
2657 &self.installed_packages,
2658 &self.exclusions,
2659 None,
2660 &env,
2661 self.tags.as_ref(),
2662 ) else {
2663 return Ok(None);
2664 };
2665
2666 let Some(dist) = candidate.compatible() else {
2668 return Ok(None);
2669 };
2670
2671 for version_map in version_map {
2673 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2674 let dist = dist.for_resolution();
2675 if version_map.index() == dist.index() {
2676 debug!("Found registry-provided metadata for: {dist}");
2677
2678 let metadata =
2679 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2680
2681 let dist = dist.to_owned();
2682 if &package_name != dist.name() {
2683 return Err(ResolveError::MismatchedPackageName {
2684 request: "distribution",
2685 expected: package_name,
2686 actual: dist.name().clone(),
2687 });
2688 }
2689
2690 let response = match dist {
2691 ResolvedDist::Installable { dist, .. } => Response::Dist {
2692 dist: (*dist).clone(),
2693 metadata,
2694 },
2695 ResolvedDist::Installed { dist } => Response::Installed {
2696 dist: (*dist).clone(),
2697 metadata,
2698 },
2699 };
2700
2701 return Ok(Some(response));
2702 }
2703 }
2704 }
2705
2706 if dist.wheel().is_none() {
2710 if !self.selector.use_highest_version(&package_name, &env) {
2711 if let Some((lower, _)) = range.iter().next() {
2712 if lower == Bound::Unbounded {
2713 debug!(
2714 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2715 );
2716 return Ok(None);
2717 }
2718 }
2719 }
2720 }
2721
2722 if let Some(requires_python) = dist.requires_python() {
2724 if !python_requirement.target().is_contained_by(requires_python) {
2725 return Ok(None);
2726 }
2727 }
2728
2729 if !self
2731 .hasher
2732 .allows_package(candidate.name(), candidate.version())
2733 {
2734 return Ok(None);
2735 }
2736
2737 let dist = dist.for_resolution();
2739 if self.index.distributions().register(dist.distribution_id()) {
2740 let dist = dist.to_owned();
2741 if &package_name != dist.name() {
2742 return Err(ResolveError::MismatchedPackageName {
2743 request: "distribution",
2744 expected: package_name,
2745 actual: dist.name().clone(),
2746 });
2747 }
2748
2749 let response = match dist {
2750 ResolvedDist::Installable { dist, .. } => {
2751 let metadata = provider
2752 .get_or_build_wheel_metadata(&dist)
2753 .boxed_local()
2754 .await?;
2755
2756 Response::Dist {
2757 dist: (*dist).clone(),
2758 metadata,
2759 }
2760 }
2761 ResolvedDist::Installed { dist } => {
2762 let metadata =
2763 provider.get_installed_metadata(&dist).boxed_local().await?;
2764
2765 Response::Installed {
2766 dist: (*dist).clone(),
2767 metadata,
2768 }
2769 }
2770 };
2771
2772 Ok(Some(response))
2773 } else {
2774 Ok(None)
2775 }
2776 }
2777 }
2778 }
2779
2780 fn convert_no_solution_err(
2781 &self,
2782 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2783 fork_urls: ForkUrls,
2784 fork_indexes: ForkIndexes,
2785 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2786 env: ResolverEnvironment,
2787 current_environment: MarkerEnvironment,
2788 visited: &FxHashSet<PackageName>,
2789 ) -> ResolveError {
2790 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2791 err,
2792 ));
2793 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2794
2795 let mut unavailable_packages = FxHashMap::default();
2796 for package in derivation_tree_packages(&err) {
2797 if let PubGrubPackageInner::Package { name, .. } = &**package {
2798 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2799 unavailable_packages.insert(name.clone(), reason.clone());
2800 }
2801 }
2802 }
2803
2804 let mut incomplete_packages = FxHashMap::default();
2805 let incomplete_packages_cache = self.incomplete_packages.pin();
2806 for package in derivation_tree_packages(&err) {
2807 if let PubGrubPackageInner::Package { name, .. } = &**package
2808 && let Some(versions) = incomplete_packages_cache.get(name)
2809 {
2810 for (version, reason) in &versions.pin() {
2811 incomplete_packages
2812 .entry(name.clone())
2813 .or_insert_with(BTreeMap::default)
2814 .insert(version.clone(), reason.clone());
2815 }
2816 }
2817 }
2818
2819 let mut available_indexes = FxHashMap::default();
2820 let mut included_versions = FxHashMap::default();
2821 let mut available_versions = FxHashMap::default();
2822
2823 let available_version_cutoff: Option<jiff::Timestamp> =
2824 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2825 .ok()
2826 .and_then(|s| s.parse().ok());
2827
2828 for package in derivation_tree_packages(&err) {
2829 let Some(name) = package.name() else { continue };
2830 if !visited.contains(name) {
2831 continue;
2836 }
2837 let versions_response = if let Some(index) = fork_indexes.get(name) {
2838 self.index
2839 .explicit()
2840 .get(&(name.clone(), index.url().clone()))
2841 } else {
2842 self.index.implicit().get(name)
2843 };
2844 if let Some(response) = versions_response {
2845 if let VersionsResponse::Found(ref version_maps) = *response {
2846 for version_map in version_maps {
2848 let package_included_versions = included_versions
2849 .entry(name.clone())
2850 .or_insert_with(BTreeSet::new);
2851 let package_available_versions = available_versions
2852 .entry(name.clone())
2853 .or_insert_with(BTreeSet::new);
2854
2855 for (version, dists) in version_map.iter(&Ranges::full()) {
2856 let excluded_from_included = || {
2861 let Some(included_version_cutoff) =
2862 version_map.included_version_cutoff()
2863 else {
2864 return false;
2865 };
2866 let Some(prioritized_dist) = dists.prioritized_dist() else {
2867 return true;
2868 };
2869 prioritized_dist.files().all(|file| {
2870 file.upload_time_utc_ms.is_none_or(|upload_time| {
2871 upload_time >= included_version_cutoff.as_millisecond()
2872 })
2873 })
2874 };
2875
2876 if !excluded_from_included() {
2877 package_included_versions.insert(version.clone());
2878 }
2879
2880 let excluded_from_available = || {
2886 let Some(ref exclude_newer) = available_version_cutoff else {
2887 return false;
2888 };
2889 let Some(prioritized_dist) = dists.prioritized_dist() else {
2890 return false;
2891 };
2892 prioritized_dist.files().all(|file| {
2893 file.upload_time_utc_ms.is_some_and(|upload_time| {
2894 upload_time >= exclude_newer.as_millisecond()
2895 })
2896 })
2897 };
2898
2899 if !excluded_from_available() {
2900 package_available_versions.insert(version.clone());
2901 }
2902 }
2903 }
2904
2905 available_indexes
2907 .entry(name.clone())
2908 .or_insert(BTreeSet::new())
2909 .extend(
2910 version_maps
2911 .iter()
2912 .filter_map(|version_map| version_map.index().cloned()),
2913 );
2914 }
2915 }
2916 }
2917
2918 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2919 err,
2920 self.index.clone(),
2921 included_versions,
2922 available_versions,
2923 available_indexes,
2924 self.selector.clone(),
2925 self.python_requirement.clone(),
2926 self.locations.clone(),
2927 self.capabilities.clone(),
2928 unavailable_packages,
2929 incomplete_packages,
2930 fork_urls,
2931 fork_indexes,
2932 env,
2933 current_environment,
2934 self.tags.clone(),
2935 self.workspace_members.clone(),
2936 self.options.clone(),
2937 )))
2938 }
2939
2940 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2941 if let Some(reporter) = self.reporter.as_ref() {
2942 match &**package {
2943 PubGrubPackageInner::Root(_) => {}
2944 PubGrubPackageInner::Python(_) => {}
2945 PubGrubPackageInner::System(_) => {}
2946 PubGrubPackageInner::Marker { .. } => {}
2947 PubGrubPackageInner::Extra { .. } => {}
2948 PubGrubPackageInner::Group { .. } => {}
2949 PubGrubPackageInner::Package { name, .. } => {
2950 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2951 }
2952 }
2953 }
2954 }
2955
2956 fn on_complete(&self) {
2957 if let Some(reporter) = self.reporter.as_ref() {
2958 reporter.on_complete();
2959 }
2960 }
2961}
2962
2963#[derive(Clone, Default)]
2966struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2967
2968impl KnownVersions {
2969 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2982 &'a mut self,
2983 index: &InMemoryIndex,
2984 installed_packages: &InstalledPackages,
2985 fork_urls: &ForkUrls,
2986 fork_indexes: &ForkIndexes,
2987 package: &PubGrubPackage,
2988 ) -> Option<&'a [Version]> {
2989 let name = package.name_no_root()?;
2990 if fork_urls.get(name).is_some() {
2992 return None;
2993 }
2994 if !self.0.contains_key(name) {
2995 let response = if let Some(index_metadata) = fork_indexes.get(name) {
2996 index
2997 .explicit()
2998 .get(&(name.clone(), index_metadata.url().clone()))?
2999 } else {
3000 index.implicit().get(name)?
3001 };
3002 let VersionsResponse::Found(ref version_maps) = *response else {
3003 return None;
3004 };
3005 let mut versions: Vec<Version> = version_maps
3006 .iter()
3007 .flat_map(|version_map| version_map.included_versions().cloned())
3008 .chain(
3009 installed_packages
3010 .get_packages(name)
3011 .iter()
3012 .map(|dist| dist.version().clone()),
3013 )
3014 .collect();
3015 versions.sort_unstable();
3016 versions.dedup();
3017 self.0.insert(name.clone(), versions.into());
3018 }
3019 Some(&self.0[name][..])
3020 }
3021}
3022
3023#[derive(Clone)]
3025pub(crate) struct ForkState {
3026 pubgrub: State<UvDependencyProvider>,
3034 initial_id: Option<Id<PubGrubPackage>>,
3037 initial_version: Option<Version>,
3040 next: Id<PubGrubPackage>,
3042 pins: FilePins,
3051 fork_urls: ForkUrls,
3057 fork_indexes: ForkIndexes,
3062 priorities: PubGrubPriorities,
3068 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3071 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3073 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3075 known_versions: KnownVersions,
3079 env: ResolverEnvironment,
3094 python_requirement: PythonRequirement,
3107 conflict_tracker: ConflictTracker,
3108 prefetcher: BatchPrefetcher,
3112}
3113
3114impl ForkState {
3115 fn new(
3116 pubgrub: State<UvDependencyProvider>,
3117 env: ResolverEnvironment,
3118 python_requirement: PythonRequirement,
3119 prefetcher: BatchPrefetcher,
3120 ) -> Self {
3121 Self {
3122 initial_id: None,
3123 initial_version: None,
3124 next: pubgrub.root_package,
3125 pubgrub,
3126 pins: FilePins::default(),
3127 fork_urls: ForkUrls::default(),
3128 fork_indexes: ForkIndexes::default(),
3129 priorities: PubGrubPriorities::default(),
3130 added_dependencies: FxHashMap::default(),
3131 pre_visited: FxHashMap::default(),
3132 selected_versions: FxHashMap::default(),
3133 known_versions: KnownVersions::default(),
3134 env,
3135 python_requirement,
3136 conflict_tracker: ConflictTracker::default(),
3137 prefetcher,
3138 }
3139 }
3140
3141 fn visit_package_version_dependencies(
3144 &mut self,
3145 for_package: Id<PubGrubPackage>,
3146 for_version: &Version,
3147 urls: &Urls,
3148 indexes: &Indexes,
3149 dependencies: &[PubGrubDependency],
3150 git: &GitResolver,
3151 workspace_members: &BTreeSet<PackageName>,
3152 resolution_strategy: &ResolutionStrategy,
3153 ) -> Result<(), ResolveError> {
3154 for dependency in dependencies {
3155 let PubGrubDependency {
3156 package,
3157 version,
3158 parent: _,
3159 source,
3160 } = dependency;
3161
3162 let mut has_url = false;
3163 if let Some(name) = package.name() {
3164 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3169 self.fork_urls.insert(name, url, &self.env)?;
3170 has_url = true;
3171 }
3172
3173 if let Some(index) = source.explicit_index() {
3174 self.fork_indexes.insert(name, index, &self.env)?;
3175 }
3176
3177 for index in indexes.get(name, &self.env) {
3179 self.fork_indexes.insert(name, index, &self.env)?;
3180 }
3181 }
3182
3183 if let Some(name) = self.pubgrub.package_store[for_package]
3184 .name_no_root()
3185 .filter(|name| !workspace_members.contains(name))
3186 {
3187 debug!(
3188 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3189 );
3190 } else {
3191 debug!("Adding direct dependency: {package}{version}");
3193
3194 let missing_lower_bound = version
3196 .bounding_range()
3197 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3198 let strategy_lowest = matches!(
3199 resolution_strategy,
3200 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3201 );
3202
3203 if !has_url && missing_lower_bound && strategy_lowest {
3204 let name = package.name_no_root().unwrap();
3205 let bound_on_other_package = dependencies.iter().any(|other| {
3212 Some(name) == other.package.name()
3213 && !other
3214 .version
3215 .bounding_range()
3216 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3217 });
3218
3219 if !bound_on_other_package {
3220 warn_user_once!(
3221 "The direct dependency `{name}` is unpinned. \
3222 Consider setting a lower bound when using `--resolution lowest` \
3223 or `--resolution lowest-direct` to avoid using outdated versions.",
3224 );
3225 }
3226 }
3227 }
3228
3229 self.priorities.insert(package, version, &self.fork_urls);
3231 if let Some(base_package) = package.base_package() {
3234 self.priorities
3235 .insert(&base_package, version, &self.fork_urls);
3236 }
3237 }
3238
3239 Ok(())
3240 }
3241
3242 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3248 &mut self,
3249 for_package: Id<PubGrubPackage>,
3250 for_version: &Version,
3251 dependencies: Vec<PubGrubDependency>,
3252 index: &InMemoryIndex,
3253 installed_packages: &InstalledPackages,
3254 ) {
3255 for dependency in &dependencies {
3256 let PubGrubDependency {
3257 package,
3258 version,
3259 parent: _,
3260 source: _,
3261 } = dependency;
3262
3263 let Some(base_package) = package.base_package() else {
3264 continue;
3265 };
3266
3267 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3268 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3269 self.pubgrub.add_proxy_package_incompatibility(
3270 proxy_package,
3271 base_package_id,
3272 version.clone(),
3273 );
3274 }
3275
3276 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
3279 let conflict = self.pubgrub.add_package_version_dependencies(
3280 self.next,
3281 for_version.clone(),
3282 versions,
3283 dependencies.into_iter().map(|dependency| {
3284 let PubGrubDependency {
3285 package,
3286 version,
3287 parent: _,
3288 source: _,
3289 } = dependency;
3290 (package, version)
3291 }),
3292 );
3293
3294 if let Some(incompatibility) = conflict {
3297 self.record_conflict(for_package, Some(for_version), incompatibility);
3298 }
3299 }
3300
3301 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
3303 &mut self,
3304 version: &Version,
3305 index: &InMemoryIndex,
3306 installed_packages: &InstalledPackages,
3307 ) -> Range<Version> {
3308 widen_to_gap(
3309 version,
3310 self.known_versions.get_or_update(
3311 index,
3312 installed_packages,
3313 &self.fork_urls,
3314 &self.fork_indexes,
3315 &self.pubgrub.package_store[self.next],
3316 ),
3317 )
3318 }
3319
3320 fn record_conflict(
3321 &mut self,
3322 affected: Id<PubGrubPackage>,
3323 version: Option<&Version>,
3324 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3325 ) {
3326 let mut culprit_is_real = false;
3327 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3328 if incompatible == affected {
3329 continue;
3330 }
3331 if self.pubgrub.package_store[affected].name()
3332 == self.pubgrub.package_store[incompatible].name()
3333 {
3334 continue;
3337 }
3338 culprit_is_real = true;
3339 let culprit_count = self
3340 .conflict_tracker
3341 .culprit
3342 .entry(incompatible)
3343 .or_default();
3344 *culprit_count += 1;
3345 if *culprit_count == CONFLICT_THRESHOLD {
3346 self.conflict_tracker.deprioritize.push(incompatible);
3347 }
3348 }
3349 if culprit_is_real {
3352 if tracing::enabled!(Level::DEBUG) {
3353 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3354 .iter()
3355 .map(|(package, _term)| &self.pubgrub.package_store[package])
3356 .join(", ");
3357 if let Some(version) = version {
3358 debug!(
3359 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3360 self.pubgrub.package_store[affected], version, incompatibility
3361 );
3362 } else {
3363 debug!(
3364 "Recording unit propagation conflict of {} from incompatibility of ({})",
3365 self.pubgrub.package_store[affected], incompatibility
3366 );
3367 }
3368 }
3369
3370 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3371 *affected_count += 1;
3372 if *affected_count == CONFLICT_THRESHOLD {
3373 self.conflict_tracker.prioritize.push(self.next);
3374 }
3375 }
3376 }
3377
3378 fn reprioritize_conflicts(&mut self) {
3382 for package in self.conflict_tracker.prioritize.drain(..) {
3383 let changed = self
3384 .priorities
3385 .mark_conflict_early(&self.pubgrub.package_store[package]);
3386 if changed {
3387 debug!(
3388 "Package {} has too many conflicts (affected), prioritizing",
3389 &self.pubgrub.package_store[package]
3390 );
3391 } else {
3392 debug!(
3393 "Package {} has too many conflicts (affected), already {:?}",
3394 self.pubgrub.package_store[package],
3395 self.priorities.get(&self.pubgrub.package_store[package])
3396 );
3397 }
3398 }
3399
3400 for package in self.conflict_tracker.deprioritize.drain(..) {
3401 let changed = self
3402 .priorities
3403 .mark_conflict_late(&self.pubgrub.package_store[package]);
3404 if changed {
3405 debug!(
3406 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3407 self.pubgrub.package_store[package],
3408 );
3409 let backtrack_level = self.pubgrub.backtrack_package(package);
3410 if let Some(backtrack_level) = backtrack_level {
3411 debug!("Backtracked {backtrack_level} decisions");
3412 } else {
3413 debug!(
3414 "Package {} is not decided, cannot backtrack",
3415 self.pubgrub.package_store[package]
3416 );
3417 }
3418 } else {
3419 debug!(
3420 "Package {} has too many conflicts (culprit), already {:?}",
3421 self.pubgrub.package_store[package],
3422 self.priorities.get(&self.pubgrub.package_store[package])
3423 );
3424 }
3425 }
3426 }
3427
3428 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3436 &mut self,
3437 version: Version,
3438 reason: UnavailableVersion,
3439 index: &InMemoryIndex,
3440 installed_packages: &InstalledPackages,
3441 ) {
3442 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3443
3444 if let UnavailableVersion::IncompatibleDist(
3448 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3449 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3450 ) = reason
3451 {
3452 let package = &self.next;
3453 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3454 PubGrubPackageInner::Python(match kind {
3455 PythonRequirementKind::Installed => PubGrubPython::Installed,
3456 PythonRequirementKind::Target => PubGrubPython::Target,
3457 }),
3458 ));
3459 self.pubgrub
3460 .add_incompatibility(Incompatibility::from_dependency(
3461 *package,
3462 versions,
3463 (
3464 python,
3465 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3466 ),
3467 ));
3468 self.pubgrub
3469 .partial_solution
3470 .add_decision(self.next, version);
3471 return;
3472 }
3473 self.pubgrub
3474 .add_incompatibility(Incompatibility::custom_term(
3475 self.next,
3476 Term::Positive(versions),
3477 UnavailableReason::Version(reason),
3478 ));
3479 }
3480
3481 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3487 self.selected_versions.clear();
3488 self.env = env;
3489 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3491 debug!("Narrowed `requires-python` bound to: {}", req.target());
3492 self.python_requirement = req;
3493 }
3494 self
3495 }
3496
3497 fn source(
3501 &self,
3502 name: &PackageName,
3503 version: &Version,
3504 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3505 let url = self.fork_urls.get(name);
3506 let index = url
3507 .is_none()
3508 .then(|| {
3509 self.pins
3510 .get(name, version)
3511 .expect("Every package should be pinned")
3512 .index()
3513 })
3514 .flatten();
3515 (url, index)
3516 }
3517
3518 fn into_resolution(self) -> Resolution {
3519 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3520 let edge_count: usize = solution
3521 .keys()
3522 .map(|package| self.pubgrub.incompatibilities[package].len())
3523 .sum();
3524 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3525 for (package, self_version) in &solution {
3526 for id in &self.pubgrub.incompatibilities[package] {
3527 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3528 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3529 &incompatibility.kind
3530 else {
3531 continue;
3532 };
3533 let (self_package, dependency_package) = (*self_package, *dependency_package);
3534 let Some((self_range, dependency_range)) =
3535 incompatibility.dependency_version_sets()
3536 else {
3537 continue;
3538 };
3539 let dependency_range =
3540 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3541 if *package != self_package {
3542 continue;
3543 }
3544 if !self_range.contains(self_version) {
3545 continue;
3546 }
3547 let Some(dependency_version) = solution.get(&dependency_package) else {
3548 continue;
3549 };
3550 if !dependency_range.contains(dependency_version) {
3551 continue;
3552 }
3553
3554 let self_package = &self.pubgrub.package_store[self_package];
3555 let dependency_package = &self.pubgrub.package_store[dependency_package];
3556
3557 let (self_name, self_extra, self_group) = match &**self_package {
3558 PubGrubPackageInner::Package {
3559 name: self_name,
3560 extra: self_extra,
3561 group: self_group,
3562 marker: _,
3563 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3564
3565 PubGrubPackageInner::Root(_) => (None, None, None),
3566
3567 _ => continue,
3568 };
3569
3570 let (self_url, self_index) = self_name
3571 .map(|self_name| self.source(self_name, self_version))
3572 .unwrap_or((None, None));
3573
3574 match **dependency_package {
3575 PubGrubPackageInner::Package {
3576 name: ref dependency_name,
3577 extra: ref dependency_extra,
3578 group: ref dependency_dev,
3579 marker: ref dependency_marker,
3580 } => {
3581 debug_assert!(
3582 dependency_extra.is_none(),
3583 "Packages should depend on an extra proxy"
3584 );
3585 debug_assert!(
3586 dependency_dev.is_none(),
3587 "Packages should depend on a group proxy"
3588 );
3589
3590 if self_group.is_none() {
3593 if self_name == Some(dependency_name) {
3594 continue;
3595 }
3596 }
3597
3598 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3599
3600 let edge = ResolutionDependencyEdge {
3601 from: self_name.cloned(),
3602 from_version: self_version.clone(),
3603 from_url: self_url.cloned(),
3604 from_index: self_index.cloned(),
3605 from_extra: self_extra.cloned(),
3606 from_group: self_group.cloned(),
3607 to: dependency_name.clone(),
3608 to_version: dependency_version.clone(),
3609 to_url: to_url.cloned(),
3610 to_index: to_index.cloned(),
3611 to_extra: dependency_extra.clone(),
3612 to_group: dependency_dev.clone(),
3613 marker: *dependency_marker,
3614 };
3615 edges.push(edge);
3616 }
3617
3618 PubGrubPackageInner::Marker {
3619 name: ref dependency_name,
3620 marker: ref dependency_marker,
3621 } => {
3622 if self_group.is_none() {
3625 if self_name == Some(dependency_name) {
3626 continue;
3627 }
3628 }
3629
3630 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3631
3632 let edge = ResolutionDependencyEdge {
3633 from: self_name.cloned(),
3634 from_version: self_version.clone(),
3635 from_url: self_url.cloned(),
3636 from_index: self_index.cloned(),
3637 from_extra: self_extra.cloned(),
3638 from_group: self_group.cloned(),
3639 to: dependency_name.clone(),
3640 to_version: dependency_version.clone(),
3641 to_url: to_url.cloned(),
3642 to_index: to_index.cloned(),
3643 to_extra: None,
3644 to_group: None,
3645 marker: *dependency_marker,
3646 };
3647 edges.push(edge);
3648 }
3649
3650 PubGrubPackageInner::Extra {
3651 name: ref dependency_name,
3652 extra: ref dependency_extra,
3653 marker: ref dependency_marker,
3654 } => {
3655 if self_group.is_none() {
3656 debug_assert!(
3657 self_name != Some(dependency_name),
3658 "Extras should be flattened"
3659 );
3660 }
3661 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3662
3663 let edge = ResolutionDependencyEdge {
3665 from: self_name.cloned(),
3666 from_version: self_version.clone(),
3667 from_url: self_url.cloned(),
3668 from_index: self_index.cloned(),
3669 from_extra: self_extra.cloned(),
3670 from_group: self_group.cloned(),
3671 to: dependency_name.clone(),
3672 to_version: dependency_version.clone(),
3673 to_url: to_url.cloned(),
3674 to_index: to_index.cloned(),
3675 to_extra: Some(dependency_extra.clone()),
3676 to_group: None,
3677 marker: *dependency_marker,
3678 };
3679 edges.push(edge);
3680
3681 let edge = ResolutionDependencyEdge {
3683 from: self_name.cloned(),
3684 from_version: self_version.clone(),
3685 from_url: self_url.cloned(),
3686 from_index: self_index.cloned(),
3687 from_extra: self_extra.cloned(),
3688 from_group: self_group.cloned(),
3689 to: dependency_name.clone(),
3690 to_version: dependency_version.clone(),
3691 to_url: to_url.cloned(),
3692 to_index: to_index.cloned(),
3693 to_extra: None,
3694 to_group: None,
3695 marker: *dependency_marker,
3696 };
3697 edges.push(edge);
3698 }
3699
3700 PubGrubPackageInner::Group {
3701 name: ref dependency_name,
3702 group: ref dependency_group,
3703 marker: ref dependency_marker,
3704 } => {
3705 debug_assert!(
3706 self_name != Some(dependency_name),
3707 "Groups should be flattened"
3708 );
3709
3710 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3711
3712 let edge = ResolutionDependencyEdge {
3715 from: self_name.cloned(),
3716 from_version: self_version.clone(),
3717 from_url: self_url.cloned(),
3718 from_index: self_index.cloned(),
3719 from_extra: self_extra.cloned(),
3720 from_group: self_group.cloned(),
3721 to: dependency_name.clone(),
3722 to_version: dependency_version.clone(),
3723 to_url: to_url.cloned(),
3724 to_index: to_index.cloned(),
3725 to_extra: None,
3726 to_group: Some(dependency_group.clone()),
3727 marker: *dependency_marker,
3728 };
3729 edges.push(edge);
3730 }
3731
3732 _ => {}
3733 }
3734 }
3735 }
3736
3737 let nodes = solution
3738 .into_iter()
3739 .filter_map(|(package, version)| {
3740 if let PubGrubPackageInner::Package {
3741 name,
3742 extra,
3743 group,
3744 marker: MarkerTree::TRUE,
3745 } = &*self.pubgrub.package_store[package]
3746 {
3747 let (url, index) = self.source(name, &version);
3748 Some((
3749 ResolutionPackage {
3750 name: name.clone(),
3751 extra: extra.clone(),
3752 dev: group.clone(),
3753 url: url.cloned(),
3754 index: index.cloned(),
3755 },
3756 version,
3757 ))
3758 } else {
3759 None
3760 }
3761 })
3762 .collect();
3763
3764 Resolution {
3765 nodes,
3766 edges,
3767 pins: self.pins,
3768 env: self.env,
3769 }
3770 }
3771}
3772
3773fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3782 let versions = Range::singleton(version.clone());
3783 match known_versions {
3784 Some(known_versions) if !known_versions.is_empty() => {
3785 versions.widen_versions(known_versions)
3786 }
3787 _ => versions,
3788 }
3789}
3790
3791#[derive(Debug)]
3793#[expect(clippy::large_enum_variant)]
3794pub(crate) enum Request {
3795 Package(PackageName, Option<IndexMetadata>),
3797 Dist(Dist),
3799 Installed(InstalledDist),
3801 Prefetch(PackageName, Range<Version>, PythonRequirement),
3803}
3804
3805impl<'a> From<ResolvedDistRef<'a>> for Request {
3806 fn from(dist: ResolvedDistRef<'a>) -> Self {
3807 match dist {
3813 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3814 let source = prioritized.source_dist().expect("a source distribution");
3817 assert_eq!(
3818 (&sdist.name, &sdist.version),
3819 (&source.name, &source.version),
3820 "expected chosen sdist to match prioritized sdist"
3821 );
3822 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3823 }
3824 ResolvedDistRef::InstallableRegistryBuiltDist {
3825 wheel, prioritized, ..
3826 } => {
3827 assert_eq!(
3828 Some(&wheel.filename),
3829 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3830 "expected chosen wheel to match best wheel"
3831 );
3832 let built = prioritized.built_dist().expect("at least one wheel");
3835 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3836 }
3837 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3838 }
3839 }
3840}
3841
3842impl Display for Request {
3843 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3844 match self {
3845 Self::Package(package_name, _) => {
3846 write!(f, "Versions {package_name}")
3847 }
3848 Self::Dist(dist) => {
3849 write!(f, "Metadata {dist}")
3850 }
3851 Self::Installed(dist) => {
3852 write!(f, "Installed metadata {dist}")
3853 }
3854 Self::Prefetch(package_name, range, _) => {
3855 write!(f, "Prefetch {package_name} {range}")
3856 }
3857 }
3858 }
3859}
3860
3861#[derive(Debug)]
3862#[expect(clippy::large_enum_variant)]
3863enum Response {
3864 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3866 Dist {
3868 dist: Dist,
3869 metadata: MetadataResponse,
3870 },
3871 Installed {
3873 dist: InstalledDist,
3874 metadata: MetadataResponse,
3875 },
3876}
3877
3878enum Dependencies {
3884 Unavailable(UnavailableVersion),
3886 Available(Vec<PubGrubDependency>),
3892 RequiresPython(VersionSpecifiers),
3894 Unforkable(Vec<PubGrubDependency>),
3900}
3901
3902#[derive(Debug)]
3909enum ForkedDependencies {
3910 Unavailable(UnavailableVersion),
3912 Unforked(Vec<PubGrubDependency>),
3916 Forked {
3922 forks: Vec<Fork>,
3923 diverging_packages: BTreeSet<PackageName>,
3925 },
3926 RequiresPython(VersionSpecifiers),
3928}
3929
3930impl ForkedDependencies {
3931 fn from_dependencies_universal(
3938 dependencies: Dependencies,
3939 env: &ResolverEnvironment,
3940 python_requirement: &PythonRequirement,
3941 conflicts: &Conflicts,
3942 ) -> Self {
3943 let deps = match dependencies {
3944 Dependencies::Available(deps) => deps,
3945 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3946 Dependencies::RequiresPython(requires_python) => {
3947 return Self::RequiresPython(requires_python);
3948 }
3949 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3950 };
3951 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3952 for dep in deps {
3953 let name = dep
3954 .package
3955 .name()
3956 .expect("dependency always has a name")
3957 .clone();
3958 name_to_deps.entry(name).or_default().push(dep);
3959 }
3960 let (mut forks, diverging_packages) =
3961 Self::fork(name_to_deps, env, python_requirement, conflicts);
3962 if forks.is_empty() {
3963 Self::Unforked(vec![])
3964 } else if forks.len() == 1 {
3965 Self::Unforked(forks.pop().unwrap().dependencies)
3966 } else {
3967 Self::Forked {
3968 forks,
3969 diverging_packages,
3970 }
3971 }
3972 }
3973
3974 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3977 match dependencies {
3978 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3979 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3980 Dependencies::Unavailable(err) => Self::Unavailable(err),
3981 }
3982 }
3983
3984 fn fork(
3992 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3993 env: &ResolverEnvironment,
3994 python_requirement: &PythonRequirement,
3995 conflicts: &Conflicts,
3996 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3997 let python_marker = python_requirement.to_marker_tree();
3998
3999 let mut forks = vec![Fork::new(env.clone())];
4000 let mut diverging_packages = BTreeSet::new();
4001 for (name, mut deps) in name_to_deps {
4002 assert!(!deps.is_empty(), "every name has at least one dependency");
4003 if let [dep] = deps.as_slice() {
4014 if marker::requires_python(dep.package.marker())
4022 .is_none_or(|bound| !python_requirement.raises(&bound))
4023 {
4024 let dep = deps.pop().unwrap();
4025 let marker = dep.package.marker();
4026 for fork in &mut forks {
4027 if fork.env.included_by_marker(marker) {
4028 fork.add_dependency(dep.clone());
4029 }
4030 }
4031 continue;
4032 }
4033 } else {
4034 if let Some(dep) = deps.first() {
4036 let marker = dep.package.marker();
4037 if deps.iter().all(|dep| marker == dep.package.marker()) {
4038 if marker::requires_python(marker)
4042 .is_none_or(|bound| !python_requirement.raises(&bound))
4043 {
4044 for dep in deps {
4045 for fork in &mut forks {
4046 if fork.env.included_by_marker(marker) {
4047 fork.add_dependency(dep.clone());
4048 }
4049 }
4050 }
4051 continue;
4052 }
4053 }
4054 }
4055 }
4056 for dep in deps {
4057 let mut forker = match ForkingPossibility::new(env, &dep) {
4058 ForkingPossibility::Possible(forker) => forker,
4059 ForkingPossibility::DependencyAlwaysExcluded => {
4060 continue;
4063 }
4064 ForkingPossibility::NoForkingPossible => {
4065 for fork in &mut forks {
4068 fork.add_dependency(dep.clone());
4069 }
4070 continue;
4071 }
4072 };
4073 diverging_packages.insert(name.clone());
4075
4076 let mut new = vec![];
4077 for fork in std::mem::take(&mut forks) {
4078 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4079 new.push(fork);
4080 continue;
4081 };
4082 forker = remaining_forker;
4083
4084 for fork_env in envs {
4085 let mut new_fork = fork.clone();
4086 new_fork.set_env(fork_env);
4087 if forker.included(&new_fork.env) {
4092 new_fork.add_dependency(dep.clone());
4093 }
4094 if new_fork.env.included_by_marker(python_marker) {
4097 new.push(new_fork);
4098 }
4099 }
4100 }
4101 forks = new;
4102 }
4103 }
4104 for set in conflicts.iter() {
4119 let mut new = vec![];
4120 for fork in std::mem::take(&mut forks) {
4121 let mut has_conflicting_dependency = false;
4129 for item in set.iter() {
4130 if fork.contains_conflicting_item(item.as_ref()) {
4131 has_conflicting_dependency = true;
4132 diverging_packages.insert(item.package().clone());
4133 break;
4134 }
4135 }
4136 if !has_conflicting_dependency {
4137 new.push(fork);
4138 continue;
4139 }
4140
4141 let non_excluded: Vec<_> = set
4147 .iter()
4148 .filter(|item| fork.env.included_by_group(item.as_ref()))
4149 .collect();
4150 if non_excluded.len() < 2 {
4151 let dominated = non_excluded.iter().all(|item| {
4156 !conflicts.iter().any(|other_set| {
4157 !std::ptr::eq(set, other_set)
4158 && other_set.contains(item.package(), item.kind().as_ref())
4159 && other_set
4160 .iter()
4161 .filter(|other_item| {
4162 other_item.package() != item.package()
4163 || other_item.kind() != item.kind()
4164 })
4165 .any(|other_item| {
4166 fork.env.included_by_group(other_item.as_ref())
4167 })
4168 })
4169 });
4170 if dominated {
4171 let rules: Vec<_> = set
4176 .iter()
4177 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4178 .cloned()
4179 .map(Err)
4180 .collect();
4181 if let Some(filtered) = fork.filter(rules) {
4182 new.push(filtered);
4183 }
4184 continue;
4185 }
4186 }
4187
4188 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4190 new.push(fork_none);
4191 }
4192
4193 for (i, _) in set.iter().enumerate() {
4201 let fork_allows_group = fork.clone().filter(
4202 set.iter()
4203 .cloned()
4204 .enumerate()
4205 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4206 );
4207 if let Some(fork_allows_group) = fork_allows_group {
4208 new.push(fork_allows_group);
4209 }
4210 }
4211 }
4212 forks = new;
4213 }
4214 (forks, diverging_packages)
4215 }
4216}
4217
4218#[derive(Clone, Debug)]
4228struct Fork {
4229 dependencies: Vec<PubGrubDependency>,
4238 conflicts: crate::FxHashbrownSet<ConflictItem>,
4244 env: ResolverEnvironment,
4256}
4257
4258impl Fork {
4259 fn new(env: ResolverEnvironment) -> Self {
4262 Self {
4263 dependencies: vec![],
4264 conflicts: crate::FxHashbrownSet::default(),
4265 env,
4266 }
4267 }
4268
4269 fn add_dependency(&mut self, dep: PubGrubDependency) {
4271 if let Some(conflicting_item) = dep.conflicting_item() {
4272 self.conflicts.insert(conflicting_item.to_owned());
4273 }
4274 self.dependencies.push(dep);
4275 }
4276
4277 fn set_env(&mut self, env: ResolverEnvironment) {
4282 self.env = env;
4283 self.dependencies.retain(|dep| {
4284 let marker = dep.package.marker();
4285 if self.env.included_by_marker(marker) {
4286 return true;
4287 }
4288 if let Some(conflicting_item) = dep.conflicting_item() {
4289 self.conflicts.remove(&conflicting_item);
4290 }
4291 false
4292 });
4293 }
4294
4295 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4298 self.conflicts.contains(&item)
4299 }
4300
4301 fn filter(
4308 mut self,
4309 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4310 ) -> Option<Self> {
4311 self.env = self.env.filter_by_group(rules)?;
4312 self.dependencies.retain(|dep| {
4313 let Some(conflicting_item) = dep.conflicting_item() else {
4314 return true;
4315 };
4316 if self.env.included_by_group(conflicting_item) {
4317 return true;
4318 }
4319 match conflicting_item.kind() {
4320 ConflictKindRef::Project => {
4323 if dep.parent.is_some() {
4324 return true;
4325 }
4326 }
4327 ConflictKindRef::Group(_) => {}
4328 ConflictKindRef::Extra(_) => {}
4329 }
4330 self.conflicts.remove(&conflicting_item);
4331 false
4332 });
4333 Some(self)
4334 }
4335
4336 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4338 cmp_requires_python(&self.env, &other.env)
4339 }
4340
4341 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4343 let self_upper_bounds = self
4348 .dependencies
4349 .iter()
4350 .filter(|dep| {
4351 dep.version
4352 .bounding_range()
4353 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4354 })
4355 .count();
4356 let other_upper_bounds = other
4357 .dependencies
4358 .iter()
4359 .filter(|dep| {
4360 dep.version
4361 .bounding_range()
4362 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4363 })
4364 .count();
4365
4366 self_upper_bounds.cmp(&other_upper_bounds)
4367 }
4368}
4369
4370fn cmp_requires_python(
4372 self_env: &ResolverEnvironment,
4373 other_env: &ResolverEnvironment,
4374) -> Ordering {
4375 let self_bound = self_env.requires_python().unwrap_or_default();
4385 let other_bound = other_env.requires_python().unwrap_or_default();
4386 self_bound.lower().cmp(other_bound.lower())
4387}
4388
4389impl Eq for Fork {}
4390
4391impl PartialEq for Fork {
4392 fn eq(&self, other: &Self) -> bool {
4393 self.dependencies == other.dependencies && self.env == other.env
4394 }
4395}
4396
4397#[derive(Debug, Clone)]
4398pub(crate) struct VersionFork {
4399 env: ResolverEnvironment,
4401 id: Id<PubGrubPackage>,
4403 version: Option<Version>,
4405}
4406
4407fn enrich_dependency_error(
4409 error: ResolveError,
4410 id: Id<PubGrubPackage>,
4411 version: &Version,
4412 pubgrub: &State<UvDependencyProvider>,
4413) -> ResolveError {
4414 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4415 return error;
4416 };
4417 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4418 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4419}
4420
4421fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4423 let package = &state.package_store[id];
4424 if package.is_root() {
4425 return MarkerTree::TRUE;
4426 }
4427
4428 let mut ancestors = FxHashSet::default();
4431 let mut stack = vec![id];
4432 let mut root = None;
4433 ancestors.insert(id);
4434
4435 while let Some(current) = stack.pop() {
4436 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4437 continue;
4438 };
4439
4440 for index in incompatibilities {
4441 let incompat = &state.incompatibility_store[*index];
4442 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4443 if current != *child {
4444 continue;
4445 }
4446 if ancestors.insert(*parent) {
4447 if state.package_store[*parent].is_root() {
4448 root = Some(*parent);
4449 }
4450 stack.push(*parent);
4451 }
4452 }
4453 }
4454 }
4455
4456 let Some(root) = root else {
4457 return MarkerTree::FALSE;
4458 };
4459
4460 let mut environments = FxHashMap::default();
4463 let mut queue = VecDeque::from([root]);
4464 environments.insert(root, MarkerTree::TRUE);
4465
4466 while let Some(current) = queue.pop_front() {
4467 let Some(current_environment) = environments.get(¤t).copied() else {
4468 continue;
4469 };
4470 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4471 continue;
4472 };
4473
4474 for index in incompatibilities {
4475 let incompat = &state.incompatibility_store[*index];
4476 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4477 continue;
4478 };
4479 if current != *parent || !ancestors.contains(child) {
4480 continue;
4481 }
4482
4483 let mut next_environment = state.package_store[*child].marker();
4484 next_environment = next_environment.and(current_environment);
4485
4486 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4487 let mut combined = *entry;
4488 combined = combined.or(next_environment);
4489 if combined != *entry {
4490 *entry = combined;
4491 queue.push_back(*child);
4492 }
4493 }
4494 }
4495
4496 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4497}
4498
4499#[derive(Debug, Default, Clone)]
4500struct ConflictTracker {
4501 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4503 prioritize: Vec<Id<PubGrubPackage>>,
4507 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4509 deprioritize: Vec<Id<PubGrubPackage>>,
4513}
4514
4515#[cfg(test)]
4516mod tests {
4517 use super::*;
4518
4519 fn versions(versions: &[&str]) -> Vec<Version> {
4520 versions
4521 .iter()
4522 .map(|version| version.parse().expect("valid version"))
4523 .collect()
4524 }
4525
4526 #[test]
4527 fn widens_a_version_to_its_gap() {
4528 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4529 let version: Version = "2.0".parse().expect("valid version");
4530
4531 assert_eq!(
4533 widen_to_gap(&version, Some(&known_versions)).to_string(),
4534 ">1.0, <3.0"
4535 );
4536
4537 let version: Version = "1.0".parse().expect("valid version");
4539 assert_eq!(
4540 widen_to_gap(&version, Some(&known_versions)).to_string(),
4541 "<2.0"
4542 );
4543 let version: Version = "3.0".parse().expect("valid version");
4544 assert_eq!(
4545 widen_to_gap(&version, Some(&known_versions)).to_string(),
4546 ">2.0"
4547 );
4548 }
4549
4550 #[test]
4551 fn widens_a_version_without_known_versions_to_itself() {
4552 let version: Version = "2.0".parse().expect("valid version");
4553
4554 assert_eq!(
4556 widen_to_gap(&version, None),
4557 Range::singleton(version.clone())
4558 );
4559
4560 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4562 }
4563}