1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{mem, thread};
11
12use futures::{FutureExt, StreamExt};
13use itertools::Itertools;
14use papaya::{HashMap, ResizeMode};
15use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
16use rustc_hash::{FxHashMap, FxHashSet};
17use tokio::sync::mpsc::{self, Receiver};
18use tokio::sync::oneshot;
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{Level, debug, info, instrument, trace, warn};
21
22use uv_configuration::{Constraints, DependencyModifiers};
23use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24use uv_distribution_types::{
25 BuiltDist, DerivationChain, Dist, DistErrorKind, Identifier, IndexCapabilities, IndexLocations,
26 IndexMetadata, IndexUrl, InstalledDist, Name, RemoteSource, Requirement, RequiresPython,
27 ResolutionRecorder, ResolvedDist, SourceDist, VersionOrUrlRef,
28};
29use uv_git::GitResolver;
30use uv_normalize::PackageName;
31use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
32use uv_pep508::{
33 MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
34};
35use uv_platform_tags::{IncompatibleTag, Tags};
36use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
37use uv_resolver_types::PackageNodeKind;
38use uv_static::EnvVars;
39use uv_torch::TorchStrategy;
40use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
41use uv_warnings::warn_user_once;
42
43use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
44use crate::dependency_provider::UvDependencyProvider;
45use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
46use crate::fork_indexes::ForkIndexes;
47use crate::fork_urls::ForkUrls;
48use crate::manifest::Manifest;
49use crate::pins::FilePins;
50use crate::preferences::{PreferenceSource, Preferences};
51use crate::prioritized_distribution::{
52 CompatibleDist, IncompatibleDist, IncompatibleSource, IncompatibleWheel, PythonRequirementKind,
53 implied_markers,
54};
55use crate::pubgrub::{
56 DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
57 PubGrubPython, Range,
58};
59use crate::python_requirement::PythonRequirement;
60use crate::resolution::ResolverOutput;
61use crate::resolution_mode::ResolutionStrategy;
62use crate::resolved::ResolvedDistRef;
63pub(crate) use crate::resolver::availability::{
64 ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
65 UnavailableVersion, UnsatisfiableRequirement,
66};
67use crate::resolver::batch_prefetch::BatchPrefetcher;
68use crate::resolver::derivation::DerivationChainBuilder;
69pub use crate::resolver::environment::ResolverEnvironment;
70use crate::resolver::environment::{
71 ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
72};
73pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
74pub use crate::resolver::index::InMemoryIndex;
75use crate::resolver::indexes::Indexes;
76use crate::resolver::package_source::PackageSource;
77pub use crate::resolver::provider::{
78 DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
79 VersionsResponse, WheelMetadataResult,
80};
81pub use crate::resolver::reporter::Reporter;
82pub(crate) use crate::resolver::requests::RegisteredMetadata;
83use crate::resolver::requests::{MetadataRequest, MetadataRequests};
84use crate::resolver::requirements::{RequirementContext, RequirementExpander};
85use crate::resolver::system::SystemDependency;
86pub(crate) use crate::resolver::urls::Urls;
87use crate::universal_marker::UniversalMarker;
88use crate::yanks::AllowedYanks;
89use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
90pub(crate) use provider::MetadataUnavailable;
91pub(crate) use resolution::{
92 Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionPackage, ResolvedFork,
93 SelectedDistribution,
94};
95use uv_configuration::ForkStrategy;
96
97mod availability;
98mod batch_prefetch;
99mod derivation;
100mod environment;
101mod fork_map;
102mod index;
103mod indexes;
104mod package_source;
105mod provider;
106mod reporter;
107mod requests;
108mod requirements;
109mod resolution;
110mod system;
111mod urls;
112
113const CONFLICT_THRESHOLD: usize = 5;
115
116pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
117 state: ResolverState<InstalledPackages>,
118 provider: Provider,
119}
120
121struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
124 recorder: Option<ResolutionRecorder>,
125 project: Option<PackageName>,
126 requirements: Vec<Requirement>,
127 constraints: Constraints,
128 modifiers: DependencyModifiers,
129 preferences: Preferences,
130 git: GitResolver,
131 capabilities: IndexCapabilities,
132 locations: IndexLocations,
133 exclusions: Exclusions,
134 urls: Urls,
135 indexes: Indexes,
136 dependency_mode: DependencyMode,
137 hasher: HashStrategy,
138 env: ResolverEnvironment,
139 current_environment: MarkerEnvironment,
141 tags: Option<Tags>,
142 python_requirement: PythonRequirement,
143 conflicts: Conflicts,
144 workspace_members: BTreeSet<PackageName>,
145 selector: CandidateSelector,
146 index: InMemoryIndex,
147 installed_packages: InstalledPackages,
148 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
151 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
153 options: Options,
155 reporter: Option<Arc<dyn Reporter>>,
157}
158
159impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
160 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
161{
162 pub fn new(
181 manifest: Manifest,
182 options: Options,
183 python_requirement: &'a PythonRequirement,
184 env: ResolverEnvironment,
185 current_environment: &MarkerEnvironment,
186 conflicts: Conflicts,
187 tags: Option<&'a Tags>,
188 flat_index: &'a FlatIndex,
189 index: &'a InMemoryIndex,
190 hasher: &'a HashStrategy,
191 build_context: &'a Context,
192 installed_packages: InstalledPackages,
193 database: DistributionDatabase<'a, Context>,
194 ) -> Result<Self, ResolveError> {
195 let provider = DefaultResolverProvider::new(
196 database,
197 flat_index,
198 tags,
199 python_requirement.target(),
200 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
201 hasher,
202 options.exclude_newer.clone(),
203 build_context.locations(),
204 build_context.build_options(),
205 build_context.capabilities(),
206 options.minimum_libc_version,
207 );
208
209 Ok(Self::new_custom_io(
210 manifest,
211 options,
212 hasher,
213 env,
214 current_environment,
215 tags.cloned(),
216 python_requirement,
217 conflicts,
218 index,
219 build_context.git(),
220 build_context.capabilities(),
221 build_context.locations(),
222 provider,
223 installed_packages,
224 ))
225 }
226}
227
228impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
229 Resolver<Provider, InstalledPackages>
230{
231 pub fn new_custom_io(
233 manifest: Manifest,
234 options: Options,
235 hasher: &HashStrategy,
236 env: ResolverEnvironment,
237 current_environment: &MarkerEnvironment,
238 tags: Option<Tags>,
239 python_requirement: &PythonRequirement,
240 conflicts: Conflicts,
241 index: &InMemoryIndex,
242 git: &GitResolver,
243 capabilities: &IndexCapabilities,
244 locations: &IndexLocations,
245 provider: Provider,
246 installed_packages: InstalledPackages,
247 ) -> Self {
248 let state = ResolverState {
249 index: index.clone(),
250 git: git.clone(),
251 capabilities: capabilities.clone(),
252 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
253 dependency_mode: options.dependency_mode,
254 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
255 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
256 recorder: manifest.recorder.clone(),
257 project: manifest.project,
258 workspace_members: manifest.workspace_members.into_keys().collect(),
259 requirements: manifest.requirements,
260 constraints: manifest
261 .constraints
262 .with_recorder(manifest.recorder.clone()),
263 modifiers: manifest.modifiers.with_recorder(manifest.recorder.clone()),
264 preferences: manifest.preferences,
265 exclusions: manifest.exclusions,
266 hasher: hasher.clone(),
267 locations: locations.clone(),
268 env,
269 current_environment: current_environment.clone(),
270 tags,
271 python_requirement: python_requirement.clone(),
272 conflicts,
273 installed_packages,
274 unavailable_packages: Box::default(),
275 incomplete_packages: Box::default(),
276 options,
277 reporter: None,
278 };
279 Self { state, provider }
280 }
281
282 #[must_use]
284 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
285 Self {
286 state: ResolverState {
287 reporter: Some(reporter.clone()),
288 ..self.state
289 },
290 provider: self
291 .provider
292 .with_reporter(reporter.into_distribution_reporter()),
293 }
294 }
295
296 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
298 let state = Arc::new(self.state);
299 let provider = Arc::new(self.provider);
300
301 let (request_sink, request_stream) = mpsc::channel(300);
305 let requests =
306 MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
307
308 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
310
311 let solver = state.clone();
313 let (tx, rx) = oneshot::channel();
314 thread::Builder::new()
315 .name("uv-resolver".into())
316 .spawn(move || {
317 let result = solver.solve(&requests);
318
319 let _ = tx.send(result);
321 })
322 .unwrap();
323
324 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
325
326 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
328
329 state.on_complete();
330 resolution
331 }
332}
333
334impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
335 #[instrument(skip_all)]
336 fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
337 debug!(
338 "Solving with installed Python version: {}",
339 self.python_requirement.exact()
340 );
341 debug!(
342 "Solving with target Python version: {}",
343 self.python_requirement.target()
344 );
345 if !self.options.exclude_newer.is_empty() {
346 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
347 }
348
349 let mut visited = FxHashSet::default();
350
351 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
352 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
353 let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
354 let state = ForkState::new(
355 pubgrub,
356 self.env.clone(),
357 self.python_requirement.clone(),
358 prefetcher,
359 );
360 let mut preferences = self.preferences.clone();
361 let mut forked_states = self.env.initial_forked_states(state)?;
362
363 match (self.options.fork_strategy, self.options.resolution_mode) {
369 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
370 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
371 }
372 (ForkStrategy::RequiresPython, _) => {
373 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
374 }
375 }
376 let mut resolutions = vec![];
377
378 'FORK: while let Some(mut state) = forked_states.pop() {
379 if let Some(split) = state.env.end_user_fork_display() {
380 let requires_python = state.python_requirement.target();
381 debug!("Solving {split} (requires-python: {requires_python:?})");
382 }
383 let start = Instant::now();
384 loop {
385 let continuation = mem::take(&mut state.continuation);
386 let (highest_priority_pkg, initial_version) = match continuation {
387 ForkContinuation::SelectVersion { package } => (package, None),
388 ForkContinuation::UseVersion { package, version } => (package, Some(version)),
389 ForkContinuation::Propagate => {
390 let result = state.pubgrub.unit_propagation(state.next);
392 match result {
393 Err(err) => {
394 return Err(self.convert_no_solution_err(
396 err,
397 state.fork_urls,
398 state.fork_indexes,
399 &state.known_versions.0,
400 state.env,
401 self.current_environment.clone(),
402 &visited,
403 ));
404 }
405 Ok(conflicts) => {
406 for (affected, incompatibility) in conflicts {
407 state.record_conflict(affected, None, incompatibility);
410 }
411 }
412 }
413
414 if self.dependency_mode.is_transitive() {
416 Self::pre_visit(
417 state.pubgrub.partial_solution.prioritized_packages().map(
418 |(id, range)| (id, &state.pubgrub.package_store[id], range),
419 ),
420 &mut state.pre_visited,
421 &self.urls,
422 &self.indexes,
423 &state.python_requirement,
424 requests,
425 )?;
426 }
427
428 state.reprioritize_conflicts();
429
430 trace!(
431 "Assigned packages: {}",
432 state
433 .pubgrub
434 .partial_solution
435 .extract_solution()
436 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
437 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
438 .join(", ")
439 );
440 let Some((highest_priority_pkg, _)) =
444 state.pubgrub.partial_solution.pick_highest_priority_pkg(
445 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
446 )
447 else {
448 if tracing::enabled!(Level::DEBUG) {
450 state.prefetcher.log_tried_versions();
451 }
452 debug!(
453 "{} resolution took {:.3}s",
454 state.env,
455 start.elapsed().as_secs_f32()
456 );
457
458 let resolution = state.into_resolution();
459
460 if matches!(
469 self.options.resolution_mode,
470 ResolutionMode::Lowest | ResolutionMode::Highest
471 ) {
472 let marker = resolution
473 .env
474 .try_universal_markers()
475 .unwrap_or(UniversalMarker::TRUE);
476 for (package, version) in &resolution.nodes {
477 preferences.insert(
478 package.name.clone(),
479 package.index.clone(),
480 marker,
481 version.clone(),
482 PreferenceSource::Resolver,
483 );
484 }
485 }
486
487 resolutions.push(resolution);
488 continue 'FORK;
489 };
490 trace!(
491 "Chose package for decision: {}. remaining choices: {}",
492 state.pubgrub.package_store[highest_priority_pkg],
493 state
494 .pubgrub
495 .partial_solution
496 .undecided_packages()
497 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
498 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
499 .join(", ")
500 );
501
502 (highest_priority_pkg, None)
503 }
504 };
505
506 state.next = highest_priority_pkg;
507
508 let next_id = state.next;
510 let next_package = &state.pubgrub.package_store[state.next];
511
512 let source =
513 PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
514
515 self.request_package(next_package, source, requests)?;
527
528 let version = if let Some(version) = initial_version {
529 version
530 } else {
531 let term_intersection = state
532 .pubgrub
533 .partial_solution
534 .term_intersection_for_package(next_id)
535 .expect("a package was chosen but we don't have a term");
536 let range = term_intersection.unwrap_positive();
537
538 let cache_selected_version = match source {
542 PackageSource::Registry(None) => true,
543 PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
544 };
545 let decision = if cache_selected_version
546 && let Some((selected_range, version)) =
547 state.selected_versions.get(&next_id)
548 && selected_range == range
549 {
550 Some(ResolverVersion::Unforked(version.clone()))
551 } else {
552 let decision = self.choose_version(
553 next_package,
554 next_id,
555 source,
556 range,
557 &mut state.pins,
558 &preferences,
559 &state.env,
560 &state.python_requirement,
561 &state.pubgrub,
562 &mut visited,
563 requests,
564 )?;
565
566 if cache_selected_version
567 && let Some(ResolverVersion::Unforked(version)) = &decision
568 {
569 state
570 .selected_versions
571 .insert(next_id, (range.clone(), version.clone()));
572 }
573
574 decision
575 };
576
577 let Some(version) = decision else {
579 debug!("No compatible version found for: {next_package}");
580
581 let term_intersection = state
582 .pubgrub
583 .partial_solution
584 .term_intersection_for_package(next_id)
585 .expect("a package was chosen but we don't have a term");
586
587 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
588 if let Some(reason) = self.unavailable_packages.pin().get(name) {
590 state
591 .pubgrub
592 .add_incompatibility(Incompatibility::custom_term(
593 next_id,
594 term_intersection.clone(),
595 UnavailableReason::Package(reason.clone()),
596 ));
597 continue;
598 }
599 }
600
601 state
602 .pubgrub
603 .add_incompatibility(Incompatibility::no_versions(
604 next_id,
605 term_intersection.clone(),
606 ));
607 continue;
608 };
609
610 let version = match version {
611 ResolverVersion::Unforked(version) => version,
612 ResolverVersion::Forked(forks) => {
613 forked_states.extend(self.version_forks_to_fork_states(state, forks));
614 continue 'FORK;
615 }
616 ResolverVersion::Unavailable(version, reason) => {
617 state.add_unavailable_version(
618 version,
619 reason,
620 &self.index,
621 &self.installed_packages,
622 );
623 continue;
624 }
625 };
626
627 if let PackageSource::Registry(index) = source {
629 state.prefetcher.prefetch_batches(
630 next_package,
631 index,
632 &version,
633 term_intersection.unwrap_positive(),
634 state
635 .pubgrub
636 .partial_solution
637 .unchanging_term_for_package(next_id),
638 &state.python_requirement,
639 &self.selector,
640 &state.env,
641 )?;
642 }
643
644 version
645 };
646
647 state.prefetcher.version_tried(next_package, &version);
648
649 self.on_progress(next_package, &version);
650
651 if state
652 .added_dependencies
653 .get(&next_id)
654 .is_some_and(|versions| versions.contains(&version))
655 {
656 state
659 .pubgrub
660 .partial_solution
661 .add_decision(next_id, version);
662 continue;
663 }
664
665 let forked_deps = self.get_dependencies_forking(
667 next_id,
668 next_package,
669 &version,
670 &state.pins,
671 &state.env,
672 &state.python_requirement,
673 &state.pubgrub,
674 )?;
675
676 match forked_deps {
677 ForkedDependencies::Unavailable(reason) => {
678 let versions = state.widen_version_to_gap(
681 &version,
682 &self.index,
683 &self.installed_packages,
684 );
685 state
686 .pubgrub
687 .add_incompatibility(Incompatibility::custom_term(
688 next_id,
689 Term::Positive(versions),
690 UnavailableReason::Version(reason),
691 ));
692 }
693 ForkedDependencies::Unforked(dependencies) => {
694 state
695 .added_dependencies
696 .entry(next_id)
697 .or_default()
698 .insert(version.clone());
699
700 state
702 .visit_package_version_dependencies(
703 next_id,
704 &version,
705 &self.urls,
706 &self.indexes,
707 &dependencies,
708 &self.git,
709 &self.workspace_members,
710 self.selector.resolution_strategy(),
711 )
712 .map_err(|err| {
713 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
714 })?;
715
716 self.visit_dependencies(&dependencies, &state, requests)
718 .map_err(|err| {
719 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
720 })?;
721
722 state.add_package_version_dependencies(
724 next_id,
725 &version,
726 dependencies,
727 &self.index,
728 &self.installed_packages,
729 );
730 }
731 ForkedDependencies::Forked {
732 mut forks,
733 diverging_packages,
734 } => {
735 state
736 .added_dependencies
737 .entry(next_id)
738 .or_default()
739 .insert(version.clone());
740
741 debug!(
742 "Pre-fork {} took {:.3}s",
743 state.env,
744 start.elapsed().as_secs_f32()
745 );
746
747 match (self.options.fork_strategy, self.options.resolution_mode) {
749 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
750 forks.sort_by(|a, b| {
754 a.cmp_requires_python(b)
755 .reverse()
756 .then_with(|| a.cmp_upper_bounds(b))
757 });
758 }
759 (ForkStrategy::RequiresPython, _) => {
760 forks.sort_by(|a, b| {
764 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
765 });
766 }
767 }
768
769 for new_fork_state in self.forks_to_fork_states(
770 state,
771 &version,
772 forks,
773 requests,
774 &diverging_packages,
775 ) {
776 forked_states.push(new_fork_state?);
777 }
778 continue 'FORK;
779 }
780 ForkedDependencies::RequiresPython(requires_python) => {
781 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
782 && state.env.marker_environment().is_none()
783 {
784 let forks = fork_version_by_python_requirement(
785 &requires_python,
786 &state.python_requirement,
787 &state.env,
788 );
789 if !forks.is_empty() {
790 debug!(
791 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
792 state.python_requirement.target(),
793 &requires_python,
794 next_package,
795 version,
796 forks
797 .iter()
798 .map(ToString::to_string)
799 .collect::<Vec<_>>()
800 .join(", ")
801 );
802
803 let forks = forks
806 .into_iter()
807 .map(|env| VersionFork {
808 env,
809 id: next_id,
810 version: None,
811 })
812 .collect();
813 forked_states
814 .extend(self.version_forks_to_fork_states(state, forks));
815 continue 'FORK;
816 }
817 }
818
819 let versions = state.widen_version_to_gap(
820 &version,
821 &self.index,
822 &self.installed_packages,
823 );
824 state
825 .pubgrub
826 .add_incompatibility(Incompatibility::custom_term(
827 next_id,
828 Term::Positive(versions),
829 UnavailableReason::Version(UnavailableVersion::RequiresPython(
830 requires_python,
831 )),
832 ));
833 }
834 }
835 }
836 }
837 if resolutions.len() > 1 {
838 info!(
839 "Solved your requirements for {} environments",
840 resolutions.len()
841 );
842 }
843 if tracing::enabled!(Level::DEBUG) {
844 for resolution in &resolutions {
845 if let Some(env) = resolution.env.end_user_fork_display() {
846 let packages: FxHashSet<_> = resolution
847 .nodes
848 .keys()
849 .map(|package| &package.name)
850 .collect();
851 debug!(
852 "Distinct solution for {env} with {} package(s)",
853 packages.len()
854 );
855 }
856 }
857 }
858 for resolution in &resolutions {
859 resolution.trace_resolution();
860 }
861 let resolutions = resolutions
862 .into_iter()
863 .map(|resolution| resolution.finalize(&self.index, &self.git))
864 .collect::<Result<Vec<_>, _>>()?;
865 crate::resolution::from_state(
866 resolutions,
867 self.project.as_ref(),
868 &self.workspace_members,
869 self.requirements.clone(),
870 self.constraints.clone(),
871 self.modifiers.clone(),
872 &self.preferences,
873 &self.hasher,
874 &self.index,
875 self.python_requirement.target().clone(),
876 &self.conflicts,
877 self.selector.resolution_strategy(),
878 self.options.clone(),
879 )
880 }
881
882 fn forks_to_fork_states<'a, 'index: 'a>(
884 &'a self,
885 current_state: ForkState<'index>,
886 version: &'a Version,
887 forks: Vec<Fork>,
888 requests: &'a MetadataRequests,
889 diverging_packages: &'a BTreeSet<PackageName>,
890 ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
891 debug!(
892 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
893 current_state.pubgrub.package_store[current_state.next],
894 version,
895 diverging_packages
896 .iter()
897 .map(ToString::to_string)
898 .join(", "),
899 forks.len(),
900 if forks.len() == 1 { "" } else { "s" }
901 );
902 assert!(forks.len() >= 2);
903 let package = current_state.next;
909 let mut cur_state = Some(current_state);
910 let forks_len = forks.len();
911 forks
912 .into_iter()
913 .enumerate()
914 .map(move |(i, fork)| {
915 let is_last = i == forks_len - 1;
916 let forked_state = cur_state.take().unwrap();
917 if !is_last {
918 cur_state = Some(forked_state.clone());
919 }
920
921 let env = fork.env.clone();
922 (fork, forked_state.with_env(env))
923 })
924 .map(move |(fork, mut forked_state)| {
925 forked_state
927 .visit_package_version_dependencies(
928 package,
929 version,
930 &self.urls,
931 &self.indexes,
932 &fork.dependencies,
933 &self.git,
934 &self.workspace_members,
935 self.selector.resolution_strategy(),
936 )
937 .map_err(|err| {
938 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
939 })?;
940
941 self.visit_dependencies(&fork.dependencies, &forked_state, requests)
943 .map_err(|err| {
944 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
945 })?;
946
947 forked_state.add_package_version_dependencies(
949 package,
950 version,
951 fork.dependencies,
952 &self.index,
953 &self.installed_packages,
954 );
955
956 Ok(forked_state)
957 })
958 }
959
960 #[expect(clippy::unused_self)]
962 fn version_forks_to_fork_states<'index>(
963 &self,
964 current_state: ForkState<'index>,
965 forks: Vec<VersionFork>,
966 ) -> impl Iterator<Item = ForkState<'index>> {
967 let mut cur_state = Some(current_state);
973 let forks_len = forks.len();
974 forks.into_iter().enumerate().map(move |(i, fork)| {
975 let is_last = i == forks_len - 1;
976 let forked_state = cur_state.take().unwrap();
977 if !is_last {
978 cur_state = Some(forked_state.clone());
979 }
980 let continuation = match fork.version {
981 Some(version) => ForkContinuation::UseVersion {
982 package: fork.id,
983 version,
984 },
985 None => ForkContinuation::SelectVersion { package: fork.id },
986 };
987 forked_state
988 .with_env(fork.env)
989 .with_continuation(continuation)
990 })
991 }
992
993 fn visit_dependencies(
995 &self,
996 dependencies: &[PubGrubDependency],
997 state: &ForkState,
998 requests: &MetadataRequests,
999 ) -> Result<(), ResolveError> {
1000 for dependency in dependencies {
1001 let PubGrubDependency {
1002 package,
1003 version: _,
1004 parent: _,
1005 source: _,
1006 } = dependency;
1007 let Some(source) = PackageSource::for_prefetch(
1008 package,
1009 &state.fork_urls,
1010 &state.fork_indexes,
1011 &self.urls,
1012 ) else {
1013 continue;
1014 };
1015 self.request_package(package, source, requests)?;
1016 }
1017 Ok(())
1018 }
1019
1020 fn request_package(
1021 &self,
1022 package: &PubGrubPackage,
1023 source: PackageSource<'_>,
1024 requests: &MetadataRequests,
1025 ) -> Result<(), ResolveError> {
1026 let Some(name) = package.name_no_root() else {
1028 return Ok(());
1029 };
1030
1031 match source {
1032 PackageSource::Url(url) => {
1033 if !self.hasher.allows_url(&url.verbatim) {
1035 return Err(ResolveError::UnhashedPackage(name.clone()));
1036 }
1037
1038 let dist = Dist::from_url(name.clone(), url.clone())?;
1040 requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1041 }
1042 PackageSource::Registry(index) => {
1043 requests.enqueue_package(name, index)?;
1044 }
1045 }
1046 Ok(())
1047 }
1048
1049 fn pre_visit<'data>(
1052 packages: impl Iterator<
1053 Item = (
1054 Id<PubGrubPackage>,
1055 &'data PubGrubPackage,
1056 &'data Range<Version>,
1057 ),
1058 >,
1059 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1060 urls: &Urls,
1061 indexes: &Indexes,
1062 python_requirement: &PythonRequirement,
1063 requests: &MetadataRequests,
1064 ) -> Result<(), ResolveError> {
1065 for (id, package, range) in packages {
1068 let PubGrubPackageInner::Package {
1069 name,
1070 kind: PackageNodeKind::Base,
1071 marker: MarkerTree::TRUE,
1072 } = &**package
1073 else {
1074 continue;
1075 };
1076 if urls.any_url(name) {
1079 continue;
1080 }
1081 if indexes.contains_key(name) {
1083 continue;
1084 }
1085 if pre_visited.get(&id) == Some(range) {
1088 continue;
1089 }
1090 pre_visited.insert(id, range.clone());
1091 requests.prefetch(name, range, python_requirement)?;
1092 }
1093 Ok(())
1094 }
1095
1096 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1104 fn choose_version<'index>(
1105 &self,
1106 package: &PubGrubPackage,
1107 id: Id<PubGrubPackage>,
1108 source: PackageSource<'_>,
1109 range: &Range<Version>,
1110 pins: &mut FilePins<'index>,
1111 preferences: &Preferences,
1112 env: &ResolverEnvironment,
1113 python_requirement: &PythonRequirement,
1114 pubgrub: &State<UvDependencyProvider>,
1115 visited: &mut FxHashSet<PackageName>,
1116 requests: &'index MetadataRequests,
1117 ) -> Result<Option<ResolverVersion>, ResolveError> {
1118 match &**package {
1119 PubGrubPackageInner::Root(_) => {
1120 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1121 }
1122
1123 PubGrubPackageInner::Python(_) => {
1124 Ok(None)
1127 }
1128
1129 PubGrubPackageInner::System(_) => {
1130 let Some(version) = range.as_singleton() else {
1133 return Ok(None);
1134 };
1135 Ok(Some(ResolverVersion::Unforked(version.clone())))
1136 }
1137
1138 PubGrubPackageInner::Marker { name, .. }
1139 | PubGrubPackageInner::Extra { name, .. }
1140 | PubGrubPackageInner::Group { name, .. }
1141 | PubGrubPackageInner::Package { name, .. } => match source {
1142 PackageSource::Url(url) => self.choose_version_url(
1143 id,
1144 name,
1145 range,
1146 url,
1147 env,
1148 python_requirement,
1149 pubgrub,
1150 pins,
1151 requests,
1152 ),
1153 PackageSource::Registry(index) => self.choose_version_registry(
1154 package,
1155 id,
1156 name,
1157 index,
1158 range,
1159 preferences,
1160 env,
1161 python_requirement,
1162 pubgrub,
1163 pins,
1164 visited,
1165 requests,
1166 ),
1167 },
1168 }
1169 }
1170
1171 fn choose_version_url<'index>(
1174 &self,
1175 id: Id<PubGrubPackage>,
1176 name: &PackageName,
1177 range: &Range<Version>,
1178 url: &VerbatimParsedUrl,
1179 env: &ResolverEnvironment,
1180 python_requirement: &PythonRequirement,
1181 pubgrub: &State<UvDependencyProvider>,
1182 pins: &mut FilePins<'index>,
1183 requests: &'index MetadataRequests,
1184 ) -> Result<Option<ResolverVersion>, ResolveError> {
1185 debug!(
1186 "Searching for a compatible version of {name} @ `{}` ({range})",
1187 url.verbatim
1188 );
1189
1190 let dist = Dist::from_url(name.clone(), url.clone())?;
1191 let registered = requests.metadata(&dist)?;
1192 let response = registered.wait();
1193
1194 let metadata = match &*response {
1196 MetadataResponse::Found(archive) => &archive.metadata,
1197 MetadataResponse::Unavailable(reason) => {
1198 self.unavailable_packages
1199 .pin()
1200 .insert(name.clone(), reason.into());
1201 return Ok(None);
1202 }
1203 MetadataResponse::Error(dist, err) => {
1206 return Err(ResolveError::Dist(
1207 DistErrorKind::from_requested_dist(dist, &**err),
1208 dist.clone(),
1209 DerivationChain::default(),
1210 err.clone(),
1211 ));
1212 }
1213 };
1214
1215 let version = &metadata.version;
1216
1217 if !range.contains(version) {
1219 return Ok(None);
1220 }
1221
1222 if let Dist::Built(dist) = &dist {
1225 let filename = match &dist {
1226 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1227 BuiltDist::DirectUrl(dist) => &dist.filename,
1228 BuiltDist::GitPath(dist) => &dist.filename,
1229 BuiltDist::Path(dist) => &dist.filename,
1230 };
1231
1232 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1234 {
1235 let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1236 for environment_marker in self.options.artifact_environments.iter().copied() {
1239 if env.included_by_marker(environment_marker)
1241 && env.included_by_marker(
1242 find_environments(id, pubgrub).and(environment_marker),
1243 )
1244 {
1245 if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1247 return Ok(Some(ResolverVersion::Unavailable(
1248 version.clone(),
1249 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1250 IncompatibleWheel::MissingPlatform(environment_marker),
1251 )),
1252 )));
1253 }
1254 }
1255 }
1256 }
1257
1258 if !python_requirement.target().matches_wheel_tag(filename) {
1260 return Ok(Some(ResolverVersion::Unavailable(
1261 filename.version.clone(),
1262 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1263 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1264 )),
1265 )));
1266 }
1267 }
1268
1269 if let Some(requires_python) = metadata.requires_python.as_ref() {
1271 if !python_requirement.target().is_contained_by(requires_python) {
1272 let kind = if python_requirement.installed() == python_requirement.target() {
1273 PythonRequirementKind::Installed
1274 } else {
1275 PythonRequirementKind::Target
1276 };
1277 return Ok(Some(ResolverVersion::Unavailable(
1278 version.clone(),
1279 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1280 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1281 )),
1282 )));
1283 }
1284 }
1285
1286 pins.insert_url(name, version, registered);
1287 Ok(Some(ResolverVersion::Unforked(version.clone())))
1288 }
1289
1290 fn choose_version_registry<'index>(
1293 &self,
1294 package: &PubGrubPackage,
1295 id: Id<PubGrubPackage>,
1296 name: &PackageName,
1297 index: Option<&IndexMetadata>,
1298 range: &Range<Version>,
1299 preferences: &Preferences,
1300 env: &ResolverEnvironment,
1301 python_requirement: &PythonRequirement,
1302 pubgrub: &State<UvDependencyProvider>,
1303 pins: &mut FilePins<'index>,
1304 visited: &mut FxHashSet<PackageName>,
1305 requests: &'index MetadataRequests,
1306 ) -> Result<Option<ResolverVersion>, ResolveError> {
1307 let versions_response = requests.request_package(name, index)?.wait();
1309 let index = index.map(IndexMetadata::url);
1310 visited.insert(name.clone());
1311
1312 let version_maps = match *versions_response {
1313 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1314 VersionsResponse::NoIndex => {
1315 self.unavailable_packages
1316 .pin()
1317 .insert(name.clone(), UnavailablePackage::NoIndex);
1318 &[]
1319 }
1320 VersionsResponse::Offline => {
1321 self.unavailable_packages
1322 .pin()
1323 .insert(name.clone(), UnavailablePackage::Offline);
1324 &[]
1325 }
1326 VersionsResponse::NotFound => {
1327 self.unavailable_packages
1328 .pin()
1329 .insert(name.clone(), UnavailablePackage::NotFound);
1330 &[]
1331 }
1332 };
1333
1334 debug!("Searching for a compatible version of {package} ({range})");
1335
1336 let Some(candidate) = self.selector.select(
1338 name,
1339 range,
1340 version_maps,
1341 preferences,
1342 &self.installed_packages,
1343 &self.exclusions,
1344 index,
1345 env,
1346 self.tags.as_ref(),
1347 ) else {
1348 return Ok(None);
1350 };
1351
1352 let dist = match candidate.dist() {
1353 CandidateDist::Compatible(dist) => dist,
1354 CandidateDist::Incompatible {
1355 incompatible_dist: incompatibility,
1356 prioritized_dist: _,
1357 } => {
1358 return Ok(Some(ResolverVersion::Unavailable(
1360 candidate.version().clone(),
1361 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1364 )));
1365 }
1366 };
1367
1368 if let Some((requires_python, incompatibility)) =
1370 Self::check_requires_python(dist, python_requirement)
1371 {
1372 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1373 if env.marker_environment().is_none() {
1374 let forks = fork_version_by_python_requirement(
1375 requires_python,
1376 python_requirement,
1377 env,
1378 );
1379 if !forks.is_empty() {
1380 debug!(
1381 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1382 python_requirement.target(),
1383 requires_python,
1384 name,
1385 candidate.version(),
1386 forks
1387 .iter()
1388 .map(ToString::to_string)
1389 .collect::<Vec<_>>()
1390 .join(", ")
1391 );
1392 let forks = forks
1393 .into_iter()
1394 .map(|env| VersionFork {
1395 env,
1396 id,
1397 version: None,
1398 })
1399 .collect();
1400 return Ok(Some(ResolverVersion::Forked(forks)));
1401 }
1402 }
1403 }
1404
1405 return Ok(Some(ResolverVersion::Unavailable(
1406 candidate.version().clone(),
1407 UnavailableVersion::IncompatibleDist(incompatibility),
1408 )));
1409 }
1410
1411 if env.marker_environment().is_none()
1415 && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1416 && !dist
1417 .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1418 {
1419 return Ok(Some(ResolverVersion::Unavailable(
1420 candidate.version().clone(),
1421 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1422 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1423 )),
1424 )));
1425 }
1426
1427 if let Some(forked) = self.fork_version_registry(
1429 &candidate,
1430 dist,
1431 version_maps,
1432 package,
1433 id,
1434 name,
1435 index,
1436 range,
1437 preferences,
1438 env,
1439 pubgrub,
1440 pins,
1441 requests,
1442 )? {
1443 return Ok(Some(forked));
1444 }
1445
1446 let filename = match dist.for_installation() {
1447 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1448 .filename()
1449 .unwrap_or(Cow::Borrowed("unknown filename")),
1450 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1451 .filename()
1452 .unwrap_or(Cow::Borrowed("unknown filename")),
1453 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1454 };
1455
1456 debug!(
1457 "Selecting: {}=={} [{}] ({})",
1458 name,
1459 candidate.version(),
1460 candidate.choice_kind(),
1461 filename,
1462 );
1463 self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1464
1465 let version = candidate.version().clone();
1466 Ok(Some(ResolverVersion::Unforked(version)))
1467 }
1468
1469 fn fork_version_registry<'index>(
1482 &self,
1483 candidate: &Candidate,
1484 dist: &CompatibleDist,
1485 version_maps: &[VersionMap],
1486 package: &PubGrubPackage,
1487 id: Id<PubGrubPackage>,
1488 name: &PackageName,
1489 index: Option<&IndexUrl>,
1490 range: &Range<Version>,
1491 preferences: &Preferences,
1492 env: &ResolverEnvironment,
1493 pubgrub: &State<UvDependencyProvider>,
1494 pins: &mut FilePins<'index>,
1495 requests: &'index MetadataRequests,
1496 ) -> Result<Option<ResolverVersion>, ResolveError> {
1497 let Some(fork_markers) = env.fork_markers() else {
1499 return Ok(None);
1500 };
1501
1502 let artifact_markers = dist.implied_markers();
1503 if artifact_markers.is_true() {
1504 return Ok(None);
1505 }
1506
1507 for marker in self.options.artifact_environments.iter().copied() {
1510 if env.included_by_marker(marker) {
1512 if !env.included_by_marker(artifact_markers.and(marker))
1514 && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1515 {
1516 let coverage = dist.implied_markers().and(fork_markers);
1521 let split = marker.restrict(marker.or(coverage));
1522 let Some((left, right)) = fork_version_by_marker(env, split) else {
1523 return Ok(Some(ResolverVersion::Unavailable(
1524 candidate.version().clone(),
1525 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1526 IncompatibleWheel::MissingPlatform(marker),
1527 )),
1528 )));
1529 };
1530
1531 debug!(
1532 "Forking on required platform `{}` for {}=={} ({})",
1533 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1534 name,
1535 candidate.version(),
1536 [&left, &right]
1537 .iter()
1538 .map(ToString::to_string)
1539 .collect::<Vec<_>>()
1540 .join(", ")
1541 );
1542 let forks = vec![
1543 VersionFork {
1544 env: left,
1545 id,
1546 version: None,
1547 },
1548 VersionFork {
1549 env: right,
1550 id,
1551 version: None,
1552 },
1553 ];
1554 return Ok(Some(ResolverVersion::Forked(forks)));
1555 }
1556 }
1557 }
1558
1559 if !candidate.version().is_local() {
1561 return Ok(None);
1562 }
1563
1564 debug!(
1565 "Looking at local version: {}=={}",
1566 name,
1567 candidate.version()
1568 );
1569
1570 let range = range.clone().intersection(&Range::singleton(
1572 candidate.version().clone().without_local(),
1573 ));
1574
1575 let Some(base_candidate) = self.selector.select(
1576 name,
1577 &range,
1578 version_maps,
1579 preferences,
1580 &self.installed_packages,
1581 &self.exclusions,
1582 index,
1583 env,
1584 self.tags.as_ref(),
1585 ) else {
1586 return Ok(None);
1587 };
1588 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1589 return Ok(None);
1590 };
1591
1592 let mut remainder = {
1594 let mut remainder = base_dist.implied_markers();
1595 remainder = remainder.and(artifact_markers.negate());
1596 remainder
1597 };
1598 if remainder.is_false() {
1599 return Ok(None);
1600 }
1601
1602 if !env.included_by_marker(remainder) {
1606 return Ok(None);
1607 }
1608
1609 if !env.included_by_marker(artifact_markers) {
1612 let filename = match dist.for_installation() {
1613 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1614 .filename()
1615 .unwrap_or(Cow::Borrowed("unknown filename")),
1616 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1617 .filename()
1618 .unwrap_or(Cow::Borrowed("unknown filename")),
1619 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1620 };
1621
1622 debug!(
1623 "Preferring non-local candidate: {}=={} [{}] ({})",
1624 name,
1625 base_candidate.version(),
1626 base_candidate.choice_kind(),
1627 filename,
1628 );
1629 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1630
1631 return Ok(Some(ResolverVersion::Unforked(
1632 base_candidate.version().clone(),
1633 )));
1634 }
1635
1636 for value in [
1645 arcstr::literal!("darwin"),
1646 arcstr::literal!("linux"),
1647 arcstr::literal!("win32"),
1648 ] {
1649 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1650 key: MarkerValueString::SysPlatform,
1651 operator: MarkerOperator::Equal,
1652 value,
1653 });
1654 if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1655 remainder = remainder.or(sys_platform);
1656 }
1657 }
1658
1659 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1661 return Ok(None);
1662 };
1663
1664 debug!(
1665 "Forking platform for {}=={} ({})",
1666 name,
1667 candidate.version(),
1668 [&base_env, &local_env]
1669 .iter()
1670 .map(ToString::to_string)
1671 .collect::<Vec<_>>()
1672 .join(", ")
1673 );
1674 self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1675 self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1676
1677 let forks = vec![
1678 VersionFork {
1679 env: base_env.clone(),
1680 id,
1681 version: Some(base_candidate.version().clone()),
1682 },
1683 VersionFork {
1684 env: local_env.clone(),
1685 id,
1686 version: Some(candidate.version().clone()),
1687 },
1688 ];
1689 Ok(Some(ResolverVersion::Forked(forks)))
1690 }
1691
1692 fn visit_candidate<'index>(
1694 &self,
1695 candidate: &Candidate,
1696 dist: &CompatibleDist,
1697 package: &PubGrubPackage,
1698 name: &PackageName,
1699 pins: &mut FilePins<'index>,
1700 requests: &'index MetadataRequests,
1701 ) -> Result<(), ResolveError> {
1702 let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1703 && self.dependency_mode.is_transitive()
1704 {
1705 Some(|| {
1706 requests.request_metadata(
1707 MetadataRequest::Resolved(dist.for_resolution()),
1708 |request| {
1709 if name != request.name() {
1710 return Err(ResolveError::MismatchedPackageName {
1711 request: "distribution",
1712 expected: name.clone(),
1713 actual: request.name().clone(),
1714 });
1715 }
1716 if !self
1718 .hasher
1719 .allows_package(candidate.name(), candidate.version())
1720 {
1721 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1722 }
1723 Ok(())
1724 },
1725 )
1726 })
1727 } else {
1728 None
1729 };
1730 pins.insert(candidate, dist, request)
1731 }
1732
1733 fn check_requires_python<'dist>(
1736 dist: &'dist CompatibleDist,
1737 python_requirement: &PythonRequirement,
1738 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1739 let requires_python = dist.requires_python()?;
1740 if python_requirement.target().is_contained_by(requires_python) {
1741 None
1742 } else {
1743 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1744 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1745 requires_python.clone(),
1746 if python_requirement.installed() == python_requirement.target() {
1747 PythonRequirementKind::Installed
1748 } else {
1749 PythonRequirementKind::Target
1750 },
1751 ))
1752 } else {
1753 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1754 requires_python.clone(),
1755 if python_requirement.installed() == python_requirement.target() {
1756 PythonRequirementKind::Installed
1757 } else {
1758 PythonRequirementKind::Target
1759 },
1760 ))
1761 };
1762 Some((requires_python, incompatibility))
1763 }
1764 }
1765
1766 fn get_dependencies_forking(
1768 &self,
1769 id: Id<PubGrubPackage>,
1770 package: &PubGrubPackage,
1771 version: &Version,
1772 pins: &FilePins,
1773 env: &ResolverEnvironment,
1774 python_requirement: &PythonRequirement,
1775 pubgrub: &State<UvDependencyProvider>,
1776 ) -> Result<ForkedDependencies, ResolveError> {
1777 let dependencies =
1778 self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1779 if env.marker_environment().is_some() {
1780 Ok(ForkedDependencies::from_dependencies_platform_specific(
1781 dependencies,
1782 ))
1783 } else {
1784 Ok(ForkedDependencies::from_dependencies_universal(
1785 dependencies,
1786 env,
1787 python_requirement,
1788 &self.conflicts,
1789 ))
1790 }
1791 }
1792
1793 #[instrument(skip_all, fields(%package, %version))]
1795 fn get_dependencies(
1796 &self,
1797 id: Id<PubGrubPackage>,
1798 package: &PubGrubPackage,
1799 version: &Version,
1800 pins: &FilePins,
1801 env: &ResolverEnvironment,
1802 python_requirement: &PythonRequirement,
1803 pubgrub: &State<UvDependencyProvider>,
1804 ) -> Result<Dependencies, ResolveError> {
1805 let expander =
1806 RequirementExpander::new(&self.constraints, &self.modifiers, env, python_requirement);
1807 let dependencies = match &**package {
1808 PubGrubPackageInner::Root(_) => {
1809 let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1810
1811 PubGrubDependency::from_requirements(
1812 &self.conflicts,
1813 requirements,
1814 None,
1815 Some(package),
1816 )
1817 }
1818
1819 PubGrubPackageInner::Package {
1820 name,
1821 kind,
1822 marker: _,
1823 } => {
1824 if self.dependency_mode.is_direct() {
1826 return Ok(Dependencies::Unforkable(Vec::default()));
1827 }
1828
1829 let registered = pins
1830 .metadata(name, version)
1831 .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1832
1833 if self.dependency_mode.is_transitive()
1835 && self.unavailable_packages.pin().contains_key(name)
1836 && self.installed_packages.get_packages(name).is_empty()
1837 {
1838 debug_assert!(
1839 false,
1840 "Dependencies were requested for a package that is not available"
1841 );
1842 return Err(ResolveError::PackageUnavailable(name.clone()));
1843 }
1844
1845 let response = registered.wait();
1847
1848 let metadata = match &*response {
1849 MetadataResponse::Found(archive) => &archive.metadata,
1850 MetadataResponse::Unavailable(reason) => {
1851 let unavailable_version = UnavailableVersion::from(reason);
1852 let message = unavailable_version.singular_message();
1853 if let Some(err) = reason.source() {
1854 warn!("{name} {message}: {err}");
1856 } else {
1857 warn!("{name} {message}");
1858 }
1859 let incomplete_packages = self.incomplete_packages.pin();
1860 let versions = incomplete_packages.get_or_insert(
1861 name.clone(),
1862 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1863 );
1864 versions.pin().insert(version.clone(), reason.clone());
1865 return Ok(Dependencies::Unavailable(unavailable_version));
1866 }
1867 MetadataResponse::Error(dist, err) => {
1868 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1869 .unwrap_or_default();
1870 return Err(ResolveError::Dist(
1871 DistErrorKind::from_requested_dist(dist, &**err),
1872 dist.clone(),
1873 chain,
1874 err.clone(),
1875 ));
1876 }
1877 };
1878
1879 if let Some(requires_python) = &metadata.requires_python {
1882 if !python_requirement.target().is_contained_by(requires_python) {
1883 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1884 }
1885 }
1886
1887 let system_dependencies = self
1889 .options
1890 .torch_backend
1891 .as_ref()
1892 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1893 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1894 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1895 .map(IndexUrl::url)
1896 .and_then(SystemDependency::from_index)
1897 .into_iter()
1898 .inspect(|system_dependency| {
1899 debug!(
1900 "Adding system dependency `{}` for `{package}@{version}`",
1901 system_dependency
1902 );
1903 })
1904 .map(PubGrubDependency::from);
1905
1906 let (requirements, context) = match kind {
1907 PackageNodeKind::Group(group) => (
1908 metadata
1909 .dependency_groups
1910 .get(group)
1911 .map_or(&[][..], AsRef::as_ref),
1912 RequirementContext::Group { name, version },
1913 ),
1914 PackageNodeKind::Extra(extra) => (
1915 metadata.requires_dist.as_ref(),
1916 RequirementContext::Extra {
1917 name,
1918 version,
1919 extra,
1920 },
1921 ),
1922 PackageNodeKind::Base => (
1923 metadata.requires_dist.as_ref(),
1924 RequirementContext::Package { name, version },
1925 ),
1926 };
1927 let requirements = expander.expand(requirements, context);
1928
1929 PubGrubDependency::from_requirements(
1930 &self.conflicts,
1931 requirements,
1932 kind.group(),
1933 Some(package),
1934 )
1935 .map(|mut dependencies| {
1936 dependencies.extend(system_dependencies);
1937 dependencies
1938 })
1939 }
1940
1941 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1942
1943 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1944
1945 PubGrubPackageInner::Marker { name, marker } => {
1947 return Ok(Dependencies::Unforkable(
1948 [MarkerTree::TRUE, *marker]
1949 .into_iter()
1950 .map(move |marker| PubGrubDependency {
1951 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1952 name: name.clone(),
1953 kind: PackageNodeKind::Base,
1954 marker,
1955 }),
1956 version: Range::singleton(version.clone()),
1957 parent: None,
1958 source: DependencySource::Unspecified,
1959 })
1960 .collect(),
1961 ));
1962 }
1963
1964 PubGrubPackageInner::Extra {
1966 name,
1967 extra,
1968 marker,
1969 } => {
1970 return Ok(Dependencies::Unforkable(
1971 [MarkerTree::TRUE, *marker]
1972 .into_iter()
1973 .dedup()
1974 .flat_map(move |marker| {
1975 [None, Some(extra)]
1976 .into_iter()
1977 .map(move |extra| PubGrubDependency {
1978 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1979 name: name.clone(),
1980 kind: extra
1981 .cloned()
1982 .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
1983 marker,
1984 }),
1985 version: Range::singleton(version.clone()),
1986 parent: None,
1987 source: DependencySource::Unspecified,
1988 })
1989 })
1990 .collect(),
1991 ));
1992 }
1993
1994 PubGrubPackageInner::Group {
1996 name,
1997 group,
1998 marker,
1999 } => {
2000 return Ok(Dependencies::Unforkable(
2001 [MarkerTree::TRUE, *marker]
2002 .into_iter()
2003 .dedup()
2004 .map(|marker| PubGrubDependency {
2005 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2006 name: name.clone(),
2007 kind: PackageNodeKind::Group(group.clone()),
2008 marker,
2009 }),
2010 version: Range::singleton(version.clone()),
2011 parent: None,
2012 source: DependencySource::Unspecified,
2013 })
2014 .collect(),
2015 ));
2016 }
2017 };
2018 Ok(match dependencies {
2019 Ok(dependencies) => Dependencies::Available(dependencies),
2020 Err(requirement) => {
2021 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2022 }
2023 })
2024 }
2025
2026 async fn fetch<Provider: ResolverProvider>(
2028 self: Arc<Self>,
2029 provider: Arc<Provider>,
2030 request_stream: Receiver<Request>,
2031 ) -> Result<(), ResolveError> {
2032 let mut response_stream = ReceiverStream::new(request_stream)
2033 .map(|request| self.process_request(request, &*provider).boxed_local())
2034 .buffer_unordered(usize::MAX);
2038
2039 while let Some(response) = response_stream.next().await {
2040 match response? {
2041 Some(Response::Package(name, index, version_map)) => {
2042 trace!("Received package metadata for: {name}");
2043 if let Some(index) = index {
2044 self.index
2045 .explicit()
2046 .done((name, index), Arc::new(version_map));
2047 } else {
2048 self.index.implicit().done(name, Arc::new(version_map));
2049 }
2050 }
2051 Some(Response::Installed { dist, metadata }) => {
2052 trace!("Received installed distribution metadata for: {dist}");
2053 self.index
2054 .distributions()
2055 .done(dist.distribution_id(), Arc::new(metadata));
2056 }
2057 Some(Response::Dist { dist, metadata }) => {
2058 let dist_kind = match dist {
2059 Dist::Built(_) => "built",
2060 Dist::Source(_) => "source",
2061 };
2062 trace!("Received {dist_kind} distribution metadata for: {dist}");
2063 if let MetadataResponse::Unavailable(reason) = &metadata {
2064 let message = UnavailableVersion::from(reason).singular_message();
2065 if let Some(err) = reason.source() {
2066 warn!("`{dist}` {message}: {err}");
2068 } else {
2069 warn!("`{dist}` {message}");
2070 }
2071 }
2072 self.index
2073 .distributions()
2074 .done(dist.distribution_id(), Arc::new(metadata));
2075 }
2076 None => {}
2077 }
2078 }
2079
2080 Ok::<(), ResolveError>(())
2081 }
2082
2083 #[instrument(skip_all, fields(%request))]
2084 async fn process_request<Provider: ResolverProvider>(
2085 &self,
2086 request: Request,
2087 provider: &Provider,
2088 ) -> Result<Option<Response>, ResolveError> {
2089 match request {
2090 Request::Package(package_name, index) => {
2092 let package_versions = provider
2093 .get_package_versions(&package_name, index.as_ref())
2094 .boxed_local()
2095 .await
2096 .map_err(ResolveError::Client)?;
2097
2098 Ok(Some(Response::Package(
2099 package_name,
2100 index.map(IndexMetadata::into_url),
2101 package_versions,
2102 )))
2103 }
2104
2105 Request::Dist(dist) => {
2107 if let Some(version) = dist.version() {
2108 if let Some(index) = dist.index() {
2109 let versions_response = self.index.implicit().get(dist.name());
2111 if let Some(VersionsResponse::Found(version_maps)) =
2112 versions_response.as_deref()
2113 {
2114 for version_map in version_maps {
2115 if version_map.index() == Some(index) {
2116 let Some(metadata) = version_map.get_metadata(version) else {
2117 continue;
2118 };
2119 debug!("Found registry-provided metadata for: {dist}");
2120 return Ok(Some(Response::Dist {
2121 dist,
2122 metadata: MetadataResponse::Found(
2123 ArchiveMetadata::from_metadata23(metadata),
2124 ),
2125 }));
2126 }
2127 }
2128 }
2129
2130 let versions_response = self
2132 .index
2133 .explicit()
2134 .get(&(dist.name().clone(), index.clone()));
2135 if let Some(VersionsResponse::Found(version_maps)) =
2136 versions_response.as_deref()
2137 {
2138 for version_map in version_maps {
2139 let Some(metadata) = version_map.get_metadata(version) else {
2140 continue;
2141 };
2142 debug!("Found registry-provided metadata for: {dist}");
2143 return Ok(Some(Response::Dist {
2144 dist,
2145 metadata: MetadataResponse::Found(
2146 ArchiveMetadata::from_metadata23(metadata),
2147 ),
2148 }));
2149 }
2150 }
2151 }
2152 }
2153
2154 let metadata = provider
2155 .get_or_build_wheel_metadata(&dist)
2156 .boxed_local()
2157 .await?;
2158
2159 if let MetadataResponse::Found(metadata) = &metadata {
2160 if &metadata.metadata.name != dist.name() {
2161 return Err(ResolveError::MismatchedPackageName {
2162 request: "distribution metadata",
2163 expected: dist.name().clone(),
2164 actual: metadata.metadata.name.clone(),
2165 });
2166 }
2167 }
2168
2169 Ok(Some(Response::Dist { dist, metadata }))
2170 }
2171
2172 Request::Installed(dist) => {
2173 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2174
2175 if let MetadataResponse::Found(metadata) = &metadata {
2176 if &metadata.metadata.name != dist.name() {
2177 return Err(ResolveError::MismatchedPackageName {
2178 request: "installed metadata",
2179 expected: dist.name().clone(),
2180 actual: metadata.metadata.name.clone(),
2181 });
2182 }
2183 }
2184
2185 Ok(Some(Response::Installed { dist, metadata }))
2186 }
2187
2188 Request::Prefetch(package_name, range, python_requirement) => {
2190 let versions_response = self
2192 .index
2193 .implicit()
2194 .get_registered(package_name.clone())
2195 .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2196 .wait()
2197 .await;
2198
2199 let version_map = match *versions_response {
2200 VersionsResponse::Found(ref version_map) => version_map,
2201 VersionsResponse::NoIndex => {
2203 self.unavailable_packages
2204 .pin()
2205 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2206
2207 return Ok(None);
2208 }
2209 VersionsResponse::Offline => {
2210 self.unavailable_packages
2211 .pin()
2212 .insert(package_name.clone(), UnavailablePackage::Offline);
2213
2214 return Ok(None);
2215 }
2216 VersionsResponse::NotFound => {
2217 self.unavailable_packages
2218 .pin()
2219 .insert(package_name.clone(), UnavailablePackage::NotFound);
2220
2221 return Ok(None);
2222 }
2223 };
2224
2225 let env = ResolverEnvironment::universal(vec![]);
2227
2228 let Some(candidate) = self.selector.select(
2231 &package_name,
2232 &range,
2233 version_map,
2234 &self.preferences,
2235 &self.installed_packages,
2236 &self.exclusions,
2237 None,
2238 &env,
2239 self.tags.as_ref(),
2240 ) else {
2241 return Ok(None);
2242 };
2243
2244 let Some(dist) = candidate.compatible() else {
2246 return Ok(None);
2247 };
2248
2249 for version_map in version_map {
2251 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2252 let dist = dist.for_resolution();
2253 if version_map.index() == dist.index() {
2254 debug!("Found registry-provided metadata for: {dist}");
2255
2256 let metadata =
2257 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2258
2259 let dist = dist.to_owned();
2260 if &package_name != dist.name() {
2261 return Err(ResolveError::MismatchedPackageName {
2262 request: "distribution",
2263 expected: package_name,
2264 actual: dist.name().clone(),
2265 });
2266 }
2267
2268 let response = match dist {
2269 ResolvedDist::Installable { dist, .. } => Response::Dist {
2270 dist: (*dist).clone(),
2271 metadata,
2272 },
2273 ResolvedDist::Installed { dist } => Response::Installed {
2274 dist: (*dist).clone(),
2275 metadata,
2276 },
2277 };
2278
2279 return Ok(Some(response));
2280 }
2281 }
2282 }
2283
2284 if dist.wheel().is_none() {
2288 if !self.selector.use_highest_version(&package_name, &env) {
2289 if let Some((lower, _)) = range.iter().next() {
2290 if lower == Bound::Unbounded {
2291 debug!(
2292 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2293 );
2294 return Ok(None);
2295 }
2296 }
2297 }
2298 }
2299
2300 if let Some(requires_python) = dist.requires_python() {
2302 if !python_requirement.target().is_contained_by(requires_python) {
2303 return Ok(None);
2304 }
2305 }
2306
2307 if !self
2309 .hasher
2310 .allows_package(candidate.name(), candidate.version())
2311 {
2312 return Ok(None);
2313 }
2314
2315 let dist = dist.for_resolution();
2317 if self.index.distributions().register(dist.distribution_id()) {
2318 let dist = dist.to_owned();
2319 if &package_name != dist.name() {
2320 return Err(ResolveError::MismatchedPackageName {
2321 request: "distribution",
2322 expected: package_name,
2323 actual: dist.name().clone(),
2324 });
2325 }
2326
2327 let response = match dist {
2328 ResolvedDist::Installable { dist, .. } => {
2329 let metadata = provider
2330 .get_or_build_wheel_metadata(&dist)
2331 .boxed_local()
2332 .await?;
2333
2334 Response::Dist {
2335 dist: (*dist).clone(),
2336 metadata,
2337 }
2338 }
2339 ResolvedDist::Installed { dist } => {
2340 let metadata =
2341 provider.get_installed_metadata(&dist).boxed_local().await?;
2342
2343 Response::Installed {
2344 dist: (*dist).clone(),
2345 metadata,
2346 }
2347 }
2348 };
2349
2350 Ok(Some(response))
2351 } else {
2352 Ok(None)
2353 }
2354 }
2355 }
2356 }
2357
2358 fn convert_no_solution_err(
2359 &self,
2360 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2361 fork_urls: ForkUrls,
2362 fork_indexes: ForkIndexes,
2363 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2364 env: ResolverEnvironment,
2365 current_environment: MarkerEnvironment,
2366 visited: &FxHashSet<PackageName>,
2367 ) -> ResolveError {
2368 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2369 err,
2370 ));
2371 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2372
2373 let mut unavailable_packages = FxHashMap::default();
2374 for package in derivation_tree_packages(&err) {
2375 if let PubGrubPackageInner::Package { name, .. } = &**package {
2376 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2377 unavailable_packages.insert(name.clone(), reason.clone());
2378 }
2379 }
2380 }
2381
2382 let mut incomplete_packages = FxHashMap::default();
2383 let incomplete_packages_cache = self.incomplete_packages.pin();
2384 for package in derivation_tree_packages(&err) {
2385 if let PubGrubPackageInner::Package { name, .. } = &**package
2386 && let Some(versions) = incomplete_packages_cache.get(name)
2387 {
2388 for (version, reason) in &versions.pin() {
2389 incomplete_packages
2390 .entry(name.clone())
2391 .or_insert_with(BTreeMap::default)
2392 .insert(version.clone(), reason.clone());
2393 }
2394 }
2395 }
2396
2397 let mut available_indexes = FxHashMap::default();
2398 let mut included_versions = FxHashMap::default();
2399 let mut available_versions = FxHashMap::default();
2400
2401 let available_version_cutoff: Option<jiff::Timestamp> =
2402 std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2403 .ok()
2404 .and_then(|s| s.parse().ok());
2405
2406 for package in derivation_tree_packages(&err) {
2407 let Some(name) = package.name() else { continue };
2408 if !visited.contains(name) {
2409 continue;
2414 }
2415 let versions_response = if let Some(index) = fork_indexes.get(name) {
2416 self.index
2417 .explicit()
2418 .get(&(name.clone(), index.url().clone()))
2419 } else {
2420 self.index.implicit().get(name)
2421 };
2422 if let Some(response) = versions_response {
2423 if let VersionsResponse::Found(ref version_maps) = *response {
2424 for version_map in version_maps {
2426 let package_included_versions = included_versions
2427 .entry(name.clone())
2428 .or_insert_with(BTreeSet::new);
2429 let package_available_versions = available_versions
2430 .entry(name.clone())
2431 .or_insert_with(BTreeSet::new);
2432
2433 for (version, dists) in version_map.iter(&Ranges::full()) {
2434 let excluded_from_included = || {
2439 let Some(included_version_cutoff) =
2440 version_map.included_version_cutoff()
2441 else {
2442 return false;
2443 };
2444 let Some(prioritized_dist) = dists.prioritized_dist() else {
2445 return true;
2446 };
2447 prioritized_dist.files().all(|file| {
2448 file.upload_time_utc_ms.is_none_or(|upload_time| {
2449 upload_time >= included_version_cutoff.as_millisecond()
2450 })
2451 })
2452 };
2453
2454 if !excluded_from_included() {
2455 package_included_versions.insert(version.clone());
2456 }
2457
2458 let excluded_from_available = || {
2464 let Some(ref exclude_newer) = available_version_cutoff else {
2465 return false;
2466 };
2467 let Some(prioritized_dist) = dists.prioritized_dist() else {
2468 return false;
2469 };
2470 prioritized_dist.files().all(|file| {
2471 file.upload_time_utc_ms.is_some_and(|upload_time| {
2472 upload_time >= exclude_newer.as_millisecond()
2473 })
2474 })
2475 };
2476
2477 if !excluded_from_available() {
2478 package_available_versions.insert(version.clone());
2479 }
2480 }
2481 }
2482
2483 available_indexes
2485 .entry(name.clone())
2486 .or_insert(BTreeSet::new())
2487 .extend(
2488 version_maps
2489 .iter()
2490 .filter_map(|version_map| version_map.index().cloned()),
2491 );
2492 }
2493 }
2494 }
2495
2496 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2497 err,
2498 self.index.clone(),
2499 included_versions,
2500 available_versions,
2501 available_indexes,
2502 self.selector.clone(),
2503 self.python_requirement.clone(),
2504 self.locations.clone(),
2505 self.capabilities.clone(),
2506 unavailable_packages,
2507 incomplete_packages,
2508 fork_urls,
2509 fork_indexes,
2510 env,
2511 current_environment,
2512 self.tags.clone(),
2513 self.workspace_members.clone(),
2514 self.options.clone(),
2515 )))
2516 }
2517
2518 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2519 if let Some(reporter) = self.reporter.as_ref() {
2520 match &**package {
2521 PubGrubPackageInner::Root(_) => {}
2522 PubGrubPackageInner::Python(_) => {}
2523 PubGrubPackageInner::System(_) => {}
2524 PubGrubPackageInner::Marker { .. } => {}
2525 PubGrubPackageInner::Extra { .. } => {}
2526 PubGrubPackageInner::Group { .. } => {}
2527 PubGrubPackageInner::Package { name, .. } => {
2528 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2529 }
2530 }
2531 }
2532 }
2533
2534 fn on_complete(&self) {
2535 if let Some(reporter) = self.reporter.as_ref() {
2536 reporter.on_complete();
2537 }
2538 }
2539}
2540
2541#[derive(Clone, Default)]
2544struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2545
2546impl KnownVersions {
2547 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2560 &'a mut self,
2561 index: &InMemoryIndex,
2562 installed_packages: &InstalledPackages,
2563 source: PackageSource<'_>,
2564 package: &PubGrubPackage,
2565 ) -> Option<&'a [Version]> {
2566 let name = package.name_no_root()?;
2567 let PackageSource::Registry(index_metadata) = source else {
2569 return None;
2570 };
2571 if !self.0.contains_key(name) {
2572 let response = if let Some(index_metadata) = index_metadata {
2573 index
2574 .explicit()
2575 .get(&(name.clone(), index_metadata.url().clone()))?
2576 } else {
2577 index.implicit().get(name)?
2578 };
2579 let VersionsResponse::Found(ref version_maps) = *response else {
2580 return None;
2581 };
2582 let mut versions: Vec<Version> = version_maps
2583 .iter()
2584 .flat_map(|version_map| version_map.included_versions().cloned())
2585 .chain(
2586 installed_packages
2587 .get_packages(name)
2588 .iter()
2589 .map(|dist| dist.version().clone()),
2590 )
2591 .collect();
2592 versions.sort_unstable();
2593 versions.dedup();
2594 self.0.insert(name.clone(), versions.into());
2595 }
2596 Some(&self.0[name][..])
2597 }
2598}
2599
2600#[derive(Clone, Default)]
2602enum ForkContinuation {
2603 #[default]
2605 Propagate,
2606 SelectVersion { package: Id<PubGrubPackage> },
2608 UseVersion {
2610 package: Id<PubGrubPackage>,
2611 version: Version,
2612 },
2613}
2614
2615#[derive(Clone)]
2617pub(crate) struct ForkState<'index> {
2618 pubgrub: State<UvDependencyProvider>,
2626 continuation: ForkContinuation,
2628 next: Id<PubGrubPackage>,
2630 pins: FilePins<'index>,
2639 fork_urls: ForkUrls,
2645 fork_indexes: ForkIndexes,
2650 priorities: PubGrubPriorities,
2656 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2659 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2661 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2663 known_versions: KnownVersions,
2667 env: ResolverEnvironment,
2682 python_requirement: PythonRequirement,
2695 conflict_tracker: ConflictTracker,
2696 prefetcher: BatchPrefetcher,
2700}
2701
2702impl<'index> ForkState<'index> {
2703 fn new(
2704 pubgrub: State<UvDependencyProvider>,
2705 env: ResolverEnvironment,
2706 python_requirement: PythonRequirement,
2707 prefetcher: BatchPrefetcher,
2708 ) -> Self {
2709 Self {
2710 continuation: ForkContinuation::Propagate,
2711 next: pubgrub.root_package,
2712 pubgrub,
2713 pins: FilePins::default(),
2714 fork_urls: ForkUrls::default(),
2715 fork_indexes: ForkIndexes::default(),
2716 priorities: PubGrubPriorities::default(),
2717 added_dependencies: FxHashMap::default(),
2718 pre_visited: FxHashMap::default(),
2719 selected_versions: FxHashMap::default(),
2720 known_versions: KnownVersions::default(),
2721 env,
2722 python_requirement,
2723 conflict_tracker: ConflictTracker::default(),
2724 prefetcher,
2725 }
2726 }
2727
2728 fn visit_package_version_dependencies(
2731 &mut self,
2732 for_package: Id<PubGrubPackage>,
2733 for_version: &Version,
2734 urls: &Urls,
2735 indexes: &Indexes,
2736 dependencies: &[PubGrubDependency],
2737 git: &GitResolver,
2738 workspace_members: &BTreeSet<PackageName>,
2739 resolution_strategy: &ResolutionStrategy,
2740 ) -> Result<(), ResolveError> {
2741 for dependency in dependencies {
2742 let PubGrubDependency {
2743 package,
2744 version,
2745 parent: _,
2746 source,
2747 } = dependency;
2748
2749 let mut has_url = false;
2750 if let Some(name) = package.name() {
2751 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2756 self.fork_urls.insert(name, url, &self.env)?;
2757 has_url = true;
2758 }
2759
2760 if let Some(index) = source.explicit_index() {
2761 self.fork_indexes.insert(name, index, &self.env)?;
2762 }
2763
2764 for index in indexes.get(name, &self.env) {
2766 self.fork_indexes.insert(name, index, &self.env)?;
2767 }
2768 }
2769
2770 if let Some(name) = self.pubgrub.package_store[for_package]
2771 .name_no_root()
2772 .filter(|name| !workspace_members.contains(name))
2773 {
2774 debug!(
2775 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2776 );
2777 } else {
2778 debug!("Adding direct dependency: {package}{version}");
2780
2781 let missing_lower_bound = version
2783 .bounding_range()
2784 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2785 let strategy_lowest = matches!(
2786 resolution_strategy,
2787 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2788 );
2789
2790 if !has_url && missing_lower_bound && strategy_lowest {
2791 let name = package.name_no_root().unwrap();
2792 let bound_on_other_package = dependencies.iter().any(|other| {
2799 Some(name) == other.package.name()
2800 && !other
2801 .version
2802 .bounding_range()
2803 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2804 });
2805
2806 if !bound_on_other_package {
2807 warn_user_once!(
2808 "The direct dependency `{name}` is unpinned. \
2809 Consider setting a lower bound when using `--resolution lowest` \
2810 or `--resolution lowest-direct` to avoid using outdated versions.",
2811 );
2812 }
2813 }
2814 }
2815
2816 self.priorities.insert(package, version, &self.fork_urls);
2818 if let Some(base_package) = package.base_package() {
2821 self.priorities
2822 .insert(&base_package, version, &self.fork_urls);
2823 }
2824 }
2825
2826 Ok(())
2827 }
2828
2829 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2835 &mut self,
2836 for_package: Id<PubGrubPackage>,
2837 for_version: &Version,
2838 dependencies: Vec<PubGrubDependency>,
2839 index: &InMemoryIndex,
2840 installed_packages: &InstalledPackages,
2841 ) {
2842 for dependency in &dependencies {
2843 let PubGrubDependency {
2844 package,
2845 version,
2846 parent: _,
2847 source: _,
2848 } = dependency;
2849
2850 let Some(base_package) = package.base_package() else {
2851 continue;
2852 };
2853
2854 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2855 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2856 self.pubgrub.add_proxy_package_incompatibility(
2857 proxy_package,
2858 base_package_id,
2859 version.clone(),
2860 );
2861 }
2862
2863 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2866 let conflict = self.pubgrub.add_package_version_dependencies(
2867 self.next,
2868 for_version.clone(),
2869 versions,
2870 dependencies.into_iter().map(|dependency| {
2871 let PubGrubDependency {
2872 package,
2873 version,
2874 parent: _,
2875 source: _,
2876 } = dependency;
2877 (package, version)
2878 }),
2879 );
2880
2881 if let Some(incompatibility) = conflict {
2884 self.record_conflict(for_package, Some(for_version), incompatibility);
2885 }
2886 }
2887
2888 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2890 &mut self,
2891 version: &Version,
2892 index: &InMemoryIndex,
2893 installed_packages: &InstalledPackages,
2894 ) -> Range<Version> {
2895 widen_to_gap(
2896 version,
2897 self.known_versions.get_or_update(
2898 index,
2899 installed_packages,
2900 PackageSource::from_fork(
2901 &self.pubgrub.package_store[self.next],
2902 &self.fork_urls,
2903 &self.fork_indexes,
2904 ),
2905 &self.pubgrub.package_store[self.next],
2906 ),
2907 )
2908 }
2909
2910 fn record_conflict(
2911 &mut self,
2912 affected: Id<PubGrubPackage>,
2913 version: Option<&Version>,
2914 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2915 ) {
2916 let mut culprit_is_real = false;
2917 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2918 if incompatible == affected {
2919 continue;
2920 }
2921 if self.pubgrub.package_store[affected].name()
2922 == self.pubgrub.package_store[incompatible].name()
2923 {
2924 continue;
2927 }
2928 culprit_is_real = true;
2929 let culprit_count = self
2930 .conflict_tracker
2931 .culprit
2932 .entry(incompatible)
2933 .or_default();
2934 *culprit_count += 1;
2935 if *culprit_count == CONFLICT_THRESHOLD {
2936 self.conflict_tracker.deprioritize.push(incompatible);
2937 }
2938 }
2939 if culprit_is_real {
2942 if tracing::enabled!(Level::DEBUG) {
2943 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
2944 .iter()
2945 .map(|(package, _term)| &self.pubgrub.package_store[package])
2946 .join(", ");
2947 if let Some(version) = version {
2948 debug!(
2949 "Recording dependency conflict of {}=={} from incompatibility of ({})",
2950 self.pubgrub.package_store[affected], version, incompatibility
2951 );
2952 } else {
2953 debug!(
2954 "Recording unit propagation conflict of {} from incompatibility of ({})",
2955 self.pubgrub.package_store[affected], incompatibility
2956 );
2957 }
2958 }
2959
2960 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
2961 *affected_count += 1;
2962 if *affected_count == CONFLICT_THRESHOLD {
2963 self.conflict_tracker.prioritize.push(self.next);
2964 }
2965 }
2966 }
2967
2968 fn reprioritize_conflicts(&mut self) {
2972 for package in self.conflict_tracker.prioritize.drain(..) {
2973 let changed = self
2974 .priorities
2975 .mark_conflict_early(&self.pubgrub.package_store[package]);
2976 if changed {
2977 debug!(
2978 "Package {} has too many conflicts (affected), prioritizing",
2979 &self.pubgrub.package_store[package]
2980 );
2981 } else {
2982 debug!(
2983 "Package {} has too many conflicts (affected), already {:?}",
2984 self.pubgrub.package_store[package],
2985 self.priorities.get(&self.pubgrub.package_store[package])
2986 );
2987 }
2988 }
2989
2990 for package in self.conflict_tracker.deprioritize.drain(..) {
2991 let changed = self
2992 .priorities
2993 .mark_conflict_late(&self.pubgrub.package_store[package]);
2994 if changed {
2995 debug!(
2996 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
2997 self.pubgrub.package_store[package],
2998 );
2999 let backtrack_level = self.pubgrub.backtrack_package(package);
3000 if let Some(backtrack_level) = backtrack_level {
3001 debug!("Backtracked {backtrack_level} decisions");
3002 } else {
3003 debug!(
3004 "Package {} is not decided, cannot backtrack",
3005 self.pubgrub.package_store[package]
3006 );
3007 }
3008 } else {
3009 debug!(
3010 "Package {} has too many conflicts (culprit), already {:?}",
3011 self.pubgrub.package_store[package],
3012 self.priorities.get(&self.pubgrub.package_store[package])
3013 );
3014 }
3015 }
3016 }
3017
3018 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3026 &mut self,
3027 version: Version,
3028 reason: UnavailableVersion,
3029 index: &InMemoryIndex,
3030 installed_packages: &InstalledPackages,
3031 ) {
3032 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3033
3034 if let UnavailableVersion::IncompatibleDist(
3038 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3039 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3040 ) = reason
3041 {
3042 let package = &self.next;
3043 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3044 PubGrubPackageInner::Python(match kind {
3045 PythonRequirementKind::Installed => PubGrubPython::Installed,
3046 PythonRequirementKind::Target => PubGrubPython::Target,
3047 }),
3048 ));
3049 self.pubgrub
3050 .add_incompatibility(Incompatibility::from_dependency(
3051 *package,
3052 versions,
3053 (
3054 python,
3055 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3056 ),
3057 ));
3058 self.pubgrub
3059 .partial_solution
3060 .add_decision(self.next, version);
3061 return;
3062 }
3063 self.pubgrub
3064 .add_incompatibility(Incompatibility::custom_term(
3065 self.next,
3066 Term::Positive(versions),
3067 UnavailableReason::Version(reason),
3068 ));
3069 }
3070
3071 fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3072 self.continuation = continuation;
3073 self
3074 }
3075
3076 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3078 self.selected_versions.clear();
3079 self.env = env;
3080 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3082 debug!("Narrowed `requires-python` bound to: {}", req.target());
3083 self.python_requirement = req;
3084 }
3085 self
3086 }
3087
3088 fn source(
3092 &self,
3093 name: &PackageName,
3094 version: &Version,
3095 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3096 let url = self.fork_urls.get(name);
3097 let index = url
3098 .is_none()
3099 .then(|| {
3100 self.pins
3101 .get(name, version)
3102 .expect("Every package should be pinned")
3103 .index()
3104 })
3105 .flatten();
3106 (url, index)
3107 }
3108
3109 fn into_resolution(self) -> Resolution<'index> {
3110 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3111 let edge_count: usize = solution
3112 .keys()
3113 .map(|package| self.pubgrub.incompatibilities[package].len())
3114 .sum();
3115 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3116 for (package, self_version) in &solution {
3117 for id in &self.pubgrub.incompatibilities[package] {
3118 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3119 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3120 &incompatibility.kind
3121 else {
3122 continue;
3123 };
3124 let (self_package, dependency_package) = (*self_package, *dependency_package);
3125 let Some((self_range, dependency_range)) =
3126 incompatibility.dependency_version_sets()
3127 else {
3128 continue;
3129 };
3130 let dependency_range =
3131 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3132 if *package != self_package {
3133 continue;
3134 }
3135 if !self_range.contains(self_version) {
3136 continue;
3137 }
3138 let Some(dependency_version) = solution.get(&dependency_package) else {
3139 continue;
3140 };
3141 if !dependency_range.contains(dependency_version) {
3142 continue;
3143 }
3144
3145 let self_package = &self.pubgrub.package_store[self_package];
3146 let dependency_package = &self.pubgrub.package_store[dependency_package];
3147
3148 let (self_name, self_kind) = match &**self_package {
3149 PubGrubPackageInner::Package {
3150 name: self_name,
3151 kind: self_kind,
3152 marker: _,
3153 } => (Some(self_name), self_kind),
3154
3155 PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3156
3157 _ => continue,
3158 };
3159
3160 let (name, kind, marker) = match &**dependency_package {
3161 PubGrubPackageInner::Package { name, kind, marker } => {
3162 debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3163
3164 if self_kind.group().is_none() && self_name == Some(name) {
3167 continue;
3168 }
3169 (name, kind.clone(), *marker)
3170 }
3171 PubGrubPackageInner::Marker { name, marker } => {
3172 if self_kind.group().is_none() && self_name == Some(name) {
3173 continue;
3174 }
3175 (name, PackageNodeKind::Base, *marker)
3176 }
3177 PubGrubPackageInner::Extra {
3178 name,
3179 extra,
3180 marker,
3181 } => {
3182 if self_kind.group().is_none() {
3183 debug_assert!(self_name != Some(name), "Extras should be flattened");
3184 }
3185 (name, PackageNodeKind::Extra(extra.clone()), *marker)
3186 }
3187 PubGrubPackageInner::Group {
3188 name,
3189 group,
3190 marker,
3191 } => {
3192 debug_assert!(self_name != Some(name), "Groups should be flattened");
3193 (name, PackageNodeKind::Group(group.clone()), *marker)
3194 }
3195 PubGrubPackageInner::Root(_)
3196 | PubGrubPackageInner::Python(_)
3197 | PubGrubPackageInner::System(_) => continue,
3198 };
3199 let from = self_name.map(|name| {
3200 let (url, index) = self.source(name, self_version);
3201 ResolutionNode {
3202 package: ResolutionPackage {
3203 name: name.clone(),
3204 kind: self_kind.clone(),
3205 url: url.cloned(),
3206 index: index.cloned(),
3207 },
3208 version: self_version.clone(),
3209 }
3210 });
3211
3212 let (url, index) = self.source(name, dependency_version);
3213 let to = ResolutionNode {
3214 package: ResolutionPackage {
3215 name: name.clone(),
3216 kind,
3217 url: url.cloned(),
3218 index: index.cloned(),
3219 },
3220 version: dependency_version.clone(),
3221 };
3222 let edge = ResolutionDependencyEdge { from, to, marker };
3223
3224 if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3227 let mut base_edge = edge.clone();
3228 base_edge.to.package.kind = PackageNodeKind::Base;
3229 edges.push(edge);
3230 edges.push(base_edge);
3231 } else {
3232 edges.push(edge);
3233 }
3234 }
3235 }
3236
3237 let nodes = solution
3238 .into_iter()
3239 .filter_map(|(package, version)| {
3240 if let PubGrubPackageInner::Package {
3241 name,
3242 kind,
3243 marker: MarkerTree::TRUE,
3244 } = &*self.pubgrub.package_store[package]
3245 {
3246 let (url, index) = self.source(name, &version);
3247 Some((
3248 ResolutionPackage {
3249 name: name.clone(),
3250 kind: kind.clone(),
3251 url: url.cloned(),
3252 index: index.cloned(),
3253 },
3254 version,
3255 ))
3256 } else {
3257 None
3258 }
3259 })
3260 .collect();
3261
3262 Resolution {
3263 nodes,
3264 edges,
3265 pins: self.pins,
3266 env: self.env,
3267 }
3268 }
3269}
3270
3271fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3280 let versions = Range::singleton(version.clone());
3281 match known_versions {
3282 Some(known_versions) if !known_versions.is_empty() => {
3283 versions.widen_versions(known_versions)
3284 }
3285 _ => versions,
3286 }
3287}
3288
3289#[derive(Debug)]
3291#[expect(clippy::large_enum_variant)]
3292pub(crate) enum Request {
3293 Package(PackageName, Option<IndexMetadata>),
3295 Dist(Dist),
3297 Installed(InstalledDist),
3299 Prefetch(PackageName, Range<Version>, PythonRequirement),
3301}
3302
3303impl<'a> From<ResolvedDistRef<'a>> for Request {
3304 fn from(dist: ResolvedDistRef<'a>) -> Self {
3305 match dist {
3311 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3312 let source = prioritized.source_dist().expect("a source distribution");
3315 assert_eq!(
3316 (&sdist.name, &sdist.version),
3317 (&source.name, &source.version),
3318 "expected chosen sdist to match prioritized sdist"
3319 );
3320 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3321 }
3322 ResolvedDistRef::InstallableRegistryBuiltDist {
3323 wheel, prioritized, ..
3324 } => {
3325 assert_eq!(
3326 Some(&wheel.filename),
3327 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3328 "expected chosen wheel to match best wheel"
3329 );
3330 let built = prioritized.built_dist().expect("at least one wheel");
3333 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3334 }
3335 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3336 }
3337 }
3338}
3339
3340impl Display for Request {
3341 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3342 match self {
3343 Self::Package(package_name, _) => {
3344 write!(f, "Versions {package_name}")
3345 }
3346 Self::Dist(dist) => {
3347 write!(f, "Metadata `{dist}`")
3348 }
3349 Self::Installed(dist) => {
3350 write!(f, "Installed metadata `{dist}`")
3351 }
3352 Self::Prefetch(package_name, range, _) => {
3353 write!(f, "Prefetch {package_name} {range}")
3354 }
3355 }
3356 }
3357}
3358
3359#[derive(Debug)]
3360#[expect(clippy::large_enum_variant)]
3361enum Response {
3362 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3364 Dist {
3366 dist: Dist,
3367 metadata: MetadataResponse,
3368 },
3369 Installed {
3371 dist: InstalledDist,
3372 metadata: MetadataResponse,
3373 },
3374}
3375
3376enum Dependencies {
3382 Unavailable(UnavailableVersion),
3384 Available(Vec<PubGrubDependency>),
3390 RequiresPython(VersionSpecifiers),
3392 Unforkable(Vec<PubGrubDependency>),
3398}
3399
3400#[derive(Debug)]
3407enum ForkedDependencies {
3408 Unavailable(UnavailableVersion),
3410 Unforked(Vec<PubGrubDependency>),
3414 Forked {
3420 forks: Vec<Fork>,
3421 diverging_packages: BTreeSet<PackageName>,
3423 },
3424 RequiresPython(VersionSpecifiers),
3426}
3427
3428impl ForkedDependencies {
3429 fn from_dependencies_universal(
3436 dependencies: Dependencies,
3437 env: &ResolverEnvironment,
3438 python_requirement: &PythonRequirement,
3439 conflicts: &Conflicts,
3440 ) -> Self {
3441 let deps = match dependencies {
3442 Dependencies::Available(deps) => deps,
3443 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3444 Dependencies::RequiresPython(requires_python) => {
3445 return Self::RequiresPython(requires_python);
3446 }
3447 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3448 };
3449 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3450 for dep in deps {
3451 let name = dep
3452 .package
3453 .name()
3454 .expect("dependency always has a name")
3455 .clone();
3456 name_to_deps.entry(name).or_default().push(dep);
3457 }
3458 let (mut forks, diverging_packages) =
3459 Self::fork(name_to_deps, env, python_requirement, conflicts);
3460 if forks.is_empty() {
3461 Self::Unforked(vec![])
3462 } else if forks.len() == 1 {
3463 Self::Unforked(forks.pop().unwrap().dependencies)
3464 } else {
3465 Self::Forked {
3466 forks,
3467 diverging_packages,
3468 }
3469 }
3470 }
3471
3472 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3475 match dependencies {
3476 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3477 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3478 Dependencies::Unavailable(err) => Self::Unavailable(err),
3479 }
3480 }
3481
3482 fn fork(
3490 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3491 env: &ResolverEnvironment,
3492 python_requirement: &PythonRequirement,
3493 conflicts: &Conflicts,
3494 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3495 let python_marker = python_requirement.to_marker_tree();
3496
3497 let mut forks = vec![Fork::new(env.clone())];
3498 let mut diverging_packages = BTreeSet::new();
3499 for (name, mut deps) in name_to_deps {
3500 assert!(!deps.is_empty(), "every name has at least one dependency");
3501 if let [dep] = deps.as_slice() {
3512 if marker::requires_python(dep.package.marker())
3520 .is_none_or(|bound| !python_requirement.raises(&bound))
3521 {
3522 let dep = deps.pop().unwrap();
3523 let marker = dep.package.marker();
3524 for fork in &mut forks {
3525 if fork.env.included_by_marker(marker) {
3526 fork.add_dependency(dep.clone());
3527 }
3528 }
3529 continue;
3530 }
3531 } else {
3532 if let Some(dep) = deps.first() {
3534 let marker = dep.package.marker();
3535 if deps.iter().all(|dep| marker == dep.package.marker()) {
3536 if marker::requires_python(marker)
3540 .is_none_or(|bound| !python_requirement.raises(&bound))
3541 {
3542 for dep in deps {
3543 for fork in &mut forks {
3544 if fork.env.included_by_marker(marker) {
3545 fork.add_dependency(dep.clone());
3546 }
3547 }
3548 }
3549 continue;
3550 }
3551 }
3552 }
3553 }
3554 for dep in deps {
3555 let mut forker = match ForkingPossibility::new(env, &dep) {
3556 ForkingPossibility::Possible(forker) => forker,
3557 ForkingPossibility::DependencyAlwaysExcluded => {
3558 continue;
3561 }
3562 ForkingPossibility::NoForkingPossible => {
3563 for fork in &mut forks {
3566 fork.add_dependency(dep.clone());
3567 }
3568 continue;
3569 }
3570 };
3571 diverging_packages.insert(name.clone());
3573
3574 let mut new = vec![];
3575 for fork in std::mem::take(&mut forks) {
3576 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3577 new.push(fork);
3578 continue;
3579 };
3580 forker = remaining_forker;
3581
3582 for fork_env in envs {
3583 let mut new_fork = fork.clone();
3584 new_fork.set_env(fork_env);
3585 if forker.included(&new_fork.env) {
3590 new_fork.add_dependency(dep.clone());
3591 }
3592 if new_fork.env.included_by_marker(python_marker) {
3595 new.push(new_fork);
3596 }
3597 }
3598 }
3599 forks = new;
3600 }
3601 }
3602 for set in conflicts.iter() {
3617 let mut new = vec![];
3618 for fork in std::mem::take(&mut forks) {
3619 let mut has_conflicting_dependency = false;
3627 for item in set.iter() {
3628 if fork.contains_conflicting_item(item.as_ref()) {
3629 has_conflicting_dependency = true;
3630 diverging_packages.insert(item.package().clone());
3631 break;
3632 }
3633 }
3634 if !has_conflicting_dependency {
3635 new.push(fork);
3636 continue;
3637 }
3638
3639 let non_excluded: Vec<_> = set
3645 .iter()
3646 .filter(|item| fork.env.included_by_group(item.as_ref()))
3647 .collect();
3648 if non_excluded.len() < 2 {
3649 let dominated = non_excluded.iter().all(|item| {
3654 !conflicts.iter().any(|other_set| {
3655 !std::ptr::eq(set, other_set)
3656 && other_set.contains(item.package(), item.kind().as_ref())
3657 && other_set
3658 .iter()
3659 .filter(|other_item| {
3660 other_item.package() != item.package()
3661 || other_item.kind() != item.kind()
3662 })
3663 .any(|other_item| {
3664 fork.env.included_by_group(other_item.as_ref())
3665 })
3666 })
3667 });
3668 if dominated {
3669 let rules: Vec<_> = set
3674 .iter()
3675 .filter(|item| !fork.env.included_by_group(item.as_ref()))
3676 .cloned()
3677 .map(Err)
3678 .collect();
3679 if let Some(filtered) = fork.filter(rules) {
3680 new.push(filtered);
3681 }
3682 continue;
3683 }
3684 }
3685
3686 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3688 new.push(fork_none);
3689 }
3690
3691 for (i, _) in set.iter().enumerate() {
3699 let fork_allows_group = fork.clone().filter(
3700 set.iter()
3701 .cloned()
3702 .enumerate()
3703 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3704 );
3705 if let Some(fork_allows_group) = fork_allows_group {
3706 new.push(fork_allows_group);
3707 }
3708 }
3709 }
3710 forks = new;
3711 }
3712 (forks, diverging_packages)
3713 }
3714}
3715
3716#[derive(Clone, Debug)]
3726struct Fork {
3727 dependencies: Vec<PubGrubDependency>,
3736 conflicts: crate::FxHashbrownSet<ConflictItem>,
3742 env: ResolverEnvironment,
3754}
3755
3756impl Fork {
3757 fn new(env: ResolverEnvironment) -> Self {
3760 Self {
3761 dependencies: vec![],
3762 conflicts: crate::FxHashbrownSet::default(),
3763 env,
3764 }
3765 }
3766
3767 fn add_dependency(&mut self, dep: PubGrubDependency) {
3769 if let Some(conflicting_item) = dep.conflicting_item() {
3770 self.conflicts.insert(conflicting_item.to_owned());
3771 }
3772 self.dependencies.push(dep);
3773 }
3774
3775 fn set_env(&mut self, env: ResolverEnvironment) {
3780 self.env = env;
3781 self.dependencies.retain(|dep| {
3782 let marker = dep.package.marker();
3783 if self.env.included_by_marker(marker) {
3784 return true;
3785 }
3786 if let Some(conflicting_item) = dep.conflicting_item() {
3787 self.conflicts.remove(&conflicting_item);
3788 }
3789 false
3790 });
3791 }
3792
3793 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3796 self.conflicts.contains(&item)
3797 }
3798
3799 fn filter(
3806 mut self,
3807 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3808 ) -> Option<Self> {
3809 self.env = self.env.filter_by_group(rules)?;
3810 self.dependencies.retain(|dep| {
3811 let Some(conflicting_item) = dep.conflicting_item() else {
3812 return true;
3813 };
3814 if self.env.included_by_group(conflicting_item) {
3815 return true;
3816 }
3817 match conflicting_item.kind() {
3818 ConflictKindRef::Project => {
3821 if dep.parent.is_some() {
3822 return true;
3823 }
3824 }
3825 ConflictKindRef::Group(_) => {}
3826 ConflictKindRef::Extra(_) => {}
3827 }
3828 self.conflicts.remove(&conflicting_item);
3829 false
3830 });
3831 Some(self)
3832 }
3833
3834 fn cmp_requires_python(&self, other: &Self) -> Ordering {
3836 cmp_requires_python(&self.env, &other.env)
3837 }
3838
3839 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3841 let self_upper_bounds = self
3846 .dependencies
3847 .iter()
3848 .filter(|dep| {
3849 dep.version
3850 .bounding_range()
3851 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3852 })
3853 .count();
3854 let other_upper_bounds = other
3855 .dependencies
3856 .iter()
3857 .filter(|dep| {
3858 dep.version
3859 .bounding_range()
3860 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3861 })
3862 .count();
3863
3864 self_upper_bounds.cmp(&other_upper_bounds)
3865 }
3866}
3867
3868fn cmp_requires_python(
3870 self_env: &ResolverEnvironment,
3871 other_env: &ResolverEnvironment,
3872) -> Ordering {
3873 let self_bound = self_env.requires_python().unwrap_or_default();
3883 let other_bound = other_env.requires_python().unwrap_or_default();
3884 self_bound.lower().cmp(other_bound.lower())
3885}
3886
3887impl Eq for Fork {}
3888
3889impl PartialEq for Fork {
3890 fn eq(&self, other: &Self) -> bool {
3891 self.dependencies == other.dependencies && self.env == other.env
3892 }
3893}
3894
3895#[derive(Debug, Clone)]
3896pub(crate) struct VersionFork {
3897 env: ResolverEnvironment,
3899 id: Id<PubGrubPackage>,
3901 version: Option<Version>,
3903}
3904
3905fn enrich_dependency_error(
3907 error: ResolveError,
3908 id: Id<PubGrubPackage>,
3909 version: &Version,
3910 pubgrub: &State<UvDependencyProvider>,
3911) -> ResolveError {
3912 let Some(name) = pubgrub.package_store[id].name_no_root() else {
3913 return error;
3914 };
3915 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
3916 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
3917}
3918
3919fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
3921 let package = &state.package_store[id];
3922 if package.is_root() {
3923 return MarkerTree::TRUE;
3924 }
3925
3926 let mut ancestors = FxHashSet::default();
3929 let mut stack = vec![id];
3930 let mut root = None;
3931 ancestors.insert(id);
3932
3933 while let Some(current) = stack.pop() {
3934 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3935 continue;
3936 };
3937
3938 for index in incompatibilities {
3939 let incompat = &state.incompatibility_store[*index];
3940 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
3941 if current != *child {
3942 continue;
3943 }
3944 if ancestors.insert(*parent) {
3945 if state.package_store[*parent].is_root() {
3946 root = Some(*parent);
3947 }
3948 stack.push(*parent);
3949 }
3950 }
3951 }
3952 }
3953
3954 let Some(root) = root else {
3955 return MarkerTree::FALSE;
3956 };
3957
3958 let mut environments = FxHashMap::default();
3961 let mut queue = VecDeque::from([root]);
3962 environments.insert(root, MarkerTree::TRUE);
3963
3964 while let Some(current) = queue.pop_front() {
3965 let Some(current_environment) = environments.get(¤t).copied() else {
3966 continue;
3967 };
3968 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
3969 continue;
3970 };
3971
3972 for index in incompatibilities {
3973 let incompat = &state.incompatibility_store[*index];
3974 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
3975 continue;
3976 };
3977 if current != *parent || !ancestors.contains(child) {
3978 continue;
3979 }
3980
3981 let mut next_environment = state.package_store[*child].marker();
3982 next_environment = next_environment.and(current_environment);
3983
3984 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
3985 let mut combined = *entry;
3986 combined = combined.or(next_environment);
3987 if combined != *entry {
3988 *entry = combined;
3989 queue.push_back(*child);
3990 }
3991 }
3992 }
3993
3994 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
3995}
3996
3997#[derive(Debug, Default, Clone)]
3998struct ConflictTracker {
3999 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4001 prioritize: Vec<Id<PubGrubPackage>>,
4005 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4007 deprioritize: Vec<Id<PubGrubPackage>>,
4011}
4012
4013#[cfg(test)]
4014mod tests {
4015 use super::*;
4016
4017 fn versions(versions: &[&str]) -> Vec<Version> {
4018 versions
4019 .iter()
4020 .map(|version| version.parse().expect("valid version"))
4021 .collect()
4022 }
4023
4024 #[test]
4025 fn widens_a_version_to_its_gap() {
4026 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4027 let version: Version = "2.0".parse().expect("valid version");
4028
4029 assert_eq!(
4031 widen_to_gap(&version, Some(&known_versions)).to_string(),
4032 ">1.0, <3.0"
4033 );
4034
4035 let version: Version = "1.0".parse().expect("valid version");
4037 assert_eq!(
4038 widen_to_gap(&version, Some(&known_versions)).to_string(),
4039 "<2.0"
4040 );
4041 let version: Version = "3.0".parse().expect("valid version");
4042 assert_eq!(
4043 widen_to_gap(&version, Some(&known_versions)).to_string(),
4044 ">2.0"
4045 );
4046 }
4047
4048 #[test]
4049 fn widens_a_version_without_known_versions_to_itself() {
4050 let version: Version = "2.0".parse().expect("valid version");
4051
4052 assert_eq!(
4054 widen_to_gap(&version, None),
4055 Range::singleton(version.clone())
4056 );
4057
4058 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4060 }
4061}