Skip to main content

lux_lib/tree/
dist.rs

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