1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter, Write};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{iter, slice, thread};
11
12use either::Either;
13use futures::{FutureExt, StreamExt};
14use itertools::Itertools;
15use papaya::{HashMap, ResizeMode};
16use pubgrub::{Id, IncompId, Incompatibility, Kind, 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::{ConflictMarker, UniversalMarker};
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84
85mod availability;
86mod batch_prefetch;
87mod derivation;
88mod environment;
89mod fork_map;
90mod index;
91mod indexes;
92mod provider;
93mod reporter;
94mod system;
95mod urls;
96
97const CONFLICT_THRESHOLD: usize = 5;
99
100pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
101 state: ResolverState<InstalledPackages>,
102 provider: Provider,
103}
104
105struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
108 project: Option<PackageName>,
109 requirements: Vec<Requirement>,
110 constraints: Constraints,
111 overrides: Overrides,
112 excludes: Excludes,
113 preferences: Preferences,
114 git: GitResolver,
115 capabilities: IndexCapabilities,
116 locations: IndexLocations,
117 exclusions: Exclusions,
118 urls: Urls,
119 indexes: Indexes,
120 dependency_mode: DependencyMode,
121 hasher: HashStrategy,
122 env: ResolverEnvironment,
123 current_environment: MarkerEnvironment,
125 tags: Option<Tags>,
126 python_requirement: PythonRequirement,
127 conflicts: Conflicts,
128 workspace_members: BTreeSet<PackageName>,
129 selector: CandidateSelector,
130 index: InMemoryIndex,
131 installed_packages: InstalledPackages,
132 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
135 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
137 options: Options,
139 reporter: Option<Arc<dyn Reporter>>,
141}
142
143impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
144 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
145{
146 pub fn new(
165 manifest: Manifest,
166 options: Options,
167 python_requirement: &'a PythonRequirement,
168 env: ResolverEnvironment,
169 current_environment: &MarkerEnvironment,
170 conflicts: Conflicts,
171 tags: Option<&'a Tags>,
172 flat_index: &'a FlatIndex,
173 index: &'a InMemoryIndex,
174 hasher: &'a HashStrategy,
175 build_context: &'a Context,
176 installed_packages: InstalledPackages,
177 database: DistributionDatabase<'a, Context>,
178 ) -> Result<Self, ResolveError> {
179 let provider = DefaultResolverProvider::new(
180 database,
181 flat_index,
182 tags,
183 python_requirement.target(),
184 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
185 hasher,
186 options.exclude_newer.clone(),
187 build_context.locations(),
188 build_context.build_options(),
189 build_context.capabilities(),
190 );
191
192 Ok(Self::new_custom_io(
193 manifest,
194 options,
195 hasher,
196 env,
197 current_environment,
198 tags.cloned(),
199 python_requirement,
200 conflicts,
201 index,
202 build_context.git(),
203 build_context.capabilities(),
204 build_context.locations(),
205 provider,
206 installed_packages,
207 ))
208 }
209}
210
211impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
212 Resolver<Provider, InstalledPackages>
213{
214 pub fn new_custom_io(
216 manifest: Manifest,
217 options: Options,
218 hasher: &HashStrategy,
219 env: ResolverEnvironment,
220 current_environment: &MarkerEnvironment,
221 tags: Option<Tags>,
222 python_requirement: &PythonRequirement,
223 conflicts: Conflicts,
224 index: &InMemoryIndex,
225 git: &GitResolver,
226 capabilities: &IndexCapabilities,
227 locations: &IndexLocations,
228 provider: Provider,
229 installed_packages: InstalledPackages,
230 ) -> Self {
231 let state = ResolverState {
232 index: index.clone(),
233 git: git.clone(),
234 capabilities: capabilities.clone(),
235 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
236 dependency_mode: options.dependency_mode,
237 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
238 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
239 project: manifest.project,
240 workspace_members: manifest.workspace_members,
241 requirements: manifest.requirements,
242 constraints: manifest.constraints,
243 overrides: manifest.overrides,
244 excludes: manifest.excludes,
245 preferences: manifest.preferences,
246 exclusions: manifest.exclusions,
247 hasher: hasher.clone(),
248 locations: locations.clone(),
249 env,
250 current_environment: current_environment.clone(),
251 tags,
252 python_requirement: python_requirement.clone(),
253 conflicts,
254 installed_packages,
255 unavailable_packages: Box::default(),
256 incomplete_packages: Box::default(),
257 options,
258 reporter: None,
259 };
260 Self { state, provider }
261 }
262
263 #[must_use]
265 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
266 Self {
267 state: ResolverState {
268 reporter: Some(reporter.clone()),
269 ..self.state
270 },
271 provider: self
272 .provider
273 .with_reporter(reporter.into_distribution_reporter()),
274 }
275 }
276
277 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
279 let state = Arc::new(self.state);
280 let provider = Arc::new(self.provider);
281
282 let (request_sink, request_stream) = mpsc::channel(300);
286
287 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
289
290 let solver = state.clone();
292 let (tx, rx) = oneshot::channel();
293 thread::Builder::new()
294 .name("uv-resolver".into())
295 .spawn(move || {
296 let result = solver.solve(&request_sink);
297
298 let _ = tx.send(result);
300 })
301 .unwrap();
302
303 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
304
305 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
307
308 state.on_complete();
309 resolution
310 }
311}
312
313impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
314 #[instrument(skip_all)]
315 fn solve(
316 self: Arc<Self>,
317 request_sink: &Sender<Request>,
318 ) -> Result<ResolverOutput, ResolveError> {
319 debug!(
320 "Solving with installed Python version: {}",
321 self.python_requirement.exact()
322 );
323 debug!(
324 "Solving with target Python version: {}",
325 self.python_requirement.target()
326 );
327 if !self.options.exclude_newer.is_empty() {
328 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
329 }
330
331 let mut visited = FxHashSet::default();
332
333 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
334 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
335 let prefetcher = BatchPrefetcher::new(
336 self.capabilities.clone(),
337 self.index.clone(),
338 request_sink.clone(),
339 );
340 let state = ForkState::new(
341 pubgrub,
342 self.env.clone(),
343 self.python_requirement.clone(),
344 prefetcher,
345 );
346 let mut preferences = self.preferences.clone();
347 let mut forked_states = self.env.initial_forked_states(state)?;
348 let mut resolutions = vec![];
349
350 'FORK: while let Some(mut state) = forked_states.pop() {
351 if let Some(split) = state.env.end_user_fork_display() {
352 let requires_python = state.python_requirement.target();
353 debug!("Solving {split} (requires-python: {requires_python:?})");
354 }
355 let start = Instant::now();
356 loop {
357 let highest_priority_pkg =
358 if let Some(initial) = state.initial_id.take() {
359 initial
363 } else {
364 let result = state.pubgrub.unit_propagation(state.next);
366 match result {
367 Err(err) => {
368 return Err(self.convert_no_solution_err(
370 err,
371 state.fork_urls,
372 state.fork_indexes,
373 &state.known_versions,
374 state.env,
375 self.current_environment.clone(),
376 &visited,
377 ));
378 }
379 Ok(conflicts) => {
380 for (affected, incompatibility) in conflicts {
381 state.record_conflict(affected, None, incompatibility);
384 }
385 }
386 }
387
388 if self.dependency_mode.is_transitive() {
390 Self::pre_visit(
391 state.pubgrub.partial_solution.prioritized_packages().map(
392 |(id, range)| (id, &state.pubgrub.package_store[id], range),
393 ),
394 &mut state.pre_visited,
395 &self.urls,
396 &self.indexes,
397 &state.python_requirement,
398 request_sink,
399 )?;
400 }
401
402 Self::reprioritize_conflicts(&mut state);
403
404 trace!(
405 "Assigned packages: {}",
406 state
407 .pubgrub
408 .partial_solution
409 .extract_solution()
410 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
411 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
412 .join(", ")
413 );
414 let Some((highest_priority_pkg, _)) =
418 state.pubgrub.partial_solution.pick_highest_priority_pkg(
419 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
420 )
421 else {
422 if tracing::enabled!(Level::DEBUG) {
424 state.prefetcher.log_tried_versions();
425 }
426 debug!(
427 "{} resolution took {:.3}s",
428 state.env,
429 start.elapsed().as_secs_f32()
430 );
431
432 let resolution = state.into_resolution();
433
434 if matches!(
443 self.options.resolution_mode,
444 ResolutionMode::Lowest | ResolutionMode::Highest
445 ) {
446 let marker = resolution
447 .env
448 .try_universal_markers()
449 .unwrap_or(UniversalMarker::TRUE);
450 for (package, version) in &resolution.nodes {
451 preferences.insert(
452 package.name.clone(),
453 package.index.clone(),
454 marker,
455 version.clone(),
456 PreferenceSource::Resolver,
457 );
458 }
459 }
460
461 resolutions.push(resolution);
462 continue 'FORK;
463 };
464 trace!(
465 "Chose package for decision: {}. remaining choices: {}",
466 state.pubgrub.package_store[highest_priority_pkg],
467 state
468 .pubgrub
469 .partial_solution
470 .undecided_packages()
471 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
472 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
473 .join(", ")
474 );
475
476 highest_priority_pkg
477 };
478
479 state.next = highest_priority_pkg;
480
481 let next_id = state.next;
483 let next_package = &state.pubgrub.package_store[state.next];
484
485 let url = next_package
486 .name()
487 .and_then(|name| state.fork_urls.get(name));
488 let index = next_package
489 .name()
490 .and_then(|name| state.fork_indexes.get(name));
491
492 self.request_package(next_package, url, index, request_sink)?;
504
505 let version = if let Some(version) = state.initial_version.take() {
506 version
510 } else {
511 let term_intersection = state
512 .pubgrub
513 .partial_solution
514 .term_intersection_for_package(next_id)
515 .expect("a package was chosen but we don't have a term");
516 let range = term_intersection.unwrap_positive();
517
518 let cache_selected_version = url.is_none() && index.is_none();
522 let decision = if cache_selected_version
523 && let Some((selected_range, version)) =
524 state.selected_versions.get(&next_id)
525 && selected_range == range
526 {
527 Some(ResolverVersion::Unforked(version.clone()))
528 } else {
529 let decision = self.choose_version(
530 next_package,
531 next_id,
532 index.map(IndexMetadata::url),
533 range,
534 &mut state.pins,
535 &preferences,
536 &state.fork_urls,
537 &state.env,
538 &state.python_requirement,
539 &state.pubgrub,
540 &mut visited,
541 request_sink,
542 )?;
543
544 if cache_selected_version
545 && let Some(ResolverVersion::Unforked(version)) = &decision
546 {
547 state
548 .selected_versions
549 .insert(next_id, (range.clone(), version.clone()));
550 }
551
552 decision
553 };
554
555 let Some(version) = decision else {
557 debug!("No compatible version found for: {next_package}");
558
559 let term_intersection = state
560 .pubgrub
561 .partial_solution
562 .term_intersection_for_package(next_id)
563 .expect("a package was chosen but we don't have a term");
564
565 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
566 if let Some(reason) = self.unavailable_packages.pin().get(name) {
568 state
569 .pubgrub
570 .add_incompatibility(Incompatibility::custom_term(
571 next_id,
572 term_intersection.clone(),
573 UnavailableReason::Package(reason.clone()),
574 ));
575 continue;
576 }
577 }
578
579 state
580 .pubgrub
581 .add_incompatibility(Incompatibility::no_versions(
582 next_id,
583 term_intersection.clone(),
584 ));
585 continue;
586 };
587
588 let version = match version {
589 ResolverVersion::Unforked(version) => version,
590 ResolverVersion::Forked(forks) => {
591 forked_states.extend(self.version_forks_to_fork_states(state, forks));
592 continue 'FORK;
593 }
594 ResolverVersion::Unavailable(version, reason) => {
595 state.add_unavailable_version(version, reason);
596 continue;
597 }
598 };
599
600 if url.is_none() {
602 state.prefetcher.prefetch_batches(
603 next_package,
604 index,
605 &version,
606 term_intersection.unwrap_positive(),
607 state
608 .pubgrub
609 .partial_solution
610 .unchanging_term_for_package(next_id),
611 &state.python_requirement,
612 &self.selector,
613 &state.env,
614 )?;
615 }
616
617 version
618 };
619
620 state.prefetcher.version_tried(next_package, &version);
621
622 self.on_progress(next_package, &version);
623
624 if state
625 .added_dependencies
626 .get(&next_id)
627 .is_some_and(|versions| versions.contains(&version))
628 {
629 state
632 .pubgrub
633 .partial_solution
634 .add_decision(next_id, version);
635 continue;
636 }
637
638 let forked_deps = self.get_dependencies_forking(
640 next_id,
641 next_package,
642 &version,
643 &state.pins,
644 &state.fork_urls,
645 &state.env,
646 &state.python_requirement,
647 &state.pubgrub,
648 )?;
649
650 match forked_deps {
651 ForkedDependencies::Unavailable(reason) => {
652 state
655 .pubgrub
656 .add_incompatibility(Incompatibility::custom_version(
657 next_id,
658 version.clone(),
659 UnavailableReason::Version(reason),
660 ));
661 }
662 ForkedDependencies::Unforked(dependencies) => {
663 state
664 .added_dependencies
665 .entry(next_id)
666 .or_default()
667 .insert(version.clone());
668
669 state
671 .visit_package_version_dependencies(
672 next_id,
673 &version,
674 &self.urls,
675 &self.indexes,
676 &dependencies,
677 &self.git,
678 &self.workspace_members,
679 self.selector.resolution_strategy(),
680 )
681 .map_err(|err| {
682 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
683 })?;
684
685 self.visit_dependencies(&dependencies, &state, request_sink)
687 .map_err(|err| {
688 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
689 })?;
690
691 state.add_package_version_dependencies(
693 next_id,
694 &version,
695 dependencies,
696 &self.index,
697 &self.installed_packages,
698 );
699 }
700 ForkedDependencies::Forked {
701 mut forks,
702 diverging_packages,
703 } => {
704 state
705 .added_dependencies
706 .entry(next_id)
707 .or_default()
708 .insert(version.clone());
709
710 debug!(
711 "Pre-fork {} took {:.3}s",
712 state.env,
713 start.elapsed().as_secs_f32()
714 );
715
716 match (self.options.fork_strategy, self.options.resolution_mode) {
718 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
719 forks.sort_by(|a, b| {
723 a.cmp_requires_python(b)
724 .reverse()
725 .then_with(|| a.cmp_upper_bounds(b))
726 });
727 }
728 (ForkStrategy::RequiresPython, _) => {
729 forks.sort_by(|a, b| {
733 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
734 });
735 }
736 }
737
738 for new_fork_state in self.forks_to_fork_states(
739 state,
740 &version,
741 forks,
742 request_sink,
743 &diverging_packages,
744 ) {
745 forked_states.push(new_fork_state?);
746 }
747 continue 'FORK;
748 }
749 ForkedDependencies::RequiresPython(requires_python) => {
750 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
751 && state.env.marker_environment().is_none()
752 {
753 let forks = fork_version_by_python_requirement(
754 &requires_python,
755 &state.python_requirement,
756 &state.env,
757 );
758 if !forks.is_empty() {
759 debug!(
760 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
761 state.python_requirement.target(),
762 &requires_python,
763 next_package,
764 version,
765 forks
766 .iter()
767 .map(ToString::to_string)
768 .collect::<Vec<_>>()
769 .join(", ")
770 );
771
772 let forks = forks
775 .into_iter()
776 .map(|env| VersionFork {
777 env,
778 id: next_id,
779 version: None,
780 })
781 .collect();
782 forked_states
783 .extend(self.version_forks_to_fork_states(state, forks));
784 continue 'FORK;
785 }
786 }
787
788 state
789 .pubgrub
790 .add_incompatibility(Incompatibility::custom_version(
791 next_id,
792 version.clone(),
793 UnavailableReason::Version(UnavailableVersion::RequiresPython(
794 requires_python,
795 )),
796 ));
797 }
798 }
799 }
800 }
801 if resolutions.len() > 1 {
802 info!(
803 "Solved your requirements for {} environments",
804 resolutions.len()
805 );
806 }
807 if tracing::enabled!(Level::DEBUG) {
808 for resolution in &resolutions {
809 if let Some(env) = resolution.env.end_user_fork_display() {
810 let packages: FxHashSet<_> = resolution
811 .nodes
812 .keys()
813 .map(|package| &package.name)
814 .collect();
815 debug!(
816 "Distinct solution for {env} with {} package(s)",
817 packages.len()
818 );
819 }
820 }
821 }
822 for resolution in &resolutions {
823 Self::trace_resolution(resolution);
824 }
825 ResolverOutput::from_state(
826 &resolutions,
827 self.requirements.clone(),
828 self.constraints.clone(),
829 self.overrides.clone(),
830 &self.preferences,
831 &self.index,
832 &self.git,
833 self.python_requirement.target().clone(),
834 &self.conflicts,
835 self.selector.resolution_strategy(),
836 self.options.clone(),
837 )
838 }
839
840 fn reprioritize_conflicts(state: &mut ForkState) {
844 for package in state.conflict_tracker.prioritize.drain(..) {
845 let changed = state
846 .priorities
847 .mark_conflict_early(&state.pubgrub.package_store[package]);
848 if changed {
849 debug!(
850 "Package {} has too many conflicts (affected), prioritizing",
851 &state.pubgrub.package_store[package]
852 );
853 } else {
854 debug!(
855 "Package {} has too many conflicts (affected), already {:?}",
856 state.pubgrub.package_store[package],
857 state.priorities.get(&state.pubgrub.package_store[package])
858 );
859 }
860 }
861
862 for package in state.conflict_tracker.deprioritize.drain(..) {
863 let changed = state
864 .priorities
865 .mark_conflict_late(&state.pubgrub.package_store[package]);
866 if changed {
867 debug!(
868 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
869 state.pubgrub.package_store[package],
870 );
871 let backtrack_level = state.pubgrub.backtrack_package(package);
872 if let Some(backtrack_level) = backtrack_level {
873 debug!("Backtracked {backtrack_level} decisions");
874 } else {
875 debug!(
876 "Package {} is not decided, cannot backtrack",
877 state.pubgrub.package_store[package]
878 );
879 }
880 } else {
881 debug!(
882 "Package {} has too many conflicts (culprit), already {:?}",
883 state.pubgrub.package_store[package],
884 state.priorities.get(&state.pubgrub.package_store[package])
885 );
886 }
887 }
888 }
889
890 fn trace_resolution(combined: &Resolution) {
896 if !tracing::enabled!(Level::TRACE) {
897 return;
898 }
899 trace!("Resolution: {:?}", combined.env);
900 for edge in &combined.edges {
901 trace!(
902 "Resolution edge: {} -> {}",
903 edge.from
904 .as_ref()
905 .map(PackageName::as_str)
906 .unwrap_or("ROOT"),
907 edge.to,
908 );
909 let mut msg = String::new();
912 write!(msg, "{}", edge.from_version).unwrap();
913 if let Some(ref extra) = edge.from_extra {
914 write!(msg, " (extra: {extra})").unwrap();
915 }
916 if let Some(ref dev) = edge.from_group {
917 write!(msg, " (group: {dev})").unwrap();
918 }
919
920 write!(msg, " -> ").unwrap();
921
922 write!(msg, "{}", edge.to_version).unwrap();
923 if let Some(ref extra) = edge.to_extra {
924 write!(msg, " (extra: {extra})").unwrap();
925 }
926 if let Some(ref dev) = edge.to_group {
927 write!(msg, " (group: {dev})").unwrap();
928 }
929 if let Some(marker) = edge.marker.contents() {
930 write!(msg, " ; {marker}").unwrap();
931 }
932 trace!("Resolution edge: {msg}");
933 }
934 }
935
936 fn forks_to_fork_states<'a>(
938 &'a self,
939 current_state: ForkState,
940 version: &'a Version,
941 forks: Vec<Fork>,
942 request_sink: &'a Sender<Request>,
943 diverging_packages: &'a [PackageName],
944 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
945 debug!(
946 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
947 current_state.pubgrub.package_store[current_state.next],
948 version,
949 diverging_packages
950 .iter()
951 .map(ToString::to_string)
952 .join(", "),
953 forks.len(),
954 if forks.len() == 1 { "" } else { "s" }
955 );
956 assert!(forks.len() >= 2);
957 let package = current_state.next;
963 let mut cur_state = Some(current_state);
964 let forks_len = forks.len();
965 forks
966 .into_iter()
967 .enumerate()
968 .map(move |(i, fork)| {
969 let is_last = i == forks_len - 1;
970 let forked_state = cur_state.take().unwrap();
971 if !is_last {
972 cur_state = Some(forked_state.clone());
973 }
974
975 let env = fork.env.clone();
976 (fork, forked_state.with_env(env))
977 })
978 .map(move |(fork, mut forked_state)| {
979 forked_state
981 .visit_package_version_dependencies(
982 package,
983 version,
984 &self.urls,
985 &self.indexes,
986 &fork.dependencies,
987 &self.git,
988 &self.workspace_members,
989 self.selector.resolution_strategy(),
990 )
991 .map_err(|err| {
992 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
993 })?;
994
995 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
997 .map_err(|err| {
998 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
999 })?;
1000
1001 forked_state.add_package_version_dependencies(
1003 package,
1004 version,
1005 fork.dependencies,
1006 &self.index,
1007 &self.installed_packages,
1008 );
1009
1010 Ok(forked_state)
1011 })
1012 }
1013
1014 #[expect(clippy::unused_self)]
1016 fn version_forks_to_fork_states(
1017 &self,
1018 current_state: ForkState,
1019 forks: Vec<VersionFork>,
1020 ) -> impl Iterator<Item = ForkState> + '_ {
1021 let mut cur_state = Some(current_state);
1027 let forks_len = forks.len();
1028 forks.into_iter().enumerate().map(move |(i, fork)| {
1029 let is_last = i == forks_len - 1;
1030 let mut forked_state = cur_state.take().unwrap();
1031 if !is_last {
1032 cur_state = Some(forked_state.clone());
1033 }
1034 forked_state.initial_id = Some(fork.id);
1035 forked_state.initial_version = fork.version;
1036 forked_state.with_env(fork.env)
1037 })
1038 }
1039
1040 fn visit_dependencies(
1042 &self,
1043 dependencies: &[PubGrubDependency],
1044 state: &ForkState,
1045 request_sink: &Sender<Request>,
1046 ) -> Result<(), ResolveError> {
1047 for dependency in dependencies {
1048 let PubGrubDependency {
1049 package,
1050 version: _,
1051 parent: _,
1052 source: _,
1053 } = dependency;
1054 let url = package.name().and_then(|name| state.fork_urls.get(name));
1055 let index = package.name().and_then(|name| state.fork_indexes.get(name));
1056 self.visit_package(package, url, index, request_sink)?;
1057 }
1058 Ok(())
1059 }
1060
1061 fn visit_package(
1064 &self,
1065 package: &PubGrubPackage,
1066 url: Option<&VerbatimParsedUrl>,
1067 index: Option<&IndexMetadata>,
1068 request_sink: &Sender<Request>,
1069 ) -> Result<(), ResolveError> {
1070 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1072 return Ok(());
1073 }
1074
1075 self.request_package(package, url, index, request_sink)
1076 }
1077
1078 fn request_package(
1079 &self,
1080 package: &PubGrubPackage,
1081 url: Option<&VerbatimParsedUrl>,
1082 index: Option<&IndexMetadata>,
1083 request_sink: &Sender<Request>,
1084 ) -> Result<(), ResolveError> {
1085 let Some(name) = package.name_no_root() else {
1087 return Ok(());
1088 };
1089
1090 if let Some(url) = url {
1091 if !self.hasher.allows_url(&url.verbatim) {
1093 return Err(ResolveError::UnhashedPackage(name.clone()));
1094 }
1095
1096 let dist = Dist::from_url(name.clone(), url.clone())?;
1098 if self.index.distributions().register(dist.distribution_id()) {
1099 request_sink.blocking_send(Request::Dist(dist))?;
1100 }
1101 } else if let Some(index) = index {
1102 if self
1104 .index
1105 .explicit()
1106 .register((name.clone(), index.url().clone()))
1107 {
1108 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1109 }
1110 } else {
1111 if self.index.implicit().register(name.clone()) {
1113 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1114 }
1115 }
1116 Ok(())
1117 }
1118
1119 fn pre_visit<'data>(
1122 packages: impl Iterator<
1123 Item = (
1124 Id<PubGrubPackage>,
1125 &'data PubGrubPackage,
1126 &'data Range<Version>,
1127 ),
1128 >,
1129 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1130 urls: &Urls,
1131 indexes: &Indexes,
1132 python_requirement: &PythonRequirement,
1133 request_sink: &Sender<Request>,
1134 ) -> Result<(), ResolveError> {
1135 for (id, package, range) in packages {
1138 let PubGrubPackageInner::Package {
1139 name,
1140 extra: None,
1141 group: None,
1142 marker: MarkerTree::TRUE,
1143 } = &**package
1144 else {
1145 continue;
1146 };
1147 if urls.any_url(name) {
1150 continue;
1151 }
1152 if indexes.contains_key(name) {
1154 continue;
1155 }
1156 if pre_visited.get(&id) == Some(range) {
1159 continue;
1160 }
1161 pre_visited.insert(id, range.clone());
1162 request_sink.blocking_send(Request::Prefetch(
1163 name.clone(),
1164 range.clone(),
1165 python_requirement.clone(),
1166 ))?;
1167 }
1168 Ok(())
1169 }
1170
1171 fn known_versions<'a>(
1183 index: &InMemoryIndex,
1184 installed_packages: &InstalledPackages,
1185 fork_urls: &ForkUrls,
1186 fork_indexes: &ForkIndexes,
1187 known_versions: &'a mut FxHashMap<PackageName, Arc<[Version]>>,
1188 package: &PubGrubPackage,
1189 ) -> Option<&'a [Version]> {
1190 let name = package.name_no_root()?;
1191 if fork_urls.get(name).is_some() {
1193 return None;
1194 }
1195 if !known_versions.contains_key(name) {
1196 let response = if let Some(index_metadata) = fork_indexes.get(name) {
1197 index
1198 .explicit()
1199 .get(&(name.clone(), index_metadata.url().clone()))?
1200 } else {
1201 index.implicit().get(name)?
1202 };
1203 let VersionsResponse::Found(ref version_maps) = *response else {
1204 return None;
1205 };
1206 let mut versions: Vec<Version> = version_maps
1207 .iter()
1208 .flat_map(|version_map| version_map.included_versions().cloned())
1209 .chain(
1210 installed_packages
1211 .get_packages(name)
1212 .iter()
1213 .map(|dist| dist.version().clone()),
1214 )
1215 .collect();
1216 versions.sort_unstable();
1217 versions.dedup();
1218 known_versions.insert(name.clone(), versions.into());
1219 }
1220 Some(&known_versions[name][..])
1221 }
1222
1223 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1231 fn choose_version(
1232 &self,
1233 package: &PubGrubPackage,
1234 id: Id<PubGrubPackage>,
1235 index: Option<&IndexUrl>,
1236 range: &Range<Version>,
1237 pins: &mut FilePins,
1238 preferences: &Preferences,
1239 fork_urls: &ForkUrls,
1240 env: &ResolverEnvironment,
1241 python_requirement: &PythonRequirement,
1242 pubgrub: &State<UvDependencyProvider>,
1243 visited: &mut FxHashSet<PackageName>,
1244 request_sink: &Sender<Request>,
1245 ) -> Result<Option<ResolverVersion>, ResolveError> {
1246 match &**package {
1247 PubGrubPackageInner::Root(_) => {
1248 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1249 }
1250
1251 PubGrubPackageInner::Python(_) => {
1252 Ok(None)
1255 }
1256
1257 PubGrubPackageInner::System(_) => {
1258 let Some(version) = range.as_singleton() else {
1261 return Ok(None);
1262 };
1263 Ok(Some(ResolverVersion::Unforked(version.clone())))
1264 }
1265
1266 PubGrubPackageInner::Marker { name, .. }
1267 | PubGrubPackageInner::Extra { name, .. }
1268 | PubGrubPackageInner::Group { name, .. }
1269 | PubGrubPackageInner::Package { name, .. } => {
1270 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1271 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1272 } else {
1273 self.choose_version_registry(
1274 package,
1275 id,
1276 name,
1277 index,
1278 range,
1279 preferences,
1280 env,
1281 python_requirement,
1282 pubgrub,
1283 pins,
1284 visited,
1285 request_sink,
1286 )
1287 }
1288 }
1289 }
1290 }
1291
1292 fn choose_version_url(
1295 &self,
1296 id: Id<PubGrubPackage>,
1297 name: &PackageName,
1298 range: &Range<Version>,
1299 url: &VerbatimParsedUrl,
1300 env: &ResolverEnvironment,
1301 python_requirement: &PythonRequirement,
1302 pubgrub: &State<UvDependencyProvider>,
1303 ) -> Result<Option<ResolverVersion>, ResolveError> {
1304 debug!(
1305 "Searching for a compatible version of {name} @ {} ({range})",
1306 url.verbatim
1307 );
1308
1309 let dist = Dist::from_url(name.clone(), url.clone())?;
1310 let distribution_id = dist.distribution_id();
1311 let response = self
1312 .index
1313 .distributions()
1314 .wait_blocking(&distribution_id)
1315 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1316
1317 let metadata = match &*response {
1319 MetadataResponse::Found(archive) => &archive.metadata,
1320 MetadataResponse::Unavailable(reason) => {
1321 self.unavailable_packages
1322 .pin()
1323 .insert(name.clone(), reason.into());
1324 return Ok(None);
1325 }
1326 MetadataResponse::Error(dist, err) => {
1329 return Err(ResolveError::Dist(
1330 DistErrorKind::from_requested_dist(dist, &**err),
1331 dist.clone(),
1332 DerivationChain::default(),
1333 err.clone(),
1334 ));
1335 }
1336 };
1337
1338 let version = &metadata.version;
1339
1340 if !range.contains(version) {
1342 return Ok(None);
1343 }
1344
1345 if let Dist::Built(dist) = &dist {
1348 let filename = match &dist {
1349 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1350 BuiltDist::DirectUrl(dist) => &dist.filename,
1351 BuiltDist::GitPath(dist) => &dist.filename,
1352 BuiltDist::Path(dist) => &dist.filename,
1353 };
1354
1355 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1358 {
1359 let wheel_marker = implied_markers(filename);
1360 for environment_marker in self.options.artifact_environments.iter().copied() {
1363 if env.included_by_marker(environment_marker)
1365 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1366 {
1367 if wheel_marker.is_disjoint(environment_marker) {
1369 return Ok(Some(ResolverVersion::Unavailable(
1370 version.clone(),
1371 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1372 IncompatibleWheel::MissingPlatform(environment_marker),
1373 )),
1374 )));
1375 }
1376 }
1377 }
1378 }
1379
1380 if !python_requirement.target().matches_wheel_tag(filename) {
1382 return Ok(Some(ResolverVersion::Unavailable(
1383 filename.version.clone(),
1384 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1385 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1386 )),
1387 )));
1388 }
1389 }
1390
1391 if let Some(requires_python) = metadata.requires_python.as_ref() {
1393 if !python_requirement.target().is_contained_by(requires_python) {
1394 let kind = if python_requirement.installed() == python_requirement.target() {
1395 PythonRequirementKind::Installed
1396 } else {
1397 PythonRequirementKind::Target
1398 };
1399 return Ok(Some(ResolverVersion::Unavailable(
1400 version.clone(),
1401 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1402 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1403 )),
1404 )));
1405 }
1406 }
1407
1408 Ok(Some(ResolverVersion::Unforked(version.clone())))
1409 }
1410
1411 fn choose_version_registry(
1414 &self,
1415 package: &PubGrubPackage,
1416 id: Id<PubGrubPackage>,
1417 name: &PackageName,
1418 index: Option<&IndexUrl>,
1419 range: &Range<Version>,
1420 preferences: &Preferences,
1421 env: &ResolverEnvironment,
1422 python_requirement: &PythonRequirement,
1423 pubgrub: &State<UvDependencyProvider>,
1424 pins: &mut FilePins,
1425 visited: &mut FxHashSet<PackageName>,
1426 request_sink: &Sender<Request>,
1427 ) -> Result<Option<ResolverVersion>, ResolveError> {
1428 let versions_response = if let Some(index) = index {
1430 self.index
1431 .explicit()
1432 .wait_blocking(&(name.clone(), index.clone()))
1433 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1434 } else {
1435 self.index
1436 .implicit()
1437 .wait_blocking(name)
1438 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1439 };
1440 visited.insert(name.clone());
1441
1442 let version_maps = match *versions_response {
1443 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1444 VersionsResponse::NoIndex => {
1445 self.unavailable_packages
1446 .pin()
1447 .insert(name.clone(), UnavailablePackage::NoIndex);
1448 &[]
1449 }
1450 VersionsResponse::Offline => {
1451 self.unavailable_packages
1452 .pin()
1453 .insert(name.clone(), UnavailablePackage::Offline);
1454 &[]
1455 }
1456 VersionsResponse::NotFound => {
1457 self.unavailable_packages
1458 .pin()
1459 .insert(name.clone(), UnavailablePackage::NotFound);
1460 &[]
1461 }
1462 };
1463
1464 debug!("Searching for a compatible version of {package} ({range})");
1465
1466 let Some(candidate) = self.selector.select(
1468 name,
1469 range,
1470 version_maps,
1471 preferences,
1472 &self.installed_packages,
1473 &self.exclusions,
1474 index,
1475 env,
1476 self.tags.as_ref(),
1477 ) else {
1478 return Ok(None);
1480 };
1481
1482 let dist = match candidate.dist() {
1483 CandidateDist::Compatible(dist) => dist,
1484 CandidateDist::Incompatible {
1485 incompatible_dist: incompatibility,
1486 prioritized_dist: _,
1487 } => {
1488 return Ok(Some(ResolverVersion::Unavailable(
1490 candidate.version().clone(),
1491 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1494 )));
1495 }
1496 };
1497
1498 if let Some((requires_python, incompatibility)) =
1500 Self::check_requires_python(dist, python_requirement)
1501 {
1502 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1503 if env.marker_environment().is_none() {
1504 let forks = fork_version_by_python_requirement(
1505 requires_python,
1506 python_requirement,
1507 env,
1508 );
1509 if !forks.is_empty() {
1510 debug!(
1511 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1512 python_requirement.target(),
1513 requires_python,
1514 name,
1515 candidate.version(),
1516 forks
1517 .iter()
1518 .map(ToString::to_string)
1519 .collect::<Vec<_>>()
1520 .join(", ")
1521 );
1522 let forks = forks
1523 .into_iter()
1524 .map(|env| VersionFork {
1525 env,
1526 id,
1527 version: None,
1528 })
1529 .collect();
1530 return Ok(Some(ResolverVersion::Forked(forks)));
1531 }
1532 }
1533 }
1534
1535 return Ok(Some(ResolverVersion::Unavailable(
1536 candidate.version().clone(),
1537 UnavailableVersion::IncompatibleDist(incompatibility),
1538 )));
1539 }
1540
1541 if let Some(forked) = self.fork_version_registry(
1543 &candidate,
1544 dist,
1545 version_maps,
1546 package,
1547 id,
1548 name,
1549 index,
1550 range,
1551 preferences,
1552 env,
1553 pubgrub,
1554 pins,
1555 request_sink,
1556 )? {
1557 return Ok(Some(forked));
1558 }
1559
1560 let filename = match dist.for_installation() {
1561 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1562 .filename()
1563 .unwrap_or(Cow::Borrowed("unknown filename")),
1564 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1565 .filename()
1566 .unwrap_or(Cow::Borrowed("unknown filename")),
1567 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1568 };
1569
1570 debug!(
1571 "Selecting: {}=={} [{}] ({})",
1572 name,
1573 candidate.version(),
1574 candidate.choice_kind(),
1575 filename,
1576 );
1577 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1578
1579 let version = candidate.version().clone();
1580 Ok(Some(ResolverVersion::Unforked(version)))
1581 }
1582
1583 fn fork_version_registry(
1596 &self,
1597 candidate: &Candidate,
1598 dist: &CompatibleDist,
1599 version_maps: &[VersionMap],
1600 package: &PubGrubPackage,
1601 id: Id<PubGrubPackage>,
1602 name: &PackageName,
1603 index: Option<&IndexUrl>,
1604 range: &Range<Version>,
1605 preferences: &Preferences,
1606 env: &ResolverEnvironment,
1607 pubgrub: &State<UvDependencyProvider>,
1608 pins: &mut FilePins,
1609 request_sink: &Sender<Request>,
1610 ) -> Result<Option<ResolverVersion>, ResolveError> {
1611 if env.marker_environment().is_some() {
1613 return Ok(None);
1614 }
1615
1616 if dist.implied_markers().is_true() {
1619 return Ok(None);
1620 }
1621
1622 for marker in self.options.artifact_environments.iter().copied() {
1625 if env.included_by_marker(marker) {
1627 if dist.implied_markers().is_disjoint(marker)
1629 && !find_environments(id, pubgrub).is_disjoint(marker)
1630 {
1631 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1633 return Ok(Some(ResolverVersion::Unavailable(
1634 candidate.version().clone(),
1635 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1636 IncompatibleWheel::MissingPlatform(marker),
1637 )),
1638 )));
1639 };
1640
1641 debug!(
1642 "Forking on required platform `{}` for {}=={} ({})",
1643 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1644 name,
1645 candidate.version(),
1646 [&left, &right]
1647 .iter()
1648 .map(ToString::to_string)
1649 .collect::<Vec<_>>()
1650 .join(", ")
1651 );
1652 let forks = vec![
1653 VersionFork {
1654 env: left,
1655 id,
1656 version: None,
1657 },
1658 VersionFork {
1659 env: right,
1660 id,
1661 version: None,
1662 },
1663 ];
1664 return Ok(Some(ResolverVersion::Forked(forks)));
1665 }
1666 }
1667 }
1668
1669 if !candidate.version().is_local() {
1671 return Ok(None);
1672 }
1673
1674 debug!(
1675 "Looking at local version: {}=={}",
1676 name,
1677 candidate.version()
1678 );
1679
1680 let range = range.clone().intersection(&Range::singleton(
1682 candidate.version().clone().without_local(),
1683 ));
1684
1685 let Some(base_candidate) = self.selector.select(
1686 name,
1687 &range,
1688 version_maps,
1689 preferences,
1690 &self.installed_packages,
1691 &self.exclusions,
1692 index,
1693 env,
1694 self.tags.as_ref(),
1695 ) else {
1696 return Ok(None);
1697 };
1698 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1699 return Ok(None);
1700 };
1701
1702 let mut remainder = {
1704 let mut remainder = base_dist.implied_markers();
1705 remainder = remainder.and(dist.implied_markers().negate());
1706 remainder
1707 };
1708 if remainder.is_false() {
1709 return Ok(None);
1710 }
1711
1712 if !env.included_by_marker(remainder) {
1716 return Ok(None);
1717 }
1718
1719 if !env.included_by_marker(dist.implied_markers()) {
1722 let filename = match dist.for_installation() {
1723 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1724 .filename()
1725 .unwrap_or(Cow::Borrowed("unknown filename")),
1726 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1727 .filename()
1728 .unwrap_or(Cow::Borrowed("unknown filename")),
1729 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1730 };
1731
1732 debug!(
1733 "Preferring non-local candidate: {}=={} [{}] ({})",
1734 name,
1735 base_candidate.version(),
1736 base_candidate.choice_kind(),
1737 filename,
1738 );
1739 self.visit_candidate(
1740 &base_candidate,
1741 base_dist,
1742 package,
1743 name,
1744 pins,
1745 request_sink,
1746 )?;
1747
1748 return Ok(Some(ResolverVersion::Unforked(
1749 base_candidate.version().clone(),
1750 )));
1751 }
1752
1753 for value in [
1762 arcstr::literal!("darwin"),
1763 arcstr::literal!("linux"),
1764 arcstr::literal!("win32"),
1765 ] {
1766 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1767 key: MarkerValueString::SysPlatform,
1768 operator: MarkerOperator::Equal,
1769 value,
1770 });
1771 if dist.implied_markers().is_disjoint(sys_platform)
1772 && !remainder.is_disjoint(sys_platform)
1773 {
1774 remainder = remainder.or(sys_platform);
1775 }
1776 }
1777
1778 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1780 return Ok(None);
1781 };
1782
1783 debug!(
1784 "Forking platform for {}=={} ({})",
1785 name,
1786 candidate.version(),
1787 [&base_env, &local_env]
1788 .iter()
1789 .map(ToString::to_string)
1790 .collect::<Vec<_>>()
1791 .join(", ")
1792 );
1793 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1794 self.visit_candidate(
1795 &base_candidate,
1796 base_dist,
1797 package,
1798 name,
1799 pins,
1800 request_sink,
1801 )?;
1802
1803 let forks = vec![
1804 VersionFork {
1805 env: base_env.clone(),
1806 id,
1807 version: Some(base_candidate.version().clone()),
1808 },
1809 VersionFork {
1810 env: local_env.clone(),
1811 id,
1812 version: Some(candidate.version().clone()),
1813 },
1814 ];
1815 Ok(Some(ResolverVersion::Forked(forks)))
1816 }
1817
1818 fn visit_candidate(
1820 &self,
1821 candidate: &Candidate,
1822 dist: &CompatibleDist,
1823 package: &PubGrubPackage,
1824 name: &PackageName,
1825 pins: &mut FilePins,
1826 request_sink: &Sender<Request>,
1827 ) -> Result<(), ResolveError> {
1828 pins.insert(candidate, dist);
1831
1832 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1834 if self.dependency_mode.is_transitive() {
1835 let dist = dist.for_resolution();
1836 if self.index.distributions().register(dist.distribution_id()) {
1837 if name != dist.name() {
1838 return Err(ResolveError::MismatchedPackageName {
1839 request: "distribution",
1840 expected: name.clone(),
1841 actual: dist.name().clone(),
1842 });
1843 }
1844 if !self
1846 .hasher
1847 .allows_package(candidate.name(), candidate.version())
1848 {
1849 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1850 }
1851
1852 let request = Request::from(dist);
1853 request_sink.blocking_send(request)?;
1854 }
1855 }
1856 }
1857
1858 Ok(())
1859 }
1860
1861 fn check_requires_python<'dist>(
1864 dist: &'dist CompatibleDist,
1865 python_requirement: &PythonRequirement,
1866 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1867 let requires_python = dist.requires_python()?;
1868 if python_requirement.target().is_contained_by(requires_python) {
1869 None
1870 } else {
1871 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1872 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1873 requires_python.clone(),
1874 if python_requirement.installed() == python_requirement.target() {
1875 PythonRequirementKind::Installed
1876 } else {
1877 PythonRequirementKind::Target
1878 },
1879 ))
1880 } else {
1881 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1882 requires_python.clone(),
1883 if python_requirement.installed() == python_requirement.target() {
1884 PythonRequirementKind::Installed
1885 } else {
1886 PythonRequirementKind::Target
1887 },
1888 ))
1889 };
1890 Some((requires_python, incompatibility))
1891 }
1892 }
1893
1894 #[instrument(skip_all, fields(%package, %version))]
1896 fn get_dependencies_forking(
1897 &self,
1898 id: Id<PubGrubPackage>,
1899 package: &PubGrubPackage,
1900 version: &Version,
1901 pins: &FilePins,
1902 fork_urls: &ForkUrls,
1903 env: &ResolverEnvironment,
1904 python_requirement: &PythonRequirement,
1905 pubgrub: &State<UvDependencyProvider>,
1906 ) -> Result<ForkedDependencies, ResolveError> {
1907 let result = self.get_dependencies(
1908 id,
1909 package,
1910 version,
1911 pins,
1912 fork_urls,
1913 env,
1914 python_requirement,
1915 pubgrub,
1916 );
1917 if env.marker_environment().is_some() {
1918 result.map(|deps| match deps {
1919 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1920 ForkedDependencies::Unforked(deps)
1921 }
1922 Dependencies::RequiresPython(requires_python) => {
1923 ForkedDependencies::RequiresPython(requires_python)
1924 }
1925 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1926 })
1927 } else {
1928 Ok(result?.fork(env, python_requirement, &self.conflicts))
1929 }
1930 }
1931
1932 #[instrument(skip_all, fields(%package, %version))]
1934 fn get_dependencies(
1935 &self,
1936 id: Id<PubGrubPackage>,
1937 package: &PubGrubPackage,
1938 version: &Version,
1939 pins: &FilePins,
1940 fork_urls: &ForkUrls,
1941 env: &ResolverEnvironment,
1942 python_requirement: &PythonRequirement,
1943 pubgrub: &State<UvDependencyProvider>,
1944 ) -> Result<Dependencies, ResolveError> {
1945 let dependencies = match &**package {
1946 PubGrubPackageInner::Root(_) => {
1947 let no_dev_deps = BTreeMap::default();
1948 let requirements = self.flatten_requirements(
1949 &self.requirements,
1950 &no_dev_deps,
1951 None,
1952 None,
1953 None,
1954 None,
1955 env,
1956 python_requirement,
1957 );
1958
1959 PubGrubDependency::from_requirements(
1960 &self.conflicts,
1961 requirements,
1962 None,
1963 Some(package),
1964 )
1965 }
1966
1967 PubGrubPackageInner::Package {
1968 name,
1969 extra,
1970 group,
1971 marker: _,
1972 } => {
1973 if self.dependency_mode.is_direct() {
1975 return Ok(Dependencies::Unforkable(Vec::default()));
1976 }
1977
1978 let owned_id;
1980 let distribution_id = if let Some((_, metadata_id)) =
1981 pins.dist_and_id(name, version)
1982 {
1983 metadata_id
1984 } else if let Some(url) = fork_urls.get(name) {
1985 let dist = Dist::from_url(name.clone(), url.clone())?;
1986 owned_id = dist.distribution_id();
1987 &owned_id
1988 } else {
1989 debug_assert!(
1990 false,
1991 "Dependencies were requested for a package without a pinned distribution"
1992 );
1993 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1994 };
1995
1996 if self.dependency_mode.is_transitive()
1998 && self.unavailable_packages.pin().contains_key(name)
1999 && self.installed_packages.get_packages(name).is_empty()
2000 {
2001 debug_assert!(
2002 false,
2003 "Dependencies were requested for a package that is not available"
2004 );
2005 return Err(ResolveError::PackageUnavailable(name.clone()));
2006 }
2007
2008 let response = self
2010 .index
2011 .distributions()
2012 .wait_blocking(distribution_id)
2013 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
2014
2015 let metadata = match &*response {
2016 MetadataResponse::Found(archive) => &archive.metadata,
2017 MetadataResponse::Unavailable(reason) => {
2018 let unavailable_version = UnavailableVersion::from(reason);
2019 let message = unavailable_version.singular_message();
2020 if let Some(err) = reason.source() {
2021 warn!("{name} {message}: {err}");
2023 } else {
2024 warn!("{name} {message}");
2025 }
2026 let incomplete_packages = self.incomplete_packages.pin();
2027 let versions = incomplete_packages.get_or_insert(
2028 name.clone(),
2029 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
2030 );
2031 versions.pin().insert(version.clone(), reason.clone());
2032 return Ok(Dependencies::Unavailable(unavailable_version));
2033 }
2034 MetadataResponse::Error(dist, err) => {
2035 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
2036 .unwrap_or_default();
2037 return Err(ResolveError::Dist(
2038 DistErrorKind::from_requested_dist(dist, &**err),
2039 dist.clone(),
2040 chain,
2041 err.clone(),
2042 ));
2043 }
2044 };
2045
2046 if let Some(requires_python) = &metadata.requires_python {
2049 if !python_requirement.target().is_contained_by(requires_python) {
2050 return Ok(Dependencies::RequiresPython(requires_python.clone()));
2051 }
2052 }
2053
2054 let system_dependencies = self
2056 .options
2057 .torch_backend
2058 .as_ref()
2059 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
2060 .filter(|torch_backend| torch_backend.has_system_dependency(name))
2061 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
2062 .map(IndexUrl::url)
2063 .and_then(SystemDependency::from_index)
2064 .into_iter()
2065 .inspect(|system_dependency| {
2066 debug!(
2067 "Adding system dependency `{}` for `{package}@{version}`",
2068 system_dependency
2069 );
2070 })
2071 .map(PubGrubDependency::from);
2072
2073 let requirements = self.flatten_requirements(
2074 &metadata.requires_dist,
2075 &metadata.dependency_groups,
2076 extra.as_ref(),
2077 group.as_ref(),
2078 Some(name),
2079 Some(version),
2080 env,
2081 python_requirement,
2082 );
2083
2084 PubGrubDependency::from_requirements(
2085 &self.conflicts,
2086 requirements,
2087 group.as_ref(),
2088 Some(package),
2089 )
2090 .map(|mut dependencies| {
2091 dependencies.extend(system_dependencies);
2092 dependencies
2093 })
2094 }
2095
2096 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2097
2098 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2099
2100 PubGrubPackageInner::Marker { name, marker } => {
2102 return Ok(Dependencies::Unforkable(
2103 [MarkerTree::TRUE, *marker]
2104 .into_iter()
2105 .map(move |marker| PubGrubDependency {
2106 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2107 name: name.clone(),
2108 extra: None,
2109 group: None,
2110 marker,
2111 }),
2112 version: Range::singleton(version.clone()),
2113 parent: None,
2114 source: DependencySource::Unspecified,
2115 })
2116 .collect(),
2117 ));
2118 }
2119
2120 PubGrubPackageInner::Extra {
2122 name,
2123 extra,
2124 marker,
2125 } => {
2126 return Ok(Dependencies::Unforkable(
2127 [MarkerTree::TRUE, *marker]
2128 .into_iter()
2129 .dedup()
2130 .flat_map(move |marker| {
2131 [None, Some(extra)]
2132 .into_iter()
2133 .map(move |extra| PubGrubDependency {
2134 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2135 name: name.clone(),
2136 extra: extra.cloned(),
2137 group: None,
2138 marker,
2139 }),
2140 version: Range::singleton(version.clone()),
2141 parent: None,
2142 source: DependencySource::Unspecified,
2143 })
2144 })
2145 .collect(),
2146 ));
2147 }
2148
2149 PubGrubPackageInner::Group {
2151 name,
2152 group,
2153 marker,
2154 } => {
2155 return Ok(Dependencies::Unforkable(
2156 [MarkerTree::TRUE, *marker]
2157 .into_iter()
2158 .dedup()
2159 .map(|marker| PubGrubDependency {
2160 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2161 name: name.clone(),
2162 extra: None,
2163 group: Some(group.clone()),
2164 marker,
2165 }),
2166 version: Range::singleton(version.clone()),
2167 parent: None,
2168 source: DependencySource::Unspecified,
2169 })
2170 .collect(),
2171 ));
2172 }
2173 };
2174 Ok(match dependencies {
2175 Ok(dependencies) => Dependencies::Available(dependencies),
2176 Err(requirement) => {
2177 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2178 }
2179 })
2180 }
2181
2182 fn flatten_requirements<'a>(
2186 &'a self,
2187 dependencies: &'a [Requirement],
2188 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2189 extra: Option<&'a ExtraName>,
2190 dev: Option<&'a GroupName>,
2191 name: Option<&'a PackageName>,
2192 version: Option<&'a Version>,
2193 env: &'a ResolverEnvironment,
2194 python_requirement: &'a PythonRequirement,
2195 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2196 let python_marker = python_requirement.to_marker_tree();
2197
2198 if let Some(dev) = dev {
2199 Either::Left(Either::Left(self.requirements_for_extra(
2202 dev_dependencies.get(dev).into_iter().flatten(),
2203 extra,
2204 None,
2205 name.zip(version),
2206 env,
2207 python_marker,
2208 python_requirement,
2209 )))
2210 } else if !dependencies
2211 .iter()
2212 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2213 {
2214 Either::Left(Either::Right(self.requirements_for_extra(
2216 dependencies.iter(),
2217 extra,
2218 name.zip(version),
2219 name.zip(version),
2220 env,
2221 python_marker,
2222 python_requirement,
2223 )))
2224 } else {
2225 let mut requirements = self
2226 .requirements_for_extra(
2227 dependencies.iter(),
2228 extra,
2229 name.zip(version),
2230 name.zip(version),
2231 env,
2232 python_marker,
2233 python_requirement,
2234 )
2235 .collect::<Vec<_>>();
2236
2237 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2240 let mut queue: VecDeque<_> = requirements
2241 .iter()
2242 .filter(|req| name == Some(&req.name))
2243 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2244 .collect();
2245 while let Some((extra, marker)) = queue.pop_front() {
2246 if !seen.insert((extra.clone(), marker)) {
2247 continue;
2248 }
2249 for requirement in self.requirements_for_extra(
2250 dependencies,
2251 Some(&extra),
2252 name.zip(version),
2253 name.zip(version),
2254 env,
2255 python_marker,
2256 python_requirement,
2257 ) {
2258 let requirement = match requirement {
2259 Cow::Owned(mut requirement) => {
2260 requirement.marker = requirement.marker.and(marker);
2261 requirement
2262 }
2263 Cow::Borrowed(requirement) => {
2264 let mut marker = marker;
2265 marker = marker.and(requirement.marker);
2266 Requirement {
2267 name: requirement.name.clone(),
2268 extras: requirement.extras.clone(),
2269 groups: requirement.groups.clone(),
2270 source: requirement.source.clone(),
2271 origin: requirement.origin.clone(),
2272 marker: marker.simplify_extras(slice::from_ref(&extra)),
2273 }
2274 }
2275 };
2276 if name == Some(&requirement.name) {
2277 queue.extend(
2279 requirement
2280 .extras
2281 .iter()
2282 .cloned()
2283 .map(|extra| (extra, requirement.marker)),
2284 );
2285 } else {
2286 requirements.push(Cow::Owned(requirement));
2288 }
2289 }
2290 }
2291
2292 let mut self_constraints = vec![];
2296 for req in &requirements {
2297 if name == Some(&req.name) && !req.source.is_empty() {
2298 self_constraints.push(Requirement {
2299 name: req.name.clone(),
2300 extras: Box::new([]),
2301 groups: req.groups.clone(),
2302 source: req.source.clone(),
2303 origin: req.origin.clone(),
2304 marker: req.marker,
2305 });
2306 }
2307 }
2308
2309 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2311 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2312
2313 Either::Right(requirements.into_iter())
2314 }
2315 }
2316
2317 fn requirements_for_extra<'data, 'parameters>(
2320 &'data self,
2321 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2322 extra: Option<&'parameters ExtraName>,
2323 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2324 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2325 env: &'parameters ResolverEnvironment,
2326 python_marker: MarkerTree,
2327 python_requirement: &'parameters PythonRequirement,
2328 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2329 where
2330 'data: 'parameters,
2331 {
2332 self.overrides
2333 .apply_for_package(override_package, dependencies)
2334 .filter(move |requirement| {
2335 !self
2336 .excludes
2337 .contains_for_package(exclusion_package, &requirement.name)
2338 })
2339 .map(move |mut requirement| {
2340 let marker = match extra {
2349 Some(extra) => requirement
2350 .marker
2351 .simplify_extras(slice::from_ref(extra))
2352 .simplify_not_extras_with(|candidate| candidate != extra)
2353 .and(
2354 requirement
2355 .marker
2356 .simplify_not_extras_with(|_| true)
2357 .negate(),
2358 ),
2359 None => requirement.marker.simplify_not_extras_with(|_| true),
2360 };
2361
2362 if requirement.marker != marker {
2363 requirement.to_mut().marker = marker;
2364 }
2365
2366 requirement
2367 })
2368 .filter(move |requirement| {
2369 Self::is_requirement_applicable(
2370 requirement,
2371 extra,
2372 env,
2373 python_marker,
2374 python_requirement,
2375 )
2376 })
2377 .flat_map(move |requirement| {
2378 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2379 requirement,
2380 extra,
2381 env,
2382 python_marker,
2383 python_requirement,
2384 ))
2385 })
2386 }
2387
2388 fn is_requirement_applicable(
2391 requirement: &Requirement,
2392 extra: Option<&ExtraName>,
2393 env: &ResolverEnvironment,
2394 python_marker: MarkerTree,
2395 python_requirement: &PythonRequirement,
2396 ) -> bool {
2397 match extra {
2399 Some(source_extra) => {
2400 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2401 return false;
2402 }
2403
2404 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2405 {
2406 return false;
2407 }
2408 }
2409 None => {
2410 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2411 return false;
2412 }
2413 }
2414 }
2415
2416 if python_marker.is_disjoint(requirement.marker) {
2419 trace!(
2420 "Skipping {requirement} because of Requires-Python: {requires_python}",
2421 requires_python = python_requirement.target(),
2422 );
2423 return false;
2424 }
2425
2426 if !env.included_by_marker(requirement.marker) {
2429 trace!("Skipping {requirement} because of {env}");
2430 return false;
2431 }
2432
2433 true
2434 }
2435
2436 fn constraints_for_requirement<'data, 'parameters>(
2439 &'data self,
2440 requirement: Cow<'data, Requirement>,
2441 extra: Option<&'parameters ExtraName>,
2442 env: &'parameters ResolverEnvironment,
2443 python_marker: MarkerTree,
2444 python_requirement: &'parameters PythonRequirement,
2445 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2446 where
2447 'data: 'parameters,
2448 {
2449 self.constraints
2450 .get(&requirement.name)
2451 .into_iter()
2452 .flatten()
2453 .filter_map(move |constraint| {
2454 let constraint = if constraint.marker.is_true() {
2457 if requirement.marker.is_true() {
2461 Cow::Borrowed(constraint)
2462 } else {
2463 let mut marker = constraint.marker;
2464 marker = marker.and(requirement.marker);
2465
2466 if marker.is_false() {
2467 trace!(
2468 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2469 constraint.marker.try_to_string().unwrap(),
2470 requirement.marker.try_to_string().unwrap(),
2471 );
2472 return None;
2473 }
2474
2475 Cow::Owned(Requirement {
2476 name: constraint.name.clone(),
2477 extras: constraint.extras.clone(),
2478 groups: constraint.groups.clone(),
2479 source: constraint.source.clone(),
2480 origin: constraint.origin.clone(),
2481 marker,
2482 })
2483 }
2484 } else {
2485 let requires_python = python_requirement.target();
2486
2487 let mut marker = constraint.marker;
2488 marker = marker.and(requirement.marker);
2489
2490 if marker.is_false() {
2491 trace!(
2492 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2493 constraint.marker.try_to_string().unwrap(),
2494 requirement.marker.try_to_string().unwrap(),
2495 );
2496 return None;
2497 }
2498
2499 if python_marker.is_disjoint(marker) {
2503 trace!(
2504 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2505 );
2506 return None;
2507 }
2508
2509 if marker == constraint.marker {
2510 Cow::Borrowed(constraint)
2511 } else {
2512 Cow::Owned(Requirement {
2513 name: constraint.name.clone(),
2514 extras: constraint.extras.clone(),
2515 groups: constraint.groups.clone(),
2516 source: constraint.source.clone(),
2517 origin: constraint.origin.clone(),
2518 marker,
2519 })
2520 }
2521 };
2522
2523 if !env.included_by_marker(constraint.marker) {
2526 trace!("Skipping {constraint} because of {env}");
2527 return None;
2528 }
2529
2530 match extra {
2532 Some(source_extra) => {
2533 if !constraint
2534 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2535 {
2536 return None;
2537 }
2538 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2539 {
2540 return None;
2541 }
2542 }
2543 None => {
2544 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2545 return None;
2546 }
2547 }
2548 }
2549
2550 Some(constraint)
2551 })
2552 }
2553
2554 async fn fetch<Provider: ResolverProvider>(
2556 self: Arc<Self>,
2557 provider: Arc<Provider>,
2558 request_stream: Receiver<Request>,
2559 ) -> Result<(), ResolveError> {
2560 let mut response_stream = ReceiverStream::new(request_stream)
2561 .map(|request| self.process_request(request, &*provider).boxed_local())
2562 .buffer_unordered(usize::MAX);
2566
2567 while let Some(response) = response_stream.next().await {
2568 match response? {
2569 Some(Response::Package(name, index, version_map)) => {
2570 trace!("Received package metadata for: {name}");
2571 if let Some(index) = index {
2572 self.index
2573 .explicit()
2574 .done((name, index), Arc::new(version_map));
2575 } else {
2576 self.index.implicit().done(name, Arc::new(version_map));
2577 }
2578 }
2579 Some(Response::Installed { dist, metadata }) => {
2580 trace!("Received installed distribution metadata for: {dist}");
2581 self.index
2582 .distributions()
2583 .done(dist.distribution_id(), Arc::new(metadata));
2584 }
2585 Some(Response::Dist { dist, metadata }) => {
2586 let dist_kind = match dist {
2587 Dist::Built(_) => "built",
2588 Dist::Source(_) => "source",
2589 };
2590 trace!("Received {dist_kind} distribution metadata for: {dist}");
2591 if let MetadataResponse::Unavailable(reason) = &metadata {
2592 let message = UnavailableVersion::from(reason).singular_message();
2593 if let Some(err) = reason.source() {
2594 warn!("{dist} {message}: {err}");
2596 } else {
2597 warn!("{dist} {message}");
2598 }
2599 }
2600 self.index
2601 .distributions()
2602 .done(dist.distribution_id(), Arc::new(metadata));
2603 }
2604 None => {}
2605 }
2606 }
2607
2608 Ok::<(), ResolveError>(())
2609 }
2610
2611 #[instrument(skip_all, fields(%request))]
2612 async fn process_request<Provider: ResolverProvider>(
2613 &self,
2614 request: Request,
2615 provider: &Provider,
2616 ) -> Result<Option<Response>, ResolveError> {
2617 match request {
2618 Request::Package(package_name, index) => {
2620 let package_versions = provider
2621 .get_package_versions(&package_name, index.as_ref())
2622 .boxed_local()
2623 .await
2624 .map_err(ResolveError::Client)?;
2625
2626 Ok(Some(Response::Package(
2627 package_name,
2628 index.map(IndexMetadata::into_url),
2629 package_versions,
2630 )))
2631 }
2632
2633 Request::Dist(dist) => {
2635 if let Some(version) = dist.version() {
2636 if let Some(index) = dist.index() {
2637 let versions_response = self.index.implicit().get(dist.name());
2639 if let Some(VersionsResponse::Found(version_maps)) =
2640 versions_response.as_deref()
2641 {
2642 for version_map in version_maps {
2643 if version_map.index() == Some(index) {
2644 let Some(metadata) = version_map.get_metadata(version) else {
2645 continue;
2646 };
2647 debug!("Found registry-provided metadata for: {dist}");
2648 return Ok(Some(Response::Dist {
2649 dist,
2650 metadata: MetadataResponse::Found(
2651 ArchiveMetadata::from_metadata23(metadata),
2652 ),
2653 }));
2654 }
2655 }
2656 }
2657
2658 let versions_response = self
2660 .index
2661 .explicit()
2662 .get(&(dist.name().clone(), index.clone()));
2663 if let Some(VersionsResponse::Found(version_maps)) =
2664 versions_response.as_deref()
2665 {
2666 for version_map in version_maps {
2667 let Some(metadata) = version_map.get_metadata(version) else {
2668 continue;
2669 };
2670 debug!("Found registry-provided metadata for: {dist}");
2671 return Ok(Some(Response::Dist {
2672 dist,
2673 metadata: MetadataResponse::Found(
2674 ArchiveMetadata::from_metadata23(metadata),
2675 ),
2676 }));
2677 }
2678 }
2679 }
2680 }
2681
2682 let metadata = provider
2683 .get_or_build_wheel_metadata(&dist)
2684 .boxed_local()
2685 .await?;
2686
2687 if let MetadataResponse::Found(metadata) = &metadata {
2688 if &metadata.metadata.name != dist.name() {
2689 return Err(ResolveError::MismatchedPackageName {
2690 request: "distribution metadata",
2691 expected: dist.name().clone(),
2692 actual: metadata.metadata.name.clone(),
2693 });
2694 }
2695 }
2696
2697 Ok(Some(Response::Dist { dist, metadata }))
2698 }
2699
2700 Request::Installed(dist) => {
2701 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2702
2703 if let MetadataResponse::Found(metadata) = &metadata {
2704 if &metadata.metadata.name != dist.name() {
2705 return Err(ResolveError::MismatchedPackageName {
2706 request: "installed metadata",
2707 expected: dist.name().clone(),
2708 actual: metadata.metadata.name.clone(),
2709 });
2710 }
2711 }
2712
2713 Ok(Some(Response::Installed { dist, metadata }))
2714 }
2715
2716 Request::Prefetch(package_name, range, python_requirement) => {
2718 let versions_response = self
2720 .index
2721 .implicit()
2722 .wait(&package_name)
2723 .await
2724 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2725
2726 let version_map = match *versions_response {
2727 VersionsResponse::Found(ref version_map) => version_map,
2728 VersionsResponse::NoIndex => {
2730 self.unavailable_packages
2731 .pin()
2732 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2733
2734 return Ok(None);
2735 }
2736 VersionsResponse::Offline => {
2737 self.unavailable_packages
2738 .pin()
2739 .insert(package_name.clone(), UnavailablePackage::Offline);
2740
2741 return Ok(None);
2742 }
2743 VersionsResponse::NotFound => {
2744 self.unavailable_packages
2745 .pin()
2746 .insert(package_name.clone(), UnavailablePackage::NotFound);
2747
2748 return Ok(None);
2749 }
2750 };
2751
2752 let env = ResolverEnvironment::universal(vec![]);
2754
2755 let Some(candidate) = self.selector.select(
2758 &package_name,
2759 &range,
2760 version_map,
2761 &self.preferences,
2762 &self.installed_packages,
2763 &self.exclusions,
2764 None,
2765 &env,
2766 self.tags.as_ref(),
2767 ) else {
2768 return Ok(None);
2769 };
2770
2771 let Some(dist) = candidate.compatible() else {
2773 return Ok(None);
2774 };
2775
2776 for version_map in version_map {
2778 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2779 let dist = dist.for_resolution();
2780 if version_map.index() == dist.index() {
2781 debug!("Found registry-provided metadata for: {dist}");
2782
2783 let metadata =
2784 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2785
2786 let dist = dist.to_owned();
2787 if &package_name != dist.name() {
2788 return Err(ResolveError::MismatchedPackageName {
2789 request: "distribution",
2790 expected: package_name,
2791 actual: dist.name().clone(),
2792 });
2793 }
2794
2795 let response = match dist {
2796 ResolvedDist::Installable { dist, .. } => Response::Dist {
2797 dist: (*dist).clone(),
2798 metadata,
2799 },
2800 ResolvedDist::Installed { dist } => Response::Installed {
2801 dist: (*dist).clone(),
2802 metadata,
2803 },
2804 };
2805
2806 return Ok(Some(response));
2807 }
2808 }
2809 }
2810
2811 if dist.wheel().is_none() {
2815 if !self.selector.use_highest_version(&package_name, &env) {
2816 if let Some((lower, _)) = range.iter().next() {
2817 if lower == Bound::Unbounded {
2818 debug!(
2819 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2820 );
2821 return Ok(None);
2822 }
2823 }
2824 }
2825 }
2826
2827 let requires_python = match dist {
2829 CompatibleDist::InstalledDist(_) => None,
2830 CompatibleDist::SourceDist { sdist, .. }
2831 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2832 sdist.file.requires_python.as_ref()
2833 }
2834 CompatibleDist::CompatibleWheel { wheel, .. } => {
2835 wheel.file.requires_python.as_ref()
2836 }
2837 };
2838 if let Some(requires_python) = requires_python.as_ref() {
2839 if !python_requirement.target().is_contained_by(requires_python) {
2840 return Ok(None);
2841 }
2842 }
2843
2844 if !self
2846 .hasher
2847 .allows_package(candidate.name(), candidate.version())
2848 {
2849 return Ok(None);
2850 }
2851
2852 let dist = dist.for_resolution();
2854 if self.index.distributions().register(dist.distribution_id()) {
2855 let dist = dist.to_owned();
2856 if &package_name != dist.name() {
2857 return Err(ResolveError::MismatchedPackageName {
2858 request: "distribution",
2859 expected: package_name,
2860 actual: dist.name().clone(),
2861 });
2862 }
2863
2864 let response = match dist {
2865 ResolvedDist::Installable { dist, .. } => {
2866 let metadata = provider
2867 .get_or_build_wheel_metadata(&dist)
2868 .boxed_local()
2869 .await?;
2870
2871 Response::Dist {
2872 dist: (*dist).clone(),
2873 metadata,
2874 }
2875 }
2876 ResolvedDist::Installed { dist } => {
2877 let metadata =
2878 provider.get_installed_metadata(&dist).boxed_local().await?;
2879
2880 Response::Installed {
2881 dist: (*dist).clone(),
2882 metadata,
2883 }
2884 }
2885 };
2886
2887 Ok(Some(response))
2888 } else {
2889 Ok(None)
2890 }
2891 }
2892 }
2893 }
2894
2895 fn convert_no_solution_err(
2896 &self,
2897 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2898 fork_urls: ForkUrls,
2899 fork_indexes: ForkIndexes,
2900 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2901 env: ResolverEnvironment,
2902 current_environment: MarkerEnvironment,
2903 visited: &FxHashSet<PackageName>,
2904 ) -> ResolveError {
2905 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2906 err,
2907 ));
2908 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2909
2910 let mut unavailable_packages = FxHashMap::default();
2911 for package in derivation_tree_packages(&err) {
2912 if let PubGrubPackageInner::Package { name, .. } = &**package {
2913 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2914 unavailable_packages.insert(name.clone(), reason.clone());
2915 }
2916 }
2917 }
2918
2919 let mut incomplete_packages = FxHashMap::default();
2920 let incomplete_packages_cache = self.incomplete_packages.pin();
2921 for package in derivation_tree_packages(&err) {
2922 if let PubGrubPackageInner::Package { name, .. } = &**package
2923 && let Some(versions) = incomplete_packages_cache.get(name)
2924 {
2925 for (version, reason) in &versions.pin() {
2926 incomplete_packages
2927 .entry(name.clone())
2928 .or_insert_with(BTreeMap::default)
2929 .insert(version.clone(), reason.clone());
2930 }
2931 }
2932 }
2933
2934 let mut available_indexes = FxHashMap::default();
2935 let mut included_versions = FxHashMap::default();
2936 let mut available_versions = FxHashMap::default();
2937
2938 let available_version_cutoff: Option<jiff::Timestamp> =
2939 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2940 .ok()
2941 .and_then(|s| s.parse().ok());
2942
2943 for package in derivation_tree_packages(&err) {
2944 let Some(name) = package.name() else { continue };
2945 if !visited.contains(name) {
2946 continue;
2951 }
2952 let versions_response = if let Some(index) = fork_indexes.get(name) {
2953 self.index
2954 .explicit()
2955 .get(&(name.clone(), index.url().clone()))
2956 } else {
2957 self.index.implicit().get(name)
2958 };
2959 if let Some(response) = versions_response {
2960 if let VersionsResponse::Found(ref version_maps) = *response {
2961 for version_map in version_maps {
2963 let package_included_versions = included_versions
2964 .entry(name.clone())
2965 .or_insert_with(BTreeSet::new);
2966 let package_available_versions = available_versions
2967 .entry(name.clone())
2968 .or_insert_with(BTreeSet::new);
2969
2970 for (version, dists) in version_map.iter(&Ranges::full()) {
2971 let excluded_from_included = || {
2976 let Some(included_version_cutoff) =
2977 version_map.included_version_cutoff()
2978 else {
2979 return false;
2980 };
2981 let Some(prioritized_dist) = dists.prioritized_dist() else {
2982 return true;
2983 };
2984 prioritized_dist.files().all(|file| {
2985 file.upload_time_utc_ms.is_none_or(|upload_time| {
2986 upload_time >= included_version_cutoff.as_millisecond()
2987 })
2988 })
2989 };
2990
2991 if !excluded_from_included() {
2992 package_included_versions.insert(version.clone());
2993 }
2994
2995 let excluded_from_available = || {
3001 let Some(ref exclude_newer) = available_version_cutoff else {
3002 return false;
3003 };
3004 let Some(prioritized_dist) = dists.prioritized_dist() else {
3005 return false;
3006 };
3007 prioritized_dist.files().all(|file| {
3008 file.upload_time_utc_ms.is_some_and(|upload_time| {
3009 upload_time >= exclude_newer.as_millisecond()
3010 })
3011 })
3012 };
3013
3014 if !excluded_from_available() {
3015 package_available_versions.insert(version.clone());
3016 }
3017 }
3018 }
3019
3020 available_indexes
3022 .entry(name.clone())
3023 .or_insert(BTreeSet::new())
3024 .extend(
3025 version_maps
3026 .iter()
3027 .filter_map(|version_map| version_map.index().cloned()),
3028 );
3029 }
3030 }
3031 }
3032
3033 ResolveError::NoSolution(Box::new(NoSolutionError::new(
3034 err,
3035 self.index.clone(),
3036 included_versions,
3037 available_versions,
3038 available_indexes,
3039 self.selector.clone(),
3040 self.python_requirement.clone(),
3041 self.locations.clone(),
3042 self.capabilities.clone(),
3043 unavailable_packages,
3044 incomplete_packages,
3045 fork_urls,
3046 fork_indexes,
3047 env,
3048 current_environment,
3049 self.tags.clone(),
3050 self.workspace_members.clone(),
3051 self.options.clone(),
3052 )))
3053 }
3054
3055 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
3056 if let Some(reporter) = self.reporter.as_ref() {
3057 match &**package {
3058 PubGrubPackageInner::Root(_) => {}
3059 PubGrubPackageInner::Python(_) => {}
3060 PubGrubPackageInner::System(_) => {}
3061 PubGrubPackageInner::Marker { .. } => {}
3062 PubGrubPackageInner::Extra { .. } => {}
3063 PubGrubPackageInner::Group { .. } => {}
3064 PubGrubPackageInner::Package { name, .. } => {
3065 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
3066 }
3067 }
3068 }
3069 }
3070
3071 fn on_complete(&self) {
3072 if let Some(reporter) = self.reporter.as_ref() {
3073 reporter.on_complete();
3074 }
3075 }
3076}
3077
3078#[derive(Clone)]
3080pub(crate) struct ForkState {
3081 pubgrub: State<UvDependencyProvider>,
3089 initial_id: Option<Id<PubGrubPackage>>,
3092 initial_version: Option<Version>,
3095 next: Id<PubGrubPackage>,
3097 pins: FilePins,
3106 fork_urls: ForkUrls,
3112 fork_indexes: ForkIndexes,
3117 priorities: PubGrubPriorities,
3123 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3126 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3128 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3130 known_versions: FxHashMap<PackageName, Arc<[Version]>>,
3135 env: ResolverEnvironment,
3150 python_requirement: PythonRequirement,
3163 conflict_tracker: ConflictTracker,
3164 prefetcher: BatchPrefetcher,
3168}
3169
3170impl ForkState {
3171 fn new(
3172 pubgrub: State<UvDependencyProvider>,
3173 env: ResolverEnvironment,
3174 python_requirement: PythonRequirement,
3175 prefetcher: BatchPrefetcher,
3176 ) -> Self {
3177 Self {
3178 initial_id: None,
3179 initial_version: None,
3180 next: pubgrub.root_package,
3181 pubgrub,
3182 pins: FilePins::default(),
3183 fork_urls: ForkUrls::default(),
3184 fork_indexes: ForkIndexes::default(),
3185 priorities: PubGrubPriorities::default(),
3186 added_dependencies: FxHashMap::default(),
3187 pre_visited: FxHashMap::default(),
3188 selected_versions: FxHashMap::default(),
3189 known_versions: FxHashMap::default(),
3190 env,
3191 python_requirement,
3192 conflict_tracker: ConflictTracker::default(),
3193 prefetcher,
3194 }
3195 }
3196
3197 fn visit_package_version_dependencies(
3200 &mut self,
3201 for_package: Id<PubGrubPackage>,
3202 for_version: &Version,
3203 urls: &Urls,
3204 indexes: &Indexes,
3205 dependencies: &[PubGrubDependency],
3206 git: &GitResolver,
3207 workspace_members: &BTreeSet<PackageName>,
3208 resolution_strategy: &ResolutionStrategy,
3209 ) -> Result<(), ResolveError> {
3210 for dependency in dependencies {
3211 let PubGrubDependency {
3212 package,
3213 version,
3214 parent: _,
3215 source,
3216 } = dependency;
3217
3218 let mut has_url = false;
3219 if let Some(name) = package.name() {
3220 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3225 self.fork_urls.insert(name, url, &self.env)?;
3226 has_url = true;
3227 }
3228
3229 if let Some(index) = source.explicit_index() {
3230 self.fork_indexes.insert(name, index, &self.env)?;
3231 }
3232
3233 for index in indexes.get(name, &self.env) {
3235 self.fork_indexes.insert(name, index, &self.env)?;
3236 }
3237 }
3238
3239 if let Some(name) = self.pubgrub.package_store[for_package]
3240 .name_no_root()
3241 .filter(|name| !workspace_members.contains(name))
3242 {
3243 debug!(
3244 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3245 );
3246 } else {
3247 debug!("Adding direct dependency: {package}{version}");
3249
3250 let missing_lower_bound = version
3252 .bounding_range()
3253 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3254 let strategy_lowest = matches!(
3255 resolution_strategy,
3256 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3257 );
3258
3259 if !has_url && missing_lower_bound && strategy_lowest {
3260 let name = package.name_no_root().unwrap();
3261 let bound_on_other_package = dependencies.iter().any(|other| {
3268 Some(name) == other.package.name()
3269 && !other
3270 .version
3271 .bounding_range()
3272 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3273 });
3274
3275 if !bound_on_other_package {
3276 warn_user_once!(
3277 "The direct dependency `{name}` is unpinned. \
3278 Consider setting a lower bound when using `--resolution lowest` \
3279 or `--resolution lowest-direct` to avoid using outdated versions.",
3280 );
3281 }
3282 }
3283 }
3284
3285 self.priorities.insert(package, version, &self.fork_urls);
3287 if let Some(base_package) = package.base_package() {
3290 self.priorities
3291 .insert(&base_package, version, &self.fork_urls);
3292 }
3293 }
3294
3295 Ok(())
3296 }
3297
3298 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3304 &mut self,
3305 for_package: Id<PubGrubPackage>,
3306 for_version: &Version,
3307 dependencies: Vec<PubGrubDependency>,
3308 index: &InMemoryIndex,
3309 installed_packages: &InstalledPackages,
3310 ) {
3311 for dependency in &dependencies {
3312 let PubGrubDependency {
3313 package,
3314 version,
3315 parent: _,
3316 source: _,
3317 } = dependency;
3318
3319 let Some(base_package) = package.base_package() else {
3320 continue;
3321 };
3322
3323 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3324 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3325 self.pubgrub.add_proxy_package_incompatibility(
3326 proxy_package,
3327 base_package_id,
3328 version.clone(),
3329 );
3330 }
3331
3332 let versions = Range::singleton(for_version.clone());
3335 let versions = if let Some(known_versions) =
3336 ResolverState::<InstalledPackages>::known_versions(
3337 index,
3338 installed_packages,
3339 &self.fork_urls,
3340 &self.fork_indexes,
3341 &mut self.known_versions,
3342 &self.pubgrub.package_store[self.next],
3343 )
3344 .filter(|versions| !versions.is_empty())
3345 {
3346 versions.widen_versions(known_versions)
3347 } else {
3348 versions
3351 };
3352 let conflict = self.pubgrub.add_package_version_dependencies(
3353 self.next,
3354 for_version.clone(),
3355 versions,
3356 dependencies.into_iter().map(|dependency| {
3357 let PubGrubDependency {
3358 package,
3359 version,
3360 parent: _,
3361 source: _,
3362 } = dependency;
3363 (package, version)
3364 }),
3365 );
3366
3367 if let Some(incompatibility) = conflict {
3370 self.record_conflict(for_package, Some(for_version), incompatibility);
3371 }
3372 }
3373
3374 fn record_conflict(
3375 &mut self,
3376 affected: Id<PubGrubPackage>,
3377 version: Option<&Version>,
3378 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3379 ) {
3380 let mut culprit_is_real = false;
3381 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3382 if incompatible == affected {
3383 continue;
3384 }
3385 if self.pubgrub.package_store[affected].name()
3386 == self.pubgrub.package_store[incompatible].name()
3387 {
3388 continue;
3391 }
3392 culprit_is_real = true;
3393 let culprit_count = self
3394 .conflict_tracker
3395 .culprit
3396 .entry(incompatible)
3397 .or_default();
3398 *culprit_count += 1;
3399 if *culprit_count == CONFLICT_THRESHOLD {
3400 self.conflict_tracker.deprioritize.push(incompatible);
3401 }
3402 }
3403 if culprit_is_real {
3406 if tracing::enabled!(Level::DEBUG) {
3407 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3408 .iter()
3409 .map(|(package, _term)| &self.pubgrub.package_store[package])
3410 .join(", ");
3411 if let Some(version) = version {
3412 debug!(
3413 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3414 self.pubgrub.package_store[affected], version, incompatibility
3415 );
3416 } else {
3417 debug!(
3418 "Recording unit propagation conflict of {} from incompatibility of ({})",
3419 self.pubgrub.package_store[affected], incompatibility
3420 );
3421 }
3422 }
3423
3424 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3425 *affected_count += 1;
3426 if *affected_count == CONFLICT_THRESHOLD {
3427 self.conflict_tracker.prioritize.push(self.next);
3428 }
3429 }
3430 }
3431
3432 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3433 if let UnavailableVersion::IncompatibleDist(
3437 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3438 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3439 ) = reason
3440 {
3441 let package = &self.next;
3442 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3443 PubGrubPackageInner::Python(match kind {
3444 PythonRequirementKind::Installed => PubGrubPython::Installed,
3445 PythonRequirementKind::Target => PubGrubPython::Target,
3446 }),
3447 ));
3448 self.pubgrub
3449 .add_incompatibility(Incompatibility::from_dependency(
3450 *package,
3451 Range::singleton(version.clone()),
3452 (
3453 python,
3454 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3455 ),
3456 ));
3457 self.pubgrub
3458 .partial_solution
3459 .add_decision(self.next, version);
3460 return;
3461 }
3462 self.pubgrub
3463 .add_incompatibility(Incompatibility::custom_version(
3464 self.next,
3465 version.clone(),
3466 UnavailableReason::Version(reason),
3467 ));
3468 }
3469
3470 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3476 self.selected_versions.clear();
3477 self.env = env;
3478 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3480 debug!("Narrowed `requires-python` bound to: {}", req.target());
3481 self.python_requirement = req;
3482 }
3483 self
3484 }
3485
3486 fn source(
3490 &self,
3491 name: &PackageName,
3492 version: &Version,
3493 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3494 let url = self.fork_urls.get(name);
3495 let index = url
3496 .is_none()
3497 .then(|| {
3498 self.pins
3499 .get(name, version)
3500 .expect("Every package should be pinned")
3501 .index()
3502 })
3503 .flatten();
3504 (url, index)
3505 }
3506
3507 fn into_resolution(self) -> Resolution {
3508 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3509 let edge_count: usize = solution
3510 .keys()
3511 .map(|package| self.pubgrub.incompatibilities[package].len())
3512 .sum();
3513 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3514 for (package, self_version) in &solution {
3515 for id in &self.pubgrub.incompatibilities[package] {
3516 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3517 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3518 &incompatibility.kind
3519 else {
3520 continue;
3521 };
3522 let (self_package, dependency_package) = (*self_package, *dependency_package);
3523 let Some((self_range, dependency_range)) =
3524 incompatibility.dependency_version_sets()
3525 else {
3526 continue;
3527 };
3528 let dependency_range =
3529 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3530 if *package != self_package {
3531 continue;
3532 }
3533 if !self_range.contains(self_version) {
3534 continue;
3535 }
3536 let Some(dependency_version) = solution.get(&dependency_package) else {
3537 continue;
3538 };
3539 if !dependency_range.contains(dependency_version) {
3540 continue;
3541 }
3542
3543 let self_package = &self.pubgrub.package_store[self_package];
3544 let dependency_package = &self.pubgrub.package_store[dependency_package];
3545
3546 let (self_name, self_extra, self_group) = match &**self_package {
3547 PubGrubPackageInner::Package {
3548 name: self_name,
3549 extra: self_extra,
3550 group: self_group,
3551 marker: _,
3552 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3553
3554 PubGrubPackageInner::Root(_) => (None, None, None),
3555
3556 _ => continue,
3557 };
3558
3559 let (self_url, self_index) = self_name
3560 .map(|self_name| self.source(self_name, self_version))
3561 .unwrap_or((None, None));
3562
3563 match **dependency_package {
3564 PubGrubPackageInner::Package {
3565 name: ref dependency_name,
3566 extra: ref dependency_extra,
3567 group: ref dependency_dev,
3568 marker: ref dependency_marker,
3569 } => {
3570 debug_assert!(
3571 dependency_extra.is_none(),
3572 "Packages should depend on an extra proxy"
3573 );
3574 debug_assert!(
3575 dependency_dev.is_none(),
3576 "Packages should depend on a group proxy"
3577 );
3578
3579 if self_group.is_none() {
3582 if self_name == Some(dependency_name) {
3583 continue;
3584 }
3585 }
3586
3587 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3588
3589 let edge = ResolutionDependencyEdge {
3590 from: self_name.cloned(),
3591 from_version: self_version.clone(),
3592 from_url: self_url.cloned(),
3593 from_index: self_index.cloned(),
3594 from_extra: self_extra.cloned(),
3595 from_group: self_group.cloned(),
3596 to: dependency_name.clone(),
3597 to_version: dependency_version.clone(),
3598 to_url: to_url.cloned(),
3599 to_index: to_index.cloned(),
3600 to_extra: dependency_extra.clone(),
3601 to_group: dependency_dev.clone(),
3602 marker: *dependency_marker,
3603 };
3604 edges.push(edge);
3605 }
3606
3607 PubGrubPackageInner::Marker {
3608 name: ref dependency_name,
3609 marker: ref dependency_marker,
3610 } => {
3611 if self_group.is_none() {
3614 if self_name == Some(dependency_name) {
3615 continue;
3616 }
3617 }
3618
3619 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3620
3621 let edge = ResolutionDependencyEdge {
3622 from: self_name.cloned(),
3623 from_version: self_version.clone(),
3624 from_url: self_url.cloned(),
3625 from_index: self_index.cloned(),
3626 from_extra: self_extra.cloned(),
3627 from_group: self_group.cloned(),
3628 to: dependency_name.clone(),
3629 to_version: dependency_version.clone(),
3630 to_url: to_url.cloned(),
3631 to_index: to_index.cloned(),
3632 to_extra: None,
3633 to_group: None,
3634 marker: *dependency_marker,
3635 };
3636 edges.push(edge);
3637 }
3638
3639 PubGrubPackageInner::Extra {
3640 name: ref dependency_name,
3641 extra: ref dependency_extra,
3642 marker: ref dependency_marker,
3643 } => {
3644 if self_group.is_none() {
3645 debug_assert!(
3646 self_name != Some(dependency_name),
3647 "Extras should be flattened"
3648 );
3649 }
3650 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3651
3652 let edge = ResolutionDependencyEdge {
3654 from: self_name.cloned(),
3655 from_version: self_version.clone(),
3656 from_url: self_url.cloned(),
3657 from_index: self_index.cloned(),
3658 from_extra: self_extra.cloned(),
3659 from_group: self_group.cloned(),
3660 to: dependency_name.clone(),
3661 to_version: dependency_version.clone(),
3662 to_url: to_url.cloned(),
3663 to_index: to_index.cloned(),
3664 to_extra: Some(dependency_extra.clone()),
3665 to_group: None,
3666 marker: *dependency_marker,
3667 };
3668 edges.push(edge);
3669
3670 let edge = ResolutionDependencyEdge {
3672 from: self_name.cloned(),
3673 from_version: self_version.clone(),
3674 from_url: self_url.cloned(),
3675 from_index: self_index.cloned(),
3676 from_extra: self_extra.cloned(),
3677 from_group: self_group.cloned(),
3678 to: dependency_name.clone(),
3679 to_version: dependency_version.clone(),
3680 to_url: to_url.cloned(),
3681 to_index: to_index.cloned(),
3682 to_extra: None,
3683 to_group: None,
3684 marker: *dependency_marker,
3685 };
3686 edges.push(edge);
3687 }
3688
3689 PubGrubPackageInner::Group {
3690 name: ref dependency_name,
3691 group: ref dependency_group,
3692 marker: ref dependency_marker,
3693 } => {
3694 debug_assert!(
3695 self_name != Some(dependency_name),
3696 "Groups should be flattened"
3697 );
3698
3699 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3700
3701 let edge = ResolutionDependencyEdge {
3704 from: self_name.cloned(),
3705 from_version: self_version.clone(),
3706 from_url: self_url.cloned(),
3707 from_index: self_index.cloned(),
3708 from_extra: self_extra.cloned(),
3709 from_group: self_group.cloned(),
3710 to: dependency_name.clone(),
3711 to_version: dependency_version.clone(),
3712 to_url: to_url.cloned(),
3713 to_index: to_index.cloned(),
3714 to_extra: None,
3715 to_group: Some(dependency_group.clone()),
3716 marker: *dependency_marker,
3717 };
3718 edges.push(edge);
3719 }
3720
3721 _ => {}
3722 }
3723 }
3724 }
3725
3726 let nodes = solution
3727 .into_iter()
3728 .filter_map(|(package, version)| {
3729 if let PubGrubPackageInner::Package {
3730 name,
3731 extra,
3732 group,
3733 marker: MarkerTree::TRUE,
3734 } = &*self.pubgrub.package_store[package]
3735 {
3736 let (url, index) = self.source(name, &version);
3737 Some((
3738 ResolutionPackage {
3739 name: name.clone(),
3740 extra: extra.clone(),
3741 dev: group.clone(),
3742 url: url.cloned(),
3743 index: index.cloned(),
3744 },
3745 version,
3746 ))
3747 } else {
3748 None
3749 }
3750 })
3751 .collect();
3752
3753 Resolution {
3754 nodes,
3755 edges,
3756 pins: self.pins,
3757 env: self.env,
3758 }
3759 }
3760}
3761
3762#[derive(Debug)]
3764pub(crate) struct Resolution {
3765 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3766 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3769 pub(crate) pins: FilePins,
3771 pub(crate) env: ResolverEnvironment,
3773}
3774
3775#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3778pub(crate) struct ResolutionPackage {
3779 pub(crate) name: PackageName,
3780 pub(crate) extra: Option<ExtraName>,
3781 pub(crate) dev: Option<GroupName>,
3782 pub(crate) url: Option<VerbatimParsedUrl>,
3784 pub(crate) index: Option<IndexUrl>,
3786}
3787
3788#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3791pub(crate) struct ResolutionDependencyEdge {
3792 pub(super) from: Option<PackageName>,
3794 pub(super) from_version: Version,
3795 pub(super) from_url: Option<VerbatimParsedUrl>,
3796 pub(super) from_index: Option<IndexUrl>,
3797 pub(super) from_extra: Option<ExtraName>,
3798 pub(super) from_group: Option<GroupName>,
3799 pub(super) to: PackageName,
3800 pub(super) to_version: Version,
3801 pub(super) to_url: Option<VerbatimParsedUrl>,
3802 pub(super) to_index: Option<IndexUrl>,
3803 pub(super) to_extra: Option<ExtraName>,
3804 pub(super) to_group: Option<GroupName>,
3805 pub(super) marker: MarkerTree,
3806}
3807
3808impl ResolutionDependencyEdge {
3809 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3810 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3814 }
3815}
3816
3817#[derive(Debug)]
3819#[expect(clippy::large_enum_variant)]
3820pub(crate) enum Request {
3821 Package(PackageName, Option<IndexMetadata>),
3823 Dist(Dist),
3825 Installed(InstalledDist),
3827 Prefetch(PackageName, Range<Version>, PythonRequirement),
3829}
3830
3831impl<'a> From<ResolvedDistRef<'a>> for Request {
3832 fn from(dist: ResolvedDistRef<'a>) -> Self {
3833 match dist {
3839 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3840 let source = prioritized.source_dist().expect("a source distribution");
3843 assert_eq!(
3844 (&sdist.name, &sdist.version),
3845 (&source.name, &source.version),
3846 "expected chosen sdist to match prioritized sdist"
3847 );
3848 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3849 }
3850 ResolvedDistRef::InstallableRegistryBuiltDist {
3851 wheel, prioritized, ..
3852 } => {
3853 assert_eq!(
3854 Some(&wheel.filename),
3855 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3856 "expected chosen wheel to match best wheel"
3857 );
3858 let built = prioritized.built_dist().expect("at least one wheel");
3861 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3862 }
3863 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3864 }
3865 }
3866}
3867
3868impl Display for Request {
3869 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3870 match self {
3871 Self::Package(package_name, _) => {
3872 write!(f, "Versions {package_name}")
3873 }
3874 Self::Dist(dist) => {
3875 write!(f, "Metadata {dist}")
3876 }
3877 Self::Installed(dist) => {
3878 write!(f, "Installed metadata {dist}")
3879 }
3880 Self::Prefetch(package_name, range, _) => {
3881 write!(f, "Prefetch {package_name} {range}")
3882 }
3883 }
3884 }
3885}
3886
3887#[derive(Debug)]
3888#[expect(clippy::large_enum_variant)]
3889enum Response {
3890 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3892 Dist {
3894 dist: Dist,
3895 metadata: MetadataResponse,
3896 },
3897 Installed {
3899 dist: InstalledDist,
3900 metadata: MetadataResponse,
3901 },
3902}
3903
3904enum Dependencies {
3910 Unavailable(UnavailableVersion),
3912 Available(Vec<PubGrubDependency>),
3918 RequiresPython(VersionSpecifiers),
3920 Unforkable(Vec<PubGrubDependency>),
3926}
3927
3928impl Dependencies {
3929 fn fork(
3936 self,
3937 env: &ResolverEnvironment,
3938 python_requirement: &PythonRequirement,
3939 conflicts: &Conflicts,
3940 ) -> ForkedDependencies {
3941 let deps = match self {
3942 Self::Available(deps) => deps,
3943 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3944 Self::RequiresPython(requires_python) => {
3945 return ForkedDependencies::RequiresPython(requires_python);
3946 }
3947 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3948 };
3949 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3950 for dep in deps {
3951 let name = dep
3952 .package
3953 .name()
3954 .expect("dependency always has a name")
3955 .clone();
3956 name_to_deps.entry(name).or_default().push(dep);
3957 }
3958 let Forks {
3959 mut forks,
3960 diverging_packages,
3961 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3962 if forks.is_empty() {
3963 ForkedDependencies::Unforked(vec![])
3964 } else if forks.len() == 1 {
3965 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3966 } else {
3967 ForkedDependencies::Forked {
3968 forks,
3969 diverging_packages: diverging_packages.into_iter().collect(),
3970 }
3971 }
3972 }
3973}
3974
3975#[derive(Debug)]
3982enum ForkedDependencies {
3983 Unavailable(UnavailableVersion),
3985 Unforked(Vec<PubGrubDependency>),
3989 Forked {
3995 forks: Vec<Fork>,
3996 diverging_packages: Vec<PackageName>,
3998 },
3999 RequiresPython(VersionSpecifiers),
4001}
4002
4003#[derive(Debug, Default)]
4008struct Forks {
4009 forks: Vec<Fork>,
4011 diverging_packages: BTreeSet<PackageName>,
4013}
4014
4015impl Forks {
4016 fn new(
4017 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
4018 env: &ResolverEnvironment,
4019 python_requirement: &PythonRequirement,
4020 conflicts: &Conflicts,
4021 ) -> Self {
4022 let python_marker = python_requirement.to_marker_tree();
4023
4024 let mut forks = vec![Fork::new(env.clone())];
4025 let mut diverging_packages = BTreeSet::new();
4026 for (name, mut deps) in name_to_deps {
4027 assert!(!deps.is_empty(), "every name has at least one dependency");
4028 if let [dep] = deps.as_slice() {
4039 if marker::requires_python(dep.package.marker())
4047 .is_none_or(|bound| !python_requirement.raises(&bound))
4048 {
4049 let dep = deps.pop().unwrap();
4050 let marker = dep.package.marker();
4051 for fork in &mut forks {
4052 if fork.env.included_by_marker(marker) {
4053 fork.add_dependency(dep.clone());
4054 }
4055 }
4056 continue;
4057 }
4058 } else {
4059 if let Some(dep) = deps.first() {
4061 let marker = dep.package.marker();
4062 if deps.iter().all(|dep| marker == dep.package.marker()) {
4063 if marker::requires_python(marker)
4067 .is_none_or(|bound| !python_requirement.raises(&bound))
4068 {
4069 for dep in deps {
4070 for fork in &mut forks {
4071 if fork.env.included_by_marker(marker) {
4072 fork.add_dependency(dep.clone());
4073 }
4074 }
4075 }
4076 continue;
4077 }
4078 }
4079 }
4080 }
4081 for dep in deps {
4082 let mut forker = match ForkingPossibility::new(env, &dep) {
4083 ForkingPossibility::Possible(forker) => forker,
4084 ForkingPossibility::DependencyAlwaysExcluded => {
4085 continue;
4088 }
4089 ForkingPossibility::NoForkingPossible => {
4090 for fork in &mut forks {
4093 fork.add_dependency(dep.clone());
4094 }
4095 continue;
4096 }
4097 };
4098 diverging_packages.insert(name.clone());
4100
4101 let mut new = vec![];
4102 for fork in std::mem::take(&mut forks) {
4103 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4104 new.push(fork);
4105 continue;
4106 };
4107 forker = remaining_forker;
4108
4109 for fork_env in envs {
4110 let mut new_fork = fork.clone();
4111 new_fork.set_env(fork_env);
4112 if forker.included(&new_fork.env) {
4117 new_fork.add_dependency(dep.clone());
4118 }
4119 if new_fork.env.included_by_marker(python_marker) {
4122 new.push(new_fork);
4123 }
4124 }
4125 }
4126 forks = new;
4127 }
4128 }
4129 for set in conflicts.iter() {
4144 let mut new = vec![];
4145 for fork in std::mem::take(&mut forks) {
4146 let mut has_conflicting_dependency = false;
4154 for item in set.iter() {
4155 if fork.contains_conflicting_item(item.as_ref()) {
4156 has_conflicting_dependency = true;
4157 diverging_packages.insert(item.package().clone());
4158 break;
4159 }
4160 }
4161 if !has_conflicting_dependency {
4162 new.push(fork);
4163 continue;
4164 }
4165
4166 let non_excluded: Vec<_> = set
4172 .iter()
4173 .filter(|item| fork.env.included_by_group(item.as_ref()))
4174 .collect();
4175 if non_excluded.len() < 2 {
4176 let dominated = non_excluded.iter().all(|item| {
4181 !conflicts.iter().any(|other_set| {
4182 !std::ptr::eq(set, other_set)
4183 && other_set.contains(item.package(), item.kind().as_ref())
4184 && other_set
4185 .iter()
4186 .filter(|other_item| {
4187 other_item.package() != item.package()
4188 || other_item.kind() != item.kind()
4189 })
4190 .any(|other_item| {
4191 fork.env.included_by_group(other_item.as_ref())
4192 })
4193 })
4194 });
4195 if dominated {
4196 let rules: Vec<_> = set
4201 .iter()
4202 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4203 .cloned()
4204 .map(Err)
4205 .collect();
4206 if let Some(filtered) = fork.filter(rules) {
4207 new.push(filtered);
4208 }
4209 continue;
4210 }
4211 }
4212
4213 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4215 new.push(fork_none);
4216 }
4217
4218 for (i, _) in set.iter().enumerate() {
4226 let fork_allows_group = fork.clone().filter(
4227 set.iter()
4228 .cloned()
4229 .enumerate()
4230 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4231 );
4232 if let Some(fork_allows_group) = fork_allows_group {
4233 new.push(fork_allows_group);
4234 }
4235 }
4236 }
4237 forks = new;
4238 }
4239 Self {
4240 forks,
4241 diverging_packages,
4242 }
4243 }
4244}
4245
4246#[derive(Clone, Debug)]
4256struct Fork {
4257 dependencies: Vec<PubGrubDependency>,
4266 conflicts: crate::FxHashbrownSet<ConflictItem>,
4272 env: ResolverEnvironment,
4284}
4285
4286impl Fork {
4287 fn new(env: ResolverEnvironment) -> Self {
4290 Self {
4291 dependencies: vec![],
4292 conflicts: crate::FxHashbrownSet::default(),
4293 env,
4294 }
4295 }
4296
4297 fn add_dependency(&mut self, dep: PubGrubDependency) {
4299 if let Some(conflicting_item) = dep.conflicting_item() {
4300 self.conflicts.insert(conflicting_item.to_owned());
4301 }
4302 self.dependencies.push(dep);
4303 }
4304
4305 fn set_env(&mut self, env: ResolverEnvironment) {
4310 self.env = env;
4311 self.dependencies.retain(|dep| {
4312 let marker = dep.package.marker();
4313 if self.env.included_by_marker(marker) {
4314 return true;
4315 }
4316 if let Some(conflicting_item) = dep.conflicting_item() {
4317 self.conflicts.remove(&conflicting_item);
4318 }
4319 false
4320 });
4321 }
4322
4323 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4326 self.conflicts.contains(&item)
4327 }
4328
4329 fn filter(
4336 mut self,
4337 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4338 ) -> Option<Self> {
4339 self.env = self.env.filter_by_group(rules)?;
4340 self.dependencies.retain(|dep| {
4341 let Some(conflicting_item) = dep.conflicting_item() else {
4342 return true;
4343 };
4344 if self.env.included_by_group(conflicting_item) {
4345 return true;
4346 }
4347 match conflicting_item.kind() {
4348 ConflictKindRef::Project => {
4351 if dep.parent.is_some() {
4352 return true;
4353 }
4354 }
4355 ConflictKindRef::Group(_) => {}
4356 ConflictKindRef::Extra(_) => {}
4357 }
4358 self.conflicts.remove(&conflicting_item);
4359 false
4360 });
4361 Some(self)
4362 }
4363
4364 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4366 let self_bound = self.env.requires_python().unwrap_or_default();
4376 let other_bound = other.env.requires_python().unwrap_or_default();
4377 self_bound.lower().cmp(other_bound.lower())
4378 }
4379
4380 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4382 let self_upper_bounds = self
4387 .dependencies
4388 .iter()
4389 .filter(|dep| {
4390 dep.version
4391 .bounding_range()
4392 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4393 })
4394 .count();
4395 let other_upper_bounds = other
4396 .dependencies
4397 .iter()
4398 .filter(|dep| {
4399 dep.version
4400 .bounding_range()
4401 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4402 })
4403 .count();
4404
4405 self_upper_bounds.cmp(&other_upper_bounds)
4406 }
4407}
4408
4409impl Eq for Fork {}
4410
4411impl PartialEq for Fork {
4412 fn eq(&self, other: &Self) -> bool {
4413 self.dependencies == other.dependencies && self.env == other.env
4414 }
4415}
4416
4417#[derive(Debug, Clone)]
4418pub(crate) struct VersionFork {
4419 env: ResolverEnvironment,
4421 id: Id<PubGrubPackage>,
4423 version: Option<Version>,
4425}
4426
4427fn enrich_dependency_error(
4429 error: ResolveError,
4430 id: Id<PubGrubPackage>,
4431 version: &Version,
4432 pubgrub: &State<UvDependencyProvider>,
4433) -> ResolveError {
4434 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4435 return error;
4436 };
4437 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4438 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4439}
4440
4441fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4443 let package = &state.package_store[id];
4444 if package.is_root() {
4445 return MarkerTree::TRUE;
4446 }
4447
4448 let mut ancestors = FxHashSet::default();
4451 let mut stack = vec![id];
4452 let mut root = None;
4453 ancestors.insert(id);
4454
4455 while let Some(current) = stack.pop() {
4456 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4457 continue;
4458 };
4459
4460 for index in incompatibilities {
4461 let incompat = &state.incompatibility_store[*index];
4462 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4463 if current != *child {
4464 continue;
4465 }
4466 if ancestors.insert(*parent) {
4467 if state.package_store[*parent].is_root() {
4468 root = Some(*parent);
4469 }
4470 stack.push(*parent);
4471 }
4472 }
4473 }
4474 }
4475
4476 let Some(root) = root else {
4477 return MarkerTree::FALSE;
4478 };
4479
4480 let mut environments = FxHashMap::default();
4483 let mut queue = VecDeque::from([root]);
4484 environments.insert(root, MarkerTree::TRUE);
4485
4486 while let Some(current) = queue.pop_front() {
4487 let Some(current_environment) = environments.get(¤t).copied() else {
4488 continue;
4489 };
4490 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4491 continue;
4492 };
4493
4494 for index in incompatibilities {
4495 let incompat = &state.incompatibility_store[*index];
4496 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4497 continue;
4498 };
4499 if current != *parent || !ancestors.contains(child) {
4500 continue;
4501 }
4502
4503 let mut next_environment = state.package_store[*child].marker();
4504 next_environment = next_environment.and(current_environment);
4505
4506 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4507 let mut combined = *entry;
4508 combined = combined.or(next_environment);
4509 if combined != *entry {
4510 *entry = combined;
4511 queue.push_back(*child);
4512 }
4513 }
4514 }
4515
4516 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4517}
4518
4519#[derive(Debug, Default, Clone)]
4520struct ConflictTracker {
4521 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4523 prioritize: Vec<Id<PubGrubPackage>>,
4527 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4529 deprioritize: Vec<Id<PubGrubPackage>>,
4533}