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