Skip to main content

lux_lib/build/
source.rs

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) | // This should not be possible. Let's ignore it.
136        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            // We copy all directories if there is no rockspec
149            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}