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 mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, UserData, Value};
22use std::{
23 collections::HashMap, env::consts::DLL_EXTENSION, fmt::Display, path::PathBuf, str::FromStr,
24};
25use thiserror::Error;
26
27use serde::{de, de::IntoDeserializer, Deserialize, Deserializer};
28
29use super::{
30 mlua_json_value_to_map, mlua_json_value_to_vec, DisplayAsLuaKV, DisplayAsLuaValue,
31 DisplayLuaKV, DisplayLuaValue, LuaTableKey, PartialOverride, PerPlatform, PlatformIdentifier,
32};
33
34#[derive(Clone, Debug, PartialEq)]
39pub struct BuildSpec {
40 pub build_backend: Option<BuildBackendSpec>,
42 pub install: InstallSpec,
46 pub copy_directories: Vec<PathBuf>,
48 pub patches: HashMap<PathBuf, String>,
52}
53
54impl Default for BuildSpec {
55 fn default() -> Self {
56 Self {
57 build_backend: Some(BuildBackendSpec::default()),
58 install: InstallSpec::default(),
59 copy_directories: Vec::default(),
60 patches: HashMap::default(),
61 }
62 }
63}
64
65impl UserData for BuildSpec {
66 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
67 methods.add_method("build_backend", |_, this, _: ()| {
68 Ok(this.build_backend.clone())
69 });
70 methods.add_method("install", |_, this, _: ()| Ok(this.install.clone()));
71 methods.add_method("copy_directories", |_, this, _: ()| {
72 Ok(this.copy_directories.clone())
73 });
74 methods.add_method("patches", |_, this, _: ()| Ok(this.patches.clone()));
75 }
76}
77
78#[derive(Error, Debug)]
79pub enum BuildSpecInternalError {
80 #[error("'builtin' modules should not have list elements")]
81 ModulesHaveListElements,
82 #[error("no 'modules' specified for the 'rust-mlua' build backend")]
83 NoModulesSpecified,
84 #[error("no 'lang' specified for 'treesitter-parser' build backend")]
85 NoTreesitterParserLanguageSpecified,
86 #[error("invalid 'rust-mlua' modules format")]
87 InvalidRustMLuaFormat,
88 #[error(transparent)]
89 ModulePathsMissingSources(#[from] ModulePathsMissingSources),
90 #[error(transparent)]
91 ParseLuaModuleError(#[from] ParseLuaModuleError),
92}
93
94impl BuildSpec {
95 pub(crate) fn from_internal_spec(
96 internal: BuildSpecInternal,
97 ) -> Result<Self, BuildSpecInternalError> {
98 let build_backend = match internal.build_type.unwrap_or_default() {
99 BuildType::Builtin => Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
100 modules: internal
101 .builtin_spec
102 .unwrap_or_default()
103 .into_iter()
104 .map(|(key, module_spec_internal)| {
105 let key_str = match key {
106 LuaTableKey::IntKey(_) => {
107 Err(BuildSpecInternalError::ModulesHaveListElements)
108 }
109 LuaTableKey::StringKey(str) => Ok(LuaModule::from_str(str.as_str())?),
110 }?;
111 match ModuleSpec::from_internal(module_spec_internal) {
112 Ok(module_spec) => Ok((key_str, module_spec)),
113 Err(err) => Err(err.into()),
114 }
115 })
116 .collect::<Result<HashMap<LuaModule, ModuleSpec>, BuildSpecInternalError>>()?,
117 })),
118 BuildType::Make => {
119 let default = MakeBuildSpec::default();
120 Some(BuildBackendSpec::Make(MakeBuildSpec {
121 makefile: internal.makefile.unwrap_or(default.makefile),
122 build_target: internal.make_build_target,
123 build_pass: internal.build_pass.unwrap_or(default.build_pass),
124 install_target: internal
125 .make_install_target
126 .unwrap_or(default.install_target),
127 install_pass: internal.install_pass.unwrap_or(default.install_pass),
128 build_variables: internal.make_build_variables.unwrap_or_default(),
129 install_variables: internal.make_install_variables.unwrap_or_default(),
130 variables: internal.variables.unwrap_or_default(),
131 }))
132 }
133 BuildType::CMake => {
134 let default = CMakeBuildSpec::default();
135 Some(BuildBackendSpec::CMake(CMakeBuildSpec {
136 cmake_lists_content: internal.cmake_lists_content,
137 build_pass: internal.build_pass.unwrap_or(default.build_pass),
138 install_pass: internal.install_pass.unwrap_or(default.install_pass),
139 variables: internal.variables.unwrap_or_default(),
140 }))
141 }
142 BuildType::Command => Some(BuildBackendSpec::Command(CommandBuildSpec {
143 build_command: internal.build_command,
144 install_command: internal.install_command,
145 })),
146 BuildType::None => None,
147 BuildType::LuaRock(s) => Some(BuildBackendSpec::LuaRock(s)),
148 BuildType::RustMlua => Some(BuildBackendSpec::RustMlua(RustMluaBuildSpec {
149 modules: internal
150 .builtin_spec
151 .ok_or(BuildSpecInternalError::NoModulesSpecified)?
152 .into_iter()
153 .map(|(key, value)| match (key, value) {
154 (LuaTableKey::IntKey(_), ModuleSpecInternal::SourcePath(module)) => {
155 let mut rust_lib: PathBuf = format!("lib{}", module.display()).into();
156 rust_lib.set_extension(DLL_EXTENSION);
157 Ok((module.to_string_lossy().to_string(), rust_lib))
158 }
159 (
160 LuaTableKey::StringKey(module_name),
161 ModuleSpecInternal::SourcePath(module),
162 ) => {
163 let mut rust_lib: PathBuf = format!("lib{}", module.display()).into();
164 rust_lib.set_extension(DLL_EXTENSION);
165 Ok((module_name, rust_lib))
166 }
167 _ => Err(BuildSpecInternalError::InvalidRustMLuaFormat),
168 })
169 .try_collect()?,
170 target_path: internal.target_path.unwrap_or("target".into()),
171 default_features: internal.default_features.unwrap_or(true),
172 include: internal
173 .include
174 .unwrap_or_default()
175 .into_iter()
176 .map(|(key, dest)| match key {
177 LuaTableKey::IntKey(_) => (dest.clone(), dest),
178 LuaTableKey::StringKey(src) => (src.into(), dest),
179 })
180 .collect(),
181 features: internal.features.unwrap_or_default(),
182 })),
183 BuildType::TreesitterParser => Some(BuildBackendSpec::TreesitterParser(
184 TreesitterParserBuildSpec {
185 lang: internal
186 .lang
187 .ok_or(BuildSpecInternalError::NoTreesitterParserLanguageSpecified)?,
188 parser: internal.parser.unwrap_or(false),
189 generate: internal.generate.unwrap_or(false),
190 location: internal.location,
191 queries: internal.queries.unwrap_or_default(),
192 },
193 )),
194 BuildType::Source => Some(BuildBackendSpec::Source),
195 };
196 Ok(Self {
197 build_backend,
198 install: internal.install.unwrap_or_default(),
199 copy_directories: internal.copy_directories.unwrap_or_default(),
200 patches: internal.patches.unwrap_or_default(),
201 })
202 }
203}
204
205impl<'de> Deserialize<'de> for BuildSpec {
206 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
207 where
208 D: Deserializer<'de>,
209 {
210 let internal = BuildSpecInternal::deserialize(deserializer)?;
211 BuildSpec::from_internal_spec(internal).map_err(de::Error::custom)
212 }
213}
214
215impl FromLua for PerPlatform<BuildSpec> {
216 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
217 let internal = PerPlatform::from_lua(value, lua)?;
218 let mut per_platform = HashMap::new();
219 for (platform, internal_override) in internal.per_platform {
220 let override_spec = BuildSpec::from_internal_spec(internal_override)
221 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
222 per_platform.insert(platform, override_spec);
223 }
224 let result = PerPlatform {
225 default: BuildSpec::from_internal_spec(internal.default)
226 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?,
227 per_platform,
228 };
229 Ok(result)
230 }
231}
232
233impl Default for BuildBackendSpec {
234 fn default() -> Self {
235 Self::Builtin(BuiltinBuildSpec::default())
236 }
237}
238
239#[derive(Debug, PartialEq, Clone)]
246pub enum BuildBackendSpec {
247 Builtin(BuiltinBuildSpec),
248 Make(MakeBuildSpec),
249 CMake(CMakeBuildSpec),
250 Command(CommandBuildSpec),
251 LuaRock(String),
252 RustMlua(RustMluaBuildSpec),
253 TreesitterParser(TreesitterParserBuildSpec),
254 Source,
260}
261
262impl BuildBackendSpec {
263 pub(crate) fn can_use_build_dependencies(&self) -> bool {
264 match self {
265 Self::Make(_) | Self::CMake(_) | Self::Command(_) | Self::LuaRock(_) => true,
266 Self::Builtin(_) | Self::RustMlua(_) | Self::TreesitterParser(_) | Self::Source => {
267 false
268 }
269 }
270 }
271}
272
273impl IntoLua for BuildBackendSpec {
274 fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
275 match self {
276 BuildBackendSpec::Builtin(spec) => spec.into_lua(lua),
277 BuildBackendSpec::Make(spec) => spec.into_lua(lua),
278 BuildBackendSpec::CMake(spec) => spec.into_lua(lua),
279 BuildBackendSpec::Command(spec) => spec.into_lua(lua),
280 BuildBackendSpec::LuaRock(s) => s.into_lua(lua),
281 BuildBackendSpec::RustMlua(spec) => spec.into_lua(lua),
282 BuildBackendSpec::TreesitterParser(spec) => spec.into_lua(lua),
283 BuildBackendSpec::Source => "source".into_lua(lua),
284 }
285 }
286}
287
288#[derive(Debug, PartialEq, Clone)]
289pub struct CommandBuildSpec {
290 pub build_command: Option<String>,
291 pub install_command: Option<String>,
292}
293
294impl UserData for CommandBuildSpec {
295 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
296 methods.add_method("build_command", |_, this, _: ()| {
297 Ok(this.build_command.clone())
298 });
299 methods.add_method("install_command", |_, this, _: ()| {
300 Ok(this.install_command.clone())
301 });
302 }
303}
304
305#[derive(Clone, Debug)]
306pub(crate) enum InstallBinaries {
307 Array(Vec<PathBuf>),
308 Table(HashMap<String, PathBuf>),
309}
310
311impl<'de> Deserialize<'de> for InstallBinaries {
312 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
313 where
314 D: Deserializer<'de>,
315 {
316 let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
317 if value.is_array() {
318 let array = mlua_json_value_to_vec(value).map_err(de::Error::custom)?;
319 Ok(InstallBinaries::Array(array))
320 } else {
321 let table: HashMap<String, PathBuf> =
322 mlua_json_value_to_map(value).map_err(de::Error::custom)?;
323 Ok(InstallBinaries::Table(table))
324 }
325 }
326}
327
328impl InstallBinaries {
329 pub(crate) fn coerce(self) -> HashMap<String, PathBuf> {
330 match self {
331 InstallBinaries::Array(array) => array
332 .into_iter()
333 .map(|path| {
334 (
335 path.file_stem().unwrap().to_str().unwrap().to_string(),
336 path,
337 )
338 })
339 .collect(),
340 InstallBinaries::Table(table) => table,
341 }
342 }
343}
344
345#[derive(Debug, PartialEq, Default, Deserialize, Clone)]
354pub struct InstallSpec {
355 #[serde(default)]
357 pub lua: HashMap<LuaModule, PathBuf>,
358 #[serde(default)]
360 pub lib: HashMap<LuaModule, PathBuf>,
361 #[serde(default)]
363 pub conf: HashMap<String, PathBuf>,
364 #[serde(default, deserialize_with = "deserialize_binaries")]
368 pub bin: HashMap<String, PathBuf>,
369}
370
371impl UserData for InstallSpec {
372 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
373 methods.add_method("lua", |_, this, _: ()| Ok(this.lua.clone()));
374 methods.add_method("lib", |_, this, _: ()| Ok(this.lib.clone()));
375 methods.add_method("conf", |_, this, _: ()| Ok(this.conf.clone()));
376 methods.add_method("bin", |_, this, _: ()| Ok(this.bin.clone()));
377 }
378}
379
380fn deserialize_binaries<'de, D>(deserializer: D) -> Result<HashMap<String, PathBuf>, D::Error>
381where
382 D: Deserializer<'de>,
383{
384 let binaries = InstallBinaries::deserialize(deserializer)?;
385 Ok(binaries.coerce())
386}
387
388fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
389where
390 D: Deserializer<'de>,
391{
392 let value: Option<serde_json::Value> = Option::deserialize(deserializer)?;
393 let copy_directories: Option<Vec<String>> = match value {
394 Some(json_value) => Some(mlua_json_value_to_vec(json_value).map_err(de::Error::custom)?),
395 None => None,
396 };
397 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
398 match special_directories
399 .into_iter()
400 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
401 {
402 Some(d) => Err(format!(
405 "directory '{d}' in copy_directories clashes with the .rock format", )),
407 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
408 }
409 .map_err(de::Error::custom)
410}
411
412impl DisplayAsLuaKV for InstallSpec {
413 fn display_lua(&self) -> DisplayLuaKV {
414 let mut result = Vec::new();
415
416 let mut lua_entries = Vec::new();
417 self.lua.iter().for_each(|(key, value)| {
418 lua_entries.push(DisplayLuaKV {
419 key: key.to_string(),
420 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
421 });
422 });
423 if !lua_entries.is_empty() {
424 result.push(DisplayLuaKV {
425 key: "lua".to_string(),
426 value: DisplayLuaValue::Table(lua_entries),
427 });
428 }
429
430 let mut lib_entries = Vec::new();
431 self.lib.iter().for_each(|(key, value)| {
432 lib_entries.push(DisplayLuaKV {
433 key: key.to_string(),
434 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
435 });
436 });
437 if !lib_entries.is_empty() {
438 result.push(DisplayLuaKV {
439 key: "lib".to_string(),
440 value: DisplayLuaValue::Table(lib_entries),
441 });
442 }
443
444 let mut bin_entries = Vec::new();
445 self.bin.iter().for_each(|(key, value)| {
446 bin_entries.push(DisplayLuaKV {
447 key: key.clone(),
448 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
449 });
450 });
451 if !bin_entries.is_empty() {
452 result.push(DisplayLuaKV {
453 key: "bin".to_string(),
454 value: DisplayLuaValue::Table(bin_entries),
455 });
456 }
457
458 let mut conf_entries = Vec::new();
459 self.conf.iter().for_each(|(key, value)| {
460 conf_entries.push(DisplayLuaKV {
461 key: key.clone(),
462 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
463 });
464 });
465 if !conf_entries.is_empty() {
466 result.push(DisplayLuaKV {
467 key: "conf".to_string(),
468 value: DisplayLuaValue::Table(conf_entries),
469 });
470 }
471
472 DisplayLuaKV {
473 key: "install".to_string(),
474 value: DisplayLuaValue::Table(result),
475 }
476 }
477}
478
479#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
480pub(crate) struct BuildSpecInternal {
481 #[serde(rename = "type", default)]
482 pub(crate) build_type: Option<BuildType>,
483 #[serde(rename = "modules", default)]
484 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
485 #[serde(default)]
486 pub(crate) makefile: Option<PathBuf>,
487 #[serde(rename = "build_target", default)]
488 pub(crate) make_build_target: Option<String>,
489 #[serde(default)]
490 pub(crate) build_pass: Option<bool>,
491 #[serde(rename = "install_target", default)]
492 pub(crate) make_install_target: Option<String>,
493 #[serde(default)]
494 pub(crate) install_pass: Option<bool>,
495 #[serde(rename = "build_variables", default)]
496 pub(crate) make_build_variables: Option<HashMap<String, String>>,
497 #[serde(rename = "install_variables", default)]
498 pub(crate) make_install_variables: Option<HashMap<String, String>>,
499 #[serde(default)]
500 pub(crate) variables: Option<HashMap<String, String>>,
501 #[serde(rename = "cmake", default)]
502 pub(crate) cmake_lists_content: Option<String>,
503 #[serde(default)]
504 pub(crate) build_command: Option<String>,
505 #[serde(default)]
506 pub(crate) install_command: Option<String>,
507 #[serde(default)]
508 pub(crate) install: Option<InstallSpec>,
509 #[serde(default, deserialize_with = "deserialize_copy_directories")]
510 pub(crate) copy_directories: Option<Vec<PathBuf>>,
511 #[serde(default)]
512 pub(crate) patches: Option<HashMap<PathBuf, String>>,
513 #[serde(default)]
515 pub(crate) target_path: Option<PathBuf>,
516 #[serde(default)]
517 pub(crate) default_features: Option<bool>,
518 #[serde(default)]
519 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
520 #[serde(default)]
521 pub(crate) features: Option<Vec<String>>,
522 #[serde(default)]
524 pub(crate) lang: Option<String>,
525 #[serde(default)]
526 pub(crate) parser: Option<bool>,
527 #[serde(default)]
528 pub(crate) generate: Option<bool>,
529 #[serde(default)]
530 pub(crate) location: Option<PathBuf>,
531 #[serde(default)]
532 pub(crate) queries: Option<HashMap<PathBuf, String>>,
533}
534
535impl FromLua for PerPlatform<BuildSpecInternal> {
536 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
537 match &value {
538 list @ Value::Table(tbl) => {
539 let mut per_platform = match tbl.get("platforms")? {
540 Value::Table(overrides) => Ok(lua.from_value(Value::Table(overrides))?),
541 Value::Nil => Ok(HashMap::default()),
542 val => Err(mlua::Error::DeserializeError(format!(
543 "Expected rockspec 'build' to be table or nil, but got {}",
544 val.type_name()
545 ))),
546 }?;
547 let _ = tbl.raw_remove("platforms");
548 let default = lua.from_value(list.clone())?;
549 override_platform_specs(&mut per_platform, &default)
550 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
551 Ok(PerPlatform {
552 default,
553 per_platform,
554 })
555 }
556 Value::Nil => Ok(PerPlatform::default()),
557 val => Err(mlua::Error::DeserializeError(format!(
558 "Expected rockspec 'build' to be a table or nil, but got {}",
559 val.type_name()
560 ))),
561 }
562 }
563}
564
565fn override_platform_specs(
568 per_platform: &mut HashMap<PlatformIdentifier, BuildSpecInternal>,
569 base: &BuildSpecInternal,
570) -> Result<(), ModuleSpecAmbiguousPlatformOverride> {
571 let per_platform_raw = per_platform.clone();
572 for (platform, build_spec) in per_platform.clone() {
573 per_platform.insert(platform, override_build_spec_internal(base, &build_spec)?);
575 }
576 for (platform, build_spec) in per_platform_raw {
577 for extended_platform in &platform.get_extended_platforms() {
578 let extended_spec = per_platform
579 .get(extended_platform)
580 .unwrap_or(&base.to_owned())
581 .to_owned();
582 per_platform.insert(
583 extended_platform.to_owned(),
584 override_build_spec_internal(&extended_spec, &build_spec)?,
585 );
586 }
587 }
588 Ok(())
589}
590
591fn override_build_spec_internal(
592 base: &BuildSpecInternal,
593 override_spec: &BuildSpecInternal,
594) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
595 Ok(BuildSpecInternal {
596 build_type: override_opt(&override_spec.build_type, &base.build_type),
597 builtin_spec: match (
598 override_spec.builtin_spec.clone(),
599 base.builtin_spec.clone(),
600 ) {
601 (Some(override_val), Some(base_spec_map)) => {
602 Some(base_spec_map.into_iter().chain(override_val).try_fold(
603 HashMap::default(),
604 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
605 (k, module_spec_override)|
606 -> Result<
607 HashMap<LuaTableKey, ModuleSpecInternal>,
608 ModuleSpecAmbiguousPlatformOverride,
609 > {
610 let overridden = match acc.get(&k) {
611 None => module_spec_override,
612 Some(base_module_spec) => {
613 base_module_spec.apply_overrides(&module_spec_override)?
614 }
615 };
616 acc.insert(k, overridden);
617 Ok(acc)
618 },
619 )?)
620 }
621 (override_val @ Some(_), _) => override_val,
622 (_, base_val @ Some(_)) => base_val,
623 _ => None,
624 },
625 makefile: override_opt(&override_spec.makefile, &base.makefile),
626 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
627 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
628 make_install_target: override_opt(
629 &override_spec.make_install_target,
630 &base.make_install_target,
631 ),
632 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
633 make_build_variables: merge_map_opts(
634 &override_spec.make_build_variables,
635 &base.make_build_variables,
636 ),
637 make_install_variables: merge_map_opts(
638 &override_spec.make_install_variables,
639 &base.make_build_variables,
640 ),
641 variables: merge_map_opts(&override_spec.variables, &base.variables),
642 cmake_lists_content: override_opt(
643 &override_spec.cmake_lists_content,
644 &base.cmake_lists_content,
645 ),
646 build_command: override_opt(&override_spec.build_command, &base.build_command),
647 install_command: override_opt(&override_spec.install_command, &base.install_command),
648 install: override_opt(&override_spec.install, &base.install),
649 copy_directories: match (
650 override_spec.copy_directories.clone(),
651 base.copy_directories.clone(),
652 ) {
653 (Some(override_vec), Some(base_vec)) => {
654 let merged: Vec<PathBuf> =
655 base_vec.into_iter().chain(override_vec).unique().collect();
656 Some(merged)
657 }
658 (None, base_vec @ Some(_)) => base_vec,
659 (override_vec @ Some(_), None) => override_vec,
660 _ => None,
661 },
662 patches: override_opt(&override_spec.patches, &base.patches),
663 target_path: override_opt(&override_spec.target_path, &base.target_path),
664 default_features: override_opt(&override_spec.default_features, &base.default_features),
665 features: override_opt(&override_spec.features, &base.features),
666 include: merge_map_opts(&override_spec.include, &base.include),
667 lang: override_opt(&override_spec.lang, &base.lang),
668 parser: override_opt(&override_spec.parser, &base.parser),
669 generate: override_opt(&override_spec.generate, &base.generate),
670 location: override_opt(&override_spec.location, &base.location),
671 queries: merge_map_opts(&override_spec.queries, &base.queries),
672 })
673}
674
675fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
676 match override_opt.clone() {
677 override_val @ Some(_) => override_val,
678 None => base.clone(),
679 }
680}
681
682fn merge_map_opts<K, V>(
683 override_map: &Option<HashMap<K, V>>,
684 base_map: &Option<HashMap<K, V>>,
685) -> Option<HashMap<K, V>>
686where
687 K: Clone,
688 K: Eq,
689 K: std::hash::Hash,
690 V: Clone,
691{
692 match (override_map.clone(), base_map.clone()) {
693 (Some(override_map), Some(base_map)) => {
694 Some(base_map.into_iter().chain(override_map).collect())
695 }
696 (_, base_map @ Some(_)) => base_map,
697 (override_map @ Some(_), _) => override_map,
698 _ => None,
699 }
700}
701
702impl DisplayAsLuaKV for BuildSpecInternal {
703 fn display_lua(&self) -> DisplayLuaKV {
704 let mut result = Vec::new();
705
706 if let Some(build_type) = &self.build_type {
707 result.push(DisplayLuaKV {
708 key: "type".to_string(),
709 value: DisplayLuaValue::String(build_type.to_string()),
710 });
711 }
712 if let Some(builtin_spec) = &self.builtin_spec {
713 result.push(DisplayLuaKV {
714 key: "modules".to_string(),
715 value: DisplayLuaValue::Table(
716 builtin_spec
717 .iter()
718 .map(|(key, value)| DisplayLuaKV {
719 key: match key {
720 LuaTableKey::StringKey(s) => s.clone(),
721 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
722 },
723 value: value.display_lua_value(),
724 })
725 .collect(),
726 ),
727 });
728 }
729 if let Some(makefile) = &self.makefile {
730 result.push(DisplayLuaKV {
731 key: "makefile".to_string(),
732 value: DisplayLuaValue::String(makefile.to_string_lossy().to_string()),
733 });
734 }
735 if let Some(make_build_target) = &self.make_build_target {
736 result.push(DisplayLuaKV {
737 key: "build_target".to_string(),
738 value: DisplayLuaValue::String(make_build_target.clone()),
739 });
740 }
741 if let Some(build_pass) = &self.build_pass {
742 result.push(DisplayLuaKV {
743 key: "build_pass".to_string(),
744 value: DisplayLuaValue::Boolean(*build_pass),
745 });
746 }
747 if let Some(make_install_target) = &self.make_install_target {
748 result.push(DisplayLuaKV {
749 key: "install_target".to_string(),
750 value: DisplayLuaValue::String(make_install_target.clone()),
751 });
752 }
753 if let Some(install_pass) = &self.install_pass {
754 result.push(DisplayLuaKV {
755 key: "install_pass".to_string(),
756 value: DisplayLuaValue::Boolean(*install_pass),
757 });
758 }
759 if let Some(make_build_variables) = &self.make_build_variables {
760 result.push(DisplayLuaKV {
761 key: "build_variables".to_string(),
762 value: DisplayLuaValue::Table(
763 make_build_variables
764 .iter()
765 .map(|(key, value)| DisplayLuaKV {
766 key: key.clone(),
767 value: DisplayLuaValue::String(value.clone()),
768 })
769 .collect(),
770 ),
771 });
772 }
773 if let Some(make_install_variables) = &self.make_install_variables {
774 result.push(DisplayLuaKV {
775 key: "install_variables".to_string(),
776 value: DisplayLuaValue::Table(
777 make_install_variables
778 .iter()
779 .map(|(key, value)| DisplayLuaKV {
780 key: key.clone(),
781 value: DisplayLuaValue::String(value.clone()),
782 })
783 .collect(),
784 ),
785 });
786 }
787 if let Some(variables) = &self.variables {
788 result.push(DisplayLuaKV {
789 key: "variables".to_string(),
790 value: DisplayLuaValue::Table(
791 variables
792 .iter()
793 .map(|(key, value)| DisplayLuaKV {
794 key: key.clone(),
795 value: DisplayLuaValue::String(value.clone()),
796 })
797 .collect(),
798 ),
799 });
800 }
801 if let Some(cmake_lists_content) = &self.cmake_lists_content {
802 result.push(DisplayLuaKV {
803 key: "cmake".to_string(),
804 value: DisplayLuaValue::String(cmake_lists_content.clone()),
805 });
806 }
807 if let Some(build_command) = &self.build_command {
808 result.push(DisplayLuaKV {
809 key: "build_command".to_string(),
810 value: DisplayLuaValue::String(build_command.clone()),
811 });
812 }
813 if let Some(install_command) = &self.install_command {
814 result.push(DisplayLuaKV {
815 key: "install_command".to_string(),
816 value: DisplayLuaValue::String(install_command.clone()),
817 });
818 }
819 if let Some(install) = &self.install {
820 result.push(install.display_lua());
821 }
822 if let Some(copy_directories) = &self.copy_directories {
823 result.push(DisplayLuaKV {
824 key: "copy_directories".to_string(),
825 value: DisplayLuaValue::List(
826 copy_directories
827 .iter()
828 .map(|path_buf| {
829 DisplayLuaValue::String(path_buf.to_string_lossy().to_string())
830 })
831 .collect(),
832 ),
833 });
834 }
835 if let Some(patches) = &self.patches {
836 result.push(DisplayLuaKV {
837 key: "patches".to_string(),
838 value: DisplayLuaValue::Table(
839 patches
840 .iter()
841 .map(|(key, value)| DisplayLuaKV {
842 key: key.to_string_lossy().to_string(),
843 value: DisplayLuaValue::String(value.clone()),
844 })
845 .collect(),
846 ),
847 });
848 }
849 if let Some(target_path) = &self.target_path {
850 result.push(DisplayLuaKV {
851 key: "target_path".to_string(),
852 value: DisplayLuaValue::String(target_path.to_string_lossy().to_string()),
853 });
854 }
855 if let Some(default_features) = &self.default_features {
856 result.push(DisplayLuaKV {
857 key: "default_features".to_string(),
858 value: DisplayLuaValue::Boolean(*default_features),
859 });
860 }
861 if let Some(include) = &self.include {
862 result.push(DisplayLuaKV {
863 key: "include".to_string(),
864 value: DisplayLuaValue::Table(
865 include
866 .iter()
867 .map(|(key, value)| DisplayLuaKV {
868 key: match key {
869 LuaTableKey::StringKey(s) => s.clone(),
870 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
871 },
872 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
873 })
874 .collect(),
875 ),
876 });
877 }
878 if let Some(features) = &self.features {
879 result.push(DisplayLuaKV {
880 key: "features".to_string(),
881 value: DisplayLuaValue::List(
882 features
883 .iter()
884 .map(|feature| DisplayLuaValue::String(feature.clone()))
885 .collect(),
886 ),
887 });
888 }
889 if let Some(lang) = &self.lang {
890 result.push(DisplayLuaKV {
891 key: "lang".to_string(),
892 value: DisplayLuaValue::String(lang.to_string()),
893 });
894 }
895 if let Some(parser) = &self.parser {
896 result.push(DisplayLuaKV {
897 key: "parser".to_string(),
898 value: DisplayLuaValue::Boolean(*parser),
899 });
900 }
901 if let Some(generate) = &self.generate {
902 result.push(DisplayLuaKV {
903 key: "generate".to_string(),
904 value: DisplayLuaValue::Boolean(*generate),
905 });
906 }
907 if let Some(location) = &self.location {
908 result.push(DisplayLuaKV {
909 key: "location".to_string(),
910 value: DisplayLuaValue::String(location.to_string_lossy().to_string()),
911 });
912 }
913 if let Some(queries) = &self.queries {
914 result.push(DisplayLuaKV {
915 key: "queries".to_string(),
916 value: DisplayLuaValue::Table(
917 queries
918 .iter()
919 .map(|(key, value)| DisplayLuaKV {
920 key: key.to_string_lossy().to_string(),
921 value: DisplayLuaValue::String(value.to_string()),
922 })
923 .collect(),
924 ),
925 });
926 }
927
928 DisplayLuaKV {
929 key: "build".to_string(),
930 value: DisplayLuaValue::Table(result),
931 }
932 }
933}
934
935#[derive(Debug, PartialEq, Deserialize, Clone)]
937#[serde(rename_all = "lowercase", remote = "BuildType")]
938pub(crate) enum BuildType {
939 Builtin,
941 Make,
943 CMake,
945 Command,
947 None,
949 LuaRock(String),
951 #[serde(rename = "rust-mlua")]
952 RustMlua,
953 #[serde(rename = "treesitter-parser")]
954 TreesitterParser,
955 Source,
956}
957
958impl<'de> Deserialize<'de> for BuildType {
961 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
962 where
963 D: Deserializer<'de>,
964 {
965 let s = String::deserialize(deserializer)?;
966 if s == "builtin" || s == "module" {
967 Ok(Self::Builtin)
968 } else {
969 match Self::deserialize(s.clone().into_deserializer()) {
970 Err(_) => Ok(Self::LuaRock(s)),
971 ok => ok,
972 }
973 }
974 }
975}
976
977impl Display for BuildType {
978 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
979 match self {
980 BuildType::Builtin => write!(f, "builtin"),
981 BuildType::Make => write!(f, "make"),
982 BuildType::CMake => write!(f, "cmake"),
983 BuildType::Command => write!(f, "command"),
984 BuildType::None => write!(f, "none"),
985 BuildType::LuaRock(s) => write!(f, "{s}"),
986 BuildType::RustMlua => write!(f, "rust-mlua"),
987 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
988 BuildType::Source => write!(f, "source"),
989 }
990 }
991}
992
993impl Default for BuildType {
994 fn default() -> Self {
995 Self::Builtin
996 }
997}
998
999#[cfg(test)]
1000mod tests {
1001
1002 use super::*;
1003
1004 #[tokio::test]
1005 pub async fn deserialize_build_type() {
1006 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
1007 assert_eq!(build_type, BuildType::Builtin);
1008 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
1009 assert_eq!(build_type, BuildType::Builtin);
1010 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1011 assert_eq!(build_type, BuildType::Make);
1012 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1013 assert_eq!(
1014 build_type,
1015 BuildType::LuaRock("custom_build_backend".into())
1016 );
1017 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1018 assert_eq!(build_type, BuildType::RustMlua);
1019 }
1020}