lux_lib/operations/
uninstall.rs1use std::io;
2
3use crate::fs;
4use crate::lockfile::{FlushLockfileError, LocalPackage, LocalPackageId};
5use crate::lua_version::{LuaVersion, LuaVersionUnset};
6use crate::tree::{EntryType, InstallTree, TreeError};
7use crate::{config::Config, tree::Tree};
8use bon::Builder;
9use futures::StreamExt;
10use itertools::Itertools;
11use miette::Diagnostic;
12use thiserror::Error;
13use tracing::Instrument;
14#[derive(Error, Debug, Diagnostic)]
15#[error(transparent)]
16pub enum RemoveError {
17 #[diagnostic(transparent)]
18 LuaVersionUnset(#[from] LuaVersionUnset),
19 #[error(transparent)]
20 #[diagnostic(transparent)]
21 Fs(#[from] fs::FsError),
22 #[error(transparent)]
23 #[diagnostic(transparent)]
24 Tree(#[from] TreeError),
25 #[error(transparent)]
26 #[diagnostic(transparent)]
27 FlushLockfile(#[from] FlushLockfileError),
28}
29
30#[derive(Builder)]
31#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
32pub struct Uninstall<'a> {
33 #[builder(field)]
34 packages: Vec<LocalPackageId>,
35 config: &'a Config,
36 tree: Option<Tree>,
37}
38
39impl<'a, State> UninstallBuilder<'a, State>
40where
41 State: uninstall_builder::State,
42{
43 pub fn packages<I>(self, packages: I) -> Self
45 where
46 I: IntoIterator<Item = LocalPackageId>,
47 {
48 Self {
49 packages: self.packages.into_iter().chain(packages).collect_vec(),
50 ..self
51 }
52 }
53
54 pub fn package(self, package: LocalPackageId) -> Self {
56 self.packages(std::iter::once(package))
57 }
58}
59
60impl<'a, State> UninstallBuilder<'a, State>
61where
62 State: uninstall_builder::State + uninstall_builder::IsComplete,
63{
64 pub async fn remove(self) -> Result<Vec<LocalPackageId>, RemoveError> {
66 let args = self._build();
67 let tree = args.tree.unwrap_or(
68 args.config
69 .user_tree(LuaVersion::from(args.config)?.clone())?,
70 );
71 remove(args.packages, tree, args.config).await
72 }
73}
74
75async fn remove(
77 package_ids: Vec<LocalPackageId>,
78 tree: Tree,
79 config: &Config,
80) -> Result<Vec<LocalPackageId>, RemoveError> {
81 let lockfile = tree.lockfile()?;
82
83 let packages = package_ids
84 .iter()
85 .filter_map(|id| {
86 lockfile
87 .get(id)
88 .map(|package| (package.clone(), lockfile.entry_type(id)))
89 })
90 .collect_vec();
91
92 futures::stream::iter(packages.into_iter().map(|(package, entry_type)| {
93 let tree = tree.clone();
94 tokio::spawn(
95 remove_package(package, tree, entry_type)
96 .instrument(tracing::trace_span!("remove_worker")),
97 )
98 }))
99 .buffered(config.max_jobs())
100 .collect::<Vec<_>>()
101 .await;
102
103 lockfile.map_then_flush(|lockfile| {
104 package_ids
105 .iter()
106 .for_each(|package| lockfile.remove_by_id(package));
107
108 Ok::<_, io::Error>(())
109 })?;
110
111 Ok(package_ids)
112}
113
114#[tracing::instrument(
115 name = "Removing",
116 level = "info",
117 skip_all,
118 fields(
119 package = package.name().to_string(),
120 version = package.version().to_string(),
121 ),
122)]
123async fn remove_package(
124 package: LocalPackage,
125 tree: Tree,
126 entry_type: EntryType,
127) -> Result<(), RemoveError> {
128 tree.cleanup(&package, entry_type)?;
129 Ok(())
130}