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