lux_cli/
install_rockspec.rs

1use 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_rockspec::{BuildBackendSpec, RemoteLuaRockspec},
11    luarocks::luarocks_installation::LuaRocksInstallation,
12    operations::{Install, PackageInstallSpec},
13    progress::MultiProgress,
14    rockspec::{LuaVersionCompatibility, Rockspec},
15    tree,
16};
17
18#[derive(Args, Default)]
19pub struct InstallRockspec {
20    /// The path to the RockSpec file to install
21    rockspec_path: PathBuf,
22
23    /// Whether to pin the installed package and dependencies.
24    #[arg(long)]
25    pin: bool,
26}
27
28/// Install a rockspec into the user tree.
29pub async fn install_rockspec(data: InstallRockspec, config: Config) -> Result<()> {
30    let pin = PinnedState::from(data.pin);
31    let path = data.rockspec_path;
32
33    if path
34        .extension()
35        .map(|ext| ext != "rockspec")
36        .unwrap_or(true)
37    {
38        return Err(eyre!("Provided path is not a valid rockspec!"));
39    }
40    let content = std::fs::read_to_string(path)?;
41    let rockspec = RemoteLuaRockspec::new(&content)?;
42    let lua_version = rockspec.lua_version_matches(&config)?;
43    let tree = config.user_tree(lua_version)?;
44
45    let progress_arc = MultiProgress::new_arc();
46    let progress = Arc::clone(&progress_arc);
47
48    // Ensure all dependencies and build dependencies are installed first
49
50    let build_dependencies = rockspec.build_dependencies().current_platform();
51
52    let build_dependencies_to_install = build_dependencies
53        .iter()
54        .filter(|dep| {
55            tree.match_rocks(dep.package_req())
56                .is_ok_and(|rock_match| rock_match.is_found())
57        })
58        .map(|dep| {
59            PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::Entrypoint)
60                .build_behaviour(BuildBehaviour::NoForce)
61                .pin(pin)
62                .opt(OptState::Required)
63                .maybe_source(dep.source().clone())
64                .build()
65        })
66        .collect();
67
68    Install::new(&config)
69        .packages(build_dependencies_to_install)
70        .tree(tree.build_tree(&config)?)
71        .progress(progress_arc.clone())
72        .install()
73        .await?;
74
75    let dependencies = rockspec.dependencies().current_platform();
76
77    let dependencies_to_install = dependencies
78        .iter()
79        .filter(|dep| {
80            tree.match_rocks(dep.package_req())
81                .is_ok_and(|rock_match| rock_match.is_found())
82        })
83        .map(|dep| {
84            PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::DependencyOnly)
85                .build_behaviour(BuildBehaviour::NoForce)
86                .pin(pin)
87                .opt(OptState::Required)
88                .maybe_source(dep.source().clone())
89                .build()
90        })
91        .collect();
92
93    Install::new(&config)
94        .packages(dependencies_to_install)
95        .tree(tree.clone())
96        .progress(progress_arc.clone())
97        .install()
98        .await?;
99
100    if let Some(BuildBackendSpec::LuaRock(_)) = &rockspec.build().current_platform().build_backend {
101        let build_tree = tree.build_tree(&config)?;
102        let luarocks = LuaRocksInstallation::new(&config, build_tree)?;
103        let bar = progress.map(|p| p.new_bar());
104        luarocks.ensure_installed(&bar).await?;
105    }
106
107    build::Build::new(
108        &rockspec,
109        &tree,
110        tree::EntryType::Entrypoint,
111        &config,
112        &progress.map(|p| p.new_bar()),
113    )
114    .pin(pin)
115    .behaviour(BuildBehaviour::Force)
116    .build()
117    .await?;
118
119    Ok(())
120}