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"
79                        | "luarocks-build-rust-binary"
80                        | "luarocks-build-treesitter-parser"
81                )
82            })
83            .filter(|dep| {
84                tree.match_rocks(dep.package_req())
85                    .is_ok_and(|rock_match| rock_match.is_found())
86            })
87            .map(|dep| {
88                PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::Entrypoint)
89                    .build_behaviour(BuildBehaviour::NoForce)
90                    .pin(pin)
91                    .opt(OptState::Required)
92                    .maybe_source(dep.source().clone())
93                    .build()
94            })
95            .collect();
96
97        Install::new(config)
98            .packages(build_dependencies_to_install)
99            .tree(tree.build_tree(config)?)
100            .install()
101            .await?;
102
103        let dependencies = rockspec.dependencies().current_platform();
104
105        let mut dependencies_to_install = Vec::new();
106        for dep in dependencies {
107            let rock_match = tree.match_rocks(dep.package_req())?;
108            if !rock_match.is_found() {
109                let dep = PackageInstallSpec::new(
110                    dep.package_req().clone(),
111                    tree::EntryType::DependencyOnly,
112                )
113                .build_behaviour(BuildBehaviour::NoForce)
114                .pin(pin)
115                .opt(OptState::Required)
116                .maybe_source(dep.source().clone())
117                .build();
118                dependencies_to_install.push(dep);
119            }
120        }
121
122        let dependencies = Install::new(config)
123            .packages(dependencies_to_install)
124            .tree(tree.clone())
125            .install()
126            .await?;
127
128        if let Some(BuildBackendSpec::LuaRock(_)) =
129            &rockspec.build().current_platform().build_backend
130        {
131            let build_tree = tree.build_tree(config)?;
132            let luarocks = LuaRocksInstallation::new(config, build_tree)?;
133            luarocks.ensure_installed(&lua).await?;
134        }
135
136        let package = build::Build::new()
137            .rockspec(&rockspec)
138            .tree(tree)
139            .lua(&lua)
140            .entry_type(tree::EntryType::Entrypoint)
141            .config(config)
142            .pin(pin)
143            .behaviour(BuildBehaviour::Force)
144            .build()
145            .await?;
146
147        let lockfile = tree.lockfile()?;
148        let build_lockfile = tree.build_tree(config)?.lockfile()?;
149
150        let mut lockfile = lockfile.write_guard();
151        lockfile.add_entrypoint(&package);
152        for dep in dependencies {
153            lockfile.add_dependency(&package, &dep);
154        }
155        for dep in build_lockfile.rocks().values() {
156            lockfile.add_build_dependency(&package, dep);
157        }
158        Ok(package)
159    }
160}