Skip to main content

lux_lib/operations/
uninstall.rs

1use 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::TreeError;
8use crate::{config::Config, tree::Tree};
9use bon::Builder;
10use futures::StreamExt;
11use itertools::Itertools;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
15#[error(transparent)]
16pub enum RemoveError {
17    LuaVersionUnset(#[from] LuaVersionUnset),
18    Io(#[from] io::Error),
19    #[error(transparent)]
20    Tree(#[from] TreeError),
21    #[error(transparent)]
22    FlushLockfile(#[from] FlushLockfileError),
23}
24
25#[derive(Builder)]
26#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
27pub struct Uninstall<'a> {
28    #[builder(field)]
29    packages: Vec<LocalPackageId>,
30    config: &'a Config,
31    progress: Option<Arc<Progress<MultiProgress>>>,
32    tree: Option<Tree>,
33}
34
35impl<'a, State> UninstallBuilder<'a, State>
36where
37    State: uninstall_builder::State,
38{
39    /// Add packages to remove.
40    pub fn packages<I>(self, packages: I) -> Self
41    where
42        I: IntoIterator<Item = LocalPackageId>,
43    {
44        Self {
45            packages: self.packages.into_iter().chain(packages).collect_vec(),
46            ..self
47        }
48    }
49
50    /// Add a package to the set of packages to remove.
51    pub fn package(self, package: LocalPackageId) -> Self {
52        self.packages(std::iter::once(package))
53    }
54}
55
56impl<'a, State> UninstallBuilder<'a, State>
57where
58    State: uninstall_builder::State + uninstall_builder::IsComplete,
59{
60    /// Remove the packages.
61    pub async fn remove(self) -> Result<(), RemoveError> {
62        let args = self._build();
63        let progress = match args.progress {
64            Some(p) => p,
65            None => MultiProgress::new_arc(args.config),
66        };
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, &Arc::clone(&progress)).await
72    }
73}
74
75// TODO: Remove dependencies recursively too!
76async fn remove(
77    package_ids: Vec<LocalPackageId>,
78    tree: Tree,
79    config: &Config,
80    progress: &Progress<MultiProgress>,
81) -> Result<(), RemoveError> {
82    let lockfile = tree.lockfile()?;
83
84    let packages = package_ids
85        .iter()
86        .filter_map(|id| lockfile.get(id))
87        .cloned()
88        .collect_vec();
89
90    futures::stream::iter(packages.into_iter().map(|package| {
91        let bar = progress.map(|p| p.new_bar());
92
93        let tree = tree.clone();
94        tokio::spawn(remove_package(package, tree, bar))
95    }))
96    .buffered(config.max_jobs())
97    .collect::<Vec<_>>()
98    .await;
99
100    lockfile.map_then_flush(|lockfile| {
101        package_ids
102            .iter()
103            .for_each(|package| lockfile.remove_by_id(package));
104
105        Ok::<_, io::Error>(())
106    })?;
107
108    Ok(())
109}
110
111async fn remove_package(
112    package: LocalPackage,
113    tree: Tree,
114    bar: Progress<ProgressBar>,
115) -> Result<(), RemoveError> {
116    bar.map(|p| {
117        p.set_message(format!(
118            "🗑️ Removing {}@{}",
119            package.name(),
120            package.version()
121        ))
122    });
123
124    let rock_layout = tree.installed_rock_layout(&package)?;
125    tokio::fs::remove_dir_all(&rock_layout.etc).await?;
126    tokio::fs::remove_dir_all(&rock_layout.rock_path).await?;
127
128    // Delete the corresponding binaries attached to the current package (located under `{LUX_TREE}/bin/`)
129    for relative_binary_path in package.spec.binaries() {
130        if let Some(binary_file_name) = relative_binary_path.file_name() {
131            let binary_path = tree.bin().join(binary_file_name);
132            if binary_path.is_file() {
133                tokio::fs::remove_file(binary_path).await?;
134            }
135
136            let unwrapped_binary_path = tree.unwrapped_bin().join(binary_file_name);
137            if unwrapped_binary_path.is_file() {
138                tokio::fs::remove_file(unwrapped_binary_path).await?;
139            }
140        }
141    }
142
143    bar.map(|p| p.finish_and_clear());
144    Ok(())
145}