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