Skip to main content

lux_lib/tree/
dist.rs

1use std::{collections::HashMap, io, path::PathBuf};
2
3use super::{InstallTree, RockLayout, Tree, TreeError};
4use crate::{
5    config::{tree::RockLayoutConfig, Config},
6    fs,
7    lockfile::{LocalPackage, Lockfile, ReadOnly},
8    lua_version::LuaVersion,
9    package::{PackageName, PackageVersion},
10    tree::mk_rock_layout,
11};
12use miette::{Diagnostic, Result};
13use thiserror::Error;
14
15#[derive(Error, Debug, Diagnostic)]
16#[non_exhaustive]
17#[error(
18    r#"cannot install conflicting packages in flat tree:
19package: {name}
20version A: {version_a}
21version B: {version_b}
22"#
23)]
24struct ConflictingPackageError {
25    name: PackageName,
26    version_a: PackageVersion,
27    version_b: PackageVersion,
28}
29
30/// A staging tree with a flat hierarchy for distribution outside of Lux.
31/// When dropped, tries to remove all build artifacts that are not meant to be distributed,
32/// as well as the `etc` directory (failing silently if any artifacts cannot be removed).
33/// Unlike a regular Lux tree, **conflicting packages are not supported.**
34#[derive(Clone, Debug)]
35pub struct FlatDistTree(Tree);
36
37impl FlatDistTree {
38    pub fn new(root: PathBuf, version: LuaVersion, config: &Config) -> Result<Self, TreeError> {
39        let version_dir = root.join(version.to_string());
40        let test_tree_dir = version_dir.join("test_dependencies");
41        let build_tree_dir = version_dir.join("build_dependencies");
42        let tree = Tree::new_with_paths(root, test_tree_dir, build_tree_dir, version, config)?;
43        Ok(Self(tree))
44    }
45
46    fn guard_no_conflicting_package(&self, package: &LocalPackage) -> Result<(), io::Error> {
47        let lockfile = self.lockfile().map_err(io::Error::other)?;
48        match lockfile.has_rock(&package.clone().into_package_req(), None) {
49            Some(existing_package) => {
50                if existing_package.version() == package.version() {
51                    Ok(())
52                } else {
53                    Err(io::Error::other(ConflictingPackageError {
54                        name: package.name().clone(),
55                        version_a: existing_package.version().clone(),
56                        version_b: package.version().clone(),
57                    }))
58                }
59            }
60            None => Ok(()),
61        }
62    }
63}
64
65impl Drop for FlatDistTree {
66    fn drop(&mut self) {
67        let build_tree_dir = &self.0.build_tree_dir;
68        if build_tree_dir.is_dir() {
69            let _ = fs::sync::remove_dir_all(build_tree_dir);
70        }
71        let package_rockspec = self.root().join("package.rockspec");
72        if package_rockspec.is_file() {
73            let _ = fs::sync::remove_file(&package_rockspec);
74        }
75        let lockfile = self.lockfile_path();
76        if lockfile.is_file() {
77            let _ = fs::sync::remove_file(&lockfile);
78        }
79        let etc_dir = self.root().join("etc");
80        if etc_dir.is_dir() {
81            let _ = fs::sync::remove_dir_all(etc_dir);
82        }
83    }
84}
85
86impl InstallTree for FlatDistTree {
87    fn version(&self) -> &LuaVersion {
88        self.0.version()
89    }
90
91    fn root(&self) -> PathBuf {
92        self.0.root()
93    }
94
95    fn root_for(&self, _package: &LocalPackage) -> PathBuf {
96        self.0.root()
97    }
98
99    fn bin(&self) -> PathBuf {
100        self.0.bin()
101    }
102
103    fn unwrapped_bin(&self) -> PathBuf {
104        self.0.unwrapped_bin()
105    }
106
107    fn entrypoint(&self, package: &LocalPackage) -> io::Result<RockLayout> {
108        self.guard_no_conflicting_package(package)?;
109        Ok(mk_rock_layout(
110            &self.root(),
111            &self.bin(),
112            &self.root_for(package),
113            package,
114            &self.0.entrypoint_layout,
115        ))
116    }
117
118    fn dependency(&self, package: &LocalPackage) -> io::Result<RockLayout> {
119        self.guard_no_conflicting_package(package)?;
120        Ok(mk_rock_layout(
121            &self.root(),
122            &self.bin(),
123            &self.root_for(package),
124            package,
125            &RockLayoutConfig::default(),
126        ))
127    }
128
129    fn lockfile(&self) -> Result<Lockfile<ReadOnly>, TreeError> {
130        self.0.lockfile()
131    }
132
133    fn lockfile_path(&self) -> PathBuf {
134        self.0.lockfile_path()
135    }
136
137    fn build_tree(&self, config: &Config) -> Result<Tree, TreeError> {
138        self.0.build_tree(config)
139    }
140
141    fn test_tree(&self, config: &Config) -> Result<Tree, TreeError> {
142        self.0.test_tree(config)
143    }
144
145    fn installed_rock_layout(&self, package: &LocalPackage) -> Result<RockLayout, TreeError> {
146        let lockfile = self.lockfile()?;
147        let layout_config = if lockfile.is_entrypoint(&package.id()) {
148            self.0.entrypoint_layout.clone()
149        } else {
150            RockLayoutConfig::default()
151        };
152        Ok(mk_rock_layout(
153            &self.root(),
154            &self.bin(),
155            &self.root_for(package),
156            package,
157            &layout_config,
158        ))
159    }
160
161    fn list(&self) -> Result<HashMap<PackageName, Vec<LocalPackage>>, TreeError> {
162        self.0.list()
163    }
164
165    fn match_rocks(
166        &self,
167        req: &crate::package::PackageReq,
168    ) -> Result<super::RockMatches, TreeError> {
169        self.0.match_rocks(req)
170    }
171}