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 either::Either;
13use futures::{FutureExt, StreamExt};
14use itertools::Itertools;
15use papaya::{HashMap, ResizeMode};
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: Box<HashMap<PackageName, UnavailablePackage>>,
135 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
137 options: Options,
139 reporter: Option<Arc<dyn Reporter>>,
141}
142
143impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
144 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
145{
146 pub fn new(
165 manifest: Manifest,
166 options: Options,
167 python_requirement: &'a PythonRequirement,
168 env: ResolverEnvironment,
169 current_environment: &MarkerEnvironment,
170 conflicts: Conflicts,
171 tags: Option<&'a Tags>,
172 flat_index: &'a FlatIndex,
173 index: &'a InMemoryIndex,
174 hasher: &'a HashStrategy,
175 build_context: &'a Context,
176 installed_packages: InstalledPackages,
177 database: DistributionDatabase<'a, Context>,
178 ) -> Result<Self, ResolveError> {
179 let provider = DefaultResolverProvider::new(
180 database,
181 flat_index,
182 tags,
183 python_requirement.target(),
184 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
185 hasher,
186 options.exclude_newer.clone(),
187 build_context.locations(),
188 build_context.build_options(),
189 build_context.capabilities(),
190 );
191
192 Ok(Self::new_custom_io(
193 manifest,
194 options,
195 hasher,
196 env,
197 current_environment,
198 tags.cloned(),
199 python_requirement,
200 conflicts,
201 index,
202 build_context.git(),
203 build_context.capabilities(),
204 build_context.locations(),
205 provider,
206 installed_packages,
207 ))
208 }
209}
210
211impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
212 Resolver<Provider, InstalledPackages>
213{
214 fn new_custom_io(
216 manifest: Manifest,
217 options: Options,
218 hasher: &HashStrategy,
219 env: ResolverEnvironment,
220 current_environment: &MarkerEnvironment,
221 tags: Option<Tags>,
222 python_requirement: &PythonRequirement,
223 conflicts: Conflicts,
224 index: &InMemoryIndex,
225 git: &GitResolver,
226 capabilities: &IndexCapabilities,
227 locations: &IndexLocations,
228 provider: Provider,
229 installed_packages: InstalledPackages,
230 ) -> Self {
231 let state = ResolverState {
232 index: index.clone(),
233 git: git.clone(),
234 capabilities: capabilities.clone(),
235 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
236 dependency_mode: options.dependency_mode,
237 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
238 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
239 project: manifest.project,
240 workspace_members: manifest.workspace_members,
241 requirements: manifest.requirements,
242 constraints: manifest.constraints,
243 overrides: manifest.overrides,
244 excludes: manifest.excludes,
245 preferences: manifest.preferences,
246 exclusions: manifest.exclusions,
247 hasher: hasher.clone(),
248 locations: locations.clone(),
249 env,
250 current_environment: current_environment.clone(),
251 tags,
252 python_requirement: python_requirement.clone(),
253 conflicts,
254 installed_packages,
255 unavailable_packages: Box::default(),
256 incomplete_packages: Box::default(),
257 options,
258 reporter: None,
259 };
260 Self { state, provider }
261 }
262
263 #[must_use]
265 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
266 Self {
267 state: ResolverState {
268 reporter: Some(reporter.clone()),
269 ..self.state
270 },
271 provider: self
272 .provider
273 .with_reporter(reporter.into_distribution_reporter()),
274 }
275 }
276
277 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
279 let state = Arc::new(self.state);
280 let provider = Arc::new(self.provider);
281
282 let (request_sink, request_stream) = mpsc::channel(300);
286
287 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
289
290 let solver = state.clone();
292 let (tx, rx) = oneshot::channel();
293 thread::Builder::new()
294 .name("uv-resolver".into())
295 .spawn(move || {
296 let result = solver.solve(&request_sink);
297
298 let _ = tx.send(result);
300 })
301 .unwrap();
302
303 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
304
305 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
307
308 state.on_complete();
309 resolution
310 }
311}
312
313impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
314 #[instrument(skip_all)]
315 fn solve(
316 self: Arc<Self>,
317 request_sink: &Sender<Request>,
318 ) -> Result<ResolverOutput, ResolveError> {
319 debug!(
320 "Solving with installed Python version: {}",
321 self.python_requirement.exact()
322 );
323 debug!(
324 "Solving with target Python version: {}",
325 self.python_requirement.target()
326 );
327 if !self.options.exclude_newer.is_empty() {
328 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
329 }
330
331 let mut visited = FxHashSet::default();
332
333 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
334 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
335 let prefetcher = BatchPrefetcher::new(
336 self.capabilities.clone(),
337 self.index.clone(),
338 request_sink.clone(),
339 );
340 let state = ForkState::new(
341 pubgrub,
342 self.env.clone(),
343 self.python_requirement.clone(),
344 prefetcher,
345 );
346 let mut preferences = self.preferences.clone();
347 let mut forked_states = self.env.initial_forked_states(state)?;
348 let mut resolutions = vec![];
349
350 'FORK: while let Some(mut state) = forked_states.pop() {
351 if let Some(split) = state.env.end_user_fork_display() {
352 let requires_python = state.python_requirement.target();
353 debug!("Solving {split} (requires-python: {requires_python:?})");
354 }
355 let start = Instant::now();
356 loop {
357 let highest_priority_pkg =
358 if let Some(initial) = state.initial_id.take() {
359 initial
363 } else {
364 let result = state.pubgrub.unit_propagation(state.next);
366 match result {
367 Err(err) => {
368 return Err(self.convert_no_solution_err(
370 err,
371 state.fork_urls,
372 state.fork_indexes,
373 state.env,
374 self.current_environment.clone(),
375 &visited,
376 ));
377 }
378 Ok(conflicts) => {
379 for (affected, incompatibility) in conflicts {
380 state.record_conflict(affected, None, incompatibility);
383 }
384 }
385 }
386
387 if self.dependency_mode.is_transitive() {
389 Self::pre_visit(
390 state
391 .pubgrub
392 .partial_solution
393 .prioritized_packages()
394 .map(|(id, range)| (&state.pubgrub.package_store[id], range)),
395 &self.urls,
396 &self.indexes,
397 &state.python_requirement,
398 request_sink,
399 )?;
400 }
401
402 Self::reprioritize_conflicts(&mut state);
403
404 trace!(
405 "Assigned packages: {}",
406 state
407 .pubgrub
408 .partial_solution
409 .extract_solution()
410 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
411 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
412 .join(", ")
413 );
414 let Some((highest_priority_pkg, _)) =
418 state.pubgrub.partial_solution.pick_highest_priority_pkg(
419 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
420 )
421 else {
422 if tracing::enabled!(Level::DEBUG) {
424 state.prefetcher.log_tried_versions();
425 }
426 debug!(
427 "{} resolution took {:.3}s",
428 state.env,
429 start.elapsed().as_secs_f32()
430 );
431
432 let resolution = state.into_resolution();
433
434 if matches!(
443 self.options.resolution_mode,
444 ResolutionMode::Lowest | ResolutionMode::Highest
445 ) {
446 for (package, version) in &resolution.nodes {
447 preferences.insert(
448 package.name.clone(),
449 package.index.clone(),
450 resolution
451 .env
452 .try_universal_markers()
453 .unwrap_or(UniversalMarker::TRUE),
454 version.clone(),
455 PreferenceSource::Resolver,
456 );
457 }
458 }
459
460 resolutions.push(resolution);
461 continue 'FORK;
462 };
463 trace!(
464 "Chose package for decision: {}. remaining choices: {}",
465 state.pubgrub.package_store[highest_priority_pkg],
466 state
467 .pubgrub
468 .partial_solution
469 .undecided_packages()
470 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
471 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
472 .join(", ")
473 );
474
475 highest_priority_pkg
476 };
477
478 state.next = highest_priority_pkg;
479
480 let next_id = state.next;
482 let next_package = &state.pubgrub.package_store[state.next];
483
484 let url = next_package
485 .name()
486 .and_then(|name| state.fork_urls.get(name));
487 let index = next_package
488 .name()
489 .and_then(|name| state.fork_indexes.get(name));
490
491 self.request_package(next_package, url, index, request_sink)?;
503
504 let version = if let Some(version) = state.initial_version.take() {
505 version
509 } else {
510 let term_intersection = state
511 .pubgrub
512 .partial_solution
513 .term_intersection_for_package(next_id)
514 .expect("a package was chosen but we don't have a term");
515 let decision = self.choose_version(
516 next_package,
517 next_id,
518 index.map(IndexMetadata::url),
519 term_intersection.unwrap_positive(),
520 &mut state.pins,
521 &preferences,
522 &state.fork_urls,
523 &state.env,
524 &state.python_requirement,
525 &state.pubgrub,
526 &mut visited,
527 request_sink,
528 )?;
529
530 let Some(version) = decision else {
532 debug!("No compatible version found for: {next_package}");
533
534 let term_intersection = state
535 .pubgrub
536 .partial_solution
537 .term_intersection_for_package(next_id)
538 .expect("a package was chosen but we don't have a term");
539
540 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
541 if let Some(reason) = self.unavailable_packages.pin().get(name) {
543 state
544 .pubgrub
545 .add_incompatibility(Incompatibility::custom_term(
546 next_id,
547 term_intersection.clone(),
548 UnavailableReason::Package(reason.clone()),
549 ));
550 continue;
551 }
552 }
553
554 state
555 .pubgrub
556 .add_incompatibility(Incompatibility::no_versions(
557 next_id,
558 term_intersection.clone(),
559 ));
560 continue;
561 };
562
563 let version = match version {
564 ResolverVersion::Unforked(version) => version,
565 ResolverVersion::Forked(forks) => {
566 forked_states.extend(self.version_forks_to_fork_states(state, forks));
567 continue 'FORK;
568 }
569 ResolverVersion::Unavailable(version, reason) => {
570 state.add_unavailable_version(version, reason);
571 continue;
572 }
573 };
574
575 if url.is_none() {
577 state.prefetcher.prefetch_batches(
578 next_package,
579 index,
580 &version,
581 term_intersection.unwrap_positive(),
582 state
583 .pubgrub
584 .partial_solution
585 .unchanging_term_for_package(next_id),
586 &state.python_requirement,
587 &self.selector,
588 &state.env,
589 )?;
590 }
591
592 version
593 };
594
595 state.prefetcher.version_tried(next_package, &version);
596
597 self.on_progress(next_package, &version);
598
599 if !state
600 .added_dependencies
601 .entry(next_id)
602 .or_default()
603 .insert(version.clone())
604 {
605 state
608 .pubgrub
609 .partial_solution
610 .add_decision(next_id, version);
611 continue;
612 }
613
614 let forked_deps = self.get_dependencies_forking(
616 next_id,
617 next_package,
618 &version,
619 &state.pins,
620 &state.fork_urls,
621 &state.env,
622 &state.python_requirement,
623 &state.pubgrub,
624 )?;
625
626 match forked_deps {
627 ForkedDependencies::Unavailable(reason) => {
628 state
631 .pubgrub
632 .add_incompatibility(Incompatibility::custom_version(
633 next_id,
634 version.clone(),
635 UnavailableReason::Version(reason),
636 ));
637 }
638 ForkedDependencies::Unforked(dependencies) => {
639 state
641 .visit_package_version_dependencies(
642 next_id,
643 &version,
644 &self.urls,
645 &self.indexes,
646 &dependencies,
647 &self.git,
648 &self.workspace_members,
649 self.selector.resolution_strategy(),
650 )
651 .map_err(|err| {
652 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
653 })?;
654
655 self.visit_dependencies(&dependencies, &state, request_sink)
657 .map_err(|err| {
658 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
659 })?;
660
661 state.add_package_version_dependencies(next_id, &version, dependencies);
663 }
664 ForkedDependencies::Forked {
665 mut forks,
666 diverging_packages,
667 } => {
668 debug!(
669 "Pre-fork {} took {:.3}s",
670 state.env,
671 start.elapsed().as_secs_f32()
672 );
673
674 match (self.options.fork_strategy, self.options.resolution_mode) {
676 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
677 forks.sort_by(|a, b| {
681 a.cmp_requires_python(b)
682 .reverse()
683 .then_with(|| a.cmp_upper_bounds(b))
684 });
685 }
686 (ForkStrategy::RequiresPython, _) => {
687 forks.sort_by(|a, b| {
691 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
692 });
693 }
694 }
695
696 for new_fork_state in self.forks_to_fork_states(
697 state,
698 &version,
699 forks,
700 request_sink,
701 &diverging_packages,
702 ) {
703 forked_states.push(new_fork_state?);
704 }
705 continue 'FORK;
706 }
707 }
708 }
709 }
710 if resolutions.len() > 1 {
711 info!(
712 "Solved your requirements for {} environments",
713 resolutions.len()
714 );
715 }
716 if tracing::enabled!(Level::DEBUG) {
717 for resolution in &resolutions {
718 if let Some(env) = resolution.env.end_user_fork_display() {
719 let packages: FxHashSet<_> = resolution
720 .nodes
721 .keys()
722 .map(|package| &package.name)
723 .collect();
724 debug!(
725 "Distinct solution for {env} with {} package(s)",
726 packages.len()
727 );
728 }
729 }
730 }
731 for resolution in &resolutions {
732 Self::trace_resolution(resolution);
733 }
734 ResolverOutput::from_state(
735 &resolutions,
736 &self.requirements,
737 &self.constraints,
738 &self.overrides,
739 &self.preferences,
740 &self.index,
741 &self.git,
742 &self.python_requirement,
743 &self.conflicts,
744 self.selector.resolution_strategy(),
745 self.options.clone(),
746 )
747 }
748
749 fn reprioritize_conflicts(state: &mut ForkState) {
753 for package in state.conflict_tracker.prioritize.drain(..) {
754 let changed = state
755 .priorities
756 .mark_conflict_early(&state.pubgrub.package_store[package]);
757 if changed {
758 debug!(
759 "Package {} has too many conflicts (affected), prioritizing",
760 &state.pubgrub.package_store[package]
761 );
762 } else {
763 debug!(
764 "Package {} has too many conflicts (affected), already {:?}",
765 state.pubgrub.package_store[package],
766 state.priorities.get(&state.pubgrub.package_store[package])
767 );
768 }
769 }
770
771 for package in state.conflict_tracker.deprioritize.drain(..) {
772 let changed = state
773 .priorities
774 .mark_conflict_late(&state.pubgrub.package_store[package]);
775 if changed {
776 debug!(
777 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
778 state.pubgrub.package_store[package],
779 );
780 let backtrack_level = state.pubgrub.backtrack_package(package);
781 if let Some(backtrack_level) = backtrack_level {
782 debug!("Backtracked {backtrack_level} decisions");
783 } else {
784 debug!(
785 "Package {} is not decided, cannot backtrack",
786 state.pubgrub.package_store[package]
787 );
788 }
789 } else {
790 debug!(
791 "Package {} has too many conflicts (culprit), already {:?}",
792 state.pubgrub.package_store[package],
793 state.priorities.get(&state.pubgrub.package_store[package])
794 );
795 }
796 }
797 }
798
799 fn trace_resolution(combined: &Resolution) {
805 if !tracing::enabled!(Level::TRACE) {
806 return;
807 }
808 trace!("Resolution: {:?}", combined.env);
809 for edge in &combined.edges {
810 trace!(
811 "Resolution edge: {} -> {}",
812 edge.from
813 .as_ref()
814 .map(PackageName::as_str)
815 .unwrap_or("ROOT"),
816 edge.to,
817 );
818 let mut msg = String::new();
821 write!(msg, "{}", edge.from_version).unwrap();
822 if let Some(ref extra) = edge.from_extra {
823 write!(msg, " (extra: {extra})").unwrap();
824 }
825 if let Some(ref dev) = edge.from_group {
826 write!(msg, " (group: {dev})").unwrap();
827 }
828
829 write!(msg, " -> ").unwrap();
830
831 write!(msg, "{}", edge.to_version).unwrap();
832 if let Some(ref extra) = edge.to_extra {
833 write!(msg, " (extra: {extra})").unwrap();
834 }
835 if let Some(ref dev) = edge.to_group {
836 write!(msg, " (group: {dev})").unwrap();
837 }
838 if let Some(marker) = edge.marker.contents() {
839 write!(msg, " ; {marker}").unwrap();
840 }
841 trace!("Resolution edge: {msg}");
842 }
843 }
844
845 fn forks_to_fork_states<'a>(
847 &'a self,
848 current_state: ForkState,
849 version: &'a Version,
850 forks: Vec<Fork>,
851 request_sink: &'a Sender<Request>,
852 diverging_packages: &'a [PackageName],
853 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
854 debug!(
855 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
856 current_state.pubgrub.package_store[current_state.next],
857 version,
858 diverging_packages
859 .iter()
860 .map(ToString::to_string)
861 .join(", "),
862 forks.len(),
863 if forks.len() == 1 { "" } else { "s" }
864 );
865 assert!(forks.len() >= 2);
866 let package = current_state.next;
872 let mut cur_state = Some(current_state);
873 let forks_len = forks.len();
874 forks
875 .into_iter()
876 .enumerate()
877 .map(move |(i, fork)| {
878 let is_last = i == forks_len - 1;
879 let forked_state = cur_state.take().unwrap();
880 if !is_last {
881 cur_state = Some(forked_state.clone());
882 }
883
884 let env = fork.env.clone();
885 (fork, forked_state.with_env(env))
886 })
887 .map(move |(fork, mut forked_state)| {
888 forked_state
890 .visit_package_version_dependencies(
891 package,
892 version,
893 &self.urls,
894 &self.indexes,
895 &fork.dependencies,
896 &self.git,
897 &self.workspace_members,
898 self.selector.resolution_strategy(),
899 )
900 .map_err(|err| {
901 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
902 })?;
903
904 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
906 .map_err(|err| {
907 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
908 })?;
909
910 forked_state.add_package_version_dependencies(package, version, fork.dependencies);
912
913 Ok(forked_state)
914 })
915 }
916
917 #[expect(clippy::unused_self)]
919 fn version_forks_to_fork_states(
920 &self,
921 current_state: ForkState,
922 forks: Vec<VersionFork>,
923 ) -> impl Iterator<Item = ForkState> + '_ {
924 let mut cur_state = Some(current_state);
930 let forks_len = forks.len();
931 forks.into_iter().enumerate().map(move |(i, fork)| {
932 let is_last = i == forks_len - 1;
933 let mut forked_state = cur_state.take().unwrap();
934 if !is_last {
935 cur_state = Some(forked_state.clone());
936 }
937 forked_state.initial_id = Some(fork.id);
938 forked_state.initial_version = fork.version;
939 forked_state.with_env(fork.env)
940 })
941 }
942
943 fn visit_dependencies(
945 &self,
946 dependencies: &[PubGrubDependency],
947 state: &ForkState,
948 request_sink: &Sender<Request>,
949 ) -> Result<(), ResolveError> {
950 for dependency in dependencies {
951 let PubGrubDependency {
952 package,
953 version: _,
954 parent: _,
955 source: _,
956 } = dependency;
957 let url = package.name().and_then(|name| state.fork_urls.get(name));
958 let index = package.name().and_then(|name| state.fork_indexes.get(name));
959 self.visit_package(package, url, index, request_sink)?;
960 }
961 Ok(())
962 }
963
964 fn visit_package(
967 &self,
968 package: &PubGrubPackage,
969 url: Option<&VerbatimParsedUrl>,
970 index: Option<&IndexMetadata>,
971 request_sink: &Sender<Request>,
972 ) -> Result<(), ResolveError> {
973 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
975 return Ok(());
976 }
977
978 self.request_package(package, url, index, request_sink)
979 }
980
981 fn request_package(
982 &self,
983 package: &PubGrubPackage,
984 url: Option<&VerbatimParsedUrl>,
985 index: Option<&IndexMetadata>,
986 request_sink: &Sender<Request>,
987 ) -> Result<(), ResolveError> {
988 let Some(name) = package.name_no_root() else {
990 return Ok(());
991 };
992
993 if let Some(url) = url {
994 if !self.hasher.allows_url(&url.verbatim) {
996 return Err(ResolveError::UnhashedPackage(name.clone()));
997 }
998
999 let dist = Dist::from_url(name.clone(), url.clone())?;
1001 if self.index.distributions().register(dist.distribution_id()) {
1002 request_sink.blocking_send(Request::Dist(dist))?;
1003 }
1004 } else if let Some(index) = index {
1005 if self
1007 .index
1008 .explicit()
1009 .register((name.clone(), index.url().clone()))
1010 {
1011 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1012 }
1013 } else {
1014 if self.index.implicit().register(name.clone()) {
1016 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1017 }
1018 }
1019 Ok(())
1020 }
1021
1022 fn pre_visit<'data>(
1025 packages: impl Iterator<Item = (&'data PubGrubPackage, &'data Range<Version>)>,
1026 urls: &Urls,
1027 indexes: &Indexes,
1028 python_requirement: &PythonRequirement,
1029 request_sink: &Sender<Request>,
1030 ) -> Result<(), ResolveError> {
1031 for (package, range) in packages {
1034 let PubGrubPackageInner::Package {
1035 name,
1036 extra: None,
1037 group: None,
1038 marker: MarkerTree::TRUE,
1039 } = &**package
1040 else {
1041 continue;
1042 };
1043 if urls.any_url(name) {
1046 continue;
1047 }
1048 if indexes.contains_key(name) {
1050 continue;
1051 }
1052 request_sink.blocking_send(Request::Prefetch(
1053 name.clone(),
1054 range.clone(),
1055 python_requirement.clone(),
1056 ))?;
1057 }
1058 Ok(())
1059 }
1060
1061 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1069 fn choose_version(
1070 &self,
1071 package: &PubGrubPackage,
1072 id: Id<PubGrubPackage>,
1073 index: Option<&IndexUrl>,
1074 range: &Range<Version>,
1075 pins: &mut FilePins,
1076 preferences: &Preferences,
1077 fork_urls: &ForkUrls,
1078 env: &ResolverEnvironment,
1079 python_requirement: &PythonRequirement,
1080 pubgrub: &State<UvDependencyProvider>,
1081 visited: &mut FxHashSet<PackageName>,
1082 request_sink: &Sender<Request>,
1083 ) -> Result<Option<ResolverVersion>, ResolveError> {
1084 match &**package {
1085 PubGrubPackageInner::Root(_) => {
1086 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1087 }
1088
1089 PubGrubPackageInner::Python(_) => {
1090 Ok(None)
1093 }
1094
1095 PubGrubPackageInner::System(_) => {
1096 let Some(version) = range.as_singleton() else {
1099 return Ok(None);
1100 };
1101 Ok(Some(ResolverVersion::Unforked(version.clone())))
1102 }
1103
1104 PubGrubPackageInner::Marker { name, .. }
1105 | PubGrubPackageInner::Extra { name, .. }
1106 | PubGrubPackageInner::Group { name, .. }
1107 | PubGrubPackageInner::Package { name, .. } => {
1108 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1109 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1110 } else {
1111 self.choose_version_registry(
1112 package,
1113 id,
1114 name,
1115 index,
1116 range,
1117 preferences,
1118 env,
1119 python_requirement,
1120 pubgrub,
1121 pins,
1122 visited,
1123 request_sink,
1124 )
1125 }
1126 }
1127 }
1128 }
1129
1130 fn choose_version_url(
1133 &self,
1134 id: Id<PubGrubPackage>,
1135 name: &PackageName,
1136 range: &Range<Version>,
1137 url: &VerbatimParsedUrl,
1138 env: &ResolverEnvironment,
1139 python_requirement: &PythonRequirement,
1140 pubgrub: &State<UvDependencyProvider>,
1141 ) -> Result<Option<ResolverVersion>, ResolveError> {
1142 debug!(
1143 "Searching for a compatible version of {name} @ {} ({range})",
1144 url.verbatim
1145 );
1146
1147 let dist = Dist::from_url(name.clone(), url.clone())?;
1148 let distribution_id = dist.distribution_id();
1149 let response = self
1150 .index
1151 .distributions()
1152 .wait_blocking(&distribution_id)
1153 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1154
1155 let metadata = match &*response {
1157 MetadataResponse::Found(archive) => &archive.metadata,
1158 MetadataResponse::Unavailable(reason) => {
1159 self.unavailable_packages
1160 .pin()
1161 .insert(name.clone(), reason.into());
1162 return Ok(None);
1163 }
1164 MetadataResponse::Error(dist, err) => {
1167 return Err(ResolveError::Dist(
1168 DistErrorKind::from_requested_dist(dist, &**err),
1169 dist.clone(),
1170 DerivationChain::default(),
1171 err.clone(),
1172 ));
1173 }
1174 };
1175
1176 let version = &metadata.version;
1177
1178 if !range.contains(version) {
1180 return Ok(None);
1181 }
1182
1183 if let Dist::Built(dist) = &dist {
1186 let filename = match &dist {
1187 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1188 BuiltDist::DirectUrl(dist) => &dist.filename,
1189 BuiltDist::GitPath(dist) => &dist.filename,
1190 BuiltDist::Path(dist) => &dist.filename,
1191 };
1192
1193 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1196 {
1197 let wheel_marker = implied_markers(filename);
1198 for environment_marker in self.options.artifact_environments.iter().copied() {
1201 if env.included_by_marker(environment_marker)
1203 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1204 {
1205 if wheel_marker.is_disjoint(environment_marker) {
1207 return Ok(Some(ResolverVersion::Unavailable(
1208 version.clone(),
1209 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1210 IncompatibleWheel::MissingPlatform(environment_marker),
1211 )),
1212 )));
1213 }
1214 }
1215 }
1216 }
1217
1218 if !python_requirement.target().matches_wheel_tag(filename) {
1220 return Ok(Some(ResolverVersion::Unavailable(
1221 filename.version.clone(),
1222 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1223 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1224 )),
1225 )));
1226 }
1227 }
1228
1229 if let Some(requires_python) = metadata.requires_python.as_ref() {
1231 if !python_requirement.target().is_contained_by(requires_python) {
1232 let kind = if python_requirement.installed() == python_requirement.target() {
1233 PythonRequirementKind::Installed
1234 } else {
1235 PythonRequirementKind::Target
1236 };
1237 return Ok(Some(ResolverVersion::Unavailable(
1238 version.clone(),
1239 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1240 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1241 )),
1242 )));
1243 }
1244 }
1245
1246 Ok(Some(ResolverVersion::Unforked(version.clone())))
1247 }
1248
1249 fn choose_version_registry(
1252 &self,
1253 package: &PubGrubPackage,
1254 id: Id<PubGrubPackage>,
1255 name: &PackageName,
1256 index: Option<&IndexUrl>,
1257 range: &Range<Version>,
1258 preferences: &Preferences,
1259 env: &ResolverEnvironment,
1260 python_requirement: &PythonRequirement,
1261 pubgrub: &State<UvDependencyProvider>,
1262 pins: &mut FilePins,
1263 visited: &mut FxHashSet<PackageName>,
1264 request_sink: &Sender<Request>,
1265 ) -> Result<Option<ResolverVersion>, ResolveError> {
1266 let versions_response = if let Some(index) = index {
1268 self.index
1269 .explicit()
1270 .wait_blocking(&(name.clone(), index.clone()))
1271 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1272 } else {
1273 self.index
1274 .implicit()
1275 .wait_blocking(name)
1276 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1277 };
1278 visited.insert(name.clone());
1279
1280 let version_maps = match *versions_response {
1281 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1282 VersionsResponse::NoIndex => {
1283 self.unavailable_packages
1284 .pin()
1285 .insert(name.clone(), UnavailablePackage::NoIndex);
1286 &[]
1287 }
1288 VersionsResponse::Offline => {
1289 self.unavailable_packages
1290 .pin()
1291 .insert(name.clone(), UnavailablePackage::Offline);
1292 &[]
1293 }
1294 VersionsResponse::NotFound => {
1295 self.unavailable_packages
1296 .pin()
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.pin().contains_key(name)
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 let incomplete_packages = self.incomplete_packages.pin();
1866 let versions = incomplete_packages.get_or_insert(
1867 name.clone(),
1868 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1869 );
1870 versions.pin().insert(version.clone(), reason.clone());
1871 return Ok(Dependencies::Unavailable(unavailable_version));
1872 }
1873 MetadataResponse::Error(dist, err) => {
1874 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1875 .unwrap_or_default();
1876 return Err(ResolveError::Dist(
1877 DistErrorKind::from_requested_dist(dist, &**err),
1878 dist.clone(),
1879 chain,
1880 err.clone(),
1881 ));
1882 }
1883 };
1884
1885 if let Some(requires_python) = &metadata.requires_python {
1888 if !python_requirement.target().is_contained_by(requires_python) {
1889 return Ok(Dependencies::Unavailable(
1890 UnavailableVersion::RequiresPython(requires_python.clone()),
1891 ));
1892 }
1893 }
1894
1895 let system_dependencies = self
1897 .options
1898 .torch_backend
1899 .as_ref()
1900 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1901 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1902 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1903 .map(IndexUrl::url)
1904 .and_then(SystemDependency::from_index)
1905 .into_iter()
1906 .inspect(|system_dependency| {
1907 debug!(
1908 "Adding system dependency `{}` for `{package}@{version}`",
1909 system_dependency
1910 );
1911 })
1912 .map(PubGrubDependency::from);
1913
1914 let requirements = self.flatten_requirements(
1915 &metadata.requires_dist,
1916 &metadata.dependency_groups,
1917 extra.as_ref(),
1918 group.as_ref(),
1919 Some(name),
1920 env,
1921 python_requirement,
1922 );
1923
1924 requirements
1925 .filter(|requirement| !self.excludes.contains(&requirement.name))
1926 .flat_map(|requirement| {
1927 PubGrubDependency::from_requirement(
1928 &self.conflicts,
1929 requirement,
1930 group.as_ref(),
1931 Some(package),
1932 )
1933 })
1934 .chain(system_dependencies)
1935 .collect()
1936 }
1937
1938 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1939
1940 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1941
1942 PubGrubPackageInner::Marker { name, marker } => {
1944 return Ok(Dependencies::Unforkable(
1945 [MarkerTree::TRUE, *marker]
1946 .into_iter()
1947 .map(move |marker| PubGrubDependency {
1948 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1949 name: name.clone(),
1950 extra: None,
1951 group: None,
1952 marker,
1953 }),
1954 version: Range::singleton(version.clone()),
1955 parent: None,
1956 source: DependencySource::Unspecified,
1957 })
1958 .collect(),
1959 ));
1960 }
1961
1962 PubGrubPackageInner::Extra {
1964 name,
1965 extra,
1966 marker,
1967 } => {
1968 return Ok(Dependencies::Unforkable(
1969 [MarkerTree::TRUE, *marker]
1970 .into_iter()
1971 .dedup()
1972 .flat_map(move |marker| {
1973 [None, Some(extra)]
1974 .into_iter()
1975 .map(move |extra| PubGrubDependency {
1976 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1977 name: name.clone(),
1978 extra: extra.cloned(),
1979 group: None,
1980 marker,
1981 }),
1982 version: Range::singleton(version.clone()),
1983 parent: None,
1984 source: DependencySource::Unspecified,
1985 })
1986 })
1987 .collect(),
1988 ));
1989 }
1990
1991 PubGrubPackageInner::Group {
1993 name,
1994 group,
1995 marker,
1996 } => {
1997 return Ok(Dependencies::Unforkable(
1998 [MarkerTree::TRUE, *marker]
1999 .into_iter()
2000 .dedup()
2001 .map(|marker| PubGrubDependency {
2002 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2003 name: name.clone(),
2004 extra: None,
2005 group: Some(group.clone()),
2006 marker,
2007 }),
2008 version: Range::singleton(version.clone()),
2009 parent: None,
2010 source: DependencySource::Unspecified,
2011 })
2012 .collect(),
2013 ));
2014 }
2015 };
2016 Ok(Dependencies::Available(dependencies))
2017 }
2018
2019 fn flatten_requirements<'a>(
2023 &'a self,
2024 dependencies: &'a [Requirement],
2025 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2026 extra: Option<&'a ExtraName>,
2027 dev: Option<&'a GroupName>,
2028 name: Option<&PackageName>,
2029 env: &'a ResolverEnvironment,
2030 python_requirement: &'a PythonRequirement,
2031 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2032 let python_marker = python_requirement.to_marker_tree();
2033
2034 if let Some(dev) = dev {
2035 Either::Left(Either::Left(self.requirements_for_extra(
2038 dev_dependencies.get(dev).into_iter().flatten(),
2039 extra,
2040 env,
2041 python_marker,
2042 python_requirement,
2043 )))
2044 } else if !dependencies
2045 .iter()
2046 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2047 {
2048 Either::Left(Either::Right(self.requirements_for_extra(
2050 dependencies.iter(),
2051 extra,
2052 env,
2053 python_marker,
2054 python_requirement,
2055 )))
2056 } else {
2057 let mut requirements = self
2058 .requirements_for_extra(
2059 dependencies.iter(),
2060 extra,
2061 env,
2062 python_marker,
2063 python_requirement,
2064 )
2065 .collect::<Vec<_>>();
2066
2067 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2070 let mut queue: VecDeque<_> = requirements
2071 .iter()
2072 .filter(|req| name == Some(&req.name))
2073 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2074 .collect();
2075 while let Some((extra, marker)) = queue.pop_front() {
2076 if !seen.insert((extra.clone(), marker)) {
2077 continue;
2078 }
2079 for requirement in self.requirements_for_extra(
2080 dependencies,
2081 Some(&extra),
2082 env,
2083 python_marker,
2084 python_requirement,
2085 ) {
2086 let requirement = match requirement {
2087 Cow::Owned(mut requirement) => {
2088 requirement.marker.and(marker);
2089 requirement
2090 }
2091 Cow::Borrowed(requirement) => {
2092 let mut marker = marker;
2093 marker.and(requirement.marker);
2094 Requirement {
2095 name: requirement.name.clone(),
2096 extras: requirement.extras.clone(),
2097 groups: requirement.groups.clone(),
2098 source: requirement.source.clone(),
2099 origin: requirement.origin.clone(),
2100 marker: marker.simplify_extras(slice::from_ref(&extra)),
2101 }
2102 }
2103 };
2104 if name == Some(&requirement.name) {
2105 queue.extend(
2107 requirement
2108 .extras
2109 .iter()
2110 .cloned()
2111 .map(|extra| (extra, requirement.marker)),
2112 );
2113 } else {
2114 requirements.push(Cow::Owned(requirement));
2116 }
2117 }
2118 }
2119
2120 let mut self_constraints = vec![];
2124 for req in &requirements {
2125 if name == Some(&req.name) && !req.source.is_empty() {
2126 self_constraints.push(Requirement {
2127 name: req.name.clone(),
2128 extras: Box::new([]),
2129 groups: req.groups.clone(),
2130 source: req.source.clone(),
2131 origin: req.origin.clone(),
2132 marker: req.marker,
2133 });
2134 }
2135 }
2136
2137 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2139 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2140
2141 Either::Right(requirements.into_iter())
2142 }
2143 }
2144
2145 fn requirements_for_extra<'data, 'parameters>(
2148 &'data self,
2149 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2150 extra: Option<&'parameters ExtraName>,
2151 env: &'parameters ResolverEnvironment,
2152 python_marker: MarkerTree,
2153 python_requirement: &'parameters PythonRequirement,
2154 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2155 where
2156 'data: 'parameters,
2157 {
2158 self.overrides
2159 .apply(dependencies)
2160 .filter(move |requirement| {
2161 Self::is_requirement_applicable(
2162 requirement,
2163 extra,
2164 env,
2165 python_marker,
2166 python_requirement,
2167 )
2168 })
2169 .flat_map(move |requirement| {
2170 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2171 requirement,
2172 extra,
2173 env,
2174 python_marker,
2175 python_requirement,
2176 ))
2177 })
2178 }
2179
2180 fn is_requirement_applicable(
2183 requirement: &Requirement,
2184 extra: Option<&ExtraName>,
2185 env: &ResolverEnvironment,
2186 python_marker: MarkerTree,
2187 python_requirement: &PythonRequirement,
2188 ) -> bool {
2189 match extra {
2191 Some(source_extra) => {
2192 if requirement.evaluate_markers(env.marker_environment(), &[]) {
2194 return false;
2195 }
2196 if !requirement
2197 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2198 {
2199 return false;
2200 }
2201 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2202 {
2203 return false;
2204 }
2205 }
2206 None => {
2207 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2208 return false;
2209 }
2210 }
2211 }
2212
2213 if python_marker.is_disjoint(requirement.marker) {
2216 trace!(
2217 "Skipping {requirement} because of Requires-Python: {requires_python}",
2218 requires_python = python_requirement.target(),
2219 );
2220 return false;
2221 }
2222
2223 if !env.included_by_marker(requirement.marker) {
2226 trace!("Skipping {requirement} because of {env}");
2227 return false;
2228 }
2229
2230 true
2231 }
2232
2233 fn constraints_for_requirement<'data, 'parameters>(
2236 &'data self,
2237 requirement: Cow<'data, Requirement>,
2238 extra: Option<&'parameters ExtraName>,
2239 env: &'parameters ResolverEnvironment,
2240 python_marker: MarkerTree,
2241 python_requirement: &'parameters PythonRequirement,
2242 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2243 where
2244 'data: 'parameters,
2245 {
2246 self.constraints
2247 .get(&requirement.name)
2248 .into_iter()
2249 .flatten()
2250 .filter_map(move |constraint| {
2251 let constraint = if constraint.marker.is_true() {
2254 if requirement.marker.is_true() {
2258 Cow::Borrowed(constraint)
2259 } else {
2260 let mut marker = constraint.marker;
2261 marker.and(requirement.marker);
2262
2263 if marker.is_false() {
2264 trace!(
2265 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2266 constraint.marker.try_to_string().unwrap(),
2267 requirement.marker.try_to_string().unwrap(),
2268 );
2269 return None;
2270 }
2271
2272 Cow::Owned(Requirement {
2273 name: constraint.name.clone(),
2274 extras: constraint.extras.clone(),
2275 groups: constraint.groups.clone(),
2276 source: constraint.source.clone(),
2277 origin: constraint.origin.clone(),
2278 marker,
2279 })
2280 }
2281 } else {
2282 let requires_python = python_requirement.target();
2283
2284 let mut marker = constraint.marker;
2285 marker.and(requirement.marker);
2286
2287 if marker.is_false() {
2288 trace!(
2289 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2290 constraint.marker.try_to_string().unwrap(),
2291 requirement.marker.try_to_string().unwrap(),
2292 );
2293 return None;
2294 }
2295
2296 if python_marker.is_disjoint(marker) {
2300 trace!(
2301 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2302 );
2303 return None;
2304 }
2305
2306 if marker == constraint.marker {
2307 Cow::Borrowed(constraint)
2308 } else {
2309 Cow::Owned(Requirement {
2310 name: constraint.name.clone(),
2311 extras: constraint.extras.clone(),
2312 groups: constraint.groups.clone(),
2313 source: constraint.source.clone(),
2314 origin: constraint.origin.clone(),
2315 marker,
2316 })
2317 }
2318 };
2319
2320 if !env.included_by_marker(constraint.marker) {
2323 trace!("Skipping {constraint} because of {env}");
2324 return None;
2325 }
2326
2327 match extra {
2329 Some(source_extra) => {
2330 if !constraint
2331 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2332 {
2333 return None;
2334 }
2335 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2336 {
2337 return None;
2338 }
2339 }
2340 None => {
2341 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2342 return None;
2343 }
2344 }
2345 }
2346
2347 Some(constraint)
2348 })
2349 }
2350
2351 async fn fetch<Provider: ResolverProvider>(
2353 self: Arc<Self>,
2354 provider: Arc<Provider>,
2355 request_stream: Receiver<Request>,
2356 ) -> Result<(), ResolveError> {
2357 let mut response_stream = ReceiverStream::new(request_stream)
2358 .map(|request| self.process_request(request, &*provider).boxed_local())
2359 .buffer_unordered(usize::MAX);
2363
2364 while let Some(response) = response_stream.next().await {
2365 match response? {
2366 Some(Response::Package(name, index, version_map)) => {
2367 trace!("Received package metadata for: {name}");
2368 if let Some(index) = index {
2369 self.index
2370 .explicit()
2371 .done((name, index), Arc::new(version_map));
2372 } else {
2373 self.index.implicit().done(name, Arc::new(version_map));
2374 }
2375 }
2376 Some(Response::Installed { dist, metadata }) => {
2377 trace!("Received installed distribution metadata for: {dist}");
2378 self.index
2379 .distributions()
2380 .done(dist.distribution_id(), Arc::new(metadata));
2381 }
2382 Some(Response::Dist { dist, metadata }) => {
2383 let dist_kind = match dist {
2384 Dist::Built(_) => "built",
2385 Dist::Source(_) => "source",
2386 };
2387 trace!("Received {dist_kind} distribution metadata for: {dist}");
2388 if let MetadataResponse::Unavailable(reason) = &metadata {
2389 let message = UnavailableVersion::from(reason).singular_message();
2390 if let Some(err) = reason.source() {
2391 warn!("{dist} {message}: {err}");
2393 } else {
2394 warn!("{dist} {message}");
2395 }
2396 }
2397 self.index
2398 .distributions()
2399 .done(dist.distribution_id(), Arc::new(metadata));
2400 }
2401 None => {}
2402 }
2403 }
2404
2405 Ok::<(), ResolveError>(())
2406 }
2407
2408 #[instrument(skip_all, fields(%request))]
2409 async fn process_request<Provider: ResolverProvider>(
2410 &self,
2411 request: Request,
2412 provider: &Provider,
2413 ) -> Result<Option<Response>, ResolveError> {
2414 match request {
2415 Request::Package(package_name, index) => {
2417 let package_versions = provider
2418 .get_package_versions(&package_name, index.as_ref())
2419 .boxed_local()
2420 .await
2421 .map_err(ResolveError::Client)?;
2422
2423 Ok(Some(Response::Package(
2424 package_name,
2425 index.map(IndexMetadata::into_url),
2426 package_versions,
2427 )))
2428 }
2429
2430 Request::Dist(dist) => {
2432 if let Some(version) = dist.version() {
2433 if let Some(index) = dist.index() {
2434 let versions_response = self.index.implicit().get(dist.name());
2436 if let Some(VersionsResponse::Found(version_maps)) =
2437 versions_response.as_deref()
2438 {
2439 for version_map in version_maps {
2440 if version_map.index() == Some(index) {
2441 let Some(metadata) = version_map.get_metadata(version) else {
2442 continue;
2443 };
2444 debug!("Found registry-provided metadata for: {dist}");
2445 return Ok(Some(Response::Dist {
2446 dist,
2447 metadata: MetadataResponse::Found(
2448 ArchiveMetadata::from_metadata23(metadata.clone()),
2449 ),
2450 }));
2451 }
2452 }
2453 }
2454
2455 let versions_response = self
2457 .index
2458 .explicit()
2459 .get(&(dist.name().clone(), index.clone()));
2460 if let Some(VersionsResponse::Found(version_maps)) =
2461 versions_response.as_deref()
2462 {
2463 for version_map in version_maps {
2464 let Some(metadata) = version_map.get_metadata(version) else {
2465 continue;
2466 };
2467 debug!("Found registry-provided metadata for: {dist}");
2468 return Ok(Some(Response::Dist {
2469 dist,
2470 metadata: MetadataResponse::Found(
2471 ArchiveMetadata::from_metadata23(metadata.clone()),
2472 ),
2473 }));
2474 }
2475 }
2476 }
2477 }
2478
2479 let metadata = provider
2480 .get_or_build_wheel_metadata(&dist)
2481 .boxed_local()
2482 .await?;
2483
2484 if let MetadataResponse::Found(metadata) = &metadata {
2485 if &metadata.metadata.name != dist.name() {
2486 return Err(ResolveError::MismatchedPackageName {
2487 request: "distribution metadata",
2488 expected: dist.name().clone(),
2489 actual: metadata.metadata.name.clone(),
2490 });
2491 }
2492 }
2493
2494 Ok(Some(Response::Dist { dist, metadata }))
2495 }
2496
2497 Request::Installed(dist) => {
2498 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2499
2500 if let MetadataResponse::Found(metadata) = &metadata {
2501 if &metadata.metadata.name != dist.name() {
2502 return Err(ResolveError::MismatchedPackageName {
2503 request: "installed metadata",
2504 expected: dist.name().clone(),
2505 actual: metadata.metadata.name.clone(),
2506 });
2507 }
2508 }
2509
2510 Ok(Some(Response::Installed { dist, metadata }))
2511 }
2512
2513 Request::Prefetch(package_name, range, python_requirement) => {
2515 let versions_response = self
2517 .index
2518 .implicit()
2519 .wait(&package_name)
2520 .await
2521 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2522
2523 let version_map = match *versions_response {
2524 VersionsResponse::Found(ref version_map) => version_map,
2525 VersionsResponse::NoIndex => {
2527 self.unavailable_packages
2528 .pin()
2529 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2530
2531 return Ok(None);
2532 }
2533 VersionsResponse::Offline => {
2534 self.unavailable_packages
2535 .pin()
2536 .insert(package_name.clone(), UnavailablePackage::Offline);
2537
2538 return Ok(None);
2539 }
2540 VersionsResponse::NotFound => {
2541 self.unavailable_packages
2542 .pin()
2543 .insert(package_name.clone(), UnavailablePackage::NotFound);
2544
2545 return Ok(None);
2546 }
2547 };
2548
2549 let env = ResolverEnvironment::universal(vec![]);
2552
2553 let Some(candidate) = self.selector.select(
2556 &package_name,
2557 &range,
2558 version_map,
2559 &self.preferences,
2560 &self.installed_packages,
2561 &self.exclusions,
2562 None,
2563 &env,
2564 self.tags.as_ref(),
2565 ) else {
2566 return Ok(None);
2567 };
2568
2569 let Some(dist) = candidate.compatible() else {
2571 return Ok(None);
2572 };
2573
2574 for version_map in version_map {
2576 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2577 let dist = dist.for_resolution();
2578 if version_map.index() == dist.index() {
2579 debug!("Found registry-provided metadata for: {dist}");
2580
2581 let metadata = MetadataResponse::Found(
2582 ArchiveMetadata::from_metadata23(metadata.clone()),
2583 );
2584
2585 let dist = dist.to_owned();
2586 if &package_name != dist.name() {
2587 return Err(ResolveError::MismatchedPackageName {
2588 request: "distribution",
2589 expected: package_name,
2590 actual: dist.name().clone(),
2591 });
2592 }
2593
2594 let response = match dist {
2595 ResolvedDist::Installable { dist, .. } => Response::Dist {
2596 dist: (*dist).clone(),
2597 metadata,
2598 },
2599 ResolvedDist::Installed { dist } => Response::Installed {
2600 dist: (*dist).clone(),
2601 metadata,
2602 },
2603 };
2604
2605 return Ok(Some(response));
2606 }
2607 }
2608 }
2609
2610 if dist.wheel().is_none() {
2614 if !self.selector.use_highest_version(&package_name, &env) {
2615 if let Some((lower, _)) = range.iter().next() {
2616 if lower == &Bound::Unbounded {
2617 debug!(
2618 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2619 );
2620 return Ok(None);
2621 }
2622 }
2623 }
2624 }
2625
2626 let requires_python = match dist {
2628 CompatibleDist::InstalledDist(_) => None,
2629 CompatibleDist::SourceDist { sdist, .. }
2630 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2631 sdist.file.requires_python.as_ref()
2632 }
2633 CompatibleDist::CompatibleWheel { wheel, .. } => {
2634 wheel.file.requires_python.as_ref()
2635 }
2636 };
2637 if let Some(requires_python) = requires_python.as_ref() {
2638 if !python_requirement.target().is_contained_by(requires_python) {
2639 return Ok(None);
2640 }
2641 }
2642
2643 if !self
2645 .hasher
2646 .allows_package(candidate.name(), candidate.version())
2647 {
2648 return Ok(None);
2649 }
2650
2651 let dist = dist.for_resolution();
2653 if self.index.distributions().register(dist.distribution_id()) {
2654 let dist = dist.to_owned();
2655 if &package_name != dist.name() {
2656 return Err(ResolveError::MismatchedPackageName {
2657 request: "distribution",
2658 expected: package_name,
2659 actual: dist.name().clone(),
2660 });
2661 }
2662
2663 let response = match dist {
2664 ResolvedDist::Installable { dist, .. } => {
2665 let metadata = provider
2666 .get_or_build_wheel_metadata(&dist)
2667 .boxed_local()
2668 .await?;
2669
2670 Response::Dist {
2671 dist: (*dist).clone(),
2672 metadata,
2673 }
2674 }
2675 ResolvedDist::Installed { dist } => {
2676 let metadata =
2677 provider.get_installed_metadata(&dist).boxed_local().await?;
2678
2679 Response::Installed {
2680 dist: (*dist).clone(),
2681 metadata,
2682 }
2683 }
2684 };
2685
2686 Ok(Some(response))
2687 } else {
2688 Ok(None)
2689 }
2690 }
2691 }
2692 }
2693
2694 fn convert_no_solution_err(
2695 &self,
2696 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2697 fork_urls: ForkUrls,
2698 fork_indexes: ForkIndexes,
2699 env: ResolverEnvironment,
2700 current_environment: MarkerEnvironment,
2701 visited: &FxHashSet<PackageName>,
2702 ) -> ResolveError {
2703 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2704 err,
2705 ));
2706
2707 let mut unavailable_packages = FxHashMap::default();
2708 for package in derivation_tree_packages(&err) {
2709 if let PubGrubPackageInner::Package { name, .. } = &**package {
2710 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2711 unavailable_packages.insert(name.clone(), reason.clone());
2712 }
2713 }
2714 }
2715
2716 let mut incomplete_packages = FxHashMap::default();
2717 let incomplete_packages_cache = self.incomplete_packages.pin();
2718 for package in derivation_tree_packages(&err) {
2719 if let PubGrubPackageInner::Package { name, .. } = &**package
2720 && let Some(versions) = incomplete_packages_cache.get(name)
2721 {
2722 for (version, reason) in &versions.pin() {
2723 incomplete_packages
2724 .entry(name.clone())
2725 .or_insert_with(BTreeMap::default)
2726 .insert(version.clone(), reason.clone());
2727 }
2728 }
2729 }
2730
2731 let mut available_indexes = FxHashMap::default();
2732 let mut included_versions = FxHashMap::default();
2733 let mut available_versions = FxHashMap::default();
2734
2735 let available_version_cutoff: Option<jiff::Timestamp> =
2736 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2737 .ok()
2738 .and_then(|s| s.parse().ok());
2739
2740 for package in derivation_tree_packages(&err) {
2741 let Some(name) = package.name() else { continue };
2742 if !visited.contains(name) {
2743 continue;
2748 }
2749 let versions_response = if let Some(index) = fork_indexes.get(name) {
2750 self.index
2751 .explicit()
2752 .get(&(name.clone(), index.url().clone()))
2753 } else {
2754 self.index.implicit().get(name)
2755 };
2756 if let Some(response) = versions_response {
2757 if let VersionsResponse::Found(ref version_maps) = *response {
2758 for version_map in version_maps {
2760 let package_included_versions = included_versions
2761 .entry(name.clone())
2762 .or_insert_with(BTreeSet::new);
2763 let package_available_versions = available_versions
2764 .entry(name.clone())
2765 .or_insert_with(BTreeSet::new);
2766
2767 for (version, dists) in version_map.iter(&Ranges::full()) {
2768 let excluded_from_included = || {
2773 let Some(included_version_cutoff) =
2774 version_map.included_version_cutoff()
2775 else {
2776 return false;
2777 };
2778 let Some(prioritized_dist) = dists.prioritized_dist() else {
2779 return true;
2780 };
2781 prioritized_dist.files().all(|file| {
2782 file.upload_time_utc_ms.is_none_or(|upload_time| {
2783 upload_time >= included_version_cutoff.as_millisecond()
2784 })
2785 })
2786 };
2787
2788 if !excluded_from_included() {
2789 package_included_versions.insert(version.clone());
2790 }
2791
2792 let excluded_from_available = || {
2798 let Some(ref exclude_newer) = available_version_cutoff else {
2799 return false;
2800 };
2801 let Some(prioritized_dist) = dists.prioritized_dist() else {
2802 return false;
2803 };
2804 prioritized_dist.files().all(|file| {
2805 file.upload_time_utc_ms.is_some_and(|upload_time| {
2806 upload_time >= exclude_newer.as_millisecond()
2807 })
2808 })
2809 };
2810
2811 if !excluded_from_available() {
2812 package_available_versions.insert(version.clone());
2813 }
2814 }
2815 }
2816
2817 available_indexes
2819 .entry(name.clone())
2820 .or_insert(BTreeSet::new())
2821 .extend(
2822 version_maps
2823 .iter()
2824 .filter_map(|version_map| version_map.index().cloned()),
2825 );
2826 }
2827 }
2828 }
2829
2830 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2831 err,
2832 self.index.clone(),
2833 included_versions,
2834 available_versions,
2835 available_indexes,
2836 self.selector.clone(),
2837 self.python_requirement.clone(),
2838 self.locations.clone(),
2839 self.capabilities.clone(),
2840 unavailable_packages,
2841 incomplete_packages,
2842 fork_urls,
2843 fork_indexes,
2844 env,
2845 current_environment,
2846 self.tags.clone(),
2847 self.workspace_members.clone(),
2848 self.options.clone(),
2849 )))
2850 }
2851
2852 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2853 if let Some(reporter) = self.reporter.as_ref() {
2854 match &**package {
2855 PubGrubPackageInner::Root(_) => {}
2856 PubGrubPackageInner::Python(_) => {}
2857 PubGrubPackageInner::System(_) => {}
2858 PubGrubPackageInner::Marker { .. } => {}
2859 PubGrubPackageInner::Extra { .. } => {}
2860 PubGrubPackageInner::Group { .. } => {}
2861 PubGrubPackageInner::Package { name, .. } => {
2862 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2863 }
2864 }
2865 }
2866 }
2867
2868 fn on_complete(&self) {
2869 if let Some(reporter) = self.reporter.as_ref() {
2870 reporter.on_complete();
2871 }
2872 }
2873}
2874
2875#[derive(Clone)]
2877pub(crate) struct ForkState {
2878 pubgrub: State<UvDependencyProvider>,
2886 initial_id: Option<Id<PubGrubPackage>>,
2889 initial_version: Option<Version>,
2892 next: Id<PubGrubPackage>,
2894 pins: FilePins,
2903 fork_urls: ForkUrls,
2909 fork_indexes: ForkIndexes,
2914 priorities: PubGrubPriorities,
2920 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2923 env: ResolverEnvironment,
2938 python_requirement: PythonRequirement,
2951 conflict_tracker: ConflictTracker,
2952 prefetcher: BatchPrefetcher,
2956}
2957
2958impl ForkState {
2959 fn new(
2960 pubgrub: State<UvDependencyProvider>,
2961 env: ResolverEnvironment,
2962 python_requirement: PythonRequirement,
2963 prefetcher: BatchPrefetcher,
2964 ) -> Self {
2965 Self {
2966 initial_id: None,
2967 initial_version: None,
2968 next: pubgrub.root_package,
2969 pubgrub,
2970 pins: FilePins::default(),
2971 fork_urls: ForkUrls::default(),
2972 fork_indexes: ForkIndexes::default(),
2973 priorities: PubGrubPriorities::default(),
2974 added_dependencies: FxHashMap::default(),
2975 env,
2976 python_requirement,
2977 conflict_tracker: ConflictTracker::default(),
2978 prefetcher,
2979 }
2980 }
2981
2982 fn visit_package_version_dependencies(
2985 &mut self,
2986 for_package: Id<PubGrubPackage>,
2987 for_version: &Version,
2988 urls: &Urls,
2989 indexes: &Indexes,
2990 dependencies: &[PubGrubDependency],
2991 git: &GitResolver,
2992 workspace_members: &BTreeSet<PackageName>,
2993 resolution_strategy: &ResolutionStrategy,
2994 ) -> Result<(), ResolveError> {
2995 for dependency in dependencies {
2996 let PubGrubDependency {
2997 package,
2998 version,
2999 parent: _,
3000 source,
3001 } = dependency;
3002
3003 let mut has_url = false;
3004 if let Some(name) = package.name() {
3005 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3010 self.fork_urls.insert(name, url, &self.env)?;
3011 has_url = true;
3012 }
3013
3014 if let Some(index) = source.explicit_index() {
3015 self.fork_indexes.insert(name, index, &self.env)?;
3016 }
3017
3018 for index in indexes.get(name, &self.env) {
3020 self.fork_indexes.insert(name, index, &self.env)?;
3021 }
3022 }
3023
3024 if let Some(name) = self.pubgrub.package_store[for_package]
3025 .name_no_root()
3026 .filter(|name| !workspace_members.contains(name))
3027 {
3028 debug!(
3029 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3030 );
3031 } else {
3032 debug!("Adding direct dependency: {package}{version}");
3034
3035 let missing_lower_bound = version
3037 .bounding_range()
3038 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3039 let strategy_lowest = matches!(
3040 resolution_strategy,
3041 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3042 );
3043
3044 if !has_url && missing_lower_bound && strategy_lowest {
3045 let name = package.name_no_root().unwrap();
3046 let bound_on_other_package = dependencies.iter().any(|other| {
3053 Some(name) == other.package.name()
3054 && !other
3055 .version
3056 .bounding_range()
3057 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3058 });
3059
3060 if !bound_on_other_package {
3061 warn_user_once!(
3062 "The direct dependency `{name}` is unpinned. \
3063 Consider setting a lower bound when using `--resolution lowest` \
3064 or `--resolution lowest-direct` to avoid using outdated versions.",
3065 );
3066 }
3067 }
3068 }
3069
3070 self.priorities.insert(package, version, &self.fork_urls);
3072 if let Some(base_package) = package.base_package() {
3075 self.priorities
3076 .insert(&base_package, version, &self.fork_urls);
3077 }
3078 }
3079
3080 Ok(())
3081 }
3082
3083 fn add_package_version_dependencies(
3085 &mut self,
3086 for_package: Id<PubGrubPackage>,
3087 for_version: &Version,
3088 dependencies: Vec<PubGrubDependency>,
3089 ) {
3090 for dependency in &dependencies {
3091 let PubGrubDependency {
3092 package,
3093 version,
3094 parent: _,
3095 source: _,
3096 } = dependency;
3097
3098 let Some(base_package) = package.base_package() else {
3099 continue;
3100 };
3101
3102 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3103 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3104 self.pubgrub
3105 .add_proxy_package(proxy_package, base_package_id, version.clone());
3106 }
3107
3108 let conflict = self.pubgrub.add_package_version_dependencies(
3109 self.next,
3110 for_version.clone(),
3111 dependencies.into_iter().map(|dependency| {
3112 let PubGrubDependency {
3113 package,
3114 version,
3115 parent: _,
3116 source: _,
3117 } = dependency;
3118 (package, version)
3119 }),
3120 );
3121
3122 if let Some(incompatibility) = conflict {
3125 self.record_conflict(for_package, Some(for_version), incompatibility);
3126 }
3127 }
3128
3129 fn record_conflict(
3130 &mut self,
3131 affected: Id<PubGrubPackage>,
3132 version: Option<&Version>,
3133 incompatibility: IncompId<PubGrubPackage, Ranges<Version>, UnavailableReason>,
3134 ) {
3135 let mut culprit_is_real = false;
3136 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3137 if incompatible == affected {
3138 continue;
3139 }
3140 if self.pubgrub.package_store[affected].name()
3141 == self.pubgrub.package_store[incompatible].name()
3142 {
3143 continue;
3146 }
3147 culprit_is_real = true;
3148 let culprit_count = self
3149 .conflict_tracker
3150 .culprit
3151 .entry(incompatible)
3152 .or_default();
3153 *culprit_count += 1;
3154 if *culprit_count == CONFLICT_THRESHOLD {
3155 self.conflict_tracker.deprioritize.push(incompatible);
3156 }
3157 }
3158 if culprit_is_real {
3161 if tracing::enabled!(Level::DEBUG) {
3162 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3163 .iter()
3164 .map(|(package, _term)| &self.pubgrub.package_store[package])
3165 .join(", ");
3166 if let Some(version) = version {
3167 debug!(
3168 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3169 self.pubgrub.package_store[affected], version, incompatibility
3170 );
3171 } else {
3172 debug!(
3173 "Recording unit propagation conflict of {} from incompatibility of ({})",
3174 self.pubgrub.package_store[affected], incompatibility
3175 );
3176 }
3177 }
3178
3179 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3180 *affected_count += 1;
3181 if *affected_count == CONFLICT_THRESHOLD {
3182 self.conflict_tracker.prioritize.push(self.next);
3183 }
3184 }
3185 }
3186
3187 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3188 if let UnavailableVersion::IncompatibleDist(
3192 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3193 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3194 ) = reason
3195 {
3196 let package = &self.next;
3197 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3198 PubGrubPackageInner::Python(match kind {
3199 PythonRequirementKind::Installed => PubGrubPython::Installed,
3200 PythonRequirementKind::Target => PubGrubPython::Target,
3201 }),
3202 ));
3203 self.pubgrub
3204 .add_incompatibility(Incompatibility::from_dependency(
3205 *package,
3206 Range::singleton(version.clone()),
3207 (python, release_specifiers_to_ranges(requires_python)),
3208 ));
3209 self.pubgrub
3210 .partial_solution
3211 .add_decision(self.next, version);
3212 return;
3213 }
3214 self.pubgrub
3215 .add_incompatibility(Incompatibility::custom_version(
3216 self.next,
3217 version.clone(),
3218 UnavailableReason::Version(reason),
3219 ));
3220 }
3221
3222 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3228 self.env = env;
3229 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3231 debug!("Narrowed `requires-python` bound to: {}", req.target());
3232 self.python_requirement = req;
3233 }
3234 self
3235 }
3236
3237 fn source(
3241 &self,
3242 name: &PackageName,
3243 version: &Version,
3244 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3245 let url = self.fork_urls.get(name);
3246 let index = url
3247 .is_none()
3248 .then(|| {
3249 self.pins
3250 .get(name, version)
3251 .expect("Every package should be pinned")
3252 .index()
3253 })
3254 .flatten();
3255 (url, index)
3256 }
3257
3258 fn into_resolution(self) -> Resolution {
3259 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3260 let edge_count: usize = solution
3261 .keys()
3262 .map(|package| self.pubgrub.incompatibilities[package].len())
3263 .sum();
3264 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3265 for (package, self_version) in &solution {
3266 for id in &self.pubgrub.incompatibilities[package] {
3267 let pubgrub::Kind::FromDependencyOf(
3268 self_package,
3269 ref self_range,
3270 dependency_package,
3271 ref dependency_range,
3272 ) = self.pubgrub.incompatibility_store[*id].kind
3273 else {
3274 continue;
3275 };
3276 if *package != self_package {
3277 continue;
3278 }
3279 if !self_range.contains(self_version) {
3280 continue;
3281 }
3282 let Some(dependency_version) = solution.get(&dependency_package) else {
3283 continue;
3284 };
3285 if !dependency_range.contains(dependency_version) {
3286 continue;
3287 }
3288
3289 let self_package = &self.pubgrub.package_store[self_package];
3290 let dependency_package = &self.pubgrub.package_store[dependency_package];
3291
3292 let (self_name, self_extra, self_group) = match &**self_package {
3293 PubGrubPackageInner::Package {
3294 name: self_name,
3295 extra: self_extra,
3296 group: self_group,
3297 marker: _,
3298 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3299
3300 PubGrubPackageInner::Root(_) => (None, None, None),
3301
3302 _ => continue,
3303 };
3304
3305 let (self_url, self_index) = self_name
3306 .map(|self_name| self.source(self_name, self_version))
3307 .unwrap_or((None, None));
3308
3309 match **dependency_package {
3310 PubGrubPackageInner::Package {
3311 name: ref dependency_name,
3312 extra: ref dependency_extra,
3313 group: ref dependency_dev,
3314 marker: ref dependency_marker,
3315 } => {
3316 debug_assert!(
3317 dependency_extra.is_none(),
3318 "Packages should depend on an extra proxy"
3319 );
3320 debug_assert!(
3321 dependency_dev.is_none(),
3322 "Packages should depend on a group proxy"
3323 );
3324
3325 if self_group.is_none() {
3328 if self_name == Some(dependency_name) {
3329 continue;
3330 }
3331 }
3332
3333 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3334
3335 let edge = ResolutionDependencyEdge {
3336 from: self_name.cloned(),
3337 from_version: self_version.clone(),
3338 from_url: self_url.cloned(),
3339 from_index: self_index.cloned(),
3340 from_extra: self_extra.cloned(),
3341 from_group: self_group.cloned(),
3342 to: dependency_name.clone(),
3343 to_version: dependency_version.clone(),
3344 to_url: to_url.cloned(),
3345 to_index: to_index.cloned(),
3346 to_extra: dependency_extra.clone(),
3347 to_group: dependency_dev.clone(),
3348 marker: *dependency_marker,
3349 };
3350 edges.push(edge);
3351 }
3352
3353 PubGrubPackageInner::Marker {
3354 name: ref dependency_name,
3355 marker: ref dependency_marker,
3356 } => {
3357 if self_group.is_none() {
3360 if self_name == Some(dependency_name) {
3361 continue;
3362 }
3363 }
3364
3365 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3366
3367 let edge = ResolutionDependencyEdge {
3368 from: self_name.cloned(),
3369 from_version: self_version.clone(),
3370 from_url: self_url.cloned(),
3371 from_index: self_index.cloned(),
3372 from_extra: self_extra.cloned(),
3373 from_group: self_group.cloned(),
3374 to: dependency_name.clone(),
3375 to_version: dependency_version.clone(),
3376 to_url: to_url.cloned(),
3377 to_index: to_index.cloned(),
3378 to_extra: None,
3379 to_group: None,
3380 marker: *dependency_marker,
3381 };
3382 edges.push(edge);
3383 }
3384
3385 PubGrubPackageInner::Extra {
3386 name: ref dependency_name,
3387 extra: ref dependency_extra,
3388 marker: ref dependency_marker,
3389 } => {
3390 if self_group.is_none() {
3391 debug_assert!(
3392 self_name != Some(dependency_name),
3393 "Extras should be flattened"
3394 );
3395 }
3396 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3397
3398 let edge = ResolutionDependencyEdge {
3400 from: self_name.cloned(),
3401 from_version: self_version.clone(),
3402 from_url: self_url.cloned(),
3403 from_index: self_index.cloned(),
3404 from_extra: self_extra.cloned(),
3405 from_group: self_group.cloned(),
3406 to: dependency_name.clone(),
3407 to_version: dependency_version.clone(),
3408 to_url: to_url.cloned(),
3409 to_index: to_index.cloned(),
3410 to_extra: Some(dependency_extra.clone()),
3411 to_group: None,
3412 marker: *dependency_marker,
3413 };
3414 edges.push(edge);
3415
3416 let edge = ResolutionDependencyEdge {
3418 from: self_name.cloned(),
3419 from_version: self_version.clone(),
3420 from_url: self_url.cloned(),
3421 from_index: self_index.cloned(),
3422 from_extra: self_extra.cloned(),
3423 from_group: self_group.cloned(),
3424 to: dependency_name.clone(),
3425 to_version: dependency_version.clone(),
3426 to_url: to_url.cloned(),
3427 to_index: to_index.cloned(),
3428 to_extra: None,
3429 to_group: None,
3430 marker: *dependency_marker,
3431 };
3432 edges.push(edge);
3433 }
3434
3435 PubGrubPackageInner::Group {
3436 name: ref dependency_name,
3437 group: ref dependency_group,
3438 marker: ref dependency_marker,
3439 } => {
3440 debug_assert!(
3441 self_name != Some(dependency_name),
3442 "Groups should be flattened"
3443 );
3444
3445 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3446
3447 let edge = ResolutionDependencyEdge {
3450 from: self_name.cloned(),
3451 from_version: self_version.clone(),
3452 from_url: self_url.cloned(),
3453 from_index: self_index.cloned(),
3454 from_extra: self_extra.cloned(),
3455 from_group: self_group.cloned(),
3456 to: dependency_name.clone(),
3457 to_version: dependency_version.clone(),
3458 to_url: to_url.cloned(),
3459 to_index: to_index.cloned(),
3460 to_extra: None,
3461 to_group: Some(dependency_group.clone()),
3462 marker: *dependency_marker,
3463 };
3464 edges.push(edge);
3465 }
3466
3467 _ => {}
3468 }
3469 }
3470 }
3471
3472 let nodes = solution
3473 .into_iter()
3474 .filter_map(|(package, version)| {
3475 if let PubGrubPackageInner::Package {
3476 name,
3477 extra,
3478 group,
3479 marker: MarkerTree::TRUE,
3480 } = &*self.pubgrub.package_store[package]
3481 {
3482 let (url, index) = self.source(name, &version);
3483 Some((
3484 ResolutionPackage {
3485 name: name.clone(),
3486 extra: extra.clone(),
3487 dev: group.clone(),
3488 url: url.cloned(),
3489 index: index.cloned(),
3490 },
3491 version,
3492 ))
3493 } else {
3494 None
3495 }
3496 })
3497 .collect();
3498
3499 Resolution {
3500 nodes,
3501 edges,
3502 pins: self.pins,
3503 env: self.env,
3504 }
3505 }
3506}
3507
3508#[derive(Debug)]
3510pub(crate) struct Resolution {
3511 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3512 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3515 pub(crate) pins: FilePins,
3517 pub(crate) env: ResolverEnvironment,
3519}
3520
3521#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3524pub(crate) struct ResolutionPackage {
3525 pub(crate) name: PackageName,
3526 pub(crate) extra: Option<ExtraName>,
3527 pub(crate) dev: Option<GroupName>,
3528 pub(crate) url: Option<VerbatimParsedUrl>,
3530 pub(crate) index: Option<IndexUrl>,
3532}
3533
3534#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3537pub(crate) struct ResolutionDependencyEdge {
3538 pub(crate) from: Option<PackageName>,
3540 pub(crate) from_version: Version,
3541 pub(crate) from_url: Option<VerbatimParsedUrl>,
3542 pub(crate) from_index: Option<IndexUrl>,
3543 pub(crate) from_extra: Option<ExtraName>,
3544 pub(crate) from_group: Option<GroupName>,
3545 pub(crate) to: PackageName,
3546 pub(crate) to_version: Version,
3547 pub(crate) to_url: Option<VerbatimParsedUrl>,
3548 pub(crate) to_index: Option<IndexUrl>,
3549 pub(crate) to_extra: Option<ExtraName>,
3550 pub(crate) to_group: Option<GroupName>,
3551 pub(crate) marker: MarkerTree,
3552}
3553
3554impl ResolutionDependencyEdge {
3555 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3556 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3560 }
3561}
3562
3563#[derive(Debug)]
3565#[expect(clippy::large_enum_variant)]
3566pub(crate) enum Request {
3567 Package(PackageName, Option<IndexMetadata>),
3569 Dist(Dist),
3571 Installed(InstalledDist),
3573 Prefetch(PackageName, Range<Version>, PythonRequirement),
3575}
3576
3577impl<'a> From<ResolvedDistRef<'a>> for Request {
3578 fn from(dist: ResolvedDistRef<'a>) -> Self {
3579 match dist {
3585 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3586 let source = prioritized.source_dist().expect("a source distribution");
3589 assert_eq!(
3590 (&sdist.name, &sdist.version),
3591 (&source.name, &source.version),
3592 "expected chosen sdist to match prioritized sdist"
3593 );
3594 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3595 }
3596 ResolvedDistRef::InstallableRegistryBuiltDist {
3597 wheel, prioritized, ..
3598 } => {
3599 assert_eq!(
3600 Some(&wheel.filename),
3601 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3602 "expected chosen wheel to match best wheel"
3603 );
3604 let built = prioritized.built_dist().expect("at least one wheel");
3607 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3608 }
3609 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3610 }
3611 }
3612}
3613
3614impl Display for Request {
3615 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3616 match self {
3617 Self::Package(package_name, _) => {
3618 write!(f, "Versions {package_name}")
3619 }
3620 Self::Dist(dist) => {
3621 write!(f, "Metadata {dist}")
3622 }
3623 Self::Installed(dist) => {
3624 write!(f, "Installed metadata {dist}")
3625 }
3626 Self::Prefetch(package_name, range, _) => {
3627 write!(f, "Prefetch {package_name} {range}")
3628 }
3629 }
3630 }
3631}
3632
3633#[derive(Debug)]
3634#[expect(clippy::large_enum_variant)]
3635enum Response {
3636 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3638 Dist {
3640 dist: Dist,
3641 metadata: MetadataResponse,
3642 },
3643 Installed {
3645 dist: InstalledDist,
3646 metadata: MetadataResponse,
3647 },
3648}
3649
3650enum Dependencies {
3656 Unavailable(UnavailableVersion),
3658 Available(Vec<PubGrubDependency>),
3664 Unforkable(Vec<PubGrubDependency>),
3670}
3671
3672impl Dependencies {
3673 fn fork(
3680 self,
3681 env: &ResolverEnvironment,
3682 python_requirement: &PythonRequirement,
3683 conflicts: &Conflicts,
3684 ) -> ForkedDependencies {
3685 let deps = match self {
3686 Self::Available(deps) => deps,
3687 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3688 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3689 };
3690 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3691 for dep in deps {
3692 let name = dep
3693 .package
3694 .name()
3695 .expect("dependency always has a name")
3696 .clone();
3697 name_to_deps.entry(name).or_default().push(dep);
3698 }
3699 let Forks {
3700 mut forks,
3701 diverging_packages,
3702 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3703 if forks.is_empty() {
3704 ForkedDependencies::Unforked(vec![])
3705 } else if forks.len() == 1 {
3706 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3707 } else {
3708 ForkedDependencies::Forked {
3709 forks,
3710 diverging_packages: diverging_packages.into_iter().collect(),
3711 }
3712 }
3713 }
3714}
3715
3716#[derive(Debug)]
3723enum ForkedDependencies {
3724 Unavailable(UnavailableVersion),
3726 Unforked(Vec<PubGrubDependency>),
3730 Forked {
3736 forks: Vec<Fork>,
3737 diverging_packages: Vec<PackageName>,
3739 },
3740}
3741
3742#[derive(Debug, Default)]
3747struct Forks {
3748 forks: Vec<Fork>,
3750 diverging_packages: BTreeSet<PackageName>,
3752}
3753
3754impl Forks {
3755 fn new(
3756 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3757 env: &ResolverEnvironment,
3758 python_requirement: &PythonRequirement,
3759 conflicts: &Conflicts,
3760 ) -> Self {
3761 let python_marker = python_requirement.to_marker_tree();
3762
3763 let mut forks = vec![Fork::new(env.clone())];
3764 let mut diverging_packages = BTreeSet::new();
3765 for (name, mut deps) in name_to_deps {
3766 assert!(!deps.is_empty(), "every name has at least one dependency");
3767 if let [dep] = deps.as_slice() {
3778 if marker::requires_python(dep.package.marker())
3786 .is_none_or(|bound| !python_requirement.raises(&bound))
3787 {
3788 let dep = deps.pop().unwrap();
3789 let marker = dep.package.marker();
3790 for fork in &mut forks {
3791 if fork.env.included_by_marker(marker) {
3792 fork.add_dependency(dep.clone());
3793 }
3794 }
3795 continue;
3796 }
3797 } else {
3798 if let Some(dep) = deps.first() {
3800 let marker = dep.package.marker();
3801 if deps.iter().all(|dep| marker == dep.package.marker()) {
3802 if marker::requires_python(marker)
3806 .is_none_or(|bound| !python_requirement.raises(&bound))
3807 {
3808 for dep in deps {
3809 for fork in &mut forks {
3810 if fork.env.included_by_marker(marker) {
3811 fork.add_dependency(dep.clone());
3812 }
3813 }
3814 }
3815 continue;
3816 }
3817 }
3818 }
3819 }
3820 for dep in deps {
3821 let mut forker = match ForkingPossibility::new(env, &dep) {
3822 ForkingPossibility::Possible(forker) => forker,
3823 ForkingPossibility::DependencyAlwaysExcluded => {
3824 continue;
3827 }
3828 ForkingPossibility::NoForkingPossible => {
3829 for fork in &mut forks {
3832 fork.add_dependency(dep.clone());
3833 }
3834 continue;
3835 }
3836 };
3837 diverging_packages.insert(name.clone());
3839
3840 let mut new = vec![];
3841 for fork in std::mem::take(&mut forks) {
3842 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3843 new.push(fork);
3844 continue;
3845 };
3846 forker = remaining_forker;
3847
3848 for fork_env in envs {
3849 let mut new_fork = fork.clone();
3850 new_fork.set_env(fork_env);
3851 if forker.included(&new_fork.env) {
3856 new_fork.add_dependency(dep.clone());
3857 }
3858 if new_fork.env.included_by_marker(python_marker) {
3861 new.push(new_fork);
3862 }
3863 }
3864 }
3865 forks = new;
3866 }
3867 }
3868 for set in conflicts.iter() {
3883 let mut new = vec![];
3884 for fork in std::mem::take(&mut forks) {
3885 let mut has_conflicting_dependency = false;
3893 for item in set.iter() {
3894 if fork.contains_conflicting_item(item.as_ref()) {
3895 has_conflicting_dependency = true;
3896 diverging_packages.insert(item.package().clone());
3897 break;
3898 }
3899 }
3900 if !has_conflicting_dependency {
3901 new.push(fork);
3902 continue;
3903 }
3904
3905 let non_excluded: Vec<_> = set
3911 .iter()
3912 .filter(|item| fork.env.included_by_group(item.as_ref()))
3913 .collect();
3914 if non_excluded.len() < 2 {
3915 let dominated = non_excluded.iter().all(|item| {
3920 !conflicts.iter().any(|other_set| {
3921 !std::ptr::eq(set, other_set)
3922 && other_set.contains(item.package(), item.kind().as_ref())
3923 && other_set
3924 .iter()
3925 .filter(|other_item| {
3926 other_item.package() != item.package()
3927 || other_item.kind() != item.kind()
3928 })
3929 .any(|other_item| {
3930 fork.env.included_by_group(other_item.as_ref())
3931 })
3932 })
3933 });
3934 if dominated {
3935 let rules: Vec<_> = set
3940 .iter()
3941 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3942 .cloned()
3943 .map(Err)
3944 .collect();
3945 if let Some(filtered) = fork.filter(rules) {
3946 new.push(filtered);
3947 }
3948 continue;
3949 }
3950 }
3951
3952 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3954 new.push(fork_none);
3955 }
3956
3957 for (i, _) in set.iter().enumerate() {
3965 let fork_allows_group = fork.clone().filter(
3966 set.iter()
3967 .cloned()
3968 .enumerate()
3969 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3970 );
3971 if let Some(fork_allows_group) = fork_allows_group {
3972 new.push(fork_allows_group);
3973 }
3974 }
3975 }
3976 forks = new;
3977 }
3978 Self {
3979 forks,
3980 diverging_packages,
3981 }
3982 }
3983}
3984
3985#[derive(Clone, Debug)]
3995struct Fork {
3996 dependencies: Vec<PubGrubDependency>,
4005 conflicts: crate::FxHashbrownSet<ConflictItem>,
4011 env: ResolverEnvironment,
4023}
4024
4025impl Fork {
4026 fn new(env: ResolverEnvironment) -> Self {
4029 Self {
4030 dependencies: vec![],
4031 conflicts: crate::FxHashbrownSet::default(),
4032 env,
4033 }
4034 }
4035
4036 fn add_dependency(&mut self, dep: PubGrubDependency) {
4038 if let Some(conflicting_item) = dep.conflicting_item() {
4039 self.conflicts.insert(conflicting_item.to_owned());
4040 }
4041 self.dependencies.push(dep);
4042 }
4043
4044 fn set_env(&mut self, env: ResolverEnvironment) {
4049 self.env = env;
4050 self.dependencies.retain(|dep| {
4051 let marker = dep.package.marker();
4052 if self.env.included_by_marker(marker) {
4053 return true;
4054 }
4055 if let Some(conflicting_item) = dep.conflicting_item() {
4056 self.conflicts.remove(&conflicting_item);
4057 }
4058 false
4059 });
4060 }
4061
4062 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4065 self.conflicts.contains(&item)
4066 }
4067
4068 fn filter(
4075 mut self,
4076 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4077 ) -> Option<Self> {
4078 self.env = self.env.filter_by_group(rules)?;
4079 self.dependencies.retain(|dep| {
4080 let Some(conflicting_item) = dep.conflicting_item() else {
4081 return true;
4082 };
4083 if self.env.included_by_group(conflicting_item) {
4084 return true;
4085 }
4086 match conflicting_item.kind() {
4087 ConflictKindRef::Project => {
4090 if dep.parent.is_some() {
4091 return true;
4092 }
4093 }
4094 ConflictKindRef::Group(_) => {}
4095 ConflictKindRef::Extra(_) => {}
4096 }
4097 self.conflicts.remove(&conflicting_item);
4098 false
4099 });
4100 Some(self)
4101 }
4102
4103 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4105 let self_bound = self.env.requires_python().unwrap_or_default();
4115 let other_bound = other.env.requires_python().unwrap_or_default();
4116 self_bound.lower().cmp(other_bound.lower())
4117 }
4118
4119 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4121 let self_upper_bounds = self
4126 .dependencies
4127 .iter()
4128 .filter(|dep| {
4129 dep.version
4130 .bounding_range()
4131 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4132 })
4133 .count();
4134 let other_upper_bounds = other
4135 .dependencies
4136 .iter()
4137 .filter(|dep| {
4138 dep.version
4139 .bounding_range()
4140 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4141 })
4142 .count();
4143
4144 self_upper_bounds.cmp(&other_upper_bounds)
4145 }
4146}
4147
4148impl Eq for Fork {}
4149
4150impl PartialEq for Fork {
4151 fn eq(&self, other: &Self) -> bool {
4152 self.dependencies == other.dependencies && self.env == other.env
4153 }
4154}
4155
4156#[derive(Debug, Clone)]
4157pub(crate) struct VersionFork {
4158 env: ResolverEnvironment,
4160 id: Id<PubGrubPackage>,
4162 version: Option<Version>,
4164}
4165
4166fn enrich_dependency_error(
4168 error: ResolveError,
4169 id: Id<PubGrubPackage>,
4170 version: &Version,
4171 pubgrub: &State<UvDependencyProvider>,
4172) -> ResolveError {
4173 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4174 return error;
4175 };
4176 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4177 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4178}
4179
4180fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4182 let package = &state.package_store[id];
4183 if package.is_root() {
4184 return MarkerTree::TRUE;
4185 }
4186
4187 let mut ancestors = FxHashSet::default();
4190 let mut stack = vec![id];
4191 let mut root = None;
4192 ancestors.insert(id);
4193
4194 while let Some(current) = stack.pop() {
4195 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4196 continue;
4197 };
4198
4199 for index in incompatibilities {
4200 let incompat = &state.incompatibility_store[*index];
4201 if let Kind::FromDependencyOf(parent, _, child, _) = &incompat.kind {
4202 if current != *child {
4203 continue;
4204 }
4205 if ancestors.insert(*parent) {
4206 if state.package_store[*parent].is_root() {
4207 root = Some(*parent);
4208 }
4209 stack.push(*parent);
4210 }
4211 }
4212 }
4213 }
4214
4215 let Some(root) = root else {
4216 return MarkerTree::FALSE;
4217 };
4218
4219 let mut environments = FxHashMap::default();
4222 let mut queue = VecDeque::from([root]);
4223 environments.insert(root, MarkerTree::TRUE);
4224
4225 while let Some(current) = queue.pop_front() {
4226 let Some(current_environment) = environments.get(¤t).copied() else {
4227 continue;
4228 };
4229 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4230 continue;
4231 };
4232
4233 for index in incompatibilities {
4234 let incompat = &state.incompatibility_store[*index];
4235 let Kind::FromDependencyOf(parent, _, child, _) = &incompat.kind else {
4236 continue;
4237 };
4238 if current != *parent || !ancestors.contains(child) {
4239 continue;
4240 }
4241
4242 let mut next_environment = state.package_store[*child].marker();
4243 next_environment.and(current_environment);
4244
4245 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4246 let mut combined = *entry;
4247 combined.or(next_environment);
4248 if combined != *entry {
4249 *entry = combined;
4250 queue.push_back(*child);
4251 }
4252 }
4253 }
4254
4255 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4256}
4257
4258#[derive(Debug, Default, Clone)]
4259struct ConflictTracker {
4260 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4262 prioritize: Vec<Id<PubGrubPackage>>,
4266 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4268 deprioritize: Vec<Id<PubGrubPackage>>,
4272}