1use itertools::Itertools;
2use std::{
3 collections::{HashMap, HashSet},
4 io,
5 path::{Path, PathBuf},
6 str::FromStr,
7};
8use thiserror::Error;
9use walkdir::WalkDir;
10
11use crate::{
12 build::utils,
13 config::Config,
14 lua_installation::LuaInstallation,
15 lua_rockspec::{Build, BuildInfo, BuiltinBuildSpec, DeploySpec, LuaModule, ModuleSpec},
16 progress::{Progress, ProgressBar},
17 tree::{RockLayout, Tree, TreeError},
18};
19
20use super::{
21 external_dependency::ExternalDependencyInfo,
22 utils::{CompileCFilesError, CompileCModulesError, InstallBinaryError},
23};
24
25#[derive(Error, Debug)]
26pub enum BuiltinBuildError {
27 #[error(transparent)]
28 CompileCFiles(#[from] CompileCFilesError),
29 #[error(transparent)]
30 CompileCModules(#[from] CompileCModulesError),
31 #[error("failed to install binary {0}: {1}")]
32 InstallBinary(String, InstallBinaryError),
33 #[error(transparent)]
34 Io(#[from] io::Error),
35 #[error(transparent)]
36 Tree(#[from] TreeError),
37}
38
39impl Build for BuiltinBuildSpec {
40 type Err = BuiltinBuildError;
41
42 async fn run(
43 self,
44 output_paths: &RockLayout,
45 _no_install: bool,
46 lua: &LuaInstallation,
47 external_dependencies: &HashMap<String, ExternalDependencyInfo>,
48 config: &Config,
49 tree: &Tree,
50 build_dir: &Path,
51 progress: &Progress<ProgressBar>,
52 ) -> Result<BuildInfo, Self::Err> {
53 let modules = autodetect_modules(build_dir, source_paths(build_dir, &self.modules))
55 .into_iter()
56 .chain(self.modules)
57 .collect::<HashMap<_, _>>();
58
59 progress.map(|p| p.set_position(modules.len() as u64));
60
61 for (destination_path, module_type) in modules.iter() {
62 match module_type {
63 ModuleSpec::SourcePath(source) => {
64 if source.extension().map(|ext| ext == "c").unwrap_or(false) {
65 progress.map(|p| {
66 p.set_message(format!(
67 "Compiling {} -> {}...",
68 &source.to_string_lossy(),
69 &destination_path
70 ))
71 });
72 let absolute_source_paths = vec![build_dir.join(source)];
73 utils::compile_c_files(
74 &absolute_source_paths,
75 destination_path,
76 &output_paths.lib,
77 lua,
78 external_dependencies,
79 )?
80 } else {
81 progress.map(|p| {
82 p.set_message(format!(
83 "Copying {} to {}...",
84 &source.to_string_lossy(),
85 &destination_path
86 ))
87 });
88 let absolute_source_path = build_dir.join(source);
89 utils::copy_lua_to_module_path(
90 &absolute_source_path,
91 destination_path,
92 &output_paths.src,
93 )?
94 }
95 }
96 ModuleSpec::SourcePaths(files) => {
97 progress.map(|p| p.set_message("Compiling C files..."));
98 let absolute_source_paths =
99 files.iter().map(|file| build_dir.join(file)).collect();
100 utils::compile_c_files(
101 &absolute_source_paths,
102 destination_path,
103 &output_paths.lib,
104 lua,
105 external_dependencies,
106 )?
107 }
108 ModuleSpec::ModulePaths(data) => {
109 progress.map(|p| p.set_message("Compiling C modules..."));
110 utils::compile_c_modules(
111 data,
112 build_dir,
113 destination_path,
114 &output_paths.lib,
115 lua,
116 external_dependencies,
117 )?
118 }
119 }
120 }
121
122 let mut binaries = Vec::new();
123 for target in autodetect_src_bin_scripts(build_dir) {
124 std::fs::create_dir_all(target.parent().unwrap())?;
125 let target = target.to_string_lossy().to_string();
126 let source = build_dir.join("src").join("bin").join(&target);
127 let installed_bin_script =
131 utils::install_binary(&source, &target, tree, lua, &DeploySpec::default(), config)
132 .await
133 .map_err(|err| BuiltinBuildError::InstallBinary(target.clone(), err))?;
134 binaries.push(
135 installed_bin_script
136 .file_name()
137 .expect("no file name")
138 .into(),
139 );
140 }
141
142 Ok(BuildInfo { binaries })
143 }
144}
145
146fn source_paths(build_dir: &Path, modules: &HashMap<LuaModule, ModuleSpec>) -> HashSet<PathBuf> {
147 modules
148 .iter()
149 .flat_map(|(_, spec)| match spec {
150 ModuleSpec::SourcePath(path_buf) => vec![path_buf],
151 ModuleSpec::SourcePaths(vec) => vec.iter().collect_vec(),
152 ModuleSpec::ModulePaths(module_paths) => module_paths.sources.iter().collect_vec(),
153 })
154 .map(|path| build_dir.join(path))
155 .collect()
156}
157
158fn autodetect_modules(
159 build_dir: &Path,
160 exclude: HashSet<PathBuf>,
161) -> HashMap<LuaModule, ModuleSpec> {
162 WalkDir::new(build_dir.join("src"))
163 .into_iter()
164 .chain(WalkDir::new(build_dir.join("lua")))
165 .chain(WalkDir::new(build_dir.join("lib")))
166 .filter_map(|file| {
167 file.ok().and_then(|file| {
168 let is_lua_file = PathBuf::from(file.file_name())
169 .extension()
170 .map(|ext| ext == "lua")
171 .unwrap_or(false);
172 if is_lua_file && !exclude.contains(&file.clone().into_path()) {
173 Some(file)
174 } else {
175 None
176 }
177 })
178 })
179 .map(|file| {
180 let diff: PathBuf =
181 pathdiff::diff_paths(build_dir.join(file.clone().into_path()), build_dir)
182 .expect("failed to autodetect modules");
183
184 let pathbuf = diff.components().skip(1).collect::<PathBuf>();
191 let mut lua_module = LuaModule::from_pathbuf(pathbuf);
192
193 if file.file_name().to_string_lossy().as_bytes() == b"init.lua" {
196 lua_module = lua_module.join(&LuaModule::from_str("init").unwrap())
197 }
198
199 (lua_module, ModuleSpec::SourcePath(diff))
200 })
201 .collect()
202}
203
204fn autodetect_src_bin_scripts(build_dir: &Path) -> Vec<PathBuf> {
205 WalkDir::new(build_dir.join("src").join("bin"))
206 .into_iter()
207 .filter_map(|file| file.ok())
208 .filter(|file| file.clone().into_path().is_file())
209 .map(|file| {
210 let diff = pathdiff::diff_paths(build_dir.join(file.into_path()), build_dir)
211 .expect("failed to autodetect bin scripts");
212 diff.components().skip(2).collect::<PathBuf>()
213 })
214 .collect()
215}