uv_resolver/
prerelease.rs

1use uv_distribution_types::RequirementSource;
2use uv_normalize::PackageName;
3use uv_pep440::Operator;
4
5use crate::resolver::ForkSet;
6use crate::{DependencyMode, Manifest, ResolverEnvironment};
7
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
9#[serde(deny_unknown_fields, rename_all = "kebab-case")]
10#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
12pub enum PrereleaseMode {
13    /// Disallow all pre-release versions.
14    Disallow,
15
16    /// Allow all pre-release versions.
17    Allow,
18
19    /// Allow pre-release versions if all versions of a package are pre-release.
20    IfNecessary,
21
22    /// Allow pre-release versions for first-party packages with explicit pre-release markers in
23    /// their version requirements.
24    Explicit,
25
26    /// Allow pre-release versions if all versions of a package are pre-release, or if the package
27    /// has an explicit pre-release marker in its version requirements.
28    #[default]
29    IfNecessaryOrExplicit,
30}
31
32impl std::fmt::Display for PrereleaseMode {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Disallow => write!(f, "disallow"),
36            Self::Allow => write!(f, "allow"),
37            Self::IfNecessary => write!(f, "if-necessary"),
38            Self::Explicit => write!(f, "explicit"),
39            Self::IfNecessaryOrExplicit => write!(f, "if-necessary-or-explicit"),
40        }
41    }
42}
43
44/// Like [`PrereleaseMode`], but with any additional information required to select a candidate,
45/// like the set of direct dependencies.
46#[derive(Debug, Clone)]
47pub(crate) enum PrereleaseStrategy {
48    /// Disallow all pre-release versions.
49    Disallow,
50
51    /// Allow all pre-release versions.
52    Allow,
53
54    /// Allow pre-release versions if all versions of a package are pre-release.
55    IfNecessary,
56
57    /// Allow pre-release versions for first-party packages with explicit pre-release markers in
58    /// their version requirements.
59    Explicit(ForkSet),
60
61    /// Allow pre-release versions if all versions of a package are pre-release, or if the package
62    /// has an explicit pre-release marker in its version requirements.
63    IfNecessaryOrExplicit(ForkSet),
64}
65
66impl PrereleaseStrategy {
67    pub(crate) fn from_mode(
68        mode: PrereleaseMode,
69        manifest: &Manifest,
70        env: &ResolverEnvironment,
71        dependencies: DependencyMode,
72    ) -> Self {
73        let mut packages = ForkSet::default();
74
75        match mode {
76            PrereleaseMode::Disallow => Self::Disallow,
77            PrereleaseMode::Allow => Self::Allow,
78            PrereleaseMode::IfNecessary => Self::IfNecessary,
79            _ => {
80                for requirement in manifest.requirements(env, dependencies) {
81                    let RequirementSource::Registry { specifier, .. } = &requirement.source else {
82                        continue;
83                    };
84
85                    if specifier
86                        .iter()
87                        .filter(|spec| {
88                            !matches!(spec.operator(), Operator::NotEqual | Operator::NotEqualStar)
89                        })
90                        .any(uv_pep440::VersionSpecifier::any_prerelease)
91                    {
92                        packages.add(&requirement, ());
93                    }
94                }
95
96                match mode {
97                    PrereleaseMode::Explicit => Self::Explicit(packages),
98                    PrereleaseMode::IfNecessaryOrExplicit => Self::IfNecessaryOrExplicit(packages),
99                    _ => unreachable!(),
100                }
101            }
102        }
103    }
104
105    /// Returns `true` if a [`PackageName`] is allowed to have pre-release versions.
106    pub(crate) fn allows(
107        &self,
108        package_name: &PackageName,
109        env: &ResolverEnvironment,
110    ) -> AllowPrerelease {
111        match self {
112            Self::Disallow => AllowPrerelease::No,
113            Self::Allow => AllowPrerelease::Yes,
114            Self::IfNecessary => AllowPrerelease::IfNecessary,
115            Self::Explicit(packages) => {
116                if packages.contains(package_name, env) {
117                    AllowPrerelease::Yes
118                } else {
119                    AllowPrerelease::No
120                }
121            }
122            Self::IfNecessaryOrExplicit(packages) => {
123                if packages.contains(package_name, env) {
124                    AllowPrerelease::Yes
125                } else {
126                    AllowPrerelease::IfNecessary
127                }
128            }
129        }
130    }
131}
132
133/// The pre-release strategy for a given package.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
135pub(crate) enum AllowPrerelease {
136    /// Allow all pre-release versions.
137    Yes,
138
139    /// Disallow all pre-release versions.
140    No,
141
142    /// Allow pre-release versions if all versions of this package are pre-release.
143    IfNecessary,
144}