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)]
352pub struct InstallSpec {
353 #[serde(default, deserialize_with = "deserialize_module_path_map")]
355 pub lua: HashMap<LuaModule, PathBuf>,
356 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
358 pub lib: HashMap<String, PathBuf>,
359 #[serde(default)]
361 pub conf: HashMap<String, PathBuf>,
362 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
366 pub bin: HashMap<String, PathBuf>,
367}
368
369fn deserialize_module_path_map<'de, D>(
370 deserializer: D,
371) -> Result<HashMap<LuaModule, PathBuf>, D::Error>
372where
373 D: Deserializer<'de>,
374{
375 let modules = LuaPathBufTable::deserialize(deserializer)?;
376 modules.coerce().map_err(de::Error::custom)
377}
378
379fn deserialize_file_name_path_map<'de, D>(
380 deserializer: D,
381) -> Result<HashMap<String, PathBuf>, D::Error>
382where
383 D: Deserializer<'de>,
384{
385 let binaries = LibPathBufTable::deserialize(deserializer)?;
386 binaries.coerce().map_err(de::Error::custom)
387}
388
389fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
390where
391 D: Deserializer<'de>,
392{
393 let value: Option<serde_value::Value> = Option::deserialize(deserializer)?;
394 let copy_directories: Option<Vec<String>> = match value {
395 Some(value) => Some(value.deserialize_into().map_err(de::Error::custom)?),
396 None => None,
397 };
398 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
399 match special_directories
400 .into_iter()
401 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
402 {
403 Some(d) => Err(format!(
406 "directory '{d}' in copy_directories clashes with the .rock format", )),
408 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
409 }
410 .map_err(de::Error::custom)
411}
412
413impl DisplayAsLuaKV for InstallSpec {
414 fn display_lua(&self) -> DisplayLuaKV {
415 let mut result = Vec::new();
416
417 let mut lua_entries = Vec::new();
418 self.lua.iter().for_each(|(key, value)| {
419 lua_entries.push(DisplayLuaKV {
420 key: key.to_string(),
421 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
422 });
423 });
424 if !lua_entries.is_empty() {
425 result.push(DisplayLuaKV {
426 key: "lua".to_string(),
427 value: DisplayLuaValue::Table(lua_entries),
428 });
429 }
430
431 let mut lib_entries = Vec::new();
432 self.lib.iter().for_each(|(key, value)| {
433 lib_entries.push(DisplayLuaKV {
434 key: key.to_string(),
435 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
436 });
437 });
438 if !lib_entries.is_empty() {
439 result.push(DisplayLuaKV {
440 key: "lib".to_string(),
441 value: DisplayLuaValue::Table(lib_entries),
442 });
443 }
444
445 let mut bin_entries = Vec::new();
446 self.bin.iter().for_each(|(key, value)| {
447 bin_entries.push(DisplayLuaKV {
448 key: key.clone(),
449 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
450 });
451 });
452 if !bin_entries.is_empty() {
453 result.push(DisplayLuaKV {
454 key: "bin".to_string(),
455 value: DisplayLuaValue::Table(bin_entries),
456 });
457 }
458
459 let mut conf_entries = Vec::new();
460 self.conf.iter().for_each(|(key, value)| {
461 conf_entries.push(DisplayLuaKV {
462 key: key.clone(),
463 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
464 });
465 });
466 if !conf_entries.is_empty() {
467 result.push(DisplayLuaKV {
468 key: "conf".to_string(),
469 value: DisplayLuaValue::Table(conf_entries),
470 });
471 }
472
473 DisplayLuaKV {
474 key: "install".to_string(),
475 value: DisplayLuaValue::Table(result),
476 }
477 }
478}
479
480fn deserialize_map_or_seq<'de, D, V>(
482 deserializer: D,
483) -> Result<Option<HashMap<LuaTableKey, V>>, D::Error>
484where
485 D: Deserializer<'de>,
486 V: de::DeserializeOwned,
487{
488 match de::DeserializeSeed::deserialize(LuaValueSeed, deserializer).map_err(de::Error::custom)? {
489 serde_value::Value::Map(map) => map
490 .into_iter()
491 .map(|(k, v)| {
492 let key = match k {
493 serde_value::Value::I64(i) => LuaTableKey::IntKey(i as u64),
494 serde_value::Value::U64(u) => LuaTableKey::IntKey(u),
495 serde_value::Value::String(s) => LuaTableKey::StringKey(s),
496 other => {
497 return Err(de::Error::custom(format!("unexpected map key: {other:?}")))
498 }
499 };
500 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
501 Ok((key, val))
502 })
503 .try_collect()
504 .map(Some),
505 serde_value::Value::Seq(seq) => seq
506 .into_iter()
507 .enumerate()
508 .map(|(i, v)| {
509 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
510 Ok((LuaTableKey::IntKey(i as u64 + 1), val))
511 })
512 .try_collect()
513 .map(Some),
514 serde_value::Value::Unit => Ok(None),
515 other => Err(de::Error::custom(format!(
516 "expected a table or nil, got {other:?}"
517 ))),
518 }
519}
520
521#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
522pub(crate) struct BuildSpecInternal {
523 #[serde(rename = "type", default)]
524 pub(crate) build_type: Option<BuildType>,
525 #[serde(
526 rename = "modules",
527 default,
528 deserialize_with = "deserialize_map_or_seq"
529 )]
530 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
531 #[serde(default)]
532 pub(crate) makefile: Option<PathBuf>,
533 #[serde(rename = "build_target", default)]
534 pub(crate) make_build_target: Option<String>,
535 #[serde(default)]
536 pub(crate) build_pass: Option<bool>,
537 #[serde(rename = "install_target", default)]
538 pub(crate) make_install_target: Option<String>,
539 #[serde(default)]
540 pub(crate) install_pass: Option<bool>,
541 #[serde(rename = "build_variables", default)]
542 pub(crate) make_build_variables: Option<HashMap<String, String>>,
543 #[serde(rename = "install_variables", default)]
544 pub(crate) make_install_variables: Option<HashMap<String, String>>,
545 #[serde(default)]
546 pub(crate) variables: Option<HashMap<String, String>>,
547 #[serde(rename = "cmake", default)]
548 pub(crate) cmake_lists_content: Option<String>,
549 #[serde(default)]
550 pub(crate) build_command: Option<String>,
551 #[serde(default)]
552 pub(crate) install_command: Option<String>,
553 #[serde(default)]
554 pub(crate) install: Option<InstallSpec>,
555 #[serde(default, deserialize_with = "deserialize_copy_directories")]
556 pub(crate) copy_directories: Option<Vec<PathBuf>>,
557 #[serde(default)]
558 pub(crate) patches: Option<HashMap<PathBuf, String>>,
559 #[serde(default)]
561 pub(crate) target_path: Option<PathBuf>,
562 #[serde(default)]
563 pub(crate) default_features: Option<bool>,
564 #[serde(default)]
565 pub(crate) features: Option<Vec<String>>,
566 pub(crate) cargo_extra_args: Option<Vec<String>>,
567 #[serde(default, deserialize_with = "deserialize_map_or_seq")]
568 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
569 #[serde(default)]
571 pub(crate) lang: Option<String>,
572 #[serde(default)]
573 pub(crate) parser: Option<bool>,
574 #[serde(default)]
575 pub(crate) generate: Option<bool>,
576 #[serde(default)]
577 pub(crate) location: Option<PathBuf>,
578 #[serde(default)]
579 pub(crate) queries: Option<HashMap<PathBuf, String>>,
580}
581
582impl PartialOverride for BuildSpecInternal {
583 type Err = ModuleSpecAmbiguousPlatformOverride;
584
585 fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
586 override_build_spec_internal(self, override_spec)
587 }
588}
589
590impl PlatformOverridable for BuildSpecInternal {
591 type Err = Infallible;
592
593 fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
594 where
595 T: PlatformOverridable,
596 T: Default,
597 {
598 Ok(PerPlatform::default())
599 }
600}
601
602fn override_build_spec_internal(
603 base: &BuildSpecInternal,
604 override_spec: &BuildSpecInternal,
605) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
606 Ok(BuildSpecInternal {
607 build_type: override_opt(&override_spec.build_type, &base.build_type),
608 builtin_spec: match (
609 override_spec.builtin_spec.clone(),
610 base.builtin_spec.clone(),
611 ) {
612 (Some(override_val), Some(base_spec_map)) => {
613 Some(base_spec_map.into_iter().chain(override_val).try_fold(
614 HashMap::default(),
615 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
616 (k, module_spec_override)|
617 -> Result<
618 HashMap<LuaTableKey, ModuleSpecInternal>,
619 ModuleSpecAmbiguousPlatformOverride,
620 > {
621 let overridden = match acc.get(&k) {
622 None => module_spec_override,
623 Some(base_module_spec) => {
624 base_module_spec.apply_overrides(&module_spec_override)?
625 }
626 };
627 acc.insert(k, overridden);
628 Ok(acc)
629 },
630 )?)
631 }
632 (override_val @ Some(_), _) => override_val,
633 (_, base_val @ Some(_)) => base_val,
634 _ => None,
635 },
636 makefile: override_opt(&override_spec.makefile, &base.makefile),
637 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
638 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
639 make_install_target: override_opt(
640 &override_spec.make_install_target,
641 &base.make_install_target,
642 ),
643 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
644 make_build_variables: merge_map_opts(
645 &override_spec.make_build_variables,
646 &base.make_build_variables,
647 ),
648 make_install_variables: merge_map_opts(
649 &override_spec.make_install_variables,
650 &base.make_build_variables,
651 ),
652 variables: merge_map_opts(&override_spec.variables, &base.variables),
653 cmake_lists_content: override_opt(
654 &override_spec.cmake_lists_content,
655 &base.cmake_lists_content,
656 ),
657 build_command: override_opt(&override_spec.build_command, &base.build_command),
658 install_command: override_opt(&override_spec.install_command, &base.install_command),
659 install: override_opt(&override_spec.install, &base.install),
660 copy_directories: match (
661 override_spec.copy_directories.clone(),
662 base.copy_directories.clone(),
663 ) {
664 (Some(override_vec), Some(base_vec)) => {
665 let merged: Vec<PathBuf> =
666 base_vec.into_iter().chain(override_vec).unique().collect();
667 Some(merged)
668 }
669 (None, base_vec @ Some(_)) => base_vec,
670 (override_vec @ Some(_), None) => override_vec,
671 _ => None,
672 },
673 patches: override_opt(&override_spec.patches, &base.patches),
674 target_path: override_opt(&override_spec.target_path, &base.target_path),
675 default_features: override_opt(&override_spec.default_features, &base.default_features),
676 features: override_opt(&override_spec.features, &base.features),
677 cargo_extra_args: override_opt(&override_spec.cargo_extra_args, &base.cargo_extra_args),
678 include: merge_map_opts(&override_spec.include, &base.include),
679 lang: override_opt(&override_spec.lang, &base.lang),
680 parser: override_opt(&override_spec.parser, &base.parser),
681 generate: override_opt(&override_spec.generate, &base.generate),
682 location: override_opt(&override_spec.location, &base.location),
683 queries: merge_map_opts(&override_spec.queries, &base.queries),
684 })
685}
686
687fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
688 match override_opt.clone() {
689 override_val @ Some(_) => override_val,
690 None => base.clone(),
691 }
692}
693
694fn merge_map_opts<K, V>(
695 override_map: &Option<HashMap<K, V>>,
696 base_map: &Option<HashMap<K, V>>,
697) -> Option<HashMap<K, V>>
698where
699 K: Clone,
700 K: Eq,
701 K: std::hash::Hash,
702 V: Clone,
703{
704 match (override_map.clone(), base_map.clone()) {
705 (Some(override_map), Some(base_map)) => {
706 Some(base_map.into_iter().chain(override_map).collect())
707 }
708 (_, base_map @ Some(_)) => base_map,
709 (override_map @ Some(_), _) => override_map,
710 _ => None,
711 }
712}
713
714impl DisplayAsLuaKV for BuildSpecInternal {
715 fn display_lua(&self) -> DisplayLuaKV {
716 let mut result = Vec::new();
717
718 if let Some(build_type) = &self.build_type {
719 result.push(DisplayLuaKV {
720 key: "type".to_string(),
721 value: DisplayLuaValue::String(build_type.to_string()),
722 });
723 }
724 if let Some(builtin_spec) = &self.builtin_spec {
725 result.push(DisplayLuaKV {
726 key: "modules".to_string(),
727 value: DisplayLuaValue::Table(
728 builtin_spec
729 .iter()
730 .map(|(key, value)| DisplayLuaKV {
731 key: match key {
732 LuaTableKey::StringKey(s) => s.clone(),
733 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
734 },
735 value: value.display_lua_value(),
736 })
737 .collect(),
738 ),
739 });
740 }
741 if let Some(makefile) = &self.makefile {
742 result.push(DisplayLuaKV {
743 key: "makefile".to_string(),
744 value: DisplayLuaValue::String(makefile.to_string_lossy().to_string()),
745 });
746 }
747 if let Some(make_build_target) = &self.make_build_target {
748 result.push(DisplayLuaKV {
749 key: "build_target".to_string(),
750 value: DisplayLuaValue::String(make_build_target.clone()),
751 });
752 }
753 if let Some(build_pass) = &self.build_pass {
754 result.push(DisplayLuaKV {
755 key: "build_pass".to_string(),
756 value: DisplayLuaValue::Boolean(*build_pass),
757 });
758 }
759 if let Some(make_install_target) = &self.make_install_target {
760 result.push(DisplayLuaKV {
761 key: "install_target".to_string(),
762 value: DisplayLuaValue::String(make_install_target.clone()),
763 });
764 }
765 if let Some(install_pass) = &self.install_pass {
766 result.push(DisplayLuaKV {
767 key: "install_pass".to_string(),
768 value: DisplayLuaValue::Boolean(*install_pass),
769 });
770 }
771 if let Some(make_build_variables) = &self.make_build_variables {
772 result.push(DisplayLuaKV {
773 key: "build_variables".to_string(),
774 value: DisplayLuaValue::Table(
775 make_build_variables
776 .iter()
777 .map(|(key, value)| DisplayLuaKV {
778 key: key.clone(),
779 value: DisplayLuaValue::String(value.clone()),
780 })
781 .collect(),
782 ),
783 });
784 }
785 if let Some(make_install_variables) = &self.make_install_variables {
786 result.push(DisplayLuaKV {
787 key: "install_variables".to_string(),
788 value: DisplayLuaValue::Table(
789 make_install_variables
790 .iter()
791 .map(|(key, value)| DisplayLuaKV {
792 key: key.clone(),
793 value: DisplayLuaValue::String(value.clone()),
794 })
795 .collect(),
796 ),
797 });
798 }
799 if let Some(variables) = &self.variables {
800 result.push(DisplayLuaKV {
801 key: "variables".to_string(),
802 value: DisplayLuaValue::Table(
803 variables
804 .iter()
805 .map(|(key, value)| DisplayLuaKV {
806 key: key.clone(),
807 value: DisplayLuaValue::String(value.clone()),
808 })
809 .collect(),
810 ),
811 });
812 }
813 if let Some(cmake_lists_content) = &self.cmake_lists_content {
814 result.push(DisplayLuaKV {
815 key: "cmake".to_string(),
816 value: DisplayLuaValue::String(cmake_lists_content.clone()),
817 });
818 }
819 if let Some(build_command) = &self.build_command {
820 result.push(DisplayLuaKV {
821 key: "build_command".to_string(),
822 value: DisplayLuaValue::String(build_command.clone()),
823 });
824 }
825 if let Some(install_command) = &self.install_command {
826 result.push(DisplayLuaKV {
827 key: "install_command".to_string(),
828 value: DisplayLuaValue::String(install_command.clone()),
829 });
830 }
831 if let Some(install) = &self.install {
832 result.push(install.display_lua());
833 }
834 if let Some(copy_directories) = &self.copy_directories {
835 result.push(DisplayLuaKV {
836 key: "copy_directories".to_string(),
837 value: DisplayLuaValue::List(
838 copy_directories
839 .iter()
840 .map(|path_buf| {
841 DisplayLuaValue::String(path_buf.to_string_lossy().to_string())
842 })
843 .collect(),
844 ),
845 });
846 }
847 if let Some(patches) = &self.patches {
848 result.push(DisplayLuaKV {
849 key: "patches".to_string(),
850 value: DisplayLuaValue::Table(
851 patches
852 .iter()
853 .map(|(key, value)| DisplayLuaKV {
854 key: key.to_string_lossy().to_string(),
855 value: DisplayLuaValue::String(value.clone()),
856 })
857 .collect(),
858 ),
859 });
860 }
861 if let Some(target_path) = &self.target_path {
862 result.push(DisplayLuaKV {
863 key: "target_path".to_string(),
864 value: DisplayLuaValue::String(target_path.to_string_lossy().to_string()),
865 });
866 }
867 if let Some(default_features) = &self.default_features {
868 result.push(DisplayLuaKV {
869 key: "default_features".to_string(),
870 value: DisplayLuaValue::Boolean(*default_features),
871 });
872 }
873 if let Some(include) = &self.include {
874 result.push(DisplayLuaKV {
875 key: "include".to_string(),
876 value: DisplayLuaValue::Table(
877 include
878 .iter()
879 .map(|(key, value)| DisplayLuaKV {
880 key: match key {
881 LuaTableKey::StringKey(s) => s.clone(),
882 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
883 },
884 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
885 })
886 .collect(),
887 ),
888 });
889 }
890 if let Some(features) = &self.features {
891 result.push(DisplayLuaKV {
892 key: "features".to_string(),
893 value: DisplayLuaValue::List(
894 features
895 .iter()
896 .map(|feature| DisplayLuaValue::String(feature.clone()))
897 .collect(),
898 ),
899 });
900 }
901 if let Some(lang) = &self.lang {
902 result.push(DisplayLuaKV {
903 key: "lang".to_string(),
904 value: DisplayLuaValue::String(lang.to_string()),
905 });
906 }
907 if let Some(parser) = &self.parser {
908 result.push(DisplayLuaKV {
909 key: "parser".to_string(),
910 value: DisplayLuaValue::Boolean(*parser),
911 });
912 }
913 if let Some(generate) = &self.generate {
914 result.push(DisplayLuaKV {
915 key: "generate".to_string(),
916 value: DisplayLuaValue::Boolean(*generate),
917 });
918 }
919 if let Some(location) = &self.location {
920 result.push(DisplayLuaKV {
921 key: "location".to_string(),
922 value: DisplayLuaValue::String(location.to_string_lossy().to_string()),
923 });
924 }
925 if let Some(queries) = &self.queries {
926 result.push(DisplayLuaKV {
927 key: "queries".to_string(),
928 value: DisplayLuaValue::Table(
929 queries
930 .iter()
931 .map(|(key, value)| DisplayLuaKV {
932 key: key.to_string_lossy().to_string(),
933 value: DisplayLuaValue::String(value.to_string()),
934 })
935 .collect(),
936 ),
937 });
938 }
939
940 DisplayLuaKV {
941 key: "build".to_string(),
942 value: DisplayLuaValue::Table(result),
943 }
944 }
945}
946
947#[derive(Debug, PartialEq, Deserialize, Clone)]
949#[serde(rename_all = "lowercase", remote = "BuildType")]
950#[derive(Default)]
951pub(crate) enum BuildType {
952 #[default]
954 Builtin,
955 Make,
957 CMake,
959 Command,
961 None,
963 LuaRock(String),
965 #[serde(rename = "rust-mlua")]
966 RustMlua,
967 #[serde(rename = "treesitter-parser")]
968 TreesitterParser,
969 Source,
970}
971
972impl BuildType {
973 pub(crate) fn luarocks_build_backend(&self) -> Option<LuaDependencySpec> {
974 match self {
975 &BuildType::Builtin
976 | &BuildType::Make
977 | &BuildType::CMake
978 | &BuildType::Command
979 | &BuildType::None
980 | &BuildType::LuaRock(_)
981 | &BuildType::Source => None,
982 &BuildType::RustMlua => unsafe {
983 Some(
984 PackageReq::parse("luarocks-build-rust-mlua >= 0.2.6")
985 .unwrap_unchecked()
986 .into(),
987 )
988 },
989 &BuildType::TreesitterParser => {
990 Some(PackageName::new("luarocks-build-treesitter-parser".into()).into())
991 } }
994 }
995}
996
997impl<'de> Deserialize<'de> for BuildType {
1000 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1001 where
1002 D: Deserializer<'de>,
1003 {
1004 let s = String::deserialize(deserializer)?;
1005 if s == "builtin" || s == "module" {
1006 Ok(Self::Builtin)
1007 } else {
1008 match Self::deserialize(s.clone().into_deserializer()) {
1009 Err(_) => Ok(Self::LuaRock(s)),
1010 ok => ok,
1011 }
1012 }
1013 }
1014}
1015
1016impl Display for BuildType {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 match self {
1019 BuildType::Builtin => write!(f, "builtin"),
1020 BuildType::Make => write!(f, "make"),
1021 BuildType::CMake => write!(f, "cmake"),
1022 BuildType::Command => write!(f, "command"),
1023 BuildType::None => write!(f, "none"),
1024 BuildType::LuaRock(s) => write!(f, "{s}"),
1025 BuildType::RustMlua => write!(f, "rust-mlua"),
1026 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
1027 BuildType::Source => write!(f, "source"),
1028 }
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034
1035 use super::*;
1036
1037 #[tokio::test]
1038 pub async fn deserialize_build_type() {
1039 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
1040 assert_eq!(build_type, BuildType::Builtin);
1041 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
1042 assert_eq!(build_type, BuildType::Builtin);
1043 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1044 assert_eq!(build_type, BuildType::Make);
1045 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1046 assert_eq!(
1047 build_type,
1048 BuildType::LuaRock("custom_build_backend".into())
1049 );
1050 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1051 assert_eq!(build_type, BuildType::RustMlua);
1052 }
1053}