lux_lib/operations/
uninstall.rs1use std::io;
2use std::sync::Arc;
3
4use crate::lockfile::{FlushLockfileError, LocalPackage, LocalPackageId};
5use crate::lua_version::{LuaVersion, LuaVersionUnset};
6use crate::progress::{MultiProgress, Progress, ProgressBar};
7use crate::tree::{InstallTree, TreeError};
8use crate::{config::Config, tree::Tree};
9use bon::Builder;
10use futures::StreamExt;
11use itertools::Itertools;
12use miette::Diagnostic;
13use thiserror::Error;
14#[derive(Error, Debug, Diagnostic)]
15#[error(transparent)]
16pub enum RemoveError {
17 #[diagnostic(transparent)]
18 LuaVersionUnset(#[from] LuaVersionUnset),
19 Io(#[from] io::Error),
20 #[error(transparent)]
21 #[diagnostic(transparent)]
22 Tree(#[from] TreeError),
23 #[error(transparent)]
24 #[diagnostic(transparent)]
25 FlushLockfile(#[from] FlushLockfileError),
26}
27
28#[derive(Builder)]
29#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
30pub struct Uninstall<'a> {
31 #[builder(field)]
32 packages: Vec<LocalPackageId>,
33 config: &'a Config,
34 progress: Option<Arc<Progress<MultiProgress>>>,
35 tree: Option<Tree>,
36}
37
38impl<'a, State> UninstallBuilder<'a, State>
39where
40 State: uninstall_builder::State,
41{
42 pub fn packages<I>(self, packages: I) -> Self
44 where
45 I: IntoIterator<Item = LocalPackageId>,
46 {
47 Self {
48 packages: self.packages.into_iter().chain(packages).collect_vec(),
49 ..self
50 }
51 }
52
53 pub fn package(self, package: LocalPackageId) -> Self {
55 self.packages(std::iter::once(package))
56 }
57}
58
59impl<'a, State> UninstallBuilder<'a, State>
60where
61 State: uninstall_builder::State + uninstall_builder::IsComplete,
62{
63 pub async fn remove(self) -> Result<(), RemoveError> {
65 let args = self._build();
66 let progress = match args.progress {
67 Some(p) => p,
68 None => MultiProgress::new_arc(args.config),
69 };
70 let tree = args.tree.unwrap_or(
71 args.config
72 .user_tree(LuaVersion::from(args.config)?.clone())?,
73 );
74 remove(args.packages, tree, args.config, &Arc::clone(&progress)).await
75 }
76}
77
78async fn remove(
80 package_ids: Vec<LocalPackageId>,
81 tree: Tree,
82 config: &Config,
83 progress: &Progress<MultiProgress>,
84) -> Result<(), RemoveError> {
85 let lockfile = tree.lockfile()?;
86
87 let packages = package_ids
88 .iter()
89 .filter_map(|id| lockfile.get(id))
90 .cloned()
91 .collect_vec();
92
93 futures::stream::iter(packages.into_iter().map(|package| {
94 let bar = progress.map(|p| p.new_bar());
95
96 let tree = tree.clone();
97 tokio::spawn(remove_package(package, tree, bar))
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(())
112}
113
114async fn remove_package(
115 package: LocalPackage,
116 tree: Tree,
117 bar: Progress<ProgressBar>,
118) -> Result<(), RemoveError> {
119 bar.map(|p| {
120 p.set_message(format!(
121 "🗑️ Removing {}@{}",
122 package.name(),
123 package.version()
124 ))
125 });
126
127 let rock_layout = tree.installed_rock_layout(&package)?;
128 tokio::fs::remove_dir_all(&rock_layout.etc).await?;
129 tokio::fs::remove_dir_all(&rock_layout.rock_path).await?;
130
131 for relative_binary_path in package.spec.binaries() {
133 if let Some(binary_file_name) = relative_binary_path.file_name() {
134 let binary_path = tree.bin().join(binary_file_name);
135 if binary_path.is_file() {
136 tokio::fs::remove_file(binary_path).await?;
137 }
138
139 let unwrapped_binary_path = tree.unwrapped_bin().join(binary_file_name);
140 if unwrapped_binary_path.is_file() {
141 tokio::fs::remove_file(unwrapped_binary_path).await?;
142 }
143 }
144 }
145
146 bar.map(|p| p.finish_and_clear());
147 Ok(())
148}