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] Box<InstallError>),
29    #[diagnostic(transparent)]
30    LuaRocks(#[from] LuaRocksError),
31    #[diagnostic(transparent)]
32    LuaRocksInstall(#[from] Box<LuaRocksInstallError>),
33    #[diagnostic(transparent)]
34    Build(#[from] Box<BuildError>),
35}
36
37impl From<InstallError> for InstallRockspecError {
38    fn from(source: InstallError) -> Self {
39        Self::Install(Box::new(source))
40    }
41}
42
43impl From<LuaRocksInstallError> for InstallRockspecError {
44    fn from(source: LuaRocksInstallError) -> Self {
45        Self::LuaRocksInstall(Box::new(source))
46    }
47}
48
49impl From<BuildError> for InstallRockspecError {
50    fn from(source: BuildError) -> Self {
51        Self::Build(Box::new(source))
52    }
53}
54
55/// Installs a Lua RockSpec into a [`Tree`].
56#[derive(Builder)]
57#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
58pub struct InstallRockspec<'a, T>
59where
60    T: InstallTree,
61{
62    rockspec: RemoteLuaRockspec,
63
64    pin: PinnedState,
65
66    config: &'a Config,
67
68    tree: &'a T,
69}
70
71impl<
72        T: InstallTree + Sync + Send + Clone + 'static,
73        State: install_rockspec_builder::State + install_rockspec_builder::IsComplete,
74    > InstallRockspecBuilder<'_, T, State>
75{
76    pub async fn install(self) -> Result<LocalPackage, InstallRockspecError> {
77        let args = self._build();
78        let rockspec = args.rockspec;
79        let pin = args.pin;
80        let config = args.config;
81        let tree = args.tree;
82
83        let lua_version = rockspec.lua_version_matches(config)?;
84        let lua = LuaInstallation::new(&lua_version, config).await?;
85
86        // Ensure all dependencies and build dependencies are installed first
87
88        let build_dependencies = rockspec.build_dependencies().current_platform();
89
90        let build_dependencies_to_install = build_dependencies
91            .iter()
92            .filter(|dep| {
93                // Exclude luarocks build backends that we have implemented in lux
94                !matches!(
95                    dep.name().to_string().as_str(),
96                    "luarocks-build-rust-mlua"
97                        | "luarocks-build-rust-binary"
98                        | "luarocks-build-treesitter-parser"
99                )
100            })
101            .filter(|dep| {
102                tree.match_rocks(dep.package_req())
103                    .is_ok_and(|rock_match| rock_match.is_found())
104            })
105            .map(|dep| {
106                PackageInstallSpec::new(dep.package_req().clone(), tree::EntryType::Entrypoint)
107                    .build_behaviour(BuildBehaviour::NoForce)
108                    .pin(pin)
109                    .opt(OptState::Required)
110                    .maybe_source(dep.source().clone())
111                    .build()
112            })
113            .collect();
114
115        Install::new(config)
116            .packages(build_dependencies_to_install)
117            .tree(tree.build_tree(config)?)
118            .install()
119            .await?;
120
121        let dependencies = rockspec.dependencies().current_platform();
122
123        let mut dependencies_to_install = Vec::new();
124        for dep in dependencies {
125            let rock_match = tree.match_rocks(dep.package_req())?;
126            if !rock_match.is_found() {
127                let dep = PackageInstallSpec::new(
128                    dep.package_req().clone(),
129                    tree::EntryType::DependencyOnly,
130                )
131                .build_behaviour(BuildBehaviour::NoForce)
132                .pin(pin)
133                .opt(OptState::Required)
134                .maybe_source(dep.source().clone())
135                .build();
136                dependencies_to_install.push(dep);
137            }
138        }
139
140        let dependencies = Install::new(config)
141            .packages(dependencies_to_install)
142            .tree(tree.clone())
143            .install()
144            .await?;
145
146        if let Some(BuildBackendSpec::LuaRock(_)) =
147            &rockspec.build().current_platform().build_backend
148        {
149            let build_tree = tree.build_tree(config)?;
150            let luarocks = LuaRocksInstallation::new(config, build_tree)?;
151            luarocks.ensure_installed(&lua).await?;
152        }
153
154        let package = build::Build::new()
155            .rockspec(&rockspec)
156            .tree(tree)
157            .lua(&lua)
158            .entry_type(tree::EntryType::Entrypoint)
159            .config(config)
160            .pin(pin)
161            .behaviour(BuildBehaviour::Force)
162            .build()
163            .await?;
164
165        let lockfile = tree.lockfile()?;
166        let build_lockfile = tree.build_tree(config)?.lockfile()?;
167
168        let mut lockfile = lockfile.write_guard();
169        lockfile.add_entrypoint(&package);
170        for dep in dependencies {
171            lockfile.add_dependency(&package, &dep);
172        }
173        for dep in build_lockfile.rocks().values() {
174            lockfile.add_build_dependency(&package, dep);
175        }
176        Ok(package)
177    }
178}