lux_lib/operations/
build_project.rs1use 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 no_lock: bool,
55
56 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 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}