Skip to main content

lux_lib/tree/
dist.rs

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