uv_resolver/
prerelease.rs1use std::borrow::Cow;
2
3use uv_distribution_types::{Requirement, RequirementSource};
4use uv_normalize::PackageName;
5use uv_pep440::{Operator, VersionSpecifiers};
6
7use crate::resolver::ForkSet;
8use crate::{DependencyMode, Manifest, ResolverEnvironment};
9
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11#[serde(deny_unknown_fields, rename_all = "kebab-case")]
12#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
13#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
14pub enum PrereleaseMode {
15 Disallow,
17
18 Allow,
20
21 #[default]
23 IfNecessary,
24
25 Explicit,
29
30 #[deprecated(note = "use `if-necessary` instead")]
32 IfNecessaryOrExplicit,
33}
34
35#[allow(deprecated)]
36impl std::fmt::Display for PrereleaseMode {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Self::Disallow => write!(f, "disallow"),
40 Self::Allow => write!(f, "allow"),
41 Self::IfNecessary => write!(f, "if-necessary"),
42 Self::Explicit => write!(f, "explicit"),
43 Self::IfNecessaryOrExplicit => write!(f, "if-necessary-or-explicit"),
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
51pub(crate) enum PrereleaseStrategy {
52 Disallow,
54
55 Allow,
57
58 IfNecessary,
60
61 Explicit(ForkSet),
65}
66
67impl PrereleaseStrategy {
68 #[allow(deprecated)]
69 pub(crate) fn from_mode(
70 mode: PrereleaseMode,
71 manifest: &Manifest,
72 env: &ResolverEnvironment,
73 dependencies: DependencyMode,
74 ) -> Self {
75 match mode {
76 PrereleaseMode::Disallow => Self::Disallow,
77 PrereleaseMode::Allow => Self::Allow,
78 PrereleaseMode::IfNecessary | PrereleaseMode::IfNecessaryOrExplicit => {
79 Self::IfNecessary
80 }
81 PrereleaseMode::Explicit => Self::Explicit(Self::explicit_packages(
82 manifest.candidate_selection_requirements(env, dependencies),
83 )),
84 }
85 }
86
87 fn explicit_packages<'a>(requirements: impl Iterator<Item = Cow<'a, Requirement>>) -> ForkSet {
88 let mut packages = ForkSet::default();
89 for requirement in requirements {
90 let RequirementSource::Registry { specifier, .. } = &requirement.source else {
91 continue;
92 };
93
94 if contains_prerelease(specifier) {
95 packages.add(&requirement, ());
96 }
97 }
98 packages
99 }
100
101 pub(crate) fn selection(
107 &self,
108 package_name: &PackageName,
109 env: &ResolverEnvironment,
110 ) -> PrereleaseSelection {
111 match self {
112 Self::Disallow => PrereleaseSelection::Disallow,
113 Self::Allow => PrereleaseSelection::Allow,
114 Self::IfNecessary => PrereleaseSelection::PreferStable,
115 Self::Explicit(packages) => {
116 if packages.contains(package_name, env) {
117 PrereleaseSelection::PreferStable
118 } else {
119 PrereleaseSelection::Disallow
120 }
121 }
122 }
123 }
124}
125
126fn contains_prerelease(specifiers: &VersionSpecifiers) -> bool {
131 specifiers
132 .iter()
133 .filter(|specifier| {
134 !matches!(
135 specifier.operator(),
136 Operator::NotEqual | Operator::NotEqualStar
137 )
138 })
139 .any(uv_pep440::VersionSpecifier::any_prerelease)
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub(crate) enum PrereleaseSelection {
145 Disallow,
147 Allow,
149 PreferStable,
152}