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 = state.env.marker_environment().is_some()
522 && url.is_none()
523 && index.is_none();
524 let decision = if cache_selected_version
525 && let Some((selected_range, version)) =
526 state.selected_versions.get(&next_id)
527 && selected_range == range
528 {
529 Some(ResolverVersion::Unforked(version.clone()))
530 } else {
531 let decision = self.choose_version(
532 next_package,
533 next_id,
534 index.map(IndexMetadata::url),
535 range,
536 &mut state.pins,
537 &preferences,
538 &state.fork_urls,
539 &state.env,
540 &state.python_requirement,
541 &state.pubgrub,
542 &mut visited,
543 request_sink,
544 )?;
545
546 if cache_selected_version
547 && let Some(ResolverVersion::Unforked(version)) = &decision
548 {
549 state
550 .selected_versions
551 .insert(next_id, (range.clone(), version.clone()));
552 }
553
554 decision
555 };
556
557 let Some(version) = decision else {
559 debug!("No compatible version found for: {next_package}");
560
561 let term_intersection = state
562 .pubgrub
563 .partial_solution
564 .term_intersection_for_package(next_id)
565 .expect("a package was chosen but we don't have a term");
566
567 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
568 if let Some(reason) = self.unavailable_packages.pin().get(name) {
570 state
571 .pubgrub
572 .add_incompatibility(Incompatibility::custom_term(
573 next_id,
574 term_intersection.clone(),
575 UnavailableReason::Package(reason.clone()),
576 ));
577 continue;
578 }
579 }
580
581 state
582 .pubgrub
583 .add_incompatibility(Incompatibility::no_versions(
584 next_id,
585 term_intersection.clone(),
586 ));
587 continue;
588 };
589
590 let version = match version {
591 ResolverVersion::Unforked(version) => version,
592 ResolverVersion::Forked(forks) => {
593 forked_states.extend(self.version_forks_to_fork_states(state, forks));
594 continue 'FORK;
595 }
596 ResolverVersion::Unavailable(version, reason) => {
597 state.add_unavailable_version(version, reason);
598 continue;
599 }
600 };
601
602 if url.is_none() {
604 state.prefetcher.prefetch_batches(
605 next_package,
606 index,
607 &version,
608 term_intersection.unwrap_positive(),
609 state
610 .pubgrub
611 .partial_solution
612 .unchanging_term_for_package(next_id),
613 &state.python_requirement,
614 &self.selector,
615 &state.env,
616 )?;
617 }
618
619 version
620 };
621
622 state.prefetcher.version_tried(next_package, &version);
623
624 self.on_progress(next_package, &version);
625
626 if state
627 .added_dependencies
628 .get(&next_id)
629 .is_some_and(|versions| versions.contains(&version))
630 {
631 state
634 .pubgrub
635 .partial_solution
636 .add_decision(next_id, version);
637 continue;
638 }
639
640 let forked_deps = self.get_dependencies_forking(
642 next_id,
643 next_package,
644 &version,
645 &state.pins,
646 &state.fork_urls,
647 &state.env,
648 &state.python_requirement,
649 &state.pubgrub,
650 )?;
651
652 match forked_deps {
653 ForkedDependencies::Unavailable(reason) => {
654 state
657 .pubgrub
658 .add_incompatibility(Incompatibility::custom_version(
659 next_id,
660 version.clone(),
661 UnavailableReason::Version(reason),
662 ));
663 }
664 ForkedDependencies::Unforked(dependencies) => {
665 state
666 .added_dependencies
667 .entry(next_id)
668 .or_default()
669 .insert(version.clone());
670
671 state
673 .visit_package_version_dependencies(
674 next_id,
675 &version,
676 &self.urls,
677 &self.indexes,
678 &dependencies,
679 &self.git,
680 &self.workspace_members,
681 self.selector.resolution_strategy(),
682 )
683 .map_err(|err| {
684 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
685 })?;
686
687 self.visit_dependencies(&dependencies, &state, request_sink)
689 .map_err(|err| {
690 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
691 })?;
692
693 state.add_package_version_dependencies(
695 next_id,
696 &version,
697 dependencies,
698 &self.index,
699 &self.installed_packages,
700 );
701 }
702 ForkedDependencies::Forked {
703 mut forks,
704 diverging_packages,
705 } => {
706 state
707 .added_dependencies
708 .entry(next_id)
709 .or_default()
710 .insert(version.clone());
711
712 debug!(
713 "Pre-fork {} took {:.3}s",
714 state.env,
715 start.elapsed().as_secs_f32()
716 );
717
718 match (self.options.fork_strategy, self.options.resolution_mode) {
720 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
721 forks.sort_by(|a, b| {
725 a.cmp_requires_python(b)
726 .reverse()
727 .then_with(|| a.cmp_upper_bounds(b))
728 });
729 }
730 (ForkStrategy::RequiresPython, _) => {
731 forks.sort_by(|a, b| {
735 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
736 });
737 }
738 }
739
740 for new_fork_state in self.forks_to_fork_states(
741 state,
742 &version,
743 forks,
744 request_sink,
745 &diverging_packages,
746 ) {
747 forked_states.push(new_fork_state?);
748 }
749 continue 'FORK;
750 }
751 ForkedDependencies::RequiresPython(requires_python) => {
752 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
753 && state.env.marker_environment().is_none()
754 {
755 let forks = fork_version_by_python_requirement(
756 &requires_python,
757 &state.python_requirement,
758 &state.env,
759 );
760 if !forks.is_empty() {
761 debug!(
762 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
763 state.python_requirement.target(),
764 &requires_python,
765 next_package,
766 version,
767 forks
768 .iter()
769 .map(ToString::to_string)
770 .collect::<Vec<_>>()
771 .join(", ")
772 );
773
774 let forks = forks
777 .into_iter()
778 .map(|env| VersionFork {
779 env,
780 id: next_id,
781 version: None,
782 })
783 .collect();
784 forked_states
785 .extend(self.version_forks_to_fork_states(state, forks));
786 continue 'FORK;
787 }
788 }
789
790 state
791 .pubgrub
792 .add_incompatibility(Incompatibility::custom_version(
793 next_id,
794 version.clone(),
795 UnavailableReason::Version(UnavailableVersion::RequiresPython(
796 requires_python,
797 )),
798 ));
799 }
800 }
801 }
802 }
803 if resolutions.len() > 1 {
804 info!(
805 "Solved your requirements for {} environments",
806 resolutions.len()
807 );
808 }
809 if tracing::enabled!(Level::DEBUG) {
810 for resolution in &resolutions {
811 if let Some(env) = resolution.env.end_user_fork_display() {
812 let packages: FxHashSet<_> = resolution
813 .nodes
814 .keys()
815 .map(|package| &package.name)
816 .collect();
817 debug!(
818 "Distinct solution for {env} with {} package(s)",
819 packages.len()
820 );
821 }
822 }
823 }
824 for resolution in &resolutions {
825 Self::trace_resolution(resolution);
826 }
827 ResolverOutput::from_state(
828 &resolutions,
829 self.requirements.clone(),
830 self.constraints.clone(),
831 self.overrides.clone(),
832 &self.preferences,
833 &self.index,
834 &self.git,
835 self.python_requirement.target().clone(),
836 &self.conflicts,
837 self.selector.resolution_strategy(),
838 self.options.clone(),
839 )
840 }
841
842 fn reprioritize_conflicts(state: &mut ForkState) {
846 for package in state.conflict_tracker.prioritize.drain(..) {
847 let changed = state
848 .priorities
849 .mark_conflict_early(&state.pubgrub.package_store[package]);
850 if changed {
851 debug!(
852 "Package {} has too many conflicts (affected), prioritizing",
853 &state.pubgrub.package_store[package]
854 );
855 } else {
856 debug!(
857 "Package {} has too many conflicts (affected), already {:?}",
858 state.pubgrub.package_store[package],
859 state.priorities.get(&state.pubgrub.package_store[package])
860 );
861 }
862 }
863
864 for package in state.conflict_tracker.deprioritize.drain(..) {
865 let changed = state
866 .priorities
867 .mark_conflict_late(&state.pubgrub.package_store[package]);
868 if changed {
869 debug!(
870 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
871 state.pubgrub.package_store[package],
872 );
873 let backtrack_level = state.pubgrub.backtrack_package(package);
874 if let Some(backtrack_level) = backtrack_level {
875 debug!("Backtracked {backtrack_level} decisions");
876 } else {
877 debug!(
878 "Package {} is not decided, cannot backtrack",
879 state.pubgrub.package_store[package]
880 );
881 }
882 } else {
883 debug!(
884 "Package {} has too many conflicts (culprit), already {:?}",
885 state.pubgrub.package_store[package],
886 state.priorities.get(&state.pubgrub.package_store[package])
887 );
888 }
889 }
890 }
891
892 fn trace_resolution(combined: &Resolution) {
898 if !tracing::enabled!(Level::TRACE) {
899 return;
900 }
901 trace!("Resolution: {:?}", combined.env);
902 for edge in &combined.edges {
903 trace!(
904 "Resolution edge: {} -> {}",
905 edge.from
906 .as_ref()
907 .map(PackageName::as_str)
908 .unwrap_or("ROOT"),
909 edge.to,
910 );
911 let mut msg = String::new();
914 write!(msg, "{}", edge.from_version).unwrap();
915 if let Some(ref extra) = edge.from_extra {
916 write!(msg, " (extra: {extra})").unwrap();
917 }
918 if let Some(ref dev) = edge.from_group {
919 write!(msg, " (group: {dev})").unwrap();
920 }
921
922 write!(msg, " -> ").unwrap();
923
924 write!(msg, "{}", edge.to_version).unwrap();
925 if let Some(ref extra) = edge.to_extra {
926 write!(msg, " (extra: {extra})").unwrap();
927 }
928 if let Some(ref dev) = edge.to_group {
929 write!(msg, " (group: {dev})").unwrap();
930 }
931 if let Some(marker) = edge.marker.contents() {
932 write!(msg, " ; {marker}").unwrap();
933 }
934 trace!("Resolution edge: {msg}");
935 }
936 }
937
938 fn forks_to_fork_states<'a>(
940 &'a self,
941 current_state: ForkState,
942 version: &'a Version,
943 forks: Vec<Fork>,
944 request_sink: &'a Sender<Request>,
945 diverging_packages: &'a [PackageName],
946 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
947 debug!(
948 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
949 current_state.pubgrub.package_store[current_state.next],
950 version,
951 diverging_packages
952 .iter()
953 .map(ToString::to_string)
954 .join(", "),
955 forks.len(),
956 if forks.len() == 1 { "" } else { "s" }
957 );
958 assert!(forks.len() >= 2);
959 let package = current_state.next;
965 let mut cur_state = Some(current_state);
966 let forks_len = forks.len();
967 forks
968 .into_iter()
969 .enumerate()
970 .map(move |(i, fork)| {
971 let is_last = i == forks_len - 1;
972 let forked_state = cur_state.take().unwrap();
973 if !is_last {
974 cur_state = Some(forked_state.clone());
975 }
976
977 let env = fork.env.clone();
978 (fork, forked_state.with_env(env))
979 })
980 .map(move |(fork, mut forked_state)| {
981 forked_state
983 .visit_package_version_dependencies(
984 package,
985 version,
986 &self.urls,
987 &self.indexes,
988 &fork.dependencies,
989 &self.git,
990 &self.workspace_members,
991 self.selector.resolution_strategy(),
992 )
993 .map_err(|err| {
994 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
995 })?;
996
997 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
999 .map_err(|err| {
1000 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
1001 })?;
1002
1003 forked_state.add_package_version_dependencies(
1005 package,
1006 version,
1007 fork.dependencies,
1008 &self.index,
1009 &self.installed_packages,
1010 );
1011
1012 Ok(forked_state)
1013 })
1014 }
1015
1016 #[expect(clippy::unused_self)]
1018 fn version_forks_to_fork_states(
1019 &self,
1020 current_state: ForkState,
1021 forks: Vec<VersionFork>,
1022 ) -> impl Iterator<Item = ForkState> + '_ {
1023 let mut cur_state = Some(current_state);
1029 let forks_len = forks.len();
1030 forks.into_iter().enumerate().map(move |(i, fork)| {
1031 let is_last = i == forks_len - 1;
1032 let mut forked_state = cur_state.take().unwrap();
1033 if !is_last {
1034 cur_state = Some(forked_state.clone());
1035 }
1036 forked_state.initial_id = Some(fork.id);
1037 forked_state.initial_version = fork.version;
1038 forked_state.with_env(fork.env)
1039 })
1040 }
1041
1042 fn visit_dependencies(
1044 &self,
1045 dependencies: &[PubGrubDependency],
1046 state: &ForkState,
1047 request_sink: &Sender<Request>,
1048 ) -> Result<(), ResolveError> {
1049 for dependency in dependencies {
1050 let PubGrubDependency {
1051 package,
1052 version: _,
1053 parent: _,
1054 source: _,
1055 } = dependency;
1056 let url = package.name().and_then(|name| state.fork_urls.get(name));
1057 let index = package.name().and_then(|name| state.fork_indexes.get(name));
1058 self.visit_package(package, url, index, request_sink)?;
1059 }
1060 Ok(())
1061 }
1062
1063 fn visit_package(
1066 &self,
1067 package: &PubGrubPackage,
1068 url: Option<&VerbatimParsedUrl>,
1069 index: Option<&IndexMetadata>,
1070 request_sink: &Sender<Request>,
1071 ) -> Result<(), ResolveError> {
1072 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1074 return Ok(());
1075 }
1076
1077 self.request_package(package, url, index, request_sink)
1078 }
1079
1080 fn request_package(
1081 &self,
1082 package: &PubGrubPackage,
1083 url: Option<&VerbatimParsedUrl>,
1084 index: Option<&IndexMetadata>,
1085 request_sink: &Sender<Request>,
1086 ) -> Result<(), ResolveError> {
1087 let Some(name) = package.name_no_root() else {
1089 return Ok(());
1090 };
1091
1092 if let Some(url) = url {
1093 if !self.hasher.allows_url(&url.verbatim) {
1095 return Err(ResolveError::UnhashedPackage(name.clone()));
1096 }
1097
1098 let dist = Dist::from_url(name.clone(), url.clone())?;
1100 if self.index.distributions().register(dist.distribution_id()) {
1101 request_sink.blocking_send(Request::Dist(dist))?;
1102 }
1103 } else if let Some(index) = index {
1104 if self
1106 .index
1107 .explicit()
1108 .register((name.clone(), index.url().clone()))
1109 {
1110 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1111 }
1112 } else {
1113 if self.index.implicit().register(name.clone()) {
1115 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1116 }
1117 }
1118 Ok(())
1119 }
1120
1121 fn pre_visit<'data>(
1124 packages: impl Iterator<
1125 Item = (
1126 Id<PubGrubPackage>,
1127 &'data PubGrubPackage,
1128 &'data Range<Version>,
1129 ),
1130 >,
1131 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1132 urls: &Urls,
1133 indexes: &Indexes,
1134 python_requirement: &PythonRequirement,
1135 request_sink: &Sender<Request>,
1136 ) -> Result<(), ResolveError> {
1137 for (id, package, range) in packages {
1140 let PubGrubPackageInner::Package {
1141 name,
1142 extra: None,
1143 group: None,
1144 marker: MarkerTree::TRUE,
1145 } = &**package
1146 else {
1147 continue;
1148 };
1149 if urls.any_url(name) {
1152 continue;
1153 }
1154 if indexes.contains_key(name) {
1156 continue;
1157 }
1158 if pre_visited.get(&id) == Some(range) {
1161 continue;
1162 }
1163 pre_visited.insert(id, range.clone());
1164 request_sink.blocking_send(Request::Prefetch(
1165 name.clone(),
1166 range.clone(),
1167 python_requirement.clone(),
1168 ))?;
1169 }
1170 Ok(())
1171 }
1172
1173 fn known_versions<'a>(
1185 index: &InMemoryIndex,
1186 installed_packages: &InstalledPackages,
1187 fork_urls: &ForkUrls,
1188 fork_indexes: &ForkIndexes,
1189 known_versions: &'a mut FxHashMap<PackageName, Arc<[Version]>>,
1190 package: &PubGrubPackage,
1191 ) -> Option<&'a [Version]> {
1192 let name = package.name_no_root()?;
1193 if fork_urls.get(name).is_some() {
1195 return None;
1196 }
1197 if !known_versions.contains_key(name) {
1198 let response = if let Some(index_metadata) = fork_indexes.get(name) {
1199 index
1200 .explicit()
1201 .get(&(name.clone(), index_metadata.url().clone()))?
1202 } else {
1203 index.implicit().get(name)?
1204 };
1205 let VersionsResponse::Found(ref version_maps) = *response else {
1206 return None;
1207 };
1208 let mut versions: Vec<Version> = version_maps
1209 .iter()
1210 .flat_map(|version_map| version_map.included_versions().cloned())
1211 .chain(
1212 installed_packages
1213 .get_packages(name)
1214 .iter()
1215 .map(|dist| dist.version().clone()),
1216 )
1217 .collect();
1218 versions.sort_unstable();
1219 versions.dedup();
1220 known_versions.insert(name.clone(), versions.into());
1221 }
1222 Some(&known_versions[name][..])
1223 }
1224
1225 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1233 fn choose_version(
1234 &self,
1235 package: &PubGrubPackage,
1236 id: Id<PubGrubPackage>,
1237 index: Option<&IndexUrl>,
1238 range: &Range<Version>,
1239 pins: &mut FilePins,
1240 preferences: &Preferences,
1241 fork_urls: &ForkUrls,
1242 env: &ResolverEnvironment,
1243 python_requirement: &PythonRequirement,
1244 pubgrub: &State<UvDependencyProvider>,
1245 visited: &mut FxHashSet<PackageName>,
1246 request_sink: &Sender<Request>,
1247 ) -> Result<Option<ResolverVersion>, ResolveError> {
1248 match &**package {
1249 PubGrubPackageInner::Root(_) => {
1250 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1251 }
1252
1253 PubGrubPackageInner::Python(_) => {
1254 Ok(None)
1257 }
1258
1259 PubGrubPackageInner::System(_) => {
1260 let Some(version) = range.as_singleton() else {
1263 return Ok(None);
1264 };
1265 Ok(Some(ResolverVersion::Unforked(version.clone())))
1266 }
1267
1268 PubGrubPackageInner::Marker { name, .. }
1269 | PubGrubPackageInner::Extra { name, .. }
1270 | PubGrubPackageInner::Group { name, .. }
1271 | PubGrubPackageInner::Package { name, .. } => {
1272 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1273 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1274 } else {
1275 self.choose_version_registry(
1276 package,
1277 id,
1278 name,
1279 index,
1280 range,
1281 preferences,
1282 env,
1283 python_requirement,
1284 pubgrub,
1285 pins,
1286 visited,
1287 request_sink,
1288 )
1289 }
1290 }
1291 }
1292 }
1293
1294 fn choose_version_url(
1297 &self,
1298 id: Id<PubGrubPackage>,
1299 name: &PackageName,
1300 range: &Range<Version>,
1301 url: &VerbatimParsedUrl,
1302 env: &ResolverEnvironment,
1303 python_requirement: &PythonRequirement,
1304 pubgrub: &State<UvDependencyProvider>,
1305 ) -> Result<Option<ResolverVersion>, ResolveError> {
1306 debug!(
1307 "Searching for a compatible version of {name} @ {} ({range})",
1308 url.verbatim
1309 );
1310
1311 let dist = Dist::from_url(name.clone(), url.clone())?;
1312 let distribution_id = dist.distribution_id();
1313 let response = self
1314 .index
1315 .distributions()
1316 .wait_blocking(&distribution_id)
1317 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1318
1319 let metadata = match &*response {
1321 MetadataResponse::Found(archive) => &archive.metadata,
1322 MetadataResponse::Unavailable(reason) => {
1323 self.unavailable_packages
1324 .pin()
1325 .insert(name.clone(), reason.into());
1326 return Ok(None);
1327 }
1328 MetadataResponse::Error(dist, err) => {
1331 return Err(ResolveError::Dist(
1332 DistErrorKind::from_requested_dist(dist, &**err),
1333 dist.clone(),
1334 DerivationChain::default(),
1335 err.clone(),
1336 ));
1337 }
1338 };
1339
1340 let version = &metadata.version;
1341
1342 if !range.contains(version) {
1344 return Ok(None);
1345 }
1346
1347 if let Dist::Built(dist) = &dist {
1350 let filename = match &dist {
1351 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1352 BuiltDist::DirectUrl(dist) => &dist.filename,
1353 BuiltDist::GitPath(dist) => &dist.filename,
1354 BuiltDist::Path(dist) => &dist.filename,
1355 };
1356
1357 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1360 {
1361 let wheel_marker = implied_markers(filename);
1362 for environment_marker in self.options.artifact_environments.iter().copied() {
1365 if env.included_by_marker(environment_marker)
1367 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1368 {
1369 if wheel_marker.is_disjoint(environment_marker) {
1371 return Ok(Some(ResolverVersion::Unavailable(
1372 version.clone(),
1373 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1374 IncompatibleWheel::MissingPlatform(environment_marker),
1375 )),
1376 )));
1377 }
1378 }
1379 }
1380 }
1381
1382 if !python_requirement.target().matches_wheel_tag(filename) {
1384 return Ok(Some(ResolverVersion::Unavailable(
1385 filename.version.clone(),
1386 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1387 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1388 )),
1389 )));
1390 }
1391 }
1392
1393 if let Some(requires_python) = metadata.requires_python.as_ref() {
1395 if !python_requirement.target().is_contained_by(requires_python) {
1396 let kind = if python_requirement.installed() == python_requirement.target() {
1397 PythonRequirementKind::Installed
1398 } else {
1399 PythonRequirementKind::Target
1400 };
1401 return Ok(Some(ResolverVersion::Unavailable(
1402 version.clone(),
1403 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1404 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1405 )),
1406 )));
1407 }
1408 }
1409
1410 Ok(Some(ResolverVersion::Unforked(version.clone())))
1411 }
1412
1413 fn choose_version_registry(
1416 &self,
1417 package: &PubGrubPackage,
1418 id: Id<PubGrubPackage>,
1419 name: &PackageName,
1420 index: Option<&IndexUrl>,
1421 range: &Range<Version>,
1422 preferences: &Preferences,
1423 env: &ResolverEnvironment,
1424 python_requirement: &PythonRequirement,
1425 pubgrub: &State<UvDependencyProvider>,
1426 pins: &mut FilePins,
1427 visited: &mut FxHashSet<PackageName>,
1428 request_sink: &Sender<Request>,
1429 ) -> Result<Option<ResolverVersion>, ResolveError> {
1430 let versions_response = if let Some(index) = index {
1432 self.index
1433 .explicit()
1434 .wait_blocking(&(name.clone(), index.clone()))
1435 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1436 } else {
1437 self.index
1438 .implicit()
1439 .wait_blocking(name)
1440 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1441 };
1442 visited.insert(name.clone());
1443
1444 let version_maps = match *versions_response {
1445 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1446 VersionsResponse::NoIndex => {
1447 self.unavailable_packages
1448 .pin()
1449 .insert(name.clone(), UnavailablePackage::NoIndex);
1450 &[]
1451 }
1452 VersionsResponse::Offline => {
1453 self.unavailable_packages
1454 .pin()
1455 .insert(name.clone(), UnavailablePackage::Offline);
1456 &[]
1457 }
1458 VersionsResponse::NotFound => {
1459 self.unavailable_packages
1460 .pin()
1461 .insert(name.clone(), UnavailablePackage::NotFound);
1462 &[]
1463 }
1464 };
1465
1466 debug!("Searching for a compatible version of {package} ({range})");
1467
1468 let Some(candidate) = self.selector.select(
1470 name,
1471 range,
1472 version_maps,
1473 preferences,
1474 &self.installed_packages,
1475 &self.exclusions,
1476 index,
1477 env,
1478 self.tags.as_ref(),
1479 ) else {
1480 return Ok(None);
1482 };
1483
1484 let dist = match candidate.dist() {
1485 CandidateDist::Compatible(dist) => dist,
1486 CandidateDist::Incompatible {
1487 incompatible_dist: incompatibility,
1488 prioritized_dist: _,
1489 } => {
1490 return Ok(Some(ResolverVersion::Unavailable(
1492 candidate.version().clone(),
1493 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1496 )));
1497 }
1498 };
1499
1500 if let Some((requires_python, incompatibility)) =
1502 Self::check_requires_python(dist, python_requirement)
1503 {
1504 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1505 if env.marker_environment().is_none() {
1506 let forks = fork_version_by_python_requirement(
1507 requires_python,
1508 python_requirement,
1509 env,
1510 );
1511 if !forks.is_empty() {
1512 debug!(
1513 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1514 python_requirement.target(),
1515 requires_python,
1516 name,
1517 candidate.version(),
1518 forks
1519 .iter()
1520 .map(ToString::to_string)
1521 .collect::<Vec<_>>()
1522 .join(", ")
1523 );
1524 let forks = forks
1525 .into_iter()
1526 .map(|env| VersionFork {
1527 env,
1528 id,
1529 version: None,
1530 })
1531 .collect();
1532 return Ok(Some(ResolverVersion::Forked(forks)));
1533 }
1534 }
1535 }
1536
1537 return Ok(Some(ResolverVersion::Unavailable(
1538 candidate.version().clone(),
1539 UnavailableVersion::IncompatibleDist(incompatibility),
1540 )));
1541 }
1542
1543 if let Some(forked) = self.fork_version_registry(
1545 &candidate,
1546 dist,
1547 version_maps,
1548 package,
1549 id,
1550 name,
1551 index,
1552 range,
1553 preferences,
1554 env,
1555 pubgrub,
1556 pins,
1557 request_sink,
1558 )? {
1559 return Ok(Some(forked));
1560 }
1561
1562 let filename = match dist.for_installation() {
1563 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1564 .filename()
1565 .unwrap_or(Cow::Borrowed("unknown filename")),
1566 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1567 .filename()
1568 .unwrap_or(Cow::Borrowed("unknown filename")),
1569 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1570 };
1571
1572 debug!(
1573 "Selecting: {}=={} [{}] ({})",
1574 name,
1575 candidate.version(),
1576 candidate.choice_kind(),
1577 filename,
1578 );
1579 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1580
1581 let version = candidate.version().clone();
1582 Ok(Some(ResolverVersion::Unforked(version)))
1583 }
1584
1585 fn fork_version_registry(
1598 &self,
1599 candidate: &Candidate,
1600 dist: &CompatibleDist,
1601 version_maps: &[VersionMap],
1602 package: &PubGrubPackage,
1603 id: Id<PubGrubPackage>,
1604 name: &PackageName,
1605 index: Option<&IndexUrl>,
1606 range: &Range<Version>,
1607 preferences: &Preferences,
1608 env: &ResolverEnvironment,
1609 pubgrub: &State<UvDependencyProvider>,
1610 pins: &mut FilePins,
1611 request_sink: &Sender<Request>,
1612 ) -> Result<Option<ResolverVersion>, ResolveError> {
1613 if env.marker_environment().is_some() {
1615 return Ok(None);
1616 }
1617
1618 if dist.implied_markers().is_true() {
1621 return Ok(None);
1622 }
1623
1624 for marker in self.options.artifact_environments.iter().copied() {
1627 if env.included_by_marker(marker) {
1629 if dist.implied_markers().is_disjoint(marker)
1631 && !find_environments(id, pubgrub).is_disjoint(marker)
1632 {
1633 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1635 return Ok(Some(ResolverVersion::Unavailable(
1636 candidate.version().clone(),
1637 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1638 IncompatibleWheel::MissingPlatform(marker),
1639 )),
1640 )));
1641 };
1642
1643 debug!(
1644 "Forking on required platform `{}` for {}=={} ({})",
1645 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1646 name,
1647 candidate.version(),
1648 [&left, &right]
1649 .iter()
1650 .map(ToString::to_string)
1651 .collect::<Vec<_>>()
1652 .join(", ")
1653 );
1654 let forks = vec![
1655 VersionFork {
1656 env: left,
1657 id,
1658 version: None,
1659 },
1660 VersionFork {
1661 env: right,
1662 id,
1663 version: None,
1664 },
1665 ];
1666 return Ok(Some(ResolverVersion::Forked(forks)));
1667 }
1668 }
1669 }
1670
1671 if !candidate.version().is_local() {
1673 return Ok(None);
1674 }
1675
1676 debug!(
1677 "Looking at local version: {}=={}",
1678 name,
1679 candidate.version()
1680 );
1681
1682 let range = range.clone().intersection(&Range::singleton(
1684 candidate.version().clone().without_local(),
1685 ));
1686
1687 let Some(base_candidate) = self.selector.select(
1688 name,
1689 &range,
1690 version_maps,
1691 preferences,
1692 &self.installed_packages,
1693 &self.exclusions,
1694 index,
1695 env,
1696 self.tags.as_ref(),
1697 ) else {
1698 return Ok(None);
1699 };
1700 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1701 return Ok(None);
1702 };
1703
1704 let mut remainder = {
1706 let mut remainder = base_dist.implied_markers();
1707 remainder = remainder.and(dist.implied_markers().negate());
1708 remainder
1709 };
1710 if remainder.is_false() {
1711 return Ok(None);
1712 }
1713
1714 if !env.included_by_marker(remainder) {
1718 return Ok(None);
1719 }
1720
1721 if !env.included_by_marker(dist.implied_markers()) {
1724 let filename = match dist.for_installation() {
1725 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1726 .filename()
1727 .unwrap_or(Cow::Borrowed("unknown filename")),
1728 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1729 .filename()
1730 .unwrap_or(Cow::Borrowed("unknown filename")),
1731 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1732 };
1733
1734 debug!(
1735 "Preferring non-local candidate: {}=={} [{}] ({})",
1736 name,
1737 base_candidate.version(),
1738 base_candidate.choice_kind(),
1739 filename,
1740 );
1741 self.visit_candidate(
1742 &base_candidate,
1743 base_dist,
1744 package,
1745 name,
1746 pins,
1747 request_sink,
1748 )?;
1749
1750 return Ok(Some(ResolverVersion::Unforked(
1751 base_candidate.version().clone(),
1752 )));
1753 }
1754
1755 for value in [
1764 arcstr::literal!("darwin"),
1765 arcstr::literal!("linux"),
1766 arcstr::literal!("win32"),
1767 ] {
1768 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1769 key: MarkerValueString::SysPlatform,
1770 operator: MarkerOperator::Equal,
1771 value,
1772 });
1773 if dist.implied_markers().is_disjoint(sys_platform)
1774 && !remainder.is_disjoint(sys_platform)
1775 {
1776 remainder = remainder.or(sys_platform);
1777 }
1778 }
1779
1780 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1782 return Ok(None);
1783 };
1784
1785 debug!(
1786 "Forking platform for {}=={} ({})",
1787 name,
1788 candidate.version(),
1789 [&base_env, &local_env]
1790 .iter()
1791 .map(ToString::to_string)
1792 .collect::<Vec<_>>()
1793 .join(", ")
1794 );
1795 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1796 self.visit_candidate(
1797 &base_candidate,
1798 base_dist,
1799 package,
1800 name,
1801 pins,
1802 request_sink,
1803 )?;
1804
1805 let forks = vec![
1806 VersionFork {
1807 env: base_env.clone(),
1808 id,
1809 version: Some(base_candidate.version().clone()),
1810 },
1811 VersionFork {
1812 env: local_env.clone(),
1813 id,
1814 version: Some(candidate.version().clone()),
1815 },
1816 ];
1817 Ok(Some(ResolverVersion::Forked(forks)))
1818 }
1819
1820 fn visit_candidate(
1822 &self,
1823 candidate: &Candidate,
1824 dist: &CompatibleDist,
1825 package: &PubGrubPackage,
1826 name: &PackageName,
1827 pins: &mut FilePins,
1828 request_sink: &Sender<Request>,
1829 ) -> Result<(), ResolveError> {
1830 pins.insert(candidate, dist);
1833
1834 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1836 if self.dependency_mode.is_transitive() {
1837 let dist = dist.for_resolution();
1838 if self.index.distributions().register(dist.distribution_id()) {
1839 if name != dist.name() {
1840 return Err(ResolveError::MismatchedPackageName {
1841 request: "distribution",
1842 expected: name.clone(),
1843 actual: dist.name().clone(),
1844 });
1845 }
1846 if !self
1848 .hasher
1849 .allows_package(candidate.name(), candidate.version())
1850 {
1851 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1852 }
1853
1854 let request = Request::from(dist);
1855 request_sink.blocking_send(request)?;
1856 }
1857 }
1858 }
1859
1860 Ok(())
1861 }
1862
1863 fn check_requires_python<'dist>(
1866 dist: &'dist CompatibleDist,
1867 python_requirement: &PythonRequirement,
1868 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1869 let requires_python = dist.requires_python()?;
1870 if python_requirement.target().is_contained_by(requires_python) {
1871 None
1872 } else {
1873 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1874 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1875 requires_python.clone(),
1876 if python_requirement.installed() == python_requirement.target() {
1877 PythonRequirementKind::Installed
1878 } else {
1879 PythonRequirementKind::Target
1880 },
1881 ))
1882 } else {
1883 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1884 requires_python.clone(),
1885 if python_requirement.installed() == python_requirement.target() {
1886 PythonRequirementKind::Installed
1887 } else {
1888 PythonRequirementKind::Target
1889 },
1890 ))
1891 };
1892 Some((requires_python, incompatibility))
1893 }
1894 }
1895
1896 #[instrument(skip_all, fields(%package, %version))]
1898 fn get_dependencies_forking(
1899 &self,
1900 id: Id<PubGrubPackage>,
1901 package: &PubGrubPackage,
1902 version: &Version,
1903 pins: &FilePins,
1904 fork_urls: &ForkUrls,
1905 env: &ResolverEnvironment,
1906 python_requirement: &PythonRequirement,
1907 pubgrub: &State<UvDependencyProvider>,
1908 ) -> Result<ForkedDependencies, ResolveError> {
1909 let result = self.get_dependencies(
1910 id,
1911 package,
1912 version,
1913 pins,
1914 fork_urls,
1915 env,
1916 python_requirement,
1917 pubgrub,
1918 );
1919 if env.marker_environment().is_some() {
1920 result.map(|deps| match deps {
1921 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
1922 ForkedDependencies::Unforked(deps)
1923 }
1924 Dependencies::RequiresPython(requires_python) => {
1925 ForkedDependencies::RequiresPython(requires_python)
1926 }
1927 Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
1928 })
1929 } else {
1930 Ok(result?.fork(env, python_requirement, &self.conflicts))
1931 }
1932 }
1933
1934 #[instrument(skip_all, fields(%package, %version))]
1936 fn get_dependencies(
1937 &self,
1938 id: Id<PubGrubPackage>,
1939 package: &PubGrubPackage,
1940 version: &Version,
1941 pins: &FilePins,
1942 fork_urls: &ForkUrls,
1943 env: &ResolverEnvironment,
1944 python_requirement: &PythonRequirement,
1945 pubgrub: &State<UvDependencyProvider>,
1946 ) -> Result<Dependencies, ResolveError> {
1947 let dependencies = match &**package {
1948 PubGrubPackageInner::Root(_) => {
1949 let no_dev_deps = BTreeMap::default();
1950 let requirements = self.flatten_requirements(
1951 &self.requirements,
1952 &no_dev_deps,
1953 None,
1954 None,
1955 None,
1956 None,
1957 env,
1958 python_requirement,
1959 );
1960
1961 PubGrubDependency::from_requirements(
1962 &self.conflicts,
1963 requirements,
1964 None,
1965 Some(package),
1966 )
1967 }
1968
1969 PubGrubPackageInner::Package {
1970 name,
1971 extra,
1972 group,
1973 marker: _,
1974 } => {
1975 if self.dependency_mode.is_direct() {
1977 return Ok(Dependencies::Unforkable(Vec::default()));
1978 }
1979
1980 let owned_id;
1982 let distribution_id = if let Some((_, metadata_id)) =
1983 pins.dist_and_id(name, version)
1984 {
1985 metadata_id
1986 } else if let Some(url) = fork_urls.get(name) {
1987 let dist = Dist::from_url(name.clone(), url.clone())?;
1988 owned_id = dist.distribution_id();
1989 &owned_id
1990 } else {
1991 debug_assert!(
1992 false,
1993 "Dependencies were requested for a package without a pinned distribution"
1994 );
1995 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1996 };
1997
1998 if self.dependency_mode.is_transitive()
2000 && self.unavailable_packages.pin().contains_key(name)
2001 && self.installed_packages.get_packages(name).is_empty()
2002 {
2003 debug_assert!(
2004 false,
2005 "Dependencies were requested for a package that is not available"
2006 );
2007 return Err(ResolveError::PackageUnavailable(name.clone()));
2008 }
2009
2010 let response = self
2012 .index
2013 .distributions()
2014 .wait_blocking(distribution_id)
2015 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
2016
2017 let metadata = match &*response {
2018 MetadataResponse::Found(archive) => &archive.metadata,
2019 MetadataResponse::Unavailable(reason) => {
2020 let unavailable_version = UnavailableVersion::from(reason);
2021 let message = unavailable_version.singular_message();
2022 if let Some(err) = reason.source() {
2023 warn!("{name} {message}: {err}");
2025 } else {
2026 warn!("{name} {message}");
2027 }
2028 let incomplete_packages = self.incomplete_packages.pin();
2029 let versions = incomplete_packages.get_or_insert(
2030 name.clone(),
2031 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
2032 );
2033 versions.pin().insert(version.clone(), reason.clone());
2034 return Ok(Dependencies::Unavailable(unavailable_version));
2035 }
2036 MetadataResponse::Error(dist, err) => {
2037 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
2038 .unwrap_or_default();
2039 return Err(ResolveError::Dist(
2040 DistErrorKind::from_requested_dist(dist, &**err),
2041 dist.clone(),
2042 chain,
2043 err.clone(),
2044 ));
2045 }
2046 };
2047
2048 if let Some(requires_python) = &metadata.requires_python {
2051 if !python_requirement.target().is_contained_by(requires_python) {
2052 return Ok(Dependencies::RequiresPython(requires_python.clone()));
2053 }
2054 }
2055
2056 let system_dependencies = self
2058 .options
2059 .torch_backend
2060 .as_ref()
2061 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
2062 .filter(|torch_backend| torch_backend.has_system_dependency(name))
2063 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
2064 .map(IndexUrl::url)
2065 .and_then(SystemDependency::from_index)
2066 .into_iter()
2067 .inspect(|system_dependency| {
2068 debug!(
2069 "Adding system dependency `{}` for `{package}@{version}`",
2070 system_dependency
2071 );
2072 })
2073 .map(PubGrubDependency::from);
2074
2075 let requirements = self.flatten_requirements(
2076 &metadata.requires_dist,
2077 &metadata.dependency_groups,
2078 extra.as_ref(),
2079 group.as_ref(),
2080 Some(name),
2081 Some(version),
2082 env,
2083 python_requirement,
2084 );
2085
2086 PubGrubDependency::from_requirements(
2087 &self.conflicts,
2088 requirements,
2089 group.as_ref(),
2090 Some(package),
2091 )
2092 .map(|mut dependencies| {
2093 dependencies.extend(system_dependencies);
2094 dependencies
2095 })
2096 }
2097
2098 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2099
2100 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
2101
2102 PubGrubPackageInner::Marker { name, marker } => {
2104 return Ok(Dependencies::Unforkable(
2105 [MarkerTree::TRUE, *marker]
2106 .into_iter()
2107 .map(move |marker| PubGrubDependency {
2108 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2109 name: name.clone(),
2110 extra: None,
2111 group: None,
2112 marker,
2113 }),
2114 version: Range::singleton(version.clone()),
2115 parent: None,
2116 source: DependencySource::Unspecified,
2117 })
2118 .collect(),
2119 ));
2120 }
2121
2122 PubGrubPackageInner::Extra {
2124 name,
2125 extra,
2126 marker,
2127 } => {
2128 return Ok(Dependencies::Unforkable(
2129 [MarkerTree::TRUE, *marker]
2130 .into_iter()
2131 .dedup()
2132 .flat_map(move |marker| {
2133 [None, Some(extra)]
2134 .into_iter()
2135 .map(move |extra| PubGrubDependency {
2136 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2137 name: name.clone(),
2138 extra: extra.cloned(),
2139 group: None,
2140 marker,
2141 }),
2142 version: Range::singleton(version.clone()),
2143 parent: None,
2144 source: DependencySource::Unspecified,
2145 })
2146 })
2147 .collect(),
2148 ));
2149 }
2150
2151 PubGrubPackageInner::Group {
2153 name,
2154 group,
2155 marker,
2156 } => {
2157 return Ok(Dependencies::Unforkable(
2158 [MarkerTree::TRUE, *marker]
2159 .into_iter()
2160 .dedup()
2161 .map(|marker| PubGrubDependency {
2162 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2163 name: name.clone(),
2164 extra: None,
2165 group: Some(group.clone()),
2166 marker,
2167 }),
2168 version: Range::singleton(version.clone()),
2169 parent: None,
2170 source: DependencySource::Unspecified,
2171 })
2172 .collect(),
2173 ));
2174 }
2175 };
2176 Ok(match dependencies {
2177 Ok(dependencies) => Dependencies::Available(dependencies),
2178 Err(requirement) => {
2179 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2180 }
2181 })
2182 }
2183
2184 fn flatten_requirements<'a>(
2188 &'a self,
2189 dependencies: &'a [Requirement],
2190 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2191 extra: Option<&'a ExtraName>,
2192 dev: Option<&'a GroupName>,
2193 name: Option<&'a PackageName>,
2194 version: Option<&'a Version>,
2195 env: &'a ResolverEnvironment,
2196 python_requirement: &'a PythonRequirement,
2197 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2198 let python_marker = python_requirement.to_marker_tree();
2199
2200 if let Some(dev) = dev {
2201 Either::Left(Either::Left(self.requirements_for_extra(
2204 dev_dependencies.get(dev).into_iter().flatten(),
2205 extra,
2206 None,
2207 name.zip(version),
2208 env,
2209 python_marker,
2210 python_requirement,
2211 )))
2212 } else if !dependencies
2213 .iter()
2214 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2215 {
2216 Either::Left(Either::Right(self.requirements_for_extra(
2218 dependencies.iter(),
2219 extra,
2220 name.zip(version),
2221 name.zip(version),
2222 env,
2223 python_marker,
2224 python_requirement,
2225 )))
2226 } else {
2227 let mut requirements = self
2228 .requirements_for_extra(
2229 dependencies.iter(),
2230 extra,
2231 name.zip(version),
2232 name.zip(version),
2233 env,
2234 python_marker,
2235 python_requirement,
2236 )
2237 .collect::<Vec<_>>();
2238
2239 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2242 let mut queue: VecDeque<_> = requirements
2243 .iter()
2244 .filter(|req| name == Some(&req.name))
2245 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2246 .collect();
2247 while let Some((extra, marker)) = queue.pop_front() {
2248 if !seen.insert((extra.clone(), marker)) {
2249 continue;
2250 }
2251 for requirement in self.requirements_for_extra(
2252 dependencies,
2253 Some(&extra),
2254 name.zip(version),
2255 name.zip(version),
2256 env,
2257 python_marker,
2258 python_requirement,
2259 ) {
2260 let requirement = match requirement {
2261 Cow::Owned(mut requirement) => {
2262 requirement.marker = requirement.marker.and(marker);
2263 requirement
2264 }
2265 Cow::Borrowed(requirement) => {
2266 let mut marker = marker;
2267 marker = marker.and(requirement.marker);
2268 Requirement {
2269 name: requirement.name.clone(),
2270 extras: requirement.extras.clone(),
2271 groups: requirement.groups.clone(),
2272 source: requirement.source.clone(),
2273 origin: requirement.origin.clone(),
2274 marker: marker.simplify_extras(slice::from_ref(&extra)),
2275 }
2276 }
2277 };
2278 if name == Some(&requirement.name) {
2279 queue.extend(
2281 requirement
2282 .extras
2283 .iter()
2284 .cloned()
2285 .map(|extra| (extra, requirement.marker)),
2286 );
2287 } else {
2288 requirements.push(Cow::Owned(requirement));
2290 }
2291 }
2292 }
2293
2294 let mut self_constraints = vec![];
2298 for req in &requirements {
2299 if name == Some(&req.name) && !req.source.is_empty() {
2300 self_constraints.push(Requirement {
2301 name: req.name.clone(),
2302 extras: Box::new([]),
2303 groups: req.groups.clone(),
2304 source: req.source.clone(),
2305 origin: req.origin.clone(),
2306 marker: req.marker,
2307 });
2308 }
2309 }
2310
2311 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2313 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2314
2315 Either::Right(requirements.into_iter())
2316 }
2317 }
2318
2319 fn requirements_for_extra<'data, 'parameters>(
2322 &'data self,
2323 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2324 extra: Option<&'parameters ExtraName>,
2325 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2326 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2327 env: &'parameters ResolverEnvironment,
2328 python_marker: MarkerTree,
2329 python_requirement: &'parameters PythonRequirement,
2330 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2331 where
2332 'data: 'parameters,
2333 {
2334 self.overrides
2335 .apply_for_package(override_package, dependencies)
2336 .filter(move |requirement| {
2337 !self
2338 .excludes
2339 .contains_for_package(exclusion_package, &requirement.name)
2340 })
2341 .map(move |mut requirement| {
2342 let marker = match extra {
2351 Some(extra) => requirement
2352 .marker
2353 .simplify_extras(slice::from_ref(extra))
2354 .simplify_not_extras_with(|candidate| candidate != extra)
2355 .and(
2356 requirement
2357 .marker
2358 .simplify_not_extras_with(|_| true)
2359 .negate(),
2360 ),
2361 None => requirement.marker.simplify_not_extras_with(|_| true),
2362 };
2363
2364 if requirement.marker != marker {
2365 requirement.to_mut().marker = marker;
2366 }
2367
2368 requirement
2369 })
2370 .filter(move |requirement| {
2371 Self::is_requirement_applicable(
2372 requirement,
2373 extra,
2374 env,
2375 python_marker,
2376 python_requirement,
2377 )
2378 })
2379 .flat_map(move |requirement| {
2380 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2381 requirement,
2382 extra,
2383 env,
2384 python_marker,
2385 python_requirement,
2386 ))
2387 })
2388 }
2389
2390 fn is_requirement_applicable(
2393 requirement: &Requirement,
2394 extra: Option<&ExtraName>,
2395 env: &ResolverEnvironment,
2396 python_marker: MarkerTree,
2397 python_requirement: &PythonRequirement,
2398 ) -> bool {
2399 match extra {
2401 Some(source_extra) => {
2402 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2403 return false;
2404 }
2405
2406 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2407 {
2408 return false;
2409 }
2410 }
2411 None => {
2412 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2413 return false;
2414 }
2415 }
2416 }
2417
2418 if python_marker.is_disjoint(requirement.marker) {
2421 trace!(
2422 "Skipping {requirement} because of Requires-Python: {requires_python}",
2423 requires_python = python_requirement.target(),
2424 );
2425 return false;
2426 }
2427
2428 if !env.included_by_marker(requirement.marker) {
2431 trace!("Skipping {requirement} because of {env}");
2432 return false;
2433 }
2434
2435 true
2436 }
2437
2438 fn constraints_for_requirement<'data, 'parameters>(
2441 &'data self,
2442 requirement: Cow<'data, Requirement>,
2443 extra: Option<&'parameters ExtraName>,
2444 env: &'parameters ResolverEnvironment,
2445 python_marker: MarkerTree,
2446 python_requirement: &'parameters PythonRequirement,
2447 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2448 where
2449 'data: 'parameters,
2450 {
2451 self.constraints
2452 .get(&requirement.name)
2453 .into_iter()
2454 .flatten()
2455 .filter_map(move |constraint| {
2456 let constraint = if constraint.marker.is_true() {
2459 if requirement.marker.is_true() {
2463 Cow::Borrowed(constraint)
2464 } else {
2465 let mut marker = constraint.marker;
2466 marker = marker.and(requirement.marker);
2467
2468 if marker.is_false() {
2469 trace!(
2470 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2471 constraint.marker.try_to_string().unwrap(),
2472 requirement.marker.try_to_string().unwrap(),
2473 );
2474 return None;
2475 }
2476
2477 Cow::Owned(Requirement {
2478 name: constraint.name.clone(),
2479 extras: constraint.extras.clone(),
2480 groups: constraint.groups.clone(),
2481 source: constraint.source.clone(),
2482 origin: constraint.origin.clone(),
2483 marker,
2484 })
2485 }
2486 } else {
2487 let requires_python = python_requirement.target();
2488
2489 let mut marker = constraint.marker;
2490 marker = marker.and(requirement.marker);
2491
2492 if marker.is_false() {
2493 trace!(
2494 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2495 constraint.marker.try_to_string().unwrap(),
2496 requirement.marker.try_to_string().unwrap(),
2497 );
2498 return None;
2499 }
2500
2501 if python_marker.is_disjoint(marker) {
2505 trace!(
2506 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2507 );
2508 return None;
2509 }
2510
2511 if marker == constraint.marker {
2512 Cow::Borrowed(constraint)
2513 } else {
2514 Cow::Owned(Requirement {
2515 name: constraint.name.clone(),
2516 extras: constraint.extras.clone(),
2517 groups: constraint.groups.clone(),
2518 source: constraint.source.clone(),
2519 origin: constraint.origin.clone(),
2520 marker,
2521 })
2522 }
2523 };
2524
2525 if !env.included_by_marker(constraint.marker) {
2528 trace!("Skipping {constraint} because of {env}");
2529 return None;
2530 }
2531
2532 match extra {
2534 Some(source_extra) => {
2535 if !constraint
2536 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2537 {
2538 return None;
2539 }
2540 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2541 {
2542 return None;
2543 }
2544 }
2545 None => {
2546 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2547 return None;
2548 }
2549 }
2550 }
2551
2552 Some(constraint)
2553 })
2554 }
2555
2556 async fn fetch<Provider: ResolverProvider>(
2558 self: Arc<Self>,
2559 provider: Arc<Provider>,
2560 request_stream: Receiver<Request>,
2561 ) -> Result<(), ResolveError> {
2562 let mut response_stream = ReceiverStream::new(request_stream)
2563 .map(|request| self.process_request(request, &*provider).boxed_local())
2564 .buffer_unordered(usize::MAX);
2568
2569 while let Some(response) = response_stream.next().await {
2570 match response? {
2571 Some(Response::Package(name, index, version_map)) => {
2572 trace!("Received package metadata for: {name}");
2573 if let Some(index) = index {
2574 self.index
2575 .explicit()
2576 .done((name, index), Arc::new(version_map));
2577 } else {
2578 self.index.implicit().done(name, Arc::new(version_map));
2579 }
2580 }
2581 Some(Response::Installed { dist, metadata }) => {
2582 trace!("Received installed distribution metadata for: {dist}");
2583 self.index
2584 .distributions()
2585 .done(dist.distribution_id(), Arc::new(metadata));
2586 }
2587 Some(Response::Dist { dist, metadata }) => {
2588 let dist_kind = match dist {
2589 Dist::Built(_) => "built",
2590 Dist::Source(_) => "source",
2591 };
2592 trace!("Received {dist_kind} distribution metadata for: {dist}");
2593 if let MetadataResponse::Unavailable(reason) = &metadata {
2594 let message = UnavailableVersion::from(reason).singular_message();
2595 if let Some(err) = reason.source() {
2596 warn!("{dist} {message}: {err}");
2598 } else {
2599 warn!("{dist} {message}");
2600 }
2601 }
2602 self.index
2603 .distributions()
2604 .done(dist.distribution_id(), Arc::new(metadata));
2605 }
2606 None => {}
2607 }
2608 }
2609
2610 Ok::<(), ResolveError>(())
2611 }
2612
2613 #[instrument(skip_all, fields(%request))]
2614 async fn process_request<Provider: ResolverProvider>(
2615 &self,
2616 request: Request,
2617 provider: &Provider,
2618 ) -> Result<Option<Response>, ResolveError> {
2619 match request {
2620 Request::Package(package_name, index) => {
2622 let package_versions = provider
2623 .get_package_versions(&package_name, index.as_ref())
2624 .boxed_local()
2625 .await
2626 .map_err(ResolveError::Client)?;
2627
2628 Ok(Some(Response::Package(
2629 package_name,
2630 index.map(IndexMetadata::into_url),
2631 package_versions,
2632 )))
2633 }
2634
2635 Request::Dist(dist) => {
2637 if let Some(version) = dist.version() {
2638 if let Some(index) = dist.index() {
2639 let versions_response = self.index.implicit().get(dist.name());
2641 if let Some(VersionsResponse::Found(version_maps)) =
2642 versions_response.as_deref()
2643 {
2644 for version_map in version_maps {
2645 if version_map.index() == Some(index) {
2646 let Some(metadata) = version_map.get_metadata(version) else {
2647 continue;
2648 };
2649 debug!("Found registry-provided metadata for: {dist}");
2650 return Ok(Some(Response::Dist {
2651 dist,
2652 metadata: MetadataResponse::Found(
2653 ArchiveMetadata::from_metadata23(metadata),
2654 ),
2655 }));
2656 }
2657 }
2658 }
2659
2660 let versions_response = self
2662 .index
2663 .explicit()
2664 .get(&(dist.name().clone(), index.clone()));
2665 if let Some(VersionsResponse::Found(version_maps)) =
2666 versions_response.as_deref()
2667 {
2668 for version_map in version_maps {
2669 let Some(metadata) = version_map.get_metadata(version) else {
2670 continue;
2671 };
2672 debug!("Found registry-provided metadata for: {dist}");
2673 return Ok(Some(Response::Dist {
2674 dist,
2675 metadata: MetadataResponse::Found(
2676 ArchiveMetadata::from_metadata23(metadata),
2677 ),
2678 }));
2679 }
2680 }
2681 }
2682 }
2683
2684 let metadata = provider
2685 .get_or_build_wheel_metadata(&dist)
2686 .boxed_local()
2687 .await?;
2688
2689 if let MetadataResponse::Found(metadata) = &metadata {
2690 if &metadata.metadata.name != dist.name() {
2691 return Err(ResolveError::MismatchedPackageName {
2692 request: "distribution metadata",
2693 expected: dist.name().clone(),
2694 actual: metadata.metadata.name.clone(),
2695 });
2696 }
2697 }
2698
2699 Ok(Some(Response::Dist { dist, metadata }))
2700 }
2701
2702 Request::Installed(dist) => {
2703 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2704
2705 if let MetadataResponse::Found(metadata) = &metadata {
2706 if &metadata.metadata.name != dist.name() {
2707 return Err(ResolveError::MismatchedPackageName {
2708 request: "installed metadata",
2709 expected: dist.name().clone(),
2710 actual: metadata.metadata.name.clone(),
2711 });
2712 }
2713 }
2714
2715 Ok(Some(Response::Installed { dist, metadata }))
2716 }
2717
2718 Request::Prefetch(package_name, range, python_requirement) => {
2720 let versions_response = self
2722 .index
2723 .implicit()
2724 .wait(&package_name)
2725 .await
2726 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2727
2728 let version_map = match *versions_response {
2729 VersionsResponse::Found(ref version_map) => version_map,
2730 VersionsResponse::NoIndex => {
2732 self.unavailable_packages
2733 .pin()
2734 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2735
2736 return Ok(None);
2737 }
2738 VersionsResponse::Offline => {
2739 self.unavailable_packages
2740 .pin()
2741 .insert(package_name.clone(), UnavailablePackage::Offline);
2742
2743 return Ok(None);
2744 }
2745 VersionsResponse::NotFound => {
2746 self.unavailable_packages
2747 .pin()
2748 .insert(package_name.clone(), UnavailablePackage::NotFound);
2749
2750 return Ok(None);
2751 }
2752 };
2753
2754 let env = ResolverEnvironment::universal(vec![]);
2757
2758 let Some(candidate) = self.selector.select(
2761 &package_name,
2762 &range,
2763 version_map,
2764 &self.preferences,
2765 &self.installed_packages,
2766 &self.exclusions,
2767 None,
2768 &env,
2769 self.tags.as_ref(),
2770 ) else {
2771 return Ok(None);
2772 };
2773
2774 let Some(dist) = candidate.compatible() else {
2776 return Ok(None);
2777 };
2778
2779 for version_map in version_map {
2781 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2782 let dist = dist.for_resolution();
2783 if version_map.index() == dist.index() {
2784 debug!("Found registry-provided metadata for: {dist}");
2785
2786 let metadata =
2787 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2788
2789 let dist = dist.to_owned();
2790 if &package_name != dist.name() {
2791 return Err(ResolveError::MismatchedPackageName {
2792 request: "distribution",
2793 expected: package_name,
2794 actual: dist.name().clone(),
2795 });
2796 }
2797
2798 let response = match dist {
2799 ResolvedDist::Installable { dist, .. } => Response::Dist {
2800 dist: (*dist).clone(),
2801 metadata,
2802 },
2803 ResolvedDist::Installed { dist } => Response::Installed {
2804 dist: (*dist).clone(),
2805 metadata,
2806 },
2807 };
2808
2809 return Ok(Some(response));
2810 }
2811 }
2812 }
2813
2814 if dist.wheel().is_none() {
2818 if !self.selector.use_highest_version(&package_name, &env) {
2819 if let Some((lower, _)) = range.iter().next() {
2820 if lower == Bound::Unbounded {
2821 debug!(
2822 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2823 );
2824 return Ok(None);
2825 }
2826 }
2827 }
2828 }
2829
2830 let requires_python = match dist {
2832 CompatibleDist::InstalledDist(_) => None,
2833 CompatibleDist::SourceDist { sdist, .. }
2834 | CompatibleDist::IncompatibleWheel { sdist, .. } => {
2835 sdist.file.requires_python.as_ref()
2836 }
2837 CompatibleDist::CompatibleWheel { wheel, .. } => {
2838 wheel.file.requires_python.as_ref()
2839 }
2840 };
2841 if let Some(requires_python) = requires_python.as_ref() {
2842 if !python_requirement.target().is_contained_by(requires_python) {
2843 return Ok(None);
2844 }
2845 }
2846
2847 if !self
2849 .hasher
2850 .allows_package(candidate.name(), candidate.version())
2851 {
2852 return Ok(None);
2853 }
2854
2855 let dist = dist.for_resolution();
2857 if self.index.distributions().register(dist.distribution_id()) {
2858 let dist = dist.to_owned();
2859 if &package_name != dist.name() {
2860 return Err(ResolveError::MismatchedPackageName {
2861 request: "distribution",
2862 expected: package_name,
2863 actual: dist.name().clone(),
2864 });
2865 }
2866
2867 let response = match dist {
2868 ResolvedDist::Installable { dist, .. } => {
2869 let metadata = provider
2870 .get_or_build_wheel_metadata(&dist)
2871 .boxed_local()
2872 .await?;
2873
2874 Response::Dist {
2875 dist: (*dist).clone(),
2876 metadata,
2877 }
2878 }
2879 ResolvedDist::Installed { dist } => {
2880 let metadata =
2881 provider.get_installed_metadata(&dist).boxed_local().await?;
2882
2883 Response::Installed {
2884 dist: (*dist).clone(),
2885 metadata,
2886 }
2887 }
2888 };
2889
2890 Ok(Some(response))
2891 } else {
2892 Ok(None)
2893 }
2894 }
2895 }
2896 }
2897
2898 fn convert_no_solution_err(
2899 &self,
2900 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2901 fork_urls: ForkUrls,
2902 fork_indexes: ForkIndexes,
2903 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2904 env: ResolverEnvironment,
2905 current_environment: MarkerEnvironment,
2906 visited: &FxHashSet<PackageName>,
2907 ) -> ResolveError {
2908 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2909 err,
2910 ));
2911 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2912
2913 let mut unavailable_packages = FxHashMap::default();
2914 for package in derivation_tree_packages(&err) {
2915 if let PubGrubPackageInner::Package { name, .. } = &**package {
2916 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2917 unavailable_packages.insert(name.clone(), reason.clone());
2918 }
2919 }
2920 }
2921
2922 let mut incomplete_packages = FxHashMap::default();
2923 let incomplete_packages_cache = self.incomplete_packages.pin();
2924 for package in derivation_tree_packages(&err) {
2925 if let PubGrubPackageInner::Package { name, .. } = &**package
2926 && let Some(versions) = incomplete_packages_cache.get(name)
2927 {
2928 for (version, reason) in &versions.pin() {
2929 incomplete_packages
2930 .entry(name.clone())
2931 .or_insert_with(BTreeMap::default)
2932 .insert(version.clone(), reason.clone());
2933 }
2934 }
2935 }
2936
2937 let mut available_indexes = FxHashMap::default();
2938 let mut included_versions = FxHashMap::default();
2939 let mut available_versions = FxHashMap::default();
2940
2941 let available_version_cutoff: Option<jiff::Timestamp> =
2942 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2943 .ok()
2944 .and_then(|s| s.parse().ok());
2945
2946 for package in derivation_tree_packages(&err) {
2947 let Some(name) = package.name() else { continue };
2948 if !visited.contains(name) {
2949 continue;
2954 }
2955 let versions_response = if let Some(index) = fork_indexes.get(name) {
2956 self.index
2957 .explicit()
2958 .get(&(name.clone(), index.url().clone()))
2959 } else {
2960 self.index.implicit().get(name)
2961 };
2962 if let Some(response) = versions_response {
2963 if let VersionsResponse::Found(ref version_maps) = *response {
2964 for version_map in version_maps {
2966 let package_included_versions = included_versions
2967 .entry(name.clone())
2968 .or_insert_with(BTreeSet::new);
2969 let package_available_versions = available_versions
2970 .entry(name.clone())
2971 .or_insert_with(BTreeSet::new);
2972
2973 for (version, dists) in version_map.iter(&Ranges::full()) {
2974 let excluded_from_included = || {
2979 let Some(included_version_cutoff) =
2980 version_map.included_version_cutoff()
2981 else {
2982 return false;
2983 };
2984 let Some(prioritized_dist) = dists.prioritized_dist() else {
2985 return true;
2986 };
2987 prioritized_dist.files().all(|file| {
2988 file.upload_time_utc_ms.is_none_or(|upload_time| {
2989 upload_time >= included_version_cutoff.as_millisecond()
2990 })
2991 })
2992 };
2993
2994 if !excluded_from_included() {
2995 package_included_versions.insert(version.clone());
2996 }
2997
2998 let excluded_from_available = || {
3004 let Some(ref exclude_newer) = available_version_cutoff else {
3005 return false;
3006 };
3007 let Some(prioritized_dist) = dists.prioritized_dist() else {
3008 return false;
3009 };
3010 prioritized_dist.files().all(|file| {
3011 file.upload_time_utc_ms.is_some_and(|upload_time| {
3012 upload_time >= exclude_newer.as_millisecond()
3013 })
3014 })
3015 };
3016
3017 if !excluded_from_available() {
3018 package_available_versions.insert(version.clone());
3019 }
3020 }
3021 }
3022
3023 available_indexes
3025 .entry(name.clone())
3026 .or_insert(BTreeSet::new())
3027 .extend(
3028 version_maps
3029 .iter()
3030 .filter_map(|version_map| version_map.index().cloned()),
3031 );
3032 }
3033 }
3034 }
3035
3036 ResolveError::NoSolution(Box::new(NoSolutionError::new(
3037 err,
3038 self.index.clone(),
3039 included_versions,
3040 available_versions,
3041 available_indexes,
3042 self.selector.clone(),
3043 self.python_requirement.clone(),
3044 self.locations.clone(),
3045 self.capabilities.clone(),
3046 unavailable_packages,
3047 incomplete_packages,
3048 fork_urls,
3049 fork_indexes,
3050 env,
3051 current_environment,
3052 self.tags.clone(),
3053 self.workspace_members.clone(),
3054 self.options.clone(),
3055 )))
3056 }
3057
3058 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
3059 if let Some(reporter) = self.reporter.as_ref() {
3060 match &**package {
3061 PubGrubPackageInner::Root(_) => {}
3062 PubGrubPackageInner::Python(_) => {}
3063 PubGrubPackageInner::System(_) => {}
3064 PubGrubPackageInner::Marker { .. } => {}
3065 PubGrubPackageInner::Extra { .. } => {}
3066 PubGrubPackageInner::Group { .. } => {}
3067 PubGrubPackageInner::Package { name, .. } => {
3068 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
3069 }
3070 }
3071 }
3072 }
3073
3074 fn on_complete(&self) {
3075 if let Some(reporter) = self.reporter.as_ref() {
3076 reporter.on_complete();
3077 }
3078 }
3079}
3080
3081#[derive(Clone)]
3083pub(crate) struct ForkState {
3084 pubgrub: State<UvDependencyProvider>,
3092 initial_id: Option<Id<PubGrubPackage>>,
3095 initial_version: Option<Version>,
3098 next: Id<PubGrubPackage>,
3100 pins: FilePins,
3109 fork_urls: ForkUrls,
3115 fork_indexes: ForkIndexes,
3120 priorities: PubGrubPriorities,
3126 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3129 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3131 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3133 known_versions: FxHashMap<PackageName, Arc<[Version]>>,
3138 env: ResolverEnvironment,
3153 python_requirement: PythonRequirement,
3166 conflict_tracker: ConflictTracker,
3167 prefetcher: BatchPrefetcher,
3171}
3172
3173impl ForkState {
3174 fn new(
3175 pubgrub: State<UvDependencyProvider>,
3176 env: ResolverEnvironment,
3177 python_requirement: PythonRequirement,
3178 prefetcher: BatchPrefetcher,
3179 ) -> Self {
3180 Self {
3181 initial_id: None,
3182 initial_version: None,
3183 next: pubgrub.root_package,
3184 pubgrub,
3185 pins: FilePins::default(),
3186 fork_urls: ForkUrls::default(),
3187 fork_indexes: ForkIndexes::default(),
3188 priorities: PubGrubPriorities::default(),
3189 added_dependencies: FxHashMap::default(),
3190 pre_visited: FxHashMap::default(),
3191 selected_versions: FxHashMap::default(),
3192 known_versions: FxHashMap::default(),
3193 env,
3194 python_requirement,
3195 conflict_tracker: ConflictTracker::default(),
3196 prefetcher,
3197 }
3198 }
3199
3200 fn visit_package_version_dependencies(
3203 &mut self,
3204 for_package: Id<PubGrubPackage>,
3205 for_version: &Version,
3206 urls: &Urls,
3207 indexes: &Indexes,
3208 dependencies: &[PubGrubDependency],
3209 git: &GitResolver,
3210 workspace_members: &BTreeSet<PackageName>,
3211 resolution_strategy: &ResolutionStrategy,
3212 ) -> Result<(), ResolveError> {
3213 for dependency in dependencies {
3214 let PubGrubDependency {
3215 package,
3216 version,
3217 parent: _,
3218 source,
3219 } = dependency;
3220
3221 let mut has_url = false;
3222 if let Some(name) = package.name() {
3223 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3228 self.fork_urls.insert(name, url, &self.env)?;
3229 has_url = true;
3230 }
3231
3232 if let Some(index) = source.explicit_index() {
3233 self.fork_indexes.insert(name, index, &self.env)?;
3234 }
3235
3236 for index in indexes.get(name, &self.env) {
3238 self.fork_indexes.insert(name, index, &self.env)?;
3239 }
3240 }
3241
3242 if let Some(name) = self.pubgrub.package_store[for_package]
3243 .name_no_root()
3244 .filter(|name| !workspace_members.contains(name))
3245 {
3246 debug!(
3247 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3248 );
3249 } else {
3250 debug!("Adding direct dependency: {package}{version}");
3252
3253 let missing_lower_bound = version
3255 .bounding_range()
3256 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3257 let strategy_lowest = matches!(
3258 resolution_strategy,
3259 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3260 );
3261
3262 if !has_url && missing_lower_bound && strategy_lowest {
3263 let name = package.name_no_root().unwrap();
3264 let bound_on_other_package = dependencies.iter().any(|other| {
3271 Some(name) == other.package.name()
3272 && !other
3273 .version
3274 .bounding_range()
3275 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3276 });
3277
3278 if !bound_on_other_package {
3279 warn_user_once!(
3280 "The direct dependency `{name}` is unpinned. \
3281 Consider setting a lower bound when using `--resolution lowest` \
3282 or `--resolution lowest-direct` to avoid using outdated versions.",
3283 );
3284 }
3285 }
3286 }
3287
3288 self.priorities.insert(package, version, &self.fork_urls);
3290 if let Some(base_package) = package.base_package() {
3293 self.priorities
3294 .insert(&base_package, version, &self.fork_urls);
3295 }
3296 }
3297
3298 Ok(())
3299 }
3300
3301 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3307 &mut self,
3308 for_package: Id<PubGrubPackage>,
3309 for_version: &Version,
3310 dependencies: Vec<PubGrubDependency>,
3311 index: &InMemoryIndex,
3312 installed_packages: &InstalledPackages,
3313 ) {
3314 for dependency in &dependencies {
3315 let PubGrubDependency {
3316 package,
3317 version,
3318 parent: _,
3319 source: _,
3320 } = dependency;
3321
3322 let Some(base_package) = package.base_package() else {
3323 continue;
3324 };
3325
3326 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3327 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3328 self.pubgrub.add_proxy_package_incompatibility(
3329 proxy_package,
3330 base_package_id,
3331 version.clone(),
3332 );
3333 }
3334
3335 let versions = Range::singleton(for_version.clone());
3338 let versions = if let Some(known_versions) =
3339 ResolverState::<InstalledPackages>::known_versions(
3340 index,
3341 installed_packages,
3342 &self.fork_urls,
3343 &self.fork_indexes,
3344 &mut self.known_versions,
3345 &self.pubgrub.package_store[self.next],
3346 )
3347 .filter(|versions| !versions.is_empty())
3348 {
3349 versions.widen_versions(known_versions)
3350 } else {
3351 versions
3354 };
3355 let conflict = self.pubgrub.add_package_version_dependencies(
3356 self.next,
3357 for_version.clone(),
3358 versions,
3359 dependencies.into_iter().map(|dependency| {
3360 let PubGrubDependency {
3361 package,
3362 version,
3363 parent: _,
3364 source: _,
3365 } = dependency;
3366 (package, version)
3367 }),
3368 );
3369
3370 if let Some(incompatibility) = conflict {
3373 self.record_conflict(for_package, Some(for_version), incompatibility);
3374 }
3375 }
3376
3377 fn record_conflict(
3378 &mut self,
3379 affected: Id<PubGrubPackage>,
3380 version: Option<&Version>,
3381 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3382 ) {
3383 let mut culprit_is_real = false;
3384 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3385 if incompatible == affected {
3386 continue;
3387 }
3388 if self.pubgrub.package_store[affected].name()
3389 == self.pubgrub.package_store[incompatible].name()
3390 {
3391 continue;
3394 }
3395 culprit_is_real = true;
3396 let culprit_count = self
3397 .conflict_tracker
3398 .culprit
3399 .entry(incompatible)
3400 .or_default();
3401 *culprit_count += 1;
3402 if *culprit_count == CONFLICT_THRESHOLD {
3403 self.conflict_tracker.deprioritize.push(incompatible);
3404 }
3405 }
3406 if culprit_is_real {
3409 if tracing::enabled!(Level::DEBUG) {
3410 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3411 .iter()
3412 .map(|(package, _term)| &self.pubgrub.package_store[package])
3413 .join(", ");
3414 if let Some(version) = version {
3415 debug!(
3416 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3417 self.pubgrub.package_store[affected], version, incompatibility
3418 );
3419 } else {
3420 debug!(
3421 "Recording unit propagation conflict of {} from incompatibility of ({})",
3422 self.pubgrub.package_store[affected], incompatibility
3423 );
3424 }
3425 }
3426
3427 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3428 *affected_count += 1;
3429 if *affected_count == CONFLICT_THRESHOLD {
3430 self.conflict_tracker.prioritize.push(self.next);
3431 }
3432 }
3433 }
3434
3435 fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
3436 if let UnavailableVersion::IncompatibleDist(
3440 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3441 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3442 ) = reason
3443 {
3444 let package = &self.next;
3445 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3446 PubGrubPackageInner::Python(match kind {
3447 PythonRequirementKind::Installed => PubGrubPython::Installed,
3448 PythonRequirementKind::Target => PubGrubPython::Target,
3449 }),
3450 ));
3451 self.pubgrub
3452 .add_incompatibility(Incompatibility::from_dependency(
3453 *package,
3454 Range::singleton(version.clone()),
3455 (
3456 python,
3457 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3458 ),
3459 ));
3460 self.pubgrub
3461 .partial_solution
3462 .add_decision(self.next, version);
3463 return;
3464 }
3465 self.pubgrub
3466 .add_incompatibility(Incompatibility::custom_version(
3467 self.next,
3468 version.clone(),
3469 UnavailableReason::Version(reason),
3470 ));
3471 }
3472
3473 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3479 self.env = env;
3480 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3482 debug!("Narrowed `requires-python` bound to: {}", req.target());
3483 self.python_requirement = req;
3484 }
3485 self
3486 }
3487
3488 fn source(
3492 &self,
3493 name: &PackageName,
3494 version: &Version,
3495 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3496 let url = self.fork_urls.get(name);
3497 let index = url
3498 .is_none()
3499 .then(|| {
3500 self.pins
3501 .get(name, version)
3502 .expect("Every package should be pinned")
3503 .index()
3504 })
3505 .flatten();
3506 (url, index)
3507 }
3508
3509 fn into_resolution(self) -> Resolution {
3510 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3511 let edge_count: usize = solution
3512 .keys()
3513 .map(|package| self.pubgrub.incompatibilities[package].len())
3514 .sum();
3515 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3516 for (package, self_version) in &solution {
3517 for id in &self.pubgrub.incompatibilities[package] {
3518 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3519 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3520 &incompatibility.kind
3521 else {
3522 continue;
3523 };
3524 let (self_package, dependency_package) = (*self_package, *dependency_package);
3525 let Some((self_range, dependency_range)) =
3526 incompatibility.dependency_version_sets()
3527 else {
3528 continue;
3529 };
3530 let dependency_range =
3531 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3532 if *package != self_package {
3533 continue;
3534 }
3535 if !self_range.contains(self_version) {
3536 continue;
3537 }
3538 let Some(dependency_version) = solution.get(&dependency_package) else {
3539 continue;
3540 };
3541 if !dependency_range.contains(dependency_version) {
3542 continue;
3543 }
3544
3545 let self_package = &self.pubgrub.package_store[self_package];
3546 let dependency_package = &self.pubgrub.package_store[dependency_package];
3547
3548 let (self_name, self_extra, self_group) = match &**self_package {
3549 PubGrubPackageInner::Package {
3550 name: self_name,
3551 extra: self_extra,
3552 group: self_group,
3553 marker: _,
3554 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3555
3556 PubGrubPackageInner::Root(_) => (None, None, None),
3557
3558 _ => continue,
3559 };
3560
3561 let (self_url, self_index) = self_name
3562 .map(|self_name| self.source(self_name, self_version))
3563 .unwrap_or((None, None));
3564
3565 match **dependency_package {
3566 PubGrubPackageInner::Package {
3567 name: ref dependency_name,
3568 extra: ref dependency_extra,
3569 group: ref dependency_dev,
3570 marker: ref dependency_marker,
3571 } => {
3572 debug_assert!(
3573 dependency_extra.is_none(),
3574 "Packages should depend on an extra proxy"
3575 );
3576 debug_assert!(
3577 dependency_dev.is_none(),
3578 "Packages should depend on a group proxy"
3579 );
3580
3581 if self_group.is_none() {
3584 if self_name == Some(dependency_name) {
3585 continue;
3586 }
3587 }
3588
3589 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3590
3591 let edge = ResolutionDependencyEdge {
3592 from: self_name.cloned(),
3593 from_version: self_version.clone(),
3594 from_url: self_url.cloned(),
3595 from_index: self_index.cloned(),
3596 from_extra: self_extra.cloned(),
3597 from_group: self_group.cloned(),
3598 to: dependency_name.clone(),
3599 to_version: dependency_version.clone(),
3600 to_url: to_url.cloned(),
3601 to_index: to_index.cloned(),
3602 to_extra: dependency_extra.clone(),
3603 to_group: dependency_dev.clone(),
3604 marker: *dependency_marker,
3605 };
3606 edges.push(edge);
3607 }
3608
3609 PubGrubPackageInner::Marker {
3610 name: ref dependency_name,
3611 marker: ref dependency_marker,
3612 } => {
3613 if self_group.is_none() {
3616 if self_name == Some(dependency_name) {
3617 continue;
3618 }
3619 }
3620
3621 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3622
3623 let edge = ResolutionDependencyEdge {
3624 from: self_name.cloned(),
3625 from_version: self_version.clone(),
3626 from_url: self_url.cloned(),
3627 from_index: self_index.cloned(),
3628 from_extra: self_extra.cloned(),
3629 from_group: self_group.cloned(),
3630 to: dependency_name.clone(),
3631 to_version: dependency_version.clone(),
3632 to_url: to_url.cloned(),
3633 to_index: to_index.cloned(),
3634 to_extra: None,
3635 to_group: None,
3636 marker: *dependency_marker,
3637 };
3638 edges.push(edge);
3639 }
3640
3641 PubGrubPackageInner::Extra {
3642 name: ref dependency_name,
3643 extra: ref dependency_extra,
3644 marker: ref dependency_marker,
3645 } => {
3646 if self_group.is_none() {
3647 debug_assert!(
3648 self_name != Some(dependency_name),
3649 "Extras should be flattened"
3650 );
3651 }
3652 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3653
3654 let edge = ResolutionDependencyEdge {
3656 from: self_name.cloned(),
3657 from_version: self_version.clone(),
3658 from_url: self_url.cloned(),
3659 from_index: self_index.cloned(),
3660 from_extra: self_extra.cloned(),
3661 from_group: self_group.cloned(),
3662 to: dependency_name.clone(),
3663 to_version: dependency_version.clone(),
3664 to_url: to_url.cloned(),
3665 to_index: to_index.cloned(),
3666 to_extra: Some(dependency_extra.clone()),
3667 to_group: None,
3668 marker: *dependency_marker,
3669 };
3670 edges.push(edge);
3671
3672 let edge = ResolutionDependencyEdge {
3674 from: self_name.cloned(),
3675 from_version: self_version.clone(),
3676 from_url: self_url.cloned(),
3677 from_index: self_index.cloned(),
3678 from_extra: self_extra.cloned(),
3679 from_group: self_group.cloned(),
3680 to: dependency_name.clone(),
3681 to_version: dependency_version.clone(),
3682 to_url: to_url.cloned(),
3683 to_index: to_index.cloned(),
3684 to_extra: None,
3685 to_group: None,
3686 marker: *dependency_marker,
3687 };
3688 edges.push(edge);
3689 }
3690
3691 PubGrubPackageInner::Group {
3692 name: ref dependency_name,
3693 group: ref dependency_group,
3694 marker: ref dependency_marker,
3695 } => {
3696 debug_assert!(
3697 self_name != Some(dependency_name),
3698 "Groups should be flattened"
3699 );
3700
3701 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3702
3703 let edge = ResolutionDependencyEdge {
3706 from: self_name.cloned(),
3707 from_version: self_version.clone(),
3708 from_url: self_url.cloned(),
3709 from_index: self_index.cloned(),
3710 from_extra: self_extra.cloned(),
3711 from_group: self_group.cloned(),
3712 to: dependency_name.clone(),
3713 to_version: dependency_version.clone(),
3714 to_url: to_url.cloned(),
3715 to_index: to_index.cloned(),
3716 to_extra: None,
3717 to_group: Some(dependency_group.clone()),
3718 marker: *dependency_marker,
3719 };
3720 edges.push(edge);
3721 }
3722
3723 _ => {}
3724 }
3725 }
3726 }
3727
3728 let nodes = solution
3729 .into_iter()
3730 .filter_map(|(package, version)| {
3731 if let PubGrubPackageInner::Package {
3732 name,
3733 extra,
3734 group,
3735 marker: MarkerTree::TRUE,
3736 } = &*self.pubgrub.package_store[package]
3737 {
3738 let (url, index) = self.source(name, &version);
3739 Some((
3740 ResolutionPackage {
3741 name: name.clone(),
3742 extra: extra.clone(),
3743 dev: group.clone(),
3744 url: url.cloned(),
3745 index: index.cloned(),
3746 },
3747 version,
3748 ))
3749 } else {
3750 None
3751 }
3752 })
3753 .collect();
3754
3755 Resolution {
3756 nodes,
3757 edges,
3758 pins: self.pins,
3759 env: self.env,
3760 }
3761 }
3762}
3763
3764#[derive(Debug)]
3766pub(crate) struct Resolution {
3767 pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
3768 pub(crate) edges: Vec<ResolutionDependencyEdge>,
3771 pub(crate) pins: FilePins,
3773 pub(crate) env: ResolverEnvironment,
3775}
3776
3777#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3780pub(crate) struct ResolutionPackage {
3781 pub(crate) name: PackageName,
3782 pub(crate) extra: Option<ExtraName>,
3783 pub(crate) dev: Option<GroupName>,
3784 pub(crate) url: Option<VerbatimParsedUrl>,
3786 pub(crate) index: Option<IndexUrl>,
3788}
3789
3790#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3793pub(crate) struct ResolutionDependencyEdge {
3794 pub(super) from: Option<PackageName>,
3796 pub(super) from_version: Version,
3797 pub(super) from_url: Option<VerbatimParsedUrl>,
3798 pub(super) from_index: Option<IndexUrl>,
3799 pub(super) from_extra: Option<ExtraName>,
3800 pub(super) from_group: Option<GroupName>,
3801 pub(super) to: PackageName,
3802 pub(super) to_version: Version,
3803 pub(super) to_url: Option<VerbatimParsedUrl>,
3804 pub(super) to_index: Option<IndexUrl>,
3805 pub(super) to_extra: Option<ExtraName>,
3806 pub(super) to_group: Option<GroupName>,
3807 pub(super) marker: MarkerTree,
3808}
3809
3810impl ResolutionDependencyEdge {
3811 pub(crate) fn universal_marker(&self) -> UniversalMarker {
3812 UniversalMarker::new(self.marker, ConflictMarker::TRUE)
3816 }
3817}
3818
3819#[derive(Debug)]
3821#[expect(clippy::large_enum_variant)]
3822pub(crate) enum Request {
3823 Package(PackageName, Option<IndexMetadata>),
3825 Dist(Dist),
3827 Installed(InstalledDist),
3829 Prefetch(PackageName, Range<Version>, PythonRequirement),
3831}
3832
3833impl<'a> From<ResolvedDistRef<'a>> for Request {
3834 fn from(dist: ResolvedDistRef<'a>) -> Self {
3835 match dist {
3841 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3842 let source = prioritized.source_dist().expect("a source distribution");
3845 assert_eq!(
3846 (&sdist.name, &sdist.version),
3847 (&source.name, &source.version),
3848 "expected chosen sdist to match prioritized sdist"
3849 );
3850 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3851 }
3852 ResolvedDistRef::InstallableRegistryBuiltDist {
3853 wheel, prioritized, ..
3854 } => {
3855 assert_eq!(
3856 Some(&wheel.filename),
3857 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3858 "expected chosen wheel to match best wheel"
3859 );
3860 let built = prioritized.built_dist().expect("at least one wheel");
3863 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3864 }
3865 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3866 }
3867 }
3868}
3869
3870impl Display for Request {
3871 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3872 match self {
3873 Self::Package(package_name, _) => {
3874 write!(f, "Versions {package_name}")
3875 }
3876 Self::Dist(dist) => {
3877 write!(f, "Metadata {dist}")
3878 }
3879 Self::Installed(dist) => {
3880 write!(f, "Installed metadata {dist}")
3881 }
3882 Self::Prefetch(package_name, range, _) => {
3883 write!(f, "Prefetch {package_name} {range}")
3884 }
3885 }
3886 }
3887}
3888
3889#[derive(Debug)]
3890#[expect(clippy::large_enum_variant)]
3891enum Response {
3892 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3894 Dist {
3896 dist: Dist,
3897 metadata: MetadataResponse,
3898 },
3899 Installed {
3901 dist: InstalledDist,
3902 metadata: MetadataResponse,
3903 },
3904}
3905
3906enum Dependencies {
3912 Unavailable(UnavailableVersion),
3914 Available(Vec<PubGrubDependency>),
3920 RequiresPython(VersionSpecifiers),
3922 Unforkable(Vec<PubGrubDependency>),
3928}
3929
3930impl Dependencies {
3931 fn fork(
3938 self,
3939 env: &ResolverEnvironment,
3940 python_requirement: &PythonRequirement,
3941 conflicts: &Conflicts,
3942 ) -> ForkedDependencies {
3943 let deps = match self {
3944 Self::Available(deps) => deps,
3945 Self::Unforkable(deps) => return ForkedDependencies::Unforked(deps),
3946 Self::RequiresPython(requires_python) => {
3947 return ForkedDependencies::RequiresPython(requires_python);
3948 }
3949 Self::Unavailable(err) => return ForkedDependencies::Unavailable(err),
3950 };
3951 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3952 for dep in deps {
3953 let name = dep
3954 .package
3955 .name()
3956 .expect("dependency always has a name")
3957 .clone();
3958 name_to_deps.entry(name).or_default().push(dep);
3959 }
3960 let Forks {
3961 mut forks,
3962 diverging_packages,
3963 } = Forks::new(name_to_deps, env, python_requirement, conflicts);
3964 if forks.is_empty() {
3965 ForkedDependencies::Unforked(vec![])
3966 } else if forks.len() == 1 {
3967 ForkedDependencies::Unforked(forks.pop().unwrap().dependencies)
3968 } else {
3969 ForkedDependencies::Forked {
3970 forks,
3971 diverging_packages: diverging_packages.into_iter().collect(),
3972 }
3973 }
3974 }
3975}
3976
3977#[derive(Debug)]
3984enum ForkedDependencies {
3985 Unavailable(UnavailableVersion),
3987 Unforked(Vec<PubGrubDependency>),
3991 Forked {
3997 forks: Vec<Fork>,
3998 diverging_packages: Vec<PackageName>,
4000 },
4001 RequiresPython(VersionSpecifiers),
4003}
4004
4005#[derive(Debug, Default)]
4010struct Forks {
4011 forks: Vec<Fork>,
4013 diverging_packages: BTreeSet<PackageName>,
4015}
4016
4017impl Forks {
4018 fn new(
4019 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
4020 env: &ResolverEnvironment,
4021 python_requirement: &PythonRequirement,
4022 conflicts: &Conflicts,
4023 ) -> Self {
4024 let python_marker = python_requirement.to_marker_tree();
4025
4026 let mut forks = vec![Fork::new(env.clone())];
4027 let mut diverging_packages = BTreeSet::new();
4028 for (name, mut deps) in name_to_deps {
4029 assert!(!deps.is_empty(), "every name has at least one dependency");
4030 if let [dep] = deps.as_slice() {
4041 if marker::requires_python(dep.package.marker())
4049 .is_none_or(|bound| !python_requirement.raises(&bound))
4050 {
4051 let dep = deps.pop().unwrap();
4052 let marker = dep.package.marker();
4053 for fork in &mut forks {
4054 if fork.env.included_by_marker(marker) {
4055 fork.add_dependency(dep.clone());
4056 }
4057 }
4058 continue;
4059 }
4060 } else {
4061 if let Some(dep) = deps.first() {
4063 let marker = dep.package.marker();
4064 if deps.iter().all(|dep| marker == dep.package.marker()) {
4065 if marker::requires_python(marker)
4069 .is_none_or(|bound| !python_requirement.raises(&bound))
4070 {
4071 for dep in deps {
4072 for fork in &mut forks {
4073 if fork.env.included_by_marker(marker) {
4074 fork.add_dependency(dep.clone());
4075 }
4076 }
4077 }
4078 continue;
4079 }
4080 }
4081 }
4082 }
4083 for dep in deps {
4084 let mut forker = match ForkingPossibility::new(env, &dep) {
4085 ForkingPossibility::Possible(forker) => forker,
4086 ForkingPossibility::DependencyAlwaysExcluded => {
4087 continue;
4090 }
4091 ForkingPossibility::NoForkingPossible => {
4092 for fork in &mut forks {
4095 fork.add_dependency(dep.clone());
4096 }
4097 continue;
4098 }
4099 };
4100 diverging_packages.insert(name.clone());
4102
4103 let mut new = vec![];
4104 for fork in std::mem::take(&mut forks) {
4105 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4106 new.push(fork);
4107 continue;
4108 };
4109 forker = remaining_forker;
4110
4111 for fork_env in envs {
4112 let mut new_fork = fork.clone();
4113 new_fork.set_env(fork_env);
4114 if forker.included(&new_fork.env) {
4119 new_fork.add_dependency(dep.clone());
4120 }
4121 if new_fork.env.included_by_marker(python_marker) {
4124 new.push(new_fork);
4125 }
4126 }
4127 }
4128 forks = new;
4129 }
4130 }
4131 for set in conflicts.iter() {
4146 let mut new = vec![];
4147 for fork in std::mem::take(&mut forks) {
4148 let mut has_conflicting_dependency = false;
4156 for item in set.iter() {
4157 if fork.contains_conflicting_item(item.as_ref()) {
4158 has_conflicting_dependency = true;
4159 diverging_packages.insert(item.package().clone());
4160 break;
4161 }
4162 }
4163 if !has_conflicting_dependency {
4164 new.push(fork);
4165 continue;
4166 }
4167
4168 let non_excluded: Vec<_> = set
4174 .iter()
4175 .filter(|item| fork.env.included_by_group(item.as_ref()))
4176 .collect();
4177 if non_excluded.len() < 2 {
4178 let dominated = non_excluded.iter().all(|item| {
4183 !conflicts.iter().any(|other_set| {
4184 !std::ptr::eq(set, other_set)
4185 && other_set.contains(item.package(), item.kind().as_ref())
4186 && other_set
4187 .iter()
4188 .filter(|other_item| {
4189 other_item.package() != item.package()
4190 || other_item.kind() != item.kind()
4191 })
4192 .any(|other_item| {
4193 fork.env.included_by_group(other_item.as_ref())
4194 })
4195 })
4196 });
4197 if dominated {
4198 let rules: Vec<_> = set
4203 .iter()
4204 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4205 .cloned()
4206 .map(Err)
4207 .collect();
4208 if let Some(filtered) = fork.filter(rules) {
4209 new.push(filtered);
4210 }
4211 continue;
4212 }
4213 }
4214
4215 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4217 new.push(fork_none);
4218 }
4219
4220 for (i, _) in set.iter().enumerate() {
4228 let fork_allows_group = fork.clone().filter(
4229 set.iter()
4230 .cloned()
4231 .enumerate()
4232 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4233 );
4234 if let Some(fork_allows_group) = fork_allows_group {
4235 new.push(fork_allows_group);
4236 }
4237 }
4238 }
4239 forks = new;
4240 }
4241 Self {
4242 forks,
4243 diverging_packages,
4244 }
4245 }
4246}
4247
4248#[derive(Clone, Debug)]
4258struct Fork {
4259 dependencies: Vec<PubGrubDependency>,
4268 conflicts: crate::FxHashbrownSet<ConflictItem>,
4274 env: ResolverEnvironment,
4286}
4287
4288impl Fork {
4289 fn new(env: ResolverEnvironment) -> Self {
4292 Self {
4293 dependencies: vec![],
4294 conflicts: crate::FxHashbrownSet::default(),
4295 env,
4296 }
4297 }
4298
4299 fn add_dependency(&mut self, dep: PubGrubDependency) {
4301 if let Some(conflicting_item) = dep.conflicting_item() {
4302 self.conflicts.insert(conflicting_item.to_owned());
4303 }
4304 self.dependencies.push(dep);
4305 }
4306
4307 fn set_env(&mut self, env: ResolverEnvironment) {
4312 self.env = env;
4313 self.dependencies.retain(|dep| {
4314 let marker = dep.package.marker();
4315 if self.env.included_by_marker(marker) {
4316 return true;
4317 }
4318 if let Some(conflicting_item) = dep.conflicting_item() {
4319 self.conflicts.remove(&conflicting_item);
4320 }
4321 false
4322 });
4323 }
4324
4325 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4328 self.conflicts.contains(&item)
4329 }
4330
4331 fn filter(
4338 mut self,
4339 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4340 ) -> Option<Self> {
4341 self.env = self.env.filter_by_group(rules)?;
4342 self.dependencies.retain(|dep| {
4343 let Some(conflicting_item) = dep.conflicting_item() else {
4344 return true;
4345 };
4346 if self.env.included_by_group(conflicting_item) {
4347 return true;
4348 }
4349 match conflicting_item.kind() {
4350 ConflictKindRef::Project => {
4353 if dep.parent.is_some() {
4354 return true;
4355 }
4356 }
4357 ConflictKindRef::Group(_) => {}
4358 ConflictKindRef::Extra(_) => {}
4359 }
4360 self.conflicts.remove(&conflicting_item);
4361 false
4362 });
4363 Some(self)
4364 }
4365
4366 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4368 let self_bound = self.env.requires_python().unwrap_or_default();
4378 let other_bound = other.env.requires_python().unwrap_or_default();
4379 self_bound.lower().cmp(other_bound.lower())
4380 }
4381
4382 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4384 let self_upper_bounds = self
4389 .dependencies
4390 .iter()
4391 .filter(|dep| {
4392 dep.version
4393 .bounding_range()
4394 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4395 })
4396 .count();
4397 let other_upper_bounds = other
4398 .dependencies
4399 .iter()
4400 .filter(|dep| {
4401 dep.version
4402 .bounding_range()
4403 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4404 })
4405 .count();
4406
4407 self_upper_bounds.cmp(&other_upper_bounds)
4408 }
4409}
4410
4411impl Eq for Fork {}
4412
4413impl PartialEq for Fork {
4414 fn eq(&self, other: &Self) -> bool {
4415 self.dependencies == other.dependencies && self.env == other.env
4416 }
4417}
4418
4419#[derive(Debug, Clone)]
4420pub(crate) struct VersionFork {
4421 env: ResolverEnvironment,
4423 id: Id<PubGrubPackage>,
4425 version: Option<Version>,
4427}
4428
4429fn enrich_dependency_error(
4431 error: ResolveError,
4432 id: Id<PubGrubPackage>,
4433 version: &Version,
4434 pubgrub: &State<UvDependencyProvider>,
4435) -> ResolveError {
4436 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4437 return error;
4438 };
4439 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4440 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4441}
4442
4443fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4445 let package = &state.package_store[id];
4446 if package.is_root() {
4447 return MarkerTree::TRUE;
4448 }
4449
4450 let mut ancestors = FxHashSet::default();
4453 let mut stack = vec![id];
4454 let mut root = None;
4455 ancestors.insert(id);
4456
4457 while let Some(current) = stack.pop() {
4458 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4459 continue;
4460 };
4461
4462 for index in incompatibilities {
4463 let incompat = &state.incompatibility_store[*index];
4464 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4465 if current != *child {
4466 continue;
4467 }
4468 if ancestors.insert(*parent) {
4469 if state.package_store[*parent].is_root() {
4470 root = Some(*parent);
4471 }
4472 stack.push(*parent);
4473 }
4474 }
4475 }
4476 }
4477
4478 let Some(root) = root else {
4479 return MarkerTree::FALSE;
4480 };
4481
4482 let mut environments = FxHashMap::default();
4485 let mut queue = VecDeque::from([root]);
4486 environments.insert(root, MarkerTree::TRUE);
4487
4488 while let Some(current) = queue.pop_front() {
4489 let Some(current_environment) = environments.get(¤t).copied() else {
4490 continue;
4491 };
4492 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4493 continue;
4494 };
4495
4496 for index in incompatibilities {
4497 let incompat = &state.incompatibility_store[*index];
4498 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4499 continue;
4500 };
4501 if current != *parent || !ancestors.contains(child) {
4502 continue;
4503 }
4504
4505 let mut next_environment = state.package_store[*child].marker();
4506 next_environment = next_environment.and(current_environment);
4507
4508 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4509 let mut combined = *entry;
4510 combined = combined.or(next_environment);
4511 if combined != *entry {
4512 *entry = combined;
4513 queue.push_back(*child);
4514 }
4515 }
4516 }
4517
4518 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4519}
4520
4521#[derive(Debug, Default, Clone)]
4522struct ConflictTracker {
4523 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4525 prioritize: Vec<Id<PubGrubPackage>>,
4529 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4531 deprioritize: Vec<Id<PubGrubPackage>>,
4535}