lux_lib/operations/
build_project.rs

1use std::sync::Arc;
2
3use bon::Builder;
4use itertools::Itertools;
5use thiserror::Error;
6
7use crate::{
8    build::{Build, BuildBehaviour, BuildError},
9    config::Config,
10    lockfile::LocalPackage,
11    lua_installation::{LuaInstallation, LuaInstallationError},
12    luarocks::luarocks_installation::{LuaRocksError, LuaRocksInstallError, LuaRocksInstallation},
13    progress::{MultiProgress, Progress},
14    project::{project_toml::LocalProjectTomlValidationError, Project, ProjectTreeError},
15    rockspec::Rockspec,
16    tree::{self, TreeError},
17};
18
19use super::{Install, InstallError, PackageInstallSpec, Sync, SyncError};
20
21#[derive(Debug, Error)]
22pub enum BuildProjectError {
23    #[error(transparent)]
24    LocalProjectTomlValidation(#[from] LocalProjectTomlValidationError),
25    #[error(transparent)]
26    ProjectTree(#[from] ProjectTreeError),
27    #[error(transparent)]
28    LuaInstallation(#[from] LuaInstallationError),
29    #[error(transparent)]
30    Tree(#[from] TreeError),
31    #[error(transparent)]
32    LuaRocks(#[from] LuaRocksError),
33    #[error(transparent)]
34    LuaRocksInstall(#[from] LuaRocksInstallError),
35    #[error("error installind dependencies:\n{0}")]
36    InstallDependencies(InstallError),
37    #[error("error installind build dependencies:\n{0}")]
38    InstallBuildDependencies(InstallError),
39    #[error("syncing dependencies with the project lockfile failed.\nUse --no-lock to force a new build.\n\n{0}")]
40    SyncDependencies(SyncError),
41    #[error("syncing build dependencies with the project lockfile failed.\nUse --no-lock to force a new build.\n\n{0}")]
42    SyncBuildDependencies(SyncError),
43    #[error("error building project:\n{0}")]
44    Build(#[from] BuildError),
45}
46
47#[derive(Builder)]
48#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
49pub struct BuildProject<'a> {
50    #[builder(start_fn)]
51    project: &'a Project,
52
53    #[builder(start_fn)]
54    config: &'a Config,
55
56    /// Ignore the project's lockfile and don't create one
57    no_lock: bool,
58
59    /// Build only the dependencies
60    only_deps: bool,
61
62    #[builder(default = MultiProgress::new_arc())]
63    progress: Arc<Progress<MultiProgress>>,
64}
65
66impl<State: build_project_builder::State + build_project_builder::IsComplete>
67    BuildProjectBuilder<'_, State>
68{
69    /// Returns `Some` if the `only_deps` option is set to `false`.
70    pub async fn build(self) -> Result<Option<LocalPackage>, BuildProjectError> {
71        let args = self._build();
72        let project = args.project;
73        let config = args.config;
74        let progress_arc = args.progress;
75        let progress = Arc::clone(&progress_arc);
76
77        let project_toml = project.toml().into_local()?;
78        let project_tree = project.tree(config)?;
79
80        let dependencies = project_toml
81            .dependencies()
82            .current_platform()
83            .iter()
84            .cloned()
85            .collect_vec();
86
87        let build_dependencies = project_toml
88            .build_dependencies()
89            .current_platform()
90            .iter()
91            .cloned()
92            .collect_vec();
93
94        let build_tree = project.build_tree(config)?;
95        let lua =
96            LuaInstallation::new_from_config(config, &progress.map(|progress| progress.new_bar()))
97                .await?;
98        let luarocks = LuaRocksInstallation::new(config, build_tree.clone())?;
99
100        if args.no_lock {
101            let dependencies_to_install = dependencies
102                .into_iter()
103                .filter(|dep| {
104                    project_tree
105                        .match_rocks(dep.package_req())
106                        .is_ok_and(|rock_match| !rock_match.is_found())
107                })
108                .map(|dep| {
109                    PackageInstallSpec::new(
110                        dep.clone().into_package_req(),
111                        tree::EntryType::Entrypoint,
112                    )
113                    .pin(*dep.pin())
114                    .opt(*dep.opt())
115                    .maybe_source(dep.source().clone())
116                    .build()
117                })
118                .collect();
119
120            Install::new(config)
121                .packages(dependencies_to_install)
122                .project(project)?
123                .progress(progress.clone())
124                .install()
125                .await
126                .map_err(BuildProjectError::InstallDependencies)?;
127
128            let build_dependencies_to_install = build_dependencies
129                .into_iter()
130                .filter(|dep| {
131                    project_tree
132                        .match_rocks(dep.package_req())
133                        .is_ok_and(|rock_match| !rock_match.is_found())
134                })
135                .map(|dep| {
136                    PackageInstallSpec::new(
137                        dep.clone().into_package_req(),
138                        tree::EntryType::Entrypoint,
139                    )
140                    .pin(*dep.pin())
141                    .opt(*dep.opt())
142                    .maybe_source(dep.source().clone())
143                    .build()
144                })
145                .collect_vec();
146
147            if !build_dependencies_to_install.is_empty() {
148                let bar = progress.map(|p| p.new_bar());
149                luarocks.ensure_installed(&lua, &bar).await?;
150                Install::new(config)
151                    .packages(build_dependencies_to_install)
152                    .tree(build_tree)
153                    .progress(progress.clone())
154                    .install()
155                    .await
156                    .map_err(BuildProjectError::InstallBuildDependencies)?;
157            }
158        } else {
159            Sync::new(project, config)
160                .progress(progress.clone())
161                .sync_dependencies()
162                .await
163                .map_err(BuildProjectError::SyncDependencies)?;
164
165            Sync::new(project, config)
166                .progress(progress.clone())
167                .sync_build_dependencies()
168                .await
169                .map_err(BuildProjectError::SyncBuildDependencies)?;
170        }
171
172        if !args.only_deps {
173            let package = Build::new()
174                .rockspec(&project_toml)
175                .lua(&lua)
176                .tree(&project_tree)
177                .entry_type(tree::EntryType::Entrypoint)
178                .config(config)
179                .progress(&progress.map(|p| p.new_bar()))
180                .behaviour(BuildBehaviour::Force)
181                .build()
182                .await?;
183
184            let lockfile = project_tree.lockfile()?;
185            let dependencies = lockfile
186                .rocks()
187                .iter()
188                .filter_map(|(pkg_id, value)| {
189                    if lockfile.is_entrypoint(pkg_id) {
190                        Some(value)
191                    } else {
192                        None
193                    }
194                })
195                .cloned()
196                .collect_vec();
197            let mut lockfile = lockfile.write_guard();
198            lockfile.add_entrypoint(&package);
199            for dep in dependencies {
200                lockfile.add_dependency(&package, &dep);
201                lockfile.remove_entrypoint(&dep);
202            }
203            Ok(Some(package))
204        } else {
205            Ok(None)
206        }
207    }
208}