1use crate::fs;
2use crate::project;
3use std::string::FromUtf8Error;
4
5use crate::{
6 build::backend::{BuildBackend, BuildInfo, RunBuildArgs},
7 lua_rockspec::{self, BuildBackendSpec, BuildSpec, LocalLuaRockspec},
8 project::{
9 project_toml::{LocalProjectTomlValidationError, PartialProjectToml},
10 ProjectRoot, TomlDeError, PROJECT_TOML,
11 },
12 rockspec::Rockspec,
13 tree::InstallTree,
14};
15use miette::Diagnostic;
16use thiserror::Error;
17
18use super::{builtin, cmake, command, make, rust_binary, rust_mlua, treesitter_parser};
19
20#[derive(Error, Debug, Diagnostic)]
21#[non_exhaustive]
22pub enum Error {
23 #[error(transparent)]
24 #[diagnostic(transparent)]
25 Fs(#[from] fs::Error),
26 #[error(transparent)]
27 FromUtf8(#[from] FromUtf8Error),
28 #[error(transparent)]
29 #[diagnostic(transparent)]
30 Toml(#[from] TomlDeError),
31 #[error(transparent)]
32 #[diagnostic(transparent)]
33 LocalProjectTomlValidation(#[from] LocalProjectTomlValidationError),
34 #[error(transparent)]
35 #[diagnostic(transparent)]
36 LuaRockspec(#[from] lua_rockspec::Error),
37 #[error("builtin build failed")]
38 #[diagnostic(forward(0))]
39 Builtin(#[from] builtin::Error),
40 #[error("cmake build failed")]
41 #[diagnostic(forward(0))]
42 CMake(#[from] cmake::Error),
43 #[error("make build failed")]
44 #[diagnostic(forward(0))]
45 Make(#[from] make::Error),
46 #[error("command build failed")]
47 #[diagnostic(forward(0))]
48 Command(#[from] command::Error),
49 #[error("rust-mlua build failed")]
50 #[diagnostic(forward(0))]
51 Rust(#[from] rust_mlua::Error),
52 #[error("rust-binary build failed")]
53 #[diagnostic(forward(0))]
54 RustBinary(#[from] rust_binary::Error),
55 #[error("treesitter-parser build failed")]
56 #[diagnostic(forward(0))]
57 TreesitterBuild(#[from] treesitter_parser::Error),
58 #[error("cannot build from a project source that requires a luarocks build backend: '{0}'")]
59 #[diagnostic(help(
60 r#"lux does not support building from source with a luarocks build backend.
61Use a pre-built rock or a different build backend."#
62 ))]
63 UnsupportedLuarocksBuildBackend(String),
64}
65
66#[tracing::instrument(name = "source::build", skip_all, level = "debug")]
67pub(crate) async fn build<T>(args: RunBuildArgs<'_, T>) -> Result<BuildInfo, Error>
68where
69 T: InstallTree + Sync,
70{
71 let package = args.package;
72 let tree = args.tree;
73 let layout = tree.layout_for(package);
74 let build_dir = args.build_dir;
75
76 let mut build_spec = BuildSpec::default();
77 let mut copy_directories = None;
78 for path in fs::sync::read_dir(build_dir)?
79 .filter_map(Result::ok)
80 .map(|entry| entry.path())
81 {
82 if path.file_name().is_some_and(|name| name == PROJECT_TOML) {
83 let toml_content = String::from_utf8(fs::tokio::read(path).await?)?;
84 let project_toml =
85 PartialProjectToml::new(PROJECT_TOML, &toml_content, ProjectRoot::new())?
86 .into_local()?;
87 build_spec = project_toml.build().current_platform().clone();
88 copy_directories = Some(build_spec.copy_directories);
89 break;
90 } else if path.extension().is_some_and(|ext| ext == "rockspec") {
91 let rockspec_content = String::from_utf8(fs::tokio::read(path).await?)?;
92 let rockspec = LocalLuaRockspec::new(&rockspec_content, ProjectRoot::new())?;
93 build_spec = rockspec.build().current_platform().clone();
94 copy_directories = Some(build_spec.copy_directories);
95 break;
96 }
97 }
98 let build_info = match build_spec.build_backend {
99 Some(BuildBackendSpec::Builtin(build_spec)) => {
100 build_spec
101 .run(args)
102 .await?
103 }
104 Some(BuildBackendSpec::Make(make_spec)) => {
105 make_spec
106 .run(args)
107 .await?
108 }
109 Some(BuildBackendSpec::CMake(cmake_spec)) => {
110 cmake_spec
111 .run(args)
112 .await?
113 }
114 Some(BuildBackendSpec::Command(command_spec)) => {
115 command_spec
116 .run(args)
117 .await?
118 }
119 Some(BuildBackendSpec::RustMlua(rust_mlua_spec)) => {
120 rust_mlua_spec
121 .run(args)
122 .await?
123 }
124 Some(BuildBackendSpec::RustBinary(rust_binary_spec)) => {
125 rust_binary_spec
126 .run(args)
127 .await?
128 }
129 Some(BuildBackendSpec::TreesitterParser(treesitter_parser_spec)) => {
130 treesitter_parser_spec
131 .run(args)
132 .await?
133 }
134 Some(BuildBackendSpec::LuaRock(build_backend)) => return Err(Error::UnsupportedLuarocksBuildBackend(build_backend)),
135 Some(BuildBackendSpec::Source) | None => BuildInfo::default(),
137 };
138 match copy_directories {
139 Some(copy_directories) => {
140 for directory in copy_directories.iter().filter(|dir| {
141 dir.file_name()
142 .is_some_and(|name| name != "doc" && name != "docs")
143 }) {
144 project::copy_dir(&build_dir.join(directory), &layout.etc).await?;
145 }
146 }
147 None => {
148 for subdirectory in fs::sync::read_dir(build_dir)?
150 .filter_map(Result::ok)
151 .filter_map(|entry| {
152 let path = entry.path();
153 if path.is_dir()
154 && path.file_name().is_some_and(|name| {
155 !matches!(
156 name.to_string_lossy().to_string().as_str(),
157 "lua" | "src" | "doc" | "docs"
158 )
159 })
160 {
161 path.file_name()
162 .map(|name| name.to_string_lossy().to_string())
163 } else {
164 None
165 }
166 })
167 {
168 project::copy_dir(
169 &build_dir.join(&subdirectory),
170 &layout.etc.join(&subdirectory),
171 )
172 .await?;
173 }
174 }
175 }
176 Ok(build_info)
177}