Skip to main content

lux_lib/build/
luarocks.rs

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}