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