1use crate::build::backend::BuildInfo;
2use crate::build::backend::RunBuildArgs;
3use crate::fs;
4use crate::lua_rockspec::LuaVersionError;
5use crate::rockspec::LuaVersionCompatibility;
6use crate::rockspec::Rockspec;
7use crate::tree;
8use crate::tree::InstallTree;
9use std::path::Path;
10
11use crate::{
12 config::Config,
13 lockfile::LockedPackageSpec,
14 luarocks::luarocks_installation::{ExecLuaRocksError, LuaRocksError, LuaRocksInstallation},
15 luarocks::{install_rock_manifest, InstallRockManifestError, RockManifestSources},
16};
17
18use crate::fs::tempfile::tempdir;
19use miette::Diagnostic;
20use thiserror::Error;
21
22#[derive(Error, Debug, Diagnostic)]
23#[non_exhaustive]
24pub enum Error {
25 #[error(transparent)]
26 #[diagnostic(transparent)]
27 Fs(#[from] fs::Error),
28 #[error("error instantiating luarocks compatibility layer")]
29 #[diagnostic(forward(0))]
30 LuaRocksError(#[from] LuaRocksError),
31 #[error("error running 'luarocks make'")]
32 #[diagnostic(forward(0))]
33 ExecLuaRocksError(#[from] ExecLuaRocksError),
34 #[error(transparent)]
35 #[diagnostic(transparent)]
36 Tree(#[from] tree::Error),
37 #[error(transparent)]
38 #[diagnostic(transparent)]
39 InstallRockManifest(#[from] InstallRockManifestError),
40 #[error(transparent)]
41 #[diagnostic(transparent)]
42 Rockspec(Box<dyn Diagnostic + Send + Sync>),
43 #[error("error installing luarocks compatibility layer")]
44 #[diagnostic(forward(0))]
45 LuaVersion(#[from] LuaVersionError),
46}
47
48#[tracing::instrument(name = "Delegating to luarocks",
49 skip_all,
50 level = "info"
51 fields(backend = build_backend_name),
52)]
53pub(crate) async fn build<R: Rockspec, T: InstallTree>(
54 build_backend_name: &str,
55 rockspec: &R,
56 args: RunBuildArgs<'_, T>,
57) -> Result<BuildInfo, Error> {
58 let package = args.package;
59 let lua = args.lua;
60 let config = args.config;
61 let build_dir = args.build_dir;
62 let tree = args.tree;
63
64 let rockspec_temp_dir = tempdir()?;
65 let rockspec_file = rockspec_temp_dir.path().join(format!(
66 "{}-{}.rockspec",
67 rockspec.package(),
68 rockspec.version()
69 ));
70 fs::tokio::write(
71 &rockspec_file,
72 rockspec
73 .to_lua_remote_rockspec_string()
74 .map_err(|err| Error::Rockspec(Box::new(err)))?,
75 )
76 .await?;
77 let luarocks = LuaRocksInstallation::new(config, tree.build_tree(config)?)?;
78 let luarocks_tree = tempdir()?;
79 luarocks
80 .make(&rockspec_file, build_dir, luarocks_tree.path(), lua)
81 .await?;
82 install(rockspec, luarocks_tree.path(), tree, package, config).await
83}
84
85async fn install<R: Rockspec, T: InstallTree>(
86 rockspec: &R,
87 luarocks_tree: &Path,
88 tree: &T,
89 package: &LockedPackageSpec,
90 config: &Config,
91) -> Result<BuildInfo, Error> {
92 let layout = tree.layout_for(package);
93 let lua_version = rockspec.lua_version_matches(config)?;
94 fs::tokio::create_dir_all(tree.bin()).await?;
95 let lua_version = lua_version.version_compatibility_str();
96 let package_dir = luarocks_tree
97 .join("lib")
98 .join("luarocks")
99 .join(format!("rocks-{lua_version}"))
100 .join(format!("{}", rockspec.package()))
101 .join(format!("{}", rockspec.version()));
102 let sources = RockManifestSources {
103 root: package_dir,
104 lib: luarocks_tree.join("lib").join("lua").join(&lua_version),
105 lua: luarocks_tree.join("share").join("lua").join(&lua_version),
106 bin: luarocks_tree.join("bin"),
107 };
108 install_rock_manifest(sources, &layout, &tree.bin()).await?;
109 Ok(BuildInfo::default())
110}
111
112#[cfg(test)]
113mod tests {
114 use std::path::Path;
115
116 use assert_fs::TempDir;
117
118 use crate::{
119 config::ConfigBuilder,
120 lockfile::{LockConstraint, LockedPackageSpec, OptState, PinnedState},
121 lua_rockspec::RemoteLuaRockspec,
122 lua_version::LuaVersion,
123 rockspec::Rockspec,
124 tree::InstallTree,
125 };
126
127 use super::install;
128
129 fn write_file(root: &Path, relative: &str, content: &str) {
130 let path = root.join(relative);
131 if let Some(parent) = path.parent() {
132 std::fs::create_dir_all(parent).unwrap();
133 }
134 std::fs::write(path, content).unwrap();
135 }
136
137 #[tokio::test]
138 async fn installs_all_build_artifacts() {
139 let rockspec = RemoteLuaRockspec::new(
140 r#"
141rockspec_format = "3.0"
142package = "test-luarocks-backend"
143version = "1.0-1"
144source = { url = "https://github.com/lumen-oss/luarocks-stub" }
145build = { type = "test-custom" }
146"#,
147 )
148 .unwrap();
149 let tree_dir = TempDir::new().unwrap();
150 let config = ConfigBuilder::new()
151 .unwrap()
152 .lua_version(Some(LuaVersion::Lua51))
153 .user_tree(Some(tree_dir.path().to_path_buf()))
154 .build()
155 .unwrap();
156 let tree = config.user_tree(LuaVersion::Lua51).unwrap();
157 let package = LockedPackageSpec::new(
158 rockspec.package(),
159 rockspec.version(),
160 LockConstraint::Unconstrained,
161 Vec::new(),
162 Vec::new(),
163 &PinnedState::default(),
164 &OptState::default(),
165 );
166 tree.prepare(&package).unwrap();
167
168 let luarocks_tree = TempDir::new().unwrap();
169 let package_dir = luarocks_tree
170 .path()
171 .join("lib/luarocks/rocks-5.1/test-luarocks-backend/1.0-1");
172 write_file(
173 &package_dir,
174 "rock_manifest",
175 r#"
176rock_manifest = {
177 lib = { ["test_luarocks_backend.so"] = "00000000000000000000000000000000" },
178 lua = { ["test_luarocks_backend/init.lua"] = "00000000000000000000000000000000" },
179 bin = { tool = "00000000000000000000000000000000" },
180 doc = { ["README.md"] = "00000000000000000000000000000000" },
181 conf = { ["config.toml"] = "00000000000000000000000000000000" },
182 plugin = { ["foo.lua"] = "00000000000000000000000000000000" },
183 ["test-luarocks-backend-1.0-1.rockspec"] = "00000000000000000000000000000000",
184}
185"#,
186 );
187 write_file(
188 luarocks_tree.path(),
189 "lib/lua/5.1/test_luarocks_backend.so",
190 "",
191 );
192 write_file(
193 luarocks_tree.path(),
194 "share/lua/5.1/test_luarocks_backend/init.lua",
195 "return true",
196 );
197 write_file(
198 luarocks_tree.path(),
199 "share/lua/5.1/unlisted.lua",
200 "return true",
201 );
202 write_file(luarocks_tree.path(), "bin/tool", "#!/bin/sh\n");
203 write_file(&package_dir, "doc/README.md", "docs");
204 write_file(&package_dir, "conf/config.toml", "conf");
205 write_file(&package_dir, "plugin/foo.lua", "return true");
206 write_file(
207 &package_dir,
208 "test-luarocks-backend-1.0-1.rockspec",
209 "rockspec",
210 );
211 write_file(&package_dir, "unlisted.txt", "not in the manifest");
212
213 install(&rockspec, luarocks_tree.path(), &tree, &package, &config)
214 .await
215 .unwrap();
216
217 let layout = tree.layout_for(&package);
218 assert!(layout.lib.join("test_luarocks_backend.so").is_file());
219 assert!(layout
220 .src
221 .join("test_luarocks_backend")
222 .join("init.lua")
223 .is_file());
224 assert!(tree.bin().join("tool").is_file());
225 assert!(layout.doc.join("README.md").is_file());
226 assert!(layout.conf.join("config.toml").is_file());
227 assert!(layout.etc.join("plugin").join("foo.lua").is_file());
228 assert!(layout
229 .etc
230 .join("test-luarocks-backend-1.0-1.rockspec")
231 .is_file());
232 assert!(!layout.etc.join("rock_manifest").exists());
233 assert!(!layout.etc.join("unlisted.txt").exists());
234 assert!(!layout.src.join("unlisted.lua").exists());
235 }
236}