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::{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    /// 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<(), 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<(), 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}