1mod builtin;
2mod cmake;
3mod make;
4mod rust_mlua;
5mod tree_sitter;
6
7pub use builtin::{BuiltinBuildSpec, LuaModule, ModulePaths, ModuleSpec, ParseLuaModuleError};
8pub use cmake::*;
9pub use make::*;
10use path_slash::PathBufExt;
11pub use rust_mlua::*;
12pub use tree_sitter::*;
13
14use builtin::{ModulePathsMissingSources, ModuleSpecAmbiguousPlatformOverride, ModuleSpecInternal};
15
16use itertools::Itertools;
17
18use std::{
19 collections::HashMap, convert::Infallible, env::consts::DLL_EXTENSION, env::consts::DLL_PREFIX,
20 fmt::Display, path::PathBuf, str::FromStr,
21};
22use thiserror::Error;
23
24use serde::{de, de::IntoDeserializer, Deserialize, Deserializer};
25
26use crate::{
27 lua_rockspec::per_platform_from_intermediate,
28 package::{PackageName, PackageReq},
29 rockspec::lua_dependency::LuaDependencySpec,
30};
31
32use super::{
33 DisplayAsLuaKV, DisplayAsLuaValue, DisplayLuaKV, DisplayLuaValue, LuaTableKey, LuaValueSeed,
34 PartialOverride, PerPlatform, PlatformOverridable,
35};
36
37#[derive(Clone, Debug, PartialEq)]
39pub struct BuildSpec {
40 pub build_backend: Option<BuildBackendSpec>,
42 pub install: InstallSpec,
46 pub copy_directories: Vec<PathBuf>,
48 pub patches: HashMap<PathBuf, String>,
52}
53
54impl Default for BuildSpec {
55 fn default() -> Self {
56 Self {
57 build_backend: Some(BuildBackendSpec::default()),
58 install: InstallSpec::default(),
59 copy_directories: Vec::default(),
60 patches: HashMap::default(),
61 }
62 }
63}
64
65#[derive(Error, Debug)]
66pub enum BuildSpecInternalError {
67 #[error("'builtin' modules should not have list elements")]
68 ModulesHaveListElements,
69 #[error("no 'modules' specified for the 'rust-mlua' build backend")]
70 NoModulesSpecified,
71 #[error("no 'lang' specified for 'treesitter-parser' build backend")]
72 NoTreesitterParserLanguageSpecified,
73 #[error("invalid 'rust-mlua' modules format")]
74 InvalidRustMLuaFormat,
75 #[error(transparent)]
76 ModulePathsMissingSources(#[from] ModulePathsMissingSources),
77 #[error(transparent)]
78 ParseLuaModuleError(#[from] ParseLuaModuleError),
79}
80
81impl BuildSpec {
82 pub(crate) fn from_internal_spec(
83 internal: BuildSpecInternal,
84 ) -> Result<Self, BuildSpecInternalError> {
85 let build_backend = match internal.build_type.unwrap_or_default() {
86 BuildType::Builtin => Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
87 modules: internal
88 .builtin_spec
89 .unwrap_or_default()
90 .into_iter()
91 .map(|(key, module_spec_internal)| {
92 let key_str = match key {
93 LuaTableKey::IntKey(_) => {
94 Err(BuildSpecInternalError::ModulesHaveListElements)
95 }
96 LuaTableKey::StringKey(str) => Ok(LuaModule::from_str(str.as_str())?),
97 }?;
98 match ModuleSpec::from_internal(module_spec_internal) {
99 Ok(module_spec) => Ok((key_str, module_spec)),
100 Err(err) => Err(err.into()),
101 }
102 })
103 .collect::<Result<HashMap<LuaModule, ModuleSpec>, BuildSpecInternalError>>()?,
104 })),
105 BuildType::Make => {
106 let default = MakeBuildSpec::default();
107 Some(BuildBackendSpec::Make(MakeBuildSpec {
108 makefile: internal.makefile.unwrap_or(default.makefile),
109 build_target: internal.make_build_target,
110 build_pass: internal.build_pass.unwrap_or(default.build_pass),
111 install_target: internal
112 .make_install_target
113 .unwrap_or(default.install_target),
114 install_pass: internal.install_pass.unwrap_or(default.install_pass),
115 build_variables: internal.make_build_variables.unwrap_or_default(),
116 install_variables: internal.make_install_variables.unwrap_or_default(),
117 variables: internal.variables.unwrap_or_default(),
118 }))
119 }
120 BuildType::CMake => {
121 let default = CMakeBuildSpec::default();
122 Some(BuildBackendSpec::CMake(CMakeBuildSpec {
123 cmake_lists_content: internal.cmake_lists_content,
124 build_pass: internal.build_pass.unwrap_or(default.build_pass),
125 install_pass: internal.install_pass.unwrap_or(default.install_pass),
126 variables: internal.variables.unwrap_or_default(),
127 }))
128 }
129 BuildType::Command => Some(BuildBackendSpec::Command(CommandBuildSpec {
130 build_command: internal.build_command,
131 install_command: internal.install_command,
132 })),
133 BuildType::None => None,
134 BuildType::LuaRock(s) => Some(BuildBackendSpec::LuaRock(s)),
135 BuildType::RustMlua => Some(BuildBackendSpec::RustMlua(RustMluaBuildSpec {
136 modules: internal
137 .builtin_spec
138 .ok_or(BuildSpecInternalError::NoModulesSpecified)?
139 .into_iter()
140 .map(|(key, value)| match (key, value) {
141 (LuaTableKey::IntKey(_), ModuleSpecInternal::SourcePath(module)) => {
142 let mut rust_lib: PathBuf =
143 format!("{DLL_PREFIX}{}", module.display()).into();
144 rust_lib.set_extension(DLL_EXTENSION);
145 Ok((module.to_string_lossy().to_string(), rust_lib))
146 }
147 (
148 LuaTableKey::StringKey(module_name),
149 ModuleSpecInternal::SourcePath(module),
150 ) => {
151 let mut rust_lib: PathBuf =
152 format!("{DLL_PREFIX}{}", module.display()).into();
153 rust_lib.set_extension(DLL_EXTENSION);
154 Ok((module_name, rust_lib))
155 }
156 _ => Err(BuildSpecInternalError::InvalidRustMLuaFormat),
157 })
158 .try_collect()?,
159 target_path: internal.target_path.unwrap_or("target".into()),
160 default_features: internal.default_features.unwrap_or(true),
161 features: internal.features.unwrap_or_default(),
162 cargo_extra_args: internal.cargo_extra_args.unwrap_or_default(),
163 include: internal
164 .include
165 .unwrap_or_default()
166 .into_iter()
167 .map(|(key, dest)| match key {
168 LuaTableKey::IntKey(_) => (dest.clone(), dest),
169 LuaTableKey::StringKey(src) => (src.into(), dest),
170 })
171 .collect(),
172 })),
173 BuildType::TreesitterParser => Some(BuildBackendSpec::TreesitterParser(
174 TreesitterParserBuildSpec {
175 lang: internal
176 .lang
177 .ok_or(BuildSpecInternalError::NoTreesitterParserLanguageSpecified)?,
178 parser: internal.parser.unwrap_or(false),
179 generate: internal.generate.unwrap_or(false),
180 location: internal.location,
181 queries: internal.queries.unwrap_or_default(),
182 },
183 )),
184 BuildType::Source => Some(BuildBackendSpec::Source),
185 };
186 Ok(Self {
187 build_backend,
188 install: internal.install.unwrap_or_default(),
189 copy_directories: internal.copy_directories.unwrap_or_default(),
190 patches: internal.patches.unwrap_or_default(),
191 })
192 }
193}
194
195impl TryFrom<BuildSpecInternal> for BuildSpec {
196 type Error = BuildSpecInternalError;
197
198 fn try_from(internal: BuildSpecInternal) -> Result<Self, Self::Error> {
199 BuildSpec::from_internal_spec(internal)
200 }
201}
202
203impl<'de> Deserialize<'de> for BuildSpec {
204 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205 where
206 D: Deserializer<'de>,
207 {
208 let internal = BuildSpecInternal::deserialize(deserializer)?;
209 BuildSpec::from_internal_spec(internal).map_err(de::Error::custom)
210 }
211}
212
213impl<'de> Deserialize<'de> for PerPlatform<BuildSpec> {
218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219 where
220 D: Deserializer<'de>,
221 {
222 per_platform_from_intermediate::<_, BuildSpecInternal, _>(deserializer)
223 }
224}
225
226impl Default for BuildBackendSpec {
227 fn default() -> Self {
228 Self::Builtin(BuiltinBuildSpec::default())
229 }
230}
231
232#[derive(Debug, PartialEq, Clone)]
239pub enum BuildBackendSpec {
240 Builtin(BuiltinBuildSpec),
241 Make(MakeBuildSpec),
242 CMake(CMakeBuildSpec),
243 Command(CommandBuildSpec),
244 LuaRock(String),
245 RustMlua(RustMluaBuildSpec),
246 TreesitterParser(TreesitterParserBuildSpec),
247 Source,
253}
254
255impl BuildBackendSpec {
256 pub(crate) fn can_use_build_dependencies(&self) -> bool {
257 match self {
258 Self::Make(_) | Self::CMake(_) | Self::Command(_) | Self::LuaRock(_) => true,
259 Self::Builtin(_) | Self::RustMlua(_) | Self::TreesitterParser(_) | Self::Source => {
260 false
261 }
262 }
263 }
264}
265
266#[derive(Debug, PartialEq, Clone)]
268pub struct CommandBuildSpec {
269 pub build_command: Option<String>,
270 pub install_command: Option<String>,
271}
272
273#[derive(Clone, Debug)]
274struct LuaPathBufTable(HashMap<LuaTableKey, PathBuf>);
275
276impl<'de> Deserialize<'de> for LuaPathBufTable {
277 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
278 Ok(LuaPathBufTable(
279 deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
280 ))
281 }
282}
283
284impl LuaPathBufTable {
285 fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
286 where
287 S: FromStr + Eq + std::hash::Hash,
288 {
289 self.0
290 .into_iter()
291 .map(|(key, value)| {
292 let key = match key {
293 LuaTableKey::IntKey(_) => value
294 .with_extension("")
295 .file_name()
296 .unwrap_or_default()
297 .to_string_lossy()
298 .to_string(),
299 LuaTableKey::StringKey(key) => key,
300 };
301 Ok((S::from_str(&key)?, value))
302 })
303 .try_collect()
304 }
305}
306
307#[derive(Clone, Debug)]
308struct LibPathBufTable(HashMap<LuaTableKey, PathBuf>);
309
310impl<'de> Deserialize<'de> for LibPathBufTable {
311 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
312 Ok(LibPathBufTable(
313 deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
314 ))
315 }
316}
317
318impl LibPathBufTable {
319 fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
320 where
321 S: FromStr + Eq + std::hash::Hash,
322 {
323 self.0
324 .into_iter()
325 .map(|(key, value)| {
326 let key = match key {
327 LuaTableKey::IntKey(_) => value
328 .file_name()
329 .unwrap_or_default()
330 .to_string_lossy()
331 .to_string(),
332 LuaTableKey::StringKey(key) => key,
333 };
334 Ok((S::from_str(&key)?, value))
335 })
336 .try_collect()
337 }
338}
339
340#[derive(Debug, PartialEq, Default, Deserialize, Clone, lux_macros::DisplayAsLuaKV)]
349#[display_lua(key = "install")]
350pub struct InstallSpec {
351 #[serde(default, deserialize_with = "deserialize_module_path_map")]
353 pub lua: HashMap<LuaModule, PathBuf>,
354 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
356 pub lib: HashMap<String, PathBuf>,
357 #[serde(default)]
359 pub conf: HashMap<String, PathBuf>,
360 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
364 pub bin: HashMap<String, PathBuf>,
365}
366
367fn deserialize_module_path_map<'de, D>(
368 deserializer: D,
369) -> Result<HashMap<LuaModule, PathBuf>, D::Error>
370where
371 D: Deserializer<'de>,
372{
373 let modules = LuaPathBufTable::deserialize(deserializer)?;
374 modules.coerce().map_err(de::Error::custom)
375}
376
377fn deserialize_file_name_path_map<'de, D>(
378 deserializer: D,
379) -> Result<HashMap<String, PathBuf>, D::Error>
380where
381 D: Deserializer<'de>,
382{
383 let binaries = LibPathBufTable::deserialize(deserializer)?;
384 binaries.coerce().map_err(de::Error::custom)
385}
386
387fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
388where
389 D: Deserializer<'de>,
390{
391 let value: Option<serde_value::Value> = Option::deserialize(deserializer)?;
392 let copy_directories: Option<Vec<String>> = match value {
393 Some(value) => Some(value.deserialize_into().map_err(de::Error::custom)?),
394 None => None,
395 };
396 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
397 match special_directories
398 .into_iter()
399 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
400 {
401 Some(d) => Err(format!(
404 "directory '{d}' in copy_directories clashes with the .rock format", )),
406 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
407 }
408 .map_err(de::Error::custom)
409}
410
411fn deserialize_map_or_seq<'de, D, V>(
413 deserializer: D,
414) -> Result<Option<HashMap<LuaTableKey, V>>, D::Error>
415where
416 D: Deserializer<'de>,
417 V: de::DeserializeOwned,
418{
419 match de::DeserializeSeed::deserialize(LuaValueSeed, deserializer).map_err(de::Error::custom)? {
420 serde_value::Value::Map(map) => map
421 .into_iter()
422 .map(|(k, v)| {
423 let key = match k {
424 serde_value::Value::I64(i) => LuaTableKey::IntKey(i as u64),
425 serde_value::Value::U64(u) => LuaTableKey::IntKey(u),
426 serde_value::Value::String(s) => LuaTableKey::StringKey(s),
427 other => {
428 return Err(de::Error::custom(format!("unexpected map key: {other:?}")))
429 }
430 };
431 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
432 Ok((key, val))
433 })
434 .try_collect()
435 .map(Some),
436 serde_value::Value::Seq(seq) => seq
437 .into_iter()
438 .enumerate()
439 .map(|(i, v)| {
440 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
441 Ok((LuaTableKey::IntKey(i as u64 + 1), val))
442 })
443 .try_collect()
444 .map(Some),
445 serde_value::Value::Unit => Ok(None),
446 other => Err(de::Error::custom(format!(
447 "expected a table or nil, got {other:?}"
448 ))),
449 }
450}
451
452fn display_builtin_spec(spec: &HashMap<LuaTableKey, ModuleSpecInternal>) -> DisplayLuaValue {
453 DisplayLuaValue::Table(
454 spec.iter()
455 .map(|(key, value)| DisplayLuaKV {
456 key: match key {
457 LuaTableKey::StringKey(s) => s.clone(),
458 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
459 },
460 value: value.display_lua_value(),
461 })
462 .collect(),
463 )
464}
465
466fn display_path_string_map(map: &HashMap<PathBuf, String>) -> DisplayLuaValue {
467 DisplayLuaValue::Table(
468 map.iter()
469 .map(|(k, v)| DisplayLuaKV {
470 key: k.to_slash_lossy().into_owned(),
471 value: DisplayLuaValue::String(v.clone()),
472 })
473 .collect(),
474 )
475}
476
477fn display_include(include: &HashMap<LuaTableKey, PathBuf>) -> DisplayLuaValue {
478 DisplayLuaValue::Table(
479 include
480 .iter()
481 .map(|(key, value)| DisplayLuaKV {
482 key: match key {
483 LuaTableKey::StringKey(s) => s.clone(),
484 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
485 },
486 value: DisplayLuaValue::String(value.to_slash_lossy().into_owned()),
487 })
488 .collect(),
489 )
490}
491
492#[derive(Debug, PartialEq, Deserialize, Default, Clone, lux_macros::DisplayAsLuaKV)]
493#[display_lua(key = "build")]
494pub(crate) struct BuildSpecInternal {
495 #[serde(rename = "type", default)]
496 #[display_lua(rename = "type")]
497 pub(crate) build_type: Option<BuildType>,
498 #[serde(
499 rename = "modules",
500 default,
501 deserialize_with = "deserialize_map_or_seq"
502 )]
503 #[display_lua(rename = "modules", convert_with = "display_builtin_spec")]
504 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
505 #[serde(default)]
506 pub(crate) makefile: Option<PathBuf>,
507 #[serde(rename = "build_target", default)]
508 #[display_lua(rename = "build_target")]
509 pub(crate) make_build_target: Option<String>,
510 #[serde(default)]
511 pub(crate) build_pass: Option<bool>,
512 #[serde(rename = "install_target", default)]
513 #[display_lua(rename = "install_target")]
514 pub(crate) make_install_target: Option<String>,
515 #[serde(default)]
516 pub(crate) install_pass: Option<bool>,
517 #[serde(rename = "build_variables", default)]
518 #[display_lua(rename = "build_variables")]
519 pub(crate) make_build_variables: Option<HashMap<String, String>>,
520 #[serde(rename = "install_variables", default)]
521 #[display_lua(rename = "install_variables")]
522 pub(crate) make_install_variables: Option<HashMap<String, String>>,
523 #[serde(default)]
524 pub(crate) variables: Option<HashMap<String, String>>,
525 #[serde(rename = "cmake", default)]
526 #[display_lua(rename = "cmake")]
527 pub(crate) cmake_lists_content: Option<String>,
528 #[serde(default)]
529 pub(crate) build_command: Option<String>,
530 #[serde(default)]
531 pub(crate) install_command: Option<String>,
532 #[serde(default)]
533 pub(crate) install: Option<InstallSpec>,
534 #[serde(default, deserialize_with = "deserialize_copy_directories")]
535 pub(crate) copy_directories: Option<Vec<PathBuf>>,
536 #[serde(default)]
537 #[display_lua(convert_with = "display_path_string_map")]
538 pub(crate) patches: Option<HashMap<PathBuf, String>>,
539 #[serde(default)]
540 pub(crate) target_path: Option<PathBuf>,
541 #[serde(default)]
542 pub(crate) default_features: Option<bool>,
543 #[serde(default)]
544 pub(crate) features: Option<Vec<String>>,
545 pub(crate) cargo_extra_args: Option<Vec<String>>,
546 #[serde(default, deserialize_with = "deserialize_map_or_seq")]
547 #[display_lua(convert_with = "display_include")]
548 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
549 #[serde(default)]
550 pub(crate) lang: Option<String>,
551 #[serde(default)]
552 pub(crate) parser: Option<bool>,
553 #[serde(default)]
554 pub(crate) generate: Option<bool>,
555 #[serde(default)]
556 pub(crate) location: Option<PathBuf>,
557 #[serde(default)]
558 #[display_lua(convert_with = "display_path_string_map")]
559 pub(crate) queries: Option<HashMap<PathBuf, String>>,
560}
561
562impl PartialOverride for BuildSpecInternal {
563 type Err = ModuleSpecAmbiguousPlatformOverride;
564
565 fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
566 override_build_spec_internal(self, override_spec)
567 }
568}
569
570impl PlatformOverridable for BuildSpecInternal {
571 type Err = Infallible;
572
573 fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
574 where
575 T: PlatformOverridable,
576 T: Default,
577 {
578 Ok(PerPlatform::default())
579 }
580}
581
582fn override_build_spec_internal(
583 base: &BuildSpecInternal,
584 override_spec: &BuildSpecInternal,
585) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
586 Ok(BuildSpecInternal {
587 build_type: override_opt(&override_spec.build_type, &base.build_type),
588 builtin_spec: match (
589 override_spec.builtin_spec.clone(),
590 base.builtin_spec.clone(),
591 ) {
592 (Some(override_val), Some(base_spec_map)) => {
593 Some(base_spec_map.into_iter().chain(override_val).try_fold(
594 HashMap::default(),
595 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
596 (k, module_spec_override)|
597 -> Result<
598 HashMap<LuaTableKey, ModuleSpecInternal>,
599 ModuleSpecAmbiguousPlatformOverride,
600 > {
601 let overridden = match acc.get(&k) {
602 None => module_spec_override,
603 Some(base_module_spec) => {
604 base_module_spec.apply_overrides(&module_spec_override)?
605 }
606 };
607 acc.insert(k, overridden);
608 Ok(acc)
609 },
610 )?)
611 }
612 (override_val @ Some(_), _) => override_val,
613 (_, base_val @ Some(_)) => base_val,
614 _ => None,
615 },
616 makefile: override_opt(&override_spec.makefile, &base.makefile),
617 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
618 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
619 make_install_target: override_opt(
620 &override_spec.make_install_target,
621 &base.make_install_target,
622 ),
623 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
624 make_build_variables: merge_map_opts(
625 &override_spec.make_build_variables,
626 &base.make_build_variables,
627 ),
628 make_install_variables: merge_map_opts(
629 &override_spec.make_install_variables,
630 &base.make_build_variables,
631 ),
632 variables: merge_map_opts(&override_spec.variables, &base.variables),
633 cmake_lists_content: override_opt(
634 &override_spec.cmake_lists_content,
635 &base.cmake_lists_content,
636 ),
637 build_command: override_opt(&override_spec.build_command, &base.build_command),
638 install_command: override_opt(&override_spec.install_command, &base.install_command),
639 install: override_opt(&override_spec.install, &base.install),
640 copy_directories: match (
641 override_spec.copy_directories.clone(),
642 base.copy_directories.clone(),
643 ) {
644 (Some(override_vec), Some(base_vec)) => {
645 let merged: Vec<PathBuf> =
646 base_vec.into_iter().chain(override_vec).unique().collect();
647 Some(merged)
648 }
649 (None, base_vec @ Some(_)) => base_vec,
650 (override_vec @ Some(_), None) => override_vec,
651 _ => None,
652 },
653 patches: override_opt(&override_spec.patches, &base.patches),
654 target_path: override_opt(&override_spec.target_path, &base.target_path),
655 default_features: override_opt(&override_spec.default_features, &base.default_features),
656 features: override_opt(&override_spec.features, &base.features),
657 cargo_extra_args: override_opt(&override_spec.cargo_extra_args, &base.cargo_extra_args),
658 include: merge_map_opts(&override_spec.include, &base.include),
659 lang: override_opt(&override_spec.lang, &base.lang),
660 parser: override_opt(&override_spec.parser, &base.parser),
661 generate: override_opt(&override_spec.generate, &base.generate),
662 location: override_opt(&override_spec.location, &base.location),
663 queries: merge_map_opts(&override_spec.queries, &base.queries),
664 })
665}
666
667fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
668 match override_opt.clone() {
669 override_val @ Some(_) => override_val,
670 None => base.clone(),
671 }
672}
673
674fn merge_map_opts<K, V>(
675 override_map: &Option<HashMap<K, V>>,
676 base_map: &Option<HashMap<K, V>>,
677) -> Option<HashMap<K, V>>
678where
679 K: Clone,
680 K: Eq,
681 K: std::hash::Hash,
682 V: Clone,
683{
684 match (override_map.clone(), base_map.clone()) {
685 (Some(override_map), Some(base_map)) => {
686 Some(base_map.into_iter().chain(override_map).collect())
687 }
688 (_, base_map @ Some(_)) => base_map,
689 (override_map @ Some(_), _) => override_map,
690 _ => None,
691 }
692}
693
694#[derive(Debug, PartialEq, Deserialize, Clone)]
696#[serde(rename_all = "lowercase", remote = "BuildType")]
697#[derive(Default)]
698pub(crate) enum BuildType {
699 #[default]
701 Builtin,
702 Make,
704 CMake,
706 Command,
708 None,
710 LuaRock(String),
712 #[serde(rename = "rust-mlua")]
713 RustMlua,
714 #[serde(rename = "treesitter-parser")]
715 TreesitterParser,
716 Source,
717}
718
719impl BuildType {
720 pub(crate) fn luarocks_build_backend(&self) -> Option<LuaDependencySpec> {
721 match self {
722 &BuildType::Builtin
723 | &BuildType::Make
724 | &BuildType::CMake
725 | &BuildType::Command
726 | &BuildType::None
727 | &BuildType::LuaRock(_)
728 | &BuildType::Source => None,
729 &BuildType::RustMlua => unsafe {
730 Some(
731 PackageReq::parse("luarocks-build-rust-mlua >= 0.2.6")
732 .unwrap_unchecked()
733 .into(),
734 )
735 },
736 &BuildType::TreesitterParser => {
737 Some(PackageName::new("luarocks-build-treesitter-parser >= 6.1.0".into()).into())
738 } }
741 }
742}
743
744impl<'de> Deserialize<'de> for BuildType {
747 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748 where
749 D: Deserializer<'de>,
750 {
751 let s = String::deserialize(deserializer)?;
752 if s == "builtin" || s == "module" {
753 Ok(Self::Builtin)
754 } else {
755 match Self::deserialize(s.clone().into_deserializer()) {
756 Err(_) => Ok(Self::LuaRock(s)),
757 ok => ok,
758 }
759 }
760 }
761}
762
763impl Display for BuildType {
764 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
765 match self {
766 BuildType::Builtin => write!(f, "builtin"),
767 BuildType::Make => write!(f, "make"),
768 BuildType::CMake => write!(f, "cmake"),
769 BuildType::Command => write!(f, "command"),
770 BuildType::None => write!(f, "none"),
771 BuildType::LuaRock(s) => write!(f, "{s}"),
772 BuildType::RustMlua => write!(f, "rust-mlua"),
773 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
774 BuildType::Source => write!(f, "source"),
775 }
776 }
777}
778
779impl DisplayAsLuaValue for BuildType {
780 fn display_lua_value(&self) -> DisplayLuaValue {
781 DisplayLuaValue::String(self.to_string())
782 }
783}
784
785impl DisplayAsLuaValue for InstallSpec {
786 fn display_lua_value(&self) -> DisplayLuaValue {
787 self.display_lua().value
788 }
789}
790
791#[cfg(test)]
792mod tests {
793
794 use super::*;
795
796 fn eval_lua_global<T: serde::de::DeserializeOwned>(code: &str, key: &'static str) -> T {
797 use ottavino::{Closure, Executor, Fuel, Lua};
798 use ottavino_util::serde::from_value;
799 Lua::core()
800 .try_enter(|ctx| {
801 let closure = Closure::load(ctx, None, code.as_bytes())?;
802 let executor = Executor::start(ctx, closure.into(), ());
803 executor.step(ctx, &mut Fuel::with(i32::MAX))?;
804 from_value(ctx.globals().get_value(ctx, key)).map_err(ottavino::Error::from)
805 })
806 .unwrap()
807 }
808
809 #[tokio::test]
810 pub async fn deserialize_build_type() {
811 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
812 assert_eq!(build_type, BuildType::Builtin);
813 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
814 assert_eq!(build_type, BuildType::Builtin);
815 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
816 assert_eq!(build_type, BuildType::Make);
817 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
818 assert_eq!(
819 build_type,
820 BuildType::LuaRock("custom_build_backend".into())
821 );
822 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
823 assert_eq!(build_type, BuildType::RustMlua);
824 }
825
826 #[test]
827 pub fn install_spec_roundtrip() {
828 let spec = InstallSpec {
829 lua: HashMap::from([(
830 "mymod".parse::<LuaModule>().unwrap(),
831 "src/mymod.lua".into(),
832 )]),
833 lib: HashMap::from([("mylib".into(), "lib/mylib.so".into())]),
834 conf: HashMap::from([("myconf".into(), "conf/myconf.cfg".into())]),
835 bin: HashMap::from([("mybinary".into(), "bin/mybinary".into())]),
836 };
837 let lua = spec.display_lua().to_string();
838 let restored: InstallSpec = eval_lua_global(&lua, "install");
839 assert_eq!(spec, restored);
840 }
841
842 #[test]
843 pub fn install_spec_empty_roundtrip() {
844 let spec = InstallSpec::default();
845 let lua = spec.display_lua().to_string();
846 let lua = if lua.trim().is_empty() {
847 "install = {}".to_string()
848 } else {
849 lua
850 };
851 let restored: InstallSpec = eval_lua_global(&lua, "install");
852 assert_eq!(spec, restored);
853 }
854
855 #[test]
856 pub fn build_spec_internal_builtin_roundtrip() {
857 let spec = BuildSpecInternal {
858 build_type: Some(BuildType::Builtin),
859 builtin_spec: Some(HashMap::from([(
860 LuaTableKey::StringKey("mymod".into()),
861 ModuleSpecInternal::SourcePath("src/mymod.lua".into()),
862 )])),
863 install: Some(InstallSpec {
864 lua: HashMap::from([(
865 "extra".parse::<LuaModule>().unwrap(),
866 "src/extra.lua".into(),
867 )]),
868 bin: HashMap::from([("mytool".into(), "bin/mytool".into())]),
869 ..Default::default()
870 }),
871 copy_directories: Some(vec!["docs".into()]),
872 ..Default::default()
873 };
874 let lua = spec.display_lua().to_string();
875 let restored: BuildSpecInternal = eval_lua_global(&lua, "build");
876 assert_eq!(spec, restored);
877 }
878
879 #[test]
880 pub fn build_spec_internal_make_roundtrip() {
881 let spec = BuildSpecInternal {
882 build_type: Some(BuildType::Make),
883 makefile: Some("GNUmakefile".into()),
884 make_build_target: Some("all".into()),
885 make_install_target: Some("install".into()),
886 make_build_variables: Some(HashMap::from([("CFLAGS".into(), "-O2".into())])),
887 make_install_variables: Some(HashMap::from([("PREFIX".into(), "/usr/local".into())])),
888 variables: Some(HashMap::from([("LUA_LIBDIR".into(), "/usr/lib".into())])),
889 ..Default::default()
890 };
891 let lua = spec.display_lua().to_string();
892 let restored: BuildSpecInternal = eval_lua_global(&lua, "build");
893 assert_eq!(spec, restored);
894 }
895}