lux_cli/
install_rockspec.rs1use miette::miette;
2use std::path::PathBuf;
3
4use clap::Args;
5use lux_lib::{
6 build::{self, BuildBehaviour},
7 config::Config,
8 lockfile::{OptState, PinnedState},
9 lua_installation::LuaInstallation,
10 lua_rockspec::{BuildBackendSpec, RemoteLuaRockspec},
11 luarocks::luarocks_installation::LuaRocksInstallation,
12 operations::{Install, PackageInstallSpec},
13 rockspec::{LuaVersionCompatibility, Rockspec},
14 tree::{self, InstallTree},
15};
16
17use miette::{IntoDiagnostic, Result};
18
19#[derive(Args, Default)]
20pub struct InstallRockspec {
21 rockspec_path: PathBuf,
23
24 #[arg(long)]
26 pin: bool,
27}
28
29pub async fn install_rockspec(data: InstallRockspec, config: Config) -> Result<()> {
31 let pin = PinnedState::from(data.pin);
32 let path = data.rockspec_path;
33
34 if path
35 .extension()
36 .map(|ext| ext != "rockspec")
37 .unwrap_or(true)
38 {
39 return Err(miette!("Provided path is not a valid rockspec!"));
40 }
41
42 let content = std::fs::read_to_string(path).into_diagnostic()?;
43 let rockspec = RemoteLuaRockspec::new(&content)?;
44 let lua_version = rockspec.lua_version_matches(&config)?;
45 let lua = LuaInstallation::new(&lua_version, &config).await?;
46 let tree = config.user_tree(lua_version)?;
47
48 let build_dependencies = rockspec.build_dependencies().current_platform();
51
52 let build_dependencies_to_install = build_dependencies
53 .iter()
54 .filter(|dep| {
55 !matches!(
57 dep.name().to_string().as_str(),
58 "luarocks-build-rust-mlua" | "luarocks-build-treesitter-parser"
59 )
60 })
61 .filter(|dep| {
62 tree.match_rocks(dep.package_req())
63 .is_ok_and(|rock_match| rock_match.is_found())
64 })
65 .map(|dep| {
66 PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::Entrypoint)
67 .build_behaviour(BuildBehaviour::NoForce)
68 .pin(pin)
69 .opt(OptState::Required)
70 .maybe_source(dep.source().clone())
71 .build()
72 })
73 .collect();
74
75 Install::new(&config)
76 .packages(build_dependencies_to_install)
77 .tree(tree.build_tree(&config)?)
78 .install()
79 .await?;
80
81 let dependencies = rockspec.dependencies().current_platform();
82
83 let mut dependencies_to_install = Vec::new();
84 for dep in dependencies {
85 let rock_match = tree.match_rocks(dep.package_req())?;
86 if !rock_match.is_found() {
87 let dep =
88 PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::DependencyOnly)
89 .build_behaviour(BuildBehaviour::NoForce)
90 .pin(pin)
91 .opt(OptState::Required)
92 .maybe_source(dep.source().clone())
93 .build();
94 dependencies_to_install.push(dep);
95 }
96 }
97
98 Install::new(&config)
99 .packages(dependencies_to_install)
100 .tree(tree.clone())
101 .install()
102 .await?;
103
104 if let Some(BuildBackendSpec::LuaRock(_)) = &rockspec.build().current_platform().build_backend {
105 let build_tree = tree.build_tree(&config)?;
106 let luarocks = LuaRocksInstallation::new(&config, build_tree)?;
107 luarocks.ensure_installed(&lua).await?;
108 }
109
110 build::Build::new()
111 .rockspec(&rockspec)
112 .tree(&tree)
113 .lua(&lua)
114 .entry_type(tree::EntryType::Entrypoint)
115 .config(&config)
116 .pin(pin)
117 .behaviour(BuildBehaviour::Force)
118 .build()
119 .await?;
120
121 Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126
127 use super::*;
128
129 use assert_fs::{
130 prelude::{FileWriteStr, PathChild, PathCreateDir},
131 TempDir,
132 };
133
134 use lux_lib::{
135 config::ConfigBuilder, lua_installation::detect_installed_lua_version,
136 lua_version::LuaVersion,
137 };
138
139 #[tokio::test]
140 async fn test_install_rockspec_from_vendored() {
141 let vendor_dir = TempDir::new().unwrap();
143 let foo_dir = vendor_dir.child("foo@1.0.0-1");
144 foo_dir.create_dir_all().unwrap();
145 let foo_rockspec = vendor_dir.child("foo-1.0.0-1.rockspec");
146 foo_rockspec
147 .write_str(
148 r#"
149 package = 'foo'
150 version = '1.0.0-1'
151 source = {
152 url = 'https://github.com/lumen-oss/luarocks-stub',
153 }
154 "#,
155 )
156 .unwrap();
157 let bar_dir = vendor_dir.child("bar@2.0.0-2");
158 bar_dir.create_dir_all().unwrap();
159 let bar_rockspec = vendor_dir.child("bar-2.0.0-2.rockspec");
160 bar_rockspec
161 .write_str(
162 r#"
163 package = 'bar'
164 version = '2.0.0-2'
165 source = {
166 url = 'https://github.com/lumen-oss/luarocks-stub',
167 }
168 "#,
169 )
170 .unwrap();
171 let baz_dir = vendor_dir.child("baz@2.0.0-1");
172 baz_dir.create_dir_all().unwrap();
173 let baz_rockspec = vendor_dir.child("baz-2.0.0-1.rockspec");
174 baz_rockspec
175 .write_str(
176 r#"
177 package = 'baz'
178 version = '2.0.0-1'
179 source = {
180 url = 'https://github.com/lumen-oss/luarocks-stub',
181 }
182 "#,
183 )
184 .unwrap();
185 let test_rock_dir = vendor_dir.child("test_rock@scm-1");
186 test_rock_dir.create_dir_all().unwrap();
187 let rockspec_content = r#"
188 package = 'test_rock'
189 version = 'scm-1'
190 source = {
191 url = 'https://github.com/lumen-oss/luarocks-stub',
192 }
193 dependencies = {
194 'foo >= 1.0.0',
195 'bar',
196 'baz == 2.0.0',
197 }
198 "#;
199 let temp_dir = TempDir::new().unwrap();
200 let rockspec = temp_dir.child("test_rock-scm-1.rockspec");
201 rockspec.write_str(rockspec_content).unwrap();
202 let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
203 let config = ConfigBuilder::new()
204 .unwrap()
205 .vendor_dir(Some(vendor_dir.to_path_buf()))
206 .lua_version(lua_version)
207 .user_tree(Some(temp_dir.to_path_buf()))
208 .build()
209 .unwrap();
210 install_rockspec(
211 InstallRockspec {
212 rockspec_path: rockspec.to_path_buf(),
213 pin: false,
214 },
215 config,
216 )
217 .await
218 .unwrap()
219 }
220}