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, Identifier, IncompatibleDist,
27 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
28 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist,
29 ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::{ExtraName, GroupName, PackageName};
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_strategy::ForkStrategy;
49use crate::fork_urls::ForkUrls;
50use crate::manifest::Manifest;
51use crate::pins::FilePins;
52use crate::preferences::{PreferenceSource, Preferences};
53use crate::pubgrub::{
54 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
55 PubGrubPython,
56};
57use crate::python_requirement::PythonRequirement;
58use crate::resolution::ResolverOutput;
59use crate::resolution_mode::ResolutionStrategy;
60pub(crate) use crate::resolver::availability::{
61 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
62 UnavailableVersion,
63};
64use crate::resolver::batch_prefetch::BatchPrefetcher;
65use crate::resolver::derivation::DerivationChainBuilder;
66pub use crate::resolver::environment::ResolverEnvironment;
67use crate::resolver::environment::{
68 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
69};
70pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
71pub use crate::resolver::index::InMemoryIndex;
72use crate::resolver::indexes::Indexes;
73pub use crate::resolver::provider::{
74 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
75 VersionsResponse, WheelMetadataResult,
76};
77pub use crate::resolver::reporter::Reporter;
78use crate::resolver::system::SystemDependency;
79pub(crate) use crate::resolver::urls::Urls;
80use crate::universal_marker::{ConflictMarker, UniversalMarker};
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84
85mod availability;
86mod batch_prefetch;
87mod derivation;
88mod environment;
89mod fork_map;
90mod index;
91mod indexes;
92mod provider;
93mod reporter;
94mod system;
95mod urls;
96
97const CONFLICT_THRESHOLD: usize = 5;
99
100pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
101 state: ResolverState<InstalledPackages>,
102 provider: Provider,
103}
104
105struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
108 project: Option<PackageName>,
109 requirements: Vec<Requirement>,
110 constraints: Constraints,
111 overrides: Overrides,
112 excludes: Excludes,
113 preferences: Preferences,
114 git: GitResolver,
115 capabilities: IndexCapabilities,
116 locations: IndexLocations,
117 exclusions: Exclusions,
118 urls: Urls,
119 indexes: Indexes,
120 dependency_mode: DependencyMode,
121 hasher: HashStrategy,
122 env: ResolverEnvironment,
123 current_environment: MarkerEnvironment,
125 tags: Option<Tags>,
126 python_requirement: PythonRequirement,
127 conflicts: Conflicts,
128 workspace_members: BTreeSet<PackageName>,
129 selector: CandidateSelector,
130 index: InMemoryIndex,
131 installed_packages: InstalledPackages,
132 unavailable_packages: DashMap<PackageName, UnavailablePackage>,
134 incomplete_packages: DashMap<PackageName, DashMap<Version, MetadataUnavailable>>,
136 options: Options,
138 reporter: Option<Arc<dyn Reporter>>,
140}
141
142impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
143 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
144{
145 pub fn new(
164 manifest: Manifest,
165 options: Options,
166 python_requirement: &'a PythonRequirement,
167 env: ResolverEnvironment,
168 current_environment: &MarkerEnvironment,
169 conflicts: Conflicts,
170 tags: Option<&'a Tags>,
171 flat_index: &'a FlatIndex,
172 index: &'a InMemoryIndex,
173 hasher: &'a HashStrategy,
174 build_context: &'a Context,
175 installed_packages: InstalledPackages,
176 database: DistributionDatabase<'a, Context>,
177 ) -> Result<Self, ResolveError> {
178 let provider = DefaultResolverProvider::new(
179 database,
180 flat_index,
181 tags,
182 python_requirement.target(),
183 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
184 hasher,
185 options.exclude_newer.clone(),
186 build_context.locations(),
187 build_context.build_options(),
188 build_context.capabilities(),
189 );
190
191 Ok(Self::new_custom_io(
192 manifest,
193 options,
194 hasher,
195 env,
196 current_environment,
197 tags.cloned(),
198 python_requirement,
199 conflicts,
200 index,
201 build_context.git(),
202 build_context.capabilities(),
203 build_context.locations(),
204 provider,
205 installed_packages,
206 ))
207 }
208}
209
210impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
211 Resolver<Provider, InstalledPackages>
212{
213 fn new_custom_io(
215 manifest: Manifest,
216 options: Options,
217 hasher: &HashStrategy,
218 env: ResolverEnvironment,
219 current_environment: &MarkerEnvironment,
220 tags: Option<Tags>,
221 python_requirement: &PythonRequirement,
222 conflicts: Conflicts,
223 index: &InMemoryIndex,
224 git: &GitResolver,
225 capabilities: &IndexCapabilities,
226 locations: &IndexLocations,
227 provider: Provider,
228 installed_packages: InstalledPackages,
229 ) -> Self {
230 let state = ResolverState {
231 index: index.clone(),
232 git: git.clone(),
233 capabilities: capabilities.clone(),
234 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
235 dependency_mode: options.dependency_mode,
236 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
237 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
238 project: manifest.project,
239 workspace_members: manifest.workspace_members,
240 requirements: manifest.requirements,
241 constraints: manifest.constraints,
242 overrides: manifest.overrides,
243 excludes: manifest.excludes,
244 preferences: manifest.preferences,
245 exclusions: manifest.exclusions,
246 hasher: hasher.clone(),
247 locations: locations.clone(),
248 env,
249 current_environment: current_environment.clone(),
250 tags,
251 python_requirement: python_requirement.clone(),
252 conflicts,
253 installed_packages,
254 unavailable_packages: DashMap::default(),
255 incomplete_packages: DashMap::default(),
256 options,
257 reporter: None,
258 };
259 Self { state, provider }
260 }
261
262 #[must_use]
264 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
265 Self {
266 state: ResolverState {
267 reporter: Some(reporter.clone()),
268 ..self.state
269 },
270 provider: self
271 .provider
272 .with_reporter(reporter.into_distribution_reporter()),
273 }
274 }
275
276 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
278 let state = Arc::new(self.state);
279 let provider = Arc::new(self.provider);
280
281 let (request_sink, request_stream) = mpsc::channel(300);
285
286 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
288
289 let solver = state.clone();
291 let (tx, rx) = oneshot::channel();
292 thread::Builder::new()
293 .name("uv-resolver".into())
294 .spawn(move || {
295 let result = solver.solve(&request_sink);
296
297 let _ = tx.send(result);
299 })
300 .unwrap();
301
302 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
303
304 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
306
307 state.on_complete();
308 resolution
309 }
310}
311
312impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
313 #[instrument(skip_all)]
314 fn solve(
315 self: Arc<Self>,
316 request_sink: &Sender<Request>,
317 ) -> Result<ResolverOutput, ResolveError> {
318 debug!(
319 "Solving with installed Python version: {}",
320 self.python_requirement.exact()
321 );
322 debug!(
323 "Solving with target Python version: {}",
324 self.python_requirement.target()
325 );
326 if !self.options.exclude_newer.is_empty() {
327 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
328 }
329
330 let mut visited = FxHashSet::default();
331
332 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
333 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
334 let prefetcher = BatchPrefetcher::new(
335 self.capabilities.clone(),
336 self.index.clone(),
337 request_sink.clone(),
338 );
339 let state = ForkState::new(
340 pubgrub,
341 self.env.clone(),
342 self.python_requirement.clone(),
343 prefetcher,
344 );
345 let mut preferences = self.preferences.clone();
346 let mut forked_states = self.env.initial_forked_states(state)?;
347 let mut resolutions = vec![];
348
349 'FORK: while let Some(mut state) = forked_states.pop() {
350 if let Some(split) = state.env.end_user_fork_display() {
351 let requires_python = state.python_requirement.target();
352 debug!("Solving {split} (requires-python: {requires_python:?})");
353 }
354 let start = Instant::now();
355 loop {
356 let highest_priority_pkg =
357 if let Some(initial) = state.initial_id.take() {
358 initial
362 } else {
363 let result = state.pubgrub.unit_propagation(state.next);
365 match result {
366 Err(err) => {
367 return Err(self.convert_no_solution_err(
369 err,
370 state.fork_urls,
371 state.fork_indexes,
372 state.env,
373 self.current_environment.clone(),
374 &visited,
375 ));
376 }
377 Ok(conflicts) => {
378 for (affected, incompatibility) in conflicts {
379 state.record_conflict(affected, None, incompatibility);
382 }
383 }
384 }
385
386 if self.dependency_mode.is_transitive() {
388 Self::pre_visit(
389 state
390 .pubgrub
391 .partial_solution
392 .prioritized_packages()
393 .map(|(id, range)| (&state.pubgrub.package_store[id], range)),
394 &self.urls,
395 &self.indexes,
396 &state.python_requirement,
397 request_sink,
398 )?;
399 }
400
401 Self::reprioritize_conflicts(&mut state);
402
403 trace!(
404 "Assigned packages: {}",
405 state
406 .pubgrub
407 .partial_solution
408 .extract_solution()
409 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
410 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
411 .join(", ")
412 );
413 let Some((highest_priority_pkg, _)) =
417 state.pubgrub.partial_solution.pick_highest_priority_pkg(
418 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
419 )
420 else {
421 if tracing::enabled!(Level::DEBUG) {
423 state.prefetcher.log_tried_versions();
424 }
425 debug!(
426 "{} resolution took {:.3}s",
427 state.env,
428 start.elapsed().as_secs_f32()
429 );
430
431 let resolution = state.into_resolution();
432
433 if matches!(
442 self.options.resolution_mode,
443 ResolutionMode::Lowest | ResolutionMode::Highest
444 ) {
445 for (package, version) in &resolution.nodes {
446 preferences.insert(
447 package.name.clone(),
448 package.index.clone(),
449 resolution
450 .env
451 .try_universal_markers()
452 .unwrap_or(UniversalMarker::TRUE),
453 version.clone(),
454 PreferenceSource::Resolver,
455 );
456 }
457 }
458
459 resolutions.push(resolution);
460 continue 'FORK;
461 };
462 trace!(
463 "Chose package for decision: {}. remaining choices: {}",
464 state.pubgrub.package_store[highest_priority_pkg],
465 state
466 .pubgrub
467 .partial_solution
468 .undecided_packages()
469 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
470 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
471 .join(", ")
472 );
473
474 highest_priority_pkg
475 };
476
477 state.next = highest_priority_pkg;
478
479 let next_id = state.next;
481 let next_package = &state.pubgrub.package_store[state.next];
482
483 let url = next_package
484 .name()
485 .and_then(|name| state.fork_urls.get(name));
486 let index = next_package
487 .name()
488 .and_then(|name| state.fork_indexes.get(name));
489
490 self.request_package(next_package, url, index, request_sink)?;
502
503 let version = if let Some(version) = state.initial_version.take() {
504 version
508 } else {
509 let term_intersection = state
510 .pubgrub
511 .partial_solution
512 .term_intersection_for_package(next_id)
513 .expect("a package was chosen but we don't have a term");
514 let decision = self.choose_version(
515 next_package,
516 next_id,
517 index.map(IndexMetadata::url),
518 term_intersection.unwrap_positive(),
519 &mut state.pins,
520 &preferences,
521 &state.fork_urls,
522 &state.env,
523 &state.python_requirement,
524 &state.pubgrub,
525 &mut visited,
526 request_sink,
527 )?;
528
529 let Some(version) = decision else {
531 debug!("No compatible version found for: {next_package}");
532
533 let term_intersection = state
534 .pubgrub
535 .partial_solution
536 .term_intersection_for_package(next_id)
537 .expect("a package was chosen but we don't have a term");
538
539 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
540 if let Some(entry) = self.unavailable_packages.get(name) {
542 state
543 .pubgrub
544 .add_incompatibility(Incompatibility::custom_term(
545 next_id,
546 term_intersection.clone(),
547 UnavailableReason::Package(entry.clone()),
548 ));
549 continue;
550 }
551 }
552
553 state
554 .pubgrub
555 .add_incompatibility(Incompatibility::no_versions(
556 next_id,
557 term_intersection.clone(),
558 ));
559 continue;
560 };
561
562 let version = match version {
563 ResolverVersion::Unforked(version) => version,
564 ResolverVersion::Forked(forks) => {
565 forked_states.extend(self.version_forks_to_fork_states(state, forks));
566 continue 'FORK;
567 }
568 ResolverVersion::Unavailable(version, reason) => {
569 state.add_unavailable_version(version, reason);
570 continue;
571 }
572 };
573
574 if url.is_none() {
576 state.prefetcher.prefetch_batches(
577 next_package,
578 index,
579 &version,
580 term_intersection.unwrap_positive(),
581 state
582 .pubgrub
583 .partial_solution
584 .unchanging_term_for_package(next_id),
585 &state.python_requirement,
586 &self.selector,
587 &state.env,
588 )?;
589 }
590
591 version
592 };
593
594 state.prefetcher.version_tried(next_package, &version);
595
596 self.on_progress(next_package, &version);
597
598 if !state
599 .added_dependencies
600 .entry(next_id)
601 .or_default()
602 .insert(version.clone())
603 {
604 state
607 .pubgrub
608 .partial_solution
609 .add_decision(next_id, version);
610 continue;
611 }
612
613 let forked_deps = self.get_dependencies_forking(
615 next_id,
616 next_package,
617 &version,
618 &state.pins,
619 &state.fork_urls,
620 &state.env,
621 &state.python_requirement,
622 &state.pubgrub,
623 )?;
624
625 match forked_deps {
626 ForkedDependencies::Unavailable(reason) => {
627 state
630 .pubgrub
631 .add_incompatibility(Incompatibility::custom_version(
632 next_id,
633 version.clone(),
634 UnavailableReason::Version(reason),
635 ));
636 }
637 ForkedDependencies::Unforked(dependencies) => {
638 state
640 .visit_package_version_dependencies(
641 next_id,
642 &version,
643 &self.urls,
644 &self.indexes,
645 &dependencies,
646 &self.git,
647 &self.workspace_members,
648 self.selector.resolution_strategy(),
649 )
650 .map_err(|err| {
651 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
652 })?;
653
654 self.visit_dependencies(&dependencies, &state, request_sink)
656 .map_err(|err| {
657 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
658 })?;
659
660 state.add_package_version_dependencies(next_id, &version, dependencies);
662 }
663 ForkedDependencies::Forked {
664 mut forks,
665 diverging_packages,
666 } => {
667 debug!(
668 "Pre-fork {} took {:.3}s",
669 state.env,
670 start.elapsed().as_secs_f32()
671 );
672
673 match (self.options.fork_strategy, self.options.resolution_mode) {
675 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
676 forks.sort_by(|a, b| {
680 a.cmp_requires_python(b)
681 .reverse()
682 .then_with(|| a.cmp_upper_bounds(b))
683 });
684 }
685 (ForkStrategy::RequiresPython, _) => {
686 forks.sort_by(|a, b| {
690 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
691 });
692 }
693 }
694
695 for new_fork_state in self.forks_to_fork_states(
696 state,
697 &version,
698 forks,
699 request_sink,
700 &diverging_packages,
701 ) {
702 forked_states.push(new_fork_state?);
703 }
704 continue 'FORK;
705 }
706 }
707 }
708 }
709 if resolutions.len() > 1 {
710 info!(
711 "Solved your requirements for {} environments",
712 resolutions.len()
713 );
714 }
715 if tracing::enabled!(Level::DEBUG) {
716 for resolution in &resolutions {
717 if let Some(env) = resolution.env.end_user_fork_display() {
718 let packages: FxHashSet<_> = resolution
719 .nodes
720 .keys()
721 .map(|package| &package.name)
722 .collect();
723 debug!(
724 "Distinct solution for {env} with {} package(s)",
725 packages.len()
726 );
727 }
728 }
729 }
730 for resolution in &resolutions {
731 Self::trace_resolution(resolution);
732 }
733 ResolverOutput::from_state(
734 &resolutions,
735 &self.requirements,
736 &self.constraints,
737 &self.overrides,
738 &self.preferences,
739 &self.index,
740 &self.git,
741 &self.python_requirement,
742 &self.conflicts,
743 self.selector.resolution_strategy(),
744 self.options.clone(),
745 )
746 }
747
748 fn reprioritize_conflicts(state: &mut ForkState) {
752 for package in state.conflict_tracker.prioritize.drain(..) {
753 let changed = state
754 .priorities
755 .mark_conflict_early(&state.pubgrub.package_store[package]);
756 if changed {
757 debug!(
758 "Package {} has too many conflicts (affected), prioritizing",
759 &state.pubgrub.package_store[package]
760 );
761 } else {
762 debug!(
763 "Package {} has too many conflicts (affected), already {:?}",
764 state.pubgrub.package_store[package],
765 state.priorities.get(&state.pubgrub.package_store[package])
766 );
767 }
768 }
769
770 for package in state.conflict_tracker.deprioritize.drain(..) {
771 let changed = state
772 .priorities
773 .mark_conflict_late(&state.pubgrub.package_store[package]);
774 if changed {
775 debug!(
776 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
777 state.pubgrub.package_store[package],
778 );
779 let backtrack_level = state.pubgrub.backtrack_package(package);
780 if let Some(backtrack_level) = backtrack_level {
781 debug!("Backtracked {backtrack_level} decisions");
782 } else {
783 debug!(
784 "Package {} is not decided, cannot backtrack",
785 state.pubgrub.package_store[package]
786 );
787 }
788 } else {
789 debug!(
790 "Package {} has too many conflicts (culprit), already {:?}",
791 state.pubgrub.package_store[package],
792 state.priorities.get(&state.pubgrub.package_store[package])
793 );
794 }
795 }
796 }
797
798 fn trace_resolution(combined: &Resolution) {
804 if !tracing::enabled!(Level::TRACE) {
805 return;
806 }
807 trace!("Resolution: {:?}", combined.env);
808 for edge in &combined.edges {
809 trace!(
810 "Resolution edge: {} -> {}",
811 edge.from
812 .as_ref()
813 .map(PackageName::as_str)
814 .unwrap_or("ROOT"),
815 edge.to,
816 );
817 let mut msg = String::new();
820 write!(msg, "{}", edge.from_version).unwrap();
821 if let Some(ref extra) = edge.from_extra {
822 write!(msg, " (extra: {extra})").unwrap();
823 }
824 if let Some(ref dev) = edge.from_group {
825 write!(msg, " (group: {dev})").unwrap();
826 }
827
828 write!(msg, " -> ").unwrap();
829
830 write!(msg, "{}", edge.to_version).unwrap();
831 if let Some(ref extra) = edge.to_extra {
832 write!(msg, " (extra: {extra})").unwrap();
833 }
834 if let Some(ref dev) = edge.to_group {
835 write!(msg, " (group: {dev})").unwrap();
836 }
837 if let Some(marker) = edge.marker.contents() {
838 write!(msg, " ; {marker}").unwrap();
839 }
840 trace!("Resolution edge: {msg}");
841 }
842 }
843
844 fn forks_to_fork_states<'a>(
846 &'a self,
847 current_state: ForkState,
848 version: &'a Version,
849 forks: Vec<Fork>,
850 request_sink: &'a Sender<Request>,
851 diverging_packages: &'a [PackageName],
852 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
853 debug!(
854 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
855 current_state.pubgrub.package_store[current_state.next],
856 version,
857 diverging_packages
858 .iter()
859 .map(ToString::to_string)
860 .join(", "),
861 forks.len(),
862 if forks.len() == 1 { "" } else { "s" }
863 );
864 assert!(forks.len() >= 2);
865 let package = current_state.next;
871 let mut cur_state = Some(current_state);
872 let forks_len = forks.len();
873 forks
874 .into_iter()
875 .enumerate()
876 .map(move |(i, fork)| {
877 let is_last = i == forks_len - 1;
878 let forked_state = cur_state.take().unwrap();
879 if !is_last {
880 cur_state = Some(forked_state.clone());
881 }
882
883 let env = fork.env.clone();
884 (fork, forked_state.with_env(env))
885 })
886 .map(move |(fork, mut forked_state)| {
887 forked_state
889 .visit_package_version_dependencies(
890 package,
891 version,
892 &self.urls,
893 &self.indexes,
894 &fork.dependencies,
895 &self.git,
896 &self.workspace_members,
897 self.selector.resolution_strategy(),
898 )
899 .map_err(|err| {
900 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
901 })?;
902
903 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
905 .map_err(|err| {
906 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
907 })?;
908
909 forked_state.add_package_version_dependencies(package, version, fork.dependencies);
911
912 Ok(forked_state)
913 })
914 }
915
916 #[expect(clippy::unused_self)]
918 fn version_forks_to_fork_states(
919 &self,
920 current_state: ForkState,
921 forks: Vec<VersionFork>,
922 ) -> impl Iterator<Item = ForkState> + '_ {
923 let mut cur_state = Some(current_state);
929 let forks_len = forks.len();
930 forks.into_iter().enumerate().map(move |(i, fork)| {
931 let is_last = i == forks_len - 1;
932 let mut forked_state = cur_state.take().unwrap();
933 if !is_last {
934 cur_state = Some(forked_state.clone());
935 }
936 forked_state.initial_id = Some(fork.id);
937 forked_state.initial_version = fork.version;
938 forked_state.with_env(fork.env)
939 })
940 }
941
942 fn visit_dependencies(
944 &self,
945 dependencies: &[PubGrubDependency],
946 state: &ForkState,
947 request_sink: &Sender<Request>,
948 ) -> Result<(), ResolveError> {
949 for dependency in dependencies {
950 let PubGrubDependency {
951 package,
952 version: _,
953 parent: _,
954 source: _,
955 } = dependency;
956 let url = package.name().and_then(|name| state.fork_urls.get(name));
957 let index = package.name().and_then(|name| state.fork_indexes.get(name));
958 self.visit_package(package, url, index, request_sink)?;
959 }
960 Ok(())
961 }
962
963 fn visit_package(
966 &self,
967 package: &PubGrubPackage,
968 url: Option<&VerbatimParsedUrl>,
969 index: Option<&IndexMetadata>,
970 request_sink: &Sender<Request>,
971 ) -> Result<(), ResolveError> {
972 if url.is_none()
974 && package
975 .name()
976 .map(|name| self.urls.any_url(name))
977 .unwrap_or(true)
978 {
979 return Ok(());
980 }
981
982 self.request_package(package, url, index, request_sink)
983 }
984
985 fn request_package(
986 &self,
987 package: &PubGrubPackage,
988 url: Option<&VerbatimParsedUrl>,
989 index: Option<&IndexMetadata>,
990 request_sink: &Sender<Request>,
991 ) -> Result<(), ResolveError> {
992 let Some(name) = package.name_no_root() else {
994 return Ok(());
995 };
996
997 if let Some(url) = url {
998 if !self.hasher.allows_url(&url.verbatim) {
1000 return Err(ResolveError::UnhashedPackage(name.clone()));
1001 }
1002
1003 let dist = Dist::from_url(name.clone(), url.clone())?;
1005 if self.index.distributions().register(dist.distribution_id()) {
1006 request_sink.blocking_send(Request::Dist(dist))?;
1007 }
1008 } else if let Some(index) = index {
1009 if self
1011 .index
1012 .explicit()
1013 .register((name.clone(), index.url().clone()))
1014 {
1015 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1016 }
1017 } else {
1018 if self.index.implicit().register(name.clone()) {
1020 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1021 }
1022 }
1023 Ok(())
1024 }
1025
1026 fn pre_visit<'data>(
1029 packages: impl Iterator<Item = (&'data PubGrubPackage, &'data Range<Version>)>,
1030 urls: &Urls,
1031 indexes: &Indexes,
1032 python_requirement: &PythonRequirement,
1033 request_sink: &Sender<Request>,
1034 ) -> Result<(), ResolveError> {
1035 for (package, range) in packages {
1038 let PubGrubPackageInner::Package {
1039 name,
1040 extra: None,
1041 group: None,
1042 marker: MarkerTree::TRUE,
1043 } = &**package
1044 else {
1045 continue;
1046 };
1047 if urls.any_url(name) {
1050 continue;
1051 }
1052 if indexes.contains_key(name) {
1054 continue;
1055 }
1056 request_sink.blocking_send(Request::Prefetch(
1057 name.clone(),
1058 range.clone(),
1059 python_requirement.clone(),
1060 ))?;
1061 }
1062 Ok(())
1063 }
1064
1065 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1073 fn choose_version(
1074 &self,
1075 package: &PubGrubPackage,
1076 id: Id<PubGrubPackage>,
1077 index: Option<&IndexUrl>,
1078 range: &Range<Version>,
1079 pins: &mut FilePins,
1080 preferences: &Preferences,
1081 fork_urls: &ForkUrls,
1082 env: &ResolverEnvironment,
1083 python_requirement: &PythonRequirement,
1084 pubgrub: &State<UvDependencyProvider>,
1085 visited: &mut FxHashSet<PackageName>,
1086 request_sink: &Sender<Request>,
1087 ) -> Result<Option<ResolverVersion>, ResolveError> {
1088 match &**package {
1089 PubGrubPackageInner::Root(_) => {
1090 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1091 }
1092
1093 PubGrubPackageInner::Python(_) => {
1094 Ok(None)
1097 }
1098
1099 PubGrubPackageInner::System(_) => {
1100 let Some(version) = range.as_singleton() else {
1103 return Ok(None);
1104 };
1105 Ok(Some(ResolverVersion::Unforked(version.clone())))
1106 }
1107
1108 PubGrubPackageInner::Marker { name, .. }
1109 | PubGrubPackageInner::Extra { name, .. }
1110 | PubGrubPackageInner::Group { name, .. }
1111 | PubGrubPackageInner::Package { name, .. } => {
1112 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1113 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1114 } else {
1115 self.choose_version_registry(
1116 package,
1117 id,
1118 name,
1119 index,
1120 range,
1121 preferences,
1122 env,
1123 python_requirement,
1124 pubgrub,
1125 pins,
1126 visited,
1127 request_sink,
1128 )
1129 }
1130 }
1131 }
1132 }
1133
1134 fn choose_version_url(
1137 &self,
1138 id: Id<PubGrubPackage>,
1139 name: &PackageName,
1140 range: &Range<Version>,
1141 url: &VerbatimParsedUrl,
1142 env: &ResolverEnvironment,
1143 python_requirement: &PythonRequirement,
1144 pubgrub: &State<UvDependencyProvider>,
1145 ) -> Result<Option<ResolverVersion>, ResolveError> {
1146 debug!(
1147 "Searching for a compatible version of {name} @ {} ({range})",
1148 url.verbatim
1149 );
1150
1151 let dist = Dist::from_url(name.clone(), url.clone())?;
1152 let distribution_id = dist.distribution_id();
1153 let response = self
1154 .index
1155 .distributions()
1156 .wait_blocking(&distribution_id)
1157 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1158
1159 let metadata = match &*response {
1161 MetadataResponse::Found(archive) => &archive.metadata,
1162 MetadataResponse::Unavailable(reason) => {
1163 self.unavailable_packages
1164 .insert(name.clone(), reason.into());
1165 return Ok(None);
1166 }
1167 MetadataResponse::Error(dist, err) => {
1170 return Err(ResolveError::Dist(
1171 DistErrorKind::from_requested_dist(dist, &**err),
1172 dist.clone(),
1173 DerivationChain::default(),
1174 err.clone(),
1175 ));
1176 }
1177 };
1178
1179 let version = &metadata.version;
1180
1181 if !range.contains(version) {
1183 return Ok(None);
1184 }
1185
1186 if let Dist::Built(dist) = &dist {
1189 let filename = match &dist {
1190 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1191 BuiltDist::DirectUrl(dist) => &dist.filename,
1192 BuiltDist::GitPath(dist) => &dist.filename,
1193 BuiltDist::Path(dist) => &dist.filename,
1194 };
1195
1196 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1199 {
1200 let wheel_marker = implied_markers(filename);
1201 for environment_marker in self.options.artifact_environments.iter().copied() {
1204 if env.included_by_marker(environment_marker)
1206 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1207 {
1208 if wheel_marker.is_disjoint(environment_marker) {
1210 return Ok(Some(ResolverVersion::Unavailable(
1211 version.clone(),
1212 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1213 IncompatibleWheel::MissingPlatform(environment_marker),
1214 )),
1215 )));
1216 }
1217 }
1218 }
1219 }
1220
1221 if !python_requirement.target().matches_wheel_tag(filename) {
1223 return Ok(Some(ResolverVersion::Unavailable(
1224 filename.version.clone(),
1225 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1226 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1227 )),
1228 )));
1229 }
1230 }
1231
1232 if let Some(requires_python) = metadata.requires_python.as_ref() {
1234 if !python_requirement.target().is_contained_by(requires_python) {
1235 let kind = if python_requirement.installed() == python_requirement.target() {
1236 PythonRequirementKind::Installed
1237 } else {
1238 PythonRequirementKind::Target
1239 };
1240 return Ok(Some(ResolverVersion::Unavailable(
1241 version.clone(),
1242 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1243 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1244 )),
1245 )));
1246 }
1247 }
1248
1249 Ok(Some(ResolverVersion::Unforked(version.clone())))
1250 }
1251
1252 fn choose_version_registry(
1255 &self,
1256 package: &PubGrubPackage,
1257 id: Id<PubGrubPackage>,
1258 name: &PackageName,
1259 index: Option<&IndexUrl>,
1260 range: &Range<Version>,
1261 preferences: &Preferences,
1262 env: &ResolverEnvironment,
1263 python_requirement: &PythonRequirement,
1264 pubgrub: &State<UvDependencyProvider>,
1265 pins: &mut FilePins,
1266 visited: &mut FxHashSet<PackageName>,
1267 request_sink: &Sender<Request>,
1268 ) -> Result<Option<ResolverVersion>, ResolveError> {
1269 let versions_response = if let Some(index) = index {
1271 self.index
1272 .explicit()
1273 .wait_blocking(&(name.clone(), index.clone()))
1274 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1275 } else {
1276 self.index
1277 .implicit()
1278 .wait_blocking(name)
1279 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1280 };
1281 visited.insert(name.clone());
1282
1283 let version_maps = match *versions_response {
1284 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1285 VersionsResponse::NoIndex => {
1286 self.unavailable_packages
1287 .insert(name.clone(), UnavailablePackage::NoIndex);
1288 &[]
1289 }
1290 VersionsResponse::Offline => {
1291 self.unavailable_packages
1292 .insert(name.clone(), UnavailablePackage::Offline);
1293 &[]
1294 }
1295 VersionsResponse::NotFound => {
1296 self.unavailable_packages
1297 .insert(name.clone(), UnavailablePackage::NotFound);
1298 &[]
1299 }
1300 };
1301
1302 debug!("Searching for a compatible version of {package} ({range})");
1303
1304 let Some(candidate) = self.selector.select(
1306 name,
1307 range,
1308 version_maps,
1309 preferences,
1310 &self.installed_packages,
1311 &self.exclusions,
1312 index,
1313 env,
1314 self.tags.as_ref(),
1315 ) else {
1316 return Ok(None);
1318 };
1319
1320 let dist = match candidate.dist() {
1321 CandidateDist::Compatible(dist) => dist,
1322 CandidateDist::Incompatible {
1323 incompatible_dist: incompatibility,
1324 prioritized_dist: _,
1325 } => {
1326 return Ok(Some(ResolverVersion::Unavailable(
1328 candidate.version().clone(),
1329 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1332 )));
1333 }
1334 };
1335
1336 if let Some((requires_python, incompatibility)) =
1338 Self::check_requires_python(dist, python_requirement)
1339 {
1340 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1341 if env.marker_environment().is_none() {
1342 let forks = fork_version_by_python_requirement(
1343 requires_python,
1344 python_requirement,
1345 env,
1346 );
1347 if !forks.is_empty() {
1348 debug!(
1349 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1350 python_requirement.target(),
1351 requires_python,
1352 name,
1353 candidate.version(),
1354 forks
1355 .iter()
1356 .map(ToString::to_string)
1357 .collect::<Vec<_>>()
1358 .join(", ")
1359 );
1360 let forks = forks
1361 .into_iter()
1362 .map(|env| VersionFork {
1363 env,
1364 id,
1365 version: None,
1366 })
1367 .collect();
1368 return Ok(Some(ResolverVersion::Forked(forks)));
1369 }
1370 }
1371 }
1372
1373 return Ok(Some(ResolverVersion::Unavailable(
1374 candidate.version().clone(),
1375 UnavailableVersion::IncompatibleDist(incompatibility),
1376 )));
1377 }
1378
1379 if let Some(forked) = self.fork_version_registry(
1381 &candidate,
1382 dist,
1383 version_maps,
1384 package,
1385 id,
1386 name,
1387 index,
1388 range,
1389 preferences,
1390 env,
1391 pubgrub,
1392 pins,
1393 request_sink,
1394 )? {
1395 return Ok(Some(forked));
1396 }
1397
1398 let filename = match dist.for_installation() {
1399 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1400 .filename()
1401 .unwrap_or(Cow::Borrowed("unknown filename")),
1402 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1403 .filename()
1404 .unwrap_or(Cow::Borrowed("unknown filename")),
1405 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1406 };
1407
1408 debug!(
1409 "Selecting: {}=={} [{}] ({})",
1410 name,
1411 candidate.version(),
1412 candidate.choice_kind(),
1413 filename,
1414 );
1415 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1416
1417 let version = candidate.version().clone();
1418 Ok(Some(ResolverVersion::Unforked(version)))
1419 }
1420
1421 fn fork_version_registry(
1434 &self,
1435 candidate: &Candidate,
1436 dist: &CompatibleDist,
1437 version_maps: &[VersionMap],
1438 package: &PubGrubPackage,
1439 id: Id<PubGrubPackage>,
1440 name: &PackageName,
1441 index: Option<&IndexUrl>,
1442 range: &Range<Version>,
1443 preferences: &Preferences,
1444 env: &ResolverEnvironment,
1445 pubgrub: &State<UvDependencyProvider>,
1446 pins: &mut FilePins,
1447 request_sink: &Sender<Request>,
1448 ) -> Result<Option<ResolverVersion>, ResolveError> {
1449 if env.marker_environment().is_some() {
1451 return Ok(None);
1452 }
1453
1454 if dist.implied_markers().is_true() {
1457 return Ok(None);
1458 }
1459
1460 for marker in self.options.artifact_environments.iter().copied() {
1463 if env.included_by_marker(marker) {
1465 if dist.implied_markers().is_disjoint(marker)
1467 && !find_environments(id, pubgrub).is_disjoint(marker)
1468 {
1469 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1471 return Ok(Some(ResolverVersion::Unavailable(
1472 candidate.version().clone(),
1473 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1474 IncompatibleWheel::MissingPlatform(marker),
1475 )),
1476 )));
1477 };
1478
1479 debug!(
1480 "Forking on required platform `{}` for {}=={} ({})",
1481 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1482 name,
1483 candidate.version(),
1484 [&left, &right]
1485 .iter()
1486 .map(ToString::to_string)
1487 .collect::<Vec<_>>()
1488 .join(", ")
1489 );
1490 let forks = vec![
1491 VersionFork {
1492 env: left,
1493 id,
1494 version: None,
1495 },
1496 VersionFork {
1497 env: right,
1498 id,
1499 version: None,
1500 },
1501 ];
1502 return Ok(Some(ResolverVersion::Forked(forks)));
1503 }
1504 }
1505 }
1506
1507 if !candidate.version().is_local() {
1509 return Ok(None);
1510 }
1511
1512 debug!(
1513 "Looking at local version: {}=={}",
1514 name,
1515 candidate.version()
1516 );
1517
1518 let range = range.clone().intersection(&Range::singleton(
1520 candidate.version().clone().without_local(),
1521 ));
1522
1523 let Some(base_candidate) = self.selector.select(
1524 name,
1525 &range,
1526 version_maps,
1527 preferences,
1528 &self.installed_packages,
1529 &self.exclusions,
1530 index,
1531 env,
1532 self.tags.as_ref(),
1533 ) else {
1534 return Ok(None);
1535 };
1536 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1537 return Ok(None);
1538 };
1539
1540 let mut remainder = {
1542 let mut remainder = base_dist.implied_markers();
1543 remainder.and(dist.implied_markers().negate());
1544 remainder
1545 };
1546 if remainder.is_false() {
1547 return Ok(None);
1548 }
1549
1550 if !env.included_by_marker(remainder) {
1554 return Ok(None);
1555 }
1556
1557 if !env.included_by_marker(dist.implied_markers()) {
1560 let filename = match dist.for_installation() {
1561 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1562 .filename()
1563 .unwrap_or(Cow::Borrowed("unknown filename")),
1564 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1565 .filename()
1566 .unwrap_or(Cow::Borrowed("unknown filename")),
1567 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1568 };
1569
1570 debug!(
1571 "Preferring non-local candidate: {}=={} [{}] ({})",
1572 name,
1573 base_candidate.version(),
1574 base_candidate.choice_kind(),
1575 filename,
1576 );
1577 self.visit_candidate(
1578 &base_candidate,
1579 base_dist,
1580 package,
1581 name,
1582 pins,
1583 request_sink,
1584 )?;
1585
1586 return Ok(Some(ResolverVersion::Unforked(
1587 base_candidate.version().clone(),
1588 )));
1589 }
1590
1591 for value in [
1600 arcstr::literal!("darwin"),
1601 arcstr::literal!("linux"),
1602 arcstr::literal!("win32"),
1603 ] {
1604 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1605 key: MarkerValueString::SysPlatform,
1606 operator: MarkerOperator::Equal,
1607 value,
1608 });
1609 if dist.implied_markers().is_disjoint(sys_platform)
1610 && !remainder.is_disjoint(sys_platform)
1611 {
1612 remainder.or(sys_platform);
1613 }
1614 }
1615
1616 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1618 return Ok(None);
1619 };
1620
1621 debug!(
1622 "Forking platform for {}=={} ({})",
1623 name,
1624 candidate.version(),
1625 [&base_env, &local_env]
1626 .iter()
1627 .map(ToString::to_string)
1628 .collect::<Vec<_>>()
1629 .join(", ")
1630 );
1631 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1632 self.visit_candidate(
1633 &base_candidate,
1634 base_dist,
1635 package,
1636 name,
1637 pins,
1638 request_sink,
1639 )?;
1640
1641 let forks = vec![
1642 VersionFork {
1643 env: base_env.clone(),
1644 id,
1645 version: Some(base_candidate.version().clone()),
1646 },
1647 VersionFork {
1648 env: local_env.clone(),
1649 id,
1650 version: Some(candidate.version().clone()),
1651 },
1652 ];
1653 Ok(Some(ResolverVersion::Forked(forks)))
1654 }
1655
1656 fn visit_candidate(
1658 &self,
1659 candidate: &Candidate,
1660 dist: &CompatibleDist,
1661 package: &PubGrubPackage,
1662 name: &PackageName,
1663 pins: &mut FilePins,
1664 request_sink: &Sender<Request>,
1665 ) -> Result<(), ResolveError> {
1666 pins.insert(candidate, dist);
1669
1670 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1672 if self.dependency_mode.is_transitive() {
1673 let dist = dist.for_resolution();
1674 if self.index.distributions().register(dist.distribution_id()) {
1675 if name != dist.name() {
1676 return Err(ResolveError::MismatchedPackageName {
1677 request: "distribution",
1678 expected: name.clone(),
1679 actual: dist.name().clone(),
1680 });
1681 }
1682 if !self
1684 .hasher
1685 .allows_package(candidate.name(), candidate.version())
1686 {
1687 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1688 }
1689
1690 let request = Request::from(dist);
1691 request_sink.blocking_send(request)?;
1692 }
1693 }
1694 }
1695
1696 Ok(())
1697 }
1698
1699 fn check_requires_python<'dist>(
1702 dist: &'dist CompatibleDist,
1703 python_requirement: &PythonRequirement,
1704 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1705 let requires_python = dist.requires_python()?;
1706 if python_requirement.target().is_contained_by(requires_python) {
1707 None
1708 } else {
1709 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1710 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1711 requires_python.clone(),
1712 if python_requirement.installed() == python_requirement.target() {
1713 PythonRequirementKind::Installed
1714 } else {
1715 PythonRequirementKind::Target
1716 },
1717 ))
1718 } else {
1719 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1720 requires_python.clone(),
1721 if python_requirement.installed() == python_requirement.target() {
1722 PythonRequirementKind::Installed
1723 } else {
1724 PythonRequirementKind::Target
1725 },
1726 ))
1727 };
1728 Some((requires_python, incompatibility))
1729 }
1730 }
1731
1732 #[instrument(skip_all, fields(%package, %version))]
1734 fn get_dependencies_forking(
1735 &self,
1736 id: Id<PubGrubPackage>,
1737 package: &PubGrubPackage,
1738 version: &Version,
1739 pins: &FilePins,
1740 fork_urls: &ForkUrls,
1741 env: &ResolverEnvironment,
1742 python_requirement: &PythonRequirement,
1743 pubgrub: &State<UvDependencyProvider>,
1744 ) -> Result<ForkedDependencies, ResolveError> {
1745 let result = self.get_dependencies(
1746 id,
1747 package,
1748 version,
1749 pins,
1750 fork_urls,
1751 env,
1752 python_requirement,
1753 pubgrub,
1754 );
1755 if env.marker_environment().is_some() {
1756 result.map(|deps| match deps {
1757 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1758 ForkedDependencies::Unforked(deps)
1759 }
1760 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1761 })
1762 } else {
1763 Ok(result?.fork(env, python_requirement, &self.conflicts))
1764 }
1765 }
1766
1767 #[instrument(skip_all, fields(%package, %version))]
1769 fn get_dependencies(
1770 &self,
1771 id: Id<PubGrubPackage>,
1772 package: &PubGrubPackage,
1773 version: &Version,
1774 pins: &FilePins,
1775 fork_urls: &ForkUrls,
1776 env: &ResolverEnvironment,
1777 python_requirement: &PythonRequirement,
1778 pubgrub: &State<UvDependencyProvider>,
1779 ) -> Result<Dependencies, ResolveError> {
1780 let dependencies = match &**package {
1781 PubGrubPackageInner::Root(_) => {
1782 let no_dev_deps = BTreeMap::default();
1783 let requirements = self.flatten_requirements(
1784 &self.requirements,
1785 &no_dev_deps,
1786 None,
1787 None,
1788 None,
1789 env,
1790 python_requirement,
1791 );
1792
1793 requirements
1794 .filter(|requirement| !self.excludes.contains(&requirement.name))
1795 .flat_map(move |requirement| {
1796 PubGrubDependency::from_requirement(
1797 &self.conflicts,
1798 requirement,
1799 None,
1800 Some(package),
1801 )
1802 })
1803 .collect()
1804 }
1805
1806 PubGrubPackageInner::Package {
1807 name,
1808 extra,
1809 group,
1810 marker: _,
1811 } => {
1812 if self.dependency_mode.is_direct() {
1814 return Ok(Dependencies::Unforkable(Vec::default()));
1815 }
1816
1817 let owned_id;
1819 let distribution_id = if let Some((_, metadata_id)) =
1820 pins.dist_and_id(name, version)
1821 {
1822 metadata_id
1823 } else if let Some(url) = fork_urls.get(name) {
1824 let dist = Dist::from_url(name.clone(), url.clone())?;
1825 owned_id = dist.distribution_id();
1826 &owned_id
1827 } else {
1828 debug_assert!(
1829 false,
1830 "Dependencies were requested for a package without a pinned distribution"
1831 );
1832 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1833 };
1834
1835 if self.dependency_mode.is_transitive()
1837 && self.unavailable_packages.get(name).is_some()
1838 && self.installed_packages.get_packages(name).is_empty()
1839 {
1840 debug_assert!(
1841 false,
1842 "Dependencies were requested for a package that is not available"
1843 );
1844 return Err(ResolveError::PackageUnavailable(name.clone()));
1845 }
1846
1847 let response = self
1849 .index
1850 .distributions()
1851 .wait_blocking(distribution_id)
1852 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1853
1854 let metadata = match &*response {
1855 MetadataResponse::Found(archive) => &archive.metadata,
1856 MetadataResponse::Unavailable(reason) => {
1857 let unavailable_version = UnavailableVersion::from(reason);
1858 let message = unavailable_version.singular_message();
1859 if let Some(err) = reason.source() {
1860 warn!("{name} {message}: {err}");
1862 } else {
1863 warn!("{name} {message}");
1864 }
1865 self.incomplete_packages
1866 .entry(name.clone())
1867 .or_default()
1868 .insert(version.clone(), reason.clone());
1869 return Ok(Dependencies::Unavailable(unavailable_version));
1870 }
1871 MetadataResponse::Error(dist, err) => {
1872 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1873 .unwrap_or_default();
1874 return Err(ResolveError::Dist(
1875 DistErrorKind::from_requested_dist(dist, &**err),
1876 dist.clone(),
1877 chain,
1878 err.clone(),
1879 ));
1880 }
1881 };
1882
1883 if let Some(requires_python) = &metadata.requires_python {
1886 if !python_requirement.target().is_contained_by(requires_python) {
1887 return Ok(Dependencies::Unavailable(
1888 UnavailableVersion::RequiresPython(requires_python.clone()),
1889 ));
1890 }
1891 }
1892
1893 let system_dependencies = self
1895 .options
1896 .torch_backend
1897 .as_ref()
1898 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1899 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1900 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1901 .map(IndexUrl::url)
1902 .and_then(SystemDependency::from_index)
1903 .into_iter()
1904 .inspect(|system_dependency| {
1905 debug!(
1906 "Adding system dependency `{}` for `{package}@{version}`",
1907 system_dependency
1908 );
1909 })
1910 .map(PubGrubDependency::from);
1911
1912 let requirements = self.flatten_requirements(
1913 &metadata.requires_dist,
1914 &metadata.dependency_groups,
1915 extra.as_ref(),
1916 group.as_ref(),
1917 Some(name),
1918 env,
1919 python_requirement,
1920 );
1921
1922 requirements
1923 .filter(|requirement| !self.excludes.contains(&requirement.name))
1924 .flat_map(|requirement| {
1925 PubGrubDependency::from_requirement(
1926 &self.conflicts,
1927 requirement,
1928 group.as_ref(),
1929 Some(package),
1930 )
1931 })
1932 .chain(system_dependencies)
1933 .collect()
1934 }
1935
1936 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1937
1938 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1939
1940 PubGrubPackageInner::Marker { name, marker } => {
1942 return Ok(Dependencies::Unforkable(
1943 [MarkerTree::TRUE, *marker]
1944 .into_iter()
1945 .map(move |marker| PubGrubDependency {
1946 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1947 name: name.clone(),
1948 extra: None,
1949 group: None,
1950 marker,
1951 }),
1952 version: Range::singleton(version.clone()),
1953 parent: None,
1954 source: DependencySource::Unspecified,
1955 })
1956 .collect(),
1957 ));
1958 }
1959
1960 PubGrubPackageInner::Extra {
1962 name,
1963 extra,
1964 marker,
1965 } => {
1966 return Ok(Dependencies::Unforkable(
1967 [MarkerTree::TRUE, *marker]
1968 .into_iter()
1969 .dedup()
1970 .flat_map(move |marker| {
1971 [None, Some(extra)]
1972 .into_iter()
1973 .map(move |extra| PubGrubDependency {
1974 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1975 name: name.clone(),
1976 extra: extra.cloned(),
1977 group: None,
1978 marker,
1979 }),
1980 version: Range::singleton(version.clone()),
1981 parent: None,
1982 source: DependencySource::Unspecified,
1983 })
1984 })
1985 .collect(),
1986 ));
1987 }
1988
1989 PubGrubPackageInner::Group {
1991 name,
1992 group,
1993 marker,
1994 } => {
1995 return Ok(Dependencies::Unforkable(
1996 [MarkerTree::TRUE, *marker]
1997 .into_iter()
1998 .dedup()
1999 .map(|marker| PubGrubDependency {
2000 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2001 name: name.clone(),
2002 extra: None,
2003 group: Some(group.clone()),
2004 marker,
2005 }),
2006 version: Range::singleton(version.clone()),
2007 parent: None,
2008 source: DependencySource::Unspecified,
2009 })
2010 .collect(),
2011 ));
2012 }
2013 };
2014 Ok(Dependencies::Available(dependencies))
2015 }
2016
2017 fn flatten_requirements<'a>(
2021 &'a self,
2022 dependencies: &'a [Requirement],
2023 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2024 extra: Option<&'a ExtraName>,
2025 dev: Option<&'a GroupName>,
2026 name: Option<&PackageName>,
2027 env: &'a ResolverEnvironment,
2028 python_requirement: &'a PythonRequirement,
2029 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2030 let python_marker = python_requirement.to_marker_tree();
2031
2032 if let Some(dev) = dev {
2033 Either::Left(Either::Left(self.requirements_for_extra(
2036 dev_dependencies.get(dev).into_iter().flatten(),
2037 extra,
2038 env,
2039 python_marker,
2040 python_requirement,
2041 )))
2042 } else if !dependencies
2043 .iter()
2044 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2045 {
2046 Either::Left(Either::Right(self.requirements_for_extra(
2048 dependencies.iter(),
2049 extra,
2050 env,
2051 python_marker,
2052 python_requirement,
2053 )))
2054 } else {
2055 let mut requirements = self
2056 .requirements_for_extra(
2057 dependencies.iter(),
2058 extra,
2059 env,
2060 python_marker,
2061 python_requirement,
2062 )
2063 .collect::<Vec<_>>();
2064
2065 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2068 let mut queue: VecDeque<_> = requirements
2069 .iter()
2070 .filter(|req| name == Some(&req.name))
2071 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2072 .collect();
2073 while let Some((extra, marker)) = queue.pop_front() {
2074 if !seen.insert((extra.clone(), marker)) {
2075 continue;
2076 }
2077 for requirement in self.requirements_for_extra(
2078 dependencies,
2079 Some(&extra),
2080 env,
2081 python_marker,
2082 python_requirement,
2083 ) {
2084 let requirement = match requirement {
2085 Cow::Owned(mut requirement) => {
2086 requirement.marker.and(marker);
2087 requirement
2088 }
2089 Cow::Borrowed(requirement) => {
2090 let mut marker = marker;
2091 marker.and(requirement.marker);
2092 Requirement {
2093 name: requirement.name.clone(),
2094 extras: requirement.extras.clone(),
2095 groups: requirement.groups.clone(),
2096 source: requirement.source.clone(),
2097 origin: requirement.origin.clone(),
2098 marker: marker.simplify_extras(slice::from_ref(&extra)),
2099 }
2100 }
2101 };
2102 if name == Some(&requirement.name) {
2103 queue.extend(
2105 requirement
2106 .extras
2107 .iter()
2108 .cloned()
2109 .map(|extra| (extra, requirement.marker)),
2110 );
2111 } else {
2112 requirements.push(Cow::Owned(requirement));
2114 }
2115 }
2116 }
2117
2118 let mut self_constraints = vec![];
2122 for req in &requirements {
2123 if name == Some(&req.name) && !req.source.is_empty() {
2124 self_constraints.push(Requirement {
2125 name: req.name.clone(),
2126 extras: Box::new([]),
2127 groups: req.groups.clone(),
2128 source: req.source.clone(),
2129 origin: req.origin.clone(),
2130 marker: req.marker,
2131 });
2132 }
2133 }
2134
2135 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2137 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2138
2139 Either::Right(requirements.into_iter())
2140 }
2141 }
2142
2143 fn requirements_for_extra<'data, 'parameters>(
2146 &'data self,
2147 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2148 extra: Option<&'parameters ExtraName>,
2149 env: &'parameters ResolverEnvironment,
2150 python_marker: MarkerTree,
2151 python_requirement: &'parameters PythonRequirement,
2152 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2153 where
2154 'data: 'parameters,
2155 {
2156 self.overrides
2157 .apply(dependencies)
2158 .filter(move |requirement| {
2159 Self::is_requirement_applicable(
2160 requirement,
2161 extra,
2162 env,
2163 python_marker,
2164 python_requirement,
2165 )
2166 })
2167 .flat_map(move |requirement| {
2168 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2169 requirement,
2170 extra,
2171 env,
2172 python_marker,
2173 python_requirement,
2174 ))
2175 })
2176 }
2177
2178 fn is_requirement_applicable(
2181 requirement: &Requirement,
2182 extra: Option<&ExtraName>,
2183 env: &ResolverEnvironment,
2184 python_marker: MarkerTree,
2185 python_requirement: &PythonRequirement,
2186 ) -> bool {
2187 match extra {
2189 Some(source_extra) => {
2190 if requirement.evaluate_markers(env.marker_environment(), &[]) {
2192 return false;
2193 }
2194 if !requirement
2195 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2196 {
2197 return false;
2198 }
2199 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2200 {
2201 return false;
2202 }
2203 }
2204 None => {
2205 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2206 return false;
2207 }
2208 }
2209 }
2210
2211 if python_marker.is_disjoint(requirement.marker) {
2214 trace!(
2215 "Skipping {requirement} because of Requires-Python: {requires_python}",
2216 requires_python = python_requirement.target(),
2217 );
2218 return false;
2219 }
2220
2221 if !env.included_by_marker(requirement.marker) {
2224 trace!("Skipping {requirement} because of {env}");
2225 return false;
2226 }
2227
2228 true
2229 }
2230
2231 fn constraints_for_requirement<'data, 'parameters>(
2234 &'data self,
2235 requirement: Cow<'data, Requirement>,
2236 extra: Option<&'parameters ExtraName>,
2237 env: &'parameters ResolverEnvironment,
2238 python_marker: MarkerTree,
2239 python_requirement: &'parameters PythonRequirement,
2240 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2241 where
2242 'data: 'parameters,
2243 {
2244 self.constraints
2245 .get(&requirement.name)
2246 .into_iter()
2247 .flatten()
2248 .filter_map(move |constraint| {
2249 let constraint = if constraint.marker.is_true() {
2252 if requirement.marker.is_true() {
2256 Cow::Borrowed(constraint)
2257 } else {
2258 let mut marker = constraint.marker;
2259 marker.and(requirement.marker);
2260
2261 if marker.is_false() {
2262 trace!(
2263 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2264 constraint.marker.try_to_string().unwrap(),
2265 requirement.marker.try_to_string().unwrap(),
2266 );
2267 return None;
2268 }
2269
2270 Cow::Owned(Requirement {
2271 name: constraint.name.clone(),
2272 extras: constraint.extras.clone(),
2273 groups: constraint.groups.clone(),
2274 source: constraint.source.clone(),
2275 origin: constraint.origin.clone(),
2276 marker,
2277 })
2278 }
2279 } else {
2280 let requires_python = python_requirement.target();
2281
2282 let mut marker = constraint.marker;
2283 marker.and(requirement.marker);
2284
2285 if marker.is_false() {
2286 trace!(
2287 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2288 constraint.marker.try_to_string().unwrap(),
2289 requirement.marker.try_to_string().unwrap(),
2290 );
2291 return None;
2292 }
2293
2294 if python_marker.is_disjoint(marker) {
2298 trace!(
2299 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2300 );
2301 return None;
2302 }
2303
2304 if marker == constraint.marker {
2305 Cow::Borrowed(constraint)
2306 } else {
2307 Cow::Owned(Requirement {
2308 name: constraint.name.clone(),
2309 extras: constraint.extras.clone(),
2310 groups: constraint.groups.clone(),
2311 source: constraint.source.clone(),
2312 origin: constraint.origin.clone(),
2313 marker,
2314 })
2315 }
2316 };
2317
2318 if !env.included_by_marker(constraint.marker) {
2321 trace!("Skipping {constraint} because of {env}");
2322 return None;
2323 }
2324
2325 match extra {
2327 Some(source_extra) => {
2328 if !constraint
2329 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2330 {
2331 return None;
2332 }
2333 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2334 {
2335 return None;
2336 }
2337 }
2338 None => {
2339 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2340 return None;
2341 }
2342 }
2343 }
2344
2345 Some(constraint)
2346 })
2347 }
2348
2349 async fn fetch<Provider: ResolverProvider>(
2351 self: Arc<Self>,
2352 provider: Arc<Provider>,
2353 request_stream: Receiver<Request>,
2354 ) -> Result<(), ResolveError> {
2355 let mut response_stream = ReceiverStream::new(request_stream)
2356 .map(|request| self.process_request(request, &*provider).boxed_local())
2357 .buffer_unordered(usize::MAX);
2361
2362 while let Some(response) = response_stream.next().await {
2363 match response? {
2364 Some(Response::Package(name, index, version_map)) => {
2365 trace!("Received package metadata for: {name}");
2366 if let Some(index) = index {
2367 self.index
2368 .explicit()
2369 .done((name, index), Arc::new(version_map));
2370 } else {
2371 self.index.implicit().done(name, Arc::new(version_map));
2372 }
2373 }
2374 Some(Response::Installed { dist, metadata }) => {
2375 trace!("Received installed distribution metadata for: {dist}");
2376 self.index
2377 .distributions()
2378 .done(dist.distribution_id(), Arc::new(metadata));
2379 }
2380 Some(Response::Dist { dist, metadata }) => {
2381 let dist_kind = match dist {
2382 Dist::Built(_) => "built",
2383 Dist::Source(_) => "source",
2384 };
2385 trace!("Received {dist_kind} distribution metadata for: {dist}");
2386 if let MetadataResponse::Unavailable(reason) = &metadata {
2387 let message = UnavailableVersion::from(reason).singular_message();
2388 if let Some(err) = reason.source() {
2389 warn!("{dist} {message}: {err}");
2391 } else {
2392 warn!("{dist} {message}");
2393 }
2394 }
2395 self.index
2396 .distributions()
2397 .done(dist.distribution_id(), Arc::new(metadata));
2398 }
2399 None => {}
2400 }
2401 }
2402
2403 Ok::<(), ResolveError>(())
2404 }
2405
2406 #[instrument(skip_all, fields(%request))]
2407 async fn process_request<Provider: ResolverProvider>(
2408 &self,
2409 request: Request,
2410 provider: &Provider,
2411 ) -> Result<Option<Response>, ResolveError> {
2412 match request {
2413 Request::Package(package_name, index) => {
2415 let package_versions = provider
2416 .get_package_versions(&package_name, index.as_ref())
2417 .boxed_local()
2418 .await
2419 .map_err(ResolveError::Client)?;
2420
2421 Ok(Some(Response::Package(
2422 package_name,
2423 index.map(IndexMetadata::into_url),
2424 package_versions,
2425 )))
2426 }
2427
2428 Request::Dist(dist) => {
2430 if let Some(version) = dist.version() {
2431 if let Some(index) = dist.index() {
2432 let versions_response = self.index.implicit().get(dist.name());
2434 if let Some(VersionsResponse::Found(version_maps)) =
2435 versions_response.as_deref()
2436 {
2437 for version_map in version_maps {
2438 if version_map.index() == Some(index) {
2439 let Some(metadata) = version_map.get_metadata(version) else {
2440 continue;
2441 };
2442 debug!("Found registry-provided metadata for: {dist}");
2443 return Ok(Some(Response::Dist {
2444 dist,
2445 metadata: MetadataResponse::Found(
2446 ArchiveMetadata::from_metadata23(metadata.clone()),
2447 ),
2448 }));
2449 }
2450 }
2451 }
2452
2453 let versions_response = self
2455 .index
2456 .explicit()
2457 .get(&(dist.name().clone(), index.clone()));
2458 if let Some(VersionsResponse::Found(version_maps)) =
2459 versions_response.as_deref()
2460 {
2461 for version_map in version_maps {
2462 let Some(metadata) = version_map.get_metadata(version) else {
2463 continue;
2464 };
2465 debug!("Found registry-provided metadata for: {dist}");
2466 return Ok(Some(Response::Dist {
2467 dist,
2468 metadata: MetadataResponse::Found(
2469 ArchiveMetadata::from_metadata23(metadata.clone()),
2470 ),
2471 }));
2472 }
2473 }
2474 }
2475 }
2476
2477 let metadata = provider
2478 .get_or_build_wheel_metadata(&dist)
2479 .boxed_local()
2480 .await?;
2481
2482 if let MetadataResponse::Found(metadata) = &metadata {
2483 if &metadata.metadata.name != dist.name() {
2484 return Err(ResolveError::MismatchedPackageName {
2485 request: "distribution metadata",
2486 expected: dist.name().clone(),
2487 actual: metadata.metadata.name.clone(),
2488 });
2489 }
2490 }
2491
2492 Ok(Some(Response::Dist { dist, metadata }))
2493 }
2494
2495 Request::Installed(dist) => {
2496 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2497
2498 if let MetadataResponse::Found(metadata) = &metadata {
2499 if &metadata.metadata.name != dist.name() {
2500 return Err(ResolveError::MismatchedPackageName {
2501 request: "installed metadata",
2502 expected: dist.name().clone(),
2503 actual: metadata.metadata.name.clone(),
2504 });
2505 }
2506 }
2507
2508 Ok(Some(Response::Installed { dist, metadata }))
2509 }
2510
2511 Request::Prefetch(package_name, range, python_requirement) => {
2513 let versions_response = self
2515 .index
2516 .implicit()
2517 .wait(&package_name)
2518 .await
2519 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2520
2521 let version_map = match *versions_response {
2522 VersionsResponse::Found(ref version_map) => version_map,
2523 VersionsResponse::NoIndex => {
2525 self.unavailable_packages
2526 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2527
2528 return Ok(None);
2529 }
2530 VersionsResponse::Offline => {
2531 self.unavailable_packages
2532 .insert(package_name.clone(), UnavailablePackage::Offline);
2533
2534 return Ok(None);
2535 }
2536 VersionsResponse::NotFound => {
2537 self.unavailable_packages
2538 .insert(package_name.clone(), UnavailablePackage::NotFound);
2539
2540 return Ok(None);
2541 }
2542 };
2543
2544 let env = ResolverEnvironment::universal(vec![]);
2547
2548 let Some(candidate) = self.selector.select(
2551 &package_name,
2552 &range,
2553 version_map,
2554 &self.preferences,
2555 &self.installed_packages,
2556 &self.exclusions,
2557 None,
2558 &env,
2559 self.tags.as_ref(),
2560 ) else {
2561 return Ok(None);
2562 };
2563
2564 let Some(dist) = candidate.compatible() else {
2566 return Ok(None);
2567 };
2568
2569 for version_map in version_map {
2571 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2572 let dist = dist.for_resolution();
2573 if version_map.index() == dist.index() {
2574 debug!("Found registry-provided metadata for: {dist}");
2575
2576 let metadata = MetadataResponse::Found(
2577 ArchiveMetadata::from_metadata23(metadata.clone()),
2578 );
2579
2580 let dist = dist.to_owned();
2581 if &package_name != dist.name() {
2582 return Err(ResolveError::MismatchedPackageName {
2583 request: "distribution",
2584 expected: package_name,
2585 actual: dist.name().clone(),
2586 });
2587 }
2588
2589 let response = match dist {
2590 ResolvedDist::Installable { dist, .. } => Response::Dist {
2591 dist: (*dist).clone(),
2592 metadata,
2593 },
2594 ResolvedDist::Installed { dist } => Response::Installed {
2595 dist: (*dist).clone(),
2596 metadata,
2597 },
2598 };
2599
2600 return Ok(Some(response));
2601 }
2602 }
2603 }
2604
2605 if dist.wheel().is_none() {
2609 if !self.selector.use_highest_version(&package_name, &env) {
2610 if let Some((lower, _)) = range.iter().next() {
2611 if lower == &Bound::Unbounded {
2612 debug!(
2613 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2614 );
2615 return Ok(None);
2616 }
2617 }
2618 }
2619 }
2620
2621 let requires_python = match dist {
2623 CompatibleDist::InstalledDist(_) => None,
2624 CompatibleDist::SourceDist { sdist, .. }
2625 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2626 sdist.file.requires_python.as_ref()
2627 }
2628 CompatibleDist::CompatibleWheel { wheel, .. } => {
2629 wheel.file.requires_python.as_ref()
2630 }
2631 };
2632 if let Some(requires_python) = requires_python.as_ref() {
2633 if !python_requirement.target().is_contained_by(requires_python) {
2634 return Ok(None);
2635 }
2636 }
2637
2638 if !self
2640 .hasher
2641 .allows_package(candidate.name(), candidate.version())
2642 {
2643 return Ok(None);
2644 }
2645
2646 let dist = dist.for_resolution();
2648 if self.index.distributions().register(dist.distribution_id()) {
2649 let dist = dist.to_owned();
2650 if &package_name != dist.name() {
2651 return Err(ResolveError::MismatchedPackageName {
2652 request: "distribution",
2653 expected: package_name,
2654 actual: dist.name().clone(),
2655 });
2656 }
2657
2658 let response = match dist {
2659 ResolvedDist::Installable { dist, .. } => {
2660 let metadata = provider
2661 .get_or_build_wheel_metadata(&dist)
2662 .boxed_local()
2663 .await?;
2664
2665 Response::Dist {
2666 dist: (*dist).clone(),
2667 metadata,
2668 }
2669 }
2670 ResolvedDist::Installed { dist } => {
2671 let metadata =
2672 provider.get_installed_metadata(&dist).boxed_local().await?;
2673
2674 Response::Installed {
2675 dist: (*dist).clone(),
2676 metadata,
2677 }
2678 }
2679 };
2680
2681 Ok(Some(response))
2682 } else {
2683 Ok(None)
2684 }
2685 }
2686 }
2687 }
2688
2689 fn convert_no_solution_err(
2690 &self,
2691 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2692 fork_urls: ForkUrls,
2693 fork_indexes: ForkIndexes,
2694 env: ResolverEnvironment,
2695 current_environment: MarkerEnvironment,
2696 visited: &FxHashSet<PackageName>,
2697 ) -> ResolveError {
2698 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2699 err,
2700 ));
2701
2702 let mut unavailable_packages = FxHashMap::default();
2703 for package in derivation_tree_packages(&err) {
2704 if let PubGrubPackageInner::Package { name, .. } = &**package {
2705 if let Some(reason) = self.unavailable_packages.get(name) {
2706 unavailable_packages.insert(name.clone(), reason.clone());
2707 }
2708 }
2709 }
2710
2711 let mut incomplete_packages = FxHashMap::default();
2712 for package in derivation_tree_packages(&err) {
2713 if let PubGrubPackageInner::Package { name, .. } = &**package {
2714 if let Some(versions) = self.incomplete_packages.get(name) {
2715 for entry in versions.iter() {
2716 let (version, reason) = entry.pair();
2717 incomplete_packages
2718 .entry(name.clone())
2719 .or_insert_with(BTreeMap::default)
2720 .insert(version.clone(), reason.clone());
2721 }
2722 }
2723 }
2724 }
2725
2726 let mut available_indexes = FxHashMap::default();
2727 let mut included_versions = FxHashMap::default();
2728 let mut available_versions = FxHashMap::default();
2729
2730 let available_version_cutoff: Option<jiff::Timestamp> =
2731 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2732 .ok()
2733 .and_then(|s| s.parse().ok());
2734
2735 for package in derivation_tree_packages(&err) {
2736 let Some(name) = package.name() else { continue };
2737 if !visited.contains(name) {
2738 continue;
2743 }
2744 let versions_response = if let Some(index) = fork_indexes.get(name) {
2745 self.index
2746 .explicit()
2747 .get(&(name.clone(), index.url().clone()))
2748 } else {
2749 self.index.implicit().get(name)
2750 };
2751 if let Some(response) = versions_response {
2752 if let VersionsResponse::Found(ref version_maps) = *response {
2753 for version_map in version_maps {
2755 let package_included_versions = included_versions
2756 .entry(name.clone())
2757 .or_insert_with(BTreeSet::new);
2758 let package_available_versions = available_versions
2759 .entry(name.clone())
2760 .or_insert_with(BTreeSet::new);
2761
2762 for (version, dists) in version_map.iter(&Ranges::full()) {
2763 let excluded_from_included = || {
2768 let Some(included_version_cutoff) =
2769 version_map.included_version_cutoff()
2770 else {
2771 return false;
2772 };
2773 let Some(prioritized_dist) = dists.prioritized_dist() else {
2774 return true;
2775 };
2776 prioritized_dist.files().all(|file| {
2777 file.upload_time_utc_ms.is_none_or(|upload_time| {
2778 upload_time >= included_version_cutoff.as_millisecond()
2779 })
2780 })
2781 };
2782
2783 if !excluded_from_included() {
2784 package_included_versions.insert(version.clone());
2785 }
2786
2787 let excluded_from_available = || {
2793 let Some(ref exclude_newer) = available_version_cutoff else {
2794 return false;
2795 };
2796 let Some(prioritized_dist) = dists.prioritized_dist() else {
2797 return false;
2798 };
2799 prioritized_dist.files().all(|file| {
2800 file.upload_time_utc_ms.is_some_and(|upload_time| {
2801 upload_time >= exclude_newer.as_millisecond()
2802 })
2803 })
2804 };
2805
2806 if !excluded_from_available() {
2807 package_available_versions.insert(version.clone());
2808 }
2809 }
2810 }
2811
2812 available_indexes
2814 .entry(name.clone())
2815 .or_insert(BTreeSet::new())
2816 .extend(
2817 version_maps
2818 .iter()
2819 .filter_map(|version_map| version_map.index().cloned()),
2820 );
2821 }
2822 }
2823 }
2824
2825 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2826 err,
2827 self.index.clone(),
2828 included_versions,
2829 available_versions,
2830 available_indexes,
2831 self.selector.clone(),
2832 self.python_requirement.clone(),
2833 self.locations.clone(),
2834 self.capabilities.clone(),
2835 unavailable_packages,
2836 incomplete_packages,
2837 fork_urls,
2838 fork_indexes,
2839 env,
2840 current_environment,
2841 self.tags.clone(),
2842 self.workspace_members.clone(),
2843 self.options.clone(),
2844 )))
2845 }
2846
2847 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2848 if let Some(reporter) = self.reporter.as_ref() {
2849 match &**package {
2850 PubGrubPackageInner::Root(_) => {}
2851 PubGrubPackageInner::Python(_) => {}
2852 PubGrubPackageInner::System(_) => {}
2853 PubGrubPackageInner::Marker { .. } => {}
2854 PubGrubPackageInner::Extra { .. } => {}
2855 PubGrubPackageInner::Group { .. } => {}
2856 PubGrubPackageInner::Package { name, .. } => {
2857 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2858 }
2859 }
2860 }
2861 }
2862
2863 fn on_complete(&self) {
2864 if let Some(reporter) = self.reporter.as_ref() {
2865 reporter.on_complete();
2866 }
2867 }
2868}
2869
2870#[derive(Clone)]
2872pub(crate) struct ForkState {
2873 pubgrub: State<UvDependencyProvider>,
2881 initial_id: Option<Id<PubGrubPackage>>,
2884 initial_version: Option<Version>,
2887 next: Id<PubGrubPackage>,
2889 pins: FilePins,
2898 fork_urls: ForkUrls,
2904 fork_indexes: ForkIndexes,
2909 priorities: PubGrubPriorities,
2915 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2918 env: ResolverEnvironment,
2933 python_requirement: PythonRequirement,
2946 conflict_tracker: ConflictTracker,
2947 prefetcher: BatchPrefetcher,
2951}
2952
2953impl ForkState {
2954 fn new(
2955 pubgrub: State<UvDependencyProvider>,
2956 env: ResolverEnvironment,
2957 python_requirement: PythonRequirement,
2958 prefetcher: BatchPrefetcher,
2959 ) -> Self {
2960 Self {
2961 initial_id: None,
2962 initial_version: None,
2963 next: pubgrub.root_package,
2964 pubgrub,
2965 pins: FilePins::default(),
2966 fork_urls: ForkUrls::default(),
2967 fork_indexes: ForkIndexes::default(),
2968 priorities: PubGrubPriorities::default(),
2969 added_dependencies: FxHashMap::default(),
2970 env,
2971 python_requirement,
2972 conflict_tracker: ConflictTracker::default(),
2973 prefetcher,
2974 }
2975 }
2976
2977 fn visit_package_version_dependencies(
2980 &mut self,
2981 for_package: Id<PubGrubPackage>,
2982 for_version: &Version,
2983 urls: &Urls,
2984 indexes: &Indexes,
2985 dependencies: &[PubGrubDependency],
2986 git: &GitResolver,
2987 workspace_members: &BTreeSet<PackageName>,
2988 resolution_strategy: &ResolutionStrategy,
2989 ) -> Result<(), ResolveError> {
2990 for dependency in dependencies {
2991 let PubGrubDependency {
2992 package,
2993 version,
2994 parent: _,
2995 source,
2996 } = dependency;
2997
2998 let mut has_url = false;
2999 if let Some(name) = package.name() {
3000 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3005 self.fork_urls.insert(name, url, &self.env)?;
3006 has_url = true;
3007 }
3008
3009 if let Some(index) = source.explicit_index() {
3010 self.fork_indexes.insert(name, index, &self.env)?;
3011 }
3012
3013 for index in indexes.get(name, &self.env) {
3015 self.fork_indexes.insert(name, index, &self.env)?;
3016 }
3017 }
3018
3019 if let Some(name) = self.pubgrub.package_store[for_package]
3020 .name_no_root()
3021 .filter(|name| !workspace_members.contains(name))
3022 {
3023 debug!(
3024 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3025 );
3026 } else {
3027 debug!("Adding direct dependency: {package}{version}");
3029
3030 let missing_lower_bound = version
3032 .bounding_range()
3033 .map(|(lowest, _highest)| lowest == Bound::Unbounded)
3034 .unwrap_or(true);
3035 let strategy_lowest = matches!(
3036 resolution_strategy,
3037 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3038 );
3039
3040 if !has_url && missing_lower_bound && strategy_lowest {
3041 let name = package.name_no_root().unwrap();
3042 let bound_on_other_package = dependencies.iter().any(|other| {
3049 Some(name) == other.package.name()
3050 && !other
3051 .version
3052 .bounding_range()
3053 .map(|(lowest, _highest)| lowest == Bound::Unbounded)
3054 .unwrap_or(true)
3055 });
3056
3057 if !bound_on_other_package {
3058 warn_user_once!(
3059 "The direct dependency `{name}` is unpinned. \
3060 Consider setting a lower bound when using `--resolution lowest` \
3061 or `--resolution lowest-direct` to avoid using outdated versions.",
3062 );
3063 }
3064 }
3065 }
3066
3067 self.priorities.insert(package, version, &self.fork_urls);
3069 if let Some(base_package) = package.base_package() {
3072 self.priorities
3073 .insert(&base_package, version, &self.fork_urls);
3074 }
3075 }
3076
3077 Ok(())
3078 }
3079
3080 fn add_package_version_dependencies(
3082 &mut self,
3083 for_package: Id<PubGrubPackage>,
3084 for_version: &Version,
3085 dependencies: Vec<PubGrubDependency>,
3086 ) {
3087 for dependency in &dependencies {
3088 let PubGrubDependency {
3089 package,
3090 version,
3091 parent: _,
3092 source: _,
3093 } = dependency;
3094
3095 let Some(base_package) = package.base_package() else {
3096 continue;
3097 };
3098
3099 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3100 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3101 self.pubgrub
3102 .add_proxy_package(proxy_package, base_package_id, version.clone());
3103 }
3104
3105 let conflict = self.pubgrub.add_package_version_dependencies(
3106 self.next,
3107 for_version.clone(),
3108 dependencies.into_iter().map(|dependency| {
3109 let PubGrubDependency {
3110 package,
3111 version,
3112 parent: _,
3113 source: _,
3114 } = dependency;
3115 (package, version)
3116 }),
3117 );
3118
3119 if let Some(incompatibility) = conflict {
3122 self.record_conflict(for_package, Some(for_version), incompatibility);
3123 }
3124 }
3125
3126 fn record_conflict(
3127 &mut self,
3128 affected: Id<PubGrubPackage>,
3129 version: Option<&Version>,
3130 incompatibility: IncompId<PubGrubPackage, Ranges<Version>, UnavailableReason>,
3131 ) {
3132 let mut culprit_is_real = false;
3133 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3134 if incompatible == affected {
3135 continue;
3136 }
3137 if self.pubgrub.package_store[affected].name()
3138 == self.pubgrub.package_store[incompatible].name()
3139 {
3140 continue;
3143 }
3144 culprit_is_real = true;
3145 let culprit_count = self
3146 .conflict_tracker
3147 .culprit
3148 .entry(incompatible)
3149 .or_default();
3150 *culprit_count += 1;
3151 if *culprit_count == CONFLICT_THRESHOLD {
3152 self.conflict_tracker.deprioritize.push(incompatible);
3153 }
3154 }
3155 if culprit_is_real {
3158 if tracing::enabled!(Level::DEBUG) {
3159 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3160 .iter()
3161 .map(|(package, _term)| &self.pubgrub.package_store[package])
3162 .join(", ");
3163 if let Some(version) = version {
3164 debug!(
3165 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3166 self.pubgrub.package_store[affected], version, incompatibility
3167 );
3168 } else {
3169 debug!(
3170 "Recording unit propagation conflict of {} from incompatibility of ({})",
3171 self.pubgrub.package_store[affected], incompatibility
3172 );
3173 }
3174 }
3175
3176 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3177 *affected_count += 1;
3178 if *affected_count == CONFLICT_THRESHOLD {
3179 self.conflict_tracker.prioritize.push(self.next);
3180 }
3181 }
3182 }
3183
3184 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3185 if let UnavailableVersion::IncompatibleDist(
3189 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3190 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3191 ) = reason
3192 {
3193 let package = &self.next;
3194 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3195 PubGrubPackageInner::Python(match kind {
3196 PythonRequirementKind::Installed => PubGrubPython::Installed,
3197 PythonRequirementKind::Target => PubGrubPython::Target,
3198 }),
3199 ));
3200 self.pubgrub
3201 .add_incompatibility(Incompatibility::from_dependency(
3202 *package,
3203 Range::singleton(version.clone()),
3204 (python, release_specifiers_to_ranges(requires_python)),
3205 ));
3206 self.pubgrub
3207 .partial_solution
3208 .add_decision(self.next, version);
3209 return;
3210 }
3211 self.pubgrub
3212 .add_incompatibility(Incompatibility::custom_version(
3213 self.next,
3214 version.clone(),
3215 UnavailableReason::Version(reason),
3216 ));
3217 }
3218
3219 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3225 self.env = env;
3226 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3228 debug!("Narrowed `requires-python` bound to: {}", req.target());
3229 self.python_requirement = req;
3230 }
3231 self
3232 }
3233
3234 fn source(
3238 &self,
3239 name: &PackageName,
3240 version: &Version,
3241 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3242 let url = self.fork_urls.get(name);
3243 let index = url
3244 .is_none()
3245 .then(|| {
3246 self.pins
3247 .get(name, version)
3248 .expect("Every package should be pinned")
3249 .index()
3250 })
3251 .flatten();
3252 (url, index)
3253 }
3254
3255 fn into_resolution(self) -> Resolution {
3256 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3257 let edge_count: usize = solution
3258 .keys()
3259 .map(|package| self.pubgrub.incompatibilities[package].len())
3260 .sum();
3261 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3262 for (package, self_version) in &solution {
3263 for id in &self.pubgrub.incompatibilities[package] {
3264 let pubgrub::Kind::FromDependencyOf(
3265 self_package,
3266 ref self_range,
3267 dependency_package,
3268 ref dependency_range,
3269 ) = self.pubgrub.incompatibility_store[*id].kind
3270 else {
3271 continue;
3272 };
3273 if *package != self_package {
3274 continue;
3275 }
3276 if !self_range.contains(self_version) {
3277 continue;
3278 }
3279 let Some(dependency_version) = solution.get(&dependency_package) else {
3280 continue;
3281 };
3282 if !dependency_range.contains(dependency_version) {
3283 continue;
3284 }
3285
3286 let self_package = &self.pubgrub.package_store[self_package];
3287 let dependency_package = &self.pubgrub.package_store[dependency_package];
3288
3289 let (self_name, self_extra, self_group) = match &**self_package {
3290 PubGrubPackageInner::Package {
3291 name: self_name,
3292 extra: self_extra,
3293 group: self_group,
3294 marker: _,
3295 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3296
3297 PubGrubPackageInner::Root(_) => (None, None, None),
3298
3299 _ => continue,
3300 };
3301
3302 let (self_url, self_index) = self_name
3303 .map(|self_name| self.source(self_name, self_version))
3304 .unwrap_or((None, None));
3305
3306 match **dependency_package {
3307 PubGrubPackageInner::Package {
3308 name: ref dependency_name,
3309 extra: ref dependency_extra,
3310 group: ref dependency_dev,
3311 marker: ref dependency_marker,
3312 } => {
3313 debug_assert!(
3314 dependency_extra.is_none(),
3315 "Packages should depend on an extra proxy"
3316 );
3317 debug_assert!(
3318 dependency_dev.is_none(),
3319 "Packages should depend on a group proxy"
3320 );
3321
3322 if self_group.is_none() {
3325 if self_name == Some(dependency_name) {
3326 continue;
3327 }
3328 }
3329
3330 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3331
3332 let edge = ResolutionDependencyEdge {
3333 from: self_name.cloned(),
3334 from_version: self_version.clone(),
3335 from_url: self_url.cloned(),
3336 from_index: self_index.cloned(),
3337 from_extra: self_extra.cloned(),
3338 from_group: self_group.cloned(),
3339 to: dependency_name.clone(),
3340 to_version: dependency_version.clone(),
3341 to_url: to_url.cloned(),
3342 to_index: to_index.cloned(),
3343 to_extra: dependency_extra.clone(),
3344 to_group: dependency_dev.clone(),
3345 marker: *dependency_marker,
3346 };
3347 edges.push(edge);
3348 }
3349
3350 PubGrubPackageInner::Marker {
3351 name: ref dependency_name,
3352 marker: ref dependency_marker,
3353 } => {
3354 if self_group.is_none() {
3357 if self_name == Some(dependency_name) {
3358 continue;
3359 }
3360 }
3361
3362 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3363
3364 let edge = ResolutionDependencyEdge {
3365 from: self_name.cloned(),
3366 from_version: self_version.clone(),
3367 from_url: self_url.cloned(),
3368 from_index: self_index.cloned(),
3369 from_extra: self_extra.cloned(),
3370 from_group: self_group.cloned(),
3371 to: dependency_name.clone(),
3372 to_version: dependency_version.clone(),
3373 to_url: to_url.cloned(),
3374 to_index: to_index.cloned(),
3375 to_extra: None,
3376 to_group: None,
3377 marker: *dependency_marker,
3378 };
3379 edges.push(edge);
3380 }
3381
3382 PubGrubPackageInner::Extra {
3383 name: ref dependency_name,
3384 extra: ref dependency_extra,
3385 marker: ref dependency_marker,
3386 } => {
3387 if self_group.is_none() {
3388 debug_assert!(
3389 self_name != Some(dependency_name),
3390 "Extras should be flattened"
3391 );
3392 }
3393 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3394
3395 let edge = ResolutionDependencyEdge {
3397 from: self_name.cloned(),
3398 from_version: self_version.clone(),
3399 from_url: self_url.cloned(),
3400 from_index: self_index.cloned(),
3401 from_extra: self_extra.cloned(),
3402 from_group: self_group.cloned(),
3403 to: dependency_name.clone(),
3404 to_version: dependency_version.clone(),
3405 to_url: to_url.cloned(),
3406 to_index: to_index.cloned(),
3407 to_extra: Some(dependency_extra.clone()),
3408 to_group: None,
3409 marker: *dependency_marker,
3410 };
3411 edges.push(edge);
3412
3413 let edge = ResolutionDependencyEdge {
3415 from: self_name.cloned(),
3416 from_version: self_version.clone(),
3417 from_url: self_url.cloned(),
3418 from_index: self_index.cloned(),
3419 from_extra: self_extra.cloned(),
3420 from_group: self_group.cloned(),
3421 to: dependency_name.clone(),
3422 to_version: dependency_version.clone(),
3423 to_url: to_url.cloned(),
3424 to_index: to_index.cloned(),
3425 to_extra: None,
3426 to_group: None,
3427 marker: *dependency_marker,
3428 };
3429 edges.push(edge);
3430 }
3431
3432 PubGrubPackageInner::Group {
3433 name: ref dependency_name,
3434 group: ref dependency_group,
3435 marker: ref dependency_marker,
3436 } => {
3437 debug_assert!(
3438 self_name != Some(dependency_name),
3439 "Groups should be flattened"
3440 );
3441
3442 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3443
3444 let edge = ResolutionDependencyEdge {
3447 from: self_name.cloned(),
3448 from_version: self_version.clone(),
3449 from_url: self_url.cloned(),
3450 from_index: self_index.cloned(),
3451 from_extra: self_extra.cloned(),
3452 from_group: self_group.cloned(),
3453 to: dependency_name.clone(),
3454 to_version: dependency_version.clone(),
3455 to_url: to_url.cloned(),
3456 to_index: to_index.cloned(),
3457 to_extra: None,
3458 to_group: Some(dependency_group.clone()),
3459 marker: *dependency_marker,
3460 };
3461 edges.push(edge);
3462 }
3463
3464 _ => {}
3465 }
3466 }
3467 }
3468
3469 let nodes = solution
3470 .into_iter()
3471 .filter_map(|(package, version)| {
3472 if let PubGrubPackageInner::Package {
3473 name,
3474 extra,
3475 group,
3476 marker: MarkerTree::TRUE,
3477 } = &*self.pubgrub.package_store[package]
3478 {
3479 let (url, index) = self.source(name, &version);
3480 Some((
3481 ResolutionPackage {
3482 name: name.clone(),
3483 extra: extra.clone(),
3484 dev: group.clone(),
3485 url: url.cloned(),
3486 index: index.cloned(),
3487 },
3488 version,
3489 ))
3490 } else {
3491 None
3492 }
3493 })
3494 .collect();
3495
3496 Resolution {
3497 nodes,
3498 edges,
3499 pins: self.pins,
3500 env: self.env,
3501 }
3502 }
3503}
3504
3505#[derive(Debug)]
3507pub(crate) struct Resolution {
3508 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3509 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3512 pub(crate) pins: FilePins,
3514 pub(crate) env: ResolverEnvironment,
3516}
3517
3518#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3521pub(crate) struct ResolutionPackage {
3522 pub(crate) name: PackageName,
3523 pub(crate) extra: Option<ExtraName>,
3524 pub(crate) dev: Option<GroupName>,
3525 pub(crate) url: Option<VerbatimParsedUrl>,
3527 pub(crate) index: Option<IndexUrl>,
3529}
3530
3531#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3534pub(crate) struct ResolutionDependencyEdge {
3535 pub(crate) from: Option<PackageName>,
3537 pub(crate) from_version: Version,
3538 pub(crate) from_url: Option<VerbatimParsedUrl>,
3539 pub(crate) from_index: Option<IndexUrl>,
3540 pub(crate) from_extra: Option<ExtraName>,
3541 pub(crate) from_group: Option<GroupName>,
3542 pub(crate) to: PackageName,
3543 pub(crate) to_version: Version,
3544 pub(crate) to_url: Option<VerbatimParsedUrl>,
3545 pub(crate) to_index: Option<IndexUrl>,
3546 pub(crate) to_extra: Option<ExtraName>,
3547 pub(crate) to_group: Option<GroupName>,
3548 pub(crate) marker: MarkerTree,
3549}
3550
3551impl ResolutionDependencyEdge {
3552 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3553 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3557 }
3558}
3559
3560#[derive(Debug)]
3562#[expect(clippy::large_enum_variant)]
3563pub(crate) enum Request {
3564 Package(PackageName, Option<IndexMetadata>),
3566 Dist(Dist),
3568 Installed(InstalledDist),
3570 Prefetch(PackageName, Range<Version>, PythonRequirement),
3572}
3573
3574impl<'a> From<ResolvedDistRef<'a>> for Request {
3575 fn from(dist: ResolvedDistRef<'a>) -> Self {
3576 match dist {
3582 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3583 let source = prioritized.source_dist().expect("a source distribution");
3586 assert_eq!(
3587 (&sdist.name, &sdist.version),
3588 (&source.name, &source.version),
3589 "expected chosen sdist to match prioritized sdist"
3590 );
3591 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3592 }
3593 ResolvedDistRef::InstallableRegistryBuiltDist {
3594 wheel, prioritized, ..
3595 } => {
3596 assert_eq!(
3597 Some(&wheel.filename),
3598 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3599 "expected chosen wheel to match best wheel"
3600 );
3601 let built = prioritized.built_dist().expect("at least one wheel");
3604 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3605 }
3606 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3607 }
3608 }
3609}
3610
3611impl Display for Request {
3612 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3613 match self {
3614 Self::Package(package_name, _) => {
3615 write!(f, "Versions {package_name}")
3616 }
3617 Self::Dist(dist) => {
3618 write!(f, "Metadata {dist}")
3619 }
3620 Self::Installed(dist) => {
3621 write!(f, "Installed metadata {dist}")
3622 }
3623 Self::Prefetch(package_name, range, _) => {
3624 write!(f, "Prefetch {package_name} {range}")
3625 }
3626 }
3627 }
3628}
3629
3630#[derive(Debug)]
3631#[expect(clippy::large_enum_variant)]
3632enum Response {
3633 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3635 Dist {
3637 dist: Dist,
3638 metadata: MetadataResponse,
3639 },
3640 Installed {
3642 dist: InstalledDist,
3643 metadata: MetadataResponse,
3644 },
3645}
3646
3647enum Dependencies {
3653 Unavailable(UnavailableVersion),
3655 Available(Vec<PubGrubDependency>),
3661 Unforkable(Vec<PubGrubDependency>),
3667}
3668
3669impl Dependencies {
3670 fn fork(
3677 self,
3678 env: &ResolverEnvironment,
3679 python_requirement: &PythonRequirement,
3680 conflicts: &Conflicts,
3681 ) -> ForkedDependencies {
3682 let deps = match self {
3683 Self::Available(deps) => deps,
3684 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3685 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3686 };
3687 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3688 for dep in deps {
3689 let name = dep
3690 .package
3691 .name()
3692 .expect("dependency always has a name")
3693 .clone();
3694 name_to_deps.entry(name).or_default().push(dep);
3695 }
3696 let Forks {
3697 mut forks,
3698 diverging_packages,
3699 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3700 if forks.is_empty() {
3701 ForkedDependencies::Unforked(vec![])
3702 } else if forks.len() == 1 {
3703 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3704 } else {
3705 ForkedDependencies::Forked {
3706 forks,
3707 diverging_packages: diverging_packages.into_iter().collect(),
3708 }
3709 }
3710 }
3711}
3712
3713#[derive(Debug)]
3720enum ForkedDependencies {
3721 Unavailable(UnavailableVersion),
3723 Unforked(Vec<PubGrubDependency>),
3727 Forked {
3733 forks: Vec<Fork>,
3734 diverging_packages: Vec<PackageName>,
3736 },
3737}
3738
3739#[derive(Debug, Default)]
3744struct Forks {
3745 forks: Vec<Fork>,
3747 diverging_packages: BTreeSet<PackageName>,
3749}
3750
3751impl Forks {
3752 fn new(
3753 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3754 env: &ResolverEnvironment,
3755 python_requirement: &PythonRequirement,
3756 conflicts: &Conflicts,
3757 ) -> Self {
3758 let python_marker = python_requirement.to_marker_tree();
3759
3760 let mut forks = vec![Fork::new(env.clone())];
3761 let mut diverging_packages = BTreeSet::new();
3762 for (name, mut deps) in name_to_deps {
3763 assert!(!deps.is_empty(), "every name has at least one dependency");
3764 if let [dep] = deps.as_slice() {
3775 if marker::requires_python(dep.package.marker())
3783 .is_none_or(|bound| !python_requirement.raises(&bound))
3784 {
3785 let dep = deps.pop().unwrap();
3786 let marker = dep.package.marker();
3787 for fork in &mut forks {
3788 if fork.env.included_by_marker(marker) {
3789 fork.add_dependency(dep.clone());
3790 }
3791 }
3792 continue;
3793 }
3794 } else {
3795 if let Some(dep) = deps.first() {
3797 let marker = dep.package.marker();
3798 if deps.iter().all(|dep| marker == dep.package.marker()) {
3799 if marker::requires_python(marker)
3803 .is_none_or(|bound| !python_requirement.raises(&bound))
3804 {
3805 for dep in deps {
3806 for fork in &mut forks {
3807 if fork.env.included_by_marker(marker) {
3808 fork.add_dependency(dep.clone());
3809 }
3810 }
3811 }
3812 continue;
3813 }
3814 }
3815 }
3816 }
3817 for dep in deps {
3818 let mut forker = match ForkingPossibility::new(env, &dep) {
3819 ForkingPossibility::Possible(forker) => forker,
3820 ForkingPossibility::DependencyAlwaysExcluded => {
3821 continue;
3824 }
3825 ForkingPossibility::NoForkingPossible => {
3826 for fork in &mut forks {
3829 fork.add_dependency(dep.clone());
3830 }
3831 continue;
3832 }
3833 };
3834 diverging_packages.insert(name.clone());
3836
3837 let mut new = vec![];
3838 for fork in std::mem::take(&mut forks) {
3839 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3840 new.push(fork);
3841 continue;
3842 };
3843 forker = remaining_forker;
3844
3845 for fork_env in envs {
3846 let mut new_fork = fork.clone();
3847 new_fork.set_env(fork_env);
3848 if forker.included(&new_fork.env) {
3853 new_fork.add_dependency(dep.clone());
3854 }
3855 if new_fork.env.included_by_marker(python_marker) {
3858 new.push(new_fork);
3859 }
3860 }
3861 }
3862 forks = new;
3863 }
3864 }
3865 for set in conflicts.iter() {
3880 let mut new = vec![];
3881 for fork in std::mem::take(&mut forks) {
3882 let mut has_conflicting_dependency = false;
3890 for item in set.iter() {
3891 if fork.contains_conflicting_item(item.as_ref()) {
3892 has_conflicting_dependency = true;
3893 diverging_packages.insert(item.package().clone());
3894 break;
3895 }
3896 }
3897 if !has_conflicting_dependency {
3898 new.push(fork);
3899 continue;
3900 }
3901
3902 let non_excluded: Vec<_> = set
3908 .iter()
3909 .filter(|item| fork.env.included_by_group(item.as_ref()))
3910 .collect();
3911 if non_excluded.len() < 2 {
3912 let dominated = non_excluded.iter().all(|item| {
3917 !conflicts.iter().any(|other_set| {
3918 !std::ptr::eq(set, other_set)
3919 && other_set.contains(item.package(), item.kind().as_ref())
3920 && other_set
3921 .iter()
3922 .filter(|other_item| {
3923 other_item.package() != item.package()
3924 || other_item.kind() != item.kind()
3925 })
3926 .any(|other_item| {
3927 fork.env.included_by_group(other_item.as_ref())
3928 })
3929 })
3930 });
3931 if dominated {
3932 let rules: Vec<_> = set
3937 .iter()
3938 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3939 .cloned()
3940 .map(Err)
3941 .collect();
3942 if let Some(filtered) = fork.filter(rules) {
3943 new.push(filtered);
3944 }
3945 continue;
3946 }
3947 }
3948
3949 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3951 new.push(fork_none);
3952 }
3953
3954 for (i, _) in set.iter().enumerate() {
3962 let fork_allows_group = fork.clone().filter(
3963 set.iter()
3964 .cloned()
3965 .enumerate()
3966 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3967 );
3968 if let Some(fork_allows_group) = fork_allows_group {
3969 new.push(fork_allows_group);
3970 }
3971 }
3972 }
3973 forks = new;
3974 }
3975 Self {
3976 forks,
3977 diverging_packages,
3978 }
3979 }
3980}
3981
3982#[derive(Clone, Debug)]
3992struct Fork {
3993 dependencies: Vec<PubGrubDependency>,
4002 conflicts: crate::FxHashbrownSet<ConflictItem>,
4008 env: ResolverEnvironment,
4020}
4021
4022impl Fork {
4023 fn new(env: ResolverEnvironment) -> Self {
4026 Self {
4027 dependencies: vec![],
4028 conflicts: crate::FxHashbrownSet::default(),
4029 env,
4030 }
4031 }
4032
4033 fn add_dependency(&mut self, dep: PubGrubDependency) {
4035 if let Some(conflicting_item) = dep.conflicting_item() {
4036 self.conflicts.insert(conflicting_item.to_owned());
4037 }
4038 self.dependencies.push(dep);
4039 }
4040
4041 fn set_env(&mut self, env: ResolverEnvironment) {
4046 self.env = env;
4047 self.dependencies.retain(|dep| {
4048 let marker = dep.package.marker();
4049 if self.env.included_by_marker(marker) {
4050 return true;
4051 }
4052 if let Some(conflicting_item) = dep.conflicting_item() {
4053 self.conflicts.remove(&conflicting_item);
4054 }
4055 false
4056 });
4057 }
4058
4059 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4062 self.conflicts.contains(&item)
4063 }
4064
4065 fn filter(
4072 mut self,
4073 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4074 ) -> Option<Self> {
4075 self.env = self.env.filter_by_group(rules)?;
4076 self.dependencies.retain(|dep| {
4077 let Some(conflicting_item) = dep.conflicting_item() else {
4078 return true;
4079 };
4080 if self.env.included_by_group(conflicting_item) {
4081 return true;
4082 }
4083 match conflicting_item.kind() {
4084 ConflictKindRef::Project => {
4087 if dep.parent.is_some() {
4088 return true;
4089 }
4090 }
4091 ConflictKindRef::Group(_) => {}
4092 ConflictKindRef::Extra(_) => {}
4093 }
4094 self.conflicts.remove(&conflicting_item);
4095 false
4096 });
4097 Some(self)
4098 }
4099
4100 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4102 let self_bound = self.env.requires_python().unwrap_or_default();
4112 let other_bound = other.env.requires_python().unwrap_or_default();
4113 self_bound.lower().cmp(other_bound.lower())
4114 }
4115
4116 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4118 let self_upper_bounds = self
4123 .dependencies
4124 .iter()
4125 .filter(|dep| {
4126 dep.version
4127 .bounding_range()
4128 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4129 })
4130 .count();
4131 let other_upper_bounds = other
4132 .dependencies
4133 .iter()
4134 .filter(|dep| {
4135 dep.version
4136 .bounding_range()
4137 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4138 })
4139 .count();
4140
4141 self_upper_bounds.cmp(&other_upper_bounds)
4142 }
4143}
4144
4145impl Eq for Fork {}
4146
4147impl PartialEq for Fork {
4148 fn eq(&self, other: &Self) -> bool {
4149 self.dependencies == other.dependencies && self.env == other.env
4150 }
4151}
4152
4153#[derive(Debug, Clone)]
4154pub(crate) struct VersionFork {
4155 env: ResolverEnvironment,
4157 id: Id<PubGrubPackage>,
4159 version: Option<Version>,
4161}
4162
4163fn enrich_dependency_error(
4165 error: ResolveError,
4166 id: Id<PubGrubPackage>,
4167 version: &Version,
4168 pubgrub: &State<UvDependencyProvider>,
4169) -> ResolveError {
4170 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4171 return error;
4172 };
4173 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4174 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4175}
4176
4177fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4179 let package = &state.package_store[id];
4180 if package.is_root() {
4181 return MarkerTree::TRUE;
4182 }
4183
4184 let mut ancestors = FxHashSet::default();
4187 let mut stack = vec![id];
4188 let mut root = None;
4189 ancestors.insert(id);
4190
4191 while let Some(current) = stack.pop() {
4192 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4193 continue;
4194 };
4195
4196 for index in incompatibilities {
4197 let incompat = &state.incompatibility_store[*index];
4198 if let Kind::FromDependencyOf(parent, _, child, _) = &incompat.kind {
4199 if current != *child {
4200 continue;
4201 }
4202 if ancestors.insert(*parent) {
4203 if state.package_store[*parent].is_root() {
4204 root = Some(*parent);
4205 }
4206 stack.push(*parent);
4207 }
4208 }
4209 }
4210 }
4211
4212 let Some(root) = root else {
4213 return MarkerTree::FALSE;
4214 };
4215
4216 let mut environments = FxHashMap::default();
4219 let mut queue = VecDeque::from([root]);
4220 environments.insert(root, MarkerTree::TRUE);
4221
4222 while let Some(current) = queue.pop_front() {
4223 let Some(current_environment) = environments.get(¤t).copied() else {
4224 continue;
4225 };
4226 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4227 continue;
4228 };
4229
4230 for index in incompatibilities {
4231 let incompat = &state.incompatibility_store[*index];
4232 let Kind::FromDependencyOf(parent, _, child, _) = &incompat.kind else {
4233 continue;
4234 };
4235 if current != *parent || !ancestors.contains(child) {
4236 continue;
4237 }
4238
4239 let mut next_environment = state.package_store[*child].marker();
4240 next_environment.and(current_environment);
4241
4242 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4243 let mut combined = *entry;
4244 combined.or(next_environment);
4245 if combined != *entry {
4246 *entry = combined;
4247 queue.push_back(*child);
4248 }
4249 }
4250 }
4251
4252 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4253}
4254
4255#[derive(Debug, Default, Clone)]
4256struct ConflictTracker {
4257 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4259 prioritize: Vec<Id<PubGrubPackage>>,
4263 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4265 deprioritize: Vec<Id<PubGrubPackage>>,
4269}