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,
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 fn known_versions<'a>(
1117 index: &InMemoryIndex,
1118 installed_packages: &InstalledPackages,
1119 fork_urls: &ForkUrls,
1120 fork_indexes: &ForkIndexes,
1121 known_versions: &'a mut FxHashMap<PackageName, Arc<[Version]>>,
1122 package: &PubGrubPackage,
1123 ) -> Option<&'a [Version]> {
1124 let name = package.name_no_root()?;
1125 if fork_urls.get(name).is_some() {
1127 return None;
1128 }
1129 if !known_versions.contains_key(name) {
1130 let response = if let Some(index_metadata) = fork_indexes.get(name) {
1131 index
1132 .explicit()
1133 .get(&(name.clone(), index_metadata.url().clone()))?
1134 } else {
1135 index.implicit().get(name)?
1136 };
1137 let VersionsResponse::Found(ref version_maps) = *response else {
1138 return None;
1139 };
1140 let mut versions: Vec<Version> = version_maps
1141 .iter()
1142 .flat_map(|version_map| version_map.included_versions().cloned())
1143 .chain(
1144 installed_packages
1145 .get_packages(name)
1146 .iter()
1147 .map(|dist| dist.version().clone()),
1148 )
1149 .collect();
1150 versions.sort_unstable();
1151 versions.dedup();
1152 known_versions.insert(name.clone(), versions.into());
1153 }
1154 Some(&known_versions[name][..])
1155 }
1156
1157 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1165 fn choose_version(
1166 &self,
1167 package: &PubGrubPackage,
1168 id: Id<PubGrubPackage>,
1169 index: Option<&IndexUrl>,
1170 range: &Range<Version>,
1171 pins: &mut FilePins,
1172 preferences: &Preferences,
1173 fork_urls: &ForkUrls,
1174 env: &ResolverEnvironment,
1175 python_requirement: &PythonRequirement,
1176 pubgrub: &State<UvDependencyProvider>,
1177 visited: &mut FxHashSet<PackageName>,
1178 request_sink: &Sender<Request>,
1179 ) -> Result<Option<ResolverVersion>, ResolveError> {
1180 match &**package {
1181 PubGrubPackageInner::Root(_) => {
1182 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1183 }
1184
1185 PubGrubPackageInner::Python(_) => {
1186 Ok(None)
1189 }
1190
1191 PubGrubPackageInner::System(_) => {
1192 let Some(version) = range.as_singleton() else {
1195 return Ok(None);
1196 };
1197 Ok(Some(ResolverVersion::Unforked(version.clone())))
1198 }
1199
1200 PubGrubPackageInner::Marker { name, .. }
1201 | PubGrubPackageInner::Extra { name, .. }
1202 | PubGrubPackageInner::Group { name, .. }
1203 | PubGrubPackageInner::Package { name, .. } => {
1204 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1205 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1206 } else {
1207 self.choose_version_registry(
1208 package,
1209 id,
1210 name,
1211 index,
1212 range,
1213 preferences,
1214 env,
1215 python_requirement,
1216 pubgrub,
1217 pins,
1218 visited,
1219 request_sink,
1220 )
1221 }
1222 }
1223 }
1224 }
1225
1226 fn choose_version_url(
1229 &self,
1230 id: Id<PubGrubPackage>,
1231 name: &PackageName,
1232 range: &Range<Version>,
1233 url: &VerbatimParsedUrl,
1234 env: &ResolverEnvironment,
1235 python_requirement: &PythonRequirement,
1236 pubgrub: &State<UvDependencyProvider>,
1237 ) -> Result<Option<ResolverVersion>, ResolveError> {
1238 debug!(
1239 "Searching for a compatible version of {name} @ {} ({range})",
1240 url.verbatim
1241 );
1242
1243 let dist = Dist::from_url(name.clone(), url.clone())?;
1244 let distribution_id = dist.distribution_id();
1245 let response = self
1246 .index
1247 .distributions()
1248 .wait_blocking(&distribution_id)
1249 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1250
1251 let metadata = match &*response {
1253 MetadataResponse::Found(archive) => &archive.metadata,
1254 MetadataResponse::Unavailable(reason) => {
1255 self.unavailable_packages
1256 .pin()
1257 .insert(name.clone(), reason.into());
1258 return Ok(None);
1259 }
1260 MetadataResponse::Error(dist, err) => {
1263 return Err(ResolveError::Dist(
1264 DistErrorKind::from_requested_dist(dist, &**err),
1265 dist.clone(),
1266 DerivationChain::default(),
1267 err.clone(),
1268 ));
1269 }
1270 };
1271
1272 let version = &metadata.version;
1273
1274 if !range.contains(version) {
1276 return Ok(None);
1277 }
1278
1279 if let Dist::Built(dist) = &dist {
1282 let filename = match &dist {
1283 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1284 BuiltDist::DirectUrl(dist) => &dist.filename,
1285 BuiltDist::GitPath(dist) => &dist.filename,
1286 BuiltDist::Path(dist) => &dist.filename,
1287 };
1288
1289 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1292 {
1293 let wheel_marker = implied_markers(filename);
1294 for environment_marker in self.options.artifact_environments.iter().copied() {
1297 if env.included_by_marker(environment_marker)
1299 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1300 {
1301 if wheel_marker.is_disjoint(environment_marker) {
1303 return Ok(Some(ResolverVersion::Unavailable(
1304 version.clone(),
1305 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1306 IncompatibleWheel::MissingPlatform(environment_marker),
1307 )),
1308 )));
1309 }
1310 }
1311 }
1312 }
1313
1314 if !python_requirement.target().matches_wheel_tag(filename) {
1316 return Ok(Some(ResolverVersion::Unavailable(
1317 filename.version.clone(),
1318 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1319 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1320 )),
1321 )));
1322 }
1323 }
1324
1325 if let Some(requires_python) = metadata.requires_python.as_ref() {
1327 if !python_requirement.target().is_contained_by(requires_python) {
1328 let kind = if python_requirement.installed() == python_requirement.target() {
1329 PythonRequirementKind::Installed
1330 } else {
1331 PythonRequirementKind::Target
1332 };
1333 return Ok(Some(ResolverVersion::Unavailable(
1334 version.clone(),
1335 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1336 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1337 )),
1338 )));
1339 }
1340 }
1341
1342 Ok(Some(ResolverVersion::Unforked(version.clone())))
1343 }
1344
1345 fn choose_version_registry(
1348 &self,
1349 package: &PubGrubPackage,
1350 id: Id<PubGrubPackage>,
1351 name: &PackageName,
1352 index: Option<&IndexUrl>,
1353 range: &Range<Version>,
1354 preferences: &Preferences,
1355 env: &ResolverEnvironment,
1356 python_requirement: &PythonRequirement,
1357 pubgrub: &State<UvDependencyProvider>,
1358 pins: &mut FilePins,
1359 visited: &mut FxHashSet<PackageName>,
1360 request_sink: &Sender<Request>,
1361 ) -> Result<Option<ResolverVersion>, ResolveError> {
1362 let versions_response = if let Some(index) = index {
1364 self.index
1365 .explicit()
1366 .wait_blocking(&(name.clone(), index.clone()))
1367 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1368 } else {
1369 self.index
1370 .implicit()
1371 .wait_blocking(name)
1372 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1373 };
1374 visited.insert(name.clone());
1375
1376 let version_maps = match *versions_response {
1377 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1378 VersionsResponse::NoIndex => {
1379 self.unavailable_packages
1380 .pin()
1381 .insert(name.clone(), UnavailablePackage::NoIndex);
1382 &[]
1383 }
1384 VersionsResponse::Offline => {
1385 self.unavailable_packages
1386 .pin()
1387 .insert(name.clone(), UnavailablePackage::Offline);
1388 &[]
1389 }
1390 VersionsResponse::NotFound => {
1391 self.unavailable_packages
1392 .pin()
1393 .insert(name.clone(), UnavailablePackage::NotFound);
1394 &[]
1395 }
1396 };
1397
1398 debug!("Searching for a compatible version of {package} ({range})");
1399
1400 let Some(candidate) = self.selector.select(
1402 name,
1403 range,
1404 version_maps,
1405 preferences,
1406 &self.installed_packages,
1407 &self.exclusions,
1408 index,
1409 env,
1410 self.tags.as_ref(),
1411 ) else {
1412 return Ok(None);
1414 };
1415
1416 let dist = match candidate.dist() {
1417 CandidateDist::Compatible(dist) => dist,
1418 CandidateDist::Incompatible {
1419 incompatible_dist: incompatibility,
1420 prioritized_dist: _,
1421 } => {
1422 return Ok(Some(ResolverVersion::Unavailable(
1424 candidate.version().clone(),
1425 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1428 )));
1429 }
1430 };
1431
1432 if let Some((requires_python, incompatibility)) =
1434 Self::check_requires_python(dist, python_requirement)
1435 {
1436 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1437 if env.marker_environment().is_none() {
1438 let forks = fork_version_by_python_requirement(
1439 requires_python,
1440 python_requirement,
1441 env,
1442 );
1443 if !forks.is_empty() {
1444 debug!(
1445 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1446 python_requirement.target(),
1447 requires_python,
1448 name,
1449 candidate.version(),
1450 forks
1451 .iter()
1452 .map(ToString::to_string)
1453 .collect::<Vec<_>>()
1454 .join(", ")
1455 );
1456 let forks = forks
1457 .into_iter()
1458 .map(|env| VersionFork {
1459 env,
1460 id,
1461 version: None,
1462 })
1463 .collect();
1464 return Ok(Some(ResolverVersion::Forked(forks)));
1465 }
1466 }
1467 }
1468
1469 return Ok(Some(ResolverVersion::Unavailable(
1470 candidate.version().clone(),
1471 UnavailableVersion::IncompatibleDist(incompatibility),
1472 )));
1473 }
1474
1475 if let Some(forked) = self.fork_version_registry(
1477 &candidate,
1478 dist,
1479 version_maps,
1480 package,
1481 id,
1482 name,
1483 index,
1484 range,
1485 preferences,
1486 env,
1487 pubgrub,
1488 pins,
1489 request_sink,
1490 )? {
1491 return Ok(Some(forked));
1492 }
1493
1494 let filename = match dist.for_installation() {
1495 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1496 .filename()
1497 .unwrap_or(Cow::Borrowed("unknown filename")),
1498 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1499 .filename()
1500 .unwrap_or(Cow::Borrowed("unknown filename")),
1501 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1502 };
1503
1504 debug!(
1505 "Selecting: {}=={} [{}] ({})",
1506 name,
1507 candidate.version(),
1508 candidate.choice_kind(),
1509 filename,
1510 );
1511 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1512
1513 let version = candidate.version().clone();
1514 Ok(Some(ResolverVersion::Unforked(version)))
1515 }
1516
1517 fn fork_version_registry(
1530 &self,
1531 candidate: &Candidate,
1532 dist: &CompatibleDist,
1533 version_maps: &[VersionMap],
1534 package: &PubGrubPackage,
1535 id: Id<PubGrubPackage>,
1536 name: &PackageName,
1537 index: Option<&IndexUrl>,
1538 range: &Range<Version>,
1539 preferences: &Preferences,
1540 env: &ResolverEnvironment,
1541 pubgrub: &State<UvDependencyProvider>,
1542 pins: &mut FilePins,
1543 request_sink: &Sender<Request>,
1544 ) -> Result<Option<ResolverVersion>, ResolveError> {
1545 if env.marker_environment().is_some() {
1547 return Ok(None);
1548 }
1549
1550 if dist.implied_markers().is_true() {
1553 return Ok(None);
1554 }
1555
1556 for marker in self.options.artifact_environments.iter().copied() {
1559 if env.included_by_marker(marker) {
1561 if dist.implied_markers().is_disjoint(marker)
1563 && !find_environments(id, pubgrub).is_disjoint(marker)
1564 {
1565 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1567 return Ok(Some(ResolverVersion::Unavailable(
1568 candidate.version().clone(),
1569 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1570 IncompatibleWheel::MissingPlatform(marker),
1571 )),
1572 )));
1573 };
1574
1575 debug!(
1576 "Forking on required platform `{}` for {}=={} ({})",
1577 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1578 name,
1579 candidate.version(),
1580 [&left, &right]
1581 .iter()
1582 .map(ToString::to_string)
1583 .collect::<Vec<_>>()
1584 .join(", ")
1585 );
1586 let forks = vec![
1587 VersionFork {
1588 env: left,
1589 id,
1590 version: None,
1591 },
1592 VersionFork {
1593 env: right,
1594 id,
1595 version: None,
1596 },
1597 ];
1598 return Ok(Some(ResolverVersion::Forked(forks)));
1599 }
1600 }
1601 }
1602
1603 if !candidate.version().is_local() {
1605 return Ok(None);
1606 }
1607
1608 debug!(
1609 "Looking at local version: {}=={}",
1610 name,
1611 candidate.version()
1612 );
1613
1614 let range = range.clone().intersection(&Range::singleton(
1616 candidate.version().clone().without_local(),
1617 ));
1618
1619 let Some(base_candidate) = self.selector.select(
1620 name,
1621 &range,
1622 version_maps,
1623 preferences,
1624 &self.installed_packages,
1625 &self.exclusions,
1626 index,
1627 env,
1628 self.tags.as_ref(),
1629 ) else {
1630 return Ok(None);
1631 };
1632 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1633 return Ok(None);
1634 };
1635
1636 let mut remainder = {
1638 let mut remainder = base_dist.implied_markers();
1639 remainder = remainder.and(dist.implied_markers().negate());
1640 remainder
1641 };
1642 if remainder.is_false() {
1643 return Ok(None);
1644 }
1645
1646 if !env.included_by_marker(remainder) {
1650 return Ok(None);
1651 }
1652
1653 if !env.included_by_marker(dist.implied_markers()) {
1656 let filename = match dist.for_installation() {
1657 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1658 .filename()
1659 .unwrap_or(Cow::Borrowed("unknown filename")),
1660 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1661 .filename()
1662 .unwrap_or(Cow::Borrowed("unknown filename")),
1663 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1664 };
1665
1666 debug!(
1667 "Preferring non-local candidate: {}=={} [{}] ({})",
1668 name,
1669 base_candidate.version(),
1670 base_candidate.choice_kind(),
1671 filename,
1672 );
1673 self.visit_candidate(
1674 &base_candidate,
1675 base_dist,
1676 package,
1677 name,
1678 pins,
1679 request_sink,
1680 )?;
1681
1682 return Ok(Some(ResolverVersion::Unforked(
1683 base_candidate.version().clone(),
1684 )));
1685 }
1686
1687 for value in [
1696 arcstr::literal!("darwin"),
1697 arcstr::literal!("linux"),
1698 arcstr::literal!("win32"),
1699 ] {
1700 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1701 key: MarkerValueString::SysPlatform,
1702 operator: MarkerOperator::Equal,
1703 value,
1704 });
1705 if dist.implied_markers().is_disjoint(sys_platform)
1706 && !remainder.is_disjoint(sys_platform)
1707 {
1708 remainder = remainder.or(sys_platform);
1709 }
1710 }
1711
1712 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1714 return Ok(None);
1715 };
1716
1717 debug!(
1718 "Forking platform for {}=={} ({})",
1719 name,
1720 candidate.version(),
1721 [&base_env, &local_env]
1722 .iter()
1723 .map(ToString::to_string)
1724 .collect::<Vec<_>>()
1725 .join(", ")
1726 );
1727 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1728 self.visit_candidate(
1729 &base_candidate,
1730 base_dist,
1731 package,
1732 name,
1733 pins,
1734 request_sink,
1735 )?;
1736
1737 let forks = vec![
1738 VersionFork {
1739 env: base_env.clone(),
1740 id,
1741 version: Some(base_candidate.version().clone()),
1742 },
1743 VersionFork {
1744 env: local_env.clone(),
1745 id,
1746 version: Some(candidate.version().clone()),
1747 },
1748 ];
1749 Ok(Some(ResolverVersion::Forked(forks)))
1750 }
1751
1752 fn visit_candidate(
1754 &self,
1755 candidate: &Candidate,
1756 dist: &CompatibleDist,
1757 package: &PubGrubPackage,
1758 name: &PackageName,
1759 pins: &mut FilePins,
1760 request_sink: &Sender<Request>,
1761 ) -> Result<(), ResolveError> {
1762 pins.insert(candidate, dist);
1765
1766 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1768 if self.dependency_mode.is_transitive() {
1769 let dist = dist.for_resolution();
1770 if self.index.distributions().register(dist.distribution_id()) {
1771 if name != dist.name() {
1772 return Err(ResolveError::MismatchedPackageName {
1773 request: "distribution",
1774 expected: name.clone(),
1775 actual: dist.name().clone(),
1776 });
1777 }
1778 if !self
1780 .hasher
1781 .allows_package(candidate.name(), candidate.version())
1782 {
1783 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1784 }
1785
1786 let request = Request::from(dist);
1787 request_sink.blocking_send(request)?;
1788 }
1789 }
1790 }
1791
1792 Ok(())
1793 }
1794
1795 fn check_requires_python<'dist>(
1798 dist: &'dist CompatibleDist,
1799 python_requirement: &PythonRequirement,
1800 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1801 let requires_python = dist.requires_python()?;
1802 if python_requirement.target().is_contained_by(requires_python) {
1803 None
1804 } else {
1805 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1806 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1807 requires_python.clone(),
1808 if python_requirement.installed() == python_requirement.target() {
1809 PythonRequirementKind::Installed
1810 } else {
1811 PythonRequirementKind::Target
1812 },
1813 ))
1814 } else {
1815 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1816 requires_python.clone(),
1817 if python_requirement.installed() == python_requirement.target() {
1818 PythonRequirementKind::Installed
1819 } else {
1820 PythonRequirementKind::Target
1821 },
1822 ))
1823 };
1824 Some((requires_python, incompatibility))
1825 }
1826 }
1827
1828 fn get_dependencies_forking(
1830 &self,
1831 id: Id<PubGrubPackage>,
1832 package: &PubGrubPackage,
1833 version: &Version,
1834 pins: &FilePins,
1835 fork_urls: &ForkUrls,
1836 env: &ResolverEnvironment,
1837 python_requirement: &PythonRequirement,
1838 pubgrub: &State<UvDependencyProvider>,
1839 ) -> Result<ForkedDependencies, ResolveError> {
1840 let dependencies = self.get_dependencies(
1841 id,
1842 package,
1843 version,
1844 pins,
1845 fork_urls,
1846 env,
1847 python_requirement,
1848 pubgrub,
1849 )?;
1850 if env.marker_environment().is_some() {
1851 Ok(ForkedDependencies::from_dependencies_platform_specific(
1852 dependencies,
1853 ))
1854 } else {
1855 Ok(ForkedDependencies::from_dependencies_universal(
1856 dependencies,
1857 env,
1858 python_requirement,
1859 &self.conflicts,
1860 ))
1861 }
1862 }
1863
1864 #[instrument(skip_all, fields(%package, %version))]
1866 fn get_dependencies(
1867 &self,
1868 id: Id<PubGrubPackage>,
1869 package: &PubGrubPackage,
1870 version: &Version,
1871 pins: &FilePins,
1872 fork_urls: &ForkUrls,
1873 env: &ResolverEnvironment,
1874 python_requirement: &PythonRequirement,
1875 pubgrub: &State<UvDependencyProvider>,
1876 ) -> Result<Dependencies, ResolveError> {
1877 let dependencies = match &**package {
1878 PubGrubPackageInner::Root(_) => {
1879 let no_dev_deps = BTreeMap::default();
1880 let requirements = self.flatten_requirements(
1881 &self.requirements,
1882 &no_dev_deps,
1883 None,
1884 None,
1885 None,
1886 None,
1887 env,
1888 python_requirement,
1889 );
1890
1891 PubGrubDependency::from_requirements(
1892 &self.conflicts,
1893 requirements,
1894 None,
1895 Some(package),
1896 )
1897 }
1898
1899 PubGrubPackageInner::Package {
1900 name,
1901 extra,
1902 group,
1903 marker: _,
1904 } => {
1905 if self.dependency_mode.is_direct() {
1907 return Ok(Dependencies::Unforkable(Vec::default()));
1908 }
1909
1910 let owned_id;
1912 let distribution_id = if let Some((_, metadata_id)) =
1913 pins.dist_and_id(name, version)
1914 {
1915 metadata_id
1916 } else if let Some(url) = fork_urls.get(name) {
1917 let dist = Dist::from_url(name.clone(), url.clone())?;
1918 owned_id = dist.distribution_id();
1919 &owned_id
1920 } else {
1921 debug_assert!(
1922 false,
1923 "Dependencies were requested for a package without a pinned distribution"
1924 );
1925 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1926 };
1927
1928 if self.dependency_mode.is_transitive()
1930 && self.unavailable_packages.pin().contains_key(name)
1931 && self.installed_packages.get_packages(name).is_empty()
1932 {
1933 debug_assert!(
1934 false,
1935 "Dependencies were requested for a package that is not available"
1936 );
1937 return Err(ResolveError::PackageUnavailable(name.clone()));
1938 }
1939
1940 let response = self
1942 .index
1943 .distributions()
1944 .wait_blocking(distribution_id)
1945 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1946
1947 let metadata = match &*response {
1948 MetadataResponse::Found(archive) => &archive.metadata,
1949 MetadataResponse::Unavailable(reason) => {
1950 let unavailable_version = UnavailableVersion::from(reason);
1951 let message = unavailable_version.singular_message();
1952 if let Some(err) = reason.source() {
1953 warn!("{name} {message}: {err}");
1955 } else {
1956 warn!("{name} {message}");
1957 }
1958 let incomplete_packages = self.incomplete_packages.pin();
1959 let versions = incomplete_packages.get_or_insert(
1960 name.clone(),
1961 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1962 );
1963 versions.pin().insert(version.clone(), reason.clone());
1964 return Ok(Dependencies::Unavailable(unavailable_version));
1965 }
1966 MetadataResponse::Error(dist, err) => {
1967 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1968 .unwrap_or_default();
1969 return Err(ResolveError::Dist(
1970 DistErrorKind::from_requested_dist(dist, &**err),
1971 dist.clone(),
1972 chain,
1973 err.clone(),
1974 ));
1975 }
1976 };
1977
1978 if let Some(requires_python) = &metadata.requires_python {
1981 if !python_requirement.target().is_contained_by(requires_python) {
1982 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1983 }
1984 }
1985
1986 let system_dependencies = self
1988 .options
1989 .torch_backend
1990 .as_ref()
1991 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1992 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1993 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1994 .map(IndexUrl::url)
1995 .and_then(SystemDependency::from_index)
1996 .into_iter()
1997 .inspect(|system_dependency| {
1998 debug!(
1999 "Adding system dependency `{}` for `{package}@{version}`",
2000 system_dependency
2001 );
2002 })
2003 .map(PubGrubDependency::from);
2004
2005 let requirements = self.flatten_requirements(
2006 &metadata.requires_dist,
2007 &metadata.dependency_groups,
2008 extra.as_ref(),
2009 group.as_ref(),
2010 Some(name),
2011 Some(version),
2012 env,
2013 python_requirement,
2014 );
2015
2016 PubGrubDependency::from_requirements(
2017 &self.conflicts,
2018 requirements,
2019 group.as_ref(),
2020 Some(package),
2021 )
2022 .map(|mut dependencies| {
2023 dependencies.extend(system_dependencies);
2024 dependencies
2025 })
2026 }
2027
2028 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2029
2030 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2031
2032 PubGrubPackageInner::Marker { name, marker } => {
2034 return Ok(Dependencies::Unforkable(
2035 [MarkerTree::TRUE, *marker]
2036 .into_iter()
2037 .map(move |marker| PubGrubDependency {
2038 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2039 name: name.clone(),
2040 extra: None,
2041 group: None,
2042 marker,
2043 }),
2044 version: Range::singleton(version.clone()),
2045 parent: None,
2046 source: DependencySource::Unspecified,
2047 })
2048 .collect(),
2049 ));
2050 }
2051
2052 PubGrubPackageInner::Extra {
2054 name,
2055 extra,
2056 marker,
2057 } => {
2058 return Ok(Dependencies::Unforkable(
2059 [MarkerTree::TRUE, *marker]
2060 .into_iter()
2061 .dedup()
2062 .flat_map(move |marker| {
2063 [None, Some(extra)]
2064 .into_iter()
2065 .map(move |extra| PubGrubDependency {
2066 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2067 name: name.clone(),
2068 extra: extra.cloned(),
2069 group: None,
2070 marker,
2071 }),
2072 version: Range::singleton(version.clone()),
2073 parent: None,
2074 source: DependencySource::Unspecified,
2075 })
2076 })
2077 .collect(),
2078 ));
2079 }
2080
2081 PubGrubPackageInner::Group {
2083 name,
2084 group,
2085 marker,
2086 } => {
2087 return Ok(Dependencies::Unforkable(
2088 [MarkerTree::TRUE, *marker]
2089 .into_iter()
2090 .dedup()
2091 .map(|marker| PubGrubDependency {
2092 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2093 name: name.clone(),
2094 extra: None,
2095 group: Some(group.clone()),
2096 marker,
2097 }),
2098 version: Range::singleton(version.clone()),
2099 parent: None,
2100 source: DependencySource::Unspecified,
2101 })
2102 .collect(),
2103 ));
2104 }
2105 };
2106 Ok(match dependencies {
2107 Ok(dependencies) => Dependencies::Available(dependencies),
2108 Err(requirement) => {
2109 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2110 }
2111 })
2112 }
2113
2114 fn flatten_requirements<'a>(
2118 &'a self,
2119 dependencies: &'a [Requirement],
2120 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2121 extra: Option<&'a ExtraName>,
2122 dev: Option<&'a GroupName>,
2123 name: Option<&'a PackageName>,
2124 version: Option<&'a Version>,
2125 env: &'a ResolverEnvironment,
2126 python_requirement: &'a PythonRequirement,
2127 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2128 let python_marker = python_requirement.to_marker_tree();
2129
2130 if let Some(dev) = dev {
2131 Either::Left(Either::Left(self.requirements_for_extra(
2134 dev_dependencies.get(dev).into_iter().flatten(),
2135 extra,
2136 None,
2137 name.zip(version),
2138 env,
2139 python_marker,
2140 python_requirement,
2141 )))
2142 } else if !dependencies
2143 .iter()
2144 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2145 {
2146 Either::Left(Either::Right(self.requirements_for_extra(
2148 dependencies.iter(),
2149 extra,
2150 name.zip(version),
2151 name.zip(version),
2152 env,
2153 python_marker,
2154 python_requirement,
2155 )))
2156 } else {
2157 let mut requirements = self
2158 .requirements_for_extra(
2159 dependencies.iter(),
2160 extra,
2161 name.zip(version),
2162 name.zip(version),
2163 env,
2164 python_marker,
2165 python_requirement,
2166 )
2167 .collect::<Vec<_>>();
2168
2169 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2172 let mut queue: VecDeque<_> = requirements
2173 .iter()
2174 .filter(|req| name == Some(&req.name))
2175 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2176 .collect();
2177 while let Some((extra, marker)) = queue.pop_front() {
2178 if !seen.insert((extra.clone(), marker)) {
2179 continue;
2180 }
2181 for requirement in self.requirements_for_extra(
2182 dependencies,
2183 Some(&extra),
2184 name.zip(version),
2185 name.zip(version),
2186 env,
2187 python_marker,
2188 python_requirement,
2189 ) {
2190 let requirement = match requirement {
2191 Cow::Owned(mut requirement) => {
2192 requirement.marker = requirement.marker.and(marker);
2193 requirement
2194 }
2195 Cow::Borrowed(requirement) => {
2196 let mut marker = marker;
2197 marker = marker.and(requirement.marker);
2198 Requirement {
2199 name: requirement.name.clone(),
2200 extras: requirement.extras.clone(),
2201 groups: requirement.groups.clone(),
2202 source: requirement.source.clone(),
2203 origin: requirement.origin.clone(),
2204 marker: marker.simplify_extras(slice::from_ref(&extra)),
2205 }
2206 }
2207 };
2208 if name == Some(&requirement.name) {
2209 queue.extend(
2211 requirement
2212 .extras
2213 .iter()
2214 .cloned()
2215 .map(|extra| (extra, requirement.marker)),
2216 );
2217 } else {
2218 requirements.push(Cow::Owned(requirement));
2220 }
2221 }
2222 }
2223
2224 let mut self_constraints = vec![];
2228 for req in &requirements {
2229 if name == Some(&req.name) && !req.source.is_empty() {
2230 self_constraints.push(Requirement {
2231 name: req.name.clone(),
2232 extras: Box::new([]),
2233 groups: req.groups.clone(),
2234 source: req.source.clone(),
2235 origin: req.origin.clone(),
2236 marker: req.marker,
2237 });
2238 }
2239 }
2240
2241 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2243 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2244
2245 Either::Right(requirements.into_iter())
2246 }
2247 }
2248
2249 fn requirements_for_extra<'data, 'parameters>(
2252 &'data self,
2253 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2254 extra: Option<&'parameters ExtraName>,
2255 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2256 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2257 env: &'parameters ResolverEnvironment,
2258 python_marker: MarkerTree,
2259 python_requirement: &'parameters PythonRequirement,
2260 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2261 where
2262 'data: 'parameters,
2263 {
2264 self.overrides
2265 .apply_for_package(override_package, dependencies)
2266 .filter(move |requirement| {
2267 !self
2268 .excludes
2269 .contains_for_package(exclusion_package, &requirement.name)
2270 })
2271 .map(move |mut requirement| {
2272 let marker = match extra {
2281 Some(extra) => requirement
2282 .marker
2283 .simplify_extras(slice::from_ref(extra))
2284 .simplify_not_extras_with(|candidate| candidate != extra)
2285 .and(
2286 requirement
2287 .marker
2288 .simplify_not_extras_with(|_| true)
2289 .negate(),
2290 ),
2291 None => requirement.marker.simplify_not_extras_with(|_| true),
2292 };
2293
2294 if requirement.marker != marker {
2295 requirement.to_mut().marker = marker;
2296 }
2297
2298 requirement
2299 })
2300 .filter(move |requirement| {
2301 Self::is_requirement_applicable(
2302 requirement,
2303 extra,
2304 env,
2305 python_marker,
2306 python_requirement,
2307 )
2308 })
2309 .flat_map(move |requirement| {
2310 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2311 requirement,
2312 extra,
2313 env,
2314 python_marker,
2315 python_requirement,
2316 ))
2317 })
2318 }
2319
2320 fn is_requirement_applicable(
2323 requirement: &Requirement,
2324 extra: Option<&ExtraName>,
2325 env: &ResolverEnvironment,
2326 python_marker: MarkerTree,
2327 python_requirement: &PythonRequirement,
2328 ) -> bool {
2329 match extra {
2331 Some(source_extra) => {
2332 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2333 return false;
2334 }
2335
2336 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2337 {
2338 return false;
2339 }
2340 }
2341 None => {
2342 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2343 return false;
2344 }
2345 }
2346 }
2347
2348 if python_marker.is_disjoint(requirement.marker) {
2351 trace!(
2352 "Skipping {requirement} because of Requires-Python: {requires_python}",
2353 requires_python = python_requirement.target(),
2354 );
2355 return false;
2356 }
2357
2358 if !env.included_by_marker(requirement.marker) {
2361 trace!("Skipping {requirement} because of {env}");
2362 return false;
2363 }
2364
2365 true
2366 }
2367
2368 fn constraints_for_requirement<'data, 'parameters>(
2371 &'data self,
2372 requirement: Cow<'data, Requirement>,
2373 extra: Option<&'parameters ExtraName>,
2374 env: &'parameters ResolverEnvironment,
2375 python_marker: MarkerTree,
2376 python_requirement: &'parameters PythonRequirement,
2377 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2378 where
2379 'data: 'parameters,
2380 {
2381 self.constraints
2382 .get(&requirement.name)
2383 .into_iter()
2384 .flatten()
2385 .filter_map(move |constraint| {
2386 let constraint = if constraint.marker.is_true() {
2389 if requirement.marker.is_true() {
2393 Cow::Borrowed(constraint)
2394 } else {
2395 let mut marker = constraint.marker;
2396 marker = marker.and(requirement.marker);
2397
2398 if marker.is_false() {
2399 trace!(
2400 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2401 constraint.marker.try_to_string().unwrap(),
2402 requirement.marker.try_to_string().unwrap(),
2403 );
2404 return None;
2405 }
2406
2407 Cow::Owned(Requirement {
2408 name: constraint.name.clone(),
2409 extras: constraint.extras.clone(),
2410 groups: constraint.groups.clone(),
2411 source: constraint.source.clone(),
2412 origin: constraint.origin.clone(),
2413 marker,
2414 })
2415 }
2416 } else {
2417 let requires_python = python_requirement.target();
2418
2419 let mut marker = constraint.marker;
2420 marker = marker.and(requirement.marker);
2421
2422 if marker.is_false() {
2423 trace!(
2424 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2425 constraint.marker.try_to_string().unwrap(),
2426 requirement.marker.try_to_string().unwrap(),
2427 );
2428 return None;
2429 }
2430
2431 if python_marker.is_disjoint(marker) {
2435 trace!(
2436 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2437 );
2438 return None;
2439 }
2440
2441 if marker == constraint.marker {
2442 Cow::Borrowed(constraint)
2443 } else {
2444 Cow::Owned(Requirement {
2445 name: constraint.name.clone(),
2446 extras: constraint.extras.clone(),
2447 groups: constraint.groups.clone(),
2448 source: constraint.source.clone(),
2449 origin: constraint.origin.clone(),
2450 marker,
2451 })
2452 }
2453 };
2454
2455 if !env.included_by_marker(constraint.marker) {
2458 trace!("Skipping {constraint} because of {env}");
2459 return None;
2460 }
2461
2462 match extra {
2464 Some(source_extra) => {
2465 if !constraint
2466 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2467 {
2468 return None;
2469 }
2470 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2471 {
2472 return None;
2473 }
2474 }
2475 None => {
2476 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2477 return None;
2478 }
2479 }
2480 }
2481
2482 Some(constraint)
2483 })
2484 }
2485
2486 async fn fetch<Provider: ResolverProvider>(
2488 self: Arc<Self>,
2489 provider: Arc<Provider>,
2490 request_stream: Receiver<Request>,
2491 ) -> Result<(), ResolveError> {
2492 let mut response_stream = ReceiverStream::new(request_stream)
2493 .map(|request| self.process_request(request, &*provider).boxed_local())
2494 .buffer_unordered(usize::MAX);
2498
2499 while let Some(response) = response_stream.next().await {
2500 match response? {
2501 Some(Response::Package(name, index, version_map)) => {
2502 trace!("Received package metadata for: {name}");
2503 if let Some(index) = index {
2504 self.index
2505 .explicit()
2506 .done((name, index), Arc::new(version_map));
2507 } else {
2508 self.index.implicit().done(name, Arc::new(version_map));
2509 }
2510 }
2511 Some(Response::Installed { dist, metadata }) => {
2512 trace!("Received installed distribution metadata for: {dist}");
2513 self.index
2514 .distributions()
2515 .done(dist.distribution_id(), Arc::new(metadata));
2516 }
2517 Some(Response::Dist { dist, metadata }) => {
2518 let dist_kind = match dist {
2519 Dist::Built(_) => "built",
2520 Dist::Source(_) => "source",
2521 };
2522 trace!("Received {dist_kind} distribution metadata for: {dist}");
2523 if let MetadataResponse::Unavailable(reason) = &metadata {
2524 let message = UnavailableVersion::from(reason).singular_message();
2525 if let Some(err) = reason.source() {
2526 warn!("{dist} {message}: {err}");
2528 } else {
2529 warn!("{dist} {message}");
2530 }
2531 }
2532 self.index
2533 .distributions()
2534 .done(dist.distribution_id(), Arc::new(metadata));
2535 }
2536 None => {}
2537 }
2538 }
2539
2540 Ok::<(), ResolveError>(())
2541 }
2542
2543 #[instrument(skip_all, fields(%request))]
2544 async fn process_request<Provider: ResolverProvider>(
2545 &self,
2546 request: Request,
2547 provider: &Provider,
2548 ) -> Result<Option<Response>, ResolveError> {
2549 match request {
2550 Request::Package(package_name, index) => {
2552 let package_versions = provider
2553 .get_package_versions(&package_name, index.as_ref())
2554 .boxed_local()
2555 .await
2556 .map_err(ResolveError::Client)?;
2557
2558 Ok(Some(Response::Package(
2559 package_name,
2560 index.map(IndexMetadata::into_url),
2561 package_versions,
2562 )))
2563 }
2564
2565 Request::Dist(dist) => {
2567 if let Some(version) = dist.version() {
2568 if let Some(index) = dist.index() {
2569 let versions_response = self.index.implicit().get(dist.name());
2571 if let Some(VersionsResponse::Found(version_maps)) =
2572 versions_response.as_deref()
2573 {
2574 for version_map in version_maps {
2575 if version_map.index() == Some(index) {
2576 let Some(metadata) = version_map.get_metadata(version) else {
2577 continue;
2578 };
2579 debug!("Found registry-provided metadata for: {dist}");
2580 return Ok(Some(Response::Dist {
2581 dist,
2582 metadata: MetadataResponse::Found(
2583 ArchiveMetadata::from_metadata23(metadata),
2584 ),
2585 }));
2586 }
2587 }
2588 }
2589
2590 let versions_response = self
2592 .index
2593 .explicit()
2594 .get(&(dist.name().clone(), index.clone()));
2595 if let Some(VersionsResponse::Found(version_maps)) =
2596 versions_response.as_deref()
2597 {
2598 for version_map in version_maps {
2599 let Some(metadata) = version_map.get_metadata(version) else {
2600 continue;
2601 };
2602 debug!("Found registry-provided metadata for: {dist}");
2603 return Ok(Some(Response::Dist {
2604 dist,
2605 metadata: MetadataResponse::Found(
2606 ArchiveMetadata::from_metadata23(metadata),
2607 ),
2608 }));
2609 }
2610 }
2611 }
2612 }
2613
2614 let metadata = provider
2615 .get_or_build_wheel_metadata(&dist)
2616 .boxed_local()
2617 .await?;
2618
2619 if let MetadataResponse::Found(metadata) = &metadata {
2620 if &metadata.metadata.name != dist.name() {
2621 return Err(ResolveError::MismatchedPackageName {
2622 request: "distribution metadata",
2623 expected: dist.name().clone(),
2624 actual: metadata.metadata.name.clone(),
2625 });
2626 }
2627 }
2628
2629 Ok(Some(Response::Dist { dist, metadata }))
2630 }
2631
2632 Request::Installed(dist) => {
2633 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2634
2635 if let MetadataResponse::Found(metadata) = &metadata {
2636 if &metadata.metadata.name != dist.name() {
2637 return Err(ResolveError::MismatchedPackageName {
2638 request: "installed metadata",
2639 expected: dist.name().clone(),
2640 actual: metadata.metadata.name.clone(),
2641 });
2642 }
2643 }
2644
2645 Ok(Some(Response::Installed { dist, metadata }))
2646 }
2647
2648 Request::Prefetch(package_name, range, python_requirement) => {
2650 let versions_response = self
2652 .index
2653 .implicit()
2654 .wait(&package_name)
2655 .await
2656 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2657
2658 let version_map = match *versions_response {
2659 VersionsResponse::Found(ref version_map) => version_map,
2660 VersionsResponse::NoIndex => {
2662 self.unavailable_packages
2663 .pin()
2664 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2665
2666 return Ok(None);
2667 }
2668 VersionsResponse::Offline => {
2669 self.unavailable_packages
2670 .pin()
2671 .insert(package_name.clone(), UnavailablePackage::Offline);
2672
2673 return Ok(None);
2674 }
2675 VersionsResponse::NotFound => {
2676 self.unavailable_packages
2677 .pin()
2678 .insert(package_name.clone(), UnavailablePackage::NotFound);
2679
2680 return Ok(None);
2681 }
2682 };
2683
2684 let env = ResolverEnvironment::universal(vec![]);
2686
2687 let Some(candidate) = self.selector.select(
2690 &package_name,
2691 &range,
2692 version_map,
2693 &self.preferences,
2694 &self.installed_packages,
2695 &self.exclusions,
2696 None,
2697 &env,
2698 self.tags.as_ref(),
2699 ) else {
2700 return Ok(None);
2701 };
2702
2703 let Some(dist) = candidate.compatible() else {
2705 return Ok(None);
2706 };
2707
2708 for version_map in version_map {
2710 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2711 let dist = dist.for_resolution();
2712 if version_map.index() == dist.index() {
2713 debug!("Found registry-provided metadata for: {dist}");
2714
2715 let metadata =
2716 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2717
2718 let dist = dist.to_owned();
2719 if &package_name != dist.name() {
2720 return Err(ResolveError::MismatchedPackageName {
2721 request: "distribution",
2722 expected: package_name,
2723 actual: dist.name().clone(),
2724 });
2725 }
2726
2727 let response = match dist {
2728 ResolvedDist::Installable { dist, .. } => Response::Dist {
2729 dist: (*dist).clone(),
2730 metadata,
2731 },
2732 ResolvedDist::Installed { dist } => Response::Installed {
2733 dist: (*dist).clone(),
2734 metadata,
2735 },
2736 };
2737
2738 return Ok(Some(response));
2739 }
2740 }
2741 }
2742
2743 if dist.wheel().is_none() {
2747 if !self.selector.use_highest_version(&package_name, &env) {
2748 if let Some((lower, _)) = range.iter().next() {
2749 if lower == Bound::Unbounded {
2750 debug!(
2751 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2752 );
2753 return Ok(None);
2754 }
2755 }
2756 }
2757 }
2758
2759 if let Some(requires_python) = dist.requires_python() {
2761 if !python_requirement.target().is_contained_by(requires_python) {
2762 return Ok(None);
2763 }
2764 }
2765
2766 if !self
2768 .hasher
2769 .allows_package(candidate.name(), candidate.version())
2770 {
2771 return Ok(None);
2772 }
2773
2774 let dist = dist.for_resolution();
2776 if self.index.distributions().register(dist.distribution_id()) {
2777 let dist = dist.to_owned();
2778 if &package_name != dist.name() {
2779 return Err(ResolveError::MismatchedPackageName {
2780 request: "distribution",
2781 expected: package_name,
2782 actual: dist.name().clone(),
2783 });
2784 }
2785
2786 let response = match dist {
2787 ResolvedDist::Installable { dist, .. } => {
2788 let metadata = provider
2789 .get_or_build_wheel_metadata(&dist)
2790 .boxed_local()
2791 .await?;
2792
2793 Response::Dist {
2794 dist: (*dist).clone(),
2795 metadata,
2796 }
2797 }
2798 ResolvedDist::Installed { dist } => {
2799 let metadata =
2800 provider.get_installed_metadata(&dist).boxed_local().await?;
2801
2802 Response::Installed {
2803 dist: (*dist).clone(),
2804 metadata,
2805 }
2806 }
2807 };
2808
2809 Ok(Some(response))
2810 } else {
2811 Ok(None)
2812 }
2813 }
2814 }
2815 }
2816
2817 fn convert_no_solution_err(
2818 &self,
2819 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2820 fork_urls: ForkUrls,
2821 fork_indexes: ForkIndexes,
2822 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2823 env: ResolverEnvironment,
2824 current_environment: MarkerEnvironment,
2825 visited: &FxHashSet<PackageName>,
2826 ) -> ResolveError {
2827 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2828 err,
2829 ));
2830 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2831
2832 let mut unavailable_packages = FxHashMap::default();
2833 for package in derivation_tree_packages(&err) {
2834 if let PubGrubPackageInner::Package { name, .. } = &**package {
2835 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2836 unavailable_packages.insert(name.clone(), reason.clone());
2837 }
2838 }
2839 }
2840
2841 let mut incomplete_packages = FxHashMap::default();
2842 let incomplete_packages_cache = self.incomplete_packages.pin();
2843 for package in derivation_tree_packages(&err) {
2844 if let PubGrubPackageInner::Package { name, .. } = &**package
2845 && let Some(versions) = incomplete_packages_cache.get(name)
2846 {
2847 for (version, reason) in &versions.pin() {
2848 incomplete_packages
2849 .entry(name.clone())
2850 .or_insert_with(BTreeMap::default)
2851 .insert(version.clone(), reason.clone());
2852 }
2853 }
2854 }
2855
2856 let mut available_indexes = FxHashMap::default();
2857 let mut included_versions = FxHashMap::default();
2858 let mut available_versions = FxHashMap::default();
2859
2860 let available_version_cutoff: Option<jiff::Timestamp> =
2861 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2862 .ok()
2863 .and_then(|s| s.parse().ok());
2864
2865 for package in derivation_tree_packages(&err) {
2866 let Some(name) = package.name() else { continue };
2867 if !visited.contains(name) {
2868 continue;
2873 }
2874 let versions_response = if let Some(index) = fork_indexes.get(name) {
2875 self.index
2876 .explicit()
2877 .get(&(name.clone(), index.url().clone()))
2878 } else {
2879 self.index.implicit().get(name)
2880 };
2881 if let Some(response) = versions_response {
2882 if let VersionsResponse::Found(ref version_maps) = *response {
2883 for version_map in version_maps {
2885 let package_included_versions = included_versions
2886 .entry(name.clone())
2887 .or_insert_with(BTreeSet::new);
2888 let package_available_versions = available_versions
2889 .entry(name.clone())
2890 .or_insert_with(BTreeSet::new);
2891
2892 for (version, dists) in version_map.iter(&Ranges::full()) {
2893 let excluded_from_included = || {
2898 let Some(included_version_cutoff) =
2899 version_map.included_version_cutoff()
2900 else {
2901 return false;
2902 };
2903 let Some(prioritized_dist) = dists.prioritized_dist() else {
2904 return true;
2905 };
2906 prioritized_dist.files().all(|file| {
2907 file.upload_time_utc_ms.is_none_or(|upload_time| {
2908 upload_time >= included_version_cutoff.as_millisecond()
2909 })
2910 })
2911 };
2912
2913 if !excluded_from_included() {
2914 package_included_versions.insert(version.clone());
2915 }
2916
2917 let excluded_from_available = || {
2923 let Some(ref exclude_newer) = available_version_cutoff else {
2924 return false;
2925 };
2926 let Some(prioritized_dist) = dists.prioritized_dist() else {
2927 return false;
2928 };
2929 prioritized_dist.files().all(|file| {
2930 file.upload_time_utc_ms.is_some_and(|upload_time| {
2931 upload_time >= exclude_newer.as_millisecond()
2932 })
2933 })
2934 };
2935
2936 if !excluded_from_available() {
2937 package_available_versions.insert(version.clone());
2938 }
2939 }
2940 }
2941
2942 available_indexes
2944 .entry(name.clone())
2945 .or_insert(BTreeSet::new())
2946 .extend(
2947 version_maps
2948 .iter()
2949 .filter_map(|version_map| version_map.index().cloned()),
2950 );
2951 }
2952 }
2953 }
2954
2955 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2956 err,
2957 self.index.clone(),
2958 included_versions,
2959 available_versions,
2960 available_indexes,
2961 self.selector.clone(),
2962 self.python_requirement.clone(),
2963 self.locations.clone(),
2964 self.capabilities.clone(),
2965 unavailable_packages,
2966 incomplete_packages,
2967 fork_urls,
2968 fork_indexes,
2969 env,
2970 current_environment,
2971 self.tags.clone(),
2972 self.workspace_members.clone(),
2973 self.options.clone(),
2974 )))
2975 }
2976
2977 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2978 if let Some(reporter) = self.reporter.as_ref() {
2979 match &**package {
2980 PubGrubPackageInner::Root(_) => {}
2981 PubGrubPackageInner::Python(_) => {}
2982 PubGrubPackageInner::System(_) => {}
2983 PubGrubPackageInner::Marker { .. } => {}
2984 PubGrubPackageInner::Extra { .. } => {}
2985 PubGrubPackageInner::Group { .. } => {}
2986 PubGrubPackageInner::Package { name, .. } => {
2987 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2988 }
2989 }
2990 }
2991 }
2992
2993 fn on_complete(&self) {
2994 if let Some(reporter) = self.reporter.as_ref() {
2995 reporter.on_complete();
2996 }
2997 }
2998}
2999
3000#[derive(Clone)]
3002pub(crate) struct ForkState {
3003 pubgrub: State<UvDependencyProvider>,
3011 initial_id: Option<Id<PubGrubPackage>>,
3014 initial_version: Option<Version>,
3017 next: Id<PubGrubPackage>,
3019 pins: FilePins,
3028 fork_urls: ForkUrls,
3034 fork_indexes: ForkIndexes,
3039 priorities: PubGrubPriorities,
3045 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3048 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3050 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3052 known_versions: FxHashMap<PackageName, Arc<[Version]>>,
3057 env: ResolverEnvironment,
3072 python_requirement: PythonRequirement,
3085 conflict_tracker: ConflictTracker,
3086 prefetcher: BatchPrefetcher,
3090}
3091
3092impl ForkState {
3093 fn new(
3094 pubgrub: State<UvDependencyProvider>,
3095 env: ResolverEnvironment,
3096 python_requirement: PythonRequirement,
3097 prefetcher: BatchPrefetcher,
3098 ) -> Self {
3099 Self {
3100 initial_id: None,
3101 initial_version: None,
3102 next: pubgrub.root_package,
3103 pubgrub,
3104 pins: FilePins::default(),
3105 fork_urls: ForkUrls::default(),
3106 fork_indexes: ForkIndexes::default(),
3107 priorities: PubGrubPriorities::default(),
3108 added_dependencies: FxHashMap::default(),
3109 pre_visited: FxHashMap::default(),
3110 selected_versions: FxHashMap::default(),
3111 known_versions: FxHashMap::default(),
3112 env,
3113 python_requirement,
3114 conflict_tracker: ConflictTracker::default(),
3115 prefetcher,
3116 }
3117 }
3118
3119 fn visit_package_version_dependencies(
3122 &mut self,
3123 for_package: Id<PubGrubPackage>,
3124 for_version: &Version,
3125 urls: &Urls,
3126 indexes: &Indexes,
3127 dependencies: &[PubGrubDependency],
3128 git: &GitResolver,
3129 workspace_members: &BTreeSet<PackageName>,
3130 resolution_strategy: &ResolutionStrategy,
3131 ) -> Result<(), ResolveError> {
3132 for dependency in dependencies {
3133 let PubGrubDependency {
3134 package,
3135 version,
3136 parent: _,
3137 source,
3138 } = dependency;
3139
3140 let mut has_url = false;
3141 if let Some(name) = package.name() {
3142 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3147 self.fork_urls.insert(name, url, &self.env)?;
3148 has_url = true;
3149 }
3150
3151 if let Some(index) = source.explicit_index() {
3152 self.fork_indexes.insert(name, index, &self.env)?;
3153 }
3154
3155 for index in indexes.get(name, &self.env) {
3157 self.fork_indexes.insert(name, index, &self.env)?;
3158 }
3159 }
3160
3161 if let Some(name) = self.pubgrub.package_store[for_package]
3162 .name_no_root()
3163 .filter(|name| !workspace_members.contains(name))
3164 {
3165 debug!(
3166 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3167 );
3168 } else {
3169 debug!("Adding direct dependency: {package}{version}");
3171
3172 let missing_lower_bound = version
3174 .bounding_range()
3175 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3176 let strategy_lowest = matches!(
3177 resolution_strategy,
3178 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3179 );
3180
3181 if !has_url && missing_lower_bound && strategy_lowest {
3182 let name = package.name_no_root().unwrap();
3183 let bound_on_other_package = dependencies.iter().any(|other| {
3190 Some(name) == other.package.name()
3191 && !other
3192 .version
3193 .bounding_range()
3194 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3195 });
3196
3197 if !bound_on_other_package {
3198 warn_user_once!(
3199 "The direct dependency `{name}` is unpinned. \
3200 Consider setting a lower bound when using `--resolution lowest` \
3201 or `--resolution lowest-direct` to avoid using outdated versions.",
3202 );
3203 }
3204 }
3205 }
3206
3207 self.priorities.insert(package, version, &self.fork_urls);
3209 if let Some(base_package) = package.base_package() {
3212 self.priorities
3213 .insert(&base_package, version, &self.fork_urls);
3214 }
3215 }
3216
3217 Ok(())
3218 }
3219
3220 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3226 &mut self,
3227 for_package: Id<PubGrubPackage>,
3228 for_version: &Version,
3229 dependencies: Vec<PubGrubDependency>,
3230 index: &InMemoryIndex,
3231 installed_packages: &InstalledPackages,
3232 ) {
3233 for dependency in &dependencies {
3234 let PubGrubDependency {
3235 package,
3236 version,
3237 parent: _,
3238 source: _,
3239 } = dependency;
3240
3241 let Some(base_package) = package.base_package() else {
3242 continue;
3243 };
3244
3245 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3246 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3247 self.pubgrub.add_proxy_package_incompatibility(
3248 proxy_package,
3249 base_package_id,
3250 version.clone(),
3251 );
3252 }
3253
3254 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
3257 let conflict = self.pubgrub.add_package_version_dependencies(
3258 self.next,
3259 for_version.clone(),
3260 versions,
3261 dependencies.into_iter().map(|dependency| {
3262 let PubGrubDependency {
3263 package,
3264 version,
3265 parent: _,
3266 source: _,
3267 } = dependency;
3268 (package, version)
3269 }),
3270 );
3271
3272 if let Some(incompatibility) = conflict {
3275 self.record_conflict(for_package, Some(for_version), incompatibility);
3276 }
3277 }
3278
3279 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
3281 &mut self,
3282 version: &Version,
3283 index: &InMemoryIndex,
3284 installed_packages: &InstalledPackages,
3285 ) -> Range<Version> {
3286 widen_to_gap(
3287 version,
3288 ResolverState::<InstalledPackages>::known_versions(
3289 index,
3290 installed_packages,
3291 &self.fork_urls,
3292 &self.fork_indexes,
3293 &mut self.known_versions,
3294 &self.pubgrub.package_store[self.next],
3295 ),
3296 )
3297 }
3298
3299 fn record_conflict(
3300 &mut self,
3301 affected: Id<PubGrubPackage>,
3302 version: Option<&Version>,
3303 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3304 ) {
3305 let mut culprit_is_real = false;
3306 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3307 if incompatible == affected {
3308 continue;
3309 }
3310 if self.pubgrub.package_store[affected].name()
3311 == self.pubgrub.package_store[incompatible].name()
3312 {
3313 continue;
3316 }
3317 culprit_is_real = true;
3318 let culprit_count = self
3319 .conflict_tracker
3320 .culprit
3321 .entry(incompatible)
3322 .or_default();
3323 *culprit_count += 1;
3324 if *culprit_count == CONFLICT_THRESHOLD {
3325 self.conflict_tracker.deprioritize.push(incompatible);
3326 }
3327 }
3328 if culprit_is_real {
3331 if tracing::enabled!(Level::DEBUG) {
3332 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3333 .iter()
3334 .map(|(package, _term)| &self.pubgrub.package_store[package])
3335 .join(", ");
3336 if let Some(version) = version {
3337 debug!(
3338 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3339 self.pubgrub.package_store[affected], version, incompatibility
3340 );
3341 } else {
3342 debug!(
3343 "Recording unit propagation conflict of {} from incompatibility of ({})",
3344 self.pubgrub.package_store[affected], incompatibility
3345 );
3346 }
3347 }
3348
3349 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3350 *affected_count += 1;
3351 if *affected_count == CONFLICT_THRESHOLD {
3352 self.conflict_tracker.prioritize.push(self.next);
3353 }
3354 }
3355 }
3356
3357 fn reprioritize_conflicts(&mut self) {
3361 for package in self.conflict_tracker.prioritize.drain(..) {
3362 let changed = self
3363 .priorities
3364 .mark_conflict_early(&self.pubgrub.package_store[package]);
3365 if changed {
3366 debug!(
3367 "Package {} has too many conflicts (affected), prioritizing",
3368 &self.pubgrub.package_store[package]
3369 );
3370 } else {
3371 debug!(
3372 "Package {} has too many conflicts (affected), already {:?}",
3373 self.pubgrub.package_store[package],
3374 self.priorities.get(&self.pubgrub.package_store[package])
3375 );
3376 }
3377 }
3378
3379 for package in self.conflict_tracker.deprioritize.drain(..) {
3380 let changed = self
3381 .priorities
3382 .mark_conflict_late(&self.pubgrub.package_store[package]);
3383 if changed {
3384 debug!(
3385 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3386 self.pubgrub.package_store[package],
3387 );
3388 let backtrack_level = self.pubgrub.backtrack_package(package);
3389 if let Some(backtrack_level) = backtrack_level {
3390 debug!("Backtracked {backtrack_level} decisions");
3391 } else {
3392 debug!(
3393 "Package {} is not decided, cannot backtrack",
3394 self.pubgrub.package_store[package]
3395 );
3396 }
3397 } else {
3398 debug!(
3399 "Package {} has too many conflicts (culprit), already {:?}",
3400 self.pubgrub.package_store[package],
3401 self.priorities.get(&self.pubgrub.package_store[package])
3402 );
3403 }
3404 }
3405 }
3406
3407 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3415 &mut self,
3416 version: Version,
3417 reason: UnavailableVersion,
3418 index: &InMemoryIndex,
3419 installed_packages: &InstalledPackages,
3420 ) {
3421 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3422
3423 if let UnavailableVersion::IncompatibleDist(
3427 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3428 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3429 ) = reason
3430 {
3431 let package = &self.next;
3432 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3433 PubGrubPackageInner::Python(match kind {
3434 PythonRequirementKind::Installed => PubGrubPython::Installed,
3435 PythonRequirementKind::Target => PubGrubPython::Target,
3436 }),
3437 ));
3438 self.pubgrub
3439 .add_incompatibility(Incompatibility::from_dependency(
3440 *package,
3441 versions,
3442 (
3443 python,
3444 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3445 ),
3446 ));
3447 self.pubgrub
3448 .partial_solution
3449 .add_decision(self.next, version);
3450 return;
3451 }
3452 self.pubgrub
3453 .add_incompatibility(Incompatibility::custom_term(
3454 self.next,
3455 Term::Positive(versions),
3456 UnavailableReason::Version(reason),
3457 ));
3458 }
3459
3460 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3466 self.selected_versions.clear();
3467 self.env = env;
3468 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3470 debug!("Narrowed `requires-python` bound to: {}", req.target());
3471 self.python_requirement = req;
3472 }
3473 self
3474 }
3475
3476 fn source(
3480 &self,
3481 name: &PackageName,
3482 version: &Version,
3483 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3484 let url = self.fork_urls.get(name);
3485 let index = url
3486 .is_none()
3487 .then(|| {
3488 self.pins
3489 .get(name, version)
3490 .expect("Every package should be pinned")
3491 .index()
3492 })
3493 .flatten();
3494 (url, index)
3495 }
3496
3497 fn into_resolution(self) -> Resolution {
3498 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3499 let edge_count: usize = solution
3500 .keys()
3501 .map(|package| self.pubgrub.incompatibilities[package].len())
3502 .sum();
3503 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3504 for (package, self_version) in &solution {
3505 for id in &self.pubgrub.incompatibilities[package] {
3506 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3507 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3508 &incompatibility.kind
3509 else {
3510 continue;
3511 };
3512 let (self_package, dependency_package) = (*self_package, *dependency_package);
3513 let Some((self_range, dependency_range)) =
3514 incompatibility.dependency_version_sets()
3515 else {
3516 continue;
3517 };
3518 let dependency_range =
3519 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3520 if *package != self_package {
3521 continue;
3522 }
3523 if !self_range.contains(self_version) {
3524 continue;
3525 }
3526 let Some(dependency_version) = solution.get(&dependency_package) else {
3527 continue;
3528 };
3529 if !dependency_range.contains(dependency_version) {
3530 continue;
3531 }
3532
3533 let self_package = &self.pubgrub.package_store[self_package];
3534 let dependency_package = &self.pubgrub.package_store[dependency_package];
3535
3536 let (self_name, self_extra, self_group) = match &**self_package {
3537 PubGrubPackageInner::Package {
3538 name: self_name,
3539 extra: self_extra,
3540 group: self_group,
3541 marker: _,
3542 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3543
3544 PubGrubPackageInner::Root(_) => (None, None, None),
3545
3546 _ => continue,
3547 };
3548
3549 let (self_url, self_index) = self_name
3550 .map(|self_name| self.source(self_name, self_version))
3551 .unwrap_or((None, None));
3552
3553 match **dependency_package {
3554 PubGrubPackageInner::Package {
3555 name: ref dependency_name,
3556 extra: ref dependency_extra,
3557 group: ref dependency_dev,
3558 marker: ref dependency_marker,
3559 } => {
3560 debug_assert!(
3561 dependency_extra.is_none(),
3562 "Packages should depend on an extra proxy"
3563 );
3564 debug_assert!(
3565 dependency_dev.is_none(),
3566 "Packages should depend on a group proxy"
3567 );
3568
3569 if self_group.is_none() {
3572 if self_name == Some(dependency_name) {
3573 continue;
3574 }
3575 }
3576
3577 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3578
3579 let edge = ResolutionDependencyEdge {
3580 from: self_name.cloned(),
3581 from_version: self_version.clone(),
3582 from_url: self_url.cloned(),
3583 from_index: self_index.cloned(),
3584 from_extra: self_extra.cloned(),
3585 from_group: self_group.cloned(),
3586 to: dependency_name.clone(),
3587 to_version: dependency_version.clone(),
3588 to_url: to_url.cloned(),
3589 to_index: to_index.cloned(),
3590 to_extra: dependency_extra.clone(),
3591 to_group: dependency_dev.clone(),
3592 marker: *dependency_marker,
3593 };
3594 edges.push(edge);
3595 }
3596
3597 PubGrubPackageInner::Marker {
3598 name: ref dependency_name,
3599 marker: ref dependency_marker,
3600 } => {
3601 if self_group.is_none() {
3604 if self_name == Some(dependency_name) {
3605 continue;
3606 }
3607 }
3608
3609 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3610
3611 let edge = ResolutionDependencyEdge {
3612 from: self_name.cloned(),
3613 from_version: self_version.clone(),
3614 from_url: self_url.cloned(),
3615 from_index: self_index.cloned(),
3616 from_extra: self_extra.cloned(),
3617 from_group: self_group.cloned(),
3618 to: dependency_name.clone(),
3619 to_version: dependency_version.clone(),
3620 to_url: to_url.cloned(),
3621 to_index: to_index.cloned(),
3622 to_extra: None,
3623 to_group: None,
3624 marker: *dependency_marker,
3625 };
3626 edges.push(edge);
3627 }
3628
3629 PubGrubPackageInner::Extra {
3630 name: ref dependency_name,
3631 extra: ref dependency_extra,
3632 marker: ref dependency_marker,
3633 } => {
3634 if self_group.is_none() {
3635 debug_assert!(
3636 self_name != Some(dependency_name),
3637 "Extras should be flattened"
3638 );
3639 }
3640 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3641
3642 let edge = ResolutionDependencyEdge {
3644 from: self_name.cloned(),
3645 from_version: self_version.clone(),
3646 from_url: self_url.cloned(),
3647 from_index: self_index.cloned(),
3648 from_extra: self_extra.cloned(),
3649 from_group: self_group.cloned(),
3650 to: dependency_name.clone(),
3651 to_version: dependency_version.clone(),
3652 to_url: to_url.cloned(),
3653 to_index: to_index.cloned(),
3654 to_extra: Some(dependency_extra.clone()),
3655 to_group: None,
3656 marker: *dependency_marker,
3657 };
3658 edges.push(edge);
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: None,
3673 to_group: None,
3674 marker: *dependency_marker,
3675 };
3676 edges.push(edge);
3677 }
3678
3679 PubGrubPackageInner::Group {
3680 name: ref dependency_name,
3681 group: ref dependency_group,
3682 marker: ref dependency_marker,
3683 } => {
3684 debug_assert!(
3685 self_name != Some(dependency_name),
3686 "Groups should be flattened"
3687 );
3688
3689 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3690
3691 let edge = ResolutionDependencyEdge {
3694 from: self_name.cloned(),
3695 from_version: self_version.clone(),
3696 from_url: self_url.cloned(),
3697 from_index: self_index.cloned(),
3698 from_extra: self_extra.cloned(),
3699 from_group: self_group.cloned(),
3700 to: dependency_name.clone(),
3701 to_version: dependency_version.clone(),
3702 to_url: to_url.cloned(),
3703 to_index: to_index.cloned(),
3704 to_extra: None,
3705 to_group: Some(dependency_group.clone()),
3706 marker: *dependency_marker,
3707 };
3708 edges.push(edge);
3709 }
3710
3711 _ => {}
3712 }
3713 }
3714 }
3715
3716 let nodes = solution
3717 .into_iter()
3718 .filter_map(|(package, version)| {
3719 if let PubGrubPackageInner::Package {
3720 name,
3721 extra,
3722 group,
3723 marker: MarkerTree::TRUE,
3724 } = &*self.pubgrub.package_store[package]
3725 {
3726 let (url, index) = self.source(name, &version);
3727 Some((
3728 ResolutionPackage {
3729 name: name.clone(),
3730 extra: extra.clone(),
3731 dev: group.clone(),
3732 url: url.cloned(),
3733 index: index.cloned(),
3734 },
3735 version,
3736 ))
3737 } else {
3738 None
3739 }
3740 })
3741 .collect();
3742
3743 Resolution {
3744 nodes,
3745 edges,
3746 pins: self.pins,
3747 env: self.env,
3748 }
3749 }
3750}
3751
3752fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3761 let versions = Range::singleton(version.clone());
3762 match known_versions {
3763 Some(known_versions) if !known_versions.is_empty() => {
3764 versions.widen_versions(known_versions)
3765 }
3766 _ => versions,
3767 }
3768}
3769
3770#[derive(Debug)]
3772#[expect(clippy::large_enum_variant)]
3773pub(crate) enum Request {
3774 Package(PackageName, Option<IndexMetadata>),
3776 Dist(Dist),
3778 Installed(InstalledDist),
3780 Prefetch(PackageName, Range<Version>, PythonRequirement),
3782}
3783
3784impl<'a> From<ResolvedDistRef<'a>> for Request {
3785 fn from(dist: ResolvedDistRef<'a>) -> Self {
3786 match dist {
3792 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3793 let source = prioritized.source_dist().expect("a source distribution");
3796 assert_eq!(
3797 (&sdist.name, &sdist.version),
3798 (&source.name, &source.version),
3799 "expected chosen sdist to match prioritized sdist"
3800 );
3801 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3802 }
3803 ResolvedDistRef::InstallableRegistryBuiltDist {
3804 wheel, prioritized, ..
3805 } => {
3806 assert_eq!(
3807 Some(&wheel.filename),
3808 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3809 "expected chosen wheel to match best wheel"
3810 );
3811 let built = prioritized.built_dist().expect("at least one wheel");
3814 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3815 }
3816 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3817 }
3818 }
3819}
3820
3821impl Display for Request {
3822 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3823 match self {
3824 Self::Package(package_name, _) => {
3825 write!(f, "Versions {package_name}")
3826 }
3827 Self::Dist(dist) => {
3828 write!(f, "Metadata {dist}")
3829 }
3830 Self::Installed(dist) => {
3831 write!(f, "Installed metadata {dist}")
3832 }
3833 Self::Prefetch(package_name, range, _) => {
3834 write!(f, "Prefetch {package_name} {range}")
3835 }
3836 }
3837 }
3838}
3839
3840#[derive(Debug)]
3841#[expect(clippy::large_enum_variant)]
3842enum Response {
3843 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3845 Dist {
3847 dist: Dist,
3848 metadata: MetadataResponse,
3849 },
3850 Installed {
3852 dist: InstalledDist,
3853 metadata: MetadataResponse,
3854 },
3855}
3856
3857enum Dependencies {
3863 Unavailable(UnavailableVersion),
3865 Available(Vec<PubGrubDependency>),
3871 RequiresPython(VersionSpecifiers),
3873 Unforkable(Vec<PubGrubDependency>),
3879}
3880
3881#[derive(Debug)]
3888enum ForkedDependencies {
3889 Unavailable(UnavailableVersion),
3891 Unforked(Vec<PubGrubDependency>),
3895 Forked {
3901 forks: Vec<Fork>,
3902 diverging_packages: BTreeSet<PackageName>,
3904 },
3905 RequiresPython(VersionSpecifiers),
3907}
3908
3909impl ForkedDependencies {
3910 fn from_dependencies_universal(
3917 dependencies: Dependencies,
3918 env: &ResolverEnvironment,
3919 python_requirement: &PythonRequirement,
3920 conflicts: &Conflicts,
3921 ) -> Self {
3922 let deps = match dependencies {
3923 Dependencies::Available(deps) => deps,
3924 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3925 Dependencies::RequiresPython(requires_python) => {
3926 return Self::RequiresPython(requires_python);
3927 }
3928 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3929 };
3930 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3931 for dep in deps {
3932 let name = dep
3933 .package
3934 .name()
3935 .expect("dependency always has a name")
3936 .clone();
3937 name_to_deps.entry(name).or_default().push(dep);
3938 }
3939 let (mut forks, diverging_packages) =
3940 Self::fork(name_to_deps, env, python_requirement, conflicts);
3941 if forks.is_empty() {
3942 Self::Unforked(vec![])
3943 } else if forks.len() == 1 {
3944 Self::Unforked(forks.pop().unwrap().dependencies)
3945 } else {
3946 Self::Forked {
3947 forks,
3948 diverging_packages,
3949 }
3950 }
3951 }
3952
3953 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3956 match dependencies {
3957 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3958 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3959 Dependencies::Unavailable(err) => Self::Unavailable(err),
3960 }
3961 }
3962
3963 fn fork(
3971 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3972 env: &ResolverEnvironment,
3973 python_requirement: &PythonRequirement,
3974 conflicts: &Conflicts,
3975 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3976 let python_marker = python_requirement.to_marker_tree();
3977
3978 let mut forks = vec![Fork::new(env.clone())];
3979 let mut diverging_packages = BTreeSet::new();
3980 for (name, mut deps) in name_to_deps {
3981 assert!(!deps.is_empty(), "every name has at least one dependency");
3982 if let [dep] = deps.as_slice() {
3993 if marker::requires_python(dep.package.marker())
4001 .is_none_or(|bound| !python_requirement.raises(&bound))
4002 {
4003 let dep = deps.pop().unwrap();
4004 let marker = dep.package.marker();
4005 for fork in &mut forks {
4006 if fork.env.included_by_marker(marker) {
4007 fork.add_dependency(dep.clone());
4008 }
4009 }
4010 continue;
4011 }
4012 } else {
4013 if let Some(dep) = deps.first() {
4015 let marker = dep.package.marker();
4016 if deps.iter().all(|dep| marker == dep.package.marker()) {
4017 if marker::requires_python(marker)
4021 .is_none_or(|bound| !python_requirement.raises(&bound))
4022 {
4023 for dep in deps {
4024 for fork in &mut forks {
4025 if fork.env.included_by_marker(marker) {
4026 fork.add_dependency(dep.clone());
4027 }
4028 }
4029 }
4030 continue;
4031 }
4032 }
4033 }
4034 }
4035 for dep in deps {
4036 let mut forker = match ForkingPossibility::new(env, &dep) {
4037 ForkingPossibility::Possible(forker) => forker,
4038 ForkingPossibility::DependencyAlwaysExcluded => {
4039 continue;
4042 }
4043 ForkingPossibility::NoForkingPossible => {
4044 for fork in &mut forks {
4047 fork.add_dependency(dep.clone());
4048 }
4049 continue;
4050 }
4051 };
4052 diverging_packages.insert(name.clone());
4054
4055 let mut new = vec![];
4056 for fork in std::mem::take(&mut forks) {
4057 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4058 new.push(fork);
4059 continue;
4060 };
4061 forker = remaining_forker;
4062
4063 for fork_env in envs {
4064 let mut new_fork = fork.clone();
4065 new_fork.set_env(fork_env);
4066 if forker.included(&new_fork.env) {
4071 new_fork.add_dependency(dep.clone());
4072 }
4073 if new_fork.env.included_by_marker(python_marker) {
4076 new.push(new_fork);
4077 }
4078 }
4079 }
4080 forks = new;
4081 }
4082 }
4083 for set in conflicts.iter() {
4098 let mut new = vec![];
4099 for fork in std::mem::take(&mut forks) {
4100 let mut has_conflicting_dependency = false;
4108 for item in set.iter() {
4109 if fork.contains_conflicting_item(item.as_ref()) {
4110 has_conflicting_dependency = true;
4111 diverging_packages.insert(item.package().clone());
4112 break;
4113 }
4114 }
4115 if !has_conflicting_dependency {
4116 new.push(fork);
4117 continue;
4118 }
4119
4120 let non_excluded: Vec<_> = set
4126 .iter()
4127 .filter(|item| fork.env.included_by_group(item.as_ref()))
4128 .collect();
4129 if non_excluded.len() < 2 {
4130 let dominated = non_excluded.iter().all(|item| {
4135 !conflicts.iter().any(|other_set| {
4136 !std::ptr::eq(set, other_set)
4137 && other_set.contains(item.package(), item.kind().as_ref())
4138 && other_set
4139 .iter()
4140 .filter(|other_item| {
4141 other_item.package() != item.package()
4142 || other_item.kind() != item.kind()
4143 })
4144 .any(|other_item| {
4145 fork.env.included_by_group(other_item.as_ref())
4146 })
4147 })
4148 });
4149 if dominated {
4150 let rules: Vec<_> = set
4155 .iter()
4156 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4157 .cloned()
4158 .map(Err)
4159 .collect();
4160 if let Some(filtered) = fork.filter(rules) {
4161 new.push(filtered);
4162 }
4163 continue;
4164 }
4165 }
4166
4167 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4169 new.push(fork_none);
4170 }
4171
4172 for (i, _) in set.iter().enumerate() {
4180 let fork_allows_group = fork.clone().filter(
4181 set.iter()
4182 .cloned()
4183 .enumerate()
4184 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4185 );
4186 if let Some(fork_allows_group) = fork_allows_group {
4187 new.push(fork_allows_group);
4188 }
4189 }
4190 }
4191 forks = new;
4192 }
4193 (forks, diverging_packages)
4194 }
4195}
4196
4197#[derive(Clone, Debug)]
4207struct Fork {
4208 dependencies: Vec<PubGrubDependency>,
4217 conflicts: crate::FxHashbrownSet<ConflictItem>,
4223 env: ResolverEnvironment,
4235}
4236
4237impl Fork {
4238 fn new(env: ResolverEnvironment) -> Self {
4241 Self {
4242 dependencies: vec![],
4243 conflicts: crate::FxHashbrownSet::default(),
4244 env,
4245 }
4246 }
4247
4248 fn add_dependency(&mut self, dep: PubGrubDependency) {
4250 if let Some(conflicting_item) = dep.conflicting_item() {
4251 self.conflicts.insert(conflicting_item.to_owned());
4252 }
4253 self.dependencies.push(dep);
4254 }
4255
4256 fn set_env(&mut self, env: ResolverEnvironment) {
4261 self.env = env;
4262 self.dependencies.retain(|dep| {
4263 let marker = dep.package.marker();
4264 if self.env.included_by_marker(marker) {
4265 return true;
4266 }
4267 if let Some(conflicting_item) = dep.conflicting_item() {
4268 self.conflicts.remove(&conflicting_item);
4269 }
4270 false
4271 });
4272 }
4273
4274 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4277 self.conflicts.contains(&item)
4278 }
4279
4280 fn filter(
4287 mut self,
4288 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4289 ) -> Option<Self> {
4290 self.env = self.env.filter_by_group(rules)?;
4291 self.dependencies.retain(|dep| {
4292 let Some(conflicting_item) = dep.conflicting_item() else {
4293 return true;
4294 };
4295 if self.env.included_by_group(conflicting_item) {
4296 return true;
4297 }
4298 match conflicting_item.kind() {
4299 ConflictKindRef::Project => {
4302 if dep.parent.is_some() {
4303 return true;
4304 }
4305 }
4306 ConflictKindRef::Group(_) => {}
4307 ConflictKindRef::Extra(_) => {}
4308 }
4309 self.conflicts.remove(&conflicting_item);
4310 false
4311 });
4312 Some(self)
4313 }
4314
4315 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4317 cmp_requires_python(&self.env, &other.env)
4318 }
4319
4320 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4322 let self_upper_bounds = self
4327 .dependencies
4328 .iter()
4329 .filter(|dep| {
4330 dep.version
4331 .bounding_range()
4332 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4333 })
4334 .count();
4335 let other_upper_bounds = other
4336 .dependencies
4337 .iter()
4338 .filter(|dep| {
4339 dep.version
4340 .bounding_range()
4341 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4342 })
4343 .count();
4344
4345 self_upper_bounds.cmp(&other_upper_bounds)
4346 }
4347}
4348
4349fn cmp_requires_python(
4351 self_env: &ResolverEnvironment,
4352 other_env: &ResolverEnvironment,
4353) -> Ordering {
4354 let self_bound = self_env.requires_python().unwrap_or_default();
4364 let other_bound = other_env.requires_python().unwrap_or_default();
4365 self_bound.lower().cmp(other_bound.lower())
4366}
4367
4368impl Eq for Fork {}
4369
4370impl PartialEq for Fork {
4371 fn eq(&self, other: &Self) -> bool {
4372 self.dependencies == other.dependencies && self.env == other.env
4373 }
4374}
4375
4376#[derive(Debug, Clone)]
4377pub(crate) struct VersionFork {
4378 env: ResolverEnvironment,
4380 id: Id<PubGrubPackage>,
4382 version: Option<Version>,
4384}
4385
4386fn enrich_dependency_error(
4388 error: ResolveError,
4389 id: Id<PubGrubPackage>,
4390 version: &Version,
4391 pubgrub: &State<UvDependencyProvider>,
4392) -> ResolveError {
4393 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4394 return error;
4395 };
4396 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4397 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4398}
4399
4400fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4402 let package = &state.package_store[id];
4403 if package.is_root() {
4404 return MarkerTree::TRUE;
4405 }
4406
4407 let mut ancestors = FxHashSet::default();
4410 let mut stack = vec![id];
4411 let mut root = None;
4412 ancestors.insert(id);
4413
4414 while let Some(current) = stack.pop() {
4415 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4416 continue;
4417 };
4418
4419 for index in incompatibilities {
4420 let incompat = &state.incompatibility_store[*index];
4421 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4422 if current != *child {
4423 continue;
4424 }
4425 if ancestors.insert(*parent) {
4426 if state.package_store[*parent].is_root() {
4427 root = Some(*parent);
4428 }
4429 stack.push(*parent);
4430 }
4431 }
4432 }
4433 }
4434
4435 let Some(root) = root else {
4436 return MarkerTree::FALSE;
4437 };
4438
4439 let mut environments = FxHashMap::default();
4442 let mut queue = VecDeque::from([root]);
4443 environments.insert(root, MarkerTree::TRUE);
4444
4445 while let Some(current) = queue.pop_front() {
4446 let Some(current_environment) = environments.get(¤t).copied() else {
4447 continue;
4448 };
4449 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4450 continue;
4451 };
4452
4453 for index in incompatibilities {
4454 let incompat = &state.incompatibility_store[*index];
4455 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4456 continue;
4457 };
4458 if current != *parent || !ancestors.contains(child) {
4459 continue;
4460 }
4461
4462 let mut next_environment = state.package_store[*child].marker();
4463 next_environment = next_environment.and(current_environment);
4464
4465 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4466 let mut combined = *entry;
4467 combined = combined.or(next_environment);
4468 if combined != *entry {
4469 *entry = combined;
4470 queue.push_back(*child);
4471 }
4472 }
4473 }
4474
4475 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4476}
4477
4478#[derive(Debug, Default, Clone)]
4479struct ConflictTracker {
4480 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4482 prioritize: Vec<Id<PubGrubPackage>>,
4486 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4488 deprioritize: Vec<Id<PubGrubPackage>>,
4492}
4493
4494#[cfg(test)]
4495mod tests {
4496 use super::*;
4497
4498 fn versions(versions: &[&str]) -> Vec<Version> {
4499 versions
4500 .iter()
4501 .map(|version| version.parse().expect("valid version"))
4502 .collect()
4503 }
4504
4505 #[test]
4506 fn widens_a_version_to_its_gap() {
4507 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4508 let version: Version = "2.0".parse().expect("valid version");
4509
4510 assert_eq!(
4512 widen_to_gap(&version, Some(&known_versions)).to_string(),
4513 ">1.0, <3.0"
4514 );
4515
4516 let version: Version = "1.0".parse().expect("valid version");
4518 assert_eq!(
4519 widen_to_gap(&version, Some(&known_versions)).to_string(),
4520 "<2.0"
4521 );
4522 let version: Version = "3.0".parse().expect("valid version");
4523 assert_eq!(
4524 widen_to_gap(&version, Some(&known_versions)).to_string(),
4525 ">2.0"
4526 );
4527 }
4528
4529 #[test]
4530 fn widens_a_version_without_known_versions_to_itself() {
4531 let version: Version = "2.0".parse().expect("valid version");
4532
4533 assert_eq!(
4535 widen_to_gap(&version, None),
4536 Range::singleton(version.clone())
4537 );
4538
4539 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4541 }
4542}