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