1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter, Write};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{iter, slice, thread};
11
12use dashmap::DashMap;
13use either::Either;
14use futures::{FutureExt, StreamExt};
15use itertools::Itertools;
16use pubgrub::{Id, IncompId, Incompatibility, Kind, Range, Ranges, State};
17use rustc_hash::{FxHashMap, FxHashSet};
18use tokio::sync::mpsc::{self, Receiver, Sender};
19use tokio::sync::oneshot;
20use tokio_stream::wrappers::ReceiverStream;
21use tracing::{Level, debug, info, instrument, trace, warn};
22
23use uv_configuration::{Constraints, Excludes, Overrides};
24use uv_distribution::{ArchiveMetadata, DistributionDatabase};
25use uv_distribution_types::{
26 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
27 IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations,
28 IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement,
29 ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::{ExtraName, GroupName, PackageName};
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_torch::TorchStrategy;
40use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
41use uv_warnings::warn_user_once;
42
43use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
44use crate::dependency_provider::UvDependencyProvider;
45use crate::error::{NoSolutionError, ResolveError};
46use crate::fork_indexes::ForkIndexes;
47use crate::fork_strategy::ForkStrategy;
48use crate::fork_urls::ForkUrls;
49use crate::manifest::Manifest;
50use crate::pins::FilePins;
51use crate::preferences::{PreferenceSource, Preferences};
52use crate::pubgrub::{
53 PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
54 PubGrubPython,
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,
62};
63use crate::resolver::batch_prefetch::BatchPrefetcher;
64pub use 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;
72pub use crate::resolver::provider::{
73 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
74 VersionsResponse, WheelMetadataResult,
75};
76pub use crate::resolver::reporter::{BuildId, Reporter};
77use crate::resolver::system::SystemDependency;
78pub(crate) use crate::resolver::urls::Urls;
79use crate::universal_marker::{ConflictMarker, UniversalMarker};
80use crate::yanks::AllowedYanks;
81use crate::{
82 DependencyMode, ExcludeNewer, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap,
83 marker,
84};
85pub(crate) use provider::MetadataUnavailable;
86
87mod availability;
88mod batch_prefetch;
89mod derivation;
90mod environment;
91mod fork_map;
92mod index;
93mod indexes;
94mod provider;
95mod reporter;
96mod system;
97mod urls;
98
99const CONFLICT_THRESHOLD: usize = 5;
101
102pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
103 state: ResolverState<InstalledPackages>,
104 provider: Provider,
105}
106
107struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
110 project: Option<PackageName>,
111 requirements: Vec<Requirement>,
112 constraints: Constraints,
113 overrides: Overrides,
114 excludes: Excludes,
115 preferences: Preferences,
116 git: GitResolver,
117 capabilities: IndexCapabilities,
118 locations: IndexLocations,
119 exclusions: Exclusions,
120 urls: Urls,
121 indexes: Indexes,
122 dependency_mode: DependencyMode,
123 hasher: HashStrategy,
124 env: ResolverEnvironment,
125 current_environment: MarkerEnvironment,
127 tags: Option<Tags>,
128 python_requirement: PythonRequirement,
129 conflicts: Conflicts,
130 workspace_members: BTreeSet<PackageName>,
131 selector: CandidateSelector,
132 index: InMemoryIndex,
133 installed_packages: InstalledPackages,
134 unavailable_packages: DashMap<PackageName, UnavailablePackage>,
136 incomplete_packages: DashMap<PackageName, DashMap<Version, MetadataUnavailable>>,
138 options: Options,
140 reporter: Option<Arc<dyn Reporter>>,
142}
143
144impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
145 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
146{
147 pub fn new(
166 manifest: Manifest,
167 options: Options,
168 python_requirement: &'a PythonRequirement,
169 env: ResolverEnvironment,
170 current_environment: &MarkerEnvironment,
171 conflicts: Conflicts,
172 tags: Option<&'a Tags>,
173 flat_index: &'a FlatIndex,
174 index: &'a InMemoryIndex,
175 hasher: &'a HashStrategy,
176 build_context: &'a Context,
177 installed_packages: InstalledPackages,
178 database: DistributionDatabase<'a, Context>,
179 ) -> Result<Self, ResolveError> {
180 let provider = DefaultResolverProvider::new(
181 database,
182 flat_index,
183 tags,
184 python_requirement.target(),
185 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
186 hasher,
187 options.exclude_newer.clone(),
188 build_context.build_options(),
189 build_context.capabilities(),
190 );
191
192 Self::new_custom_io(
193 manifest,
194 options,
195 hasher,
196 env,
197 current_environment,
198 tags.cloned(),
199 python_requirement,
200 conflicts,
201 index,
202 build_context.git(),
203 build_context.capabilities(),
204 build_context.locations(),
205 provider,
206 installed_packages,
207 )
208 }
209}
210
211impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
212 Resolver<Provider, InstalledPackages>
213{
214 pub fn new_custom_io(
216 manifest: Manifest,
217 options: Options,
218 hasher: &HashStrategy,
219 env: ResolverEnvironment,
220 current_environment: &MarkerEnvironment,
221 tags: Option<Tags>,
222 python_requirement: &PythonRequirement,
223 conflicts: Conflicts,
224 index: &InMemoryIndex,
225 git: &GitResolver,
226 capabilities: &IndexCapabilities,
227 locations: &IndexLocations,
228 provider: Provider,
229 installed_packages: InstalledPackages,
230 ) -> Result<Self, ResolveError> {
231 let state = ResolverState {
232 index: index.clone(),
233 git: git.clone(),
234 capabilities: capabilities.clone(),
235 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
236 dependency_mode: options.dependency_mode,
237 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
238 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
239 project: manifest.project,
240 workspace_members: manifest.workspace_members,
241 requirements: manifest.requirements,
242 constraints: manifest.constraints,
243 overrides: manifest.overrides,
244 excludes: manifest.excludes,
245 preferences: manifest.preferences,
246 exclusions: manifest.exclusions,
247 hasher: hasher.clone(),
248 locations: locations.clone(),
249 env,
250 current_environment: current_environment.clone(),
251 tags,
252 python_requirement: python_requirement.clone(),
253 conflicts,
254 installed_packages,
255 unavailable_packages: DashMap::default(),
256 incomplete_packages: DashMap::default(),
257 options,
258 reporter: None,
259 };
260 Ok(Self { state, provider })
261 }
262
263 #[must_use]
265 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
266 Self {
267 state: ResolverState {
268 reporter: Some(reporter.clone()),
269 ..self.state
270 },
271 provider: self
272 .provider
273 .with_reporter(reporter.into_distribution_reporter()),
274 }
275 }
276
277 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
279 let state = Arc::new(self.state);
280 let provider = Arc::new(self.provider);
281
282 let (request_sink, request_stream) = mpsc::channel(300);
286
287 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
289
290 let solver = state.clone();
292 let (tx, rx) = oneshot::channel();
293 thread::Builder::new()
294 .name("uv-resolver".into())
295 .spawn(move || {
296 let result = solver.solve(&request_sink);
297
298 let _ = tx.send(result);
300 })
301 .unwrap();
302
303 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
304
305 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
307
308 state.on_complete();
309 resolution
310 }
311}
312
313impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
314 #[instrument(skip_all)]
315 fn solve(
316 self: Arc<Self>,
317 request_sink: &Sender<Request>,
318 ) -> Result<ResolverOutput, ResolveError> {
319 debug!(
320 "Solving with installed Python version: {}",
321 self.python_requirement.exact()
322 );
323 debug!(
324 "Solving with target Python version: {}",
325 self.python_requirement.target()
326 );
327 if !self.options.exclude_newer.is_empty() {
328 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
329 }
330
331 let mut visited = FxHashSet::default();
332
333 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
334 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
335 let prefetcher = BatchPrefetcher::new(
336 self.capabilities.clone(),
337 self.index.clone(),
338 request_sink.clone(),
339 );
340 let state = ForkState::new(
341 pubgrub,
342 self.env.clone(),
343 self.python_requirement.clone(),
344 prefetcher,
345 );
346 let mut preferences = self.preferences.clone();
347 let mut forked_states = self.env.initial_forked_states(state)?;
348 let mut resolutions = vec![];
349
350 'FORK: while let Some(mut state) = forked_states.pop() {
351 if let Some(split) = state.env.end_user_fork_display() {
352 let requires_python = state.python_requirement.target();
353 debug!("Solving {split} (requires-python: {requires_python:?})");
354 }
355 let start = Instant::now();
356 loop {
357 let highest_priority_pkg =
358 if let Some(initial) = state.initial_id.take() {
359 initial
363 } else {
364 let result = state.pubgrub.unit_propagation(state.next);
366 match result {
367 Err(err) => {
368 return Err(self.convert_no_solution_err(
370 err,
371 state.fork_urls,
372 state.fork_indexes,
373 state.env,
374 self.current_environment.clone(),
375 Some(&self.options.exclude_newer),
376 &visited,
377 ));
378 }
379 Ok(conflicts) => {
380 for (affected, incompatibility) in conflicts {
381 state.record_conflict(affected, None, incompatibility);
384 }
385 }
386 }
387
388 if self.dependency_mode.is_transitive() {
390 Self::pre_visit(
391 state
392 .pubgrub
393 .partial_solution
394 .prioritized_packages()
395 .map(|(id, range)| (&state.pubgrub.package_store[id], range)),
396 &self.urls,
397 &self.indexes,
398 &state.python_requirement,
399 request_sink,
400 )?;
401 }
402
403 Self::reprioritize_conflicts(&mut state);
404
405 trace!(
406 "Assigned packages: {}",
407 state
408 .pubgrub
409 .partial_solution
410 .extract_solution()
411 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
412 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
413 .join(", ")
414 );
415 let Some((highest_priority_pkg, _)) =
419 state.pubgrub.partial_solution.pick_highest_priority_pkg(
420 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
421 )
422 else {
423 if tracing::enabled!(Level::DEBUG) {
425 state.prefetcher.log_tried_versions();
426 }
427 debug!(
428 "{} resolution took {:.3}s",
429 state.env,
430 start.elapsed().as_secs_f32()
431 );
432
433 let resolution = state.into_resolution();
434
435 if matches!(
444 self.options.resolution_mode,
445 ResolutionMode::Lowest | ResolutionMode::Highest
446 ) {
447 for (package, version) in &resolution.nodes {
448 preferences.insert(
449 package.name.clone(),
450 package.index.clone(),
451 resolution
452 .env
453 .try_universal_markers()
454 .unwrap_or(UniversalMarker::TRUE),
455 version.clone(),
456 PreferenceSource::Resolver,
457 );
458 }
459 }
460
461 resolutions.push(resolution);
462 continue 'FORK;
463 };
464 trace!(
465 "Chose package for decision: {}. remaining choices: {}",
466 state.pubgrub.package_store[highest_priority_pkg],
467 state
468 .pubgrub
469 .partial_solution
470 .undecided_packages()
471 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
472 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
473 .join(", ")
474 );
475
476 highest_priority_pkg
477 };
478
479 state.next = highest_priority_pkg;
480
481 let next_id = state.next;
483 let next_package = &state.pubgrub.package_store[state.next];
484
485 let url = next_package
486 .name()
487 .and_then(|name| state.fork_urls.get(name));
488 let index = next_package
489 .name()
490 .and_then(|name| state.fork_indexes.get(name));
491
492 self.request_package(next_package, url, index, request_sink)?;
504
505 let version = if let Some(version) = state.initial_version.take() {
506 version
510 } else {
511 let term_intersection = state
512 .pubgrub
513 .partial_solution
514 .term_intersection_for_package(next_id)
515 .expect("a package was chosen but we don't have a term");
516 let decision = self.choose_version(
517 next_package,
518 next_id,
519 index.map(IndexMetadata::url),
520 term_intersection.unwrap_positive(),
521 &mut state.pins,
522 &preferences,
523 &state.fork_urls,
524 &state.env,
525 &state.python_requirement,
526 &state.pubgrub,
527 &mut visited,
528 request_sink,
529 )?;
530
531 let Some(version) = decision else {
533 debug!("No compatible version found for: {next_package}");
534
535 let term_intersection = state
536 .pubgrub
537 .partial_solution
538 .term_intersection_for_package(next_id)
539 .expect("a package was chosen but we don't have a term");
540
541 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
542 if let Some(entry) = self.unavailable_packages.get(name) {
544 state
545 .pubgrub
546 .add_incompatibility(Incompatibility::custom_term(
547 next_id,
548 term_intersection.clone(),
549 UnavailableReason::Package(entry.clone()),
550 ));
551 continue;
552 }
553 }
554
555 state
556 .pubgrub
557 .add_incompatibility(Incompatibility::no_versions(
558 next_id,
559 term_intersection.clone(),
560 ));
561 continue;
562 };
563
564 let version = match version {
565 ResolverVersion::Unforked(version) => version,
566 ResolverVersion::Forked(forks) => {
567 forked_states.extend(self.version_forks_to_fork_states(state, forks));
568 continue 'FORK;
569 }
570 ResolverVersion::Unavailable(version, reason) => {
571 state.add_unavailable_version(version, reason);
572 continue;
573 }
574 };
575
576 if url.is_none() {
578 state.prefetcher.prefetch_batches(
579 next_package,
580 index,
581 &version,
582 term_intersection.unwrap_positive(),
583 state
584 .pubgrub
585 .partial_solution
586 .unchanging_term_for_package(next_id),
587 &state.python_requirement,
588 &self.selector,
589 &state.env,
590 )?;
591 }
592
593 version
594 };
595
596 state.prefetcher.version_tried(next_package, &version);
597
598 self.on_progress(next_package, &version);
599
600 if !state
601 .added_dependencies
602 .entry(next_id)
603 .or_default()
604 .insert(version.clone())
605 {
606 state
609 .pubgrub
610 .partial_solution
611 .add_decision(next_id, version);
612 continue;
613 }
614
615 let forked_deps = self.get_dependencies_forking(
617 next_id,
618 next_package,
619 &version,
620 &state.pins,
621 &state.fork_urls,
622 &state.env,
623 &state.python_requirement,
624 &state.pubgrub,
625 )?;
626
627 match forked_deps {
628 ForkedDependencies::Unavailable(reason) => {
629 state
632 .pubgrub
633 .add_incompatibility(Incompatibility::custom_version(
634 next_id,
635 version.clone(),
636 UnavailableReason::Version(reason),
637 ));
638 }
639 ForkedDependencies::Unforked(dependencies) => {
640 state
642 .visit_package_version_dependencies(
643 next_id,
644 &version,
645 &self.urls,
646 &self.indexes,
647 &dependencies,
648 &self.git,
649 &self.workspace_members,
650 self.selector.resolution_strategy(),
651 )
652 .map_err(|err| {
653 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
654 })?;
655
656 self.visit_dependencies(&dependencies, &state, request_sink)
658 .map_err(|err| {
659 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
660 })?;
661
662 state.add_package_version_dependencies(next_id, &version, dependencies);
664 }
665 ForkedDependencies::Forked {
666 mut forks,
667 diverging_packages,
668 } => {
669 debug!(
670 "Pre-fork {} took {:.3}s",
671 state.env,
672 start.elapsed().as_secs_f32()
673 );
674
675 match (self.options.fork_strategy, self.options.resolution_mode) {
677 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
678 forks.sort_by(|a, b| {
682 a.cmp_requires_python(b)
683 .reverse()
684 .then_with(|| a.cmp_upper_bounds(b))
685 });
686 }
687 (ForkStrategy::RequiresPython, _) => {
688 forks.sort_by(|a, b| {
692 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
693 });
694 }
695 }
696
697 for new_fork_state in self.forks_to_fork_states(
698 state,
699 &version,
700 forks,
701 request_sink,
702 &diverging_packages,
703 ) {
704 forked_states.push(new_fork_state?);
705 }
706 continue 'FORK;
707 }
708 }
709 }
710 }
711 if resolutions.len() > 1 {
712 info!(
713 "Solved your requirements for {} environments",
714 resolutions.len()
715 );
716 }
717 if tracing::enabled!(Level::DEBUG) {
718 for resolution in &resolutions {
719 if let Some(env) = resolution.env.end_user_fork_display() {
720 let packages: FxHashSet<_> = resolution
721 .nodes
722 .keys()
723 .map(|package| &package.name)
724 .collect();
725 debug!(
726 "Distinct solution for {env} with {} package(s)",
727 packages.len()
728 );
729 }
730 }
731 }
732 for resolution in &resolutions {
733 Self::trace_resolution(resolution);
734 }
735 ResolverOutput::from_state(
736 &resolutions,
737 &self.requirements,
738 &self.constraints,
739 &self.overrides,
740 &self.preferences,
741 &self.index,
742 &self.git,
743 &self.python_requirement,
744 &self.conflicts,
745 self.selector.resolution_strategy(),
746 self.options.clone(),
747 )
748 }
749
750 fn reprioritize_conflicts(state: &mut ForkState) {
754 for package in state.conflict_tracker.prioritize.drain(..) {
755 let changed = state
756 .priorities
757 .mark_conflict_early(&state.pubgrub.package_store[package]);
758 if changed {
759 debug!(
760 "Package {} has too many conflicts (affected), prioritizing",
761 &state.pubgrub.package_store[package]
762 );
763 } else {
764 debug!(
765 "Package {} has too many conflicts (affected), already {:?}",
766 state.pubgrub.package_store[package],
767 state.priorities.get(&state.pubgrub.package_store[package])
768 );
769 }
770 }
771
772 for package in state.conflict_tracker.deprioritize.drain(..) {
773 let changed = state
774 .priorities
775 .mark_conflict_late(&state.pubgrub.package_store[package]);
776 if changed {
777 debug!(
778 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
779 state.pubgrub.package_store[package],
780 );
781 let backtrack_level = state.pubgrub.backtrack_package(package);
782 if let Some(backtrack_level) = backtrack_level {
783 debug!("Backtracked {backtrack_level} decisions");
784 } else {
785 debug!(
786 "Package {} is not decided, cannot backtrack",
787 state.pubgrub.package_store[package]
788 );
789 }
790 } else {
791 debug!(
792 "Package {} has too many conflicts (culprit), already {:?}",
793 state.pubgrub.package_store[package],
794 state.priorities.get(&state.pubgrub.package_store[package])
795 );
796 }
797 }
798 }
799
800 fn trace_resolution(combined: &Resolution) {
806 if !tracing::enabled!(Level::TRACE) {
807 return;
808 }
809 trace!("Resolution: {:?}", combined.env);
810 for edge in &combined.edges {
811 trace!(
812 "Resolution edge: {} -> {}",
813 edge.from
814 .as_ref()
815 .map(PackageName::as_str)
816 .unwrap_or("ROOT"),
817 edge.to,
818 );
819 let mut msg = String::new();
822 write!(msg, "{}", edge.from_version).unwrap();
823 if let Some(ref extra) = edge.from_extra {
824 write!(msg, " (extra: {extra})").unwrap();
825 }
826 if let Some(ref dev) = edge.from_group {
827 write!(msg, " (group: {dev})").unwrap();
828 }
829
830 write!(msg, " -> ").unwrap();
831
832 write!(msg, "{}", edge.to_version).unwrap();
833 if let Some(ref extra) = edge.to_extra {
834 write!(msg, " (extra: {extra})").unwrap();
835 }
836 if let Some(ref dev) = edge.to_group {
837 write!(msg, " (group: {dev})").unwrap();
838 }
839 if let Some(marker) = edge.marker.contents() {
840 write!(msg, " ; {marker}").unwrap();
841 }
842 trace!("Resolution edge: {msg}");
843 }
844 }
845
846 fn forks_to_fork_states<'a>(
848 &'a self,
849 current_state: ForkState,
850 version: &'a Version,
851 forks: Vec<Fork>,
852 request_sink: &'a Sender<Request>,
853 diverging_packages: &'a [PackageName],
854 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
855 debug!(
856 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
857 current_state.pubgrub.package_store[current_state.next],
858 version,
859 diverging_packages
860 .iter()
861 .map(ToString::to_string)
862 .join(", "),
863 forks.len(),
864 if forks.len() == 1 { "" } else { "s" }
865 );
866 assert!(forks.len() >= 2);
867 let package = current_state.next;
873 let mut cur_state = Some(current_state);
874 let forks_len = forks.len();
875 forks
876 .into_iter()
877 .enumerate()
878 .map(move |(i, fork)| {
879 let is_last = i == forks_len - 1;
880 let forked_state = cur_state.take().unwrap();
881 if !is_last {
882 cur_state = Some(forked_state.clone());
883 }
884
885 let env = fork.env.clone();
886 (fork, forked_state.with_env(env))
887 })
888 .map(move |(fork, mut forked_state)| {
889 forked_state
891 .visit_package_version_dependencies(
892 package,
893 version,
894 &self.urls,
895 &self.indexes,
896 &fork.dependencies,
897 &self.git,
898 &self.workspace_members,
899 self.selector.resolution_strategy(),
900 )
901 .map_err(|err| {
902 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
903 })?;
904
905 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
907 .map_err(|err| {
908 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
909 })?;
910
911 forked_state.add_package_version_dependencies(package, version, fork.dependencies);
913
914 Ok(forked_state)
915 })
916 }
917
918 #[expect(clippy::unused_self)]
920 fn version_forks_to_fork_states(
921 &self,
922 current_state: ForkState,
923 forks: Vec<VersionFork>,
924 ) -> impl Iterator<Item = ForkState> + '_ {
925 let mut cur_state = Some(current_state);
931 let forks_len = forks.len();
932 forks.into_iter().enumerate().map(move |(i, fork)| {
933 let is_last = i == forks_len - 1;
934 let mut forked_state = cur_state.take().unwrap();
935 if !is_last {
936 cur_state = Some(forked_state.clone());
937 }
938 forked_state.initial_id = Some(fork.id);
939 forked_state.initial_version = fork.version;
940 forked_state.with_env(fork.env)
941 })
942 }
943
944 fn visit_dependencies(
946 &self,
947 dependencies: &[PubGrubDependency],
948 state: &ForkState,
949 request_sink: &Sender<Request>,
950 ) -> Result<(), ResolveError> {
951 for dependency in dependencies {
952 let PubGrubDependency {
953 package,
954 version: _,
955 parent: _,
956 url: _,
957 } = dependency;
958 let url = package.name().and_then(|name| state.fork_urls.get(name));
959 let index = package.name().and_then(|name| state.fork_indexes.get(name));
960 self.visit_package(package, url, index, request_sink)?;
961 }
962 Ok(())
963 }
964
965 fn visit_package(
968 &self,
969 package: &PubGrubPackage,
970 url: Option<&VerbatimParsedUrl>,
971 index: Option<&IndexMetadata>,
972 request_sink: &Sender<Request>,
973 ) -> Result<(), ResolveError> {
974 if url.is_none()
976 && package
977 .name()
978 .map(|name| self.urls.any_url(name))
979 .unwrap_or(true)
980 {
981 return Ok(());
982 }
983
984 self.request_package(package, url, index, request_sink)
985 }
986
987 fn request_package(
988 &self,
989 package: &PubGrubPackage,
990 url: Option<&VerbatimParsedUrl>,
991 index: Option<&IndexMetadata>,
992 request_sink: &Sender<Request>,
993 ) -> Result<(), ResolveError> {
994 let Some(name) = package.name_no_root() else {
996 return Ok(());
997 };
998
999 if let Some(url) = url {
1000 if !self.hasher.allows_url(&url.verbatim) {
1002 return Err(ResolveError::UnhashedPackage(name.clone()));
1003 }
1004
1005 let dist = Dist::from_url(name.clone(), url.clone())?;
1007 if self.index.distributions().register(dist.version_id()) {
1008 request_sink.blocking_send(Request::Dist(dist))?;
1009 }
1010 } else if let Some(index) = index {
1011 if self
1013 .index
1014 .explicit()
1015 .register((name.clone(), index.url().clone()))
1016 {
1017 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1018 }
1019 } else {
1020 if self.index.implicit().register(name.clone()) {
1022 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1023 }
1024 }
1025 Ok(())
1026 }
1027
1028 fn pre_visit<'data>(
1031 packages: impl Iterator<Item = (&'data PubGrubPackage, &'data Range<Version>)>,
1032 urls: &Urls,
1033 indexes: &Indexes,
1034 python_requirement: &PythonRequirement,
1035 request_sink: &Sender<Request>,
1036 ) -> Result<(), ResolveError> {
1037 for (package, range) in packages {
1040 let PubGrubPackageInner::Package {
1041 name,
1042 extra: None,
1043 group: None,
1044 marker: MarkerTree::TRUE,
1045 } = &**package
1046 else {
1047 continue;
1048 };
1049 if urls.any_url(name) {
1052 continue;
1053 }
1054 if indexes.contains_key(name) {
1056 continue;
1057 }
1058 request_sink.blocking_send(Request::Prefetch(
1059 name.clone(),
1060 range.clone(),
1061 python_requirement.clone(),
1062 ))?;
1063 }
1064 Ok(())
1065 }
1066
1067 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1075 fn choose_version(
1076 &self,
1077 package: &PubGrubPackage,
1078 id: Id<PubGrubPackage>,
1079 index: Option<&IndexUrl>,
1080 range: &Range<Version>,
1081 pins: &mut FilePins,
1082 preferences: &Preferences,
1083 fork_urls: &ForkUrls,
1084 env: &ResolverEnvironment,
1085 python_requirement: &PythonRequirement,
1086 pubgrub: &State<UvDependencyProvider>,
1087 visited: &mut FxHashSet<PackageName>,
1088 request_sink: &Sender<Request>,
1089 ) -> Result<Option<ResolverVersion>, ResolveError> {
1090 match &**package {
1091 PubGrubPackageInner::Root(_) => {
1092 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1093 }
1094
1095 PubGrubPackageInner::Python(_) => {
1096 Ok(None)
1099 }
1100
1101 PubGrubPackageInner::System(_) => {
1102 let Some(version) = range.as_singleton() else {
1105 return Ok(None);
1106 };
1107 Ok(Some(ResolverVersion::Unforked(version.clone())))
1108 }
1109
1110 PubGrubPackageInner::Marker { name, .. }
1111 | PubGrubPackageInner::Extra { name, .. }
1112 | PubGrubPackageInner::Group { name, .. }
1113 | PubGrubPackageInner::Package { name, .. } => {
1114 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1115 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1116 } else {
1117 self.choose_version_registry(
1118 package,
1119 id,
1120 name,
1121 index,
1122 range,
1123 preferences,
1124 env,
1125 python_requirement,
1126 pubgrub,
1127 pins,
1128 visited,
1129 request_sink,
1130 )
1131 }
1132 }
1133 }
1134 }
1135
1136 fn choose_version_url(
1139 &self,
1140 id: Id<PubGrubPackage>,
1141 name: &PackageName,
1142 range: &Range<Version>,
1143 url: &VerbatimParsedUrl,
1144 env: &ResolverEnvironment,
1145 python_requirement: &PythonRequirement,
1146 pubgrub: &State<UvDependencyProvider>,
1147 ) -> Result<Option<ResolverVersion>, ResolveError> {
1148 debug!(
1149 "Searching for a compatible version of {name} @ {} ({range})",
1150 url.verbatim
1151 );
1152
1153 let dist = PubGrubDistribution::from_url(name, url);
1154 let response = self
1155 .index
1156 .distributions()
1157 .wait_blocking(&dist.version_id())
1158 .ok_or_else(|| ResolveError::UnregisteredTask(dist.version_id().to_string()))?;
1159
1160 let metadata = match &*response {
1162 MetadataResponse::Found(archive) => &archive.metadata,
1163 MetadataResponse::Unavailable(reason) => {
1164 self.unavailable_packages
1165 .insert(name.clone(), reason.into());
1166 return Ok(None);
1167 }
1168 MetadataResponse::Error(dist, err) => {
1171 return Err(ResolveError::Dist(
1172 DistErrorKind::from_requested_dist(dist, &**err),
1173 dist.clone(),
1174 DerivationChain::default(),
1175 err.clone(),
1176 ));
1177 }
1178 };
1179
1180 let version = &metadata.version;
1181
1182 if !range.contains(version) {
1184 return Ok(None);
1185 }
1186
1187 let dist = Dist::from_url(name.clone(), url.clone())?;
1190 if let Dist::Built(dist) = dist {
1191 let filename = match &dist {
1192 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1193 BuiltDist::DirectUrl(dist) => &dist.filename,
1194 BuiltDist::Path(dist) => &dist.filename,
1195 };
1196
1197 if env.marker_environment().is_none() && !self.options.required_environments.is_empty()
1199 {
1200 let wheel_marker = implied_markers(filename);
1201 for environment_marker in self.options.required_environments.iter().copied() {
1203 if env.included_by_marker(environment_marker)
1205 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1206 {
1207 if wheel_marker.is_disjoint(environment_marker) {
1209 return Ok(Some(ResolverVersion::Unavailable(
1210 version.clone(),
1211 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1212 IncompatibleWheel::MissingPlatform(environment_marker),
1213 )),
1214 )));
1215 }
1216 }
1217 }
1218 }
1219
1220 if !python_requirement.target().matches_wheel_tag(filename) {
1222 return Ok(Some(ResolverVersion::Unavailable(
1223 filename.version.clone(),
1224 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1225 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1226 )),
1227 )));
1228 }
1229 }
1230
1231 if let Some(requires_python) = metadata.requires_python.as_ref() {
1233 if !python_requirement.target().is_contained_by(requires_python) {
1234 let kind = if python_requirement.installed() == python_requirement.target() {
1235 PythonRequirementKind::Installed
1236 } else {
1237 PythonRequirementKind::Target
1238 };
1239 return Ok(Some(ResolverVersion::Unavailable(
1240 version.clone(),
1241 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1242 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1243 )),
1244 )));
1245 }
1246 }
1247
1248 Ok(Some(ResolverVersion::Unforked(version.clone())))
1249 }
1250
1251 fn choose_version_registry(
1254 &self,
1255 package: &PubGrubPackage,
1256 id: Id<PubGrubPackage>,
1257 name: &PackageName,
1258 index: Option<&IndexUrl>,
1259 range: &Range<Version>,
1260 preferences: &Preferences,
1261 env: &ResolverEnvironment,
1262 python_requirement: &PythonRequirement,
1263 pubgrub: &State<UvDependencyProvider>,
1264 pins: &mut FilePins,
1265 visited: &mut FxHashSet<PackageName>,
1266 request_sink: &Sender<Request>,
1267 ) -> Result<Option<ResolverVersion>, ResolveError> {
1268 let versions_response = if let Some(index) = index {
1270 self.index
1271 .explicit()
1272 .wait_blocking(&(name.clone(), index.clone()))
1273 .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
1274 } else {
1275 self.index
1276 .implicit()
1277 .wait_blocking(name)
1278 .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
1279 };
1280 visited.insert(name.clone());
1281
1282 let version_maps = match *versions_response {
1283 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1284 VersionsResponse::NoIndex => {
1285 self.unavailable_packages
1286 .insert(name.clone(), UnavailablePackage::NoIndex);
1287 &[]
1288 }
1289 VersionsResponse::Offline => {
1290 self.unavailable_packages
1291 .insert(name.clone(), UnavailablePackage::Offline);
1292 &[]
1293 }
1294 VersionsResponse::NotFound => {
1295 self.unavailable_packages
1296 .insert(name.clone(), UnavailablePackage::NotFound);
1297 &[]
1298 }
1299 };
1300
1301 debug!("Searching for a compatible version of {package} ({range})");
1302
1303 let Some(candidate) = self.selector.select(
1305 name,
1306 range,
1307 version_maps,
1308 preferences,
1309 &self.installed_packages,
1310 &self.exclusions,
1311 index,
1312 env,
1313 self.tags.as_ref(),
1314 ) else {
1315 return Ok(None);
1317 };
1318
1319 let dist = match candidate.dist() {
1320 CandidateDist::Compatible(dist) => dist,
1321 CandidateDist::Incompatible {
1322 incompatible_dist: incompatibility,
1323 prioritized_dist: _,
1324 } => {
1325 return Ok(Some(ResolverVersion::Unavailable(
1327 candidate.version().clone(),
1328 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1331 )));
1332 }
1333 };
1334
1335 if let Some((requires_python, incompatibility)) =
1337 Self::check_requires_python(dist, python_requirement)
1338 {
1339 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1340 if env.marker_environment().is_none() {
1341 let forks = fork_version_by_python_requirement(
1342 requires_python,
1343 python_requirement,
1344 env,
1345 );
1346 if !forks.is_empty() {
1347 debug!(
1348 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1349 python_requirement.target(),
1350 requires_python,
1351 name,
1352 candidate.version(),
1353 forks
1354 .iter()
1355 .map(ToString::to_string)
1356 .collect::<Vec<_>>()
1357 .join(", ")
1358 );
1359 let forks = forks
1360 .into_iter()
1361 .map(|env| VersionFork {
1362 env,
1363 id,
1364 version: None,
1365 })
1366 .collect();
1367 return Ok(Some(ResolverVersion::Forked(forks)));
1368 }
1369 }
1370 }
1371
1372 return Ok(Some(ResolverVersion::Unavailable(
1373 candidate.version().clone(),
1374 UnavailableVersion::IncompatibleDist(incompatibility),
1375 )));
1376 }
1377
1378 if let Some(forked) = self.fork_version_registry(
1380 &candidate,
1381 dist,
1382 version_maps,
1383 package,
1384 id,
1385 name,
1386 index,
1387 range,
1388 preferences,
1389 env,
1390 pubgrub,
1391 pins,
1392 request_sink,
1393 )? {
1394 return Ok(Some(forked));
1395 }
1396
1397 let filename = match dist.for_installation() {
1398 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1399 .filename()
1400 .unwrap_or(Cow::Borrowed("unknown filename")),
1401 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1402 .filename()
1403 .unwrap_or(Cow::Borrowed("unknown filename")),
1404 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1405 };
1406
1407 debug!(
1408 "Selecting: {}=={} [{}] ({})",
1409 name,
1410 candidate.version(),
1411 candidate.choice_kind(),
1412 filename,
1413 );
1414 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1415
1416 let version = candidate.version().clone();
1417 Ok(Some(ResolverVersion::Unforked(version)))
1418 }
1419
1420 fn fork_version_registry(
1433 &self,
1434 candidate: &Candidate,
1435 dist: &CompatibleDist,
1436 version_maps: &[VersionMap],
1437 package: &PubGrubPackage,
1438 id: Id<PubGrubPackage>,
1439 name: &PackageName,
1440 index: Option<&IndexUrl>,
1441 range: &Range<Version>,
1442 preferences: &Preferences,
1443 env: &ResolverEnvironment,
1444 pubgrub: &State<UvDependencyProvider>,
1445 pins: &mut FilePins,
1446 request_sink: &Sender<Request>,
1447 ) -> Result<Option<ResolverVersion>, ResolveError> {
1448 if env.marker_environment().is_some() {
1450 return Ok(None);
1451 }
1452
1453 if dist.implied_markers().is_true() {
1456 return Ok(None);
1457 }
1458
1459 for marker in self.options.required_environments.iter().copied() {
1461 if env.included_by_marker(marker) {
1463 if dist.implied_markers().is_disjoint(marker)
1465 && !find_environments(id, pubgrub).is_disjoint(marker)
1466 {
1467 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1469 return Ok(Some(ResolverVersion::Unavailable(
1470 candidate.version().clone(),
1471 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1472 IncompatibleWheel::MissingPlatform(marker),
1473 )),
1474 )));
1475 };
1476
1477 debug!(
1478 "Forking on required platform `{}` for {}=={} ({})",
1479 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1480 name,
1481 candidate.version(),
1482 [&left, &right]
1483 .iter()
1484 .map(ToString::to_string)
1485 .collect::<Vec<_>>()
1486 .join(", ")
1487 );
1488 let forks = vec![
1489 VersionFork {
1490 env: left,
1491 id,
1492 version: None,
1493 },
1494 VersionFork {
1495 env: right,
1496 id,
1497 version: None,
1498 },
1499 ];
1500 return Ok(Some(ResolverVersion::Forked(forks)));
1501 }
1502 }
1503 }
1504
1505 if !candidate.version().is_local() {
1507 return Ok(None);
1508 }
1509
1510 debug!(
1511 "Looking at local version: {}=={}",
1512 name,
1513 candidate.version()
1514 );
1515
1516 let range = range.clone().intersection(&Range::singleton(
1518 candidate.version().clone().without_local(),
1519 ));
1520
1521 let Some(base_candidate) = self.selector.select(
1522 name,
1523 &range,
1524 version_maps,
1525 preferences,
1526 &self.installed_packages,
1527 &self.exclusions,
1528 index,
1529 env,
1530 self.tags.as_ref(),
1531 ) else {
1532 return Ok(None);
1533 };
1534 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1535 return Ok(None);
1536 };
1537
1538 let mut remainder = {
1540 let mut remainder = base_dist.implied_markers();
1541 remainder.and(dist.implied_markers().negate());
1542 remainder
1543 };
1544 if remainder.is_false() {
1545 return Ok(None);
1546 }
1547
1548 if !env.included_by_marker(remainder) {
1552 return Ok(None);
1553 }
1554
1555 if !env.included_by_marker(dist.implied_markers()) {
1558 let filename = match dist.for_installation() {
1559 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1560 .filename()
1561 .unwrap_or(Cow::Borrowed("unknown filename")),
1562 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1563 .filename()
1564 .unwrap_or(Cow::Borrowed("unknown filename")),
1565 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1566 };
1567
1568 debug!(
1569 "Preferring non-local candidate: {}=={} [{}] ({})",
1570 name,
1571 base_candidate.version(),
1572 base_candidate.choice_kind(),
1573 filename,
1574 );
1575 self.visit_candidate(
1576 &base_candidate,
1577 base_dist,
1578 package,
1579 name,
1580 pins,
1581 request_sink,
1582 )?;
1583
1584 return Ok(Some(ResolverVersion::Unforked(
1585 base_candidate.version().clone(),
1586 )));
1587 }
1588
1589 for value in [
1598 arcstr::literal!("darwin"),
1599 arcstr::literal!("linux"),
1600 arcstr::literal!("win32"),
1601 ] {
1602 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1603 key: MarkerValueString::SysPlatform,
1604 operator: MarkerOperator::Equal,
1605 value,
1606 });
1607 if dist.implied_markers().is_disjoint(sys_platform)
1608 && !remainder.is_disjoint(sys_platform)
1609 {
1610 remainder.or(sys_platform);
1611 }
1612 }
1613
1614 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1616 return Ok(None);
1617 };
1618
1619 debug!(
1620 "Forking platform for {}=={} ({})",
1621 name,
1622 candidate.version(),
1623 [&base_env, &local_env]
1624 .iter()
1625 .map(ToString::to_string)
1626 .collect::<Vec<_>>()
1627 .join(", ")
1628 );
1629 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1630 self.visit_candidate(
1631 &base_candidate,
1632 base_dist,
1633 package,
1634 name,
1635 pins,
1636 request_sink,
1637 )?;
1638
1639 let forks = vec![
1640 VersionFork {
1641 env: base_env.clone(),
1642 id,
1643 version: Some(base_candidate.version().clone()),
1644 },
1645 VersionFork {
1646 env: local_env.clone(),
1647 id,
1648 version: Some(candidate.version().clone()),
1649 },
1650 ];
1651 Ok(Some(ResolverVersion::Forked(forks)))
1652 }
1653
1654 fn visit_candidate(
1656 &self,
1657 candidate: &Candidate,
1658 dist: &CompatibleDist,
1659 package: &PubGrubPackage,
1660 name: &PackageName,
1661 pins: &mut FilePins,
1662 request_sink: &Sender<Request>,
1663 ) -> Result<(), ResolveError> {
1664 pins.insert(candidate, dist);
1667
1668 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1670 if self.dependency_mode.is_transitive() {
1671 if self.index.distributions().register(candidate.version_id()) {
1672 if name != dist.name() {
1673 return Err(ResolveError::MismatchedPackageName {
1674 request: "distribution",
1675 expected: name.clone(),
1676 actual: dist.name().clone(),
1677 });
1678 }
1679 if !self
1681 .hasher
1682 .allows_package(candidate.name(), candidate.version())
1683 {
1684 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1685 }
1686
1687 let request = Request::from(dist.for_resolution());
1688 request_sink.blocking_send(request)?;
1689 }
1690 }
1691 }
1692
1693 Ok(())
1694 }
1695
1696 fn check_requires_python<'dist>(
1699 dist: &'dist CompatibleDist,
1700 python_requirement: &PythonRequirement,
1701 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1702 let requires_python = dist.requires_python()?;
1703 if python_requirement.target().is_contained_by(requires_python) {
1704 None
1705 } else {
1706 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1707 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1708 requires_python.clone(),
1709 if python_requirement.installed() == python_requirement.target() {
1710 PythonRequirementKind::Installed
1711 } else {
1712 PythonRequirementKind::Target
1713 },
1714 ))
1715 } else {
1716 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1717 requires_python.clone(),
1718 if python_requirement.installed() == python_requirement.target() {
1719 PythonRequirementKind::Installed
1720 } else {
1721 PythonRequirementKind::Target
1722 },
1723 ))
1724 };
1725 Some((requires_python, incompatibility))
1726 }
1727 }
1728
1729 #[instrument(skip_all, fields(%package, %version))]
1731 fn get_dependencies_forking(
1732 &self,
1733 id: Id<PubGrubPackage>,
1734 package: &PubGrubPackage,
1735 version: &Version,
1736 pins: &FilePins,
1737 fork_urls: &ForkUrls,
1738 env: &ResolverEnvironment,
1739 python_requirement: &PythonRequirement,
1740 pubgrub: &State<UvDependencyProvider>,
1741 ) -> Result<ForkedDependencies, ResolveError> {
1742 let result = self.get_dependencies(
1743 id,
1744 package,
1745 version,
1746 pins,
1747 fork_urls,
1748 env,
1749 python_requirement,
1750 pubgrub,
1751 );
1752 if env.marker_environment().is_some() {
1753 result.map(|deps| match deps {
1754 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1755 ForkedDependencies::Unforked(deps)
1756 }
1757 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1758 })
1759 } else {
1760 Ok(result?.fork(env, python_requirement, &self.conflicts))
1761 }
1762 }
1763
1764 #[instrument(skip_all, fields(%package, %version))]
1766 fn get_dependencies(
1767 &self,
1768 id: Id<PubGrubPackage>,
1769 package: &PubGrubPackage,
1770 version: &Version,
1771 pins: &FilePins,
1772 fork_urls: &ForkUrls,
1773 env: &ResolverEnvironment,
1774 python_requirement: &PythonRequirement,
1775 pubgrub: &State<UvDependencyProvider>,
1776 ) -> Result<Dependencies, ResolveError> {
1777 let url = package.name().and_then(|name| fork_urls.get(name));
1778 let dependencies = match &**package {
1779 PubGrubPackageInner::Root(_) => {
1780 let no_dev_deps = BTreeMap::default();
1781 let requirements = self.flatten_requirements(
1782 &self.requirements,
1783 &no_dev_deps,
1784 None,
1785 None,
1786 None,
1787 env,
1788 python_requirement,
1789 );
1790
1791 requirements
1792 .flat_map(move |requirement| {
1793 PubGrubDependency::from_requirement(
1794 &self.conflicts,
1795 requirement,
1796 None,
1797 Some(package),
1798 )
1799 })
1800 .collect()
1801 }
1802
1803 PubGrubPackageInner::Package {
1804 name,
1805 extra,
1806 group,
1807 marker: _,
1808 } => {
1809 if self.dependency_mode.is_direct() {
1811 return Ok(Dependencies::Unforkable(Vec::default()));
1812 }
1813
1814 let dist = match url {
1816 Some(url) => PubGrubDistribution::from_url(name, url),
1817 None => PubGrubDistribution::from_registry(name, version),
1818 };
1819 let version_id = dist.version_id();
1820
1821 if self.dependency_mode.is_transitive()
1823 && self.unavailable_packages.get(name).is_some()
1824 && self.installed_packages.get_packages(name).is_empty()
1825 {
1826 debug_assert!(
1827 false,
1828 "Dependencies were requested for a package that is not available"
1829 );
1830 return Err(ResolveError::PackageUnavailable(name.clone()));
1831 }
1832
1833 let response = self
1835 .index
1836 .distributions()
1837 .wait_blocking(&version_id)
1838 .ok_or_else(|| ResolveError::UnregisteredTask(version_id.to_string()))?;
1839
1840 let metadata = match &*response {
1841 MetadataResponse::Found(archive) => &archive.metadata,
1842 MetadataResponse::Unavailable(reason) => {
1843 let unavailable_version = UnavailableVersion::from(reason);
1844 let message = unavailable_version.singular_message();
1845 if let Some(err) = reason.source() {
1846 warn!("{name} {message}: {err}");
1848 } else {
1849 warn!("{name} {message}");
1850 }
1851 self.incomplete_packages
1852 .entry(name.clone())
1853 .or_default()
1854 .insert(version.clone(), reason.clone());
1855 return Ok(Dependencies::Unavailable(unavailable_version));
1856 }
1857 MetadataResponse::Error(dist, err) => {
1858 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1859 .unwrap_or_default();
1860 return Err(ResolveError::Dist(
1861 DistErrorKind::from_requested_dist(dist, &**err),
1862 dist.clone(),
1863 chain,
1864 err.clone(),
1865 ));
1866 }
1867 };
1868
1869 if let Some(requires_python) = &metadata.requires_python {
1872 if !python_requirement.target().is_contained_by(requires_python) {
1873 return Ok(Dependencies::Unavailable(
1874 UnavailableVersion::RequiresPython(requires_python.clone()),
1875 ));
1876 }
1877 }
1878
1879 let system_dependencies = self
1881 .options
1882 .torch_backend
1883 .as_ref()
1884 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1885 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1886 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1887 .map(IndexUrl::url)
1888 .and_then(SystemDependency::from_index)
1889 .into_iter()
1890 .inspect(|system_dependency| {
1891 debug!(
1892 "Adding system dependency `{}` for `{package}@{version}`",
1893 system_dependency
1894 );
1895 })
1896 .map(PubGrubDependency::from);
1897
1898 let requirements = self.flatten_requirements(
1899 &metadata.requires_dist,
1900 &metadata.dependency_groups,
1901 extra.as_ref(),
1902 group.as_ref(),
1903 Some(name),
1904 env,
1905 python_requirement,
1906 );
1907
1908 requirements
1909 .filter(|requirement| !self.excludes.contains(&requirement.name))
1910 .flat_map(|requirement| {
1911 PubGrubDependency::from_requirement(
1912 &self.conflicts,
1913 requirement,
1914 group.as_ref(),
1915 Some(package),
1916 )
1917 })
1918 .chain(system_dependencies)
1919 .collect()
1920 }
1921
1922 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1923
1924 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1925
1926 PubGrubPackageInner::Marker { name, marker } => {
1928 return Ok(Dependencies::Unforkable(
1929 [MarkerTree::TRUE, *marker]
1930 .into_iter()
1931 .map(move |marker| PubGrubDependency {
1932 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1933 name: name.clone(),
1934 extra: None,
1935 group: None,
1936 marker,
1937 }),
1938 version: Range::singleton(version.clone()),
1939 parent: None,
1940 url: None,
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 extra: extra.cloned(),
1963 group: None,
1964 marker,
1965 }),
1966 version: Range::singleton(version.clone()),
1967 parent: None,
1968 url: None,
1969 })
1970 })
1971 .collect(),
1972 ));
1973 }
1974
1975 PubGrubPackageInner::Group {
1977 name,
1978 group,
1979 marker,
1980 } => {
1981 return Ok(Dependencies::Unforkable(
1982 [MarkerTree::TRUE, *marker]
1983 .into_iter()
1984 .dedup()
1985 .map(|marker| PubGrubDependency {
1986 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1987 name: name.clone(),
1988 extra: None,
1989 group: Some(group.clone()),
1990 marker,
1991 }),
1992 version: Range::singleton(version.clone()),
1993 parent: None,
1994 url: None,
1995 })
1996 .collect(),
1997 ));
1998 }
1999 };
2000 Ok(Dependencies::Available(dependencies))
2001 }
2002
2003 fn flatten_requirements<'a>(
2007 &'a self,
2008 dependencies: &'a [Requirement],
2009 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2010 extra: Option<&'a ExtraName>,
2011 dev: Option<&'a GroupName>,
2012 name: Option<&PackageName>,
2013 env: &'a ResolverEnvironment,
2014 python_requirement: &'a PythonRequirement,
2015 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2016 let python_marker = python_requirement.to_marker_tree();
2017
2018 if let Some(dev) = dev {
2019 Either::Left(Either::Left(self.requirements_for_extra(
2022 dev_dependencies.get(dev).into_iter().flatten(),
2023 extra,
2024 env,
2025 python_marker,
2026 python_requirement,
2027 )))
2028 } else if !dependencies
2029 .iter()
2030 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2031 {
2032 Either::Left(Either::Right(self.requirements_for_extra(
2034 dependencies.iter(),
2035 extra,
2036 env,
2037 python_marker,
2038 python_requirement,
2039 )))
2040 } else {
2041 let mut requirements = self
2042 .requirements_for_extra(
2043 dependencies.iter(),
2044 extra,
2045 env,
2046 python_marker,
2047 python_requirement,
2048 )
2049 .collect::<Vec<_>>();
2050
2051 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2054 let mut queue: VecDeque<_> = requirements
2055 .iter()
2056 .filter(|req| name == Some(&req.name))
2057 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2058 .collect();
2059 while let Some((extra, marker)) = queue.pop_front() {
2060 if !seen.insert((extra.clone(), marker)) {
2061 continue;
2062 }
2063 for requirement in self.requirements_for_extra(
2064 dependencies,
2065 Some(&extra),
2066 env,
2067 python_marker,
2068 python_requirement,
2069 ) {
2070 let requirement = match requirement {
2071 Cow::Owned(mut requirement) => {
2072 requirement.marker.and(marker);
2073 requirement
2074 }
2075 Cow::Borrowed(requirement) => {
2076 let mut marker = marker;
2077 marker.and(requirement.marker);
2078 Requirement {
2079 name: requirement.name.clone(),
2080 extras: requirement.extras.clone(),
2081 groups: requirement.groups.clone(),
2082 source: requirement.source.clone(),
2083 origin: requirement.origin.clone(),
2084 marker: marker.simplify_extras(slice::from_ref(&extra)),
2085 }
2086 }
2087 };
2088 if name == Some(&requirement.name) {
2089 queue.extend(
2091 requirement
2092 .extras
2093 .iter()
2094 .cloned()
2095 .map(|extra| (extra, requirement.marker)),
2096 );
2097 } else {
2098 requirements.push(Cow::Owned(requirement));
2100 }
2101 }
2102 }
2103
2104 let mut self_constraints = vec![];
2108 for req in &requirements {
2109 if name == Some(&req.name) && !req.source.is_empty() {
2110 self_constraints.push(Requirement {
2111 name: req.name.clone(),
2112 extras: Box::new([]),
2113 groups: req.groups.clone(),
2114 source: req.source.clone(),
2115 origin: req.origin.clone(),
2116 marker: req.marker,
2117 });
2118 }
2119 }
2120
2121 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2123 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2124
2125 Either::Right(requirements.into_iter())
2126 }
2127 }
2128
2129 fn requirements_for_extra<'data, 'parameters>(
2132 &'data self,
2133 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2134 extra: Option<&'parameters ExtraName>,
2135 env: &'parameters ResolverEnvironment,
2136 python_marker: MarkerTree,
2137 python_requirement: &'parameters PythonRequirement,
2138 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2139 where
2140 'data: 'parameters,
2141 {
2142 self.overrides
2143 .apply(dependencies)
2144 .filter(move |requirement| {
2145 Self::is_requirement_applicable(
2146 requirement,
2147 extra,
2148 env,
2149 python_marker,
2150 python_requirement,
2151 )
2152 })
2153 .flat_map(move |requirement| {
2154 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2155 requirement,
2156 extra,
2157 env,
2158 python_marker,
2159 python_requirement,
2160 ))
2161 })
2162 }
2163
2164 fn is_requirement_applicable(
2167 requirement: &Requirement,
2168 extra: Option<&ExtraName>,
2169 env: &ResolverEnvironment,
2170 python_marker: MarkerTree,
2171 python_requirement: &PythonRequirement,
2172 ) -> bool {
2173 match extra {
2175 Some(source_extra) => {
2176 if requirement.evaluate_markers(env.marker_environment(), &[]) {
2178 return false;
2179 }
2180 if !requirement
2181 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2182 {
2183 return false;
2184 }
2185 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2186 {
2187 return false;
2188 }
2189 }
2190 None => {
2191 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2192 return false;
2193 }
2194 }
2195 }
2196
2197 if python_marker.is_disjoint(requirement.marker) {
2200 trace!(
2201 "Skipping {requirement} because of Requires-Python: {requires_python}",
2202 requires_python = python_requirement.target(),
2203 );
2204 return false;
2205 }
2206
2207 if !env.included_by_marker(requirement.marker) {
2210 trace!("Skipping {requirement} because of {env}");
2211 return false;
2212 }
2213
2214 true
2215 }
2216
2217 fn constraints_for_requirement<'data, 'parameters>(
2220 &'data self,
2221 requirement: Cow<'data, Requirement>,
2222 extra: Option<&'parameters ExtraName>,
2223 env: &'parameters ResolverEnvironment,
2224 python_marker: MarkerTree,
2225 python_requirement: &'parameters PythonRequirement,
2226 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2227 where
2228 'data: 'parameters,
2229 {
2230 self.constraints
2231 .get(&requirement.name)
2232 .into_iter()
2233 .flatten()
2234 .filter_map(move |constraint| {
2235 let constraint = if constraint.marker.is_true() {
2238 if requirement.marker.is_true() {
2242 Cow::Borrowed(constraint)
2243 } else {
2244 let mut marker = constraint.marker;
2245 marker.and(requirement.marker);
2246
2247 if marker.is_false() {
2248 trace!(
2249 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2250 constraint.marker.try_to_string().unwrap(),
2251 requirement.marker.try_to_string().unwrap(),
2252 );
2253 return None;
2254 }
2255
2256 Cow::Owned(Requirement {
2257 name: constraint.name.clone(),
2258 extras: constraint.extras.clone(),
2259 groups: constraint.groups.clone(),
2260 source: constraint.source.clone(),
2261 origin: constraint.origin.clone(),
2262 marker,
2263 })
2264 }
2265 } else {
2266 let requires_python = python_requirement.target();
2267
2268 let mut marker = constraint.marker;
2269 marker.and(requirement.marker);
2270
2271 if marker.is_false() {
2272 trace!(
2273 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2274 constraint.marker.try_to_string().unwrap(),
2275 requirement.marker.try_to_string().unwrap(),
2276 );
2277 return None;
2278 }
2279
2280 if python_marker.is_disjoint(marker) {
2284 trace!(
2285 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2286 );
2287 return None;
2288 }
2289
2290 if marker == constraint.marker {
2291 Cow::Borrowed(constraint)
2292 } else {
2293 Cow::Owned(Requirement {
2294 name: constraint.name.clone(),
2295 extras: constraint.extras.clone(),
2296 groups: constraint.groups.clone(),
2297 source: constraint.source.clone(),
2298 origin: constraint.origin.clone(),
2299 marker,
2300 })
2301 }
2302 };
2303
2304 if !env.included_by_marker(constraint.marker) {
2307 trace!("Skipping {constraint} because of {env}");
2308 return None;
2309 }
2310
2311 match extra {
2313 Some(source_extra) => {
2314 if !constraint
2315 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2316 {
2317 return None;
2318 }
2319 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2320 {
2321 return None;
2322 }
2323 }
2324 None => {
2325 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2326 return None;
2327 }
2328 }
2329 }
2330
2331 Some(constraint)
2332 })
2333 }
2334
2335 async fn fetch<Provider: ResolverProvider>(
2337 self: Arc<Self>,
2338 provider: Arc<Provider>,
2339 request_stream: Receiver<Request>,
2340 ) -> Result<(), ResolveError> {
2341 let mut response_stream = ReceiverStream::new(request_stream)
2342 .map(|request| self.process_request(request, &*provider).boxed_local())
2343 .buffer_unordered(usize::MAX);
2347
2348 while let Some(response) = response_stream.next().await {
2349 match response? {
2350 Some(Response::Package(name, index, version_map)) => {
2351 trace!("Received package metadata for: {name}");
2352 if let Some(index) = index {
2353 self.index
2354 .explicit()
2355 .done((name, index), Arc::new(version_map));
2356 } else {
2357 self.index.implicit().done(name, Arc::new(version_map));
2358 }
2359 }
2360 Some(Response::Installed { dist, metadata }) => {
2361 trace!("Received installed distribution metadata for: {dist}");
2362 self.index
2363 .distributions()
2364 .done(dist.version_id(), Arc::new(metadata));
2365 }
2366 Some(Response::Dist { dist, metadata }) => {
2367 let dist_kind = match dist {
2368 Dist::Built(_) => "built",
2369 Dist::Source(_) => "source",
2370 };
2371 trace!("Received {dist_kind} distribution metadata for: {dist}");
2372 if let MetadataResponse::Unavailable(reason) = &metadata {
2373 let message = UnavailableVersion::from(reason).singular_message();
2374 if let Some(err) = reason.source() {
2375 warn!("{dist} {message}: {err}");
2377 } else {
2378 warn!("{dist} {message}");
2379 }
2380 }
2381 self.index
2382 .distributions()
2383 .done(dist.version_id(), Arc::new(metadata));
2384 }
2385 None => {}
2386 }
2387 }
2388
2389 Ok::<(), ResolveError>(())
2390 }
2391
2392 #[instrument(skip_all, fields(%request))]
2393 async fn process_request<Provider: ResolverProvider>(
2394 &self,
2395 request: Request,
2396 provider: &Provider,
2397 ) -> Result<Option<Response>, ResolveError> {
2398 match request {
2399 Request::Package(package_name, index) => {
2401 let package_versions = provider
2402 .get_package_versions(&package_name, index.as_ref())
2403 .boxed_local()
2404 .await
2405 .map_err(ResolveError::Client)?;
2406
2407 Ok(Some(Response::Package(
2408 package_name,
2409 index.map(IndexMetadata::into_url),
2410 package_versions,
2411 )))
2412 }
2413
2414 Request::Dist(dist) => {
2416 if let Some(version) = dist.version() {
2417 if let Some(index) = dist.index() {
2418 let versions_response = self.index.implicit().get(dist.name());
2420 if let Some(VersionsResponse::Found(version_maps)) =
2421 versions_response.as_deref()
2422 {
2423 for version_map in version_maps {
2424 if version_map.index() == Some(index) {
2425 let Some(metadata) = version_map.get_metadata(version) else {
2426 continue;
2427 };
2428 debug!("Found registry-provided metadata for: {dist}");
2429 return Ok(Some(Response::Dist {
2430 dist,
2431 metadata: MetadataResponse::Found(
2432 ArchiveMetadata::from_metadata23(metadata.clone()),
2433 ),
2434 }));
2435 }
2436 }
2437 }
2438
2439 let versions_response = self
2441 .index
2442 .explicit()
2443 .get(&(dist.name().clone(), index.clone()));
2444 if let Some(VersionsResponse::Found(version_maps)) =
2445 versions_response.as_deref()
2446 {
2447 for version_map in version_maps {
2448 let Some(metadata) = version_map.get_metadata(version) else {
2449 continue;
2450 };
2451 debug!("Found registry-provided metadata for: {dist}");
2452 return Ok(Some(Response::Dist {
2453 dist,
2454 metadata: MetadataResponse::Found(
2455 ArchiveMetadata::from_metadata23(metadata.clone()),
2456 ),
2457 }));
2458 }
2459 }
2460 }
2461 }
2462
2463 let metadata = provider
2464 .get_or_build_wheel_metadata(&dist)
2465 .boxed_local()
2466 .await?;
2467
2468 if let MetadataResponse::Found(metadata) = &metadata {
2469 if &metadata.metadata.name != dist.name() {
2470 return Err(ResolveError::MismatchedPackageName {
2471 request: "distribution metadata",
2472 expected: dist.name().clone(),
2473 actual: metadata.metadata.name.clone(),
2474 });
2475 }
2476 }
2477
2478 Ok(Some(Response::Dist { dist, metadata }))
2479 }
2480
2481 Request::Installed(dist) => {
2482 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2483
2484 if let MetadataResponse::Found(metadata) = &metadata {
2485 if &metadata.metadata.name != dist.name() {
2486 return Err(ResolveError::MismatchedPackageName {
2487 request: "installed metadata",
2488 expected: dist.name().clone(),
2489 actual: metadata.metadata.name.clone(),
2490 });
2491 }
2492 }
2493
2494 Ok(Some(Response::Installed { dist, metadata }))
2495 }
2496
2497 Request::Prefetch(package_name, range, python_requirement) => {
2499 let versions_response = self
2501 .index
2502 .implicit()
2503 .wait(&package_name)
2504 .await
2505 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?;
2506
2507 let version_map = match *versions_response {
2508 VersionsResponse::Found(ref version_map) => version_map,
2509 VersionsResponse::NoIndex => {
2511 self.unavailable_packages
2512 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2513
2514 return Ok(None);
2515 }
2516 VersionsResponse::Offline => {
2517 self.unavailable_packages
2518 .insert(package_name.clone(), UnavailablePackage::Offline);
2519
2520 return Ok(None);
2521 }
2522 VersionsResponse::NotFound => {
2523 self.unavailable_packages
2524 .insert(package_name.clone(), UnavailablePackage::NotFound);
2525
2526 return Ok(None);
2527 }
2528 };
2529
2530 let env = ResolverEnvironment::universal(vec![]);
2533
2534 let Some(candidate) = self.selector.select(
2537 &package_name,
2538 &range,
2539 version_map,
2540 &self.preferences,
2541 &self.installed_packages,
2542 &self.exclusions,
2543 None,
2544 &env,
2545 self.tags.as_ref(),
2546 ) else {
2547 return Ok(None);
2548 };
2549
2550 let Some(dist) = candidate.compatible() else {
2552 return Ok(None);
2553 };
2554
2555 for version_map in version_map {
2557 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2558 let dist = dist.for_resolution();
2559 if version_map.index() == dist.index() {
2560 debug!("Found registry-provided metadata for: {dist}");
2561
2562 let metadata = MetadataResponse::Found(
2563 ArchiveMetadata::from_metadata23(metadata.clone()),
2564 );
2565
2566 let dist = dist.to_owned();
2567 if &package_name != dist.name() {
2568 return Err(ResolveError::MismatchedPackageName {
2569 request: "distribution",
2570 expected: package_name,
2571 actual: dist.name().clone(),
2572 });
2573 }
2574
2575 let response = match dist {
2576 ResolvedDist::Installable { dist, .. } => Response::Dist {
2577 dist: (*dist).clone(),
2578 metadata,
2579 },
2580 ResolvedDist::Installed { dist } => Response::Installed {
2581 dist: (*dist).clone(),
2582 metadata,
2583 },
2584 };
2585
2586 return Ok(Some(response));
2587 }
2588 }
2589 }
2590
2591 if dist.wheel().is_none() {
2595 if !self.selector.use_highest_version(&package_name, &env) {
2596 if let Some((lower, _)) = range.iter().next() {
2597 if lower == &Bound::Unbounded {
2598 debug!(
2599 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2600 );
2601 return Ok(None);
2602 }
2603 }
2604 }
2605 }
2606
2607 let requires_python = match dist {
2609 CompatibleDist::InstalledDist(_) => None,
2610 CompatibleDist::SourceDist { sdist, .. }
2611 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2612 sdist.file.requires_python.as_ref()
2613 }
2614 CompatibleDist::CompatibleWheel { wheel, .. } => {
2615 wheel.file.requires_python.as_ref()
2616 }
2617 };
2618 if let Some(requires_python) = requires_python.as_ref() {
2619 if !python_requirement.target().is_contained_by(requires_python) {
2620 return Ok(None);
2621 }
2622 }
2623
2624 if !self
2626 .hasher
2627 .allows_package(candidate.name(), candidate.version())
2628 {
2629 return Ok(None);
2630 }
2631
2632 if self.index.distributions().register(candidate.version_id()) {
2634 let dist = dist.for_resolution().to_owned();
2635 if &package_name != dist.name() {
2636 return Err(ResolveError::MismatchedPackageName {
2637 request: "distribution",
2638 expected: package_name,
2639 actual: dist.name().clone(),
2640 });
2641 }
2642
2643 let response = match dist {
2644 ResolvedDist::Installable { dist, .. } => {
2645 let metadata = provider
2646 .get_or_build_wheel_metadata(&dist)
2647 .boxed_local()
2648 .await?;
2649
2650 Response::Dist {
2651 dist: (*dist).clone(),
2652 metadata,
2653 }
2654 }
2655 ResolvedDist::Installed { dist } => {
2656 let metadata =
2657 provider.get_installed_metadata(&dist).boxed_local().await?;
2658
2659 Response::Installed {
2660 dist: (*dist).clone(),
2661 metadata,
2662 }
2663 }
2664 };
2665
2666 Ok(Some(response))
2667 } else {
2668 Ok(None)
2669 }
2670 }
2671 }
2672 }
2673
2674 fn convert_no_solution_err(
2675 &self,
2676 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2677 fork_urls: ForkUrls,
2678 fork_indexes: ForkIndexes,
2679 env: ResolverEnvironment,
2680 current_environment: MarkerEnvironment,
2681 exclude_newer: Option<&ExcludeNewer>,
2682 visited: &FxHashSet<PackageName>,
2683 ) -> ResolveError {
2684 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2685 err,
2686 ));
2687
2688 let mut unavailable_packages = FxHashMap::default();
2689 for package in err.packages() {
2690 if let PubGrubPackageInner::Package { name, .. } = &**package {
2691 if let Some(reason) = self.unavailable_packages.get(name) {
2692 unavailable_packages.insert(name.clone(), reason.clone());
2693 }
2694 }
2695 }
2696
2697 let mut incomplete_packages = FxHashMap::default();
2698 for package in err.packages() {
2699 if let PubGrubPackageInner::Package { name, .. } = &**package {
2700 if let Some(versions) = self.incomplete_packages.get(name) {
2701 for entry in versions.iter() {
2702 let (version, reason) = entry.pair();
2703 incomplete_packages
2704 .entry(name.clone())
2705 .or_insert_with(BTreeMap::default)
2706 .insert(version.clone(), reason.clone());
2707 }
2708 }
2709 }
2710 }
2711
2712 let mut available_indexes = FxHashMap::default();
2713 let mut available_versions = FxHashMap::default();
2714 for package in err.packages() {
2715 let Some(name) = package.name() else { continue };
2716 if !visited.contains(name) {
2717 continue;
2722 }
2723 let versions_response = if let Some(index) = fork_indexes.get(name) {
2724 self.index
2725 .explicit()
2726 .get(&(name.clone(), index.url().clone()))
2727 } else {
2728 self.index.implicit().get(name)
2729 };
2730 if let Some(response) = versions_response {
2731 if let VersionsResponse::Found(ref version_maps) = *response {
2732 for version_map in version_maps {
2734 let package_versions = available_versions
2735 .entry(name.clone())
2736 .or_insert_with(BTreeSet::new);
2737
2738 for (version, dists) in version_map.iter(&Ranges::full()) {
2739 if let Some(exclude_newer) =
2741 exclude_newer.and_then(|en| en.exclude_newer_package(name))
2742 {
2743 let Some(prioritized_dist) = dists.prioritized_dist() else {
2744 continue;
2745 };
2746 if prioritized_dist.files().all(|file| {
2747 file.upload_time_utc_ms.is_none_or(|upload_time| {
2748 upload_time >= exclude_newer.timestamp_millis()
2749 })
2750 }) {
2751 continue;
2752 }
2753 }
2754
2755 package_versions.insert(version.clone());
2756 }
2757 }
2758
2759 available_indexes
2761 .entry(name.clone())
2762 .or_insert(BTreeSet::new())
2763 .extend(
2764 version_maps
2765 .iter()
2766 .filter_map(|version_map| version_map.index().cloned()),
2767 );
2768 }
2769 }
2770 }
2771
2772 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2773 err,
2774 self.index.clone(),
2775 available_versions,
2776 available_indexes,
2777 self.selector.clone(),
2778 self.python_requirement.clone(),
2779 self.locations.clone(),
2780 self.capabilities.clone(),
2781 unavailable_packages,
2782 incomplete_packages,
2783 fork_urls,
2784 fork_indexes,
2785 env,
2786 current_environment,
2787 self.tags.clone(),
2788 self.workspace_members.clone(),
2789 self.options.clone(),
2790 )))
2791 }
2792
2793 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2794 if let Some(reporter) = self.reporter.as_ref() {
2795 match &**package {
2796 PubGrubPackageInner::Root(_) => {}
2797 PubGrubPackageInner::Python(_) => {}
2798 PubGrubPackageInner::System(_) => {}
2799 PubGrubPackageInner::Marker { .. } => {}
2800 PubGrubPackageInner::Extra { .. } => {}
2801 PubGrubPackageInner::Group { .. } => {}
2802 PubGrubPackageInner::Package { name, .. } => {
2803 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2804 }
2805 }
2806 }
2807 }
2808
2809 fn on_complete(&self) {
2810 if let Some(reporter) = self.reporter.as_ref() {
2811 reporter.on_complete();
2812 }
2813 }
2814}
2815
2816#[derive(Clone)]
2818pub(crate) struct ForkState {
2819 pubgrub: State<UvDependencyProvider>,
2827 initial_id: Option<Id<PubGrubPackage>>,
2830 initial_version: Option<Version>,
2833 next: Id<PubGrubPackage>,
2835 pins: FilePins,
2845 fork_urls: ForkUrls,
2851 fork_indexes: ForkIndexes,
2856 priorities: PubGrubPriorities,
2862 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2865 env: ResolverEnvironment,
2880 python_requirement: PythonRequirement,
2893 conflict_tracker: ConflictTracker,
2894 prefetcher: BatchPrefetcher,
2898}
2899
2900impl ForkState {
2901 fn new(
2902 pubgrub: State<UvDependencyProvider>,
2903 env: ResolverEnvironment,
2904 python_requirement: PythonRequirement,
2905 prefetcher: BatchPrefetcher,
2906 ) -> Self {
2907 Self {
2908 initial_id: None,
2909 initial_version: None,
2910 next: pubgrub.root_package,
2911 pubgrub,
2912 pins: FilePins::default(),
2913 fork_urls: ForkUrls::default(),
2914 fork_indexes: ForkIndexes::default(),
2915 priorities: PubGrubPriorities::default(),
2916 added_dependencies: FxHashMap::default(),
2917 env,
2918 python_requirement,
2919 conflict_tracker: ConflictTracker::default(),
2920 prefetcher,
2921 }
2922 }
2923
2924 fn visit_package_version_dependencies(
2927 &mut self,
2928 for_package: Id<PubGrubPackage>,
2929 for_version: &Version,
2930 urls: &Urls,
2931 indexes: &Indexes,
2932 dependencies: &[PubGrubDependency],
2933 git: &GitResolver,
2934 workspace_members: &BTreeSet<PackageName>,
2935 resolution_strategy: &ResolutionStrategy,
2936 ) -> Result<(), ResolveError> {
2937 for dependency in dependencies {
2938 let PubGrubDependency {
2939 package,
2940 version,
2941 parent: _,
2942 url,
2943 } = dependency;
2944
2945 let mut has_url = false;
2946 if let Some(name) = package.name() {
2947 for url in urls.get_url(&self.env, name, url.as_ref(), git)? {
2952 self.fork_urls.insert(name, url, &self.env)?;
2953 has_url = true;
2954 }
2955
2956 for index in indexes.get(name, &self.env) {
2958 self.fork_indexes.insert(name, index, &self.env)?;
2959 }
2960 }
2961
2962 if let Some(name) = self.pubgrub.package_store[for_package]
2963 .name_no_root()
2964 .filter(|name| !workspace_members.contains(name))
2965 {
2966 debug!(
2967 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2968 );
2969 } else {
2970 debug!("Adding direct dependency: {package}{version}");
2972
2973 let missing_lower_bound = version
2975 .bounding_range()
2976 .map(|(lowest, _highest)| lowest == Bound::Unbounded)
2977 .unwrap_or(true);
2978 let strategy_lowest = matches!(
2979 resolution_strategy,
2980 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2981 );
2982
2983 if !has_url && missing_lower_bound && strategy_lowest {
2984 let name = package.name_no_root().unwrap();
2985 let bound_on_other_package = dependencies.iter().any(|other| {
2992 Some(name) == other.package.name()
2993 && !other
2994 .version
2995 .bounding_range()
2996 .map(|(lowest, _highest)| lowest == Bound::Unbounded)
2997 .unwrap_or(true)
2998 });
2999
3000 if !bound_on_other_package {
3001 warn_user_once!(
3002 "The direct dependency `{name}` is unpinned. \
3003 Consider setting a lower bound when using `--resolution lowest` \
3004 or `--resolution lowest-direct` to avoid using outdated versions.",
3005 );
3006 }
3007 }
3008 }
3009
3010 self.priorities.insert(package, version, &self.fork_urls);
3012 if let Some(base_package) = package.base_package() {
3015 self.priorities
3016 .insert(&base_package, version, &self.fork_urls);
3017 }
3018 }
3019
3020 Ok(())
3021 }
3022
3023 fn add_package_version_dependencies(
3025 &mut self,
3026 for_package: Id<PubGrubPackage>,
3027 for_version: &Version,
3028 dependencies: Vec<PubGrubDependency>,
3029 ) {
3030 for dependency in &dependencies {
3031 let PubGrubDependency {
3032 package,
3033 version,
3034 parent: _,
3035 url: _,
3036 } = dependency;
3037
3038 let Some(base_package) = package.base_package() else {
3039 continue;
3040 };
3041
3042 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3043 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3044 self.pubgrub
3045 .add_proxy_package(proxy_package, base_package_id, version.clone());
3046 }
3047
3048 let conflict = self.pubgrub.add_package_version_dependencies(
3049 self.next,
3050 for_version.clone(),
3051 dependencies.into_iter().map(|dependency| {
3052 let PubGrubDependency {
3053 package,
3054 version,
3055 parent: _,
3056 url: _,
3057 } = dependency;
3058 (package, version)
3059 }),
3060 );
3061
3062 if let Some(incompatibility) = conflict {
3065 self.record_conflict(for_package, Some(for_version), incompatibility);
3066 }
3067 }
3068
3069 fn record_conflict(
3070 &mut self,
3071 affected: Id<PubGrubPackage>,
3072 version: Option<&Version>,
3073 incompatibility: IncompId<PubGrubPackage, Ranges<Version>, UnavailableReason>,
3074 ) {
3075 let mut culprit_is_real = false;
3076 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3077 if incompatible == affected {
3078 continue;
3079 }
3080 if self.pubgrub.package_store[affected].name()
3081 == self.pubgrub.package_store[incompatible].name()
3082 {
3083 continue;
3086 }
3087 culprit_is_real = true;
3088 let culprit_count = self
3089 .conflict_tracker
3090 .culprit
3091 .entry(incompatible)
3092 .or_default();
3093 *culprit_count += 1;
3094 if *culprit_count == CONFLICT_THRESHOLD {
3095 self.conflict_tracker.deprioritize.push(incompatible);
3096 }
3097 }
3098 if culprit_is_real {
3101 if tracing::enabled!(Level::DEBUG) {
3102 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3103 .iter()
3104 .map(|(package, _term)| &self.pubgrub.package_store[package])
3105 .join(", ");
3106 if let Some(version) = version {
3107 debug!(
3108 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3109 self.pubgrub.package_store[affected], version, incompatibility
3110 );
3111 } else {
3112 debug!(
3113 "Recording unit propagation conflict of {} from incompatibility of ({})",
3114 self.pubgrub.package_store[affected], incompatibility
3115 );
3116 }
3117 }
3118
3119 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3120 *affected_count += 1;
3121 if *affected_count == CONFLICT_THRESHOLD {
3122 self.conflict_tracker.prioritize.push(self.next);
3123 }
3124 }
3125 }
3126
3127 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3128 if let UnavailableVersion::IncompatibleDist(
3132 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3133 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3134 ) = reason
3135 {
3136 let package = &self.next;
3137 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3138 PubGrubPackageInner::Python(match kind {
3139 PythonRequirementKind::Installed => PubGrubPython::Installed,
3140 PythonRequirementKind::Target => PubGrubPython::Target,
3141 }),
3142 ));
3143 self.pubgrub
3144 .add_incompatibility(Incompatibility::from_dependency(
3145 *package,
3146 Range::singleton(version.clone()),
3147 (python, release_specifiers_to_ranges(requires_python)),
3148 ));
3149 self.pubgrub
3150 .partial_solution
3151 .add_decision(self.next, version);
3152 return;
3153 }
3154 self.pubgrub
3155 .add_incompatibility(Incompatibility::custom_version(
3156 self.next,
3157 version.clone(),
3158 UnavailableReason::Version(reason),
3159 ));
3160 }
3161
3162 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3168 self.env = env;
3169 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3171 debug!("Narrowed `requires-python` bound to: {}", req.target());
3172 self.python_requirement = req;
3173 }
3174 self
3175 }
3176
3177 fn source(
3181 &self,
3182 name: &PackageName,
3183 version: &Version,
3184 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3185 let url = self.fork_urls.get(name);
3186 let index = url
3187 .is_none()
3188 .then(|| {
3189 self.pins
3190 .get(name, version)
3191 .expect("Every package should be pinned")
3192 .index()
3193 })
3194 .flatten();
3195 (url, index)
3196 }
3197
3198 fn into_resolution(self) -> Resolution {
3199 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3200 let edge_count: usize = solution
3201 .keys()
3202 .map(|package| self.pubgrub.incompatibilities[package].len())
3203 .sum();
3204 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3205 for (package, self_version) in &solution {
3206 for id in &self.pubgrub.incompatibilities[package] {
3207 let pubgrub::Kind::FromDependencyOf(
3208 self_package,
3209 ref self_range,
3210 dependency_package,
3211 ref dependency_range,
3212 ) = self.pubgrub.incompatibility_store[*id].kind
3213 else {
3214 continue;
3215 };
3216 if *package != self_package {
3217 continue;
3218 }
3219 if !self_range.contains(self_version) {
3220 continue;
3221 }
3222 let Some(dependency_version) = solution.get(&dependency_package) else {
3223 continue;
3224 };
3225 if !dependency_range.contains(dependency_version) {
3226 continue;
3227 }
3228
3229 let self_package = &self.pubgrub.package_store[self_package];
3230 let dependency_package = &self.pubgrub.package_store[dependency_package];
3231
3232 let (self_name, self_extra, self_group) = match &**self_package {
3233 PubGrubPackageInner::Package {
3234 name: self_name,
3235 extra: self_extra,
3236 group: self_group,
3237 marker: _,
3238 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3239
3240 PubGrubPackageInner::Root(_) => (None, None, None),
3241
3242 _ => continue,
3243 };
3244
3245 let (self_url, self_index) = self_name
3246 .map(|self_name| self.source(self_name, self_version))
3247 .unwrap_or((None, None));
3248
3249 match **dependency_package {
3250 PubGrubPackageInner::Package {
3251 name: ref dependency_name,
3252 extra: ref dependency_extra,
3253 group: ref dependency_dev,
3254 marker: ref dependency_marker,
3255 } => {
3256 debug_assert!(
3257 dependency_extra.is_none(),
3258 "Packages should depend on an extra proxy"
3259 );
3260 debug_assert!(
3261 dependency_dev.is_none(),
3262 "Packages should depend on a group proxy"
3263 );
3264
3265 if self_group.is_none() {
3268 if self_name == Some(dependency_name) {
3269 continue;
3270 }
3271 }
3272
3273 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3274
3275 let edge = ResolutionDependencyEdge {
3276 from: self_name.cloned(),
3277 from_version: self_version.clone(),
3278 from_url: self_url.cloned(),
3279 from_index: self_index.cloned(),
3280 from_extra: self_extra.cloned(),
3281 from_group: self_group.cloned(),
3282 to: dependency_name.clone(),
3283 to_version: dependency_version.clone(),
3284 to_url: to_url.cloned(),
3285 to_index: to_index.cloned(),
3286 to_extra: dependency_extra.clone(),
3287 to_group: dependency_dev.clone(),
3288 marker: *dependency_marker,
3289 };
3290 edges.push(edge);
3291 }
3292
3293 PubGrubPackageInner::Marker {
3294 name: ref dependency_name,
3295 marker: ref dependency_marker,
3296 } => {
3297 if self_group.is_none() {
3300 if self_name == Some(dependency_name) {
3301 continue;
3302 }
3303 }
3304
3305 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3306
3307 let edge = ResolutionDependencyEdge {
3308 from: self_name.cloned(),
3309 from_version: self_version.clone(),
3310 from_url: self_url.cloned(),
3311 from_index: self_index.cloned(),
3312 from_extra: self_extra.cloned(),
3313 from_group: self_group.cloned(),
3314 to: dependency_name.clone(),
3315 to_version: dependency_version.clone(),
3316 to_url: to_url.cloned(),
3317 to_index: to_index.cloned(),
3318 to_extra: None,
3319 to_group: None,
3320 marker: *dependency_marker,
3321 };
3322 edges.push(edge);
3323 }
3324
3325 PubGrubPackageInner::Extra {
3326 name: ref dependency_name,
3327 extra: ref dependency_extra,
3328 marker: ref dependency_marker,
3329 } => {
3330 if self_group.is_none() {
3331 debug_assert!(
3332 self_name != Some(dependency_name),
3333 "Extras should be flattened"
3334 );
3335 }
3336 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3337
3338 let edge = ResolutionDependencyEdge {
3340 from: self_name.cloned(),
3341 from_version: self_version.clone(),
3342 from_url: self_url.cloned(),
3343 from_index: self_index.cloned(),
3344 from_extra: self_extra.cloned(),
3345 from_group: self_group.cloned(),
3346 to: dependency_name.clone(),
3347 to_version: dependency_version.clone(),
3348 to_url: to_url.cloned(),
3349 to_index: to_index.cloned(),
3350 to_extra: Some(dependency_extra.clone()),
3351 to_group: None,
3352 marker: *dependency_marker,
3353 };
3354 edges.push(edge);
3355
3356 let edge = ResolutionDependencyEdge {
3358 from: self_name.cloned(),
3359 from_version: self_version.clone(),
3360 from_url: self_url.cloned(),
3361 from_index: self_index.cloned(),
3362 from_extra: self_extra.cloned(),
3363 from_group: self_group.cloned(),
3364 to: dependency_name.clone(),
3365 to_version: dependency_version.clone(),
3366 to_url: to_url.cloned(),
3367 to_index: to_index.cloned(),
3368 to_extra: None,
3369 to_group: None,
3370 marker: *dependency_marker,
3371 };
3372 edges.push(edge);
3373 }
3374
3375 PubGrubPackageInner::Group {
3376 name: ref dependency_name,
3377 group: ref dependency_group,
3378 marker: ref dependency_marker,
3379 } => {
3380 debug_assert!(
3381 self_name != Some(dependency_name),
3382 "Groups should be flattened"
3383 );
3384
3385 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3386
3387 let edge = ResolutionDependencyEdge {
3390 from: self_name.cloned(),
3391 from_version: self_version.clone(),
3392 from_url: self_url.cloned(),
3393 from_index: self_index.cloned(),
3394 from_extra: self_extra.cloned(),
3395 from_group: self_group.cloned(),
3396 to: dependency_name.clone(),
3397 to_version: dependency_version.clone(),
3398 to_url: to_url.cloned(),
3399 to_index: to_index.cloned(),
3400 to_extra: None,
3401 to_group: Some(dependency_group.clone()),
3402 marker: *dependency_marker,
3403 };
3404 edges.push(edge);
3405 }
3406
3407 _ => {}
3408 }
3409 }
3410 }
3411
3412 let nodes = solution
3413 .into_iter()
3414 .filter_map(|(package, version)| {
3415 if let PubGrubPackageInner::Package {
3416 name,
3417 extra,
3418 group,
3419 marker: MarkerTree::TRUE,
3420 } = &*self.pubgrub.package_store[package]
3421 {
3422 let (url, index) = self.source(name, &version);
3423 Some((
3424 ResolutionPackage {
3425 name: name.clone(),
3426 extra: extra.clone(),
3427 dev: group.clone(),
3428 url: url.cloned(),
3429 index: index.cloned(),
3430 },
3431 version,
3432 ))
3433 } else {
3434 None
3435 }
3436 })
3437 .collect();
3438
3439 Resolution {
3440 nodes,
3441 edges,
3442 pins: self.pins,
3443 env: self.env,
3444 }
3445 }
3446}
3447
3448#[derive(Debug)]
3450pub(crate) struct Resolution {
3451 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3452 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3455 pub(crate) pins: FilePins,
3457 pub(crate) env: ResolverEnvironment,
3459}
3460
3461#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3464pub(crate) struct ResolutionPackage {
3465 pub(crate) name: PackageName,
3466 pub(crate) extra: Option<ExtraName>,
3467 pub(crate) dev: Option<GroupName>,
3468 pub(crate) url: Option<VerbatimParsedUrl>,
3470 pub(crate) index: Option<IndexUrl>,
3472}
3473
3474#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3477pub(crate) struct ResolutionDependencyEdge {
3478 pub(crate) from: Option<PackageName>,
3480 pub(crate) from_version: Version,
3481 pub(crate) from_url: Option<VerbatimParsedUrl>,
3482 pub(crate) from_index: Option<IndexUrl>,
3483 pub(crate) from_extra: Option<ExtraName>,
3484 pub(crate) from_group: Option<GroupName>,
3485 pub(crate) to: PackageName,
3486 pub(crate) to_version: Version,
3487 pub(crate) to_url: Option<VerbatimParsedUrl>,
3488 pub(crate) to_index: Option<IndexUrl>,
3489 pub(crate) to_extra: Option<ExtraName>,
3490 pub(crate) to_group: Option<GroupName>,
3491 pub(crate) marker: MarkerTree,
3492}
3493
3494impl ResolutionDependencyEdge {
3495 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3496 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3500 }
3501}
3502
3503#[derive(Debug)]
3505#[expect(clippy::large_enum_variant)]
3506pub(crate) enum Request {
3507 Package(PackageName, Option<IndexMetadata>),
3509 Dist(Dist),
3511 Installed(InstalledDist),
3513 Prefetch(PackageName, Range<Version>, PythonRequirement),
3515}
3516
3517impl<'a> From<ResolvedDistRef<'a>> for Request {
3518 fn from(dist: ResolvedDistRef<'a>) -> Self {
3519 match dist {
3525 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3526 let source = prioritized.source_dist().expect("a source distribution");
3529 assert_eq!(
3530 (&sdist.name, &sdist.version),
3531 (&source.name, &source.version),
3532 "expected chosen sdist to match prioritized sdist"
3533 );
3534 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3535 }
3536 ResolvedDistRef::InstallableRegistryBuiltDist {
3537 wheel, prioritized, ..
3538 } => {
3539 assert_eq!(
3540 Some(&wheel.filename),
3541 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3542 "expected chosen wheel to match best wheel"
3543 );
3544 let built = prioritized.built_dist().expect("at least one wheel");
3547 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3548 }
3549 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3550 }
3551 }
3552}
3553
3554impl Display for Request {
3555 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3556 match self {
3557 Self::Package(package_name, _) => {
3558 write!(f, "Versions {package_name}")
3559 }
3560 Self::Dist(dist) => {
3561 write!(f, "Metadata {dist}")
3562 }
3563 Self::Installed(dist) => {
3564 write!(f, "Installed metadata {dist}")
3565 }
3566 Self::Prefetch(package_name, range, _) => {
3567 write!(f, "Prefetch {package_name} {range}")
3568 }
3569 }
3570 }
3571}
3572
3573#[derive(Debug)]
3574#[expect(clippy::large_enum_variant)]
3575enum Response {
3576 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3578 Dist {
3580 dist: Dist,
3581 metadata: MetadataResponse,
3582 },
3583 Installed {
3585 dist: InstalledDist,
3586 metadata: MetadataResponse,
3587 },
3588}
3589
3590enum Dependencies {
3596 Unavailable(UnavailableVersion),
3598 Available(Vec<PubGrubDependency>),
3604 Unforkable(Vec<PubGrubDependency>),
3610}
3611
3612impl Dependencies {
3613 fn fork(
3620 self,
3621 env: &ResolverEnvironment,
3622 python_requirement: &PythonRequirement,
3623 conflicts: &Conflicts,
3624 ) -> ForkedDependencies {
3625 let deps = match self {
3626 Self::Available(deps) => deps,
3627 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3628 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3629 };
3630 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3631 for dep in deps {
3632 let name = dep
3633 .package
3634 .name()
3635 .expect("dependency always has a name")
3636 .clone();
3637 name_to_deps.entry(name).or_default().push(dep);
3638 }
3639 let Forks {
3640 mut forks,
3641 diverging_packages,
3642 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3643 if forks.is_empty() {
3644 ForkedDependencies::Unforked(vec![])
3645 } else if forks.len() == 1 {
3646 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3647 } else {
3648 ForkedDependencies::Forked {
3649 forks,
3650 diverging_packages: diverging_packages.into_iter().collect(),
3651 }
3652 }
3653 }
3654}
3655
3656#[derive(Debug)]
3663enum ForkedDependencies {
3664 Unavailable(UnavailableVersion),
3666 Unforked(Vec<PubGrubDependency>),
3670 Forked {
3676 forks: Vec<Fork>,
3677 diverging_packages: Vec<PackageName>,
3679 },
3680}
3681
3682#[derive(Debug, Default)]
3687struct Forks {
3688 forks: Vec<Fork>,
3690 diverging_packages: BTreeSet<PackageName>,
3692}
3693
3694impl Forks {
3695 fn new(
3696 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3697 env: &ResolverEnvironment,
3698 python_requirement: &PythonRequirement,
3699 conflicts: &Conflicts,
3700 ) -> Self {
3701 let python_marker = python_requirement.to_marker_tree();
3702
3703 let mut forks = vec![Fork::new(env.clone())];
3704 let mut diverging_packages = BTreeSet::new();
3705 for (name, mut deps) in name_to_deps {
3706 assert!(!deps.is_empty(), "every name has at least one dependency");
3707 if let [dep] = deps.as_slice() {
3718 if marker::requires_python(dep.package.marker())
3726 .is_none_or(|bound| !python_requirement.raises(&bound))
3727 {
3728 let dep = deps.pop().unwrap();
3729 let marker = dep.package.marker();
3730 for fork in &mut forks {
3731 if fork.env.included_by_marker(marker) {
3732 fork.add_dependency(dep.clone());
3733 }
3734 }
3735 continue;
3736 }
3737 } else {
3738 if let Some(dep) = deps.first() {
3740 let marker = dep.package.marker();
3741 if deps.iter().all(|dep| marker == dep.package.marker()) {
3742 if marker::requires_python(marker)
3746 .is_none_or(|bound| !python_requirement.raises(&bound))
3747 {
3748 for dep in deps {
3749 for fork in &mut forks {
3750 if fork.env.included_by_marker(marker) {
3751 fork.add_dependency(dep.clone());
3752 }
3753 }
3754 }
3755 continue;
3756 }
3757 }
3758 }
3759 }
3760 for dep in deps {
3761 let mut forker = match ForkingPossibility::new(env, &dep) {
3762 ForkingPossibility::Possible(forker) => forker,
3763 ForkingPossibility::DependencyAlwaysExcluded => {
3764 continue;
3767 }
3768 ForkingPossibility::NoForkingPossible => {
3769 for fork in &mut forks {
3772 fork.add_dependency(dep.clone());
3773 }
3774 continue;
3775 }
3776 };
3777 diverging_packages.insert(name.clone());
3779
3780 let mut new = vec![];
3781 for fork in std::mem::take(&mut forks) {
3782 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3783 new.push(fork);
3784 continue;
3785 };
3786 forker = remaining_forker;
3787
3788 for fork_env in envs {
3789 let mut new_fork = fork.clone();
3790 new_fork.set_env(fork_env);
3791 if forker.included(&new_fork.env) {
3796 new_fork.add_dependency(dep.clone());
3797 }
3798 if new_fork.env.included_by_marker(python_marker) {
3801 new.push(new_fork);
3802 }
3803 }
3804 }
3805 forks = new;
3806 }
3807 }
3808 for set in conflicts.iter() {
3823 let mut new = vec![];
3824 for fork in std::mem::take(&mut forks) {
3825 let mut has_conflicting_dependency = false;
3833 for item in set.iter() {
3834 if fork.contains_conflicting_item(item.as_ref()) {
3835 has_conflicting_dependency = true;
3836 diverging_packages.insert(item.package().clone());
3837 break;
3838 }
3839 }
3840 if !has_conflicting_dependency {
3841 new.push(fork);
3842 continue;
3843 }
3844
3845 let non_excluded: Vec<_> = set
3851 .iter()
3852 .filter(|item| fork.env.included_by_group(item.as_ref()))
3853 .collect();
3854 if non_excluded.len() < 2 {
3855 let dominated = non_excluded.iter().all(|item| {
3860 !conflicts.iter().any(|other_set| {
3861 !std::ptr::eq(set, other_set)
3862 && other_set.contains(item.package(), item.kind().as_ref())
3863 && other_set
3864 .iter()
3865 .filter(|other_item| {
3866 other_item.package() != item.package()
3867 || other_item.kind() != item.kind()
3868 })
3869 .any(|other_item| {
3870 fork.env.included_by_group(other_item.as_ref())
3871 })
3872 })
3873 });
3874 if dominated {
3875 let rules: Vec<_> = set
3880 .iter()
3881 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3882 .cloned()
3883 .map(Err)
3884 .collect();
3885 if let Some(filtered) = fork.filter(rules) {
3886 new.push(filtered);
3887 }
3888 continue;
3889 }
3890 }
3891
3892 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3894 new.push(fork_none);
3895 }
3896
3897 for (i, _) in set.iter().enumerate() {
3905 let fork_allows_group = fork.clone().filter(
3906 set.iter()
3907 .cloned()
3908 .enumerate()
3909 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3910 );
3911 if let Some(fork_allows_group) = fork_allows_group {
3912 new.push(fork_allows_group);
3913 }
3914 }
3915 }
3916 forks = new;
3917 }
3918 Self {
3919 forks,
3920 diverging_packages,
3921 }
3922 }
3923}
3924
3925#[derive(Clone, Debug)]
3935struct Fork {
3936 dependencies: Vec<PubGrubDependency>,
3945 conflicts: crate::FxHashbrownSet<ConflictItem>,
3951 env: ResolverEnvironment,
3963}
3964
3965impl Fork {
3966 fn new(env: ResolverEnvironment) -> Self {
3969 Self {
3970 dependencies: vec![],
3971 conflicts: crate::FxHashbrownSet::default(),
3972 env,
3973 }
3974 }
3975
3976 fn add_dependency(&mut self, dep: PubGrubDependency) {
3978 if let Some(conflicting_item) = dep.conflicting_item() {
3979 self.conflicts.insert(conflicting_item.to_owned());
3980 }
3981 self.dependencies.push(dep);
3982 }
3983
3984 fn set_env(&mut self, env: ResolverEnvironment) {
3989 self.env = env;
3990 self.dependencies.retain(|dep| {
3991 let marker = dep.package.marker();
3992 if self.env.included_by_marker(marker) {
3993 return true;
3994 }
3995 if let Some(conflicting_item) = dep.conflicting_item() {
3996 self.conflicts.remove(&conflicting_item);
3997 }
3998 false
3999 });
4000 }
4001
4002 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4005 self.conflicts.contains(&item)
4006 }
4007
4008 fn filter(
4015 mut self,
4016 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4017 ) -> Option<Self> {
4018 self.env = self.env.filter_by_group(rules)?;
4019 self.dependencies.retain(|dep| {
4020 let Some(conflicting_item) = dep.conflicting_item() else {
4021 return true;
4022 };
4023 if self.env.included_by_group(conflicting_item) {
4024 return true;
4025 }
4026 match conflicting_item.kind() {
4027 ConflictKindRef::Project => {
4030 if dep.parent.is_some() {
4031 return true;
4032 }
4033 }
4034 ConflictKindRef::Group(_) => {}
4035 ConflictKindRef::Extra(_) => {}
4036 }
4037 self.conflicts.remove(&conflicting_item);
4038 false
4039 });
4040 Some(self)
4041 }
4042
4043 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4045 let self_bound = self.env.requires_python().unwrap_or_default();
4055 let other_bound = other.env.requires_python().unwrap_or_default();
4056 self_bound.lower().cmp(other_bound.lower())
4057 }
4058
4059 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4061 let self_upper_bounds = self
4066 .dependencies
4067 .iter()
4068 .filter(|dep| {
4069 dep.version
4070 .bounding_range()
4071 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4072 })
4073 .count();
4074 let other_upper_bounds = other
4075 .dependencies
4076 .iter()
4077 .filter(|dep| {
4078 dep.version
4079 .bounding_range()
4080 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4081 })
4082 .count();
4083
4084 self_upper_bounds.cmp(&other_upper_bounds)
4085 }
4086}
4087
4088impl Eq for Fork {}
4089
4090impl PartialEq for Fork {
4091 fn eq(&self, other: &Self) -> bool {
4092 self.dependencies == other.dependencies && self.env == other.env
4093 }
4094}
4095
4096#[derive(Debug, Clone)]
4097pub(crate) struct VersionFork {
4098 env: ResolverEnvironment,
4100 id: Id<PubGrubPackage>,
4102 version: Option<Version>,
4104}
4105
4106fn enrich_dependency_error(
4108 error: ResolveError,
4109 id: Id<PubGrubPackage>,
4110 version: &Version,
4111 pubgrub: &State<UvDependencyProvider>,
4112) -> ResolveError {
4113 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4114 return error;
4115 };
4116 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4117 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4118}
4119
4120fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4122 let package = &state.package_store[id];
4123 if package.is_root() {
4124 return MarkerTree::TRUE;
4125 }
4126
4127 let Some(incompatibilities) = state.incompatibilities.get(&id) else {
4129 return MarkerTree::FALSE;
4130 };
4131
4132 let mut marker = MarkerTree::FALSE;
4134 for index in incompatibilities {
4135 let incompat = &state.incompatibility_store[*index];
4136 if let Kind::FromDependencyOf(id1, _, id2, _) = &incompat.kind {
4137 if id == *id2 {
4138 marker.or({
4139 let mut marker = package.marker();
4140 marker.and(find_environments(*id1, state));
4141 marker
4142 });
4143 }
4144 }
4145 }
4146 marker
4147}
4148
4149#[derive(Debug, Default, Clone)]
4150struct ConflictTracker {
4151 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4153 prioritize: Vec<Id<PubGrubPackage>>,
4157 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4159 deprioritize: Vec<Id<PubGrubPackage>>,
4163}