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