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::{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::{span, 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<(), 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<(), RemoveError> {
81 let lockfile = tree.lockfile()?;
82
83 let packages = package_ids
84 .iter()
85 .filter_map(|id| lockfile.get(id))
86 .cloned()
87 .collect_vec();
88
89 futures::stream::iter(packages.into_iter().map(|package| {
90 let tree = tree.clone();
91 tokio::spawn(
92 remove_package(package, tree).instrument(tracing::trace_span!("remove_worker")),
93 )
94 }))
95 .buffered(config.max_jobs())
96 .collect::<Vec<_>>()
97 .await;
98
99 lockfile.map_then_flush(|lockfile| {
100 package_ids
101 .iter()
102 .for_each(|package| lockfile.remove_by_id(package));
103
104 Ok::<_, io::Error>(())
105 })?;
106
107 Ok(())
108}
109
110async fn remove_package(package: LocalPackage, tree: Tree) -> Result<(), RemoveError> {
111 let span = span!(
112 tracing::Level::INFO,
113 "Removing",
114 package = package.name().to_string(),
115 version = package.version().to_string(),
116 );
117 let _enter = span.enter();
118
119 let rock_layout = tree.installed_rock_layout(&package)?;
120 fs::tokio::remove_dir_all(&rock_layout.etc).await?;
121 fs::tokio::remove_dir_all(&rock_layout.rock_path).await?;
122
123 for relative_binary_path in package.spec.binaries() {
124 if let Some(binary_file_name) = relative_binary_path.file_name() {
125 let binary_path = tree.bin().join(binary_file_name);
126 if binary_path.is_file() {
127 fs::tokio::remove_file(binary_path).await?;
128 }
129
130 let unwrapped_binary_path = tree.unwrapped_bin().join(binary_file_name);
131 if unwrapped_binary_path.is_file() {
132 fs::tokio::remove_file(unwrapped_binary_path).await?;
133 }
134 }
135 }
136 Ok(())
137}