Skip to main content

lux_lib/operations/
install_rockspec.rs

1use bon::Builder;
2use miette::Diagnostic;
3use thiserror::Error;
4
5use crate::{
6    build::{self, BuildBehaviour, BuildError},
7    config::Config,
8    lockfile::{LocalPackage, OptState, PinnedState},
9    lua_installation::{LuaInstallation, LuaInstallationError},
10    lua_rockspec::{BuildBackendSpec, LuaVersionError, RemoteLuaRockspec},
11    luarocks::luarocks_installation::{LuaRocksError, LuaRocksInstallError, LuaRocksInstallation},
12    operations::{Install, InstallError, PackageInstallSpec},
13    rockspec::{LuaVersionCompatibility, Rockspec},
14    tree::{self, InstallTree, TreeError},
15};
16
17#[derive(Debug, Error, Diagnostic)]
18#[error(transparent)]
19#[non_exhaustive]
20pub enum InstallRockspecError {
21    #[diagnostic(transparent)]
22    LuaInstallation(#[from] LuaInstallationError),
23    #[diagnostic(transparent)]
24    LuaVersion(#[from] LuaVersionError),
25    #[diagnostic(transparent)]
26    Tree(#[from] TreeError),
27    #[diagnostic(transparent)]
28    Install(#[from] InstallError),
29    #[diagnostic(transparent)]
30    LuaRocks(#[from] LuaRocksError),
31    #[diagnostic(transparent)]
32    LuaRocksInstall(#[from] LuaRocksInstallError),
33    #[diagnostic(transparent)]
34    Build(#[from] BuildError),
35}
36
37/// Installs a Lua RockSpec into a [`Tree`].
38#[derive(Builder)]
39#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
40pub struct InstallRockspec<'a, T>
41where
42    T: InstallTree,
43{
44    rockspec: RemoteLuaRockspec,
45
46    pin: PinnedState,
47
48    config: &'a Config,
49
50    tree: &'a T,
51}
52
53impl<
54        T: InstallTree + Sync + Send + Clone + 'static,
55        State: install_rockspec_builder::State + install_rockspec_builder::IsComplete,
56    > InstallRockspecBuilder<'_, T, State>
57{
58    pub async fn install(self) -> Result<LocalPackage, InstallRockspecError> {
59        let args = self._build();
60        let rockspec = args.rockspec;
61        let pin = args.pin;
62        let config = args.config;
63        let tree = args.tree;
64
65        let lua_version = rockspec.lua_version_matches(config)?;
66        let lua = LuaInstallation::new(&lua_version, config).await?;
67
68        // Ensure all dependencies and build dependencies are installed first
69
70        let build_dependencies = rockspec.build_dependencies().current_platform();
71
72        let build_dependencies_to_install = build_dependencies
73            .iter()
74            .filter(|dep| {
75                // Exclude luarocks build backends that we have implemented in lux
76                !matches!(
77                    dep.name().to_string().as_str(),
78                    "luarocks-build-rust-mlua" | "luarocks-build-treesitter-parser"
79                )
80            })
81            .filter(|dep| {
82                tree.match_rocks(dep.package_req())
83                    .is_ok_and(|rock_match| rock_match.is_found())
84            })
85            .map(|dep| {
86                PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::Entrypoint)
87                    .build_behaviour(BuildBehaviour::NoForce)
88                    .pin(pin)
89                    .opt(OptState::Required)
90                    .maybe_source(dep.source().clone())
91                    .build()
92            })
93            .collect();
94
95        Install::new(config)
96            .packages(build_dependencies_to_install)
97            .tree(tree.build_tree(config)?)
98            .install()
99            .await?;
100
101        let dependencies = rockspec.dependencies().current_platform();
102
103        let mut dependencies_to_install = Vec::new();
104        for dep in dependencies {
105            let rock_match = tree.match_rocks(dep.package_req())?;
106            if !rock_match.is_found() {
107                let dep = PackageInstallSpec::new(
108                    dep.package_req().clone(),
109                    tree::EntryType::DependencyOnly,
110                )
111                .build_behaviour(BuildBehaviour::NoForce)
112                .pin(pin)
113                .opt(OptState::Required)
114                .maybe_source(dep.source().clone())
115                .build();
116                dependencies_to_install.push(dep);
117            }
118        }
119
120        let dependencies = Install::new(config)
121            .packages(dependencies_to_install)
122            .tree(tree.clone())
123            .install()
124            .await?;
125
126        if let Some(BuildBackendSpec::LuaRock(_)) =
127            &rockspec.build().current_platform().build_backend
128        {
129            let build_tree = tree.build_tree(config)?;
130            let luarocks = LuaRocksInstallation::new(config, build_tree)?;
131            luarocks.ensure_installed(&lua).await?;
132        }
133
134        let package = build::Build::new()
135            .rockspec(&rockspec)
136            .tree(tree)
137            .lua(&lua)
138            .entry_type(tree::EntryType::Entrypoint)
139            .config(config)
140            .pin(pin)
141            .behaviour(BuildBehaviour::Force)
142            .build()
143            .await?;
144
145        let lockfile = tree.lockfile()?;
146        let mut lockfile = lockfile.write_guard();
147        lockfile.add_entrypoint(&package);
148        for dep in dependencies {
149            lockfile.add_dependency(&package, &dep);
150        }
151        Ok(package)
152    }
153}