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