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