1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{iter, slice, thread};
11
12use either::Either;
13use futures::{FutureExt, StreamExt};
14use itertools::Itertools;
15use papaya::{HashMap, ResizeMode};
16use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State};
17use rustc_hash::{FxHashMap, FxHashSet};
18use tokio::sync::mpsc::{self, Receiver, Sender};
19use tokio::sync::oneshot;
20use tokio_stream::wrappers::ReceiverStream;
21use tracing::{Level, debug, info, instrument, trace, warn};
22
23use uv_configuration::{Constraints, Excludes, Overrides};
24use uv_distribution::{ArchiveMetadata, DistributionDatabase};
25use uv_distribution_types::{
26 BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
27 IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
28 IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist,
29 ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::{ExtraName, GroupName, PackageName};
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_strategy::ForkStrategy;
49use crate::fork_urls::ForkUrls;
50use crate::manifest::Manifest;
51use crate::pins::FilePins;
52use crate::preferences::{PreferenceSource, Preferences};
53use crate::pubgrub::{
54 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
55 PubGrubPython, Range,
56};
57use crate::python_requirement::PythonRequirement;
58use crate::resolution::ResolverOutput;
59use crate::resolution_mode::ResolutionStrategy;
60pub(crate) use crate::resolver::availability::{
61 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
62 UnavailableVersion, UnsatisfiableRequirement,
63};
64use crate::resolver::batch_prefetch::BatchPrefetcher;
65use crate::resolver::derivation::DerivationChainBuilder;
66pub use crate::resolver::environment::ResolverEnvironment;
67use crate::resolver::environment::{
68 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
69};
70pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
71pub use crate::resolver::index::InMemoryIndex;
72use crate::resolver::indexes::Indexes;
73pub use crate::resolver::provider::{
74 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
75 VersionsResponse, WheelMetadataResult,
76};
77pub use crate::resolver::reporter::Reporter;
78use crate::resolver::system::SystemDependency;
79pub(crate) use crate::resolver::urls::Urls;
80use crate::universal_marker::UniversalMarker;
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84pub(crate) use resolution::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
85
86mod availability;
87mod batch_prefetch;
88mod derivation;
89mod environment;
90mod fork_map;
91mod index;
92mod indexes;
93mod provider;
94mod reporter;
95mod resolution;
96mod system;
97mod urls;
98
99const CONFLICT_THRESHOLD: usize = 5;
101
102pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
103 state: ResolverState<InstalledPackages>,
104 provider: Provider,
105}
106
107struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
110 project: Option<PackageName>,
111 requirements: Vec<Requirement>,
112 constraints: Constraints,
113 overrides: Overrides,
114 excludes: Excludes,
115 preferences: Preferences,
116 git: GitResolver,
117 capabilities: IndexCapabilities,
118 locations: IndexLocations,
119 exclusions: Exclusions,
120 urls: Urls,
121 indexes: Indexes,
122 dependency_mode: DependencyMode,
123 hasher: HashStrategy,
124 env: ResolverEnvironment,
125 current_environment: MarkerEnvironment,
127 tags: Option<Tags>,
128 python_requirement: PythonRequirement,
129 conflicts: Conflicts,
130 workspace_members: BTreeSet<PackageName>,
131 selector: CandidateSelector,
132 index: InMemoryIndex,
133 installed_packages: InstalledPackages,
134 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
137 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
139 options: Options,
141 reporter: Option<Arc<dyn Reporter>>,
143}
144
145impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
146 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
147{
148 pub fn new(
167 manifest: Manifest,
168 options: Options,
169 python_requirement: &'a PythonRequirement,
170 env: ResolverEnvironment,
171 current_environment: &MarkerEnvironment,
172 conflicts: Conflicts,
173 tags: Option<&'a Tags>,
174 flat_index: &'a FlatIndex,
175 index: &'a InMemoryIndex,
176 hasher: &'a HashStrategy,
177 build_context: &'a Context,
178 installed_packages: InstalledPackages,
179 database: DistributionDatabase<'a, Context>,
180 ) -> Result<Self, ResolveError> {
181 let provider = DefaultResolverProvider::new(
182 database,
183 flat_index,
184 tags,
185 python_requirement.target(),
186 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
187 hasher,
188 options.exclude_newer.clone(),
189 build_context.locations(),
190 build_context.build_options(),
191 build_context.capabilities(),
192 );
193
194 Ok(Self::new_custom_io(
195 manifest,
196 options,
197 hasher,
198 env,
199 current_environment,
200 tags.cloned(),
201 python_requirement,
202 conflicts,
203 index,
204 build_context.git(),
205 build_context.capabilities(),
206 build_context.locations(),
207 provider,
208 installed_packages,
209 ))
210 }
211}
212
213impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
214 Resolver<Provider, InstalledPackages>
215{
216 pub fn new_custom_io(
218 manifest: Manifest,
219 options: Options,
220 hasher: &HashStrategy,
221 env: ResolverEnvironment,
222 current_environment: &MarkerEnvironment,
223 tags: Option<Tags>,
224 python_requirement: &PythonRequirement,
225 conflicts: Conflicts,
226 index: &InMemoryIndex,
227 git: &GitResolver,
228 capabilities: &IndexCapabilities,
229 locations: &IndexLocations,
230 provider: Provider,
231 installed_packages: InstalledPackages,
232 ) -> Self {
233 let state = ResolverState {
234 index: index.clone(),
235 git: git.clone(),
236 capabilities: capabilities.clone(),
237 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
238 dependency_mode: options.dependency_mode,
239 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
240 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
241 project: manifest.project,
242 workspace_members: manifest.workspace_members,
243 requirements: manifest.requirements,
244 constraints: manifest.constraints,
245 overrides: manifest.overrides,
246 excludes: manifest.excludes,
247 preferences: manifest.preferences,
248 exclusions: manifest.exclusions,
249 hasher: hasher.clone(),
250 locations: locations.clone(),
251 env,
252 current_environment: current_environment.clone(),
253 tags,
254 python_requirement: python_requirement.clone(),
255 conflicts,
256 installed_packages,
257 unavailable_packages: Box::default(),
258 incomplete_packages: Box::default(),
259 options,
260 reporter: None,
261 };
262 Self { state, provider }
263 }
264
265 #[must_use]
267 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
268 Self {
269 state: ResolverState {
270 reporter: Some(reporter.clone()),
271 ..self.state
272 },
273 provider: self
274 .provider
275 .with_reporter(reporter.into_distribution_reporter()),
276 }
277 }
278
279 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
281 let state = Arc::new(self.state);
282 let provider = Arc::new(self.provider);
283
284 let (request_sink, request_stream) = mpsc::channel(300);
288
289 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
291
292 let solver = state.clone();
294 let (tx, rx) = oneshot::channel();
295 thread::Builder::new()
296 .name("uv-resolver".into())
297 .spawn(move || {
298 let result = solver.solve(&request_sink);
299
300 let _ = tx.send(result);
302 })
303 .unwrap();
304
305 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
306
307 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
309
310 state.on_complete();
311 resolution
312 }
313}
314
315impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
316 #[instrument(skip_all)]
317 fn solve(
318 self: Arc<Self>,
319 request_sink: &Sender<Request>,
320 ) -> Result<ResolverOutput, ResolveError> {
321 debug!(
322 "Solving with installed Python version: {}",
323 self.python_requirement.exact()
324 );
325 debug!(
326 "Solving with target Python version: {}",
327 self.python_requirement.target()
328 );
329 if !self.options.exclude_newer.is_empty() {
330 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
331 }
332
333 let mut visited = FxHashSet::default();
334
335 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
336 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
337 let prefetcher = BatchPrefetcher::new(
338 self.capabilities.clone(),
339 self.index.clone(),
340 request_sink.clone(),
341 );
342 let state = ForkState::new(
343 pubgrub,
344 self.env.clone(),
345 self.python_requirement.clone(),
346 prefetcher,
347 );
348 let mut preferences = self.preferences.clone();
349 let mut forked_states = self.env.initial_forked_states(state)?;
350 let mut resolutions = vec![];
351
352 'FORK: while let Some(mut state) = forked_states.pop() {
353 if let Some(split) = state.env.end_user_fork_display() {
354 let requires_python = state.python_requirement.target();
355 debug!("Solving {split} (requires-python: {requires_python:?})");
356 }
357 let start = Instant::now();
358 loop {
359 let highest_priority_pkg =
360 if let Some(initial) = state.initial_id.take() {
361 initial
365 } else {
366 let result = state.pubgrub.unit_propagation(state.next);
368 match result {
369 Err(err) => {
370 return Err(self.convert_no_solution_err(
372 err,
373 state.fork_urls,
374 state.fork_indexes,
375 &state.known_versions,
376 state.env,
377 self.current_environment.clone(),
378 &visited,
379 ));
380 }
381 Ok(conflicts) => {
382 for (affected, incompatibility) in conflicts {
383 state.record_conflict(affected, None, incompatibility);
386 }
387 }
388 }
389
390 if self.dependency_mode.is_transitive() {
392 Self::pre_visit(
393 state.pubgrub.partial_solution.prioritized_packages().map(
394 |(id, range)| (id, &state.pubgrub.package_store[id], range),
395 ),
396 &mut state.pre_visited,
397 &self.urls,
398 &self.indexes,
399 &state.python_requirement,
400 request_sink,
401 )?;
402 }
403
404 state.reprioritize_conflicts();
405
406 trace!(
407 "Assigned packages: {}",
408 state
409 .pubgrub
410 .partial_solution
411 .extract_solution()
412 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
413 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
414 .join(", ")
415 );
416 let Some((highest_priority_pkg, _)) =
420 state.pubgrub.partial_solution.pick_highest_priority_pkg(
421 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
422 )
423 else {
424 if tracing::enabled!(Level::DEBUG) {
426 state.prefetcher.log_tried_versions();
427 }
428 debug!(
429 "{} resolution took {:.3}s",
430 state.env,
431 start.elapsed().as_secs_f32()
432 );
433
434 let resolution = state.into_resolution();
435
436 if matches!(
445 self.options.resolution_mode,
446 ResolutionMode::Lowest | ResolutionMode::Highest
447 ) {
448 let marker = resolution
449 .env
450 .try_universal_markers()
451 .unwrap_or(UniversalMarker::TRUE);
452 for (package, version) in &resolution.nodes {
453 preferences.insert(
454 package.name.clone(),
455 package.index.clone(),
456 marker,
457 version.clone(),
458 PreferenceSource::Resolver,
459 );
460 }
461 }
462
463 resolutions.push(resolution);
464 continue 'FORK;
465 };
466 trace!(
467 "Chose package for decision: {}. remaining choices: {}",
468 state.pubgrub.package_store[highest_priority_pkg],
469 state
470 .pubgrub
471 .partial_solution
472 .undecided_packages()
473 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
474 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
475 .join(", ")
476 );
477
478 highest_priority_pkg
479 };
480
481 state.next = highest_priority_pkg;
482
483 let next_id = state.next;
485 let next_package = &state.pubgrub.package_store[state.next];
486
487 let url = next_package
488 .name()
489 .and_then(|name| state.fork_urls.get(name));
490 let index = next_package
491 .name()
492 .and_then(|name| state.fork_indexes.get(name));
493
494 self.request_package(next_package, url, index, request_sink)?;
506
507 let version = if let Some(version) = state.initial_version.take() {
508 version
512 } else {
513 let term_intersection = state
514 .pubgrub
515 .partial_solution
516 .term_intersection_for_package(next_id)
517 .expect("a package was chosen but we don't have a term");
518 let range = term_intersection.unwrap_positive();
519
520 let cache_selected_version = url.is_none() && index.is_none();
524 let decision = if cache_selected_version
525 && let Some((selected_range, version)) =
526 state.selected_versions.get(&next_id)
527 && selected_range == range
528 {
529 Some(ResolverVersion::Unforked(version.clone()))
530 } else {
531 let decision = self.choose_version(
532 next_package,
533 next_id,
534 index.map(IndexMetadata::url),
535 range,
536 &mut state.pins,
537 &preferences,
538 &state.fork_urls,
539 &state.env,
540 &state.python_requirement,
541 &state.pubgrub,
542 &mut visited,
543 request_sink,
544 )?;
545
546 if cache_selected_version
547 && let Some(ResolverVersion::Unforked(version)) = &decision
548 {
549 state
550 .selected_versions
551 .insert(next_id, (range.clone(), version.clone()));
552 }
553
554 decision
555 };
556
557 let Some(version) = decision else {
559 debug!("No compatible version found for: {next_package}");
560
561 let term_intersection = state
562 .pubgrub
563 .partial_solution
564 .term_intersection_for_package(next_id)
565 .expect("a package was chosen but we don't have a term");
566
567 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
568 if let Some(reason) = self.unavailable_packages.pin().get(name) {
570 state
571 .pubgrub
572 .add_incompatibility(Incompatibility::custom_term(
573 next_id,
574 term_intersection.clone(),
575 UnavailableReason::Package(reason.clone()),
576 ));
577 continue;
578 }
579 }
580
581 state
582 .pubgrub
583 .add_incompatibility(Incompatibility::no_versions(
584 next_id,
585 term_intersection.clone(),
586 ));
587 continue;
588 };
589
590 let version = match version {
591 ResolverVersion::Unforked(version) => version,
592 ResolverVersion::Forked(forks) => {
593 forked_states.extend(self.version_forks_to_fork_states(state, forks));
594 continue 'FORK;
595 }
596 ResolverVersion::Unavailable(version, reason) => {
597 state.add_unavailable_version(version, reason);
598 continue;
599 }
600 };
601
602 if url.is_none() {
604 state.prefetcher.prefetch_batches(
605 next_package,
606 index,
607 &version,
608 term_intersection.unwrap_positive(),
609 state
610 .pubgrub
611 .partial_solution
612 .unchanging_term_for_package(next_id),
613 &state.python_requirement,
614 &self.selector,
615 &state.env,
616 )?;
617 }
618
619 version
620 };
621
622 state.prefetcher.version_tried(next_package, &version);
623
624 self.on_progress(next_package, &version);
625
626 if state
627 .added_dependencies
628 .get(&next_id)
629 .is_some_and(|versions| versions.contains(&version))
630 {
631 state
634 .pubgrub
635 .partial_solution
636 .add_decision(next_id, version);
637 continue;
638 }
639
640 let forked_deps = self.get_dependencies_forking(
642 next_id,
643 next_package,
644 &version,
645 &state.pins,
646 &state.fork_urls,
647 &state.env,
648 &state.python_requirement,
649 &state.pubgrub,
650 )?;
651
652 match forked_deps {
653 ForkedDependencies::Unavailable(reason) => {
654 state
657 .pubgrub
658 .add_incompatibility(Incompatibility::custom_version(
659 next_id,
660 version.clone(),
661 UnavailableReason::Version(reason),
662 ));
663 }
664 ForkedDependencies::Unforked(dependencies) => {
665 state
666 .added_dependencies
667 .entry(next_id)
668 .or_default()
669 .insert(version.clone());
670
671 state
673 .visit_package_version_dependencies(
674 next_id,
675 &version,
676 &self.urls,
677 &self.indexes,
678 &dependencies,
679 &self.git,
680 &self.workspace_members,
681 self.selector.resolution_strategy(),
682 )
683 .map_err(|err| {
684 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
685 })?;
686
687 self.visit_dependencies(&dependencies, &state, request_sink)
689 .map_err(|err| {
690 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
691 })?;
692
693 state.add_package_version_dependencies(
695 next_id,
696 &version,
697 dependencies,
698 &self.index,
699 &self.installed_packages,
700 );
701 }
702 ForkedDependencies::Forked {
703 mut forks,
704 diverging_packages,
705 } => {
706 state
707 .added_dependencies
708 .entry(next_id)
709 .or_default()
710 .insert(version.clone());
711
712 debug!(
713 "Pre-fork {} took {:.3}s",
714 state.env,
715 start.elapsed().as_secs_f32()
716 );
717
718 match (self.options.fork_strategy, self.options.resolution_mode) {
720 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
721 forks.sort_by(|a, b| {
725 a.cmp_requires_python(b)
726 .reverse()
727 .then_with(|| a.cmp_upper_bounds(b))
728 });
729 }
730 (ForkStrategy::RequiresPython, _) => {
731 forks.sort_by(|a, b| {
735 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
736 });
737 }
738 }
739
740 for new_fork_state in self.forks_to_fork_states(
741 state,
742 &version,
743 forks,
744 request_sink,
745 &diverging_packages,
746 ) {
747 forked_states.push(new_fork_state?);
748 }
749 continue 'FORK;
750 }
751 ForkedDependencies::RequiresPython(requires_python) => {
752 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
753 && state.env.marker_environment().is_none()
754 {
755 let forks = fork_version_by_python_requirement(
756 &requires_python,
757 &state.python_requirement,
758 &state.env,
759 );
760 if !forks.is_empty() {
761 debug!(
762 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
763 state.python_requirement.target(),
764 &requires_python,
765 next_package,
766 version,
767 forks
768 .iter()
769 .map(ToString::to_string)
770 .collect::<Vec<_>>()
771 .join(", ")
772 );
773
774 let forks = forks
777 .into_iter()
778 .map(|env| VersionFork {
779 env,
780 id: next_id,
781 version: None,
782 })
783 .collect();
784 forked_states
785 .extend(self.version_forks_to_fork_states(state, forks));
786 continue 'FORK;
787 }
788 }
789
790 state
791 .pubgrub
792 .add_incompatibility(Incompatibility::custom_version(
793 next_id,
794 version.clone(),
795 UnavailableReason::Version(UnavailableVersion::RequiresPython(
796 requires_python,
797 )),
798 ));
799 }
800 }
801 }
802 }
803 if resolutions.len() > 1 {
804 info!(
805 "Solved your requirements for {} environments",
806 resolutions.len()
807 );
808 }
809 if tracing::enabled!(Level::DEBUG) {
810 for resolution in &resolutions {
811 if let Some(env) = resolution.env.end_user_fork_display() {
812 let packages: FxHashSet<_> = resolution
813 .nodes
814 .keys()
815 .map(|package| &package.name)
816 .collect();
817 debug!(
818 "Distinct solution for {env} with {} package(s)",
819 packages.len()
820 );
821 }
822 }
823 }
824 for resolution in &resolutions {
825 resolution.trace_resolution();
826 }
827 ResolverOutput::from_state(
828 &resolutions,
829 self.requirements.clone(),
830 self.constraints.clone(),
831 self.overrides.clone(),
832 &self.preferences,
833 &self.index,
834 &self.git,
835 self.python_requirement.target().clone(),
836 &self.conflicts,
837 self.selector.resolution_strategy(),
838 self.options.clone(),
839 )
840 }
841
842 fn forks_to_fork_states<'a>(
844 &'a self,
845 current_state: ForkState,
846 version: &'a Version,
847 forks: Vec<Fork>,
848 request_sink: &'a Sender<Request>,
849 diverging_packages: &'a BTreeSet<PackageName>,
850 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
851 debug!(
852 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
853 current_state.pubgrub.package_store[current_state.next],
854 version,
855 diverging_packages
856 .iter()
857 .map(ToString::to_string)
858 .join(", "),
859 forks.len(),
860 if forks.len() == 1 { "" } else { "s" }
861 );
862 assert!(forks.len() >= 2);
863 let package = current_state.next;
869 let mut cur_state = Some(current_state);
870 let forks_len = forks.len();
871 forks
872 .into_iter()
873 .enumerate()
874 .map(move |(i, fork)| {
875 let is_last = i == forks_len - 1;
876 let forked_state = cur_state.take().unwrap();
877 if !is_last {
878 cur_state = Some(forked_state.clone());
879 }
880
881 let env = fork.env.clone();
882 (fork, forked_state.with_env(env))
883 })
884 .map(move |(fork, mut forked_state)| {
885 forked_state
887 .visit_package_version_dependencies(
888 package,
889 version,
890 &self.urls,
891 &self.indexes,
892 &fork.dependencies,
893 &self.git,
894 &self.workspace_members,
895 self.selector.resolution_strategy(),
896 )
897 .map_err(|err| {
898 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
899 })?;
900
901 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
903 .map_err(|err| {
904 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
905 })?;
906
907 forked_state.add_package_version_dependencies(
909 package,
910 version,
911 fork.dependencies,
912 &self.index,
913 &self.installed_packages,
914 );
915
916 Ok(forked_state)
917 })
918 }
919
920 #[expect(clippy::unused_self)]
922 fn version_forks_to_fork_states(
923 &self,
924 current_state: ForkState,
925 forks: Vec<VersionFork>,
926 ) -> impl Iterator<Item = ForkState> + '_ {
927 let mut cur_state = Some(current_state);
933 let forks_len = forks.len();
934 forks.into_iter().enumerate().map(move |(i, fork)| {
935 let is_last = i == forks_len - 1;
936 let mut forked_state = cur_state.take().unwrap();
937 if !is_last {
938 cur_state = Some(forked_state.clone());
939 }
940 forked_state.initial_id = Some(fork.id);
941 forked_state.initial_version = fork.version;
942 forked_state.with_env(fork.env)
943 })
944 }
945
946 fn visit_dependencies(
948 &self,
949 dependencies: &[PubGrubDependency],
950 state: &ForkState,
951 request_sink: &Sender<Request>,
952 ) -> Result<(), ResolveError> {
953 for dependency in dependencies {
954 let PubGrubDependency {
955 package,
956 version: _,
957 parent: _,
958 source: _,
959 } = dependency;
960 let url = package.name().and_then(|name| state.fork_urls.get(name));
961 let index = package.name().and_then(|name| state.fork_indexes.get(name));
962 self.visit_package(package, url, index, request_sink)?;
963 }
964 Ok(())
965 }
966
967 fn visit_package(
970 &self,
971 package: &PubGrubPackage,
972 url: Option<&VerbatimParsedUrl>,
973 index: Option<&IndexMetadata>,
974 request_sink: &Sender<Request>,
975 ) -> Result<(), ResolveError> {
976 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
978 return Ok(());
979 }
980
981 self.request_package(package, url, index, request_sink)
982 }
983
984 fn request_package(
985 &self,
986 package: &PubGrubPackage,
987 url: Option<&VerbatimParsedUrl>,
988 index: Option<&IndexMetadata>,
989 request_sink: &Sender<Request>,
990 ) -> Result<(), ResolveError> {
991 let Some(name) = package.name_no_root() else {
993 return Ok(());
994 };
995
996 if let Some(url) = url {
997 if !self.hasher.allows_url(&url.verbatim) {
999 return Err(ResolveError::UnhashedPackage(name.clone()));
1000 }
1001
1002 let dist = Dist::from_url(name.clone(), url.clone())?;
1004 if self.index.distributions().register(dist.distribution_id()) {
1005 request_sink.blocking_send(Request::Dist(dist))?;
1006 }
1007 } else if let Some(index) = index {
1008 if self
1010 .index
1011 .explicit()
1012 .register((name.clone(), index.url().clone()))
1013 {
1014 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1015 }
1016 } else {
1017 if self.index.implicit().register(name.clone()) {
1019 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1020 }
1021 }
1022 Ok(())
1023 }
1024
1025 fn pre_visit<'data>(
1028 packages: impl Iterator<
1029 Item = (
1030 Id<PubGrubPackage>,
1031 &'data PubGrubPackage,
1032 &'data Range<Version>,
1033 ),
1034 >,
1035 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1036 urls: &Urls,
1037 indexes: &Indexes,
1038 python_requirement: &PythonRequirement,
1039 request_sink: &Sender<Request>,
1040 ) -> Result<(), ResolveError> {
1041 for (id, package, range) in packages {
1044 let PubGrubPackageInner::Package {
1045 name,
1046 extra: None,
1047 group: None,
1048 marker: MarkerTree::TRUE,
1049 } = &**package
1050 else {
1051 continue;
1052 };
1053 if urls.any_url(name) {
1056 continue;
1057 }
1058 if indexes.contains_key(name) {
1060 continue;
1061 }
1062 if pre_visited.get(&id) == Some(range) {
1065 continue;
1066 }
1067 pre_visited.insert(id, range.clone());
1068 request_sink.blocking_send(Request::Prefetch(
1069 name.clone(),
1070 range.clone(),
1071 python_requirement.clone(),
1072 ))?;
1073 }
1074 Ok(())
1075 }
1076
1077 fn known_versions<'a>(
1089 index: &InMemoryIndex,
1090 installed_packages: &InstalledPackages,
1091 fork_urls: &ForkUrls,
1092 fork_indexes: &ForkIndexes,
1093 known_versions: &'a mut FxHashMap<PackageName, Arc<[Version]>>,
1094 package: &PubGrubPackage,
1095 ) -> Option<&'a [Version]> {
1096 let name = package.name_no_root()?;
1097 if fork_urls.get(name).is_some() {
1099 return None;
1100 }
1101 if !known_versions.contains_key(name) {
1102 let response = if let Some(index_metadata) = fork_indexes.get(name) {
1103 index
1104 .explicit()
1105 .get(&(name.clone(), index_metadata.url().clone()))?
1106 } else {
1107 index.implicit().get(name)?
1108 };
1109 let VersionsResponse::Found(ref version_maps) = *response else {
1110 return None;
1111 };
1112 let mut versions: Vec<Version> = version_maps
1113 .iter()
1114 .flat_map(|version_map| version_map.included_versions().cloned())
1115 .chain(
1116 installed_packages
1117 .get_packages(name)
1118 .iter()
1119 .map(|dist| dist.version().clone()),
1120 )
1121 .collect();
1122 versions.sort_unstable();
1123 versions.dedup();
1124 known_versions.insert(name.clone(), versions.into());
1125 }
1126 Some(&known_versions[name][..])
1127 }
1128
1129 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1137 fn choose_version(
1138 &self,
1139 package: &PubGrubPackage,
1140 id: Id<PubGrubPackage>,
1141 index: Option<&IndexUrl>,
1142 range: &Range<Version>,
1143 pins: &mut FilePins,
1144 preferences: &Preferences,
1145 fork_urls: &ForkUrls,
1146 env: &ResolverEnvironment,
1147 python_requirement: &PythonRequirement,
1148 pubgrub: &State<UvDependencyProvider>,
1149 visited: &mut FxHashSet<PackageName>,
1150 request_sink: &Sender<Request>,
1151 ) -> Result<Option<ResolverVersion>, ResolveError> {
1152 match &**package {
1153 PubGrubPackageInner::Root(_) => {
1154 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1155 }
1156
1157 PubGrubPackageInner::Python(_) => {
1158 Ok(None)
1161 }
1162
1163 PubGrubPackageInner::System(_) => {
1164 let Some(version) = range.as_singleton() else {
1167 return Ok(None);
1168 };
1169 Ok(Some(ResolverVersion::Unforked(version.clone())))
1170 }
1171
1172 PubGrubPackageInner::Marker { name, .. }
1173 | PubGrubPackageInner::Extra { name, .. }
1174 | PubGrubPackageInner::Group { name, .. }
1175 | PubGrubPackageInner::Package { name, .. } => {
1176 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1177 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1178 } else {
1179 self.choose_version_registry(
1180 package,
1181 id,
1182 name,
1183 index,
1184 range,
1185 preferences,
1186 env,
1187 python_requirement,
1188 pubgrub,
1189 pins,
1190 visited,
1191 request_sink,
1192 )
1193 }
1194 }
1195 }
1196 }
1197
1198 fn choose_version_url(
1201 &self,
1202 id: Id<PubGrubPackage>,
1203 name: &PackageName,
1204 range: &Range<Version>,
1205 url: &VerbatimParsedUrl,
1206 env: &ResolverEnvironment,
1207 python_requirement: &PythonRequirement,
1208 pubgrub: &State<UvDependencyProvider>,
1209 ) -> Result<Option<ResolverVersion>, ResolveError> {
1210 debug!(
1211 "Searching for a compatible version of {name} @ {} ({range})",
1212 url.verbatim
1213 );
1214
1215 let dist = Dist::from_url(name.clone(), url.clone())?;
1216 let distribution_id = dist.distribution_id();
1217 let response = self
1218 .index
1219 .distributions()
1220 .wait_blocking(&distribution_id)
1221 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1222
1223 let metadata = match &*response {
1225 MetadataResponse::Found(archive) => &archive.metadata,
1226 MetadataResponse::Unavailable(reason) => {
1227 self.unavailable_packages
1228 .pin()
1229 .insert(name.clone(), reason.into());
1230 return Ok(None);
1231 }
1232 MetadataResponse::Error(dist, err) => {
1235 return Err(ResolveError::Dist(
1236 DistErrorKind::from_requested_dist(dist, &**err),
1237 dist.clone(),
1238 DerivationChain::default(),
1239 err.clone(),
1240 ));
1241 }
1242 };
1243
1244 let version = &metadata.version;
1245
1246 if !range.contains(version) {
1248 return Ok(None);
1249 }
1250
1251 if let Dist::Built(dist) = &dist {
1254 let filename = match &dist {
1255 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1256 BuiltDist::DirectUrl(dist) => &dist.filename,
1257 BuiltDist::GitPath(dist) => &dist.filename,
1258 BuiltDist::Path(dist) => &dist.filename,
1259 };
1260
1261 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1264 {
1265 let wheel_marker = implied_markers(filename);
1266 for environment_marker in self.options.artifact_environments.iter().copied() {
1269 if env.included_by_marker(environment_marker)
1271 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1272 {
1273 if wheel_marker.is_disjoint(environment_marker) {
1275 return Ok(Some(ResolverVersion::Unavailable(
1276 version.clone(),
1277 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1278 IncompatibleWheel::MissingPlatform(environment_marker),
1279 )),
1280 )));
1281 }
1282 }
1283 }
1284 }
1285
1286 if !python_requirement.target().matches_wheel_tag(filename) {
1288 return Ok(Some(ResolverVersion::Unavailable(
1289 filename.version.clone(),
1290 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1291 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1292 )),
1293 )));
1294 }
1295 }
1296
1297 if let Some(requires_python) = metadata.requires_python.as_ref() {
1299 if !python_requirement.target().is_contained_by(requires_python) {
1300 let kind = if python_requirement.installed() == python_requirement.target() {
1301 PythonRequirementKind::Installed
1302 } else {
1303 PythonRequirementKind::Target
1304 };
1305 return Ok(Some(ResolverVersion::Unavailable(
1306 version.clone(),
1307 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1308 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1309 )),
1310 )));
1311 }
1312 }
1313
1314 Ok(Some(ResolverVersion::Unforked(version.clone())))
1315 }
1316
1317 fn choose_version_registry(
1320 &self,
1321 package: &PubGrubPackage,
1322 id: Id<PubGrubPackage>,
1323 name: &PackageName,
1324 index: Option<&IndexUrl>,
1325 range: &Range<Version>,
1326 preferences: &Preferences,
1327 env: &ResolverEnvironment,
1328 python_requirement: &PythonRequirement,
1329 pubgrub: &State<UvDependencyProvider>,
1330 pins: &mut FilePins,
1331 visited: &mut FxHashSet<PackageName>,
1332 request_sink: &Sender<Request>,
1333 ) -> Result<Option<ResolverVersion>, ResolveError> {
1334 let versions_response = if let Some(index) = index {
1336 self.index
1337 .explicit()
1338 .wait_blocking(&(name.clone(), index.clone()))
1339 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1340 } else {
1341 self.index
1342 .implicit()
1343 .wait_blocking(name)
1344 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1345 };
1346 visited.insert(name.clone());
1347
1348 let version_maps = match *versions_response {
1349 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1350 VersionsResponse::NoIndex => {
1351 self.unavailable_packages
1352 .pin()
1353 .insert(name.clone(), UnavailablePackage::NoIndex);
1354 &[]
1355 }
1356 VersionsResponse::Offline => {
1357 self.unavailable_packages
1358 .pin()
1359 .insert(name.clone(), UnavailablePackage::Offline);
1360 &[]
1361 }
1362 VersionsResponse::NotFound => {
1363 self.unavailable_packages
1364 .pin()
1365 .insert(name.clone(), UnavailablePackage::NotFound);
1366 &[]
1367 }
1368 };
1369
1370 debug!("Searching for a compatible version of {package} ({range})");
1371
1372 let Some(candidate) = self.selector.select(
1374 name,
1375 range,
1376 version_maps,
1377 preferences,
1378 &self.installed_packages,
1379 &self.exclusions,
1380 index,
1381 env,
1382 self.tags.as_ref(),
1383 ) else {
1384 return Ok(None);
1386 };
1387
1388 let dist = match candidate.dist() {
1389 CandidateDist::Compatible(dist) => dist,
1390 CandidateDist::Incompatible {
1391 incompatible_dist: incompatibility,
1392 prioritized_dist: _,
1393 } => {
1394 return Ok(Some(ResolverVersion::Unavailable(
1396 candidate.version().clone(),
1397 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1400 )));
1401 }
1402 };
1403
1404 if let Some((requires_python, incompatibility)) =
1406 Self::check_requires_python(dist, python_requirement)
1407 {
1408 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1409 if env.marker_environment().is_none() {
1410 let forks = fork_version_by_python_requirement(
1411 requires_python,
1412 python_requirement,
1413 env,
1414 );
1415 if !forks.is_empty() {
1416 debug!(
1417 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1418 python_requirement.target(),
1419 requires_python,
1420 name,
1421 candidate.version(),
1422 forks
1423 .iter()
1424 .map(ToString::to_string)
1425 .collect::<Vec<_>>()
1426 .join(", ")
1427 );
1428 let forks = forks
1429 .into_iter()
1430 .map(|env| VersionFork {
1431 env,
1432 id,
1433 version: None,
1434 })
1435 .collect();
1436 return Ok(Some(ResolverVersion::Forked(forks)));
1437 }
1438 }
1439 }
1440
1441 return Ok(Some(ResolverVersion::Unavailable(
1442 candidate.version().clone(),
1443 UnavailableVersion::IncompatibleDist(incompatibility),
1444 )));
1445 }
1446
1447 if let Some(forked) = self.fork_version_registry(
1449 &candidate,
1450 dist,
1451 version_maps,
1452 package,
1453 id,
1454 name,
1455 index,
1456 range,
1457 preferences,
1458 env,
1459 pubgrub,
1460 pins,
1461 request_sink,
1462 )? {
1463 return Ok(Some(forked));
1464 }
1465
1466 let filename = match dist.for_installation() {
1467 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1468 .filename()
1469 .unwrap_or(Cow::Borrowed("unknown filename")),
1470 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1471 .filename()
1472 .unwrap_or(Cow::Borrowed("unknown filename")),
1473 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1474 };
1475
1476 debug!(
1477 "Selecting: {}=={} [{}] ({})",
1478 name,
1479 candidate.version(),
1480 candidate.choice_kind(),
1481 filename,
1482 );
1483 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1484
1485 let version = candidate.version().clone();
1486 Ok(Some(ResolverVersion::Unforked(version)))
1487 }
1488
1489 fn fork_version_registry(
1502 &self,
1503 candidate: &Candidate,
1504 dist: &CompatibleDist,
1505 version_maps: &[VersionMap],
1506 package: &PubGrubPackage,
1507 id: Id<PubGrubPackage>,
1508 name: &PackageName,
1509 index: Option<&IndexUrl>,
1510 range: &Range<Version>,
1511 preferences: &Preferences,
1512 env: &ResolverEnvironment,
1513 pubgrub: &State<UvDependencyProvider>,
1514 pins: &mut FilePins,
1515 request_sink: &Sender<Request>,
1516 ) -> Result<Option<ResolverVersion>, ResolveError> {
1517 if env.marker_environment().is_some() {
1519 return Ok(None);
1520 }
1521
1522 if dist.implied_markers().is_true() {
1525 return Ok(None);
1526 }
1527
1528 for marker in self.options.artifact_environments.iter().copied() {
1531 if env.included_by_marker(marker) {
1533 if dist.implied_markers().is_disjoint(marker)
1535 && !find_environments(id, pubgrub).is_disjoint(marker)
1536 {
1537 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1539 return Ok(Some(ResolverVersion::Unavailable(
1540 candidate.version().clone(),
1541 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1542 IncompatibleWheel::MissingPlatform(marker),
1543 )),
1544 )));
1545 };
1546
1547 debug!(
1548 "Forking on required platform `{}` for {}=={} ({})",
1549 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1550 name,
1551 candidate.version(),
1552 [&left, &right]
1553 .iter()
1554 .map(ToString::to_string)
1555 .collect::<Vec<_>>()
1556 .join(", ")
1557 );
1558 let forks = vec![
1559 VersionFork {
1560 env: left,
1561 id,
1562 version: None,
1563 },
1564 VersionFork {
1565 env: right,
1566 id,
1567 version: None,
1568 },
1569 ];
1570 return Ok(Some(ResolverVersion::Forked(forks)));
1571 }
1572 }
1573 }
1574
1575 if !candidate.version().is_local() {
1577 return Ok(None);
1578 }
1579
1580 debug!(
1581 "Looking at local version: {}=={}",
1582 name,
1583 candidate.version()
1584 );
1585
1586 let range = range.clone().intersection(&Range::singleton(
1588 candidate.version().clone().without_local(),
1589 ));
1590
1591 let Some(base_candidate) = self.selector.select(
1592 name,
1593 &range,
1594 version_maps,
1595 preferences,
1596 &self.installed_packages,
1597 &self.exclusions,
1598 index,
1599 env,
1600 self.tags.as_ref(),
1601 ) else {
1602 return Ok(None);
1603 };
1604 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1605 return Ok(None);
1606 };
1607
1608 let mut remainder = {
1610 let mut remainder = base_dist.implied_markers();
1611 remainder = remainder.and(dist.implied_markers().negate());
1612 remainder
1613 };
1614 if remainder.is_false() {
1615 return Ok(None);
1616 }
1617
1618 if !env.included_by_marker(remainder) {
1622 return Ok(None);
1623 }
1624
1625 if !env.included_by_marker(dist.implied_markers()) {
1628 let filename = match dist.for_installation() {
1629 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1630 .filename()
1631 .unwrap_or(Cow::Borrowed("unknown filename")),
1632 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1633 .filename()
1634 .unwrap_or(Cow::Borrowed("unknown filename")),
1635 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1636 };
1637
1638 debug!(
1639 "Preferring non-local candidate: {}=={} [{}] ({})",
1640 name,
1641 base_candidate.version(),
1642 base_candidate.choice_kind(),
1643 filename,
1644 );
1645 self.visit_candidate(
1646 &base_candidate,
1647 base_dist,
1648 package,
1649 name,
1650 pins,
1651 request_sink,
1652 )?;
1653
1654 return Ok(Some(ResolverVersion::Unforked(
1655 base_candidate.version().clone(),
1656 )));
1657 }
1658
1659 for value in [
1668 arcstr::literal!("darwin"),
1669 arcstr::literal!("linux"),
1670 arcstr::literal!("win32"),
1671 ] {
1672 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1673 key: MarkerValueString::SysPlatform,
1674 operator: MarkerOperator::Equal,
1675 value,
1676 });
1677 if dist.implied_markers().is_disjoint(sys_platform)
1678 && !remainder.is_disjoint(sys_platform)
1679 {
1680 remainder = remainder.or(sys_platform);
1681 }
1682 }
1683
1684 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1686 return Ok(None);
1687 };
1688
1689 debug!(
1690 "Forking platform for {}=={} ({})",
1691 name,
1692 candidate.version(),
1693 [&base_env, &local_env]
1694 .iter()
1695 .map(ToString::to_string)
1696 .collect::<Vec<_>>()
1697 .join(", ")
1698 );
1699 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1700 self.visit_candidate(
1701 &base_candidate,
1702 base_dist,
1703 package,
1704 name,
1705 pins,
1706 request_sink,
1707 )?;
1708
1709 let forks = vec![
1710 VersionFork {
1711 env: base_env.clone(),
1712 id,
1713 version: Some(base_candidate.version().clone()),
1714 },
1715 VersionFork {
1716 env: local_env.clone(),
1717 id,
1718 version: Some(candidate.version().clone()),
1719 },
1720 ];
1721 Ok(Some(ResolverVersion::Forked(forks)))
1722 }
1723
1724 fn visit_candidate(
1726 &self,
1727 candidate: &Candidate,
1728 dist: &CompatibleDist,
1729 package: &PubGrubPackage,
1730 name: &PackageName,
1731 pins: &mut FilePins,
1732 request_sink: &Sender<Request>,
1733 ) -> Result<(), ResolveError> {
1734 pins.insert(candidate, dist);
1737
1738 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1740 if self.dependency_mode.is_transitive() {
1741 let dist = dist.for_resolution();
1742 if self.index.distributions().register(dist.distribution_id()) {
1743 if name != dist.name() {
1744 return Err(ResolveError::MismatchedPackageName {
1745 request: "distribution",
1746 expected: name.clone(),
1747 actual: dist.name().clone(),
1748 });
1749 }
1750 if !self
1752 .hasher
1753 .allows_package(candidate.name(), candidate.version())
1754 {
1755 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1756 }
1757
1758 let request = Request::from(dist);
1759 request_sink.blocking_send(request)?;
1760 }
1761 }
1762 }
1763
1764 Ok(())
1765 }
1766
1767 fn check_requires_python<'dist>(
1770 dist: &'dist CompatibleDist,
1771 python_requirement: &PythonRequirement,
1772 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1773 let requires_python = dist.requires_python()?;
1774 if python_requirement.target().is_contained_by(requires_python) {
1775 None
1776 } else {
1777 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1778 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1779 requires_python.clone(),
1780 if python_requirement.installed() == python_requirement.target() {
1781 PythonRequirementKind::Installed
1782 } else {
1783 PythonRequirementKind::Target
1784 },
1785 ))
1786 } else {
1787 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1788 requires_python.clone(),
1789 if python_requirement.installed() == python_requirement.target() {
1790 PythonRequirementKind::Installed
1791 } else {
1792 PythonRequirementKind::Target
1793 },
1794 ))
1795 };
1796 Some((requires_python, incompatibility))
1797 }
1798 }
1799
1800 fn get_dependencies_forking(
1802 &self,
1803 id: Id<PubGrubPackage>,
1804 package: &PubGrubPackage,
1805 version: &Version,
1806 pins: &FilePins,
1807 fork_urls: &ForkUrls,
1808 env: &ResolverEnvironment,
1809 python_requirement: &PythonRequirement,
1810 pubgrub: &State<UvDependencyProvider>,
1811 ) -> Result<ForkedDependencies, ResolveError> {
1812 let dependencies = self.get_dependencies(
1813 id,
1814 package,
1815 version,
1816 pins,
1817 fork_urls,
1818 env,
1819 python_requirement,
1820 pubgrub,
1821 )?;
1822 if env.marker_environment().is_some() {
1823 Ok(ForkedDependencies::from_dependencies_platform_specific(
1824 dependencies,
1825 ))
1826 } else {
1827 Ok(ForkedDependencies::from_dependencies_universal(
1828 dependencies,
1829 env,
1830 python_requirement,
1831 &self.conflicts,
1832 ))
1833 }
1834 }
1835
1836 #[instrument(skip_all, fields(%package, %version))]
1838 fn get_dependencies(
1839 &self,
1840 id: Id<PubGrubPackage>,
1841 package: &PubGrubPackage,
1842 version: &Version,
1843 pins: &FilePins,
1844 fork_urls: &ForkUrls,
1845 env: &ResolverEnvironment,
1846 python_requirement: &PythonRequirement,
1847 pubgrub: &State<UvDependencyProvider>,
1848 ) -> Result<Dependencies, ResolveError> {
1849 let dependencies = match &**package {
1850 PubGrubPackageInner::Root(_) => {
1851 let no_dev_deps = BTreeMap::default();
1852 let requirements = self.flatten_requirements(
1853 &self.requirements,
1854 &no_dev_deps,
1855 None,
1856 None,
1857 None,
1858 None,
1859 env,
1860 python_requirement,
1861 );
1862
1863 PubGrubDependency::from_requirements(
1864 &self.conflicts,
1865 requirements,
1866 None,
1867 Some(package),
1868 )
1869 }
1870
1871 PubGrubPackageInner::Package {
1872 name,
1873 extra,
1874 group,
1875 marker: _,
1876 } => {
1877 if self.dependency_mode.is_direct() {
1879 return Ok(Dependencies::Unforkable(Vec::default()));
1880 }
1881
1882 let owned_id;
1884 let distribution_id = if let Some((_, metadata_id)) =
1885 pins.dist_and_id(name, version)
1886 {
1887 metadata_id
1888 } else if let Some(url) = fork_urls.get(name) {
1889 let dist = Dist::from_url(name.clone(), url.clone())?;
1890 owned_id = dist.distribution_id();
1891 &owned_id
1892 } else {
1893 debug_assert!(
1894 false,
1895 "Dependencies were requested for a package without a pinned distribution"
1896 );
1897 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1898 };
1899
1900 if self.dependency_mode.is_transitive()
1902 && self.unavailable_packages.pin().contains_key(name)
1903 && self.installed_packages.get_packages(name).is_empty()
1904 {
1905 debug_assert!(
1906 false,
1907 "Dependencies were requested for a package that is not available"
1908 );
1909 return Err(ResolveError::PackageUnavailable(name.clone()));
1910 }
1911
1912 let response = self
1914 .index
1915 .distributions()
1916 .wait_blocking(distribution_id)
1917 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1918
1919 let metadata = match &*response {
1920 MetadataResponse::Found(archive) => &archive.metadata,
1921 MetadataResponse::Unavailable(reason) => {
1922 let unavailable_version = UnavailableVersion::from(reason);
1923 let message = unavailable_version.singular_message();
1924 if let Some(err) = reason.source() {
1925 warn!("{name} {message}: {err}");
1927 } else {
1928 warn!("{name} {message}");
1929 }
1930 let incomplete_packages = self.incomplete_packages.pin();
1931 let versions = incomplete_packages.get_or_insert(
1932 name.clone(),
1933 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1934 );
1935 versions.pin().insert(version.clone(), reason.clone());
1936 return Ok(Dependencies::Unavailable(unavailable_version));
1937 }
1938 MetadataResponse::Error(dist, err) => {
1939 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1940 .unwrap_or_default();
1941 return Err(ResolveError::Dist(
1942 DistErrorKind::from_requested_dist(dist, &**err),
1943 dist.clone(),
1944 chain,
1945 err.clone(),
1946 ));
1947 }
1948 };
1949
1950 if let Some(requires_python) = &metadata.requires_python {
1953 if !python_requirement.target().is_contained_by(requires_python) {
1954 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1955 }
1956 }
1957
1958 let system_dependencies = self
1960 .options
1961 .torch_backend
1962 .as_ref()
1963 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1964 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1965 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1966 .map(IndexUrl::url)
1967 .and_then(SystemDependency::from_index)
1968 .into_iter()
1969 .inspect(|system_dependency| {
1970 debug!(
1971 "Adding system dependency `{}` for `{package}@{version}`",
1972 system_dependency
1973 );
1974 })
1975 .map(PubGrubDependency::from);
1976
1977 let requirements = self.flatten_requirements(
1978 &metadata.requires_dist,
1979 &metadata.dependency_groups,
1980 extra.as_ref(),
1981 group.as_ref(),
1982 Some(name),
1983 Some(version),
1984 env,
1985 python_requirement,
1986 );
1987
1988 PubGrubDependency::from_requirements(
1989 &self.conflicts,
1990 requirements,
1991 group.as_ref(),
1992 Some(package),
1993 )
1994 .map(|mut dependencies| {
1995 dependencies.extend(system_dependencies);
1996 dependencies
1997 })
1998 }
1999
2000 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2001
2002 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2003
2004 PubGrubPackageInner::Marker { name, marker } => {
2006 return Ok(Dependencies::Unforkable(
2007 [MarkerTree::TRUE, *marker]
2008 .into_iter()
2009 .map(move |marker| PubGrubDependency {
2010 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2011 name: name.clone(),
2012 extra: None,
2013 group: None,
2014 marker,
2015 }),
2016 version: Range::singleton(version.clone()),
2017 parent: None,
2018 source: DependencySource::Unspecified,
2019 })
2020 .collect(),
2021 ));
2022 }
2023
2024 PubGrubPackageInner::Extra {
2026 name,
2027 extra,
2028 marker,
2029 } => {
2030 return Ok(Dependencies::Unforkable(
2031 [MarkerTree::TRUE, *marker]
2032 .into_iter()
2033 .dedup()
2034 .flat_map(move |marker| {
2035 [None, Some(extra)]
2036 .into_iter()
2037 .map(move |extra| PubGrubDependency {
2038 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2039 name: name.clone(),
2040 extra: extra.cloned(),
2041 group: None,
2042 marker,
2043 }),
2044 version: Range::singleton(version.clone()),
2045 parent: None,
2046 source: DependencySource::Unspecified,
2047 })
2048 })
2049 .collect(),
2050 ));
2051 }
2052
2053 PubGrubPackageInner::Group {
2055 name,
2056 group,
2057 marker,
2058 } => {
2059 return Ok(Dependencies::Unforkable(
2060 [MarkerTree::TRUE, *marker]
2061 .into_iter()
2062 .dedup()
2063 .map(|marker| PubGrubDependency {
2064 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2065 name: name.clone(),
2066 extra: None,
2067 group: Some(group.clone()),
2068 marker,
2069 }),
2070 version: Range::singleton(version.clone()),
2071 parent: None,
2072 source: DependencySource::Unspecified,
2073 })
2074 .collect(),
2075 ));
2076 }
2077 };
2078 Ok(match dependencies {
2079 Ok(dependencies) => Dependencies::Available(dependencies),
2080 Err(requirement) => {
2081 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2082 }
2083 })
2084 }
2085
2086 fn flatten_requirements<'a>(
2090 &'a self,
2091 dependencies: &'a [Requirement],
2092 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2093 extra: Option<&'a ExtraName>,
2094 dev: Option<&'a GroupName>,
2095 name: Option<&'a PackageName>,
2096 version: Option<&'a Version>,
2097 env: &'a ResolverEnvironment,
2098 python_requirement: &'a PythonRequirement,
2099 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2100 let python_marker = python_requirement.to_marker_tree();
2101
2102 if let Some(dev) = dev {
2103 Either::Left(Either::Left(self.requirements_for_extra(
2106 dev_dependencies.get(dev).into_iter().flatten(),
2107 extra,
2108 None,
2109 name.zip(version),
2110 env,
2111 python_marker,
2112 python_requirement,
2113 )))
2114 } else if !dependencies
2115 .iter()
2116 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2117 {
2118 Either::Left(Either::Right(self.requirements_for_extra(
2120 dependencies.iter(),
2121 extra,
2122 name.zip(version),
2123 name.zip(version),
2124 env,
2125 python_marker,
2126 python_requirement,
2127 )))
2128 } else {
2129 let mut requirements = self
2130 .requirements_for_extra(
2131 dependencies.iter(),
2132 extra,
2133 name.zip(version),
2134 name.zip(version),
2135 env,
2136 python_marker,
2137 python_requirement,
2138 )
2139 .collect::<Vec<_>>();
2140
2141 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2144 let mut queue: VecDeque<_> = requirements
2145 .iter()
2146 .filter(|req| name == Some(&req.name))
2147 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2148 .collect();
2149 while let Some((extra, marker)) = queue.pop_front() {
2150 if !seen.insert((extra.clone(), marker)) {
2151 continue;
2152 }
2153 for requirement in self.requirements_for_extra(
2154 dependencies,
2155 Some(&extra),
2156 name.zip(version),
2157 name.zip(version),
2158 env,
2159 python_marker,
2160 python_requirement,
2161 ) {
2162 let requirement = match requirement {
2163 Cow::Owned(mut requirement) => {
2164 requirement.marker = requirement.marker.and(marker);
2165 requirement
2166 }
2167 Cow::Borrowed(requirement) => {
2168 let mut marker = marker;
2169 marker = marker.and(requirement.marker);
2170 Requirement {
2171 name: requirement.name.clone(),
2172 extras: requirement.extras.clone(),
2173 groups: requirement.groups.clone(),
2174 source: requirement.source.clone(),
2175 origin: requirement.origin.clone(),
2176 marker: marker.simplify_extras(slice::from_ref(&extra)),
2177 }
2178 }
2179 };
2180 if name == Some(&requirement.name) {
2181 queue.extend(
2183 requirement
2184 .extras
2185 .iter()
2186 .cloned()
2187 .map(|extra| (extra, requirement.marker)),
2188 );
2189 } else {
2190 requirements.push(Cow::Owned(requirement));
2192 }
2193 }
2194 }
2195
2196 let mut self_constraints = vec![];
2200 for req in &requirements {
2201 if name == Some(&req.name) && !req.source.is_empty() {
2202 self_constraints.push(Requirement {
2203 name: req.name.clone(),
2204 extras: Box::new([]),
2205 groups: req.groups.clone(),
2206 source: req.source.clone(),
2207 origin: req.origin.clone(),
2208 marker: req.marker,
2209 });
2210 }
2211 }
2212
2213 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2215 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2216
2217 Either::Right(requirements.into_iter())
2218 }
2219 }
2220
2221 fn requirements_for_extra<'data, 'parameters>(
2224 &'data self,
2225 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2226 extra: Option<&'parameters ExtraName>,
2227 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2228 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2229 env: &'parameters ResolverEnvironment,
2230 python_marker: MarkerTree,
2231 python_requirement: &'parameters PythonRequirement,
2232 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2233 where
2234 'data: 'parameters,
2235 {
2236 self.overrides
2237 .apply_for_package(override_package, dependencies)
2238 .filter(move |requirement| {
2239 !self
2240 .excludes
2241 .contains_for_package(exclusion_package, &requirement.name)
2242 })
2243 .map(move |mut requirement| {
2244 let marker = match extra {
2253 Some(extra) => requirement
2254 .marker
2255 .simplify_extras(slice::from_ref(extra))
2256 .simplify_not_extras_with(|candidate| candidate != extra)
2257 .and(
2258 requirement
2259 .marker
2260 .simplify_not_extras_with(|_| true)
2261 .negate(),
2262 ),
2263 None => requirement.marker.simplify_not_extras_with(|_| true),
2264 };
2265
2266 if requirement.marker != marker {
2267 requirement.to_mut().marker = marker;
2268 }
2269
2270 requirement
2271 })
2272 .filter(move |requirement| {
2273 Self::is_requirement_applicable(
2274 requirement,
2275 extra,
2276 env,
2277 python_marker,
2278 python_requirement,
2279 )
2280 })
2281 .flat_map(move |requirement| {
2282 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2283 requirement,
2284 extra,
2285 env,
2286 python_marker,
2287 python_requirement,
2288 ))
2289 })
2290 }
2291
2292 fn is_requirement_applicable(
2295 requirement: &Requirement,
2296 extra: Option<&ExtraName>,
2297 env: &ResolverEnvironment,
2298 python_marker: MarkerTree,
2299 python_requirement: &PythonRequirement,
2300 ) -> bool {
2301 match extra {
2303 Some(source_extra) => {
2304 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2305 return false;
2306 }
2307
2308 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2309 {
2310 return false;
2311 }
2312 }
2313 None => {
2314 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2315 return false;
2316 }
2317 }
2318 }
2319
2320 if python_marker.is_disjoint(requirement.marker) {
2323 trace!(
2324 "Skipping {requirement} because of Requires-Python: {requires_python}",
2325 requires_python = python_requirement.target(),
2326 );
2327 return false;
2328 }
2329
2330 if !env.included_by_marker(requirement.marker) {
2333 trace!("Skipping {requirement} because of {env}");
2334 return false;
2335 }
2336
2337 true
2338 }
2339
2340 fn constraints_for_requirement<'data, 'parameters>(
2343 &'data self,
2344 requirement: Cow<'data, Requirement>,
2345 extra: Option<&'parameters ExtraName>,
2346 env: &'parameters ResolverEnvironment,
2347 python_marker: MarkerTree,
2348 python_requirement: &'parameters PythonRequirement,
2349 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2350 where
2351 'data: 'parameters,
2352 {
2353 self.constraints
2354 .get(&requirement.name)
2355 .into_iter()
2356 .flatten()
2357 .filter_map(move |constraint| {
2358 let constraint = if constraint.marker.is_true() {
2361 if requirement.marker.is_true() {
2365 Cow::Borrowed(constraint)
2366 } else {
2367 let mut marker = constraint.marker;
2368 marker = marker.and(requirement.marker);
2369
2370 if marker.is_false() {
2371 trace!(
2372 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2373 constraint.marker.try_to_string().unwrap(),
2374 requirement.marker.try_to_string().unwrap(),
2375 );
2376 return None;
2377 }
2378
2379 Cow::Owned(Requirement {
2380 name: constraint.name.clone(),
2381 extras: constraint.extras.clone(),
2382 groups: constraint.groups.clone(),
2383 source: constraint.source.clone(),
2384 origin: constraint.origin.clone(),
2385 marker,
2386 })
2387 }
2388 } else {
2389 let requires_python = python_requirement.target();
2390
2391 let mut marker = constraint.marker;
2392 marker = marker.and(requirement.marker);
2393
2394 if marker.is_false() {
2395 trace!(
2396 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2397 constraint.marker.try_to_string().unwrap(),
2398 requirement.marker.try_to_string().unwrap(),
2399 );
2400 return None;
2401 }
2402
2403 if python_marker.is_disjoint(marker) {
2407 trace!(
2408 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2409 );
2410 return None;
2411 }
2412
2413 if marker == constraint.marker {
2414 Cow::Borrowed(constraint)
2415 } else {
2416 Cow::Owned(Requirement {
2417 name: constraint.name.clone(),
2418 extras: constraint.extras.clone(),
2419 groups: constraint.groups.clone(),
2420 source: constraint.source.clone(),
2421 origin: constraint.origin.clone(),
2422 marker,
2423 })
2424 }
2425 };
2426
2427 if !env.included_by_marker(constraint.marker) {
2430 trace!("Skipping {constraint} because of {env}");
2431 return None;
2432 }
2433
2434 match extra {
2436 Some(source_extra) => {
2437 if !constraint
2438 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2439 {
2440 return None;
2441 }
2442 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2443 {
2444 return None;
2445 }
2446 }
2447 None => {
2448 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2449 return None;
2450 }
2451 }
2452 }
2453
2454 Some(constraint)
2455 })
2456 }
2457
2458 async fn fetch<Provider: ResolverProvider>(
2460 self: Arc<Self>,
2461 provider: Arc<Provider>,
2462 request_stream: Receiver<Request>,
2463 ) -> Result<(), ResolveError> {
2464 let mut response_stream = ReceiverStream::new(request_stream)
2465 .map(|request| self.process_request(request, &*provider).boxed_local())
2466 .buffer_unordered(usize::MAX);
2470
2471 while let Some(response) = response_stream.next().await {
2472 match response? {
2473 Some(Response::Package(name, index, version_map)) => {
2474 trace!("Received package metadata for: {name}");
2475 if let Some(index) = index {
2476 self.index
2477 .explicit()
2478 .done((name, index), Arc::new(version_map));
2479 } else {
2480 self.index.implicit().done(name, Arc::new(version_map));
2481 }
2482 }
2483 Some(Response::Installed { dist, metadata }) => {
2484 trace!("Received installed distribution metadata for: {dist}");
2485 self.index
2486 .distributions()
2487 .done(dist.distribution_id(), Arc::new(metadata));
2488 }
2489 Some(Response::Dist { dist, metadata }) => {
2490 let dist_kind = match dist {
2491 Dist::Built(_) => "built",
2492 Dist::Source(_) => "source",
2493 };
2494 trace!("Received {dist_kind} distribution metadata for: {dist}");
2495 if let MetadataResponse::Unavailable(reason) = &metadata {
2496 let message = UnavailableVersion::from(reason).singular_message();
2497 if let Some(err) = reason.source() {
2498 warn!("{dist} {message}: {err}");
2500 } else {
2501 warn!("{dist} {message}");
2502 }
2503 }
2504 self.index
2505 .distributions()
2506 .done(dist.distribution_id(), Arc::new(metadata));
2507 }
2508 None => {}
2509 }
2510 }
2511
2512 Ok::<(), ResolveError>(())
2513 }
2514
2515 #[instrument(skip_all, fields(%request))]
2516 async fn process_request<Provider: ResolverProvider>(
2517 &self,
2518 request: Request,
2519 provider: &Provider,
2520 ) -> Result<Option<Response>, ResolveError> {
2521 match request {
2522 Request::Package(package_name, index) => {
2524 let package_versions = provider
2525 .get_package_versions(&package_name, index.as_ref())
2526 .boxed_local()
2527 .await
2528 .map_err(ResolveError::Client)?;
2529
2530 Ok(Some(Response::Package(
2531 package_name,
2532 index.map(IndexMetadata::into_url),
2533 package_versions,
2534 )))
2535 }
2536
2537 Request::Dist(dist) => {
2539 if let Some(version) = dist.version() {
2540 if let Some(index) = dist.index() {
2541 let versions_response = self.index.implicit().get(dist.name());
2543 if let Some(VersionsResponse::Found(version_maps)) =
2544 versions_response.as_deref()
2545 {
2546 for version_map in version_maps {
2547 if version_map.index() == Some(index) {
2548 let Some(metadata) = version_map.get_metadata(version) else {
2549 continue;
2550 };
2551 debug!("Found registry-provided metadata for: {dist}");
2552 return Ok(Some(Response::Dist {
2553 dist,
2554 metadata: MetadataResponse::Found(
2555 ArchiveMetadata::from_metadata23(metadata),
2556 ),
2557 }));
2558 }
2559 }
2560 }
2561
2562 let versions_response = self
2564 .index
2565 .explicit()
2566 .get(&(dist.name().clone(), index.clone()));
2567 if let Some(VersionsResponse::Found(version_maps)) =
2568 versions_response.as_deref()
2569 {
2570 for version_map in version_maps {
2571 let Some(metadata) = version_map.get_metadata(version) else {
2572 continue;
2573 };
2574 debug!("Found registry-provided metadata for: {dist}");
2575 return Ok(Some(Response::Dist {
2576 dist,
2577 metadata: MetadataResponse::Found(
2578 ArchiveMetadata::from_metadata23(metadata),
2579 ),
2580 }));
2581 }
2582 }
2583 }
2584 }
2585
2586 let metadata = provider
2587 .get_or_build_wheel_metadata(&dist)
2588 .boxed_local()
2589 .await?;
2590
2591 if let MetadataResponse::Found(metadata) = &metadata {
2592 if &metadata.metadata.name != dist.name() {
2593 return Err(ResolveError::MismatchedPackageName {
2594 request: "distribution metadata",
2595 expected: dist.name().clone(),
2596 actual: metadata.metadata.name.clone(),
2597 });
2598 }
2599 }
2600
2601 Ok(Some(Response::Dist { dist, metadata }))
2602 }
2603
2604 Request::Installed(dist) => {
2605 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2606
2607 if let MetadataResponse::Found(metadata) = &metadata {
2608 if &metadata.metadata.name != dist.name() {
2609 return Err(ResolveError::MismatchedPackageName {
2610 request: "installed metadata",
2611 expected: dist.name().clone(),
2612 actual: metadata.metadata.name.clone(),
2613 });
2614 }
2615 }
2616
2617 Ok(Some(Response::Installed { dist, metadata }))
2618 }
2619
2620 Request::Prefetch(package_name, range, python_requirement) => {
2622 let versions_response = self
2624 .index
2625 .implicit()
2626 .wait(&package_name)
2627 .await
2628 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2629
2630 let version_map = match *versions_response {
2631 VersionsResponse::Found(ref version_map) => version_map,
2632 VersionsResponse::NoIndex => {
2634 self.unavailable_packages
2635 .pin()
2636 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2637
2638 return Ok(None);
2639 }
2640 VersionsResponse::Offline => {
2641 self.unavailable_packages
2642 .pin()
2643 .insert(package_name.clone(), UnavailablePackage::Offline);
2644
2645 return Ok(None);
2646 }
2647 VersionsResponse::NotFound => {
2648 self.unavailable_packages
2649 .pin()
2650 .insert(package_name.clone(), UnavailablePackage::NotFound);
2651
2652 return Ok(None);
2653 }
2654 };
2655
2656 let env = ResolverEnvironment::universal(vec![]);
2658
2659 let Some(candidate) = self.selector.select(
2662 &package_name,
2663 &range,
2664 version_map,
2665 &self.preferences,
2666 &self.installed_packages,
2667 &self.exclusions,
2668 None,
2669 &env,
2670 self.tags.as_ref(),
2671 ) else {
2672 return Ok(None);
2673 };
2674
2675 let Some(dist) = candidate.compatible() else {
2677 return Ok(None);
2678 };
2679
2680 for version_map in version_map {
2682 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2683 let dist = dist.for_resolution();
2684 if version_map.index() == dist.index() {
2685 debug!("Found registry-provided metadata for: {dist}");
2686
2687 let metadata =
2688 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2689
2690 let dist = dist.to_owned();
2691 if &package_name != dist.name() {
2692 return Err(ResolveError::MismatchedPackageName {
2693 request: "distribution",
2694 expected: package_name,
2695 actual: dist.name().clone(),
2696 });
2697 }
2698
2699 let response = match dist {
2700 ResolvedDist::Installable { dist, .. } => Response::Dist {
2701 dist: (*dist).clone(),
2702 metadata,
2703 },
2704 ResolvedDist::Installed { dist } => Response::Installed {
2705 dist: (*dist).clone(),
2706 metadata,
2707 },
2708 };
2709
2710 return Ok(Some(response));
2711 }
2712 }
2713 }
2714
2715 if dist.wheel().is_none() {
2719 if !self.selector.use_highest_version(&package_name, &env) {
2720 if let Some((lower, _)) = range.iter().next() {
2721 if lower == Bound::Unbounded {
2722 debug!(
2723 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2724 );
2725 return Ok(None);
2726 }
2727 }
2728 }
2729 }
2730
2731 if let Some(requires_python) = dist.requires_python() {
2733 if !python_requirement.target().is_contained_by(requires_python) {
2734 return Ok(None);
2735 }
2736 }
2737
2738 if !self
2740 .hasher
2741 .allows_package(candidate.name(), candidate.version())
2742 {
2743 return Ok(None);
2744 }
2745
2746 let dist = dist.for_resolution();
2748 if self.index.distributions().register(dist.distribution_id()) {
2749 let dist = dist.to_owned();
2750 if &package_name != dist.name() {
2751 return Err(ResolveError::MismatchedPackageName {
2752 request: "distribution",
2753 expected: package_name,
2754 actual: dist.name().clone(),
2755 });
2756 }
2757
2758 let response = match dist {
2759 ResolvedDist::Installable { dist, .. } => {
2760 let metadata = provider
2761 .get_or_build_wheel_metadata(&dist)
2762 .boxed_local()
2763 .await?;
2764
2765 Response::Dist {
2766 dist: (*dist).clone(),
2767 metadata,
2768 }
2769 }
2770 ResolvedDist::Installed { dist } => {
2771 let metadata =
2772 provider.get_installed_metadata(&dist).boxed_local().await?;
2773
2774 Response::Installed {
2775 dist: (*dist).clone(),
2776 metadata,
2777 }
2778 }
2779 };
2780
2781 Ok(Some(response))
2782 } else {
2783 Ok(None)
2784 }
2785 }
2786 }
2787 }
2788
2789 fn convert_no_solution_err(
2790 &self,
2791 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2792 fork_urls: ForkUrls,
2793 fork_indexes: ForkIndexes,
2794 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2795 env: ResolverEnvironment,
2796 current_environment: MarkerEnvironment,
2797 visited: &FxHashSet<PackageName>,
2798 ) -> ResolveError {
2799 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2800 err,
2801 ));
2802 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2803
2804 let mut unavailable_packages = FxHashMap::default();
2805 for package in derivation_tree_packages(&err) {
2806 if let PubGrubPackageInner::Package { name, .. } = &**package {
2807 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2808 unavailable_packages.insert(name.clone(), reason.clone());
2809 }
2810 }
2811 }
2812
2813 let mut incomplete_packages = FxHashMap::default();
2814 let incomplete_packages_cache = self.incomplete_packages.pin();
2815 for package in derivation_tree_packages(&err) {
2816 if let PubGrubPackageInner::Package { name, .. } = &**package
2817 && let Some(versions) = incomplete_packages_cache.get(name)
2818 {
2819 for (version, reason) in &versions.pin() {
2820 incomplete_packages
2821 .entry(name.clone())
2822 .or_insert_with(BTreeMap::default)
2823 .insert(version.clone(), reason.clone());
2824 }
2825 }
2826 }
2827
2828 let mut available_indexes = FxHashMap::default();
2829 let mut included_versions = FxHashMap::default();
2830 let mut available_versions = FxHashMap::default();
2831
2832 let available_version_cutoff: Option<jiff::Timestamp> =
2833 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2834 .ok()
2835 .and_then(|s| s.parse().ok());
2836
2837 for package in derivation_tree_packages(&err) {
2838 let Some(name) = package.name() else { continue };
2839 if !visited.contains(name) {
2840 continue;
2845 }
2846 let versions_response = if let Some(index) = fork_indexes.get(name) {
2847 self.index
2848 .explicit()
2849 .get(&(name.clone(), index.url().clone()))
2850 } else {
2851 self.index.implicit().get(name)
2852 };
2853 if let Some(response) = versions_response {
2854 if let VersionsResponse::Found(ref version_maps) = *response {
2855 for version_map in version_maps {
2857 let package_included_versions = included_versions
2858 .entry(name.clone())
2859 .or_insert_with(BTreeSet::new);
2860 let package_available_versions = available_versions
2861 .entry(name.clone())
2862 .or_insert_with(BTreeSet::new);
2863
2864 for (version, dists) in version_map.iter(&Ranges::full()) {
2865 let excluded_from_included = || {
2870 let Some(included_version_cutoff) =
2871 version_map.included_version_cutoff()
2872 else {
2873 return false;
2874 };
2875 let Some(prioritized_dist) = dists.prioritized_dist() else {
2876 return true;
2877 };
2878 prioritized_dist.files().all(|file| {
2879 file.upload_time_utc_ms.is_none_or(|upload_time| {
2880 upload_time >= included_version_cutoff.as_millisecond()
2881 })
2882 })
2883 };
2884
2885 if !excluded_from_included() {
2886 package_included_versions.insert(version.clone());
2887 }
2888
2889 let excluded_from_available = || {
2895 let Some(ref exclude_newer) = available_version_cutoff else {
2896 return false;
2897 };
2898 let Some(prioritized_dist) = dists.prioritized_dist() else {
2899 return false;
2900 };
2901 prioritized_dist.files().all(|file| {
2902 file.upload_time_utc_ms.is_some_and(|upload_time| {
2903 upload_time >= exclude_newer.as_millisecond()
2904 })
2905 })
2906 };
2907
2908 if !excluded_from_available() {
2909 package_available_versions.insert(version.clone());
2910 }
2911 }
2912 }
2913
2914 available_indexes
2916 .entry(name.clone())
2917 .or_insert(BTreeSet::new())
2918 .extend(
2919 version_maps
2920 .iter()
2921 .filter_map(|version_map| version_map.index().cloned()),
2922 );
2923 }
2924 }
2925 }
2926
2927 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2928 err,
2929 self.index.clone(),
2930 included_versions,
2931 available_versions,
2932 available_indexes,
2933 self.selector.clone(),
2934 self.python_requirement.clone(),
2935 self.locations.clone(),
2936 self.capabilities.clone(),
2937 unavailable_packages,
2938 incomplete_packages,
2939 fork_urls,
2940 fork_indexes,
2941 env,
2942 current_environment,
2943 self.tags.clone(),
2944 self.workspace_members.clone(),
2945 self.options.clone(),
2946 )))
2947 }
2948
2949 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2950 if let Some(reporter) = self.reporter.as_ref() {
2951 match &**package {
2952 PubGrubPackageInner::Root(_) => {}
2953 PubGrubPackageInner::Python(_) => {}
2954 PubGrubPackageInner::System(_) => {}
2955 PubGrubPackageInner::Marker { .. } => {}
2956 PubGrubPackageInner::Extra { .. } => {}
2957 PubGrubPackageInner::Group { .. } => {}
2958 PubGrubPackageInner::Package { name, .. } => {
2959 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2960 }
2961 }
2962 }
2963 }
2964
2965 fn on_complete(&self) {
2966 if let Some(reporter) = self.reporter.as_ref() {
2967 reporter.on_complete();
2968 }
2969 }
2970}
2971
2972#[derive(Clone)]
2974pub(crate) struct ForkState {
2975 pubgrub: State<UvDependencyProvider>,
2983 initial_id: Option<Id<PubGrubPackage>>,
2986 initial_version: Option<Version>,
2989 next: Id<PubGrubPackage>,
2991 pins: FilePins,
3000 fork_urls: ForkUrls,
3006 fork_indexes: ForkIndexes,
3011 priorities: PubGrubPriorities,
3017 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3020 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3022 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3024 known_versions: FxHashMap<PackageName, Arc<[Version]>>,
3029 env: ResolverEnvironment,
3044 python_requirement: PythonRequirement,
3057 conflict_tracker: ConflictTracker,
3058 prefetcher: BatchPrefetcher,
3062}
3063
3064impl ForkState {
3065 fn new(
3066 pubgrub: State<UvDependencyProvider>,
3067 env: ResolverEnvironment,
3068 python_requirement: PythonRequirement,
3069 prefetcher: BatchPrefetcher,
3070 ) -> Self {
3071 Self {
3072 initial_id: None,
3073 initial_version: None,
3074 next: pubgrub.root_package,
3075 pubgrub,
3076 pins: FilePins::default(),
3077 fork_urls: ForkUrls::default(),
3078 fork_indexes: ForkIndexes::default(),
3079 priorities: PubGrubPriorities::default(),
3080 added_dependencies: FxHashMap::default(),
3081 pre_visited: FxHashMap::default(),
3082 selected_versions: FxHashMap::default(),
3083 known_versions: FxHashMap::default(),
3084 env,
3085 python_requirement,
3086 conflict_tracker: ConflictTracker::default(),
3087 prefetcher,
3088 }
3089 }
3090
3091 fn visit_package_version_dependencies(
3094 &mut self,
3095 for_package: Id<PubGrubPackage>,
3096 for_version: &Version,
3097 urls: &Urls,
3098 indexes: &Indexes,
3099 dependencies: &[PubGrubDependency],
3100 git: &GitResolver,
3101 workspace_members: &BTreeSet<PackageName>,
3102 resolution_strategy: &ResolutionStrategy,
3103 ) -> Result<(), ResolveError> {
3104 for dependency in dependencies {
3105 let PubGrubDependency {
3106 package,
3107 version,
3108 parent: _,
3109 source,
3110 } = dependency;
3111
3112 let mut has_url = false;
3113 if let Some(name) = package.name() {
3114 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3119 self.fork_urls.insert(name, url, &self.env)?;
3120 has_url = true;
3121 }
3122
3123 if let Some(index) = source.explicit_index() {
3124 self.fork_indexes.insert(name, index, &self.env)?;
3125 }
3126
3127 for index in indexes.get(name, &self.env) {
3129 self.fork_indexes.insert(name, index, &self.env)?;
3130 }
3131 }
3132
3133 if let Some(name) = self.pubgrub.package_store[for_package]
3134 .name_no_root()
3135 .filter(|name| !workspace_members.contains(name))
3136 {
3137 debug!(
3138 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3139 );
3140 } else {
3141 debug!("Adding direct dependency: {package}{version}");
3143
3144 let missing_lower_bound = version
3146 .bounding_range()
3147 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3148 let strategy_lowest = matches!(
3149 resolution_strategy,
3150 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3151 );
3152
3153 if !has_url && missing_lower_bound && strategy_lowest {
3154 let name = package.name_no_root().unwrap();
3155 let bound_on_other_package = dependencies.iter().any(|other| {
3162 Some(name) == other.package.name()
3163 && !other
3164 .version
3165 .bounding_range()
3166 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3167 });
3168
3169 if !bound_on_other_package {
3170 warn_user_once!(
3171 "The direct dependency `{name}` is unpinned. \
3172 Consider setting a lower bound when using `--resolution lowest` \
3173 or `--resolution lowest-direct` to avoid using outdated versions.",
3174 );
3175 }
3176 }
3177 }
3178
3179 self.priorities.insert(package, version, &self.fork_urls);
3181 if let Some(base_package) = package.base_package() {
3184 self.priorities
3185 .insert(&base_package, version, &self.fork_urls);
3186 }
3187 }
3188
3189 Ok(())
3190 }
3191
3192 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3198 &mut self,
3199 for_package: Id<PubGrubPackage>,
3200 for_version: &Version,
3201 dependencies: Vec<PubGrubDependency>,
3202 index: &InMemoryIndex,
3203 installed_packages: &InstalledPackages,
3204 ) {
3205 for dependency in &dependencies {
3206 let PubGrubDependency {
3207 package,
3208 version,
3209 parent: _,
3210 source: _,
3211 } = dependency;
3212
3213 let Some(base_package) = package.base_package() else {
3214 continue;
3215 };
3216
3217 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3218 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3219 self.pubgrub.add_proxy_package_incompatibility(
3220 proxy_package,
3221 base_package_id,
3222 version.clone(),
3223 );
3224 }
3225
3226 let versions = Range::singleton(for_version.clone());
3229 let versions = if let Some(known_versions) =
3230 ResolverState::<InstalledPackages>::known_versions(
3231 index,
3232 installed_packages,
3233 &self.fork_urls,
3234 &self.fork_indexes,
3235 &mut self.known_versions,
3236 &self.pubgrub.package_store[self.next],
3237 )
3238 .filter(|versions| !versions.is_empty())
3239 {
3240 versions.widen_versions(known_versions)
3241 } else {
3242 versions
3245 };
3246 let conflict = self.pubgrub.add_package_version_dependencies(
3247 self.next,
3248 for_version.clone(),
3249 versions,
3250 dependencies.into_iter().map(|dependency| {
3251 let PubGrubDependency {
3252 package,
3253 version,
3254 parent: _,
3255 source: _,
3256 } = dependency;
3257 (package, version)
3258 }),
3259 );
3260
3261 if let Some(incompatibility) = conflict {
3264 self.record_conflict(for_package, Some(for_version), incompatibility);
3265 }
3266 }
3267
3268 fn record_conflict(
3269 &mut self,
3270 affected: Id<PubGrubPackage>,
3271 version: Option<&Version>,
3272 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3273 ) {
3274 let mut culprit_is_real = false;
3275 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3276 if incompatible == affected {
3277 continue;
3278 }
3279 if self.pubgrub.package_store[affected].name()
3280 == self.pubgrub.package_store[incompatible].name()
3281 {
3282 continue;
3285 }
3286 culprit_is_real = true;
3287 let culprit_count = self
3288 .conflict_tracker
3289 .culprit
3290 .entry(incompatible)
3291 .or_default();
3292 *culprit_count += 1;
3293 if *culprit_count == CONFLICT_THRESHOLD {
3294 self.conflict_tracker.deprioritize.push(incompatible);
3295 }
3296 }
3297 if culprit_is_real {
3300 if tracing::enabled!(Level::DEBUG) {
3301 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3302 .iter()
3303 .map(|(package, _term)| &self.pubgrub.package_store[package])
3304 .join(", ");
3305 if let Some(version) = version {
3306 debug!(
3307 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3308 self.pubgrub.package_store[affected], version, incompatibility
3309 );
3310 } else {
3311 debug!(
3312 "Recording unit propagation conflict of {} from incompatibility of ({})",
3313 self.pubgrub.package_store[affected], incompatibility
3314 );
3315 }
3316 }
3317
3318 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3319 *affected_count += 1;
3320 if *affected_count == CONFLICT_THRESHOLD {
3321 self.conflict_tracker.prioritize.push(self.next);
3322 }
3323 }
3324 }
3325
3326 fn reprioritize_conflicts(&mut self) {
3330 for package in self.conflict_tracker.prioritize.drain(..) {
3331 let changed = self
3332 .priorities
3333 .mark_conflict_early(&self.pubgrub.package_store[package]);
3334 if changed {
3335 debug!(
3336 "Package {} has too many conflicts (affected), prioritizing",
3337 &self.pubgrub.package_store[package]
3338 );
3339 } else {
3340 debug!(
3341 "Package {} has too many conflicts (affected), already {:?}",
3342 self.pubgrub.package_store[package],
3343 self.priorities.get(&self.pubgrub.package_store[package])
3344 );
3345 }
3346 }
3347
3348 for package in self.conflict_tracker.deprioritize.drain(..) {
3349 let changed = self
3350 .priorities
3351 .mark_conflict_late(&self.pubgrub.package_store[package]);
3352 if changed {
3353 debug!(
3354 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3355 self.pubgrub.package_store[package],
3356 );
3357 let backtrack_level = self.pubgrub.backtrack_package(package);
3358 if let Some(backtrack_level) = backtrack_level {
3359 debug!("Backtracked {backtrack_level} decisions");
3360 } else {
3361 debug!(
3362 "Package {} is not decided, cannot backtrack",
3363 self.pubgrub.package_store[package]
3364 );
3365 }
3366 } else {
3367 debug!(
3368 "Package {} has too many conflicts (culprit), already {:?}",
3369 self.pubgrub.package_store[package],
3370 self.priorities.get(&self.pubgrub.package_store[package])
3371 );
3372 }
3373 }
3374 }
3375
3376 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3377 if let UnavailableVersion::IncompatibleDist(
3381 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3382 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3383 ) = reason
3384 {
3385 let package = &self.next;
3386 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3387 PubGrubPackageInner::Python(match kind {
3388 PythonRequirementKind::Installed => PubGrubPython::Installed,
3389 PythonRequirementKind::Target => PubGrubPython::Target,
3390 }),
3391 ));
3392 self.pubgrub
3393 .add_incompatibility(Incompatibility::from_dependency(
3394 *package,
3395 Range::singleton(version.clone()),
3396 (
3397 python,
3398 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3399 ),
3400 ));
3401 self.pubgrub
3402 .partial_solution
3403 .add_decision(self.next, version);
3404 return;
3405 }
3406 self.pubgrub
3407 .add_incompatibility(Incompatibility::custom_version(
3408 self.next,
3409 version.clone(),
3410 UnavailableReason::Version(reason),
3411 ));
3412 }
3413
3414 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3420 self.selected_versions.clear();
3421 self.env = env;
3422 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3424 debug!("Narrowed `requires-python` bound to: {}", req.target());
3425 self.python_requirement = req;
3426 }
3427 self
3428 }
3429
3430 fn source(
3434 &self,
3435 name: &PackageName,
3436 version: &Version,
3437 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3438 let url = self.fork_urls.get(name);
3439 let index = url
3440 .is_none()
3441 .then(|| {
3442 self.pins
3443 .get(name, version)
3444 .expect("Every package should be pinned")
3445 .index()
3446 })
3447 .flatten();
3448 (url, index)
3449 }
3450
3451 fn into_resolution(self) -> Resolution {
3452 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3453 let edge_count: usize = solution
3454 .keys()
3455 .map(|package| self.pubgrub.incompatibilities[package].len())
3456 .sum();
3457 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3458 for (package, self_version) in &solution {
3459 for id in &self.pubgrub.incompatibilities[package] {
3460 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3461 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3462 &incompatibility.kind
3463 else {
3464 continue;
3465 };
3466 let (self_package, dependency_package) = (*self_package, *dependency_package);
3467 let Some((self_range, dependency_range)) =
3468 incompatibility.dependency_version_sets()
3469 else {
3470 continue;
3471 };
3472 let dependency_range =
3473 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3474 if *package != self_package {
3475 continue;
3476 }
3477 if !self_range.contains(self_version) {
3478 continue;
3479 }
3480 let Some(dependency_version) = solution.get(&dependency_package) else {
3481 continue;
3482 };
3483 if !dependency_range.contains(dependency_version) {
3484 continue;
3485 }
3486
3487 let self_package = &self.pubgrub.package_store[self_package];
3488 let dependency_package = &self.pubgrub.package_store[dependency_package];
3489
3490 let (self_name, self_extra, self_group) = match &**self_package {
3491 PubGrubPackageInner::Package {
3492 name: self_name,
3493 extra: self_extra,
3494 group: self_group,
3495 marker: _,
3496 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3497
3498 PubGrubPackageInner::Root(_) => (None, None, None),
3499
3500 _ => continue,
3501 };
3502
3503 let (self_url, self_index) = self_name
3504 .map(|self_name| self.source(self_name, self_version))
3505 .unwrap_or((None, None));
3506
3507 match **dependency_package {
3508 PubGrubPackageInner::Package {
3509 name: ref dependency_name,
3510 extra: ref dependency_extra,
3511 group: ref dependency_dev,
3512 marker: ref dependency_marker,
3513 } => {
3514 debug_assert!(
3515 dependency_extra.is_none(),
3516 "Packages should depend on an extra proxy"
3517 );
3518 debug_assert!(
3519 dependency_dev.is_none(),
3520 "Packages should depend on a group proxy"
3521 );
3522
3523 if self_group.is_none() {
3526 if self_name == Some(dependency_name) {
3527 continue;
3528 }
3529 }
3530
3531 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3532
3533 let edge = ResolutionDependencyEdge {
3534 from: self_name.cloned(),
3535 from_version: self_version.clone(),
3536 from_url: self_url.cloned(),
3537 from_index: self_index.cloned(),
3538 from_extra: self_extra.cloned(),
3539 from_group: self_group.cloned(),
3540 to: dependency_name.clone(),
3541 to_version: dependency_version.clone(),
3542 to_url: to_url.cloned(),
3543 to_index: to_index.cloned(),
3544 to_extra: dependency_extra.clone(),
3545 to_group: dependency_dev.clone(),
3546 marker: *dependency_marker,
3547 };
3548 edges.push(edge);
3549 }
3550
3551 PubGrubPackageInner::Marker {
3552 name: ref dependency_name,
3553 marker: ref dependency_marker,
3554 } => {
3555 if self_group.is_none() {
3558 if self_name == Some(dependency_name) {
3559 continue;
3560 }
3561 }
3562
3563 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3564
3565 let edge = ResolutionDependencyEdge {
3566 from: self_name.cloned(),
3567 from_version: self_version.clone(),
3568 from_url: self_url.cloned(),
3569 from_index: self_index.cloned(),
3570 from_extra: self_extra.cloned(),
3571 from_group: self_group.cloned(),
3572 to: dependency_name.clone(),
3573 to_version: dependency_version.clone(),
3574 to_url: to_url.cloned(),
3575 to_index: to_index.cloned(),
3576 to_extra: None,
3577 to_group: None,
3578 marker: *dependency_marker,
3579 };
3580 edges.push(edge);
3581 }
3582
3583 PubGrubPackageInner::Extra {
3584 name: ref dependency_name,
3585 extra: ref dependency_extra,
3586 marker: ref dependency_marker,
3587 } => {
3588 if self_group.is_none() {
3589 debug_assert!(
3590 self_name != Some(dependency_name),
3591 "Extras should be flattened"
3592 );
3593 }
3594 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3595
3596 let edge = ResolutionDependencyEdge {
3598 from: self_name.cloned(),
3599 from_version: self_version.clone(),
3600 from_url: self_url.cloned(),
3601 from_index: self_index.cloned(),
3602 from_extra: self_extra.cloned(),
3603 from_group: self_group.cloned(),
3604 to: dependency_name.clone(),
3605 to_version: dependency_version.clone(),
3606 to_url: to_url.cloned(),
3607 to_index: to_index.cloned(),
3608 to_extra: Some(dependency_extra.clone()),
3609 to_group: None,
3610 marker: *dependency_marker,
3611 };
3612 edges.push(edge);
3613
3614 let edge = ResolutionDependencyEdge {
3616 from: self_name.cloned(),
3617 from_version: self_version.clone(),
3618 from_url: self_url.cloned(),
3619 from_index: self_index.cloned(),
3620 from_extra: self_extra.cloned(),
3621 from_group: self_group.cloned(),
3622 to: dependency_name.clone(),
3623 to_version: dependency_version.clone(),
3624 to_url: to_url.cloned(),
3625 to_index: to_index.cloned(),
3626 to_extra: None,
3627 to_group: None,
3628 marker: *dependency_marker,
3629 };
3630 edges.push(edge);
3631 }
3632
3633 PubGrubPackageInner::Group {
3634 name: ref dependency_name,
3635 group: ref dependency_group,
3636 marker: ref dependency_marker,
3637 } => {
3638 debug_assert!(
3639 self_name != Some(dependency_name),
3640 "Groups should be flattened"
3641 );
3642
3643 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3644
3645 let edge = ResolutionDependencyEdge {
3648 from: self_name.cloned(),
3649 from_version: self_version.clone(),
3650 from_url: self_url.cloned(),
3651 from_index: self_index.cloned(),
3652 from_extra: self_extra.cloned(),
3653 from_group: self_group.cloned(),
3654 to: dependency_name.clone(),
3655 to_version: dependency_version.clone(),
3656 to_url: to_url.cloned(),
3657 to_index: to_index.cloned(),
3658 to_extra: None,
3659 to_group: Some(dependency_group.clone()),
3660 marker: *dependency_marker,
3661 };
3662 edges.push(edge);
3663 }
3664
3665 _ => {}
3666 }
3667 }
3668 }
3669
3670 let nodes = solution
3671 .into_iter()
3672 .filter_map(|(package, version)| {
3673 if let PubGrubPackageInner::Package {
3674 name,
3675 extra,
3676 group,
3677 marker: MarkerTree::TRUE,
3678 } = &*self.pubgrub.package_store[package]
3679 {
3680 let (url, index) = self.source(name, &version);
3681 Some((
3682 ResolutionPackage {
3683 name: name.clone(),
3684 extra: extra.clone(),
3685 dev: group.clone(),
3686 url: url.cloned(),
3687 index: index.cloned(),
3688 },
3689 version,
3690 ))
3691 } else {
3692 None
3693 }
3694 })
3695 .collect();
3696
3697 Resolution {
3698 nodes,
3699 edges,
3700 pins: self.pins,
3701 env: self.env,
3702 }
3703 }
3704}
3705
3706#[derive(Debug)]
3708#[expect(clippy::large_enum_variant)]
3709pub(crate) enum Request {
3710 Package(PackageName, Option<IndexMetadata>),
3712 Dist(Dist),
3714 Installed(InstalledDist),
3716 Prefetch(PackageName, Range<Version>, PythonRequirement),
3718}
3719
3720impl<'a> From<ResolvedDistRef<'a>> for Request {
3721 fn from(dist: ResolvedDistRef<'a>) -> Self {
3722 match dist {
3728 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3729 let source = prioritized.source_dist().expect("a source distribution");
3732 assert_eq!(
3733 (&sdist.name, &sdist.version),
3734 (&source.name, &source.version),
3735 "expected chosen sdist to match prioritized sdist"
3736 );
3737 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3738 }
3739 ResolvedDistRef::InstallableRegistryBuiltDist {
3740 wheel, prioritized, ..
3741 } => {
3742 assert_eq!(
3743 Some(&wheel.filename),
3744 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3745 "expected chosen wheel to match best wheel"
3746 );
3747 let built = prioritized.built_dist().expect("at least one wheel");
3750 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3751 }
3752 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3753 }
3754 }
3755}
3756
3757impl Display for Request {
3758 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3759 match self {
3760 Self::Package(package_name, _) => {
3761 write!(f, "Versions {package_name}")
3762 }
3763 Self::Dist(dist) => {
3764 write!(f, "Metadata {dist}")
3765 }
3766 Self::Installed(dist) => {
3767 write!(f, "Installed metadata {dist}")
3768 }
3769 Self::Prefetch(package_name, range, _) => {
3770 write!(f, "Prefetch {package_name} {range}")
3771 }
3772 }
3773 }
3774}
3775
3776#[derive(Debug)]
3777#[expect(clippy::large_enum_variant)]
3778enum Response {
3779 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3781 Dist {
3783 dist: Dist,
3784 metadata: MetadataResponse,
3785 },
3786 Installed {
3788 dist: InstalledDist,
3789 metadata: MetadataResponse,
3790 },
3791}
3792
3793enum Dependencies {
3799 Unavailable(UnavailableVersion),
3801 Available(Vec<PubGrubDependency>),
3807 RequiresPython(VersionSpecifiers),
3809 Unforkable(Vec<PubGrubDependency>),
3815}
3816
3817#[derive(Debug)]
3824enum ForkedDependencies {
3825 Unavailable(UnavailableVersion),
3827 Unforked(Vec<PubGrubDependency>),
3831 Forked {
3837 forks: Vec<Fork>,
3838 diverging_packages: BTreeSet<PackageName>,
3840 },
3841 RequiresPython(VersionSpecifiers),
3843}
3844
3845impl ForkedDependencies {
3846 fn from_dependencies_universal(
3853 dependencies: Dependencies,
3854 env: &ResolverEnvironment,
3855 python_requirement: &PythonRequirement,
3856 conflicts: &Conflicts,
3857 ) -> Self {
3858 let deps = match dependencies {
3859 Dependencies::Available(deps) => deps,
3860 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3861 Dependencies::RequiresPython(requires_python) => {
3862 return Self::RequiresPython(requires_python);
3863 }
3864 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3865 };
3866 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3867 for dep in deps {
3868 let name = dep
3869 .package
3870 .name()
3871 .expect("dependency always has a name")
3872 .clone();
3873 name_to_deps.entry(name).or_default().push(dep);
3874 }
3875 let (mut forks, diverging_packages) =
3876 Self::fork(name_to_deps, env, python_requirement, conflicts);
3877 if forks.is_empty() {
3878 Self::Unforked(vec![])
3879 } else if forks.len() == 1 {
3880 Self::Unforked(forks.pop().unwrap().dependencies)
3881 } else {
3882 Self::Forked {
3883 forks,
3884 diverging_packages,
3885 }
3886 }
3887 }
3888
3889 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3892 match dependencies {
3893 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3894 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3895 Dependencies::Unavailable(err) => Self::Unavailable(err),
3896 }
3897 }
3898
3899 fn fork(
3907 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3908 env: &ResolverEnvironment,
3909 python_requirement: &PythonRequirement,
3910 conflicts: &Conflicts,
3911 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3912 let python_marker = python_requirement.to_marker_tree();
3913
3914 let mut forks = vec![Fork::new(env.clone())];
3915 let mut diverging_packages = BTreeSet::new();
3916 for (name, mut deps) in name_to_deps {
3917 assert!(!deps.is_empty(), "every name has at least one dependency");
3918 if let [dep] = deps.as_slice() {
3929 if marker::requires_python(dep.package.marker())
3937 .is_none_or(|bound| !python_requirement.raises(&bound))
3938 {
3939 let dep = deps.pop().unwrap();
3940 let marker = dep.package.marker();
3941 for fork in &mut forks {
3942 if fork.env.included_by_marker(marker) {
3943 fork.add_dependency(dep.clone());
3944 }
3945 }
3946 continue;
3947 }
3948 } else {
3949 if let Some(dep) = deps.first() {
3951 let marker = dep.package.marker();
3952 if deps.iter().all(|dep| marker == dep.package.marker()) {
3953 if marker::requires_python(marker)
3957 .is_none_or(|bound| !python_requirement.raises(&bound))
3958 {
3959 for dep in deps {
3960 for fork in &mut forks {
3961 if fork.env.included_by_marker(marker) {
3962 fork.add_dependency(dep.clone());
3963 }
3964 }
3965 }
3966 continue;
3967 }
3968 }
3969 }
3970 }
3971 for dep in deps {
3972 let mut forker = match ForkingPossibility::new(env, &dep) {
3973 ForkingPossibility::Possible(forker) => forker,
3974 ForkingPossibility::DependencyAlwaysExcluded => {
3975 continue;
3978 }
3979 ForkingPossibility::NoForkingPossible => {
3980 for fork in &mut forks {
3983 fork.add_dependency(dep.clone());
3984 }
3985 continue;
3986 }
3987 };
3988 diverging_packages.insert(name.clone());
3990
3991 let mut new = vec![];
3992 for fork in std::mem::take(&mut forks) {
3993 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3994 new.push(fork);
3995 continue;
3996 };
3997 forker = remaining_forker;
3998
3999 for fork_env in envs {
4000 let mut new_fork = fork.clone();
4001 new_fork.set_env(fork_env);
4002 if forker.included(&new_fork.env) {
4007 new_fork.add_dependency(dep.clone());
4008 }
4009 if new_fork.env.included_by_marker(python_marker) {
4012 new.push(new_fork);
4013 }
4014 }
4015 }
4016 forks = new;
4017 }
4018 }
4019 for set in conflicts.iter() {
4034 let mut new = vec![];
4035 for fork in std::mem::take(&mut forks) {
4036 let mut has_conflicting_dependency = false;
4044 for item in set.iter() {
4045 if fork.contains_conflicting_item(item.as_ref()) {
4046 has_conflicting_dependency = true;
4047 diverging_packages.insert(item.package().clone());
4048 break;
4049 }
4050 }
4051 if !has_conflicting_dependency {
4052 new.push(fork);
4053 continue;
4054 }
4055
4056 let non_excluded: Vec<_> = set
4062 .iter()
4063 .filter(|item| fork.env.included_by_group(item.as_ref()))
4064 .collect();
4065 if non_excluded.len() < 2 {
4066 let dominated = non_excluded.iter().all(|item| {
4071 !conflicts.iter().any(|other_set| {
4072 !std::ptr::eq(set, other_set)
4073 && other_set.contains(item.package(), item.kind().as_ref())
4074 && other_set
4075 .iter()
4076 .filter(|other_item| {
4077 other_item.package() != item.package()
4078 || other_item.kind() != item.kind()
4079 })
4080 .any(|other_item| {
4081 fork.env.included_by_group(other_item.as_ref())
4082 })
4083 })
4084 });
4085 if dominated {
4086 let rules: Vec<_> = set
4091 .iter()
4092 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4093 .cloned()
4094 .map(Err)
4095 .collect();
4096 if let Some(filtered) = fork.filter(rules) {
4097 new.push(filtered);
4098 }
4099 continue;
4100 }
4101 }
4102
4103 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4105 new.push(fork_none);
4106 }
4107
4108 for (i, _) in set.iter().enumerate() {
4116 let fork_allows_group = fork.clone().filter(
4117 set.iter()
4118 .cloned()
4119 .enumerate()
4120 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4121 );
4122 if let Some(fork_allows_group) = fork_allows_group {
4123 new.push(fork_allows_group);
4124 }
4125 }
4126 }
4127 forks = new;
4128 }
4129 (forks, diverging_packages)
4130 }
4131}
4132
4133#[derive(Clone, Debug)]
4143struct Fork {
4144 dependencies: Vec<PubGrubDependency>,
4153 conflicts: crate::FxHashbrownSet<ConflictItem>,
4159 env: ResolverEnvironment,
4171}
4172
4173impl Fork {
4174 fn new(env: ResolverEnvironment) -> Self {
4177 Self {
4178 dependencies: vec![],
4179 conflicts: crate::FxHashbrownSet::default(),
4180 env,
4181 }
4182 }
4183
4184 fn add_dependency(&mut self, dep: PubGrubDependency) {
4186 if let Some(conflicting_item) = dep.conflicting_item() {
4187 self.conflicts.insert(conflicting_item.to_owned());
4188 }
4189 self.dependencies.push(dep);
4190 }
4191
4192 fn set_env(&mut self, env: ResolverEnvironment) {
4197 self.env = env;
4198 self.dependencies.retain(|dep| {
4199 let marker = dep.package.marker();
4200 if self.env.included_by_marker(marker) {
4201 return true;
4202 }
4203 if let Some(conflicting_item) = dep.conflicting_item() {
4204 self.conflicts.remove(&conflicting_item);
4205 }
4206 false
4207 });
4208 }
4209
4210 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4213 self.conflicts.contains(&item)
4214 }
4215
4216 fn filter(
4223 mut self,
4224 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4225 ) -> Option<Self> {
4226 self.env = self.env.filter_by_group(rules)?;
4227 self.dependencies.retain(|dep| {
4228 let Some(conflicting_item) = dep.conflicting_item() else {
4229 return true;
4230 };
4231 if self.env.included_by_group(conflicting_item) {
4232 return true;
4233 }
4234 match conflicting_item.kind() {
4235 ConflictKindRef::Project => {
4238 if dep.parent.is_some() {
4239 return true;
4240 }
4241 }
4242 ConflictKindRef::Group(_) => {}
4243 ConflictKindRef::Extra(_) => {}
4244 }
4245 self.conflicts.remove(&conflicting_item);
4246 false
4247 });
4248 Some(self)
4249 }
4250
4251 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4253 let self_bound = self.env.requires_python().unwrap_or_default();
4263 let other_bound = other.env.requires_python().unwrap_or_default();
4264 self_bound.lower().cmp(other_bound.lower())
4265 }
4266
4267 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4269 let self_upper_bounds = self
4274 .dependencies
4275 .iter()
4276 .filter(|dep| {
4277 dep.version
4278 .bounding_range()
4279 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4280 })
4281 .count();
4282 let other_upper_bounds = other
4283 .dependencies
4284 .iter()
4285 .filter(|dep| {
4286 dep.version
4287 .bounding_range()
4288 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4289 })
4290 .count();
4291
4292 self_upper_bounds.cmp(&other_upper_bounds)
4293 }
4294}
4295
4296impl Eq for Fork {}
4297
4298impl PartialEq for Fork {
4299 fn eq(&self, other: &Self) -> bool {
4300 self.dependencies == other.dependencies && self.env == other.env
4301 }
4302}
4303
4304#[derive(Debug, Clone)]
4305pub(crate) struct VersionFork {
4306 env: ResolverEnvironment,
4308 id: Id<PubGrubPackage>,
4310 version: Option<Version>,
4312}
4313
4314fn enrich_dependency_error(
4316 error: ResolveError,
4317 id: Id<PubGrubPackage>,
4318 version: &Version,
4319 pubgrub: &State<UvDependencyProvider>,
4320) -> ResolveError {
4321 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4322 return error;
4323 };
4324 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4325 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4326}
4327
4328fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4330 let package = &state.package_store[id];
4331 if package.is_root() {
4332 return MarkerTree::TRUE;
4333 }
4334
4335 let mut ancestors = FxHashSet::default();
4338 let mut stack = vec![id];
4339 let mut root = None;
4340 ancestors.insert(id);
4341
4342 while let Some(current) = stack.pop() {
4343 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4344 continue;
4345 };
4346
4347 for index in incompatibilities {
4348 let incompat = &state.incompatibility_store[*index];
4349 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4350 if current != *child {
4351 continue;
4352 }
4353 if ancestors.insert(*parent) {
4354 if state.package_store[*parent].is_root() {
4355 root = Some(*parent);
4356 }
4357 stack.push(*parent);
4358 }
4359 }
4360 }
4361 }
4362
4363 let Some(root) = root else {
4364 return MarkerTree::FALSE;
4365 };
4366
4367 let mut environments = FxHashMap::default();
4370 let mut queue = VecDeque::from([root]);
4371 environments.insert(root, MarkerTree::TRUE);
4372
4373 while let Some(current) = queue.pop_front() {
4374 let Some(current_environment) = environments.get(¤t).copied() else {
4375 continue;
4376 };
4377 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4378 continue;
4379 };
4380
4381 for index in incompatibilities {
4382 let incompat = &state.incompatibility_store[*index];
4383 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4384 continue;
4385 };
4386 if current != *parent || !ancestors.contains(child) {
4387 continue;
4388 }
4389
4390 let mut next_environment = state.package_store[*child].marker();
4391 next_environment = next_environment.and(current_environment);
4392
4393 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4394 let mut combined = *entry;
4395 combined = combined.or(next_environment);
4396 if combined != *entry {
4397 *entry = combined;
4398 queue.push_back(*child);
4399 }
4400 }
4401 }
4402
4403 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4404}
4405
4406#[derive(Debug, Default, Clone)]
4407struct ConflictTracker {
4408 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4410 prioritize: Vec<Id<PubGrubPackage>>,
4414 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4416 deprioritize: Vec<Id<PubGrubPackage>>,
4420}