use std::{io, string::FromUtf8Error};
use thiserror::Error;
use crate::{
build::backend::{BuildBackend, BuildInfo, RunBuildArgs},
lua_rockspec::{BuildBackendSpec, BuildSpec, LocalLuaRockspec, LuaRockspecError},
project::{
project_toml::{LocalProjectTomlValidationError, PartialProjectToml},
ProjectRoot, PROJECT_TOML,
},
rockspec::Rockspec,
};
use super::{
builtin::BuiltinBuildError, cmake::CMakeError, command::CommandError, make::MakeError,
rust_mlua::RustError, treesitter_parser::TreesitterBuildError, utils::recursive_copy_dir,
};
#[derive(Error, Debug)]
pub enum SourceBuildError {
#[error("IO operation failed: {0}")]
Io(#[from] io::Error),
#[error(transparent)]
FromUtf8(#[from] FromUtf8Error),
#[error(transparent)]
Toml(#[from] toml::de::Error),
#[error(transparent)]
LocalProjectTomlValidation(#[from] LocalProjectTomlValidationError),
#[error(transparent)]
LuaRockspec(#[from] LuaRockspecError),
#[error("builtin build failed: {0}")]
Builtin(#[from] BuiltinBuildError),
#[error("cmake build failed: {0}")]
CMake(#[from] CMakeError),
#[error("make build failed: {0}")]
Make(#[from] MakeError),
#[error("command build failed: {0}")]
Command(#[from] CommandError),
#[error("rust-mlua build failed: {0}")]
Rust(#[from] RustError),
#[error("treesitter-parser build failed: {0}")]
TreesitterBuild(#[from] TreesitterBuildError),
#[error("cannot build from a project source that requires a luarocks build backend: {0}")]
UnsupporedLuarocksBuildBackend(String),
}
pub(crate) async fn build(args: RunBuildArgs<'_>) -> Result<BuildInfo, SourceBuildError> {
let output_paths = args.output_paths;
let build_dir = args.build_dir;
let mut build_spec = BuildSpec::default();
let mut copy_directories = None;
for path in std::fs::read_dir(build_dir)?
.filter_map(Result::ok)
.map(|entry| entry.path())
{
if path.file_name().is_some_and(|name| name == PROJECT_TOML) {
let toml_content = String::from_utf8(tokio::fs::read(path).await?)?;
let project_toml =
PartialProjectToml::new(&toml_content, ProjectRoot::new())?.into_local()?;
build_spec = project_toml.build().current_platform().clone();
copy_directories = Some(build_spec.copy_directories);
break;
} else if path.extension().is_some_and(|ext| ext == "rockspec") {
let rockspec_content = String::from_utf8(tokio::fs::read(path).await?)?;
let rockspec = LocalLuaRockspec::new(&rockspec_content, ProjectRoot::new())?;
build_spec = rockspec.build().current_platform().clone();
copy_directories = Some(build_spec.copy_directories);
break;
}
}
let build_info = match build_spec.build_backend {
Some(BuildBackendSpec::Builtin(build_spec)) => {
build_spec
.run(args)
.await?
}
Some(BuildBackendSpec::Make(make_spec)) => {
make_spec
.run(args)
.await?
}
Some(BuildBackendSpec::CMake(cmake_spec)) => {
cmake_spec
.run(args)
.await?
}
Some(BuildBackendSpec::Command(command_spec)) => {
command_spec
.run(args)
.await?
}
Some(BuildBackendSpec::RustMlua(rust_mlua_spec)) => {
rust_mlua_spec
.run(args)
.await?
}
Some(BuildBackendSpec::TreesitterParser(treesitter_parser_spec)) => {
treesitter_parser_spec
.run(args)
.await?
}
Some(BuildBackendSpec::LuaRock(build_backend)) => return Err(SourceBuildError::UnsupporedLuarocksBuildBackend(build_backend)),
Some(BuildBackendSpec::Source) | None => BuildInfo::default(),
};
match copy_directories {
Some(copy_directories) => {
for directory in copy_directories.iter().filter(|dir| {
dir.file_name()
.is_some_and(|name| name != "doc" && name != "docs")
}) {
recursive_copy_dir(&build_dir.join(directory), &output_paths.etc).await?;
}
}
None => {
for subdirectory in std::fs::read_dir(build_dir)?
.filter_map(Result::ok)
.filter_map(|entry| {
let path = entry.path();
if path.is_dir()
&& path.file_name().is_some_and(|name| {
!matches!(
name.to_string_lossy().to_string().as_str(),
"lua" | "src" | "doc" | "docs"
)
})
{
path.file_name()
.map(|name| name.to_string_lossy().to_string())
} else {
None
}
})
{
recursive_copy_dir(
&build_dir.join(&subdirectory),
&output_paths.etc.join(&subdirectory),
)
.await?;
}
}
}
Ok(build_info)
}