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::{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    /// Add packages to remove.
43    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    /// Add a package to the set of packages to remove.
54    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    /// Remove the packages.
64    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
78// TODO: Remove dependencies recursively too!
79async 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    // Delete the corresponding binaries attached to the current package (located under `{LUX_TREE}/bin/`)
132    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}