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