1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{mem, thread};
11
12use futures::{FutureExt, StreamExt};
13use itertools::Itertools;
14use papaya::{HashMap, ResizeMode};
15use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
16use rustc_hash::{FxHashMap, FxHashSet};
17use tokio::sync::mpsc::{self, Receiver};
18use tokio::sync::oneshot;
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{Level, debug, info, instrument, trace, warn};
21
22use uv_configuration::{Constraints, Excludes, Overrides};
23use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24use uv_distribution_types::{
25 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
26 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
27 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist,
28 ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
29};
30use uv_git::GitResolver;
31use uv_normalize::PackageName;
32use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
33use uv_pep508::{
34 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
35};
36use uv_platform_tags::{IncompatibleTag, Tags};
37use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
38use uv_resolver_types::PackageNodeKind;
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_urls::ForkUrls;
49use crate::manifest::Manifest;
50use crate::pins::FilePins;
51use crate::preferences::{PreferenceSource, Preferences};
52use crate::pubgrub::{
53 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
54 PubGrubPython, Range,
55};
56use crate::python_requirement::PythonRequirement;
57use crate::resolution::ResolverOutput;
58use crate::resolution_mode::ResolutionStrategy;
59pub(crate) use crate::resolver::availability::{
60 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
61 UnavailableVersion, UnsatisfiableRequirement,
62};
63use crate::resolver::batch_prefetch::BatchPrefetcher;
64use crate::resolver::derivation::DerivationChainBuilder;
65pub use crate::resolver::environment::ResolverEnvironment;
66use crate::resolver::environment::{
67 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
68};
69pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
70pub use crate::resolver::index::InMemoryIndex;
71use crate::resolver::indexes::Indexes;
72use crate::resolver::package_source::PackageSource;
73pub use crate::resolver::provider::{
74 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
75 VersionsResponse, WheelMetadataResult,
76};
77pub use crate::resolver::reporter::Reporter;
78pub(crate) use crate::resolver::requests::RegisteredMetadata;
79use crate::resolver::requests::{MetadataRequest, MetadataRequests};
80use crate::resolver::requirements::{RequirementContext, RequirementExpander};
81use crate::resolver::system::SystemDependency;
82pub(crate) use crate::resolver::urls::Urls;
83use crate::universal_marker::UniversalMarker;
84use crate::yanks::AllowedYanks;
85use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
86pub(crate) use provider::MetadataUnavailable;
87pub(crate) use resolution::{
88 Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionPackage, ResolvedFork,
89 SelectedDistribution,
90};
91use uv_configuration::ForkStrategy;
92
93mod availability;
94mod batch_prefetch;
95mod derivation;
96mod environment;
97mod fork_map;
98mod index;
99mod indexes;
100mod package_source;
101mod provider;
102mod reporter;
103mod requests;
104mod requirements;
105mod resolution;
106mod system;
107mod urls;
108
109const CONFLICT_THRESHOLD: usize = 5;
111
112pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
113 state: ResolverState<InstalledPackages>,
114 provider: Provider,
115}
116
117struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
120 project: Option<PackageName>,
121 requirements: Vec<Requirement>,
122 constraints: Constraints,
123 overrides: Overrides,
124 excludes: Excludes,
125 preferences: Preferences,
126 git: GitResolver,
127 capabilities: IndexCapabilities,
128 locations: IndexLocations,
129 exclusions: Exclusions,
130 urls: Urls,
131 indexes: Indexes,
132 dependency_mode: DependencyMode,
133 hasher: HashStrategy,
134 env: ResolverEnvironment,
135 current_environment: MarkerEnvironment,
137 tags: Option<Tags>,
138 python_requirement: PythonRequirement,
139 conflicts: Conflicts,
140 workspace_members: BTreeSet<PackageName>,
141 selector: CandidateSelector,
142 index: InMemoryIndex,
143 installed_packages: InstalledPackages,
144 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
147 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
149 options: Options,
151 reporter: Option<Arc<dyn Reporter>>,
153}
154
155impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
156 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
157{
158 pub fn new(
177 manifest: Manifest,
178 options: Options,
179 python_requirement: &'a PythonRequirement,
180 env: ResolverEnvironment,
181 current_environment: &MarkerEnvironment,
182 conflicts: Conflicts,
183 tags: Option<&'a Tags>,
184 flat_index: &'a FlatIndex,
185 index: &'a InMemoryIndex,
186 hasher: &'a HashStrategy,
187 build_context: &'a Context,
188 installed_packages: InstalledPackages,
189 database: DistributionDatabase<'a, Context>,
190 ) -> Result<Self, ResolveError> {
191 let provider = DefaultResolverProvider::new(
192 database,
193 flat_index,
194 tags,
195 python_requirement.target(),
196 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
197 hasher,
198 options.exclude_newer.clone(),
199 build_context.locations(),
200 build_context.build_options(),
201 build_context.capabilities(),
202 options.minimum_libc_version,
203 );
204
205 Ok(Self::new_custom_io(
206 manifest,
207 options,
208 hasher,
209 env,
210 current_environment,
211 tags.cloned(),
212 python_requirement,
213 conflicts,
214 index,
215 build_context.git(),
216 build_context.capabilities(),
217 build_context.locations(),
218 provider,
219 installed_packages,
220 ))
221 }
222}
223
224impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
225 Resolver<Provider, InstalledPackages>
226{
227 pub fn new_custom_io(
229 manifest: Manifest,
230 options: Options,
231 hasher: &HashStrategy,
232 env: ResolverEnvironment,
233 current_environment: &MarkerEnvironment,
234 tags: Option<Tags>,
235 python_requirement: &PythonRequirement,
236 conflicts: Conflicts,
237 index: &InMemoryIndex,
238 git: &GitResolver,
239 capabilities: &IndexCapabilities,
240 locations: &IndexLocations,
241 provider: Provider,
242 installed_packages: InstalledPackages,
243 ) -> Self {
244 let state = ResolverState {
245 index: index.clone(),
246 git: git.clone(),
247 capabilities: capabilities.clone(),
248 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
249 dependency_mode: options.dependency_mode,
250 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
251 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
252 project: manifest.project,
253 workspace_members: manifest.workspace_members,
254 requirements: manifest.requirements,
255 constraints: manifest.constraints,
256 overrides: manifest.overrides,
257 excludes: manifest.excludes,
258 preferences: manifest.preferences,
259 exclusions: manifest.exclusions,
260 hasher: hasher.clone(),
261 locations: locations.clone(),
262 env,
263 current_environment: current_environment.clone(),
264 tags,
265 python_requirement: python_requirement.clone(),
266 conflicts,
267 installed_packages,
268 unavailable_packages: Box::default(),
269 incomplete_packages: Box::default(),
270 options,
271 reporter: None,
272 };
273 Self { state, provider }
274 }
275
276 #[must_use]
278 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
279 Self {
280 state: ResolverState {
281 reporter: Some(reporter.clone()),
282 ..self.state
283 },
284 provider: self
285 .provider
286 .with_reporter(reporter.into_distribution_reporter()),
287 }
288 }
289
290 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
292 let state = Arc::new(self.state);
293 let provider = Arc::new(self.provider);
294
295 let (request_sink, request_stream) = mpsc::channel(300);
299 let requests = MetadataRequests::new(state.index.clone(), request_sink);
300
301 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
303
304 let solver = state.clone();
306 let (tx, rx) = oneshot::channel();
307 thread::Builder::new()
308 .name("uv-resolver".into())
309 .spawn(move || {
310 let result = solver.solve(&requests);
311
312 let _ = tx.send(result);
314 })
315 .unwrap();
316
317 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
318
319 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
321
322 state.on_complete();
323 resolution
324 }
325}
326
327impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
328 #[instrument(skip_all)]
329 fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
330 debug!(
331 "Solving with installed Python version: {}",
332 self.python_requirement.exact()
333 );
334 debug!(
335 "Solving with target Python version: {}",
336 self.python_requirement.target()
337 );
338 if !self.options.exclude_newer.is_empty() {
339 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
340 }
341
342 let mut visited = FxHashSet::default();
343
344 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
345 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
346 let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
347 let state = ForkState::new(
348 pubgrub,
349 self.env.clone(),
350 self.python_requirement.clone(),
351 prefetcher,
352 );
353 let mut preferences = self.preferences.clone();
354 let mut forked_states = self.env.initial_forked_states(state)?;
355
356 match (self.options.fork_strategy, self.options.resolution_mode) {
362 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
363 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
364 }
365 (ForkStrategy::RequiresPython, _) => {
366 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
367 }
368 }
369 let mut resolutions = vec![];
370
371 'FORK: while let Some(mut state) = forked_states.pop() {
372 if let Some(split) = state.env.end_user_fork_display() {
373 let requires_python = state.python_requirement.target();
374 debug!("Solving {split} (requires-python: {requires_python:?})");
375 }
376 let start = Instant::now();
377 loop {
378 let continuation = mem::take(&mut state.continuation);
379 let (highest_priority_pkg, initial_version) = match continuation {
380 ForkContinuation::SelectVersion { package } => (package, None),
381 ForkContinuation::UseVersion { package, version } => (package, Some(version)),
382 ForkContinuation::Propagate => {
383 let result = state.pubgrub.unit_propagation(state.next);
385 match result {
386 Err(err) => {
387 return Err(self.convert_no_solution_err(
389 err,
390 state.fork_urls,
391 state.fork_indexes,
392 &state.known_versions.0,
393 state.env,
394 self.current_environment.clone(),
395 &visited,
396 ));
397 }
398 Ok(conflicts) => {
399 for (affected, incompatibility) in conflicts {
400 state.record_conflict(affected, None, incompatibility);
403 }
404 }
405 }
406
407 if self.dependency_mode.is_transitive() {
409 Self::pre_visit(
410 state.pubgrub.partial_solution.prioritized_packages().map(
411 |(id, range)| (id, &state.pubgrub.package_store[id], range),
412 ),
413 &mut state.pre_visited,
414 &self.urls,
415 &self.indexes,
416 &state.python_requirement,
417 requests,
418 )?;
419 }
420
421 state.reprioritize_conflicts();
422
423 trace!(
424 "Assigned packages: {}",
425 state
426 .pubgrub
427 .partial_solution
428 .extract_solution()
429 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
430 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
431 .join(", ")
432 );
433 let Some((highest_priority_pkg, _)) =
437 state.pubgrub.partial_solution.pick_highest_priority_pkg(
438 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
439 )
440 else {
441 if tracing::enabled!(Level::DEBUG) {
443 state.prefetcher.log_tried_versions();
444 }
445 debug!(
446 "{} resolution took {:.3}s",
447 state.env,
448 start.elapsed().as_secs_f32()
449 );
450
451 let resolution = state.into_resolution();
452
453 if matches!(
462 self.options.resolution_mode,
463 ResolutionMode::Lowest | ResolutionMode::Highest
464 ) {
465 let marker = resolution
466 .env
467 .try_universal_markers()
468 .unwrap_or(UniversalMarker::TRUE);
469 for (package, version) in &resolution.nodes {
470 preferences.insert(
471 package.name.clone(),
472 package.index.clone(),
473 marker,
474 version.clone(),
475 PreferenceSource::Resolver,
476 );
477 }
478 }
479
480 resolutions.push(resolution);
481 continue 'FORK;
482 };
483 trace!(
484 "Chose package for decision: {}. remaining choices: {}",
485 state.pubgrub.package_store[highest_priority_pkg],
486 state
487 .pubgrub
488 .partial_solution
489 .undecided_packages()
490 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
491 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
492 .join(", ")
493 );
494
495 (highest_priority_pkg, None)
496 }
497 };
498
499 state.next = highest_priority_pkg;
500
501 let next_id = state.next;
503 let next_package = &state.pubgrub.package_store[state.next];
504
505 let source =
506 PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
507
508 self.request_package(next_package, source, requests)?;
520
521 let version = if let Some(version) = initial_version {
522 version
523 } else {
524 let term_intersection = state
525 .pubgrub
526 .partial_solution
527 .term_intersection_for_package(next_id)
528 .expect("a package was chosen but we don't have a term");
529 let range = term_intersection.unwrap_positive();
530
531 let cache_selected_version = match source {
535 PackageSource::Registry(None) => true,
536 PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
537 };
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 source,
549 range,
550 &mut state.pins,
551 &preferences,
552 &state.env,
553 &state.python_requirement,
554 &state.pubgrub,
555 &mut visited,
556 requests,
557 )?;
558
559 if cache_selected_version
560 && let Some(ResolverVersion::Unforked(version)) = &decision
561 {
562 state
563 .selected_versions
564 .insert(next_id, (range.clone(), version.clone()));
565 }
566
567 decision
568 };
569
570 let Some(version) = decision else {
572 debug!("No compatible version found for: {next_package}");
573
574 let term_intersection = state
575 .pubgrub
576 .partial_solution
577 .term_intersection_for_package(next_id)
578 .expect("a package was chosen but we don't have a term");
579
580 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
581 if let Some(reason) = self.unavailable_packages.pin().get(name) {
583 state
584 .pubgrub
585 .add_incompatibility(Incompatibility::custom_term(
586 next_id,
587 term_intersection.clone(),
588 UnavailableReason::Package(reason.clone()),
589 ));
590 continue;
591 }
592 }
593
594 state
595 .pubgrub
596 .add_incompatibility(Incompatibility::no_versions(
597 next_id,
598 term_intersection.clone(),
599 ));
600 continue;
601 };
602
603 let version = match version {
604 ResolverVersion::Unforked(version) => version,
605 ResolverVersion::Forked(forks) => {
606 forked_states.extend(self.version_forks_to_fork_states(state, forks));
607 continue 'FORK;
608 }
609 ResolverVersion::Unavailable(version, reason) => {
610 state.add_unavailable_version(
611 version,
612 reason,
613 &self.index,
614 &self.installed_packages,
615 );
616 continue;
617 }
618 };
619
620 if let PackageSource::Registry(index) = source {
622 state.prefetcher.prefetch_batches(
623 next_package,
624 index,
625 &version,
626 term_intersection.unwrap_positive(),
627 state
628 .pubgrub
629 .partial_solution
630 .unchanging_term_for_package(next_id),
631 &state.python_requirement,
632 &self.selector,
633 &state.env,
634 )?;
635 }
636
637 version
638 };
639
640 state.prefetcher.version_tried(next_package, &version);
641
642 self.on_progress(next_package, &version);
643
644 if state
645 .added_dependencies
646 .get(&next_id)
647 .is_some_and(|versions| versions.contains(&version))
648 {
649 state
652 .pubgrub
653 .partial_solution
654 .add_decision(next_id, version);
655 continue;
656 }
657
658 let forked_deps = self.get_dependencies_forking(
660 next_id,
661 next_package,
662 &version,
663 &state.pins,
664 &state.env,
665 &state.python_requirement,
666 &state.pubgrub,
667 )?;
668
669 match forked_deps {
670 ForkedDependencies::Unavailable(reason) => {
671 let versions = state.widen_version_to_gap(
674 &version,
675 &self.index,
676 &self.installed_packages,
677 );
678 state
679 .pubgrub
680 .add_incompatibility(Incompatibility::custom_term(
681 next_id,
682 Term::Positive(versions),
683 UnavailableReason::Version(reason),
684 ));
685 }
686 ForkedDependencies::Unforked(dependencies) => {
687 state
688 .added_dependencies
689 .entry(next_id)
690 .or_default()
691 .insert(version.clone());
692
693 state
695 .visit_package_version_dependencies(
696 next_id,
697 &version,
698 &self.urls,
699 &self.indexes,
700 &dependencies,
701 &self.git,
702 &self.workspace_members,
703 self.selector.resolution_strategy(),
704 )
705 .map_err(|err| {
706 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
707 })?;
708
709 self.visit_dependencies(&dependencies, &state, requests)
711 .map_err(|err| {
712 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
713 })?;
714
715 state.add_package_version_dependencies(
717 next_id,
718 &version,
719 dependencies,
720 &self.index,
721 &self.installed_packages,
722 );
723 }
724 ForkedDependencies::Forked {
725 mut forks,
726 diverging_packages,
727 } => {
728 state
729 .added_dependencies
730 .entry(next_id)
731 .or_default()
732 .insert(version.clone());
733
734 debug!(
735 "Pre-fork {} took {:.3}s",
736 state.env,
737 start.elapsed().as_secs_f32()
738 );
739
740 match (self.options.fork_strategy, self.options.resolution_mode) {
742 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
743 forks.sort_by(|a, b| {
747 a.cmp_requires_python(b)
748 .reverse()
749 .then_with(|| a.cmp_upper_bounds(b))
750 });
751 }
752 (ForkStrategy::RequiresPython, _) => {
753 forks.sort_by(|a, b| {
757 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
758 });
759 }
760 }
761
762 for new_fork_state in self.forks_to_fork_states(
763 state,
764 &version,
765 forks,
766 requests,
767 &diverging_packages,
768 ) {
769 forked_states.push(new_fork_state?);
770 }
771 continue 'FORK;
772 }
773 ForkedDependencies::RequiresPython(requires_python) => {
774 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
775 && state.env.marker_environment().is_none()
776 {
777 let forks = fork_version_by_python_requirement(
778 &requires_python,
779 &state.python_requirement,
780 &state.env,
781 );
782 if !forks.is_empty() {
783 debug!(
784 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
785 state.python_requirement.target(),
786 &requires_python,
787 next_package,
788 version,
789 forks
790 .iter()
791 .map(ToString::to_string)
792 .collect::<Vec<_>>()
793 .join(", ")
794 );
795
796 let forks = forks
799 .into_iter()
800 .map(|env| VersionFork {
801 env,
802 id: next_id,
803 version: None,
804 })
805 .collect();
806 forked_states
807 .extend(self.version_forks_to_fork_states(state, forks));
808 continue 'FORK;
809 }
810 }
811
812 let versions = state.widen_version_to_gap(
813 &version,
814 &self.index,
815 &self.installed_packages,
816 );
817 state
818 .pubgrub
819 .add_incompatibility(Incompatibility::custom_term(
820 next_id,
821 Term::Positive(versions),
822 UnavailableReason::Version(UnavailableVersion::RequiresPython(
823 requires_python,
824 )),
825 ));
826 }
827 }
828 }
829 }
830 if resolutions.len() > 1 {
831 info!(
832 "Solved your requirements for {} environments",
833 resolutions.len()
834 );
835 }
836 if tracing::enabled!(Level::DEBUG) {
837 for resolution in &resolutions {
838 if let Some(env) = resolution.env.end_user_fork_display() {
839 let packages: FxHashSet<_> = resolution
840 .nodes
841 .keys()
842 .map(|package| &package.name)
843 .collect();
844 debug!(
845 "Distinct solution for {env} with {} package(s)",
846 packages.len()
847 );
848 }
849 }
850 }
851 for resolution in &resolutions {
852 resolution.trace_resolution();
853 }
854 let resolutions = resolutions
855 .into_iter()
856 .map(|resolution| resolution.finalize(&self.index, &self.git))
857 .collect::<Result<Vec<_>, _>>()?;
858 crate::resolution::from_state(
859 resolutions,
860 self.project.as_ref(),
861 &self.workspace_members,
862 self.requirements.clone(),
863 self.constraints.clone(),
864 self.overrides.clone(),
865 &self.preferences,
866 &self.hasher,
867 &self.index,
868 self.python_requirement.target().clone(),
869 &self.conflicts,
870 self.selector.resolution_strategy(),
871 self.options.clone(),
872 )
873 }
874
875 fn forks_to_fork_states<'a, 'index: 'a>(
877 &'a self,
878 current_state: ForkState<'index>,
879 version: &'a Version,
880 forks: Vec<Fork>,
881 requests: &'a MetadataRequests,
882 diverging_packages: &'a BTreeSet<PackageName>,
883 ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
884 debug!(
885 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
886 current_state.pubgrub.package_store[current_state.next],
887 version,
888 diverging_packages
889 .iter()
890 .map(ToString::to_string)
891 .join(", "),
892 forks.len(),
893 if forks.len() == 1 { "" } else { "s" }
894 );
895 assert!(forks.len() >= 2);
896 let package = current_state.next;
902 let mut cur_state = Some(current_state);
903 let forks_len = forks.len();
904 forks
905 .into_iter()
906 .enumerate()
907 .map(move |(i, fork)| {
908 let is_last = i == forks_len - 1;
909 let forked_state = cur_state.take().unwrap();
910 if !is_last {
911 cur_state = Some(forked_state.clone());
912 }
913
914 let env = fork.env.clone();
915 (fork, forked_state.with_env(env))
916 })
917 .map(move |(fork, mut forked_state)| {
918 forked_state
920 .visit_package_version_dependencies(
921 package,
922 version,
923 &self.urls,
924 &self.indexes,
925 &fork.dependencies,
926 &self.git,
927 &self.workspace_members,
928 self.selector.resolution_strategy(),
929 )
930 .map_err(|err| {
931 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
932 })?;
933
934 self.visit_dependencies(&fork.dependencies, &forked_state, requests)
936 .map_err(|err| {
937 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
938 })?;
939
940 forked_state.add_package_version_dependencies(
942 package,
943 version,
944 fork.dependencies,
945 &self.index,
946 &self.installed_packages,
947 );
948
949 Ok(forked_state)
950 })
951 }
952
953 #[expect(clippy::unused_self)]
955 fn version_forks_to_fork_states<'index>(
956 &self,
957 current_state: ForkState<'index>,
958 forks: Vec<VersionFork>,
959 ) -> impl Iterator<Item = ForkState<'index>> {
960 let mut cur_state = Some(current_state);
966 let forks_len = forks.len();
967 forks.into_iter().enumerate().map(move |(i, fork)| {
968 let is_last = i == forks_len - 1;
969 let forked_state = cur_state.take().unwrap();
970 if !is_last {
971 cur_state = Some(forked_state.clone());
972 }
973 let continuation = match fork.version {
974 Some(version) => ForkContinuation::UseVersion {
975 package: fork.id,
976 version,
977 },
978 None => ForkContinuation::SelectVersion { package: fork.id },
979 };
980 forked_state
981 .with_env(fork.env)
982 .with_continuation(continuation)
983 })
984 }
985
986 fn visit_dependencies(
988 &self,
989 dependencies: &[PubGrubDependency],
990 state: &ForkState,
991 requests: &MetadataRequests,
992 ) -> Result<(), ResolveError> {
993 for dependency in dependencies {
994 let PubGrubDependency {
995 package,
996 version: _,
997 parent: _,
998 source: _,
999 } = dependency;
1000 let Some(source) = PackageSource::for_prefetch(
1001 package,
1002 &state.fork_urls,
1003 &state.fork_indexes,
1004 &self.urls,
1005 ) else {
1006 continue;
1007 };
1008 self.request_package(package, source, requests)?;
1009 }
1010 Ok(())
1011 }
1012
1013 fn request_package(
1014 &self,
1015 package: &PubGrubPackage,
1016 source: PackageSource<'_>,
1017 requests: &MetadataRequests,
1018 ) -> Result<(), ResolveError> {
1019 let Some(name) = package.name_no_root() else {
1021 return Ok(());
1022 };
1023
1024 match source {
1025 PackageSource::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 requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1034 }
1035 PackageSource::Registry(index) => {
1036 requests.enqueue_package(name, index)?;
1037 }
1038 }
1039 Ok(())
1040 }
1041
1042 fn pre_visit<'data>(
1045 packages: impl Iterator<
1046 Item = (
1047 Id<PubGrubPackage>,
1048 &'data PubGrubPackage,
1049 &'data Range<Version>,
1050 ),
1051 >,
1052 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1053 urls: &Urls,
1054 indexes: &Indexes,
1055 python_requirement: &PythonRequirement,
1056 requests: &MetadataRequests,
1057 ) -> Result<(), ResolveError> {
1058 for (id, package, range) in packages {
1061 let PubGrubPackageInner::Package {
1062 name,
1063 kind: PackageNodeKind::Base,
1064 marker: MarkerTree::TRUE,
1065 } = &**package
1066 else {
1067 continue;
1068 };
1069 if urls.any_url(name) {
1072 continue;
1073 }
1074 if indexes.contains_key(name) {
1076 continue;
1077 }
1078 if pre_visited.get(&id) == Some(range) {
1081 continue;
1082 }
1083 pre_visited.insert(id, range.clone());
1084 requests.prefetch(name, range, python_requirement)?;
1085 }
1086 Ok(())
1087 }
1088
1089 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1097 fn choose_version<'index>(
1098 &self,
1099 package: &PubGrubPackage,
1100 id: Id<PubGrubPackage>,
1101 source: PackageSource<'_>,
1102 range: &Range<Version>,
1103 pins: &mut FilePins<'index>,
1104 preferences: &Preferences,
1105 env: &ResolverEnvironment,
1106 python_requirement: &PythonRequirement,
1107 pubgrub: &State<UvDependencyProvider>,
1108 visited: &mut FxHashSet<PackageName>,
1109 requests: &'index MetadataRequests,
1110 ) -> Result<Option<ResolverVersion>, ResolveError> {
1111 match &**package {
1112 PubGrubPackageInner::Root(_) => {
1113 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1114 }
1115
1116 PubGrubPackageInner::Python(_) => {
1117 Ok(None)
1120 }
1121
1122 PubGrubPackageInner::System(_) => {
1123 let Some(version) = range.as_singleton() else {
1126 return Ok(None);
1127 };
1128 Ok(Some(ResolverVersion::Unforked(version.clone())))
1129 }
1130
1131 PubGrubPackageInner::Marker { name, .. }
1132 | PubGrubPackageInner::Extra { name, .. }
1133 | PubGrubPackageInner::Group { name, .. }
1134 | PubGrubPackageInner::Package { name, .. } => match source {
1135 PackageSource::Url(url) => self.choose_version_url(
1136 id,
1137 name,
1138 range,
1139 url,
1140 env,
1141 python_requirement,
1142 pubgrub,
1143 pins,
1144 requests,
1145 ),
1146 PackageSource::Registry(index) => self.choose_version_registry(
1147 package,
1148 id,
1149 name,
1150 index,
1151 range,
1152 preferences,
1153 env,
1154 python_requirement,
1155 pubgrub,
1156 pins,
1157 visited,
1158 requests,
1159 ),
1160 },
1161 }
1162 }
1163
1164 fn choose_version_url<'index>(
1167 &self,
1168 id: Id<PubGrubPackage>,
1169 name: &PackageName,
1170 range: &Range<Version>,
1171 url: &VerbatimParsedUrl,
1172 env: &ResolverEnvironment,
1173 python_requirement: &PythonRequirement,
1174 pubgrub: &State<UvDependencyProvider>,
1175 pins: &mut FilePins<'index>,
1176 requests: &'index MetadataRequests,
1177 ) -> Result<Option<ResolverVersion>, ResolveError> {
1178 debug!(
1179 "Searching for a compatible version of {name} @ {} ({range})",
1180 url.verbatim
1181 );
1182
1183 let dist = Dist::from_url(name.clone(), url.clone())?;
1184 let registered = requests.metadata(&dist)?;
1185 let response = registered.wait();
1186
1187 let metadata = match &*response {
1189 MetadataResponse::Found(archive) => &archive.metadata,
1190 MetadataResponse::Unavailable(reason) => {
1191 self.unavailable_packages
1192 .pin()
1193 .insert(name.clone(), reason.into());
1194 return Ok(None);
1195 }
1196 MetadataResponse::Error(dist, err) => {
1199 return Err(ResolveError::Dist(
1200 DistErrorKind::from_requested_dist(dist, &**err),
1201 dist.clone(),
1202 DerivationChain::default(),
1203 err.clone(),
1204 ));
1205 }
1206 };
1207
1208 let version = &metadata.version;
1209
1210 if !range.contains(version) {
1212 return Ok(None);
1213 }
1214
1215 if let Dist::Built(dist) = &dist {
1218 let filename = match &dist {
1219 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1220 BuiltDist::DirectUrl(dist) => &dist.filename,
1221 BuiltDist::GitPath(dist) => &dist.filename,
1222 BuiltDist::Path(dist) => &dist.filename,
1223 };
1224
1225 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1227 {
1228 let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1229 for environment_marker in self.options.artifact_environments.iter().copied() {
1232 if env.included_by_marker(environment_marker)
1234 && env.included_by_marker(
1235 find_environments(id, pubgrub).and(environment_marker),
1236 )
1237 {
1238 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1240 return Ok(Some(ResolverVersion::Unavailable(
1241 version.clone(),
1242 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1243 IncompatibleWheel::MissingPlatform(environment_marker),
1244 )),
1245 )));
1246 }
1247 }
1248 }
1249 }
1250
1251 if !python_requirement.target().matches_wheel_tag(filename) {
1253 return Ok(Some(ResolverVersion::Unavailable(
1254 filename.version.clone(),
1255 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1256 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1257 )),
1258 )));
1259 }
1260 }
1261
1262 if let Some(requires_python) = metadata.requires_python.as_ref() {
1264 if !python_requirement.target().is_contained_by(requires_python) {
1265 let kind = if python_requirement.installed() == python_requirement.target() {
1266 PythonRequirementKind::Installed
1267 } else {
1268 PythonRequirementKind::Target
1269 };
1270 return Ok(Some(ResolverVersion::Unavailable(
1271 version.clone(),
1272 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1273 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1274 )),
1275 )));
1276 }
1277 }
1278
1279 pins.insert_url(name, version, registered);
1280 Ok(Some(ResolverVersion::Unforked(version.clone())))
1281 }
1282
1283 fn choose_version_registry<'index>(
1286 &self,
1287 package: &PubGrubPackage,
1288 id: Id<PubGrubPackage>,
1289 name: &PackageName,
1290 index: Option<&IndexMetadata>,
1291 range: &Range<Version>,
1292 preferences: &Preferences,
1293 env: &ResolverEnvironment,
1294 python_requirement: &PythonRequirement,
1295 pubgrub: &State<UvDependencyProvider>,
1296 pins: &mut FilePins<'index>,
1297 visited: &mut FxHashSet<PackageName>,
1298 requests: &'index MetadataRequests,
1299 ) -> Result<Option<ResolverVersion>, ResolveError> {
1300 let versions_response = requests.request_package(name, index)?.wait();
1302 let index = index.map(IndexMetadata::url);
1303 visited.insert(name.clone());
1304
1305 let version_maps = match *versions_response {
1306 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1307 VersionsResponse::NoIndex => {
1308 self.unavailable_packages
1309 .pin()
1310 .insert(name.clone(), UnavailablePackage::NoIndex);
1311 &[]
1312 }
1313 VersionsResponse::Offline => {
1314 self.unavailable_packages
1315 .pin()
1316 .insert(name.clone(), UnavailablePackage::Offline);
1317 &[]
1318 }
1319 VersionsResponse::NotFound => {
1320 self.unavailable_packages
1321 .pin()
1322 .insert(name.clone(), UnavailablePackage::NotFound);
1323 &[]
1324 }
1325 };
1326
1327 debug!("Searching for a compatible version of {package} ({range})");
1328
1329 let Some(candidate) = self.selector.select(
1331 name,
1332 range,
1333 version_maps,
1334 preferences,
1335 &self.installed_packages,
1336 &self.exclusions,
1337 index,
1338 env,
1339 self.tags.as_ref(),
1340 ) else {
1341 return Ok(None);
1343 };
1344
1345 let dist = match candidate.dist() {
1346 CandidateDist::Compatible(dist) => dist,
1347 CandidateDist::Incompatible {
1348 incompatible_dist: incompatibility,
1349 prioritized_dist: _,
1350 } => {
1351 return Ok(Some(ResolverVersion::Unavailable(
1353 candidate.version().clone(),
1354 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1357 )));
1358 }
1359 };
1360
1361 if let Some((requires_python, incompatibility)) =
1363 Self::check_requires_python(dist, python_requirement)
1364 {
1365 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1366 if env.marker_environment().is_none() {
1367 let forks = fork_version_by_python_requirement(
1368 requires_python,
1369 python_requirement,
1370 env,
1371 );
1372 if !forks.is_empty() {
1373 debug!(
1374 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1375 python_requirement.target(),
1376 requires_python,
1377 name,
1378 candidate.version(),
1379 forks
1380 .iter()
1381 .map(ToString::to_string)
1382 .collect::<Vec<_>>()
1383 .join(", ")
1384 );
1385 let forks = forks
1386 .into_iter()
1387 .map(|env| VersionFork {
1388 env,
1389 id,
1390 version: None,
1391 })
1392 .collect();
1393 return Ok(Some(ResolverVersion::Forked(forks)));
1394 }
1395 }
1396 }
1397
1398 return Ok(Some(ResolverVersion::Unavailable(
1399 candidate.version().clone(),
1400 UnavailableVersion::IncompatibleDist(incompatibility),
1401 )));
1402 }
1403
1404 if let Some(forked) = self.fork_version_registry(
1406 &candidate,
1407 dist,
1408 version_maps,
1409 package,
1410 id,
1411 name,
1412 index,
1413 range,
1414 preferences,
1415 env,
1416 pubgrub,
1417 pins,
1418 requests,
1419 )? {
1420 return Ok(Some(forked));
1421 }
1422
1423 let filename = match dist.for_installation() {
1424 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1425 .filename()
1426 .unwrap_or(Cow::Borrowed("unknown filename")),
1427 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1428 .filename()
1429 .unwrap_or(Cow::Borrowed("unknown filename")),
1430 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1431 };
1432
1433 debug!(
1434 "Selecting: {}=={} [{}] ({})",
1435 name,
1436 candidate.version(),
1437 candidate.choice_kind(),
1438 filename,
1439 );
1440 self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1441
1442 let version = candidate.version().clone();
1443 Ok(Some(ResolverVersion::Unforked(version)))
1444 }
1445
1446 fn fork_version_registry<'index>(
1459 &self,
1460 candidate: &Candidate,
1461 dist: &CompatibleDist,
1462 version_maps: &[VersionMap],
1463 package: &PubGrubPackage,
1464 id: Id<PubGrubPackage>,
1465 name: &PackageName,
1466 index: Option<&IndexUrl>,
1467 range: &Range<Version>,
1468 preferences: &Preferences,
1469 env: &ResolverEnvironment,
1470 pubgrub: &State<UvDependencyProvider>,
1471 pins: &mut FilePins<'index>,
1472 requests: &'index MetadataRequests,
1473 ) -> Result<Option<ResolverVersion>, ResolveError> {
1474 let Some(fork_markers) = env.fork_markers() else {
1476 return Ok(None);
1477 };
1478
1479 let artifact_markers = dist.implied_markers();
1480 if artifact_markers.is_true() {
1481 return Ok(None);
1482 }
1483
1484 for marker in self.options.artifact_environments.iter().copied() {
1487 if env.included_by_marker(marker) {
1489 if !env.included_by_marker(artifact_markers.and(marker))
1491 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1492 {
1493 let coverage = dist.implied_markers().and(fork_markers);
1498 let split = marker.restrict(marker.or(coverage));
1499 let Some((left, right)) = fork_version_by_marker(env, split) else {
1500 return Ok(Some(ResolverVersion::Unavailable(
1501 candidate.version().clone(),
1502 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1503 IncompatibleWheel::MissingPlatform(marker),
1504 )),
1505 )));
1506 };
1507
1508 debug!(
1509 "Forking on required platform `{}` for {}=={} ({})",
1510 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1511 name,
1512 candidate.version(),
1513 [&left, &right]
1514 .iter()
1515 .map(ToString::to_string)
1516 .collect::<Vec<_>>()
1517 .join(", ")
1518 );
1519 let forks = vec![
1520 VersionFork {
1521 env: left,
1522 id,
1523 version: None,
1524 },
1525 VersionFork {
1526 env: right,
1527 id,
1528 version: None,
1529 },
1530 ];
1531 return Ok(Some(ResolverVersion::Forked(forks)));
1532 }
1533 }
1534 }
1535
1536 if !candidate.version().is_local() {
1538 return Ok(None);
1539 }
1540
1541 debug!(
1542 "Looking at local version: {}=={}",
1543 name,
1544 candidate.version()
1545 );
1546
1547 let range = range.clone().intersection(&Range::singleton(
1549 candidate.version().clone().without_local(),
1550 ));
1551
1552 let Some(base_candidate) = self.selector.select(
1553 name,
1554 &range,
1555 version_maps,
1556 preferences,
1557 &self.installed_packages,
1558 &self.exclusions,
1559 index,
1560 env,
1561 self.tags.as_ref(),
1562 ) else {
1563 return Ok(None);
1564 };
1565 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1566 return Ok(None);
1567 };
1568
1569 let mut remainder = {
1571 let mut remainder = base_dist.implied_markers();
1572 remainder = remainder.and(artifact_markers.negate());
1573 remainder
1574 };
1575 if remainder.is_false() {
1576 return Ok(None);
1577 }
1578
1579 if !env.included_by_marker(remainder) {
1583 return Ok(None);
1584 }
1585
1586 if !env.included_by_marker(artifact_markers) {
1589 let filename = match dist.for_installation() {
1590 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1591 .filename()
1592 .unwrap_or(Cow::Borrowed("unknown filename")),
1593 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1594 .filename()
1595 .unwrap_or(Cow::Borrowed("unknown filename")),
1596 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1597 };
1598
1599 debug!(
1600 "Preferring non-local candidate: {}=={} [{}] ({})",
1601 name,
1602 base_candidate.version(),
1603 base_candidate.choice_kind(),
1604 filename,
1605 );
1606 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1607
1608 return Ok(Some(ResolverVersion::Unforked(
1609 base_candidate.version().clone(),
1610 )));
1611 }
1612
1613 for value in [
1622 arcstr::literal!("darwin"),
1623 arcstr::literal!("linux"),
1624 arcstr::literal!("win32"),
1625 ] {
1626 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1627 key: MarkerValueString::SysPlatform,
1628 operator: MarkerOperator::Equal,
1629 value,
1630 });
1631 if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1632 remainder = remainder.or(sys_platform);
1633 }
1634 }
1635
1636 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1638 return Ok(None);
1639 };
1640
1641 debug!(
1642 "Forking platform for {}=={} ({})",
1643 name,
1644 candidate.version(),
1645 [&base_env, &local_env]
1646 .iter()
1647 .map(ToString::to_string)
1648 .collect::<Vec<_>>()
1649 .join(", ")
1650 );
1651 self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1652 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1653
1654 let forks = vec![
1655 VersionFork {
1656 env: base_env.clone(),
1657 id,
1658 version: Some(base_candidate.version().clone()),
1659 },
1660 VersionFork {
1661 env: local_env.clone(),
1662 id,
1663 version: Some(candidate.version().clone()),
1664 },
1665 ];
1666 Ok(Some(ResolverVersion::Forked(forks)))
1667 }
1668
1669 fn visit_candidate<'index>(
1671 &self,
1672 candidate: &Candidate,
1673 dist: &CompatibleDist,
1674 package: &PubGrubPackage,
1675 name: &PackageName,
1676 pins: &mut FilePins<'index>,
1677 requests: &'index MetadataRequests,
1678 ) -> Result<(), ResolveError> {
1679 let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1680 && self.dependency_mode.is_transitive()
1681 {
1682 Some(|| {
1683 requests.request_metadata(
1684 MetadataRequest::Resolved(dist.for_resolution()),
1685 |request| {
1686 if name != request.name() {
1687 return Err(ResolveError::MismatchedPackageName {
1688 request: "distribution",
1689 expected: name.clone(),
1690 actual: request.name().clone(),
1691 });
1692 }
1693 if !self
1695 .hasher
1696 .allows_package(candidate.name(), candidate.version())
1697 {
1698 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1699 }
1700 Ok(())
1701 },
1702 )
1703 })
1704 } else {
1705 None
1706 };
1707 pins.insert(candidate, dist, request)
1708 }
1709
1710 fn check_requires_python<'dist>(
1713 dist: &'dist CompatibleDist,
1714 python_requirement: &PythonRequirement,
1715 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1716 let requires_python = dist.requires_python()?;
1717 if python_requirement.target().is_contained_by(requires_python) {
1718 None
1719 } else {
1720 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1721 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1722 requires_python.clone(),
1723 if python_requirement.installed() == python_requirement.target() {
1724 PythonRequirementKind::Installed
1725 } else {
1726 PythonRequirementKind::Target
1727 },
1728 ))
1729 } else {
1730 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1731 requires_python.clone(),
1732 if python_requirement.installed() == python_requirement.target() {
1733 PythonRequirementKind::Installed
1734 } else {
1735 PythonRequirementKind::Target
1736 },
1737 ))
1738 };
1739 Some((requires_python, incompatibility))
1740 }
1741 }
1742
1743 fn get_dependencies_forking(
1745 &self,
1746 id: Id<PubGrubPackage>,
1747 package: &PubGrubPackage,
1748 version: &Version,
1749 pins: &FilePins,
1750 env: &ResolverEnvironment,
1751 python_requirement: &PythonRequirement,
1752 pubgrub: &State<UvDependencyProvider>,
1753 ) -> Result<ForkedDependencies, ResolveError> {
1754 let dependencies =
1755 self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1756 if env.marker_environment().is_some() {
1757 Ok(ForkedDependencies::from_dependencies_platform_specific(
1758 dependencies,
1759 ))
1760 } else {
1761 Ok(ForkedDependencies::from_dependencies_universal(
1762 dependencies,
1763 env,
1764 python_requirement,
1765 &self.conflicts,
1766 ))
1767 }
1768 }
1769
1770 #[instrument(skip_all, fields(%package, %version))]
1772 fn get_dependencies(
1773 &self,
1774 id: Id<PubGrubPackage>,
1775 package: &PubGrubPackage,
1776 version: &Version,
1777 pins: &FilePins,
1778 env: &ResolverEnvironment,
1779 python_requirement: &PythonRequirement,
1780 pubgrub: &State<UvDependencyProvider>,
1781 ) -> Result<Dependencies, ResolveError> {
1782 let expander = RequirementExpander::new(
1783 &self.constraints,
1784 &self.overrides,
1785 &self.excludes,
1786 env,
1787 python_requirement,
1788 );
1789 let dependencies = match &**package {
1790 PubGrubPackageInner::Root(_) => {
1791 let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1792
1793 PubGrubDependency::from_requirements(
1794 &self.conflicts,
1795 requirements,
1796 None,
1797 Some(package),
1798 )
1799 }
1800
1801 PubGrubPackageInner::Package {
1802 name,
1803 kind,
1804 marker: _,
1805 } => {
1806 if self.dependency_mode.is_direct() {
1808 return Ok(Dependencies::Unforkable(Vec::default()));
1809 }
1810
1811 let registered = pins
1812 .metadata(name, version)
1813 .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1814
1815 if self.dependency_mode.is_transitive()
1817 && self.unavailable_packages.pin().contains_key(name)
1818 && self.installed_packages.get_packages(name).is_empty()
1819 {
1820 debug_assert!(
1821 false,
1822 "Dependencies were requested for a package that is not available"
1823 );
1824 return Err(ResolveError::PackageUnavailable(name.clone()));
1825 }
1826
1827 let response = registered.wait();
1829
1830 let metadata = match &*response {
1831 MetadataResponse::Found(archive) => &archive.metadata,
1832 MetadataResponse::Unavailable(reason) => {
1833 let unavailable_version = UnavailableVersion::from(reason);
1834 let message = unavailable_version.singular_message();
1835 if let Some(err) = reason.source() {
1836 warn!("{name} {message}: {err}");
1838 } else {
1839 warn!("{name} {message}");
1840 }
1841 let incomplete_packages = self.incomplete_packages.pin();
1842 let versions = incomplete_packages.get_or_insert(
1843 name.clone(),
1844 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1845 );
1846 versions.pin().insert(version.clone(), reason.clone());
1847 return Ok(Dependencies::Unavailable(unavailable_version));
1848 }
1849 MetadataResponse::Error(dist, err) => {
1850 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1851 .unwrap_or_default();
1852 return Err(ResolveError::Dist(
1853 DistErrorKind::from_requested_dist(dist, &**err),
1854 dist.clone(),
1855 chain,
1856 err.clone(),
1857 ));
1858 }
1859 };
1860
1861 if let Some(requires_python) = &metadata.requires_python {
1864 if !python_requirement.target().is_contained_by(requires_python) {
1865 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1866 }
1867 }
1868
1869 let system_dependencies = self
1871 .options
1872 .torch_backend
1873 .as_ref()
1874 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1875 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1876 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1877 .map(IndexUrl::url)
1878 .and_then(SystemDependency::from_index)
1879 .into_iter()
1880 .inspect(|system_dependency| {
1881 debug!(
1882 "Adding system dependency `{}` for `{package}@{version}`",
1883 system_dependency
1884 );
1885 })
1886 .map(PubGrubDependency::from);
1887
1888 let (requirements, context) = match kind {
1889 PackageNodeKind::Group(group) => (
1890 metadata
1891 .dependency_groups
1892 .get(group)
1893 .map_or(&[][..], AsRef::as_ref),
1894 RequirementContext::Group { name, version },
1895 ),
1896 PackageNodeKind::Extra(extra) => (
1897 metadata.requires_dist.as_ref(),
1898 RequirementContext::Extra {
1899 name,
1900 version,
1901 extra,
1902 },
1903 ),
1904 PackageNodeKind::Base => (
1905 metadata.requires_dist.as_ref(),
1906 RequirementContext::Package { name, version },
1907 ),
1908 };
1909 let requirements = expander.expand(requirements, context);
1910
1911 PubGrubDependency::from_requirements(
1912 &self.conflicts,
1913 requirements,
1914 kind.group(),
1915 Some(package),
1916 )
1917 .map(|mut dependencies| {
1918 dependencies.extend(system_dependencies);
1919 dependencies
1920 })
1921 }
1922
1923 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1924
1925 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1926
1927 PubGrubPackageInner::Marker { name, marker } => {
1929 return Ok(Dependencies::Unforkable(
1930 [MarkerTree::TRUE, *marker]
1931 .into_iter()
1932 .map(move |marker| PubGrubDependency {
1933 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1934 name: name.clone(),
1935 kind: PackageNodeKind::Base,
1936 marker,
1937 }),
1938 version: Range::singleton(version.clone()),
1939 parent: None,
1940 source: DependencySource::Unspecified,
1941 })
1942 .collect(),
1943 ));
1944 }
1945
1946 PubGrubPackageInner::Extra {
1948 name,
1949 extra,
1950 marker,
1951 } => {
1952 return Ok(Dependencies::Unforkable(
1953 [MarkerTree::TRUE, *marker]
1954 .into_iter()
1955 .dedup()
1956 .flat_map(move |marker| {
1957 [None, Some(extra)]
1958 .into_iter()
1959 .map(move |extra| PubGrubDependency {
1960 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1961 name: name.clone(),
1962 kind: extra
1963 .cloned()
1964 .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
1965 marker,
1966 }),
1967 version: Range::singleton(version.clone()),
1968 parent: None,
1969 source: DependencySource::Unspecified,
1970 })
1971 })
1972 .collect(),
1973 ));
1974 }
1975
1976 PubGrubPackageInner::Group {
1978 name,
1979 group,
1980 marker,
1981 } => {
1982 return Ok(Dependencies::Unforkable(
1983 [MarkerTree::TRUE, *marker]
1984 .into_iter()
1985 .dedup()
1986 .map(|marker| PubGrubDependency {
1987 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1988 name: name.clone(),
1989 kind: PackageNodeKind::Group(group.clone()),
1990 marker,
1991 }),
1992 version: Range::singleton(version.clone()),
1993 parent: None,
1994 source: DependencySource::Unspecified,
1995 })
1996 .collect(),
1997 ));
1998 }
1999 };
2000 Ok(match dependencies {
2001 Ok(dependencies) => Dependencies::Available(dependencies),
2002 Err(requirement) => {
2003 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2004 }
2005 })
2006 }
2007
2008 async fn fetch<Provider: ResolverProvider>(
2010 self: Arc<Self>,
2011 provider: Arc<Provider>,
2012 request_stream: Receiver<Request>,
2013 ) -> Result<(), ResolveError> {
2014 let mut response_stream = ReceiverStream::new(request_stream)
2015 .map(|request| self.process_request(request, &*provider).boxed_local())
2016 .buffer_unordered(usize::MAX);
2020
2021 while let Some(response) = response_stream.next().await {
2022 match response? {
2023 Some(Response::Package(name, index, version_map)) => {
2024 trace!("Received package metadata for: {name}");
2025 if let Some(index) = index {
2026 self.index
2027 .explicit()
2028 .done((name, index), Arc::new(version_map));
2029 } else {
2030 self.index.implicit().done(name, Arc::new(version_map));
2031 }
2032 }
2033 Some(Response::Installed { dist, metadata }) => {
2034 trace!("Received installed distribution metadata for: {dist}");
2035 self.index
2036 .distributions()
2037 .done(dist.distribution_id(), Arc::new(metadata));
2038 }
2039 Some(Response::Dist { dist, metadata }) => {
2040 let dist_kind = match dist {
2041 Dist::Built(_) => "built",
2042 Dist::Source(_) => "source",
2043 };
2044 trace!("Received {dist_kind} distribution metadata for: {dist}");
2045 if let MetadataResponse::Unavailable(reason) = &metadata {
2046 let message = UnavailableVersion::from(reason).singular_message();
2047 if let Some(err) = reason.source() {
2048 warn!("{dist} {message}: {err}");
2050 } else {
2051 warn!("{dist} {message}");
2052 }
2053 }
2054 self.index
2055 .distributions()
2056 .done(dist.distribution_id(), Arc::new(metadata));
2057 }
2058 None => {}
2059 }
2060 }
2061
2062 Ok::<(), ResolveError>(())
2063 }
2064
2065 #[instrument(skip_all, fields(%request))]
2066 async fn process_request<Provider: ResolverProvider>(
2067 &self,
2068 request: Request,
2069 provider: &Provider,
2070 ) -> Result<Option<Response>, ResolveError> {
2071 match request {
2072 Request::Package(package_name, index) => {
2074 let package_versions = provider
2075 .get_package_versions(&package_name, index.as_ref())
2076 .boxed_local()
2077 .await
2078 .map_err(ResolveError::Client)?;
2079
2080 Ok(Some(Response::Package(
2081 package_name,
2082 index.map(IndexMetadata::into_url),
2083 package_versions,
2084 )))
2085 }
2086
2087 Request::Dist(dist) => {
2089 if let Some(version) = dist.version() {
2090 if let Some(index) = dist.index() {
2091 let versions_response = self.index.implicit().get(dist.name());
2093 if let Some(VersionsResponse::Found(version_maps)) =
2094 versions_response.as_deref()
2095 {
2096 for version_map in version_maps {
2097 if version_map.index() == Some(index) {
2098 let Some(metadata) = version_map.get_metadata(version) else {
2099 continue;
2100 };
2101 debug!("Found registry-provided metadata for: {dist}");
2102 return Ok(Some(Response::Dist {
2103 dist,
2104 metadata: MetadataResponse::Found(
2105 ArchiveMetadata::from_metadata23(metadata),
2106 ),
2107 }));
2108 }
2109 }
2110 }
2111
2112 let versions_response = self
2114 .index
2115 .explicit()
2116 .get(&(dist.name().clone(), index.clone()));
2117 if let Some(VersionsResponse::Found(version_maps)) =
2118 versions_response.as_deref()
2119 {
2120 for version_map in version_maps {
2121 let Some(metadata) = version_map.get_metadata(version) else {
2122 continue;
2123 };
2124 debug!("Found registry-provided metadata for: {dist}");
2125 return Ok(Some(Response::Dist {
2126 dist,
2127 metadata: MetadataResponse::Found(
2128 ArchiveMetadata::from_metadata23(metadata),
2129 ),
2130 }));
2131 }
2132 }
2133 }
2134 }
2135
2136 let metadata = provider
2137 .get_or_build_wheel_metadata(&dist)
2138 .boxed_local()
2139 .await?;
2140
2141 if let MetadataResponse::Found(metadata) = &metadata {
2142 if &metadata.metadata.name != dist.name() {
2143 return Err(ResolveError::MismatchedPackageName {
2144 request: "distribution metadata",
2145 expected: dist.name().clone(),
2146 actual: metadata.metadata.name.clone(),
2147 });
2148 }
2149 }
2150
2151 Ok(Some(Response::Dist { dist, metadata }))
2152 }
2153
2154 Request::Installed(dist) => {
2155 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2156
2157 if let MetadataResponse::Found(metadata) = &metadata {
2158 if &metadata.metadata.name != dist.name() {
2159 return Err(ResolveError::MismatchedPackageName {
2160 request: "installed metadata",
2161 expected: dist.name().clone(),
2162 actual: metadata.metadata.name.clone(),
2163 });
2164 }
2165 }
2166
2167 Ok(Some(Response::Installed { dist, metadata }))
2168 }
2169
2170 Request::Prefetch(package_name, range, python_requirement) => {
2172 let versions_response = self
2174 .index
2175 .implicit()
2176 .get_registered(package_name.clone())
2177 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2178 .wait()
2179 .await;
2180
2181 let version_map = match *versions_response {
2182 VersionsResponse::Found(ref version_map) => version_map,
2183 VersionsResponse::NoIndex => {
2185 self.unavailable_packages
2186 .pin()
2187 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2188
2189 return Ok(None);
2190 }
2191 VersionsResponse::Offline => {
2192 self.unavailable_packages
2193 .pin()
2194 .insert(package_name.clone(), UnavailablePackage::Offline);
2195
2196 return Ok(None);
2197 }
2198 VersionsResponse::NotFound => {
2199 self.unavailable_packages
2200 .pin()
2201 .insert(package_name.clone(), UnavailablePackage::NotFound);
2202
2203 return Ok(None);
2204 }
2205 };
2206
2207 let env = ResolverEnvironment::universal(vec![]);
2209
2210 let Some(candidate) = self.selector.select(
2213 &package_name,
2214 &range,
2215 version_map,
2216 &self.preferences,
2217 &self.installed_packages,
2218 &self.exclusions,
2219 None,
2220 &env,
2221 self.tags.as_ref(),
2222 ) else {
2223 return Ok(None);
2224 };
2225
2226 let Some(dist) = candidate.compatible() else {
2228 return Ok(None);
2229 };
2230
2231 for version_map in version_map {
2233 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2234 let dist = dist.for_resolution();
2235 if version_map.index() == dist.index() {
2236 debug!("Found registry-provided metadata for: {dist}");
2237
2238 let metadata =
2239 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2240
2241 let dist = dist.to_owned();
2242 if &package_name != dist.name() {
2243 return Err(ResolveError::MismatchedPackageName {
2244 request: "distribution",
2245 expected: package_name,
2246 actual: dist.name().clone(),
2247 });
2248 }
2249
2250 let response = match dist {
2251 ResolvedDist::Installable { dist, .. } => Response::Dist {
2252 dist: (*dist).clone(),
2253 metadata,
2254 },
2255 ResolvedDist::Installed { dist } => Response::Installed {
2256 dist: (*dist).clone(),
2257 metadata,
2258 },
2259 };
2260
2261 return Ok(Some(response));
2262 }
2263 }
2264 }
2265
2266 if dist.wheel().is_none() {
2270 if !self.selector.use_highest_version(&package_name, &env) {
2271 if let Some((lower, _)) = range.iter().next() {
2272 if lower == Bound::Unbounded {
2273 debug!(
2274 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2275 );
2276 return Ok(None);
2277 }
2278 }
2279 }
2280 }
2281
2282 if let Some(requires_python) = dist.requires_python() {
2284 if !python_requirement.target().is_contained_by(requires_python) {
2285 return Ok(None);
2286 }
2287 }
2288
2289 if !self
2291 .hasher
2292 .allows_package(candidate.name(), candidate.version())
2293 {
2294 return Ok(None);
2295 }
2296
2297 let dist = dist.for_resolution();
2299 if self.index.distributions().register(dist.distribution_id()) {
2300 let dist = dist.to_owned();
2301 if &package_name != dist.name() {
2302 return Err(ResolveError::MismatchedPackageName {
2303 request: "distribution",
2304 expected: package_name,
2305 actual: dist.name().clone(),
2306 });
2307 }
2308
2309 let response = match dist {
2310 ResolvedDist::Installable { dist, .. } => {
2311 let metadata = provider
2312 .get_or_build_wheel_metadata(&dist)
2313 .boxed_local()
2314 .await?;
2315
2316 Response::Dist {
2317 dist: (*dist).clone(),
2318 metadata,
2319 }
2320 }
2321 ResolvedDist::Installed { dist } => {
2322 let metadata =
2323 provider.get_installed_metadata(&dist).boxed_local().await?;
2324
2325 Response::Installed {
2326 dist: (*dist).clone(),
2327 metadata,
2328 }
2329 }
2330 };
2331
2332 Ok(Some(response))
2333 } else {
2334 Ok(None)
2335 }
2336 }
2337 }
2338 }
2339
2340 fn convert_no_solution_err(
2341 &self,
2342 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2343 fork_urls: ForkUrls,
2344 fork_indexes: ForkIndexes,
2345 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2346 env: ResolverEnvironment,
2347 current_environment: MarkerEnvironment,
2348 visited: &FxHashSet<PackageName>,
2349 ) -> ResolveError {
2350 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2351 err,
2352 ));
2353 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2354
2355 let mut unavailable_packages = FxHashMap::default();
2356 for package in derivation_tree_packages(&err) {
2357 if let PubGrubPackageInner::Package { name, .. } = &**package {
2358 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2359 unavailable_packages.insert(name.clone(), reason.clone());
2360 }
2361 }
2362 }
2363
2364 let mut incomplete_packages = FxHashMap::default();
2365 let incomplete_packages_cache = self.incomplete_packages.pin();
2366 for package in derivation_tree_packages(&err) {
2367 if let PubGrubPackageInner::Package { name, .. } = &**package
2368 && let Some(versions) = incomplete_packages_cache.get(name)
2369 {
2370 for (version, reason) in &versions.pin() {
2371 incomplete_packages
2372 .entry(name.clone())
2373 .or_insert_with(BTreeMap::default)
2374 .insert(version.clone(), reason.clone());
2375 }
2376 }
2377 }
2378
2379 let mut available_indexes = FxHashMap::default();
2380 let mut included_versions = FxHashMap::default();
2381 let mut available_versions = FxHashMap::default();
2382
2383 let available_version_cutoff: Option<jiff::Timestamp> =
2384 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2385 .ok()
2386 .and_then(|s| s.parse().ok());
2387
2388 for package in derivation_tree_packages(&err) {
2389 let Some(name) = package.name() else { continue };
2390 if !visited.contains(name) {
2391 continue;
2396 }
2397 let versions_response = if let Some(index) = fork_indexes.get(name) {
2398 self.index
2399 .explicit()
2400 .get(&(name.clone(), index.url().clone()))
2401 } else {
2402 self.index.implicit().get(name)
2403 };
2404 if let Some(response) = versions_response {
2405 if let VersionsResponse::Found(ref version_maps) = *response {
2406 for version_map in version_maps {
2408 let package_included_versions = included_versions
2409 .entry(name.clone())
2410 .or_insert_with(BTreeSet::new);
2411 let package_available_versions = available_versions
2412 .entry(name.clone())
2413 .or_insert_with(BTreeSet::new);
2414
2415 for (version, dists) in version_map.iter(&Ranges::full()) {
2416 let excluded_from_included = || {
2421 let Some(included_version_cutoff) =
2422 version_map.included_version_cutoff()
2423 else {
2424 return false;
2425 };
2426 let Some(prioritized_dist) = dists.prioritized_dist() else {
2427 return true;
2428 };
2429 prioritized_dist.files().all(|file| {
2430 file.upload_time_utc_ms.is_none_or(|upload_time| {
2431 upload_time >= included_version_cutoff.as_millisecond()
2432 })
2433 })
2434 };
2435
2436 if !excluded_from_included() {
2437 package_included_versions.insert(version.clone());
2438 }
2439
2440 let excluded_from_available = || {
2446 let Some(ref exclude_newer) = available_version_cutoff else {
2447 return false;
2448 };
2449 let Some(prioritized_dist) = dists.prioritized_dist() else {
2450 return false;
2451 };
2452 prioritized_dist.files().all(|file| {
2453 file.upload_time_utc_ms.is_some_and(|upload_time| {
2454 upload_time >= exclude_newer.as_millisecond()
2455 })
2456 })
2457 };
2458
2459 if !excluded_from_available() {
2460 package_available_versions.insert(version.clone());
2461 }
2462 }
2463 }
2464
2465 available_indexes
2467 .entry(name.clone())
2468 .or_insert(BTreeSet::new())
2469 .extend(
2470 version_maps
2471 .iter()
2472 .filter_map(|version_map| version_map.index().cloned()),
2473 );
2474 }
2475 }
2476 }
2477
2478 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2479 err,
2480 self.index.clone(),
2481 included_versions,
2482 available_versions,
2483 available_indexes,
2484 self.selector.clone(),
2485 self.python_requirement.clone(),
2486 self.locations.clone(),
2487 self.capabilities.clone(),
2488 unavailable_packages,
2489 incomplete_packages,
2490 fork_urls,
2491 fork_indexes,
2492 env,
2493 current_environment,
2494 self.tags.clone(),
2495 self.workspace_members.clone(),
2496 self.options.clone(),
2497 )))
2498 }
2499
2500 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2501 if let Some(reporter) = self.reporter.as_ref() {
2502 match &**package {
2503 PubGrubPackageInner::Root(_) => {}
2504 PubGrubPackageInner::Python(_) => {}
2505 PubGrubPackageInner::System(_) => {}
2506 PubGrubPackageInner::Marker { .. } => {}
2507 PubGrubPackageInner::Extra { .. } => {}
2508 PubGrubPackageInner::Group { .. } => {}
2509 PubGrubPackageInner::Package { name, .. } => {
2510 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2511 }
2512 }
2513 }
2514 }
2515
2516 fn on_complete(&self) {
2517 if let Some(reporter) = self.reporter.as_ref() {
2518 reporter.on_complete();
2519 }
2520 }
2521}
2522
2523#[derive(Clone, Default)]
2526struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2527
2528impl KnownVersions {
2529 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2542 &'a mut self,
2543 index: &InMemoryIndex,
2544 installed_packages: &InstalledPackages,
2545 source: PackageSource<'_>,
2546 package: &PubGrubPackage,
2547 ) -> Option<&'a [Version]> {
2548 let name = package.name_no_root()?;
2549 let PackageSource::Registry(index_metadata) = source else {
2551 return None;
2552 };
2553 if !self.0.contains_key(name) {
2554 let response = if let Some(index_metadata) = index_metadata {
2555 index
2556 .explicit()
2557 .get(&(name.clone(), index_metadata.url().clone()))?
2558 } else {
2559 index.implicit().get(name)?
2560 };
2561 let VersionsResponse::Found(ref version_maps) = *response else {
2562 return None;
2563 };
2564 let mut versions: Vec<Version> = version_maps
2565 .iter()
2566 .flat_map(|version_map| version_map.included_versions().cloned())
2567 .chain(
2568 installed_packages
2569 .get_packages(name)
2570 .iter()
2571 .map(|dist| dist.version().clone()),
2572 )
2573 .collect();
2574 versions.sort_unstable();
2575 versions.dedup();
2576 self.0.insert(name.clone(), versions.into());
2577 }
2578 Some(&self.0[name][..])
2579 }
2580}
2581
2582#[derive(Clone, Default)]
2584enum ForkContinuation {
2585 #[default]
2587 Propagate,
2588 SelectVersion { package: Id<PubGrubPackage> },
2590 UseVersion {
2592 package: Id<PubGrubPackage>,
2593 version: Version,
2594 },
2595}
2596
2597#[derive(Clone)]
2599pub(crate) struct ForkState<'index> {
2600 pubgrub: State<UvDependencyProvider>,
2608 continuation: ForkContinuation,
2610 next: Id<PubGrubPackage>,
2612 pins: FilePins<'index>,
2621 fork_urls: ForkUrls,
2627 fork_indexes: ForkIndexes,
2632 priorities: PubGrubPriorities,
2638 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2641 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2643 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2645 known_versions: KnownVersions,
2649 env: ResolverEnvironment,
2664 python_requirement: PythonRequirement,
2677 conflict_tracker: ConflictTracker,
2678 prefetcher: BatchPrefetcher,
2682}
2683
2684impl<'index> ForkState<'index> {
2685 fn new(
2686 pubgrub: State<UvDependencyProvider>,
2687 env: ResolverEnvironment,
2688 python_requirement: PythonRequirement,
2689 prefetcher: BatchPrefetcher,
2690 ) -> Self {
2691 Self {
2692 continuation: ForkContinuation::Propagate,
2693 next: pubgrub.root_package,
2694 pubgrub,
2695 pins: FilePins::default(),
2696 fork_urls: ForkUrls::default(),
2697 fork_indexes: ForkIndexes::default(),
2698 priorities: PubGrubPriorities::default(),
2699 added_dependencies: FxHashMap::default(),
2700 pre_visited: FxHashMap::default(),
2701 selected_versions: FxHashMap::default(),
2702 known_versions: KnownVersions::default(),
2703 env,
2704 python_requirement,
2705 conflict_tracker: ConflictTracker::default(),
2706 prefetcher,
2707 }
2708 }
2709
2710 fn visit_package_version_dependencies(
2713 &mut self,
2714 for_package: Id<PubGrubPackage>,
2715 for_version: &Version,
2716 urls: &Urls,
2717 indexes: &Indexes,
2718 dependencies: &[PubGrubDependency],
2719 git: &GitResolver,
2720 workspace_members: &BTreeSet<PackageName>,
2721 resolution_strategy: &ResolutionStrategy,
2722 ) -> Result<(), ResolveError> {
2723 for dependency in dependencies {
2724 let PubGrubDependency {
2725 package,
2726 version,
2727 parent: _,
2728 source,
2729 } = dependency;
2730
2731 let mut has_url = false;
2732 if let Some(name) = package.name() {
2733 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2738 self.fork_urls.insert(name, url, &self.env)?;
2739 has_url = true;
2740 }
2741
2742 if let Some(index) = source.explicit_index() {
2743 self.fork_indexes.insert(name, index, &self.env)?;
2744 }
2745
2746 for index in indexes.get(name, &self.env) {
2748 self.fork_indexes.insert(name, index, &self.env)?;
2749 }
2750 }
2751
2752 if let Some(name) = self.pubgrub.package_store[for_package]
2753 .name_no_root()
2754 .filter(|name| !workspace_members.contains(name))
2755 {
2756 debug!(
2757 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2758 );
2759 } else {
2760 debug!("Adding direct dependency: {package}{version}");
2762
2763 let missing_lower_bound = version
2765 .bounding_range()
2766 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2767 let strategy_lowest = matches!(
2768 resolution_strategy,
2769 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2770 );
2771
2772 if !has_url && missing_lower_bound && strategy_lowest {
2773 let name = package.name_no_root().unwrap();
2774 let bound_on_other_package = dependencies.iter().any(|other| {
2781 Some(name) == other.package.name()
2782 && !other
2783 .version
2784 .bounding_range()
2785 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2786 });
2787
2788 if !bound_on_other_package {
2789 warn_user_once!(
2790 "The direct dependency `{name}` is unpinned. \
2791 Consider setting a lower bound when using `--resolution lowest` \
2792 or `--resolution lowest-direct` to avoid using outdated versions.",
2793 );
2794 }
2795 }
2796 }
2797
2798 self.priorities.insert(package, version, &self.fork_urls);
2800 if let Some(base_package) = package.base_package() {
2803 self.priorities
2804 .insert(&base_package, version, &self.fork_urls);
2805 }
2806 }
2807
2808 Ok(())
2809 }
2810
2811 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2817 &mut self,
2818 for_package: Id<PubGrubPackage>,
2819 for_version: &Version,
2820 dependencies: Vec<PubGrubDependency>,
2821 index: &InMemoryIndex,
2822 installed_packages: &InstalledPackages,
2823 ) {
2824 for dependency in &dependencies {
2825 let PubGrubDependency {
2826 package,
2827 version,
2828 parent: _,
2829 source: _,
2830 } = dependency;
2831
2832 let Some(base_package) = package.base_package() else {
2833 continue;
2834 };
2835
2836 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2837 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2838 self.pubgrub.add_proxy_package_incompatibility(
2839 proxy_package,
2840 base_package_id,
2841 version.clone(),
2842 );
2843 }
2844
2845 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2848 let conflict = self.pubgrub.add_package_version_dependencies(
2849 self.next,
2850 for_version.clone(),
2851 versions,
2852 dependencies.into_iter().map(|dependency| {
2853 let PubGrubDependency {
2854 package,
2855 version,
2856 parent: _,
2857 source: _,
2858 } = dependency;
2859 (package, version)
2860 }),
2861 );
2862
2863 if let Some(incompatibility) = conflict {
2866 self.record_conflict(for_package, Some(for_version), incompatibility);
2867 }
2868 }
2869
2870 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2872 &mut self,
2873 version: &Version,
2874 index: &InMemoryIndex,
2875 installed_packages: &InstalledPackages,
2876 ) -> Range<Version> {
2877 widen_to_gap(
2878 version,
2879 self.known_versions.get_or_update(
2880 index,
2881 installed_packages,
2882 PackageSource::from_fork(
2883 &self.pubgrub.package_store[self.next],
2884 &self.fork_urls,
2885 &self.fork_indexes,
2886 ),
2887 &self.pubgrub.package_store[self.next],
2888 ),
2889 )
2890 }
2891
2892 fn record_conflict(
2893 &mut self,
2894 affected: Id<PubGrubPackage>,
2895 version: Option<&Version>,
2896 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2897 ) {
2898 let mut culprit_is_real = false;
2899 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2900 if incompatible == affected {
2901 continue;
2902 }
2903 if self.pubgrub.package_store[affected].name()
2904 == self.pubgrub.package_store[incompatible].name()
2905 {
2906 continue;
2909 }
2910 culprit_is_real = true;
2911 let culprit_count = self
2912 .conflict_tracker
2913 .culprit
2914 .entry(incompatible)
2915 .or_default();
2916 *culprit_count += 1;
2917 if *culprit_count == CONFLICT_THRESHOLD {
2918 self.conflict_tracker.deprioritize.push(incompatible);
2919 }
2920 }
2921 if culprit_is_real {
2924 if tracing::enabled!(Level::DEBUG) {
2925 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
2926 .iter()
2927 .map(|(package, _term)| &self.pubgrub.package_store[package])
2928 .join(", ");
2929 if let Some(version) = version {
2930 debug!(
2931 "Recording dependency conflict of {}=={} from incompatibility of ({})",
2932 self.pubgrub.package_store[affected], version, incompatibility
2933 );
2934 } else {
2935 debug!(
2936 "Recording unit propagation conflict of {} from incompatibility of ({})",
2937 self.pubgrub.package_store[affected], incompatibility
2938 );
2939 }
2940 }
2941
2942 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
2943 *affected_count += 1;
2944 if *affected_count == CONFLICT_THRESHOLD {
2945 self.conflict_tracker.prioritize.push(self.next);
2946 }
2947 }
2948 }
2949
2950 fn reprioritize_conflicts(&mut self) {
2954 for package in self.conflict_tracker.prioritize.drain(..) {
2955 let changed = self
2956 .priorities
2957 .mark_conflict_early(&self.pubgrub.package_store[package]);
2958 if changed {
2959 debug!(
2960 "Package {} has too many conflicts (affected), prioritizing",
2961 &self.pubgrub.package_store[package]
2962 );
2963 } else {
2964 debug!(
2965 "Package {} has too many conflicts (affected), already {:?}",
2966 self.pubgrub.package_store[package],
2967 self.priorities.get(&self.pubgrub.package_store[package])
2968 );
2969 }
2970 }
2971
2972 for package in self.conflict_tracker.deprioritize.drain(..) {
2973 let changed = self
2974 .priorities
2975 .mark_conflict_late(&self.pubgrub.package_store[package]);
2976 if changed {
2977 debug!(
2978 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
2979 self.pubgrub.package_store[package],
2980 );
2981 let backtrack_level = self.pubgrub.backtrack_package(package);
2982 if let Some(backtrack_level) = backtrack_level {
2983 debug!("Backtracked {backtrack_level} decisions");
2984 } else {
2985 debug!(
2986 "Package {} is not decided, cannot backtrack",
2987 self.pubgrub.package_store[package]
2988 );
2989 }
2990 } else {
2991 debug!(
2992 "Package {} has too many conflicts (culprit), already {:?}",
2993 self.pubgrub.package_store[package],
2994 self.priorities.get(&self.pubgrub.package_store[package])
2995 );
2996 }
2997 }
2998 }
2999
3000 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3008 &mut self,
3009 version: Version,
3010 reason: UnavailableVersion,
3011 index: &InMemoryIndex,
3012 installed_packages: &InstalledPackages,
3013 ) {
3014 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3015
3016 if let UnavailableVersion::IncompatibleDist(
3020 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3021 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3022 ) = reason
3023 {
3024 let package = &self.next;
3025 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3026 PubGrubPackageInner::Python(match kind {
3027 PythonRequirementKind::Installed => PubGrubPython::Installed,
3028 PythonRequirementKind::Target => PubGrubPython::Target,
3029 }),
3030 ));
3031 self.pubgrub
3032 .add_incompatibility(Incompatibility::from_dependency(
3033 *package,
3034 versions,
3035 (
3036 python,
3037 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3038 ),
3039 ));
3040 self.pubgrub
3041 .partial_solution
3042 .add_decision(self.next, version);
3043 return;
3044 }
3045 self.pubgrub
3046 .add_incompatibility(Incompatibility::custom_term(
3047 self.next,
3048 Term::Positive(versions),
3049 UnavailableReason::Version(reason),
3050 ));
3051 }
3052
3053 fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3054 self.continuation = continuation;
3055 self
3056 }
3057
3058 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3060 self.selected_versions.clear();
3061 self.env = env;
3062 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3064 debug!("Narrowed `requires-python` bound to: {}", req.target());
3065 self.python_requirement = req;
3066 }
3067 self
3068 }
3069
3070 fn source(
3074 &self,
3075 name: &PackageName,
3076 version: &Version,
3077 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3078 let url = self.fork_urls.get(name);
3079 let index = url
3080 .is_none()
3081 .then(|| {
3082 self.pins
3083 .get(name, version)
3084 .expect("Every package should be pinned")
3085 .index()
3086 })
3087 .flatten();
3088 (url, index)
3089 }
3090
3091 fn into_resolution(self) -> Resolution<'index> {
3092 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3093 let edge_count: usize = solution
3094 .keys()
3095 .map(|package| self.pubgrub.incompatibilities[package].len())
3096 .sum();
3097 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3098 for (package, self_version) in &solution {
3099 for id in &self.pubgrub.incompatibilities[package] {
3100 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3101 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3102 &incompatibility.kind
3103 else {
3104 continue;
3105 };
3106 let (self_package, dependency_package) = (*self_package, *dependency_package);
3107 let Some((self_range, dependency_range)) =
3108 incompatibility.dependency_version_sets()
3109 else {
3110 continue;
3111 };
3112 let dependency_range =
3113 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3114 if *package != self_package {
3115 continue;
3116 }
3117 if !self_range.contains(self_version) {
3118 continue;
3119 }
3120 let Some(dependency_version) = solution.get(&dependency_package) else {
3121 continue;
3122 };
3123 if !dependency_range.contains(dependency_version) {
3124 continue;
3125 }
3126
3127 let self_package = &self.pubgrub.package_store[self_package];
3128 let dependency_package = &self.pubgrub.package_store[dependency_package];
3129
3130 let (self_name, self_kind) = match &**self_package {
3131 PubGrubPackageInner::Package {
3132 name: self_name,
3133 kind: self_kind,
3134 marker: _,
3135 } => (Some(self_name), self_kind),
3136
3137 PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3138
3139 _ => continue,
3140 };
3141
3142 let (name, kind, marker) = match &**dependency_package {
3143 PubGrubPackageInner::Package { name, kind, marker } => {
3144 debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3145
3146 if self_kind.group().is_none() && self_name == Some(name) {
3149 continue;
3150 }
3151 (name, kind.clone(), *marker)
3152 }
3153 PubGrubPackageInner::Marker { name, marker } => {
3154 if self_kind.group().is_none() && self_name == Some(name) {
3155 continue;
3156 }
3157 (name, PackageNodeKind::Base, *marker)
3158 }
3159 PubGrubPackageInner::Extra {
3160 name,
3161 extra,
3162 marker,
3163 } => {
3164 if self_kind.group().is_none() {
3165 debug_assert!(self_name != Some(name), "Extras should be flattened");
3166 }
3167 (name, PackageNodeKind::Extra(extra.clone()), *marker)
3168 }
3169 PubGrubPackageInner::Group {
3170 name,
3171 group,
3172 marker,
3173 } => {
3174 debug_assert!(self_name != Some(name), "Groups should be flattened");
3175 (name, PackageNodeKind::Group(group.clone()), *marker)
3176 }
3177 PubGrubPackageInner::Root(_)
3178 | PubGrubPackageInner::Python(_)
3179 | PubGrubPackageInner::System(_) => continue,
3180 };
3181 let from = self_name.map(|name| {
3182 let (url, index) = self.source(name, self_version);
3183 ResolutionNode {
3184 package: ResolutionPackage {
3185 name: name.clone(),
3186 kind: self_kind.clone(),
3187 url: url.cloned(),
3188 index: index.cloned(),
3189 },
3190 version: self_version.clone(),
3191 }
3192 });
3193
3194 let (url, index) = self.source(name, dependency_version);
3195 let to = ResolutionNode {
3196 package: ResolutionPackage {
3197 name: name.clone(),
3198 kind,
3199 url: url.cloned(),
3200 index: index.cloned(),
3201 },
3202 version: dependency_version.clone(),
3203 };
3204 let edge = ResolutionDependencyEdge { from, to, marker };
3205
3206 if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3209 let mut base_edge = edge.clone();
3210 base_edge.to.package.kind = PackageNodeKind::Base;
3211 edges.push(edge);
3212 edges.push(base_edge);
3213 } else {
3214 edges.push(edge);
3215 }
3216 }
3217 }
3218
3219 let nodes = solution
3220 .into_iter()
3221 .filter_map(|(package, version)| {
3222 if let PubGrubPackageInner::Package {
3223 name,
3224 kind,
3225 marker: MarkerTree::TRUE,
3226 } = &*self.pubgrub.package_store[package]
3227 {
3228 let (url, index) = self.source(name, &version);
3229 Some((
3230 ResolutionPackage {
3231 name: name.clone(),
3232 kind: kind.clone(),
3233 url: url.cloned(),
3234 index: index.cloned(),
3235 },
3236 version,
3237 ))
3238 } else {
3239 None
3240 }
3241 })
3242 .collect();
3243
3244 Resolution {
3245 nodes,
3246 edges,
3247 pins: self.pins,
3248 env: self.env,
3249 }
3250 }
3251}
3252
3253fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3262 let versions = Range::singleton(version.clone());
3263 match known_versions {
3264 Some(known_versions) if !known_versions.is_empty() => {
3265 versions.widen_versions(known_versions)
3266 }
3267 _ => versions,
3268 }
3269}
3270
3271#[derive(Debug)]
3273#[expect(clippy::large_enum_variant)]
3274pub(crate) enum Request {
3275 Package(PackageName, Option<IndexMetadata>),
3277 Dist(Dist),
3279 Installed(InstalledDist),
3281 Prefetch(PackageName, Range<Version>, PythonRequirement),
3283}
3284
3285impl<'a> From<ResolvedDistRef<'a>> for Request {
3286 fn from(dist: ResolvedDistRef<'a>) -> Self {
3287 match dist {
3293 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3294 let source = prioritized.source_dist().expect("a source distribution");
3297 assert_eq!(
3298 (&sdist.name, &sdist.version),
3299 (&source.name, &source.version),
3300 "expected chosen sdist to match prioritized sdist"
3301 );
3302 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3303 }
3304 ResolvedDistRef::InstallableRegistryBuiltDist {
3305 wheel, prioritized, ..
3306 } => {
3307 assert_eq!(
3308 Some(&wheel.filename),
3309 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3310 "expected chosen wheel to match best wheel"
3311 );
3312 let built = prioritized.built_dist().expect("at least one wheel");
3315 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3316 }
3317 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3318 }
3319 }
3320}
3321
3322impl Display for Request {
3323 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3324 match self {
3325 Self::Package(package_name, _) => {
3326 write!(f, "Versions {package_name}")
3327 }
3328 Self::Dist(dist) => {
3329 write!(f, "Metadata {dist}")
3330 }
3331 Self::Installed(dist) => {
3332 write!(f, "Installed metadata {dist}")
3333 }
3334 Self::Prefetch(package_name, range, _) => {
3335 write!(f, "Prefetch {package_name} {range}")
3336 }
3337 }
3338 }
3339}
3340
3341#[derive(Debug)]
3342#[expect(clippy::large_enum_variant)]
3343enum Response {
3344 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3346 Dist {
3348 dist: Dist,
3349 metadata: MetadataResponse,
3350 },
3351 Installed {
3353 dist: InstalledDist,
3354 metadata: MetadataResponse,
3355 },
3356}
3357
3358enum Dependencies {
3364 Unavailable(UnavailableVersion),
3366 Available(Vec<PubGrubDependency>),
3372 RequiresPython(VersionSpecifiers),
3374 Unforkable(Vec<PubGrubDependency>),
3380}
3381
3382#[derive(Debug)]
3389enum ForkedDependencies {
3390 Unavailable(UnavailableVersion),
3392 Unforked(Vec<PubGrubDependency>),
3396 Forked {
3402 forks: Vec<Fork>,
3403 diverging_packages: BTreeSet<PackageName>,
3405 },
3406 RequiresPython(VersionSpecifiers),
3408}
3409
3410impl ForkedDependencies {
3411 fn from_dependencies_universal(
3418 dependencies: Dependencies,
3419 env: &ResolverEnvironment,
3420 python_requirement: &PythonRequirement,
3421 conflicts: &Conflicts,
3422 ) -> Self {
3423 let deps = match dependencies {
3424 Dependencies::Available(deps) => deps,
3425 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3426 Dependencies::RequiresPython(requires_python) => {
3427 return Self::RequiresPython(requires_python);
3428 }
3429 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3430 };
3431 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3432 for dep in deps {
3433 let name = dep
3434 .package
3435 .name()
3436 .expect("dependency always has a name")
3437 .clone();
3438 name_to_deps.entry(name).or_default().push(dep);
3439 }
3440 let (mut forks, diverging_packages) =
3441 Self::fork(name_to_deps, env, python_requirement, conflicts);
3442 if forks.is_empty() {
3443 Self::Unforked(vec![])
3444 } else if forks.len() == 1 {
3445 Self::Unforked(forks.pop().unwrap().dependencies)
3446 } else {
3447 Self::Forked {
3448 forks,
3449 diverging_packages,
3450 }
3451 }
3452 }
3453
3454 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3457 match dependencies {
3458 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3459 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3460 Dependencies::Unavailable(err) => Self::Unavailable(err),
3461 }
3462 }
3463
3464 fn fork(
3472 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3473 env: &ResolverEnvironment,
3474 python_requirement: &PythonRequirement,
3475 conflicts: &Conflicts,
3476 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3477 let python_marker = python_requirement.to_marker_tree();
3478
3479 let mut forks = vec![Fork::new(env.clone())];
3480 let mut diverging_packages = BTreeSet::new();
3481 for (name, mut deps) in name_to_deps {
3482 assert!(!deps.is_empty(), "every name has at least one dependency");
3483 if let [dep] = deps.as_slice() {
3494 if marker::requires_python(dep.package.marker())
3502 .is_none_or(|bound| !python_requirement.raises(&bound))
3503 {
3504 let dep = deps.pop().unwrap();
3505 let marker = dep.package.marker();
3506 for fork in &mut forks {
3507 if fork.env.included_by_marker(marker) {
3508 fork.add_dependency(dep.clone());
3509 }
3510 }
3511 continue;
3512 }
3513 } else {
3514 if let Some(dep) = deps.first() {
3516 let marker = dep.package.marker();
3517 if deps.iter().all(|dep| marker == dep.package.marker()) {
3518 if marker::requires_python(marker)
3522 .is_none_or(|bound| !python_requirement.raises(&bound))
3523 {
3524 for dep in deps {
3525 for fork in &mut forks {
3526 if fork.env.included_by_marker(marker) {
3527 fork.add_dependency(dep.clone());
3528 }
3529 }
3530 }
3531 continue;
3532 }
3533 }
3534 }
3535 }
3536 for dep in deps {
3537 let mut forker = match ForkingPossibility::new(env, &dep) {
3538 ForkingPossibility::Possible(forker) => forker,
3539 ForkingPossibility::DependencyAlwaysExcluded => {
3540 continue;
3543 }
3544 ForkingPossibility::NoForkingPossible => {
3545 for fork in &mut forks {
3548 fork.add_dependency(dep.clone());
3549 }
3550 continue;
3551 }
3552 };
3553 diverging_packages.insert(name.clone());
3555
3556 let mut new = vec![];
3557 for fork in std::mem::take(&mut forks) {
3558 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3559 new.push(fork);
3560 continue;
3561 };
3562 forker = remaining_forker;
3563
3564 for fork_env in envs {
3565 let mut new_fork = fork.clone();
3566 new_fork.set_env(fork_env);
3567 if forker.included(&new_fork.env) {
3572 new_fork.add_dependency(dep.clone());
3573 }
3574 if new_fork.env.included_by_marker(python_marker) {
3577 new.push(new_fork);
3578 }
3579 }
3580 }
3581 forks = new;
3582 }
3583 }
3584 for set in conflicts.iter() {
3599 let mut new = vec![];
3600 for fork in std::mem::take(&mut forks) {
3601 let mut has_conflicting_dependency = false;
3609 for item in set.iter() {
3610 if fork.contains_conflicting_item(item.as_ref()) {
3611 has_conflicting_dependency = true;
3612 diverging_packages.insert(item.package().clone());
3613 break;
3614 }
3615 }
3616 if !has_conflicting_dependency {
3617 new.push(fork);
3618 continue;
3619 }
3620
3621 let non_excluded: Vec<_> = set
3627 .iter()
3628 .filter(|item| fork.env.included_by_group(item.as_ref()))
3629 .collect();
3630 if non_excluded.len() < 2 {
3631 let dominated = non_excluded.iter().all(|item| {
3636 !conflicts.iter().any(|other_set| {
3637 !std::ptr::eq(set, other_set)
3638 && other_set.contains(item.package(), item.kind().as_ref())
3639 && other_set
3640 .iter()
3641 .filter(|other_item| {
3642 other_item.package() != item.package()
3643 || other_item.kind() != item.kind()
3644 })
3645 .any(|other_item| {
3646 fork.env.included_by_group(other_item.as_ref())
3647 })
3648 })
3649 });
3650 if dominated {
3651 let rules: Vec<_> = set
3656 .iter()
3657 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3658 .cloned()
3659 .map(Err)
3660 .collect();
3661 if let Some(filtered) = fork.filter(rules) {
3662 new.push(filtered);
3663 }
3664 continue;
3665 }
3666 }
3667
3668 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3670 new.push(fork_none);
3671 }
3672
3673 for (i, _) in set.iter().enumerate() {
3681 let fork_allows_group = fork.clone().filter(
3682 set.iter()
3683 .cloned()
3684 .enumerate()
3685 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3686 );
3687 if let Some(fork_allows_group) = fork_allows_group {
3688 new.push(fork_allows_group);
3689 }
3690 }
3691 }
3692 forks = new;
3693 }
3694 (forks, diverging_packages)
3695 }
3696}
3697
3698#[derive(Clone, Debug)]
3708struct Fork {
3709 dependencies: Vec<PubGrubDependency>,
3718 conflicts: crate::FxHashbrownSet<ConflictItem>,
3724 env: ResolverEnvironment,
3736}
3737
3738impl Fork {
3739 fn new(env: ResolverEnvironment) -> Self {
3742 Self {
3743 dependencies: vec![],
3744 conflicts: crate::FxHashbrownSet::default(),
3745 env,
3746 }
3747 }
3748
3749 fn add_dependency(&mut self, dep: PubGrubDependency) {
3751 if let Some(conflicting_item) = dep.conflicting_item() {
3752 self.conflicts.insert(conflicting_item.to_owned());
3753 }
3754 self.dependencies.push(dep);
3755 }
3756
3757 fn set_env(&mut self, env: ResolverEnvironment) {
3762 self.env = env;
3763 self.dependencies.retain(|dep| {
3764 let marker = dep.package.marker();
3765 if self.env.included_by_marker(marker) {
3766 return true;
3767 }
3768 if let Some(conflicting_item) = dep.conflicting_item() {
3769 self.conflicts.remove(&conflicting_item);
3770 }
3771 false
3772 });
3773 }
3774
3775 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3778 self.conflicts.contains(&item)
3779 }
3780
3781 fn filter(
3788 mut self,
3789 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3790 ) -> Option<Self> {
3791 self.env = self.env.filter_by_group(rules)?;
3792 self.dependencies.retain(|dep| {
3793 let Some(conflicting_item) = dep.conflicting_item() else {
3794 return true;
3795 };
3796 if self.env.included_by_group(conflicting_item) {
3797 return true;
3798 }
3799 match conflicting_item.kind() {
3800 ConflictKindRef::Project => {
3803 if dep.parent.is_some() {
3804 return true;
3805 }
3806 }
3807 ConflictKindRef::Group(_) => {}
3808 ConflictKindRef::Extra(_) => {}
3809 }
3810 self.conflicts.remove(&conflicting_item);
3811 false
3812 });
3813 Some(self)
3814 }
3815
3816 fn cmp_requires_python(&self, other: &Self) -> Ordering {
3818 cmp_requires_python(&self.env, &other.env)
3819 }
3820
3821 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3823 let self_upper_bounds = self
3828 .dependencies
3829 .iter()
3830 .filter(|dep| {
3831 dep.version
3832 .bounding_range()
3833 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3834 })
3835 .count();
3836 let other_upper_bounds = other
3837 .dependencies
3838 .iter()
3839 .filter(|dep| {
3840 dep.version
3841 .bounding_range()
3842 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3843 })
3844 .count();
3845
3846 self_upper_bounds.cmp(&other_upper_bounds)
3847 }
3848}
3849
3850fn cmp_requires_python(
3852 self_env: &ResolverEnvironment,
3853 other_env: &ResolverEnvironment,
3854) -> Ordering {
3855 let self_bound = self_env.requires_python().unwrap_or_default();
3865 let other_bound = other_env.requires_python().unwrap_or_default();
3866 self_bound.lower().cmp(other_bound.lower())
3867}
3868
3869impl Eq for Fork {}
3870
3871impl PartialEq for Fork {
3872 fn eq(&self, other: &Self) -> bool {
3873 self.dependencies == other.dependencies && self.env == other.env
3874 }
3875}
3876
3877#[derive(Debug, Clone)]
3878pub(crate) struct VersionFork {
3879 env: ResolverEnvironment,
3881 id: Id<PubGrubPackage>,
3883 version: Option<Version>,
3885}
3886
3887fn enrich_dependency_error(
3889 error: ResolveError,
3890 id: Id<PubGrubPackage>,
3891 version: &Version,
3892 pubgrub: &State<UvDependencyProvider>,
3893) -> ResolveError {
3894 let Some(name) = pubgrub.package_store[id].name_no_root() else {
3895 return error;
3896 };
3897 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
3898 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
3899}
3900
3901fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
3903 let package = &state.package_store[id];
3904 if package.is_root() {
3905 return MarkerTree::TRUE;
3906 }
3907
3908 let mut ancestors = FxHashSet::default();
3911 let mut stack = vec![id];
3912 let mut root = None;
3913 ancestors.insert(id);
3914
3915 while let Some(current) = stack.pop() {
3916 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3917 continue;
3918 };
3919
3920 for index in incompatibilities {
3921 let incompat = &state.incompatibility_store[*index];
3922 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
3923 if current != *child {
3924 continue;
3925 }
3926 if ancestors.insert(*parent) {
3927 if state.package_store[*parent].is_root() {
3928 root = Some(*parent);
3929 }
3930 stack.push(*parent);
3931 }
3932 }
3933 }
3934 }
3935
3936 let Some(root) = root else {
3937 return MarkerTree::FALSE;
3938 };
3939
3940 let mut environments = FxHashMap::default();
3943 let mut queue = VecDeque::from([root]);
3944 environments.insert(root, MarkerTree::TRUE);
3945
3946 while let Some(current) = queue.pop_front() {
3947 let Some(current_environment) = environments.get(¤t).copied() else {
3948 continue;
3949 };
3950 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3951 continue;
3952 };
3953
3954 for index in incompatibilities {
3955 let incompat = &state.incompatibility_store[*index];
3956 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
3957 continue;
3958 };
3959 if current != *parent || !ancestors.contains(child) {
3960 continue;
3961 }
3962
3963 let mut next_environment = state.package_store[*child].marker();
3964 next_environment = next_environment.and(current_environment);
3965
3966 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
3967 let mut combined = *entry;
3968 combined = combined.or(next_environment);
3969 if combined != *entry {
3970 *entry = combined;
3971 queue.push_back(*child);
3972 }
3973 }
3974 }
3975
3976 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
3977}
3978
3979#[derive(Debug, Default, Clone)]
3980struct ConflictTracker {
3981 affected: FxHashMap<Id<PubGrubPackage>, usize>,
3983 prioritize: Vec<Id<PubGrubPackage>>,
3987 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
3989 deprioritize: Vec<Id<PubGrubPackage>>,
3993}
3994
3995#[cfg(test)]
3996mod tests {
3997 use super::*;
3998
3999 fn versions(versions: &[&str]) -> Vec<Version> {
4000 versions
4001 .iter()
4002 .map(|version| version.parse().expect("valid version"))
4003 .collect()
4004 }
4005
4006 #[test]
4007 fn widens_a_version_to_its_gap() {
4008 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4009 let version: Version = "2.0".parse().expect("valid version");
4010
4011 assert_eq!(
4013 widen_to_gap(&version, Some(&known_versions)).to_string(),
4014 ">1.0, <3.0"
4015 );
4016
4017 let version: Version = "1.0".parse().expect("valid version");
4019 assert_eq!(
4020 widen_to_gap(&version, Some(&known_versions)).to_string(),
4021 "<2.0"
4022 );
4023 let version: Version = "3.0".parse().expect("valid version");
4024 assert_eq!(
4025 widen_to_gap(&version, Some(&known_versions)).to_string(),
4026 ">2.0"
4027 );
4028 }
4029
4030 #[test]
4031 fn widens_a_version_without_known_versions_to_itself() {
4032 let version: Version = "2.0".parse().expect("valid version");
4033
4034 assert_eq!(
4036 widen_to_gap(&version, None),
4037 Range::singleton(version.clone())
4038 );
4039
4040 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4042 }
4043}