1use std::{collections::HashMap, io, path::PathBuf};
2
3use super::{EntryType, InstallTree, RockLayout, Tree, TreeError};
4use crate::{
5 config::Config,
6 fs,
7 lockfile::{LocalPackage, 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#[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: &LocalPackage) -> 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: &LocalPackage) -> 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: &LocalPackage) -> 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: &LocalPackage, _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<LocalPackage>>, 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}