Skip to main content

lux_lib/operations/
uninstall.rs

1use std::io;
2
3use crate::fs;
4use crate::lockfile::{FlushLockfileError, LocalPackage, LocalPackageId};
5use crate::lua_version::{LuaVersion, LuaVersionUnset};
6use crate::tree::{EntryType, 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::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    /// Add packages to remove.
44    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    /// Add a package to the set of packages to remove.
55    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    /// Remove the packages.
65    pub async fn remove(self) -> Result<Vec<LocalPackageId>, 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
75// TODO: Remove dependencies recursively too!
76async fn remove(
77    package_ids: Vec<LocalPackageId>,
78    tree: Tree,
79    config: &Config,
80) -> Result<Vec<LocalPackageId>, RemoveError> {
81    let lockfile = tree.lockfile()?;
82
83    let packages = package_ids
84        .iter()
85        .filter_map(|id| {
86            lockfile
87                .get(id)
88                .map(|package| (package.clone(), lockfile.entry_type(id)))
89        })
90        .collect_vec();
91
92    futures::stream::iter(packages.into_iter().map(|(package, entry_type)| {
93        let tree = tree.clone();
94        tokio::spawn(
95            remove_package(package, tree, entry_type)
96                .instrument(tracing::trace_span!("remove_worker")),
97        )
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(package_ids)
112}
113
114#[tracing::instrument(
115    name = "Removing",
116    level = "info",
117    skip_all,
118    fields(
119        package = package.name().to_string(),
120        version = package.version().to_string(),
121    ),
122)]
123async fn remove_package(
124    package: LocalPackage,
125    tree: Tree,
126    entry_type: EntryType,
127) -> Result<(), RemoveError> {
128    tree.cleanup(&package, entry_type)?;
129    Ok(())
130}