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 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 no_lock: bool,
58
59 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 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}