1mod builtin;
2mod cmake;
3mod make;
4mod rust_mlua;
5mod tree_sitter;
6
7pub use builtin::{BuiltinBuildSpec, LuaModule, ModulePaths, ModuleSpec};
8pub use cmake::*;
9pub use make::*;
10pub use rust_mlua::*;
11pub use tree_sitter::*;
12
13use builtin::{
14 ModulePathsMissingSources, ModuleSpecAmbiguousPlatformOverride, ModuleSpecInternal,
15 ParseLuaModuleError,
16};
17
18use itertools::Itertools;
19
20use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, UserData, Value};
21use std::{
22 collections::HashMap,
23 env::consts::DLL_EXTENSION,
24 fmt::Display,
25 future::Future,
26 path::{Path, PathBuf},
27 str::FromStr,
28};
29use thiserror::Error;
30
31use serde::{de, de::IntoDeserializer, Deserialize, Deserializer};
32
33use crate::{
34 build::external_dependency::ExternalDependencyInfo,
35 config::Config,
36 lua_installation::LuaInstallation,
37 progress::{Progress, ProgressBar},
38 tree::{RockLayout, Tree},
39};
40
41use super::{
42 mlua_json_value_to_map, mlua_json_value_to_vec, DisplayAsLuaKV, DisplayAsLuaValue,
43 DisplayLuaKV, DisplayLuaValue, LuaTableKey, PartialOverride, PerPlatform, PlatformIdentifier,
44};
45
46#[derive(Clone, Debug, PartialEq)]
51pub struct BuildSpec {
52 pub build_backend: Option<BuildBackendSpec>,
54 pub install: InstallSpec,
58 pub copy_directories: Vec<PathBuf>,
60 pub patches: HashMap<PathBuf, String>,
64}
65
66impl Default for BuildSpec {
67 fn default() -> Self {
68 Self {
69 build_backend: Some(BuildBackendSpec::default()),
70 install: InstallSpec::default(),
71 copy_directories: Vec::default(),
72 patches: HashMap::default(),
73 }
74 }
75}
76
77impl UserData for BuildSpec {
78 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
79 methods.add_method("build_backend", |_, this, _: ()| {
80 Ok(this.build_backend.clone())
81 });
82 methods.add_method("install", |_, this, _: ()| Ok(this.install.clone()));
83 methods.add_method("copy_directories", |_, this, _: ()| {
84 Ok(this.copy_directories.clone())
85 });
86 methods.add_method("patches", |_, this, _: ()| Ok(this.patches.clone()));
87 }
88}
89
90#[derive(Error, Debug)]
91pub enum BuildSpecInternalError {
92 #[error("'builtin' modules should not have list elements")]
93 ModulesHaveListElements,
94 #[error("no 'build_command' specified for the 'command' build backend")]
95 NoBuildCommand,
96 #[error("no 'install_command' specified for the 'command' build backend")]
97 NoInstallCommand,
98 #[error("no 'modules' specified for the 'rust-mlua' build backend")]
99 NoModulesSpecified,
100 #[error("no 'lang' specified for 'treesitter-parser' build backend")]
101 NoTreesitterParserLanguageSpecified,
102 #[error("invalid 'rust-mlua' modules format")]
103 InvalidRustMLuaFormat,
104 #[error(transparent)]
105 ModulePathsMissingSources(#[from] ModulePathsMissingSources),
106 #[error(transparent)]
107 ParseLuaModuleError(#[from] ParseLuaModuleError),
108}
109
110impl BuildSpec {
111 pub(crate) fn from_internal_spec(
112 internal: BuildSpecInternal,
113 ) -> Result<Self, BuildSpecInternalError> {
114 let build_backend = match internal.build_type.unwrap_or_default() {
115 BuildType::Builtin => Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
116 modules: internal
117 .builtin_spec
118 .unwrap_or_default()
119 .into_iter()
120 .map(|(key, module_spec_internal)| {
121 let key_str = match key {
122 LuaTableKey::IntKey(_) => {
123 Err(BuildSpecInternalError::ModulesHaveListElements)
124 }
125 LuaTableKey::StringKey(str) => Ok(LuaModule::from_str(str.as_str())?),
126 }?;
127 match ModuleSpec::from_internal(module_spec_internal) {
128 Ok(module_spec) => Ok((key_str, module_spec)),
129 Err(err) => Err(err.into()),
130 }
131 })
132 .collect::<Result<HashMap<LuaModule, ModuleSpec>, BuildSpecInternalError>>()?,
133 })),
134 BuildType::Make => {
135 let default = MakeBuildSpec::default();
136 Some(BuildBackendSpec::Make(MakeBuildSpec {
137 makefile: internal.makefile.unwrap_or(default.makefile),
138 build_target: internal.make_build_target,
139 build_pass: internal.build_pass.unwrap_or(default.build_pass),
140 install_target: internal
141 .make_install_target
142 .unwrap_or(default.install_target),
143 install_pass: internal.install_pass.unwrap_or(default.install_pass),
144 build_variables: internal.make_build_variables.unwrap_or_default(),
145 install_variables: internal.make_install_variables.unwrap_or_default(),
146 variables: internal.variables.unwrap_or_default(),
147 }))
148 }
149 BuildType::CMake => {
150 let default = CMakeBuildSpec::default();
151 Some(BuildBackendSpec::CMake(CMakeBuildSpec {
152 cmake_lists_content: internal.cmake_lists_content,
153 build_pass: internal.build_pass.unwrap_or(default.build_pass),
154 install_pass: internal.install_pass.unwrap_or(default.install_pass),
155 variables: internal.variables.unwrap_or_default(),
156 }))
157 }
158 BuildType::Command => {
159 let build_command = internal
160 .build_command
161 .ok_or(BuildSpecInternalError::NoBuildCommand)?;
162 let install_command = internal
163 .install_command
164 .ok_or(BuildSpecInternalError::NoInstallCommand)?;
165 Some(BuildBackendSpec::Command(CommandBuildSpec {
166 build_command,
167 install_command,
168 }))
169 }
170 BuildType::None => None,
171 BuildType::LuaRock(s) => Some(BuildBackendSpec::LuaRock(s)),
172 BuildType::RustMlua => Some(BuildBackendSpec::RustMlua(RustMluaBuildSpec {
173 modules: internal
174 .builtin_spec
175 .ok_or(BuildSpecInternalError::NoModulesSpecified)?
176 .into_iter()
177 .map(|(key, value)| match (key, value) {
178 (LuaTableKey::IntKey(_), ModuleSpecInternal::SourcePath(module)) => {
179 let mut rust_lib: PathBuf = format!("lib{}", module.display()).into();
180 rust_lib.set_extension(DLL_EXTENSION);
181 Ok((module.to_string_lossy().to_string(), rust_lib))
182 }
183 (
184 LuaTableKey::StringKey(module_name),
185 ModuleSpecInternal::SourcePath(module),
186 ) => {
187 let mut rust_lib: PathBuf = format!("lib{}", module.display()).into();
188 rust_lib.set_extension(DLL_EXTENSION);
189 Ok((module_name, rust_lib))
190 }
191 _ => Err(BuildSpecInternalError::InvalidRustMLuaFormat),
192 })
193 .try_collect()?,
194 target_path: internal.target_path.unwrap_or("target".into()),
195 default_features: internal.default_features.unwrap_or(true),
196 include: internal
197 .include
198 .unwrap_or_default()
199 .into_iter()
200 .map(|(key, dest)| match key {
201 LuaTableKey::IntKey(_) => (dest.clone(), dest),
202 LuaTableKey::StringKey(src) => (src.into(), dest),
203 })
204 .collect(),
205 features: internal.features.unwrap_or_default(),
206 })),
207 BuildType::TreesitterParser => Some(BuildBackendSpec::TreesitterParser(
208 TreesitterParserBuildSpec {
209 lang: internal
210 .lang
211 .ok_or(BuildSpecInternalError::NoTreesitterParserLanguageSpecified)?,
212 parser: internal.parser.unwrap_or(false),
213 generate: internal.generate.unwrap_or(false),
214 location: internal.location,
215 queries: internal.queries.unwrap_or_default(),
216 },
217 )),
218 BuildType::Source => Some(BuildBackendSpec::Source),
219 };
220 Ok(Self {
221 build_backend,
222 install: internal.install.unwrap_or_default(),
223 copy_directories: internal.copy_directories.unwrap_or_default(),
224 patches: internal.patches.unwrap_or_default(),
225 })
226 }
227}
228
229impl<'de> Deserialize<'de> for BuildSpec {
230 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231 where
232 D: Deserializer<'de>,
233 {
234 let internal = BuildSpecInternal::deserialize(deserializer)?;
235 BuildSpec::from_internal_spec(internal).map_err(de::Error::custom)
236 }
237}
238
239impl FromLua for PerPlatform<BuildSpec> {
240 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
241 let internal = PerPlatform::from_lua(value, lua)?;
242 let mut per_platform = HashMap::new();
243 for (platform, internal_override) in internal.per_platform {
244 let override_spec = BuildSpec::from_internal_spec(internal_override)
245 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
246 per_platform.insert(platform, override_spec);
247 }
248 let result = PerPlatform {
249 default: BuildSpec::from_internal_spec(internal.default)
250 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?,
251 per_platform,
252 };
253 Ok(result)
254 }
255}
256
257impl Default for BuildBackendSpec {
258 fn default() -> Self {
259 Self::Builtin(BuiltinBuildSpec::default())
260 }
261}
262
263#[derive(Debug, PartialEq, Clone)]
270pub enum BuildBackendSpec {
271 Builtin(BuiltinBuildSpec),
272 Make(MakeBuildSpec),
273 CMake(CMakeBuildSpec),
274 Command(CommandBuildSpec),
275 LuaRock(String),
276 RustMlua(RustMluaBuildSpec),
277 TreesitterParser(TreesitterParserBuildSpec),
278 Source,
284}
285
286impl IntoLua for BuildBackendSpec {
287 fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
288 match self {
289 BuildBackendSpec::Builtin(spec) => spec.into_lua(lua),
290 BuildBackendSpec::Make(spec) => spec.into_lua(lua),
291 BuildBackendSpec::CMake(spec) => spec.into_lua(lua),
292 BuildBackendSpec::Command(spec) => spec.into_lua(lua),
293 BuildBackendSpec::LuaRock(s) => s.into_lua(lua),
294 BuildBackendSpec::RustMlua(spec) => spec.into_lua(lua),
295 BuildBackendSpec::TreesitterParser(spec) => spec.into_lua(lua),
296 BuildBackendSpec::Source => "source".into_lua(lua),
297 }
298 }
299}
300
301#[derive(Debug, PartialEq, Clone)]
302pub struct CommandBuildSpec {
303 pub build_command: String,
304 pub install_command: String,
305}
306
307impl UserData for CommandBuildSpec {
308 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
309 methods.add_method("build_command", |_, this, _: ()| {
310 Ok(this.build_command.clone())
311 });
312 methods.add_method("install_command", |_, this, _: ()| {
313 Ok(this.install_command.clone())
314 });
315 }
316}
317
318#[derive(Clone, Debug)]
319pub(crate) enum InstallBinaries {
320 Array(Vec<PathBuf>),
321 Table(HashMap<String, PathBuf>),
322}
323
324impl<'de> Deserialize<'de> for InstallBinaries {
325 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
326 where
327 D: Deserializer<'de>,
328 {
329 let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
330 if value.is_array() {
331 let array = mlua_json_value_to_vec(value).map_err(de::Error::custom)?;
332 Ok(InstallBinaries::Array(array))
333 } else {
334 let table: HashMap<String, PathBuf> =
335 mlua_json_value_to_map(value).map_err(de::Error::custom)?;
336 Ok(InstallBinaries::Table(table))
337 }
338 }
339}
340
341impl InstallBinaries {
342 pub(crate) fn coerce(self) -> HashMap<String, PathBuf> {
343 match self {
344 InstallBinaries::Array(array) => array
345 .into_iter()
346 .map(|path| {
347 (
348 path.file_stem().unwrap().to_str().unwrap().to_string(),
349 path,
350 )
351 })
352 .collect(),
353 InstallBinaries::Table(table) => table,
354 }
355 }
356}
357
358#[derive(Debug, PartialEq, Default, Deserialize, Clone)]
367pub struct InstallSpec {
368 #[serde(default)]
370 pub lua: HashMap<LuaModule, PathBuf>,
371 #[serde(default)]
373 pub lib: HashMap<LuaModule, PathBuf>,
374 #[serde(default)]
376 pub conf: HashMap<String, PathBuf>,
377 #[serde(default, deserialize_with = "deserialize_binaries")]
381 pub bin: HashMap<String, PathBuf>,
382}
383
384impl UserData for InstallSpec {
385 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
386 methods.add_method("lua", |_, this, _: ()| Ok(this.lua.clone()));
387 methods.add_method("lib", |_, this, _: ()| Ok(this.lib.clone()));
388 methods.add_method("conf", |_, this, _: ()| Ok(this.conf.clone()));
389 methods.add_method("bin", |_, this, _: ()| Ok(this.bin.clone()));
390 }
391}
392
393fn deserialize_binaries<'de, D>(deserializer: D) -> Result<HashMap<String, PathBuf>, D::Error>
394where
395 D: Deserializer<'de>,
396{
397 let binaries = InstallBinaries::deserialize(deserializer)?;
398 Ok(binaries.coerce())
399}
400
401fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
402where
403 D: Deserializer<'de>,
404{
405 let value: Option<serde_json::Value> = Option::deserialize(deserializer)?;
406 let copy_directories: Option<Vec<String>> = match value {
407 Some(json_value) => Some(mlua_json_value_to_vec(json_value).map_err(de::Error::custom)?),
408 None => None,
409 };
410 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
411 match special_directories
412 .into_iter()
413 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
414 {
415 Some(d) => Err(format!(
418 "directory '{}' in copy_directories clashes with the .rock format", d
420 )),
421 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
422 }
423 .map_err(de::Error::custom)
424}
425
426impl DisplayAsLuaKV for InstallSpec {
427 fn display_lua(&self) -> DisplayLuaKV {
428 let mut result = Vec::new();
429
430 self.lua
431 .iter()
432 .chain(self.lib.iter())
433 .for_each(|(key, value)| {
434 result.push(DisplayLuaKV {
435 key: key.to_string(),
436 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
437 });
438 });
439
440 self.conf
441 .iter()
442 .chain(self.bin.iter())
443 .for_each(|(key, value)| {
444 result.push(DisplayLuaKV {
445 key: key.clone(),
446 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
447 });
448 });
449
450 DisplayLuaKV {
451 key: "install".to_string(),
452 value: DisplayLuaValue::Table(result),
453 }
454 }
455}
456
457#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
458pub(crate) struct BuildSpecInternal {
459 #[serde(rename = "type", default)]
460 pub(crate) build_type: Option<BuildType>,
461 #[serde(rename = "modules", default)]
462 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
463 #[serde(default)]
464 pub(crate) makefile: Option<PathBuf>,
465 #[serde(rename = "build_target", default)]
466 pub(crate) make_build_target: Option<String>,
467 #[serde(default)]
468 pub(crate) build_pass: Option<bool>,
469 #[serde(rename = "install_target", default)]
470 pub(crate) make_install_target: Option<String>,
471 #[serde(default)]
472 pub(crate) install_pass: Option<bool>,
473 #[serde(rename = "build_variables", default)]
474 pub(crate) make_build_variables: Option<HashMap<String, String>>,
475 #[serde(rename = "install_variables", default)]
476 pub(crate) make_install_variables: Option<HashMap<String, String>>,
477 #[serde(default)]
478 pub(crate) variables: Option<HashMap<String, String>>,
479 #[serde(rename = "cmake", default)]
480 pub(crate) cmake_lists_content: Option<String>,
481 #[serde(default)]
482 pub(crate) build_command: Option<String>,
483 #[serde(default)]
484 pub(crate) install_command: Option<String>,
485 #[serde(default)]
486 pub(crate) install: Option<InstallSpec>,
487 #[serde(default, deserialize_with = "deserialize_copy_directories")]
488 pub(crate) copy_directories: Option<Vec<PathBuf>>,
489 #[serde(default)]
490 pub(crate) patches: Option<HashMap<PathBuf, String>>,
491 #[serde(default)]
493 pub(crate) target_path: Option<PathBuf>,
494 #[serde(default)]
495 pub(crate) default_features: Option<bool>,
496 #[serde(default)]
497 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
498 #[serde(default)]
499 pub(crate) features: Option<Vec<String>>,
500 #[serde(default)]
502 pub(crate) lang: Option<String>,
503 #[serde(default)]
504 pub(crate) parser: Option<bool>,
505 #[serde(default)]
506 pub(crate) generate: Option<bool>,
507 #[serde(default)]
508 pub(crate) location: Option<PathBuf>,
509 #[serde(default)]
510 pub(crate) queries: Option<HashMap<PathBuf, String>>,
511}
512
513impl FromLua for PerPlatform<BuildSpecInternal> {
514 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
515 match &value {
516 list @ Value::Table(tbl) => {
517 let mut per_platform = match tbl.get("platforms")? {
518 Value::Table(overrides) => Ok(lua.from_value(Value::Table(overrides))?),
519 Value::Nil => Ok(HashMap::default()),
520 val => Err(mlua::Error::DeserializeError(format!(
521 "Expected rockspec 'build' to be table or nil, but got {}",
522 val.type_name()
523 ))),
524 }?;
525 let _ = tbl.raw_remove("platforms");
526 let default = lua.from_value(list.clone())?;
527 override_platform_specs(&mut per_platform, &default)
528 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
529 Ok(PerPlatform {
530 default,
531 per_platform,
532 })
533 }
534 Value::Nil => Ok(PerPlatform::default()),
535 val => Err(mlua::Error::DeserializeError(format!(
536 "Expected rockspec 'build' to be a table or nil, but got {}",
537 val.type_name()
538 ))),
539 }
540 }
541}
542
543fn override_platform_specs(
546 per_platform: &mut HashMap<PlatformIdentifier, BuildSpecInternal>,
547 base: &BuildSpecInternal,
548) -> Result<(), ModuleSpecAmbiguousPlatformOverride> {
549 let per_platform_raw = per_platform.clone();
550 for (platform, build_spec) in per_platform.clone() {
551 per_platform.insert(platform, override_build_spec_internal(base, &build_spec)?);
553 }
554 for (platform, build_spec) in per_platform_raw {
555 for extended_platform in &platform.get_extended_platforms() {
556 let extended_spec = per_platform
557 .get(extended_platform)
558 .unwrap_or(&base.to_owned())
559 .to_owned();
560 per_platform.insert(
561 extended_platform.to_owned(),
562 override_build_spec_internal(&extended_spec, &build_spec)?,
563 );
564 }
565 }
566 Ok(())
567}
568
569fn override_build_spec_internal(
570 base: &BuildSpecInternal,
571 override_spec: &BuildSpecInternal,
572) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
573 Ok(BuildSpecInternal {
574 build_type: override_opt(&override_spec.build_type, &base.build_type),
575 builtin_spec: match (
576 override_spec.builtin_spec.clone(),
577 base.builtin_spec.clone(),
578 ) {
579 (Some(override_val), Some(base_spec_map)) => {
580 Some(base_spec_map.into_iter().chain(override_val).try_fold(
581 HashMap::default(),
582 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
583 (k, module_spec_override)|
584 -> Result<
585 HashMap<LuaTableKey, ModuleSpecInternal>,
586 ModuleSpecAmbiguousPlatformOverride,
587 > {
588 let overridden = match acc.get(&k) {
589 None => module_spec_override,
590 Some(base_module_spec) => {
591 base_module_spec.apply_overrides(&module_spec_override)?
592 }
593 };
594 acc.insert(k, overridden);
595 Ok(acc)
596 },
597 )?)
598 }
599 (override_val @ Some(_), _) => override_val,
600 (_, base_val @ Some(_)) => base_val,
601 _ => None,
602 },
603 makefile: override_opt(&override_spec.makefile, &base.makefile),
604 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
605 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
606 make_install_target: override_opt(
607 &override_spec.make_install_target,
608 &base.make_install_target,
609 ),
610 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
611 make_build_variables: merge_map_opts(
612 &override_spec.make_build_variables,
613 &base.make_build_variables,
614 ),
615 make_install_variables: merge_map_opts(
616 &override_spec.make_install_variables,
617 &base.make_build_variables,
618 ),
619 variables: merge_map_opts(&override_spec.variables, &base.variables),
620 cmake_lists_content: override_opt(
621 &override_spec.cmake_lists_content,
622 &base.cmake_lists_content,
623 ),
624 build_command: override_opt(&override_spec.build_command, &base.build_command),
625 install_command: override_opt(&override_spec.install_command, &base.install_command),
626 install: override_opt(&override_spec.install, &base.install),
627 copy_directories: match (
628 override_spec.copy_directories.clone(),
629 base.copy_directories.clone(),
630 ) {
631 (Some(override_vec), Some(base_vec)) => {
632 let merged: Vec<PathBuf> =
633 base_vec.into_iter().chain(override_vec).unique().collect();
634 Some(merged)
635 }
636 (None, base_vec @ Some(_)) => base_vec,
637 (override_vec @ Some(_), None) => override_vec,
638 _ => None,
639 },
640 patches: override_opt(&override_spec.patches, &base.patches),
641 target_path: override_opt(&override_spec.target_path, &base.target_path),
642 default_features: override_opt(&override_spec.default_features, &base.default_features),
643 features: override_opt(&override_spec.features, &base.features),
644 include: merge_map_opts(&override_spec.include, &base.include),
645 lang: override_opt(&override_spec.lang, &base.lang),
646 parser: override_opt(&override_spec.parser, &base.parser),
647 generate: override_opt(&override_spec.generate, &base.generate),
648 location: override_opt(&override_spec.location, &base.location),
649 queries: merge_map_opts(&override_spec.queries, &base.queries),
650 })
651}
652
653fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
654 match override_opt.clone() {
655 override_val @ Some(_) => override_val,
656 None => base.clone(),
657 }
658}
659
660fn merge_map_opts<K, V>(
661 override_map: &Option<HashMap<K, V>>,
662 base_map: &Option<HashMap<K, V>>,
663) -> Option<HashMap<K, V>>
664where
665 K: Clone,
666 K: Eq,
667 K: std::hash::Hash,
668 V: Clone,
669{
670 match (override_map.clone(), base_map.clone()) {
671 (Some(override_map), Some(base_map)) => {
672 Some(base_map.into_iter().chain(override_map).collect())
673 }
674 (_, base_map @ Some(_)) => base_map,
675 (override_map @ Some(_), _) => override_map,
676 _ => None,
677 }
678}
679
680impl DisplayAsLuaKV for BuildSpecInternal {
681 fn display_lua(&self) -> DisplayLuaKV {
682 let mut result = Vec::new();
683
684 if let Some(build_type) = &self.build_type {
685 result.push(DisplayLuaKV {
686 key: "type".to_string(),
687 value: DisplayLuaValue::String(build_type.to_string()),
688 });
689 }
690 if let Some(builtin_spec) = &self.builtin_spec {
691 result.push(DisplayLuaKV {
692 key: "modules".to_string(),
693 value: DisplayLuaValue::Table(
694 builtin_spec
695 .iter()
696 .map(|(key, value)| DisplayLuaKV {
697 key: match key {
698 LuaTableKey::StringKey(s) => s.clone(),
699 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
700 },
701 value: value.display_lua_value(),
702 })
703 .collect(),
704 ),
705 });
706 }
707 if let Some(makefile) = &self.makefile {
708 result.push(DisplayLuaKV {
709 key: "makefile".to_string(),
710 value: DisplayLuaValue::String(makefile.to_string_lossy().to_string()),
711 });
712 }
713 if let Some(make_build_target) = &self.make_build_target {
714 result.push(DisplayLuaKV {
715 key: "build_target".to_string(),
716 value: DisplayLuaValue::String(make_build_target.clone()),
717 });
718 }
719 if let Some(build_pass) = &self.build_pass {
720 result.push(DisplayLuaKV {
721 key: "build_pass".to_string(),
722 value: DisplayLuaValue::Boolean(*build_pass),
723 });
724 }
725 if let Some(make_install_target) = &self.make_install_target {
726 result.push(DisplayLuaKV {
727 key: "install_target".to_string(),
728 value: DisplayLuaValue::String(make_install_target.clone()),
729 });
730 }
731 if let Some(install_pass) = &self.install_pass {
732 result.push(DisplayLuaKV {
733 key: "install_pass".to_string(),
734 value: DisplayLuaValue::Boolean(*install_pass),
735 });
736 }
737 if let Some(make_build_variables) = &self.make_build_variables {
738 result.push(DisplayLuaKV {
739 key: "build_variables".to_string(),
740 value: DisplayLuaValue::Table(
741 make_build_variables
742 .iter()
743 .map(|(key, value)| DisplayLuaKV {
744 key: key.clone(),
745 value: DisplayLuaValue::String(value.clone()),
746 })
747 .collect(),
748 ),
749 });
750 }
751 if let Some(make_install_variables) = &self.make_install_variables {
752 result.push(DisplayLuaKV {
753 key: "install_variables".to_string(),
754 value: DisplayLuaValue::Table(
755 make_install_variables
756 .iter()
757 .map(|(key, value)| DisplayLuaKV {
758 key: key.clone(),
759 value: DisplayLuaValue::String(value.clone()),
760 })
761 .collect(),
762 ),
763 });
764 }
765 if let Some(variables) = &self.variables {
766 result.push(DisplayLuaKV {
767 key: "variables".to_string(),
768 value: DisplayLuaValue::Table(
769 variables
770 .iter()
771 .map(|(key, value)| DisplayLuaKV {
772 key: key.clone(),
773 value: DisplayLuaValue::String(value.clone()),
774 })
775 .collect(),
776 ),
777 });
778 }
779 if let Some(cmake_lists_content) = &self.cmake_lists_content {
780 result.push(DisplayLuaKV {
781 key: "cmake".to_string(),
782 value: DisplayLuaValue::String(cmake_lists_content.clone()),
783 });
784 }
785 if let Some(build_command) = &self.build_command {
786 result.push(DisplayLuaKV {
787 key: "build_command".to_string(),
788 value: DisplayLuaValue::String(build_command.clone()),
789 });
790 }
791 if let Some(install_command) = &self.install_command {
792 result.push(DisplayLuaKV {
793 key: "install_command".to_string(),
794 value: DisplayLuaValue::String(install_command.clone()),
795 });
796 }
797 if let Some(install) = &self.install {
798 result.push(install.display_lua());
799 }
800 if let Some(copy_directories) = &self.copy_directories {
801 result.push(DisplayLuaKV {
802 key: "copy_directories".to_string(),
803 value: DisplayLuaValue::List(
804 copy_directories
805 .iter()
806 .map(|path_buf| {
807 DisplayLuaValue::String(path_buf.to_string_lossy().to_string())
808 })
809 .collect(),
810 ),
811 });
812 }
813 if let Some(patches) = &self.patches {
814 result.push(DisplayLuaKV {
815 key: "patches".to_string(),
816 value: DisplayLuaValue::Table(
817 patches
818 .iter()
819 .map(|(key, value)| DisplayLuaKV {
820 key: key.to_string_lossy().to_string(),
821 value: DisplayLuaValue::String(value.clone()),
822 })
823 .collect(),
824 ),
825 });
826 }
827 if let Some(target_path) = &self.target_path {
828 result.push(DisplayLuaKV {
829 key: "target_path".to_string(),
830 value: DisplayLuaValue::String(target_path.to_string_lossy().to_string()),
831 });
832 }
833 if let Some(default_features) = &self.default_features {
834 result.push(DisplayLuaKV {
835 key: "default_features".to_string(),
836 value: DisplayLuaValue::Boolean(*default_features),
837 });
838 }
839 if let Some(include) = &self.include {
840 result.push(DisplayLuaKV {
841 key: "include".to_string(),
842 value: DisplayLuaValue::Table(
843 include
844 .iter()
845 .map(|(key, value)| DisplayLuaKV {
846 key: match key {
847 LuaTableKey::StringKey(s) => s.clone(),
848 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
849 },
850 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
851 })
852 .collect(),
853 ),
854 });
855 }
856 if let Some(features) = &self.features {
857 result.push(DisplayLuaKV {
858 key: "features".to_string(),
859 value: DisplayLuaValue::List(
860 features
861 .iter()
862 .map(|feature| DisplayLuaValue::String(feature.clone()))
863 .collect(),
864 ),
865 });
866 }
867 if let Some(lang) = &self.lang {
868 result.push(DisplayLuaKV {
869 key: "lang".to_string(),
870 value: DisplayLuaValue::String(lang.to_string()),
871 });
872 }
873 if let Some(parser) = &self.parser {
874 result.push(DisplayLuaKV {
875 key: "parser".to_string(),
876 value: DisplayLuaValue::Boolean(*parser),
877 });
878 }
879 if let Some(generate) = &self.generate {
880 result.push(DisplayLuaKV {
881 key: "generate".to_string(),
882 value: DisplayLuaValue::Boolean(*generate),
883 });
884 }
885 if let Some(location) = &self.location {
886 result.push(DisplayLuaKV {
887 key: "location".to_string(),
888 value: DisplayLuaValue::String(location.to_string_lossy().to_string()),
889 });
890 }
891 if let Some(queries) = &self.queries {
892 result.push(DisplayLuaKV {
893 key: "queries".to_string(),
894 value: DisplayLuaValue::Table(
895 queries
896 .iter()
897 .map(|(key, value)| DisplayLuaKV {
898 key: key.to_string_lossy().to_string(),
899 value: DisplayLuaValue::String(value.to_string()),
900 })
901 .collect(),
902 ),
903 });
904 }
905
906 DisplayLuaKV {
907 key: "build".to_string(),
908 value: DisplayLuaValue::Table(result),
909 }
910 }
911}
912
913#[derive(Debug, PartialEq, Deserialize, Clone)]
915#[serde(rename_all = "lowercase", remote = "BuildType")]
916pub(crate) enum BuildType {
917 Builtin,
919 Make,
921 CMake,
923 Command,
925 None,
927 LuaRock(String),
929 #[serde(rename = "rust-mlua")]
930 RustMlua,
931 #[serde(rename = "treesitter-parser")]
932 TreesitterParser,
933 Source,
934}
935
936impl<'de> Deserialize<'de> for BuildType {
939 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
940 where
941 D: Deserializer<'de>,
942 {
943 let s = String::deserialize(deserializer)?;
944 if s == "builtin" || s == "module" {
945 Ok(Self::Builtin)
946 } else {
947 match Self::deserialize(s.clone().into_deserializer()) {
948 Err(_) => Ok(Self::LuaRock(s)),
949 ok => ok,
950 }
951 }
952 }
953}
954
955impl Display for BuildType {
956 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
957 match self {
958 BuildType::Builtin => write!(f, "builtin"),
959 BuildType::Make => write!(f, "make"),
960 BuildType::CMake => write!(f, "cmake"),
961 BuildType::Command => write!(f, "command"),
962 BuildType::None => write!(f, "none"),
963 BuildType::LuaRock(s) => write!(f, "{}", s),
964 BuildType::RustMlua => write!(f, "rust-mlua"),
965 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
966 BuildType::Source => write!(f, "source"),
967 }
968 }
969}
970
971impl Default for BuildType {
972 fn default() -> Self {
973 Self::Builtin
974 }
975}
976
977#[derive(Default)]
978pub struct BuildInfo {
979 pub binaries: Vec<PathBuf>,
980}
981
982pub trait Build {
984 type Err: std::error::Error;
985
986 #[allow(clippy::too_many_arguments)]
987 fn run(
988 self,
989 output_paths: &RockLayout,
990 no_install: bool,
991 lua: &LuaInstallation,
992 external_dependencies: &HashMap<String, ExternalDependencyInfo>,
993 config: &Config,
994 tree: &Tree,
995 build_dir: &Path,
996 progress: &Progress<ProgressBar>,
997 ) -> impl Future<Output = Result<BuildInfo, Self::Err>> + Send;
998}
999
1000#[cfg(test)]
1001mod tests {
1002
1003 use super::*;
1004
1005 #[tokio::test]
1006 pub async fn deserialize_build_type() {
1007 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
1008 assert_eq!(build_type, BuildType::Builtin);
1009 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
1010 assert_eq!(build_type, BuildType::Builtin);
1011 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1012 assert_eq!(build_type, BuildType::Make);
1013 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1014 assert_eq!(
1015 build_type,
1016 BuildType::LuaRock("custom_build_backend".into())
1017 );
1018 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1019 assert_eq!(build_type, BuildType::RustMlua);
1020 }
1021}