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