1use std::path::Path;
2
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use uv_cache::Refresh;
6use uv_cache_info::Timestamp;
7use uv_distribution_types::{Requirement, RequirementSource};
8use uv_normalize::{GroupName, PackageName};
9
10#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
12#[serde(rename_all = "kebab-case", deny_unknown_fields)]
13pub enum Reinstall {
14 #[default]
16 None,
17
18 All,
20
21 Packages(Vec<PackageName>, Vec<Box<Path>>),
23}
24
25impl Reinstall {
26 pub fn from_args(reinstall: Option<bool>, reinstall_package: Vec<PackageName>) -> Option<Self> {
28 match reinstall {
29 Some(true) => Some(Self::All),
30 Some(false) => Some(Self::None),
31 None if reinstall_package.is_empty() => None,
32 None => Some(Self::Packages(reinstall_package, Vec::new())),
33 }
34 }
35
36 pub fn is_none(&self) -> bool {
38 matches!(self, Self::None)
39 }
40
41 pub fn contains_package(&self, package_name: &PackageName) -> bool {
43 match self {
44 Self::None => false,
45 Self::All => true,
46 Self::Packages(packages, ..) => packages.contains(package_name),
47 }
48 }
49
50 pub fn contains_path(&self, path: &Path) -> bool {
52 match self {
53 Self::None => false,
54 Self::All => true,
55 Self::Packages(.., paths) => paths
56 .iter()
57 .any(|target| same_file::is_same_file(path, target).unwrap_or(false)),
58 }
59 }
60
61 #[must_use]
63 pub fn combine(self, other: Self) -> Self {
64 match self {
65 Self::All | Self::None => self,
67 Self::Packages(self_packages, self_paths) => match other {
68 Self::All => other,
70 Self::None => Self::Packages(self_packages, self_paths),
72 Self::Packages(other_packages, other_paths) => {
74 let mut combined_packages = self_packages;
75 combined_packages.extend(other_packages);
76 let mut combined_paths = self_paths;
77 combined_paths.extend(other_paths);
78 Self::Packages(combined_packages, combined_paths)
79 }
80 },
81 }
82 }
83
84 #[must_use]
86 pub fn with_path(self, path: Box<Path>) -> Self {
87 match self {
88 Self::None => Self::Packages(vec![], vec![path]),
89 Self::All => Self::All,
90 Self::Packages(packages, mut paths) => {
91 paths.push(path);
92 Self::Packages(packages, paths)
93 }
94 }
95 }
96
97 #[must_use]
99 pub fn with_package(self, package_name: PackageName) -> Self {
100 match self {
101 Self::None => Self::Packages(vec![package_name], vec![]),
102 Self::All => Self::All,
103 Self::Packages(mut packages, paths) => {
104 packages.push(package_name);
105 Self::Packages(packages, paths)
106 }
107 }
108 }
109
110 pub fn package(package_name: PackageName) -> Self {
112 Self::Packages(vec![package_name], vec![])
113 }
114}
115
116impl From<Reinstall> for Refresh {
118 fn from(value: Reinstall) -> Self {
119 match value {
120 Reinstall::None => Self::None(Timestamp::now()),
121 Reinstall::All => Self::All(Timestamp::now()),
122 Reinstall::Packages(packages, paths) => {
123 Self::Packages(packages, paths, Timestamp::now())
124 }
125 }
126 }
127}
128
129#[derive(Debug, Default, Clone)]
131pub enum UpgradeStrategy {
132 #[default]
134 None,
135
136 All(FxHashSet<GroupName>),
141
142 Some(FxHashSet<PackageName>, FxHashSet<GroupName>),
144}
145
146#[derive(Debug, Default, Clone)]
148pub struct Upgrade {
149 strategy: UpgradeStrategy,
151
152 constraints: FxHashMap<PackageName, Vec<Requirement>>,
154}
155
156impl Upgrade {
157 fn none() -> Self {
159 Self {
160 strategy: UpgradeStrategy::None,
161 constraints: FxHashMap::default(),
162 }
163 }
164
165 pub fn from_args(
167 upgrade: Option<bool>,
168 upgrade_package: Vec<Requirement>,
169 upgrade_group: Vec<GroupName>,
170 ) -> Option<Self> {
171 let groups: FxHashSet<GroupName> = upgrade_group.into_iter().collect();
172
173 let strategy = match upgrade {
174 Some(true) => UpgradeStrategy::All(groups),
175 Some(false) => {
176 if upgrade_package.is_empty() && groups.is_empty() {
177 return Some(Self::none());
178 }
179 let packages = upgrade_package.iter().map(|req| req.name.clone()).collect();
182 UpgradeStrategy::Some(packages, groups)
183 }
184 None => {
185 if upgrade_package.is_empty() && groups.is_empty() {
186 return None;
187 }
188 let packages = upgrade_package.iter().map(|req| req.name.clone()).collect();
189 UpgradeStrategy::Some(packages, groups)
190 }
191 };
192
193 let mut constraints: FxHashMap<PackageName, Vec<Requirement>> = FxHashMap::default();
194 for requirement in upgrade_package {
195 if let RequirementSource::Registry { specifier, .. } = &requirement.source
197 && specifier.is_empty()
198 {
199 continue;
200 }
201 constraints
202 .entry(requirement.name.clone())
203 .or_default()
204 .push(requirement);
205 }
206
207 Some(Self {
208 strategy,
209 constraints,
210 })
211 }
212
213 pub fn package(package_name: PackageName) -> Self {
215 Self::from_packages([package_name])
216 }
217
218 pub fn from_packages(package_names: impl IntoIterator<Item = PackageName>) -> Self {
220 let mut packages = FxHashSet::default();
221 packages.extend(package_names);
222 Self {
223 strategy: UpgradeStrategy::Some(packages, FxHashSet::default()),
224 constraints: FxHashMap::default(),
225 }
226 }
227
228 pub fn is_none(&self) -> bool {
230 matches!(self.strategy, UpgradeStrategy::None)
231 }
232
233 pub fn is_all(&self) -> bool {
235 matches!(self.strategy, UpgradeStrategy::All(_))
236 }
237
238 pub fn constraints(&self) -> impl Iterator<Item = &Requirement> {
242 self.constraints
243 .values()
244 .flat_map(|requirements| requirements.iter())
245 }
246
247 pub fn packages(&self) -> Option<&FxHashSet<PackageName>> {
249 match &self.strategy {
250 UpgradeStrategy::Some(packages, _) => Some(packages),
251 _ => None,
252 }
253 }
254
255 pub fn groups(&self) -> Option<&FxHashSet<GroupName>> {
257 match &self.strategy {
258 UpgradeStrategy::All(groups) | UpgradeStrategy::Some(_, groups)
259 if !groups.is_empty() =>
260 {
261 Some(groups)
262 }
263 _ => None,
264 }
265 }
266
267 #[must_use]
269 pub fn combine(self, other: Self) -> Self {
270 let strategy = match (self.strategy, other.strategy) {
273 (UpgradeStrategy::All(mut groups), UpgradeStrategy::All(other_groups)) => {
274 groups.extend(other_groups);
275 UpgradeStrategy::All(groups)
276 }
277 (UpgradeStrategy::All(mut groups), UpgradeStrategy::Some(_, other_groups)) => {
278 groups.extend(other_groups);
279 UpgradeStrategy::All(groups)
280 }
281 (UpgradeStrategy::All(groups), UpgradeStrategy::None) => UpgradeStrategy::All(groups),
282 (UpgradeStrategy::None, _) => UpgradeStrategy::None,
283 (UpgradeStrategy::Some(_, groups), UpgradeStrategy::All(mut other_groups)) => {
284 other_groups.extend(groups);
285 UpgradeStrategy::All(other_groups)
286 }
287 (UpgradeStrategy::Some(packages, groups), UpgradeStrategy::None) => {
288 UpgradeStrategy::Some(packages, groups)
289 }
290 (
291 UpgradeStrategy::Some(mut self_packages, mut self_groups),
292 UpgradeStrategy::Some(other_packages, other_groups),
293 ) => {
294 self_packages.extend(other_packages);
295 self_groups.extend(other_groups);
296 UpgradeStrategy::Some(self_packages, self_groups)
297 }
298 };
299
300 let mut combined_constraints = self.constraints.clone();
302 for (package, requirements) in other.constraints {
303 combined_constraints
304 .entry(package)
305 .or_default()
306 .extend(requirements);
307 }
308
309 Self {
310 strategy,
311 constraints: combined_constraints,
312 }
313 }
314}
315
316impl From<Upgrade> for Refresh {
318 fn from(value: Upgrade) -> Self {
319 match value.strategy {
320 UpgradeStrategy::None => Self::None(Timestamp::now()),
321 UpgradeStrategy::All(_) => Self::All(Timestamp::now()),
322 UpgradeStrategy::Some(packages, _) => Self::Packages(
323 packages.into_iter().collect::<Vec<_>>(),
324 Vec::new(),
325 Timestamp::now(),
326 ),
327 }
328 }
329}
330
331#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
333#[serde(rename_all = "kebab-case", deny_unknown_fields)]
334#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
335pub enum BuildIsolation {
336 #[default]
338 Isolate,
339
340 Shared,
342
343 SharedPackage(Vec<PackageName>),
345}
346
347impl BuildIsolation {
348 pub fn from_args(
350 no_build_isolation: Option<bool>,
351 no_build_isolation_package: Vec<PackageName>,
352 ) -> Option<Self> {
353 match no_build_isolation {
354 Some(true) => Some(Self::Shared),
355 Some(false) => Some(Self::Isolate),
356 None if no_build_isolation_package.is_empty() => None,
357 None => Some(Self::SharedPackage(no_build_isolation_package)),
358 }
359 }
360
361 #[must_use]
363 pub fn combine(self, other: Self) -> Self {
364 match self {
365 Self::Isolate | Self::Shared => self,
367 Self::SharedPackage(self_packages) => match other {
368 Self::Shared => other,
370 Self::Isolate => Self::SharedPackage(self_packages),
372 Self::SharedPackage(other_packages) => {
374 let mut combined = self_packages;
375 combined.extend(other_packages);
376 Self::SharedPackage(combined)
377 }
378 },
379 }
380 }
381}