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.index,
863 &self.git,
864 self.python_requirement.target().clone(),
865 &self.conflicts,
866 self.selector.resolution_strategy(),
867 self.options.clone(),
868 )
869 }
870
871 fn forks_to_fork_states<'a>(
873 &'a self,
874 current_state: ForkState,
875 version: &'a Version,
876 forks: Vec<Fork>,
877 request_sink: &'a Sender<Request>,
878 diverging_packages: &'a BTreeSet<PackageName>,
879 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
880 debug!(
881 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
882 current_state.pubgrub.package_store[current_state.next],
883 version,
884 diverging_packages
885 .iter()
886 .map(ToString::to_string)
887 .join(", "),
888 forks.len(),
889 if forks.len() == 1 { "" } else { "s" }
890 );
891 assert!(forks.len() >= 2);
892 let package = current_state.next;
898 let mut cur_state = Some(current_state);
899 let forks_len = forks.len();
900 forks
901 .into_iter()
902 .enumerate()
903 .map(move |(i, fork)| {
904 let is_last = i == forks_len - 1;
905 let forked_state = cur_state.take().unwrap();
906 if !is_last {
907 cur_state = Some(forked_state.clone());
908 }
909
910 let env = fork.env.clone();
911 (fork, forked_state.with_env(env))
912 })
913 .map(move |(fork, mut forked_state)| {
914 forked_state
916 .visit_package_version_dependencies(
917 package,
918 version,
919 &self.urls,
920 &self.indexes,
921 &fork.dependencies,
922 &self.git,
923 &self.workspace_members,
924 self.selector.resolution_strategy(),
925 )
926 .map_err(|err| {
927 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
928 })?;
929
930 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
932 .map_err(|err| {
933 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
934 })?;
935
936 forked_state.add_package_version_dependencies(
938 package,
939 version,
940 fork.dependencies,
941 &self.index,
942 &self.installed_packages,
943 );
944
945 Ok(forked_state)
946 })
947 }
948
949 #[expect(clippy::unused_self)]
951 fn version_forks_to_fork_states(
952 &self,
953 current_state: ForkState,
954 forks: Vec<VersionFork>,
955 ) -> impl Iterator<Item = ForkState> + '_ {
956 let mut cur_state = Some(current_state);
962 let forks_len = forks.len();
963 forks.into_iter().enumerate().map(move |(i, fork)| {
964 let is_last = i == forks_len - 1;
965 let mut forked_state = cur_state.take().unwrap();
966 if !is_last {
967 cur_state = Some(forked_state.clone());
968 }
969 forked_state.initial_id = Some(fork.id);
970 forked_state.initial_version = fork.version;
971 forked_state.with_env(fork.env)
972 })
973 }
974
975 fn visit_dependencies(
977 &self,
978 dependencies: &[PubGrubDependency],
979 state: &ForkState,
980 request_sink: &Sender<Request>,
981 ) -> Result<(), ResolveError> {
982 for dependency in dependencies {
983 let PubGrubDependency {
984 package,
985 version: _,
986 parent: _,
987 source: _,
988 } = dependency;
989 let url = package.name().and_then(|name| state.fork_urls.get(name));
990 let index = package.name().and_then(|name| state.fork_indexes.get(name));
991 self.visit_package(package, url, index, request_sink)?;
992 }
993 Ok(())
994 }
995
996 fn visit_package(
999 &self,
1000 package: &PubGrubPackage,
1001 url: Option<&VerbatimParsedUrl>,
1002 index: Option<&IndexMetadata>,
1003 request_sink: &Sender<Request>,
1004 ) -> Result<(), ResolveError> {
1005 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1007 return Ok(());
1008 }
1009
1010 self.request_package(package, url, index, request_sink)
1011 }
1012
1013 fn request_package(
1014 &self,
1015 package: &PubGrubPackage,
1016 url: Option<&VerbatimParsedUrl>,
1017 index: Option<&IndexMetadata>,
1018 request_sink: &Sender<Request>,
1019 ) -> Result<(), ResolveError> {
1020 let Some(name) = package.name_no_root() else {
1022 return Ok(());
1023 };
1024
1025 if let Some(url) = url {
1026 if !self.hasher.allows_url(&url.verbatim) {
1028 return Err(ResolveError::UnhashedPackage(name.clone()));
1029 }
1030
1031 let dist = Dist::from_url(name.clone(), url.clone())?;
1033 if self.index.distributions().register(dist.distribution_id()) {
1034 request_sink.blocking_send(Request::Dist(dist))?;
1035 }
1036 } else if let Some(index) = index {
1037 if self
1039 .index
1040 .explicit()
1041 .register((name.clone(), index.url().clone()))
1042 {
1043 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1044 }
1045 } else {
1046 if self.index.implicit().register(name.clone()) {
1048 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1049 }
1050 }
1051 Ok(())
1052 }
1053
1054 fn pre_visit<'data>(
1057 packages: impl Iterator<
1058 Item = (
1059 Id<PubGrubPackage>,
1060 &'data PubGrubPackage,
1061 &'data Range<Version>,
1062 ),
1063 >,
1064 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1065 urls: &Urls,
1066 indexes: &Indexes,
1067 python_requirement: &PythonRequirement,
1068 request_sink: &Sender<Request>,
1069 ) -> Result<(), ResolveError> {
1070 for (id, package, range) in packages {
1073 let PubGrubPackageInner::Package {
1074 name,
1075 extra: None,
1076 group: None,
1077 marker: MarkerTree::TRUE,
1078 } = &**package
1079 else {
1080 continue;
1081 };
1082 if urls.any_url(name) {
1085 continue;
1086 }
1087 if indexes.contains_key(name) {
1089 continue;
1090 }
1091 if pre_visited.get(&id) == Some(range) {
1094 continue;
1095 }
1096 pre_visited.insert(id, range.clone());
1097 request_sink.blocking_send(Request::Prefetch(
1098 name.clone(),
1099 range.clone(),
1100 python_requirement.clone(),
1101 ))?;
1102 }
1103 Ok(())
1104 }
1105
1106 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1114 fn choose_version(
1115 &self,
1116 package: &PubGrubPackage,
1117 id: Id<PubGrubPackage>,
1118 index: Option<&IndexUrl>,
1119 range: &Range<Version>,
1120 pins: &mut FilePins,
1121 preferences: &Preferences,
1122 fork_urls: &ForkUrls,
1123 env: &ResolverEnvironment,
1124 python_requirement: &PythonRequirement,
1125 pubgrub: &State<UvDependencyProvider>,
1126 visited: &mut FxHashSet<PackageName>,
1127 request_sink: &Sender<Request>,
1128 ) -> Result<Option<ResolverVersion>, ResolveError> {
1129 match &**package {
1130 PubGrubPackageInner::Root(_) => {
1131 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1132 }
1133
1134 PubGrubPackageInner::Python(_) => {
1135 Ok(None)
1138 }
1139
1140 PubGrubPackageInner::System(_) => {
1141 let Some(version) = range.as_singleton() else {
1144 return Ok(None);
1145 };
1146 Ok(Some(ResolverVersion::Unforked(version.clone())))
1147 }
1148
1149 PubGrubPackageInner::Marker { name, .. }
1150 | PubGrubPackageInner::Extra { name, .. }
1151 | PubGrubPackageInner::Group { name, .. }
1152 | PubGrubPackageInner::Package { name, .. } => {
1153 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1154 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1155 } else {
1156 self.choose_version_registry(
1157 package,
1158 id,
1159 name,
1160 index,
1161 range,
1162 preferences,
1163 env,
1164 python_requirement,
1165 pubgrub,
1166 pins,
1167 visited,
1168 request_sink,
1169 )
1170 }
1171 }
1172 }
1173 }
1174
1175 fn choose_version_url(
1178 &self,
1179 id: Id<PubGrubPackage>,
1180 name: &PackageName,
1181 range: &Range<Version>,
1182 url: &VerbatimParsedUrl,
1183 env: &ResolverEnvironment,
1184 python_requirement: &PythonRequirement,
1185 pubgrub: &State<UvDependencyProvider>,
1186 ) -> Result<Option<ResolverVersion>, ResolveError> {
1187 debug!(
1188 "Searching for a compatible version of {name} @ {} ({range})",
1189 url.verbatim
1190 );
1191
1192 let dist = Dist::from_url(name.clone(), url.clone())?;
1193 let distribution_id = dist.distribution_id();
1194 let response = self
1195 .index
1196 .distributions()
1197 .wait_blocking(&distribution_id)
1198 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1199
1200 let metadata = match &*response {
1202 MetadataResponse::Found(archive) => &archive.metadata,
1203 MetadataResponse::Unavailable(reason) => {
1204 self.unavailable_packages
1205 .pin()
1206 .insert(name.clone(), reason.into());
1207 return Ok(None);
1208 }
1209 MetadataResponse::Error(dist, err) => {
1212 return Err(ResolveError::Dist(
1213 DistErrorKind::from_requested_dist(dist, &**err),
1214 dist.clone(),
1215 DerivationChain::default(),
1216 err.clone(),
1217 ));
1218 }
1219 };
1220
1221 let version = &metadata.version;
1222
1223 if !range.contains(version) {
1225 return Ok(None);
1226 }
1227
1228 if let Dist::Built(dist) = &dist {
1231 let filename = match &dist {
1232 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1233 BuiltDist::DirectUrl(dist) => &dist.filename,
1234 BuiltDist::GitPath(dist) => &dist.filename,
1235 BuiltDist::Path(dist) => &dist.filename,
1236 };
1237
1238 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1241 {
1242 let wheel_marker = implied_markers(filename);
1243 for environment_marker in self.options.artifact_environments.iter().copied() {
1246 if env.included_by_marker(environment_marker)
1248 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1249 {
1250 if wheel_marker.is_disjoint(environment_marker) {
1252 return Ok(Some(ResolverVersion::Unavailable(
1253 version.clone(),
1254 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1255 IncompatibleWheel::MissingPlatform(environment_marker),
1256 )),
1257 )));
1258 }
1259 }
1260 }
1261 }
1262
1263 if !python_requirement.target().matches_wheel_tag(filename) {
1265 return Ok(Some(ResolverVersion::Unavailable(
1266 filename.version.clone(),
1267 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1268 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1269 )),
1270 )));
1271 }
1272 }
1273
1274 if let Some(requires_python) = metadata.requires_python.as_ref() {
1276 if !python_requirement.target().is_contained_by(requires_python) {
1277 let kind = if python_requirement.installed() == python_requirement.target() {
1278 PythonRequirementKind::Installed
1279 } else {
1280 PythonRequirementKind::Target
1281 };
1282 return Ok(Some(ResolverVersion::Unavailable(
1283 version.clone(),
1284 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1285 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1286 )),
1287 )));
1288 }
1289 }
1290
1291 Ok(Some(ResolverVersion::Unforked(version.clone())))
1292 }
1293
1294 fn choose_version_registry(
1297 &self,
1298 package: &PubGrubPackage,
1299 id: Id<PubGrubPackage>,
1300 name: &PackageName,
1301 index: Option<&IndexUrl>,
1302 range: &Range<Version>,
1303 preferences: &Preferences,
1304 env: &ResolverEnvironment,
1305 python_requirement: &PythonRequirement,
1306 pubgrub: &State<UvDependencyProvider>,
1307 pins: &mut FilePins,
1308 visited: &mut FxHashSet<PackageName>,
1309 request_sink: &Sender<Request>,
1310 ) -> Result<Option<ResolverVersion>, ResolveError> {
1311 let versions_response = if let Some(index) = index {
1313 self.index
1314 .explicit()
1315 .wait_blocking(&(name.clone(), index.clone()))
1316 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1317 } else {
1318 self.index
1319 .implicit()
1320 .wait_blocking(name)
1321 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1322 };
1323 visited.insert(name.clone());
1324
1325 let version_maps = match *versions_response {
1326 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1327 VersionsResponse::NoIndex => {
1328 self.unavailable_packages
1329 .pin()
1330 .insert(name.clone(), UnavailablePackage::NoIndex);
1331 &[]
1332 }
1333 VersionsResponse::Offline => {
1334 self.unavailable_packages
1335 .pin()
1336 .insert(name.clone(), UnavailablePackage::Offline);
1337 &[]
1338 }
1339 VersionsResponse::NotFound => {
1340 self.unavailable_packages
1341 .pin()
1342 .insert(name.clone(), UnavailablePackage::NotFound);
1343 &[]
1344 }
1345 };
1346
1347 debug!("Searching for a compatible version of {package} ({range})");
1348
1349 let Some(candidate) = self.selector.select(
1351 name,
1352 range,
1353 version_maps,
1354 preferences,
1355 &self.installed_packages,
1356 &self.exclusions,
1357 index,
1358 env,
1359 self.tags.as_ref(),
1360 ) else {
1361 return Ok(None);
1363 };
1364
1365 let dist = match candidate.dist() {
1366 CandidateDist::Compatible(dist) => dist,
1367 CandidateDist::Incompatible {
1368 incompatible_dist: incompatibility,
1369 prioritized_dist: _,
1370 } => {
1371 return Ok(Some(ResolverVersion::Unavailable(
1373 candidate.version().clone(),
1374 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1377 )));
1378 }
1379 };
1380
1381 if let Some((requires_python, incompatibility)) =
1383 Self::check_requires_python(dist, python_requirement)
1384 {
1385 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1386 if env.marker_environment().is_none() {
1387 let forks = fork_version_by_python_requirement(
1388 requires_python,
1389 python_requirement,
1390 env,
1391 );
1392 if !forks.is_empty() {
1393 debug!(
1394 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1395 python_requirement.target(),
1396 requires_python,
1397 name,
1398 candidate.version(),
1399 forks
1400 .iter()
1401 .map(ToString::to_string)
1402 .collect::<Vec<_>>()
1403 .join(", ")
1404 );
1405 let forks = forks
1406 .into_iter()
1407 .map(|env| VersionFork {
1408 env,
1409 id,
1410 version: None,
1411 })
1412 .collect();
1413 return Ok(Some(ResolverVersion::Forked(forks)));
1414 }
1415 }
1416 }
1417
1418 return Ok(Some(ResolverVersion::Unavailable(
1419 candidate.version().clone(),
1420 UnavailableVersion::IncompatibleDist(incompatibility),
1421 )));
1422 }
1423
1424 if let Some(forked) = self.fork_version_registry(
1426 &candidate,
1427 dist,
1428 version_maps,
1429 package,
1430 id,
1431 name,
1432 index,
1433 range,
1434 preferences,
1435 env,
1436 pubgrub,
1437 pins,
1438 request_sink,
1439 )? {
1440 return Ok(Some(forked));
1441 }
1442
1443 let filename = match dist.for_installation() {
1444 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1445 .filename()
1446 .unwrap_or(Cow::Borrowed("unknown filename")),
1447 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1448 .filename()
1449 .unwrap_or(Cow::Borrowed("unknown filename")),
1450 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1451 };
1452
1453 debug!(
1454 "Selecting: {}=={} [{}] ({})",
1455 name,
1456 candidate.version(),
1457 candidate.choice_kind(),
1458 filename,
1459 );
1460 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1461
1462 let version = candidate.version().clone();
1463 Ok(Some(ResolverVersion::Unforked(version)))
1464 }
1465
1466 fn fork_version_registry(
1479 &self,
1480 candidate: &Candidate,
1481 dist: &CompatibleDist,
1482 version_maps: &[VersionMap],
1483 package: &PubGrubPackage,
1484 id: Id<PubGrubPackage>,
1485 name: &PackageName,
1486 index: Option<&IndexUrl>,
1487 range: &Range<Version>,
1488 preferences: &Preferences,
1489 env: &ResolverEnvironment,
1490 pubgrub: &State<UvDependencyProvider>,
1491 pins: &mut FilePins,
1492 request_sink: &Sender<Request>,
1493 ) -> Result<Option<ResolverVersion>, ResolveError> {
1494 if env.marker_environment().is_some() {
1496 return Ok(None);
1497 }
1498
1499 if dist.implied_markers().is_true() {
1502 return Ok(None);
1503 }
1504
1505 for marker in self.options.artifact_environments.iter().copied() {
1508 if env.included_by_marker(marker) {
1510 if dist.implied_markers().is_disjoint(marker)
1512 && !find_environments(id, pubgrub).is_disjoint(marker)
1513 {
1514 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1516 return Ok(Some(ResolverVersion::Unavailable(
1517 candidate.version().clone(),
1518 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1519 IncompatibleWheel::MissingPlatform(marker),
1520 )),
1521 )));
1522 };
1523
1524 debug!(
1525 "Forking on required platform `{}` for {}=={} ({})",
1526 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1527 name,
1528 candidate.version(),
1529 [&left, &right]
1530 .iter()
1531 .map(ToString::to_string)
1532 .collect::<Vec<_>>()
1533 .join(", ")
1534 );
1535 let forks = vec![
1536 VersionFork {
1537 env: left,
1538 id,
1539 version: None,
1540 },
1541 VersionFork {
1542 env: right,
1543 id,
1544 version: None,
1545 },
1546 ];
1547 return Ok(Some(ResolverVersion::Forked(forks)));
1548 }
1549 }
1550 }
1551
1552 if !candidate.version().is_local() {
1554 return Ok(None);
1555 }
1556
1557 debug!(
1558 "Looking at local version: {}=={}",
1559 name,
1560 candidate.version()
1561 );
1562
1563 let range = range.clone().intersection(&Range::singleton(
1565 candidate.version().clone().without_local(),
1566 ));
1567
1568 let Some(base_candidate) = self.selector.select(
1569 name,
1570 &range,
1571 version_maps,
1572 preferences,
1573 &self.installed_packages,
1574 &self.exclusions,
1575 index,
1576 env,
1577 self.tags.as_ref(),
1578 ) else {
1579 return Ok(None);
1580 };
1581 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1582 return Ok(None);
1583 };
1584
1585 let mut remainder = {
1587 let mut remainder = base_dist.implied_markers();
1588 remainder = remainder.and(dist.implied_markers().negate());
1589 remainder
1590 };
1591 if remainder.is_false() {
1592 return Ok(None);
1593 }
1594
1595 if !env.included_by_marker(remainder) {
1599 return Ok(None);
1600 }
1601
1602 if !env.included_by_marker(dist.implied_markers()) {
1605 let filename = match dist.for_installation() {
1606 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1607 .filename()
1608 .unwrap_or(Cow::Borrowed("unknown filename")),
1609 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1610 .filename()
1611 .unwrap_or(Cow::Borrowed("unknown filename")),
1612 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1613 };
1614
1615 debug!(
1616 "Preferring non-local candidate: {}=={} [{}] ({})",
1617 name,
1618 base_candidate.version(),
1619 base_candidate.choice_kind(),
1620 filename,
1621 );
1622 self.visit_candidate(
1623 &base_candidate,
1624 base_dist,
1625 package,
1626 name,
1627 pins,
1628 request_sink,
1629 )?;
1630
1631 return Ok(Some(ResolverVersion::Unforked(
1632 base_candidate.version().clone(),
1633 )));
1634 }
1635
1636 for value in [
1645 arcstr::literal!("darwin"),
1646 arcstr::literal!("linux"),
1647 arcstr::literal!("win32"),
1648 ] {
1649 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1650 key: MarkerValueString::SysPlatform,
1651 operator: MarkerOperator::Equal,
1652 value,
1653 });
1654 if dist.implied_markers().is_disjoint(sys_platform)
1655 && !remainder.is_disjoint(sys_platform)
1656 {
1657 remainder = remainder.or(sys_platform);
1658 }
1659 }
1660
1661 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1663 return Ok(None);
1664 };
1665
1666 debug!(
1667 "Forking platform for {}=={} ({})",
1668 name,
1669 candidate.version(),
1670 [&base_env, &local_env]
1671 .iter()
1672 .map(ToString::to_string)
1673 .collect::<Vec<_>>()
1674 .join(", ")
1675 );
1676 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1677 self.visit_candidate(
1678 &base_candidate,
1679 base_dist,
1680 package,
1681 name,
1682 pins,
1683 request_sink,
1684 )?;
1685
1686 let forks = vec![
1687 VersionFork {
1688 env: base_env.clone(),
1689 id,
1690 version: Some(base_candidate.version().clone()),
1691 },
1692 VersionFork {
1693 env: local_env.clone(),
1694 id,
1695 version: Some(candidate.version().clone()),
1696 },
1697 ];
1698 Ok(Some(ResolverVersion::Forked(forks)))
1699 }
1700
1701 fn visit_candidate(
1703 &self,
1704 candidate: &Candidate,
1705 dist: &CompatibleDist,
1706 package: &PubGrubPackage,
1707 name: &PackageName,
1708 pins: &mut FilePins,
1709 request_sink: &Sender<Request>,
1710 ) -> Result<(), ResolveError> {
1711 pins.insert(candidate, dist);
1714
1715 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1717 if self.dependency_mode.is_transitive() {
1718 let dist = dist.for_resolution();
1719 if self.index.distributions().register(dist.distribution_id()) {
1720 if name != dist.name() {
1721 return Err(ResolveError::MismatchedPackageName {
1722 request: "distribution",
1723 expected: name.clone(),
1724 actual: dist.name().clone(),
1725 });
1726 }
1727 if !self
1729 .hasher
1730 .allows_package(candidate.name(), candidate.version())
1731 {
1732 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1733 }
1734
1735 let request = Request::from(dist);
1736 request_sink.blocking_send(request)?;
1737 }
1738 }
1739 }
1740
1741 Ok(())
1742 }
1743
1744 fn check_requires_python<'dist>(
1747 dist: &'dist CompatibleDist,
1748 python_requirement: &PythonRequirement,
1749 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1750 let requires_python = dist.requires_python()?;
1751 if python_requirement.target().is_contained_by(requires_python) {
1752 None
1753 } else {
1754 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1755 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1756 requires_python.clone(),
1757 if python_requirement.installed() == python_requirement.target() {
1758 PythonRequirementKind::Installed
1759 } else {
1760 PythonRequirementKind::Target
1761 },
1762 ))
1763 } else {
1764 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1765 requires_python.clone(),
1766 if python_requirement.installed() == python_requirement.target() {
1767 PythonRequirementKind::Installed
1768 } else {
1769 PythonRequirementKind::Target
1770 },
1771 ))
1772 };
1773 Some((requires_python, incompatibility))
1774 }
1775 }
1776
1777 fn get_dependencies_forking(
1779 &self,
1780 id: Id<PubGrubPackage>,
1781 package: &PubGrubPackage,
1782 version: &Version,
1783 pins: &FilePins,
1784 fork_urls: &ForkUrls,
1785 env: &ResolverEnvironment,
1786 python_requirement: &PythonRequirement,
1787 pubgrub: &State<UvDependencyProvider>,
1788 ) -> Result<ForkedDependencies, ResolveError> {
1789 let dependencies = self.get_dependencies(
1790 id,
1791 package,
1792 version,
1793 pins,
1794 fork_urls,
1795 env,
1796 python_requirement,
1797 pubgrub,
1798 )?;
1799 if env.marker_environment().is_some() {
1800 Ok(ForkedDependencies::from_dependencies_platform_specific(
1801 dependencies,
1802 ))
1803 } else {
1804 Ok(ForkedDependencies::from_dependencies_universal(
1805 dependencies,
1806 env,
1807 python_requirement,
1808 &self.conflicts,
1809 ))
1810 }
1811 }
1812
1813 #[instrument(skip_all, fields(%package, %version))]
1815 fn get_dependencies(
1816 &self,
1817 id: Id<PubGrubPackage>,
1818 package: &PubGrubPackage,
1819 version: &Version,
1820 pins: &FilePins,
1821 fork_urls: &ForkUrls,
1822 env: &ResolverEnvironment,
1823 python_requirement: &PythonRequirement,
1824 pubgrub: &State<UvDependencyProvider>,
1825 ) -> Result<Dependencies, ResolveError> {
1826 let dependencies = match &**package {
1827 PubGrubPackageInner::Root(_) => {
1828 let no_dev_deps = BTreeMap::default();
1829 let requirements = self.flatten_requirements(
1830 &self.requirements,
1831 &no_dev_deps,
1832 None,
1833 None,
1834 None,
1835 None,
1836 env,
1837 python_requirement,
1838 );
1839
1840 PubGrubDependency::from_requirements(
1841 &self.conflicts,
1842 requirements,
1843 None,
1844 Some(package),
1845 )
1846 }
1847
1848 PubGrubPackageInner::Package {
1849 name,
1850 extra,
1851 group,
1852 marker: _,
1853 } => {
1854 if self.dependency_mode.is_direct() {
1856 return Ok(Dependencies::Unforkable(Vec::default()));
1857 }
1858
1859 let owned_id;
1861 let distribution_id = if let Some((_, metadata_id)) =
1862 pins.dist_and_id(name, version)
1863 {
1864 metadata_id
1865 } else if let Some(url) = fork_urls.get(name) {
1866 let dist = Dist::from_url(name.clone(), url.clone())?;
1867 owned_id = dist.distribution_id();
1868 &owned_id
1869 } else {
1870 debug_assert!(
1871 false,
1872 "Dependencies were requested for a package without a pinned distribution"
1873 );
1874 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1875 };
1876
1877 if self.dependency_mode.is_transitive()
1879 && self.unavailable_packages.pin().contains_key(name)
1880 && self.installed_packages.get_packages(name).is_empty()
1881 {
1882 debug_assert!(
1883 false,
1884 "Dependencies were requested for a package that is not available"
1885 );
1886 return Err(ResolveError::PackageUnavailable(name.clone()));
1887 }
1888
1889 let response = self
1891 .index
1892 .distributions()
1893 .wait_blocking(distribution_id)
1894 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1895
1896 let metadata = match &*response {
1897 MetadataResponse::Found(archive) => &archive.metadata,
1898 MetadataResponse::Unavailable(reason) => {
1899 let unavailable_version = UnavailableVersion::from(reason);
1900 let message = unavailable_version.singular_message();
1901 if let Some(err) = reason.source() {
1902 warn!("{name} {message}: {err}");
1904 } else {
1905 warn!("{name} {message}");
1906 }
1907 let incomplete_packages = self.incomplete_packages.pin();
1908 let versions = incomplete_packages.get_or_insert(
1909 name.clone(),
1910 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1911 );
1912 versions.pin().insert(version.clone(), reason.clone());
1913 return Ok(Dependencies::Unavailable(unavailable_version));
1914 }
1915 MetadataResponse::Error(dist, err) => {
1916 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1917 .unwrap_or_default();
1918 return Err(ResolveError::Dist(
1919 DistErrorKind::from_requested_dist(dist, &**err),
1920 dist.clone(),
1921 chain,
1922 err.clone(),
1923 ));
1924 }
1925 };
1926
1927 if let Some(requires_python) = &metadata.requires_python {
1930 if !python_requirement.target().is_contained_by(requires_python) {
1931 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1932 }
1933 }
1934
1935 let system_dependencies = self
1937 .options
1938 .torch_backend
1939 .as_ref()
1940 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1941 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1942 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1943 .map(IndexUrl::url)
1944 .and_then(SystemDependency::from_index)
1945 .into_iter()
1946 .inspect(|system_dependency| {
1947 debug!(
1948 "Adding system dependency `{}` for `{package}@{version}`",
1949 system_dependency
1950 );
1951 })
1952 .map(PubGrubDependency::from);
1953
1954 let requirements = self.flatten_requirements(
1955 &metadata.requires_dist,
1956 &metadata.dependency_groups,
1957 extra.as_ref(),
1958 group.as_ref(),
1959 Some(name),
1960 Some(version),
1961 env,
1962 python_requirement,
1963 );
1964
1965 PubGrubDependency::from_requirements(
1966 &self.conflicts,
1967 requirements,
1968 group.as_ref(),
1969 Some(package),
1970 )
1971 .map(|mut dependencies| {
1972 dependencies.extend(system_dependencies);
1973 dependencies
1974 })
1975 }
1976
1977 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1978
1979 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1980
1981 PubGrubPackageInner::Marker { name, marker } => {
1983 return Ok(Dependencies::Unforkable(
1984 [MarkerTree::TRUE, *marker]
1985 .into_iter()
1986 .map(move |marker| PubGrubDependency {
1987 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1988 name: name.clone(),
1989 extra: None,
1990 group: None,
1991 marker,
1992 }),
1993 version: Range::singleton(version.clone()),
1994 parent: None,
1995 source: DependencySource::Unspecified,
1996 })
1997 .collect(),
1998 ));
1999 }
2000
2001 PubGrubPackageInner::Extra {
2003 name,
2004 extra,
2005 marker,
2006 } => {
2007 return Ok(Dependencies::Unforkable(
2008 [MarkerTree::TRUE, *marker]
2009 .into_iter()
2010 .dedup()
2011 .flat_map(move |marker| {
2012 [None, Some(extra)]
2013 .into_iter()
2014 .map(move |extra| PubGrubDependency {
2015 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2016 name: name.clone(),
2017 extra: extra.cloned(),
2018 group: None,
2019 marker,
2020 }),
2021 version: Range::singleton(version.clone()),
2022 parent: None,
2023 source: DependencySource::Unspecified,
2024 })
2025 })
2026 .collect(),
2027 ));
2028 }
2029
2030 PubGrubPackageInner::Group {
2032 name,
2033 group,
2034 marker,
2035 } => {
2036 return Ok(Dependencies::Unforkable(
2037 [MarkerTree::TRUE, *marker]
2038 .into_iter()
2039 .dedup()
2040 .map(|marker| PubGrubDependency {
2041 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2042 name: name.clone(),
2043 extra: None,
2044 group: Some(group.clone()),
2045 marker,
2046 }),
2047 version: Range::singleton(version.clone()),
2048 parent: None,
2049 source: DependencySource::Unspecified,
2050 })
2051 .collect(),
2052 ));
2053 }
2054 };
2055 Ok(match dependencies {
2056 Ok(dependencies) => Dependencies::Available(dependencies),
2057 Err(requirement) => {
2058 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2059 }
2060 })
2061 }
2062
2063 fn flatten_requirements<'a>(
2067 &'a self,
2068 dependencies: &'a [Requirement],
2069 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2070 extra: Option<&'a ExtraName>,
2071 dev: Option<&'a GroupName>,
2072 name: Option<&'a PackageName>,
2073 version: Option<&'a Version>,
2074 env: &'a ResolverEnvironment,
2075 python_requirement: &'a PythonRequirement,
2076 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2077 let python_marker = python_requirement.to_marker_tree();
2078
2079 if let Some(dev) = dev {
2080 Either::Left(Either::Left(self.requirements_for_extra(
2083 dev_dependencies.get(dev).into_iter().flatten(),
2084 extra,
2085 None,
2086 name.zip(version),
2087 env,
2088 python_marker,
2089 python_requirement,
2090 )))
2091 } else if !dependencies
2092 .iter()
2093 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2094 {
2095 Either::Left(Either::Right(self.requirements_for_extra(
2097 dependencies.iter(),
2098 extra,
2099 name.zip(version),
2100 name.zip(version),
2101 env,
2102 python_marker,
2103 python_requirement,
2104 )))
2105 } else {
2106 let mut requirements = self
2107 .requirements_for_extra(
2108 dependencies.iter(),
2109 extra,
2110 name.zip(version),
2111 name.zip(version),
2112 env,
2113 python_marker,
2114 python_requirement,
2115 )
2116 .collect::<Vec<_>>();
2117
2118 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2121 let mut queue: VecDeque<_> = requirements
2122 .iter()
2123 .filter(|req| name == Some(&req.name))
2124 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2125 .collect();
2126 while let Some((extra, marker)) = queue.pop_front() {
2127 if !seen.insert((extra.clone(), marker)) {
2128 continue;
2129 }
2130 for requirement in self.requirements_for_extra(
2131 dependencies,
2132 Some(&extra),
2133 name.zip(version),
2134 name.zip(version),
2135 env,
2136 python_marker,
2137 python_requirement,
2138 ) {
2139 let requirement = match requirement {
2140 Cow::Owned(mut requirement) => {
2141 requirement.marker = requirement.marker.and(marker);
2142 requirement
2143 }
2144 Cow::Borrowed(requirement) => {
2145 let mut marker = marker;
2146 marker = marker.and(requirement.marker);
2147 Requirement {
2148 name: requirement.name.clone(),
2149 extras: requirement.extras.clone(),
2150 groups: requirement.groups.clone(),
2151 source: requirement.source.clone(),
2152 origin: requirement.origin.clone(),
2153 marker: marker.simplify_extras(slice::from_ref(&extra)),
2154 }
2155 }
2156 };
2157 let applicable_marker = requirement
2160 .marker
2161 .simplify_extras(slice::from_ref(&extra))
2162 .simplify_not_extras_with(|candidate| candidate != &extra);
2163 if python_marker.is_disjoint(applicable_marker)
2164 || !env.included_by_marker(applicable_marker)
2165 {
2166 continue;
2167 }
2168 if name == Some(&requirement.name) {
2169 queue.extend(
2171 requirement
2172 .extras
2173 .iter()
2174 .cloned()
2175 .map(|extra| (extra, requirement.marker)),
2176 );
2177 }
2178
2179 requirements.push(Cow::Owned(requirement));
2181 }
2182 }
2183
2184 let mut self_constraints = vec![];
2188 for req in &requirements {
2189 if name == Some(&req.name) && !req.extras.is_empty() && !req.source.is_empty() {
2190 self_constraints.push(Requirement {
2191 name: req.name.clone(),
2192 extras: Box::new([]),
2193 groups: req.groups.clone(),
2194 source: req.source.clone(),
2195 origin: req.origin.clone(),
2196 marker: req.marker,
2197 });
2198 }
2199 }
2200
2201 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2203 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2204
2205 Either::Right(requirements.into_iter())
2206 }
2207 }
2208
2209 fn requirements_for_extra<'data, 'parameters>(
2212 &'data self,
2213 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2214 extra: Option<&'parameters ExtraName>,
2215 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2216 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2217 env: &'parameters ResolverEnvironment,
2218 python_marker: MarkerTree,
2219 python_requirement: &'parameters PythonRequirement,
2220 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2221 where
2222 'data: 'parameters,
2223 {
2224 self.overrides
2225 .apply_for_package(override_package, dependencies)
2226 .filter(move |requirement| {
2227 !self
2228 .excludes
2229 .contains_for_package(exclusion_package, &requirement.name)
2230 })
2231 .map(move |mut requirement| {
2232 let marker = match extra {
2241 Some(extra) => requirement
2242 .marker
2243 .simplify_extras(slice::from_ref(extra))
2244 .simplify_not_extras_with(|candidate| candidate != extra)
2245 .and(
2246 requirement
2247 .marker
2248 .simplify_not_extras_with(|_| true)
2249 .negate(),
2250 ),
2251 None => requirement.marker.simplify_not_extras_with(|_| true),
2252 };
2253
2254 if requirement.marker != marker {
2255 requirement.to_mut().marker = marker;
2256 }
2257
2258 requirement
2259 })
2260 .filter(move |requirement| {
2261 Self::is_requirement_applicable(
2262 requirement,
2263 extra,
2264 env,
2265 python_marker,
2266 python_requirement,
2267 )
2268 })
2269 .flat_map(move |requirement| {
2270 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2271 requirement,
2272 extra,
2273 env,
2274 python_marker,
2275 python_requirement,
2276 ))
2277 })
2278 }
2279
2280 fn is_requirement_applicable(
2283 requirement: &Requirement,
2284 extra: Option<&ExtraName>,
2285 env: &ResolverEnvironment,
2286 python_marker: MarkerTree,
2287 python_requirement: &PythonRequirement,
2288 ) -> bool {
2289 match extra {
2291 Some(source_extra) => {
2292 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2293 return false;
2294 }
2295
2296 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2297 {
2298 return false;
2299 }
2300 }
2301 None => {
2302 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2303 return false;
2304 }
2305 }
2306 }
2307
2308 if python_marker.is_disjoint(requirement.marker) {
2311 trace!(
2312 "Skipping {requirement} because of Requires-Python: {requires_python}",
2313 requires_python = python_requirement.target(),
2314 );
2315 return false;
2316 }
2317
2318 if !env.included_by_marker(requirement.marker) {
2321 trace!("Skipping {requirement} because of {env}");
2322 return false;
2323 }
2324
2325 true
2326 }
2327
2328 fn constraints_for_requirement<'data, 'parameters>(
2331 &'data self,
2332 requirement: Cow<'data, Requirement>,
2333 extra: Option<&'parameters ExtraName>,
2334 env: &'parameters ResolverEnvironment,
2335 python_marker: MarkerTree,
2336 python_requirement: &'parameters PythonRequirement,
2337 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2338 where
2339 'data: 'parameters,
2340 {
2341 self.constraints
2342 .get(&requirement.name)
2343 .into_iter()
2344 .flatten()
2345 .filter_map(move |constraint| {
2346 let constraint = if constraint.marker.is_true() {
2349 if requirement.marker.is_true() {
2353 Cow::Borrowed(constraint)
2354 } else {
2355 let mut marker = constraint.marker;
2356 marker = marker.and(requirement.marker);
2357
2358 if marker.is_false() {
2359 trace!(
2360 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2361 constraint.marker.try_to_string().unwrap(),
2362 requirement.marker.try_to_string().unwrap(),
2363 );
2364 return None;
2365 }
2366
2367 Cow::Owned(Requirement {
2368 name: constraint.name.clone(),
2369 extras: constraint.extras.clone(),
2370 groups: constraint.groups.clone(),
2371 source: constraint.source.clone(),
2372 origin: constraint.origin.clone(),
2373 marker,
2374 })
2375 }
2376 } else {
2377 let requires_python = python_requirement.target();
2378
2379 let mut marker = constraint.marker;
2380 marker = marker.and(requirement.marker);
2381
2382 if marker.is_false() {
2383 trace!(
2384 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2385 constraint.marker.try_to_string().unwrap(),
2386 requirement.marker.try_to_string().unwrap(),
2387 );
2388 return None;
2389 }
2390
2391 if python_marker.is_disjoint(marker) {
2395 trace!(
2396 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2397 );
2398 return None;
2399 }
2400
2401 if marker == constraint.marker {
2402 Cow::Borrowed(constraint)
2403 } else {
2404 Cow::Owned(Requirement {
2405 name: constraint.name.clone(),
2406 extras: constraint.extras.clone(),
2407 groups: constraint.groups.clone(),
2408 source: constraint.source.clone(),
2409 origin: constraint.origin.clone(),
2410 marker,
2411 })
2412 }
2413 };
2414
2415 if !env.included_by_marker(constraint.marker) {
2418 trace!("Skipping {constraint} because of {env}");
2419 return None;
2420 }
2421
2422 match extra {
2424 Some(source_extra) => {
2425 if !constraint
2426 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2427 {
2428 return None;
2429 }
2430 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2431 {
2432 return None;
2433 }
2434 }
2435 None => {
2436 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2437 return None;
2438 }
2439 }
2440 }
2441
2442 Some(constraint)
2443 })
2444 }
2445
2446 async fn fetch<Provider: ResolverProvider>(
2448 self: Arc<Self>,
2449 provider: Arc<Provider>,
2450 request_stream: Receiver<Request>,
2451 ) -> Result<(), ResolveError> {
2452 let mut response_stream = ReceiverStream::new(request_stream)
2453 .map(|request| self.process_request(request, &*provider).boxed_local())
2454 .buffer_unordered(usize::MAX);
2458
2459 while let Some(response) = response_stream.next().await {
2460 match response? {
2461 Some(Response::Package(name, index, version_map)) => {
2462 trace!("Received package metadata for: {name}");
2463 if let Some(index) = index {
2464 self.index
2465 .explicit()
2466 .done((name, index), Arc::new(version_map));
2467 } else {
2468 self.index.implicit().done(name, Arc::new(version_map));
2469 }
2470 }
2471 Some(Response::Installed { dist, metadata }) => {
2472 trace!("Received installed distribution metadata for: {dist}");
2473 self.index
2474 .distributions()
2475 .done(dist.distribution_id(), Arc::new(metadata));
2476 }
2477 Some(Response::Dist { dist, metadata }) => {
2478 let dist_kind = match dist {
2479 Dist::Built(_) => "built",
2480 Dist::Source(_) => "source",
2481 };
2482 trace!("Received {dist_kind} distribution metadata for: {dist}");
2483 if let MetadataResponse::Unavailable(reason) = &metadata {
2484 let message = UnavailableVersion::from(reason).singular_message();
2485 if let Some(err) = reason.source() {
2486 warn!("{dist} {message}: {err}");
2488 } else {
2489 warn!("{dist} {message}");
2490 }
2491 }
2492 self.index
2493 .distributions()
2494 .done(dist.distribution_id(), Arc::new(metadata));
2495 }
2496 None => {}
2497 }
2498 }
2499
2500 Ok::<(), ResolveError>(())
2501 }
2502
2503 #[instrument(skip_all, fields(%request))]
2504 async fn process_request<Provider: ResolverProvider>(
2505 &self,
2506 request: Request,
2507 provider: &Provider,
2508 ) -> Result<Option<Response>, ResolveError> {
2509 match request {
2510 Request::Package(package_name, index) => {
2512 let package_versions = provider
2513 .get_package_versions(&package_name, index.as_ref())
2514 .boxed_local()
2515 .await
2516 .map_err(ResolveError::Client)?;
2517
2518 Ok(Some(Response::Package(
2519 package_name,
2520 index.map(IndexMetadata::into_url),
2521 package_versions,
2522 )))
2523 }
2524
2525 Request::Dist(dist) => {
2527 if let Some(version) = dist.version() {
2528 if let Some(index) = dist.index() {
2529 let versions_response = self.index.implicit().get(dist.name());
2531 if let Some(VersionsResponse::Found(version_maps)) =
2532 versions_response.as_deref()
2533 {
2534 for version_map in version_maps {
2535 if version_map.index() == Some(index) {
2536 let Some(metadata) = version_map.get_metadata(version) else {
2537 continue;
2538 };
2539 debug!("Found registry-provided metadata for: {dist}");
2540 return Ok(Some(Response::Dist {
2541 dist,
2542 metadata: MetadataResponse::Found(
2543 ArchiveMetadata::from_metadata23(metadata),
2544 ),
2545 }));
2546 }
2547 }
2548 }
2549
2550 let versions_response = self
2552 .index
2553 .explicit()
2554 .get(&(dist.name().clone(), index.clone()));
2555 if let Some(VersionsResponse::Found(version_maps)) =
2556 versions_response.as_deref()
2557 {
2558 for version_map in version_maps {
2559 let Some(metadata) = version_map.get_metadata(version) else {
2560 continue;
2561 };
2562 debug!("Found registry-provided metadata for: {dist}");
2563 return Ok(Some(Response::Dist {
2564 dist,
2565 metadata: MetadataResponse::Found(
2566 ArchiveMetadata::from_metadata23(metadata),
2567 ),
2568 }));
2569 }
2570 }
2571 }
2572 }
2573
2574 let metadata = provider
2575 .get_or_build_wheel_metadata(&dist)
2576 .boxed_local()
2577 .await?;
2578
2579 if let MetadataResponse::Found(metadata) = &metadata {
2580 if &metadata.metadata.name != dist.name() {
2581 return Err(ResolveError::MismatchedPackageName {
2582 request: "distribution metadata",
2583 expected: dist.name().clone(),
2584 actual: metadata.metadata.name.clone(),
2585 });
2586 }
2587 }
2588
2589 Ok(Some(Response::Dist { dist, metadata }))
2590 }
2591
2592 Request::Installed(dist) => {
2593 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2594
2595 if let MetadataResponse::Found(metadata) = &metadata {
2596 if &metadata.metadata.name != dist.name() {
2597 return Err(ResolveError::MismatchedPackageName {
2598 request: "installed metadata",
2599 expected: dist.name().clone(),
2600 actual: metadata.metadata.name.clone(),
2601 });
2602 }
2603 }
2604
2605 Ok(Some(Response::Installed { dist, metadata }))
2606 }
2607
2608 Request::Prefetch(package_name, range, python_requirement) => {
2610 let versions_response = self
2612 .index
2613 .implicit()
2614 .wait(&package_name)
2615 .await
2616 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2617
2618 let version_map = match *versions_response {
2619 VersionsResponse::Found(ref version_map) => version_map,
2620 VersionsResponse::NoIndex => {
2622 self.unavailable_packages
2623 .pin()
2624 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2625
2626 return Ok(None);
2627 }
2628 VersionsResponse::Offline => {
2629 self.unavailable_packages
2630 .pin()
2631 .insert(package_name.clone(), UnavailablePackage::Offline);
2632
2633 return Ok(None);
2634 }
2635 VersionsResponse::NotFound => {
2636 self.unavailable_packages
2637 .pin()
2638 .insert(package_name.clone(), UnavailablePackage::NotFound);
2639
2640 return Ok(None);
2641 }
2642 };
2643
2644 let env = ResolverEnvironment::universal(vec![]);
2646
2647 let Some(candidate) = self.selector.select(
2650 &package_name,
2651 &range,
2652 version_map,
2653 &self.preferences,
2654 &self.installed_packages,
2655 &self.exclusions,
2656 None,
2657 &env,
2658 self.tags.as_ref(),
2659 ) else {
2660 return Ok(None);
2661 };
2662
2663 let Some(dist) = candidate.compatible() else {
2665 return Ok(None);
2666 };
2667
2668 for version_map in version_map {
2670 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2671 let dist = dist.for_resolution();
2672 if version_map.index() == dist.index() {
2673 debug!("Found registry-provided metadata for: {dist}");
2674
2675 let metadata =
2676 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2677
2678 let dist = dist.to_owned();
2679 if &package_name != dist.name() {
2680 return Err(ResolveError::MismatchedPackageName {
2681 request: "distribution",
2682 expected: package_name,
2683 actual: dist.name().clone(),
2684 });
2685 }
2686
2687 let response = match dist {
2688 ResolvedDist::Installable { dist, .. } => Response::Dist {
2689 dist: (*dist).clone(),
2690 metadata,
2691 },
2692 ResolvedDist::Installed { dist } => Response::Installed {
2693 dist: (*dist).clone(),
2694 metadata,
2695 },
2696 };
2697
2698 return Ok(Some(response));
2699 }
2700 }
2701 }
2702
2703 if dist.wheel().is_none() {
2707 if !self.selector.use_highest_version(&package_name, &env) {
2708 if let Some((lower, _)) = range.iter().next() {
2709 if lower == Bound::Unbounded {
2710 debug!(
2711 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2712 );
2713 return Ok(None);
2714 }
2715 }
2716 }
2717 }
2718
2719 if let Some(requires_python) = dist.requires_python() {
2721 if !python_requirement.target().is_contained_by(requires_python) {
2722 return Ok(None);
2723 }
2724 }
2725
2726 if !self
2728 .hasher
2729 .allows_package(candidate.name(), candidate.version())
2730 {
2731 return Ok(None);
2732 }
2733
2734 let dist = dist.for_resolution();
2736 if self.index.distributions().register(dist.distribution_id()) {
2737 let dist = dist.to_owned();
2738 if &package_name != dist.name() {
2739 return Err(ResolveError::MismatchedPackageName {
2740 request: "distribution",
2741 expected: package_name,
2742 actual: dist.name().clone(),
2743 });
2744 }
2745
2746 let response = match dist {
2747 ResolvedDist::Installable { dist, .. } => {
2748 let metadata = provider
2749 .get_or_build_wheel_metadata(&dist)
2750 .boxed_local()
2751 .await?;
2752
2753 Response::Dist {
2754 dist: (*dist).clone(),
2755 metadata,
2756 }
2757 }
2758 ResolvedDist::Installed { dist } => {
2759 let metadata =
2760 provider.get_installed_metadata(&dist).boxed_local().await?;
2761
2762 Response::Installed {
2763 dist: (*dist).clone(),
2764 metadata,
2765 }
2766 }
2767 };
2768
2769 Ok(Some(response))
2770 } else {
2771 Ok(None)
2772 }
2773 }
2774 }
2775 }
2776
2777 fn convert_no_solution_err(
2778 &self,
2779 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2780 fork_urls: ForkUrls,
2781 fork_indexes: ForkIndexes,
2782 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2783 env: ResolverEnvironment,
2784 current_environment: MarkerEnvironment,
2785 visited: &FxHashSet<PackageName>,
2786 ) -> ResolveError {
2787 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2788 err,
2789 ));
2790 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2791
2792 let mut unavailable_packages = FxHashMap::default();
2793 for package in derivation_tree_packages(&err) {
2794 if let PubGrubPackageInner::Package { name, .. } = &**package {
2795 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2796 unavailable_packages.insert(name.clone(), reason.clone());
2797 }
2798 }
2799 }
2800
2801 let mut incomplete_packages = FxHashMap::default();
2802 let incomplete_packages_cache = self.incomplete_packages.pin();
2803 for package in derivation_tree_packages(&err) {
2804 if let PubGrubPackageInner::Package { name, .. } = &**package
2805 && let Some(versions) = incomplete_packages_cache.get(name)
2806 {
2807 for (version, reason) in &versions.pin() {
2808 incomplete_packages
2809 .entry(name.clone())
2810 .or_insert_with(BTreeMap::default)
2811 .insert(version.clone(), reason.clone());
2812 }
2813 }
2814 }
2815
2816 let mut available_indexes = FxHashMap::default();
2817 let mut included_versions = FxHashMap::default();
2818 let mut available_versions = FxHashMap::default();
2819
2820 let available_version_cutoff: Option<jiff::Timestamp> =
2821 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2822 .ok()
2823 .and_then(|s| s.parse().ok());
2824
2825 for package in derivation_tree_packages(&err) {
2826 let Some(name) = package.name() else { continue };
2827 if !visited.contains(name) {
2828 continue;
2833 }
2834 let versions_response = if let Some(index) = fork_indexes.get(name) {
2835 self.index
2836 .explicit()
2837 .get(&(name.clone(), index.url().clone()))
2838 } else {
2839 self.index.implicit().get(name)
2840 };
2841 if let Some(response) = versions_response {
2842 if let VersionsResponse::Found(ref version_maps) = *response {
2843 for version_map in version_maps {
2845 let package_included_versions = included_versions
2846 .entry(name.clone())
2847 .or_insert_with(BTreeSet::new);
2848 let package_available_versions = available_versions
2849 .entry(name.clone())
2850 .or_insert_with(BTreeSet::new);
2851
2852 for (version, dists) in version_map.iter(&Ranges::full()) {
2853 let excluded_from_included = || {
2858 let Some(included_version_cutoff) =
2859 version_map.included_version_cutoff()
2860 else {
2861 return false;
2862 };
2863 let Some(prioritized_dist) = dists.prioritized_dist() else {
2864 return true;
2865 };
2866 prioritized_dist.files().all(|file| {
2867 file.upload_time_utc_ms.is_none_or(|upload_time| {
2868 upload_time >= included_version_cutoff.as_millisecond()
2869 })
2870 })
2871 };
2872
2873 if !excluded_from_included() {
2874 package_included_versions.insert(version.clone());
2875 }
2876
2877 let excluded_from_available = || {
2883 let Some(ref exclude_newer) = available_version_cutoff else {
2884 return false;
2885 };
2886 let Some(prioritized_dist) = dists.prioritized_dist() else {
2887 return false;
2888 };
2889 prioritized_dist.files().all(|file| {
2890 file.upload_time_utc_ms.is_some_and(|upload_time| {
2891 upload_time >= exclude_newer.as_millisecond()
2892 })
2893 })
2894 };
2895
2896 if !excluded_from_available() {
2897 package_available_versions.insert(version.clone());
2898 }
2899 }
2900 }
2901
2902 available_indexes
2904 .entry(name.clone())
2905 .or_insert(BTreeSet::new())
2906 .extend(
2907 version_maps
2908 .iter()
2909 .filter_map(|version_map| version_map.index().cloned()),
2910 );
2911 }
2912 }
2913 }
2914
2915 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2916 err,
2917 self.index.clone(),
2918 included_versions,
2919 available_versions,
2920 available_indexes,
2921 self.selector.clone(),
2922 self.python_requirement.clone(),
2923 self.locations.clone(),
2924 self.capabilities.clone(),
2925 unavailable_packages,
2926 incomplete_packages,
2927 fork_urls,
2928 fork_indexes,
2929 env,
2930 current_environment,
2931 self.tags.clone(),
2932 self.workspace_members.clone(),
2933 self.options.clone(),
2934 )))
2935 }
2936
2937 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2938 if let Some(reporter) = self.reporter.as_ref() {
2939 match &**package {
2940 PubGrubPackageInner::Root(_) => {}
2941 PubGrubPackageInner::Python(_) => {}
2942 PubGrubPackageInner::System(_) => {}
2943 PubGrubPackageInner::Marker { .. } => {}
2944 PubGrubPackageInner::Extra { .. } => {}
2945 PubGrubPackageInner::Group { .. } => {}
2946 PubGrubPackageInner::Package { name, .. } => {
2947 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2948 }
2949 }
2950 }
2951 }
2952
2953 fn on_complete(&self) {
2954 if let Some(reporter) = self.reporter.as_ref() {
2955 reporter.on_complete();
2956 }
2957 }
2958}
2959
2960#[derive(Clone, Default)]
2963struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2964
2965impl KnownVersions {
2966 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2979 &'a mut self,
2980 index: &InMemoryIndex,
2981 installed_packages: &InstalledPackages,
2982 fork_urls: &ForkUrls,
2983 fork_indexes: &ForkIndexes,
2984 package: &PubGrubPackage,
2985 ) -> Option<&'a [Version]> {
2986 let name = package.name_no_root()?;
2987 if fork_urls.get(name).is_some() {
2989 return None;
2990 }
2991 if !self.0.contains_key(name) {
2992 let response = if let Some(index_metadata) = fork_indexes.get(name) {
2993 index
2994 .explicit()
2995 .get(&(name.clone(), index_metadata.url().clone()))?
2996 } else {
2997 index.implicit().get(name)?
2998 };
2999 let VersionsResponse::Found(ref version_maps) = *response else {
3000 return None;
3001 };
3002 let mut versions: Vec<Version> = version_maps
3003 .iter()
3004 .flat_map(|version_map| version_map.included_versions().cloned())
3005 .chain(
3006 installed_packages
3007 .get_packages(name)
3008 .iter()
3009 .map(|dist| dist.version().clone()),
3010 )
3011 .collect();
3012 versions.sort_unstable();
3013 versions.dedup();
3014 self.0.insert(name.clone(), versions.into());
3015 }
3016 Some(&self.0[name][..])
3017 }
3018}
3019
3020#[derive(Clone)]
3022pub(crate) struct ForkState {
3023 pubgrub: State<UvDependencyProvider>,
3031 initial_id: Option<Id<PubGrubPackage>>,
3034 initial_version: Option<Version>,
3037 next: Id<PubGrubPackage>,
3039 pins: FilePins,
3048 fork_urls: ForkUrls,
3054 fork_indexes: ForkIndexes,
3059 priorities: PubGrubPriorities,
3065 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3068 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3070 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3072 known_versions: KnownVersions,
3076 env: ResolverEnvironment,
3091 python_requirement: PythonRequirement,
3104 conflict_tracker: ConflictTracker,
3105 prefetcher: BatchPrefetcher,
3109}
3110
3111impl ForkState {
3112 fn new(
3113 pubgrub: State<UvDependencyProvider>,
3114 env: ResolverEnvironment,
3115 python_requirement: PythonRequirement,
3116 prefetcher: BatchPrefetcher,
3117 ) -> Self {
3118 Self {
3119 initial_id: None,
3120 initial_version: None,
3121 next: pubgrub.root_package,
3122 pubgrub,
3123 pins: FilePins::default(),
3124 fork_urls: ForkUrls::default(),
3125 fork_indexes: ForkIndexes::default(),
3126 priorities: PubGrubPriorities::default(),
3127 added_dependencies: FxHashMap::default(),
3128 pre_visited: FxHashMap::default(),
3129 selected_versions: FxHashMap::default(),
3130 known_versions: KnownVersions::default(),
3131 env,
3132 python_requirement,
3133 conflict_tracker: ConflictTracker::default(),
3134 prefetcher,
3135 }
3136 }
3137
3138 fn visit_package_version_dependencies(
3141 &mut self,
3142 for_package: Id<PubGrubPackage>,
3143 for_version: &Version,
3144 urls: &Urls,
3145 indexes: &Indexes,
3146 dependencies: &[PubGrubDependency],
3147 git: &GitResolver,
3148 workspace_members: &BTreeSet<PackageName>,
3149 resolution_strategy: &ResolutionStrategy,
3150 ) -> Result<(), ResolveError> {
3151 for dependency in dependencies {
3152 let PubGrubDependency {
3153 package,
3154 version,
3155 parent: _,
3156 source,
3157 } = dependency;
3158
3159 let mut has_url = false;
3160 if let Some(name) = package.name() {
3161 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3166 self.fork_urls.insert(name, url, &self.env)?;
3167 has_url = true;
3168 }
3169
3170 if let Some(index) = source.explicit_index() {
3171 self.fork_indexes.insert(name, index, &self.env)?;
3172 }
3173
3174 for index in indexes.get(name, &self.env) {
3176 self.fork_indexes.insert(name, index, &self.env)?;
3177 }
3178 }
3179
3180 if let Some(name) = self.pubgrub.package_store[for_package]
3181 .name_no_root()
3182 .filter(|name| !workspace_members.contains(name))
3183 {
3184 debug!(
3185 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3186 );
3187 } else {
3188 debug!("Adding direct dependency: {package}{version}");
3190
3191 let missing_lower_bound = version
3193 .bounding_range()
3194 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3195 let strategy_lowest = matches!(
3196 resolution_strategy,
3197 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3198 );
3199
3200 if !has_url && missing_lower_bound && strategy_lowest {
3201 let name = package.name_no_root().unwrap();
3202 let bound_on_other_package = dependencies.iter().any(|other| {
3209 Some(name) == other.package.name()
3210 && !other
3211 .version
3212 .bounding_range()
3213 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3214 });
3215
3216 if !bound_on_other_package {
3217 warn_user_once!(
3218 "The direct dependency `{name}` is unpinned. \
3219 Consider setting a lower bound when using `--resolution lowest` \
3220 or `--resolution lowest-direct` to avoid using outdated versions.",
3221 );
3222 }
3223 }
3224 }
3225
3226 self.priorities.insert(package, version, &self.fork_urls);
3228 if let Some(base_package) = package.base_package() {
3231 self.priorities
3232 .insert(&base_package, version, &self.fork_urls);
3233 }
3234 }
3235
3236 Ok(())
3237 }
3238
3239 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3245 &mut self,
3246 for_package: Id<PubGrubPackage>,
3247 for_version: &Version,
3248 dependencies: Vec<PubGrubDependency>,
3249 index: &InMemoryIndex,
3250 installed_packages: &InstalledPackages,
3251 ) {
3252 for dependency in &dependencies {
3253 let PubGrubDependency {
3254 package,
3255 version,
3256 parent: _,
3257 source: _,
3258 } = dependency;
3259
3260 let Some(base_package) = package.base_package() else {
3261 continue;
3262 };
3263
3264 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3265 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3266 self.pubgrub.add_proxy_package_incompatibility(
3267 proxy_package,
3268 base_package_id,
3269 version.clone(),
3270 );
3271 }
3272
3273 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
3276 let conflict = self.pubgrub.add_package_version_dependencies(
3277 self.next,
3278 for_version.clone(),
3279 versions,
3280 dependencies.into_iter().map(|dependency| {
3281 let PubGrubDependency {
3282 package,
3283 version,
3284 parent: _,
3285 source: _,
3286 } = dependency;
3287 (package, version)
3288 }),
3289 );
3290
3291 if let Some(incompatibility) = conflict {
3294 self.record_conflict(for_package, Some(for_version), incompatibility);
3295 }
3296 }
3297
3298 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
3300 &mut self,
3301 version: &Version,
3302 index: &InMemoryIndex,
3303 installed_packages: &InstalledPackages,
3304 ) -> Range<Version> {
3305 widen_to_gap(
3306 version,
3307 self.known_versions.get_or_update(
3308 index,
3309 installed_packages,
3310 &self.fork_urls,
3311 &self.fork_indexes,
3312 &self.pubgrub.package_store[self.next],
3313 ),
3314 )
3315 }
3316
3317 fn record_conflict(
3318 &mut self,
3319 affected: Id<PubGrubPackage>,
3320 version: Option<&Version>,
3321 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3322 ) {
3323 let mut culprit_is_real = false;
3324 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3325 if incompatible == affected {
3326 continue;
3327 }
3328 if self.pubgrub.package_store[affected].name()
3329 == self.pubgrub.package_store[incompatible].name()
3330 {
3331 continue;
3334 }
3335 culprit_is_real = true;
3336 let culprit_count = self
3337 .conflict_tracker
3338 .culprit
3339 .entry(incompatible)
3340 .or_default();
3341 *culprit_count += 1;
3342 if *culprit_count == CONFLICT_THRESHOLD {
3343 self.conflict_tracker.deprioritize.push(incompatible);
3344 }
3345 }
3346 if culprit_is_real {
3349 if tracing::enabled!(Level::DEBUG) {
3350 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3351 .iter()
3352 .map(|(package, _term)| &self.pubgrub.package_store[package])
3353 .join(", ");
3354 if let Some(version) = version {
3355 debug!(
3356 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3357 self.pubgrub.package_store[affected], version, incompatibility
3358 );
3359 } else {
3360 debug!(
3361 "Recording unit propagation conflict of {} from incompatibility of ({})",
3362 self.pubgrub.package_store[affected], incompatibility
3363 );
3364 }
3365 }
3366
3367 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3368 *affected_count += 1;
3369 if *affected_count == CONFLICT_THRESHOLD {
3370 self.conflict_tracker.prioritize.push(self.next);
3371 }
3372 }
3373 }
3374
3375 fn reprioritize_conflicts(&mut self) {
3379 for package in self.conflict_tracker.prioritize.drain(..) {
3380 let changed = self
3381 .priorities
3382 .mark_conflict_early(&self.pubgrub.package_store[package]);
3383 if changed {
3384 debug!(
3385 "Package {} has too many conflicts (affected), prioritizing",
3386 &self.pubgrub.package_store[package]
3387 );
3388 } else {
3389 debug!(
3390 "Package {} has too many conflicts (affected), already {:?}",
3391 self.pubgrub.package_store[package],
3392 self.priorities.get(&self.pubgrub.package_store[package])
3393 );
3394 }
3395 }
3396
3397 for package in self.conflict_tracker.deprioritize.drain(..) {
3398 let changed = self
3399 .priorities
3400 .mark_conflict_late(&self.pubgrub.package_store[package]);
3401 if changed {
3402 debug!(
3403 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3404 self.pubgrub.package_store[package],
3405 );
3406 let backtrack_level = self.pubgrub.backtrack_package(package);
3407 if let Some(backtrack_level) = backtrack_level {
3408 debug!("Backtracked {backtrack_level} decisions");
3409 } else {
3410 debug!(
3411 "Package {} is not decided, cannot backtrack",
3412 self.pubgrub.package_store[package]
3413 );
3414 }
3415 } else {
3416 debug!(
3417 "Package {} has too many conflicts (culprit), already {:?}",
3418 self.pubgrub.package_store[package],
3419 self.priorities.get(&self.pubgrub.package_store[package])
3420 );
3421 }
3422 }
3423 }
3424
3425 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3433 &mut self,
3434 version: Version,
3435 reason: UnavailableVersion,
3436 index: &InMemoryIndex,
3437 installed_packages: &InstalledPackages,
3438 ) {
3439 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3440
3441 if let UnavailableVersion::IncompatibleDist(
3445 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3446 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3447 ) = reason
3448 {
3449 let package = &self.next;
3450 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3451 PubGrubPackageInner::Python(match kind {
3452 PythonRequirementKind::Installed => PubGrubPython::Installed,
3453 PythonRequirementKind::Target => PubGrubPython::Target,
3454 }),
3455 ));
3456 self.pubgrub
3457 .add_incompatibility(Incompatibility::from_dependency(
3458 *package,
3459 versions,
3460 (
3461 python,
3462 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3463 ),
3464 ));
3465 self.pubgrub
3466 .partial_solution
3467 .add_decision(self.next, version);
3468 return;
3469 }
3470 self.pubgrub
3471 .add_incompatibility(Incompatibility::custom_term(
3472 self.next,
3473 Term::Positive(versions),
3474 UnavailableReason::Version(reason),
3475 ));
3476 }
3477
3478 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3484 self.selected_versions.clear();
3485 self.env = env;
3486 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3488 debug!("Narrowed `requires-python` bound to: {}", req.target());
3489 self.python_requirement = req;
3490 }
3491 self
3492 }
3493
3494 fn source(
3498 &self,
3499 name: &PackageName,
3500 version: &Version,
3501 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3502 let url = self.fork_urls.get(name);
3503 let index = url
3504 .is_none()
3505 .then(|| {
3506 self.pins
3507 .get(name, version)
3508 .expect("Every package should be pinned")
3509 .index()
3510 })
3511 .flatten();
3512 (url, index)
3513 }
3514
3515 fn into_resolution(self) -> Resolution {
3516 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3517 let edge_count: usize = solution
3518 .keys()
3519 .map(|package| self.pubgrub.incompatibilities[package].len())
3520 .sum();
3521 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3522 for (package, self_version) in &solution {
3523 for id in &self.pubgrub.incompatibilities[package] {
3524 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3525 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3526 &incompatibility.kind
3527 else {
3528 continue;
3529 };
3530 let (self_package, dependency_package) = (*self_package, *dependency_package);
3531 let Some((self_range, dependency_range)) =
3532 incompatibility.dependency_version_sets()
3533 else {
3534 continue;
3535 };
3536 let dependency_range =
3537 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3538 if *package != self_package {
3539 continue;
3540 }
3541 if !self_range.contains(self_version) {
3542 continue;
3543 }
3544 let Some(dependency_version) = solution.get(&dependency_package) else {
3545 continue;
3546 };
3547 if !dependency_range.contains(dependency_version) {
3548 continue;
3549 }
3550
3551 let self_package = &self.pubgrub.package_store[self_package];
3552 let dependency_package = &self.pubgrub.package_store[dependency_package];
3553
3554 let (self_name, self_extra, self_group) = match &**self_package {
3555 PubGrubPackageInner::Package {
3556 name: self_name,
3557 extra: self_extra,
3558 group: self_group,
3559 marker: _,
3560 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3561
3562 PubGrubPackageInner::Root(_) => (None, None, None),
3563
3564 _ => continue,
3565 };
3566
3567 let (self_url, self_index) = self_name
3568 .map(|self_name| self.source(self_name, self_version))
3569 .unwrap_or((None, None));
3570
3571 match **dependency_package {
3572 PubGrubPackageInner::Package {
3573 name: ref dependency_name,
3574 extra: ref dependency_extra,
3575 group: ref dependency_dev,
3576 marker: ref dependency_marker,
3577 } => {
3578 debug_assert!(
3579 dependency_extra.is_none(),
3580 "Packages should depend on an extra proxy"
3581 );
3582 debug_assert!(
3583 dependency_dev.is_none(),
3584 "Packages should depend on a group proxy"
3585 );
3586
3587 if self_group.is_none() {
3590 if self_name == Some(dependency_name) {
3591 continue;
3592 }
3593 }
3594
3595 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3596
3597 let edge = ResolutionDependencyEdge {
3598 from: self_name.cloned(),
3599 from_version: self_version.clone(),
3600 from_url: self_url.cloned(),
3601 from_index: self_index.cloned(),
3602 from_extra: self_extra.cloned(),
3603 from_group: self_group.cloned(),
3604 to: dependency_name.clone(),
3605 to_version: dependency_version.clone(),
3606 to_url: to_url.cloned(),
3607 to_index: to_index.cloned(),
3608 to_extra: dependency_extra.clone(),
3609 to_group: dependency_dev.clone(),
3610 marker: *dependency_marker,
3611 };
3612 edges.push(edge);
3613 }
3614
3615 PubGrubPackageInner::Marker {
3616 name: ref dependency_name,
3617 marker: ref dependency_marker,
3618 } => {
3619 if self_group.is_none() {
3622 if self_name == Some(dependency_name) {
3623 continue;
3624 }
3625 }
3626
3627 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3628
3629 let edge = ResolutionDependencyEdge {
3630 from: self_name.cloned(),
3631 from_version: self_version.clone(),
3632 from_url: self_url.cloned(),
3633 from_index: self_index.cloned(),
3634 from_extra: self_extra.cloned(),
3635 from_group: self_group.cloned(),
3636 to: dependency_name.clone(),
3637 to_version: dependency_version.clone(),
3638 to_url: to_url.cloned(),
3639 to_index: to_index.cloned(),
3640 to_extra: None,
3641 to_group: None,
3642 marker: *dependency_marker,
3643 };
3644 edges.push(edge);
3645 }
3646
3647 PubGrubPackageInner::Extra {
3648 name: ref dependency_name,
3649 extra: ref dependency_extra,
3650 marker: ref dependency_marker,
3651 } => {
3652 if self_group.is_none() {
3653 debug_assert!(
3654 self_name != Some(dependency_name),
3655 "Extras should be flattened"
3656 );
3657 }
3658 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3659
3660 let edge = ResolutionDependencyEdge {
3662 from: self_name.cloned(),
3663 from_version: self_version.clone(),
3664 from_url: self_url.cloned(),
3665 from_index: self_index.cloned(),
3666 from_extra: self_extra.cloned(),
3667 from_group: self_group.cloned(),
3668 to: dependency_name.clone(),
3669 to_version: dependency_version.clone(),
3670 to_url: to_url.cloned(),
3671 to_index: to_index.cloned(),
3672 to_extra: Some(dependency_extra.clone()),
3673 to_group: None,
3674 marker: *dependency_marker,
3675 };
3676 edges.push(edge);
3677
3678 let edge = ResolutionDependencyEdge {
3680 from: self_name.cloned(),
3681 from_version: self_version.clone(),
3682 from_url: self_url.cloned(),
3683 from_index: self_index.cloned(),
3684 from_extra: self_extra.cloned(),
3685 from_group: self_group.cloned(),
3686 to: dependency_name.clone(),
3687 to_version: dependency_version.clone(),
3688 to_url: to_url.cloned(),
3689 to_index: to_index.cloned(),
3690 to_extra: None,
3691 to_group: None,
3692 marker: *dependency_marker,
3693 };
3694 edges.push(edge);
3695 }
3696
3697 PubGrubPackageInner::Group {
3698 name: ref dependency_name,
3699 group: ref dependency_group,
3700 marker: ref dependency_marker,
3701 } => {
3702 debug_assert!(
3703 self_name != Some(dependency_name),
3704 "Groups should be flattened"
3705 );
3706
3707 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3708
3709 let edge = ResolutionDependencyEdge {
3712 from: self_name.cloned(),
3713 from_version: self_version.clone(),
3714 from_url: self_url.cloned(),
3715 from_index: self_index.cloned(),
3716 from_extra: self_extra.cloned(),
3717 from_group: self_group.cloned(),
3718 to: dependency_name.clone(),
3719 to_version: dependency_version.clone(),
3720 to_url: to_url.cloned(),
3721 to_index: to_index.cloned(),
3722 to_extra: None,
3723 to_group: Some(dependency_group.clone()),
3724 marker: *dependency_marker,
3725 };
3726 edges.push(edge);
3727 }
3728
3729 _ => {}
3730 }
3731 }
3732 }
3733
3734 let nodes = solution
3735 .into_iter()
3736 .filter_map(|(package, version)| {
3737 if let PubGrubPackageInner::Package {
3738 name,
3739 extra,
3740 group,
3741 marker: MarkerTree::TRUE,
3742 } = &*self.pubgrub.package_store[package]
3743 {
3744 let (url, index) = self.source(name, &version);
3745 Some((
3746 ResolutionPackage {
3747 name: name.clone(),
3748 extra: extra.clone(),
3749 dev: group.clone(),
3750 url: url.cloned(),
3751 index: index.cloned(),
3752 },
3753 version,
3754 ))
3755 } else {
3756 None
3757 }
3758 })
3759 .collect();
3760
3761 Resolution {
3762 nodes,
3763 edges,
3764 pins: self.pins,
3765 env: self.env,
3766 }
3767 }
3768}
3769
3770fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3779 let versions = Range::singleton(version.clone());
3780 match known_versions {
3781 Some(known_versions) if !known_versions.is_empty() => {
3782 versions.widen_versions(known_versions)
3783 }
3784 _ => versions,
3785 }
3786}
3787
3788#[derive(Debug)]
3790#[expect(clippy::large_enum_variant)]
3791pub(crate) enum Request {
3792 Package(PackageName, Option<IndexMetadata>),
3794 Dist(Dist),
3796 Installed(InstalledDist),
3798 Prefetch(PackageName, Range<Version>, PythonRequirement),
3800}
3801
3802impl<'a> From<ResolvedDistRef<'a>> for Request {
3803 fn from(dist: ResolvedDistRef<'a>) -> Self {
3804 match dist {
3810 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3811 let source = prioritized.source_dist().expect("a source distribution");
3814 assert_eq!(
3815 (&sdist.name, &sdist.version),
3816 (&source.name, &source.version),
3817 "expected chosen sdist to match prioritized sdist"
3818 );
3819 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3820 }
3821 ResolvedDistRef::InstallableRegistryBuiltDist {
3822 wheel, prioritized, ..
3823 } => {
3824 assert_eq!(
3825 Some(&wheel.filename),
3826 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3827 "expected chosen wheel to match best wheel"
3828 );
3829 let built = prioritized.built_dist().expect("at least one wheel");
3832 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3833 }
3834 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3835 }
3836 }
3837}
3838
3839impl Display for Request {
3840 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3841 match self {
3842 Self::Package(package_name, _) => {
3843 write!(f, "Versions {package_name}")
3844 }
3845 Self::Dist(dist) => {
3846 write!(f, "Metadata {dist}")
3847 }
3848 Self::Installed(dist) => {
3849 write!(f, "Installed metadata {dist}")
3850 }
3851 Self::Prefetch(package_name, range, _) => {
3852 write!(f, "Prefetch {package_name} {range}")
3853 }
3854 }
3855 }
3856}
3857
3858#[derive(Debug)]
3859#[expect(clippy::large_enum_variant)]
3860enum Response {
3861 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3863 Dist {
3865 dist: Dist,
3866 metadata: MetadataResponse,
3867 },
3868 Installed {
3870 dist: InstalledDist,
3871 metadata: MetadataResponse,
3872 },
3873}
3874
3875enum Dependencies {
3881 Unavailable(UnavailableVersion),
3883 Available(Vec<PubGrubDependency>),
3889 RequiresPython(VersionSpecifiers),
3891 Unforkable(Vec<PubGrubDependency>),
3897}
3898
3899#[derive(Debug)]
3906enum ForkedDependencies {
3907 Unavailable(UnavailableVersion),
3909 Unforked(Vec<PubGrubDependency>),
3913 Forked {
3919 forks: Vec<Fork>,
3920 diverging_packages: BTreeSet<PackageName>,
3922 },
3923 RequiresPython(VersionSpecifiers),
3925}
3926
3927impl ForkedDependencies {
3928 fn from_dependencies_universal(
3935 dependencies: Dependencies,
3936 env: &ResolverEnvironment,
3937 python_requirement: &PythonRequirement,
3938 conflicts: &Conflicts,
3939 ) -> Self {
3940 let deps = match dependencies {
3941 Dependencies::Available(deps) => deps,
3942 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3943 Dependencies::RequiresPython(requires_python) => {
3944 return Self::RequiresPython(requires_python);
3945 }
3946 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3947 };
3948 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3949 for dep in deps {
3950 let name = dep
3951 .package
3952 .name()
3953 .expect("dependency always has a name")
3954 .clone();
3955 name_to_deps.entry(name).or_default().push(dep);
3956 }
3957 let (mut forks, diverging_packages) =
3958 Self::fork(name_to_deps, env, python_requirement, conflicts);
3959 if forks.is_empty() {
3960 Self::Unforked(vec![])
3961 } else if forks.len() == 1 {
3962 Self::Unforked(forks.pop().unwrap().dependencies)
3963 } else {
3964 Self::Forked {
3965 forks,
3966 diverging_packages,
3967 }
3968 }
3969 }
3970
3971 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3974 match dependencies {
3975 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3976 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3977 Dependencies::Unavailable(err) => Self::Unavailable(err),
3978 }
3979 }
3980
3981 fn fork(
3989 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3990 env: &ResolverEnvironment,
3991 python_requirement: &PythonRequirement,
3992 conflicts: &Conflicts,
3993 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3994 let python_marker = python_requirement.to_marker_tree();
3995
3996 let mut forks = vec![Fork::new(env.clone())];
3997 let mut diverging_packages = BTreeSet::new();
3998 for (name, mut deps) in name_to_deps {
3999 assert!(!deps.is_empty(), "every name has at least one dependency");
4000 if let [dep] = deps.as_slice() {
4011 if marker::requires_python(dep.package.marker())
4019 .is_none_or(|bound| !python_requirement.raises(&bound))
4020 {
4021 let dep = deps.pop().unwrap();
4022 let marker = dep.package.marker();
4023 for fork in &mut forks {
4024 if fork.env.included_by_marker(marker) {
4025 fork.add_dependency(dep.clone());
4026 }
4027 }
4028 continue;
4029 }
4030 } else {
4031 if let Some(dep) = deps.first() {
4033 let marker = dep.package.marker();
4034 if deps.iter().all(|dep| marker == dep.package.marker()) {
4035 if marker::requires_python(marker)
4039 .is_none_or(|bound| !python_requirement.raises(&bound))
4040 {
4041 for dep in deps {
4042 for fork in &mut forks {
4043 if fork.env.included_by_marker(marker) {
4044 fork.add_dependency(dep.clone());
4045 }
4046 }
4047 }
4048 continue;
4049 }
4050 }
4051 }
4052 }
4053 for dep in deps {
4054 let mut forker = match ForkingPossibility::new(env, &dep) {
4055 ForkingPossibility::Possible(forker) => forker,
4056 ForkingPossibility::DependencyAlwaysExcluded => {
4057 continue;
4060 }
4061 ForkingPossibility::NoForkingPossible => {
4062 for fork in &mut forks {
4065 fork.add_dependency(dep.clone());
4066 }
4067 continue;
4068 }
4069 };
4070 diverging_packages.insert(name.clone());
4072
4073 let mut new = vec![];
4074 for fork in std::mem::take(&mut forks) {
4075 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4076 new.push(fork);
4077 continue;
4078 };
4079 forker = remaining_forker;
4080
4081 for fork_env in envs {
4082 let mut new_fork = fork.clone();
4083 new_fork.set_env(fork_env);
4084 if forker.included(&new_fork.env) {
4089 new_fork.add_dependency(dep.clone());
4090 }
4091 if new_fork.env.included_by_marker(python_marker) {
4094 new.push(new_fork);
4095 }
4096 }
4097 }
4098 forks = new;
4099 }
4100 }
4101 for set in conflicts.iter() {
4116 let mut new = vec![];
4117 for fork in std::mem::take(&mut forks) {
4118 let mut has_conflicting_dependency = false;
4126 for item in set.iter() {
4127 if fork.contains_conflicting_item(item.as_ref()) {
4128 has_conflicting_dependency = true;
4129 diverging_packages.insert(item.package().clone());
4130 break;
4131 }
4132 }
4133 if !has_conflicting_dependency {
4134 new.push(fork);
4135 continue;
4136 }
4137
4138 let non_excluded: Vec<_> = set
4144 .iter()
4145 .filter(|item| fork.env.included_by_group(item.as_ref()))
4146 .collect();
4147 if non_excluded.len() < 2 {
4148 let dominated = non_excluded.iter().all(|item| {
4153 !conflicts.iter().any(|other_set| {
4154 !std::ptr::eq(set, other_set)
4155 && other_set.contains(item.package(), item.kind().as_ref())
4156 && other_set
4157 .iter()
4158 .filter(|other_item| {
4159 other_item.package() != item.package()
4160 || other_item.kind() != item.kind()
4161 })
4162 .any(|other_item| {
4163 fork.env.included_by_group(other_item.as_ref())
4164 })
4165 })
4166 });
4167 if dominated {
4168 let rules: Vec<_> = set
4173 .iter()
4174 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4175 .cloned()
4176 .map(Err)
4177 .collect();
4178 if let Some(filtered) = fork.filter(rules) {
4179 new.push(filtered);
4180 }
4181 continue;
4182 }
4183 }
4184
4185 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4187 new.push(fork_none);
4188 }
4189
4190 for (i, _) in set.iter().enumerate() {
4198 let fork_allows_group = fork.clone().filter(
4199 set.iter()
4200 .cloned()
4201 .enumerate()
4202 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4203 );
4204 if let Some(fork_allows_group) = fork_allows_group {
4205 new.push(fork_allows_group);
4206 }
4207 }
4208 }
4209 forks = new;
4210 }
4211 (forks, diverging_packages)
4212 }
4213}
4214
4215#[derive(Clone, Debug)]
4225struct Fork {
4226 dependencies: Vec<PubGrubDependency>,
4235 conflicts: crate::FxHashbrownSet<ConflictItem>,
4241 env: ResolverEnvironment,
4253}
4254
4255impl Fork {
4256 fn new(env: ResolverEnvironment) -> Self {
4259 Self {
4260 dependencies: vec![],
4261 conflicts: crate::FxHashbrownSet::default(),
4262 env,
4263 }
4264 }
4265
4266 fn add_dependency(&mut self, dep: PubGrubDependency) {
4268 if let Some(conflicting_item) = dep.conflicting_item() {
4269 self.conflicts.insert(conflicting_item.to_owned());
4270 }
4271 self.dependencies.push(dep);
4272 }
4273
4274 fn set_env(&mut self, env: ResolverEnvironment) {
4279 self.env = env;
4280 self.dependencies.retain(|dep| {
4281 let marker = dep.package.marker();
4282 if self.env.included_by_marker(marker) {
4283 return true;
4284 }
4285 if let Some(conflicting_item) = dep.conflicting_item() {
4286 self.conflicts.remove(&conflicting_item);
4287 }
4288 false
4289 });
4290 }
4291
4292 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4295 self.conflicts.contains(&item)
4296 }
4297
4298 fn filter(
4305 mut self,
4306 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4307 ) -> Option<Self> {
4308 self.env = self.env.filter_by_group(rules)?;
4309 self.dependencies.retain(|dep| {
4310 let Some(conflicting_item) = dep.conflicting_item() else {
4311 return true;
4312 };
4313 if self.env.included_by_group(conflicting_item) {
4314 return true;
4315 }
4316 match conflicting_item.kind() {
4317 ConflictKindRef::Project => {
4320 if dep.parent.is_some() {
4321 return true;
4322 }
4323 }
4324 ConflictKindRef::Group(_) => {}
4325 ConflictKindRef::Extra(_) => {}
4326 }
4327 self.conflicts.remove(&conflicting_item);
4328 false
4329 });
4330 Some(self)
4331 }
4332
4333 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4335 cmp_requires_python(&self.env, &other.env)
4336 }
4337
4338 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4340 let self_upper_bounds = self
4345 .dependencies
4346 .iter()
4347 .filter(|dep| {
4348 dep.version
4349 .bounding_range()
4350 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4351 })
4352 .count();
4353 let other_upper_bounds = other
4354 .dependencies
4355 .iter()
4356 .filter(|dep| {
4357 dep.version
4358 .bounding_range()
4359 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4360 })
4361 .count();
4362
4363 self_upper_bounds.cmp(&other_upper_bounds)
4364 }
4365}
4366
4367fn cmp_requires_python(
4369 self_env: &ResolverEnvironment,
4370 other_env: &ResolverEnvironment,
4371) -> Ordering {
4372 let self_bound = self_env.requires_python().unwrap_or_default();
4382 let other_bound = other_env.requires_python().unwrap_or_default();
4383 self_bound.lower().cmp(other_bound.lower())
4384}
4385
4386impl Eq for Fork {}
4387
4388impl PartialEq for Fork {
4389 fn eq(&self, other: &Self) -> bool {
4390 self.dependencies == other.dependencies && self.env == other.env
4391 }
4392}
4393
4394#[derive(Debug, Clone)]
4395pub(crate) struct VersionFork {
4396 env: ResolverEnvironment,
4398 id: Id<PubGrubPackage>,
4400 version: Option<Version>,
4402}
4403
4404fn enrich_dependency_error(
4406 error: ResolveError,
4407 id: Id<PubGrubPackage>,
4408 version: &Version,
4409 pubgrub: &State<UvDependencyProvider>,
4410) -> ResolveError {
4411 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4412 return error;
4413 };
4414 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4415 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4416}
4417
4418fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4420 let package = &state.package_store[id];
4421 if package.is_root() {
4422 return MarkerTree::TRUE;
4423 }
4424
4425 let mut ancestors = FxHashSet::default();
4428 let mut stack = vec![id];
4429 let mut root = None;
4430 ancestors.insert(id);
4431
4432 while let Some(current) = stack.pop() {
4433 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4434 continue;
4435 };
4436
4437 for index in incompatibilities {
4438 let incompat = &state.incompatibility_store[*index];
4439 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4440 if current != *child {
4441 continue;
4442 }
4443 if ancestors.insert(*parent) {
4444 if state.package_store[*parent].is_root() {
4445 root = Some(*parent);
4446 }
4447 stack.push(*parent);
4448 }
4449 }
4450 }
4451 }
4452
4453 let Some(root) = root else {
4454 return MarkerTree::FALSE;
4455 };
4456
4457 let mut environments = FxHashMap::default();
4460 let mut queue = VecDeque::from([root]);
4461 environments.insert(root, MarkerTree::TRUE);
4462
4463 while let Some(current) = queue.pop_front() {
4464 let Some(current_environment) = environments.get(¤t).copied() else {
4465 continue;
4466 };
4467 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4468 continue;
4469 };
4470
4471 for index in incompatibilities {
4472 let incompat = &state.incompatibility_store[*index];
4473 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4474 continue;
4475 };
4476 if current != *parent || !ancestors.contains(child) {
4477 continue;
4478 }
4479
4480 let mut next_environment = state.package_store[*child].marker();
4481 next_environment = next_environment.and(current_environment);
4482
4483 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4484 let mut combined = *entry;
4485 combined = combined.or(next_environment);
4486 if combined != *entry {
4487 *entry = combined;
4488 queue.push_back(*child);
4489 }
4490 }
4491 }
4492
4493 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4494}
4495
4496#[derive(Debug, Default, Clone)]
4497struct ConflictTracker {
4498 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4500 prioritize: Vec<Id<PubGrubPackage>>,
4504 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4506 deprioritize: Vec<Id<PubGrubPackage>>,
4510}
4511
4512#[cfg(test)]
4513mod tests {
4514 use super::*;
4515
4516 fn versions(versions: &[&str]) -> Vec<Version> {
4517 versions
4518 .iter()
4519 .map(|version| version.parse().expect("valid version"))
4520 .collect()
4521 }
4522
4523 #[test]
4524 fn widens_a_version_to_its_gap() {
4525 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4526 let version: Version = "2.0".parse().expect("valid version");
4527
4528 assert_eq!(
4530 widen_to_gap(&version, Some(&known_versions)).to_string(),
4531 ">1.0, <3.0"
4532 );
4533
4534 let version: Version = "1.0".parse().expect("valid version");
4536 assert_eq!(
4537 widen_to_gap(&version, Some(&known_versions)).to_string(),
4538 "<2.0"
4539 );
4540 let version: Version = "3.0".parse().expect("valid version");
4541 assert_eq!(
4542 widen_to_gap(&version, Some(&known_versions)).to_string(),
4543 ">2.0"
4544 );
4545 }
4546
4547 #[test]
4548 fn widens_a_version_without_known_versions_to_itself() {
4549 let version: Version = "2.0".parse().expect("valid version");
4550
4551 assert_eq!(
4553 widen_to_gap(&version, None),
4554 Range::singleton(version.clone())
4555 );
4556
4557 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4559 }
4560}