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 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 no_lock: bool,
54
55 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}