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