Skip to main content

lux_lib/operations/
uninstall.rs

1use 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    /// Add packages to remove.
41    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    /// Add a package to the set of packages to remove.
52    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    /// Remove the packages.
62    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
72// TODO: Remove dependencies recursively too!
73async 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}