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 IntoLua for BuildBackendSpec {
263 fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
264 match self {
265 BuildBackendSpec::Builtin(spec) => spec.into_lua(lua),
266 BuildBackendSpec::Make(spec) => spec.into_lua(lua),
267 BuildBackendSpec::CMake(spec) => spec.into_lua(lua),
268 BuildBackendSpec::Command(spec) => spec.into_lua(lua),
269 BuildBackendSpec::LuaRock(s) => s.into_lua(lua),
270 BuildBackendSpec::RustMlua(spec) => spec.into_lua(lua),
271 BuildBackendSpec::TreesitterParser(spec) => spec.into_lua(lua),
272 BuildBackendSpec::Source => "source".into_lua(lua),
273 }
274 }
275}
276
277#[derive(Debug, PartialEq, Clone)]
278pub struct CommandBuildSpec {
279 pub build_command: Option<String>,
280 pub install_command: Option<String>,
281}
282
283impl UserData for CommandBuildSpec {
284 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
285 methods.add_method("build_command", |_, this, _: ()| {
286 Ok(this.build_command.clone())
287 });
288 methods.add_method("install_command", |_, this, _: ()| {
289 Ok(this.install_command.clone())
290 });
291 }
292}
293
294#[derive(Clone, Debug)]
295pub(crate) enum InstallBinaries {
296 Array(Vec<PathBuf>),
297 Table(HashMap<String, PathBuf>),
298}
299
300impl<'de> Deserialize<'de> for InstallBinaries {
301 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
302 where
303 D: Deserializer<'de>,
304 {
305 let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
306 if value.is_array() {
307 let array = mlua_json_value_to_vec(value).map_err(de::Error::custom)?;
308 Ok(InstallBinaries::Array(array))
309 } else {
310 let table: HashMap<String, PathBuf> =
311 mlua_json_value_to_map(value).map_err(de::Error::custom)?;
312 Ok(InstallBinaries::Table(table))
313 }
314 }
315}
316
317impl InstallBinaries {
318 pub(crate) fn coerce(self) -> HashMap<String, PathBuf> {
319 match self {
320 InstallBinaries::Array(array) => array
321 .into_iter()
322 .map(|path| {
323 (
324 path.file_stem().unwrap().to_str().unwrap().to_string(),
325 path,
326 )
327 })
328 .collect(),
329 InstallBinaries::Table(table) => table,
330 }
331 }
332}
333
334#[derive(Debug, PartialEq, Default, Deserialize, Clone)]
343pub struct InstallSpec {
344 #[serde(default)]
346 pub lua: HashMap<LuaModule, PathBuf>,
347 #[serde(default)]
349 pub lib: HashMap<LuaModule, PathBuf>,
350 #[serde(default)]
352 pub conf: HashMap<String, PathBuf>,
353 #[serde(default, deserialize_with = "deserialize_binaries")]
357 pub bin: HashMap<String, PathBuf>,
358}
359
360impl UserData for InstallSpec {
361 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
362 methods.add_method("lua", |_, this, _: ()| Ok(this.lua.clone()));
363 methods.add_method("lib", |_, this, _: ()| Ok(this.lib.clone()));
364 methods.add_method("conf", |_, this, _: ()| Ok(this.conf.clone()));
365 methods.add_method("bin", |_, this, _: ()| Ok(this.bin.clone()));
366 }
367}
368
369fn deserialize_binaries<'de, D>(deserializer: D) -> Result<HashMap<String, PathBuf>, D::Error>
370where
371 D: Deserializer<'de>,
372{
373 let binaries = InstallBinaries::deserialize(deserializer)?;
374 Ok(binaries.coerce())
375}
376
377fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
378where
379 D: Deserializer<'de>,
380{
381 let value: Option<serde_json::Value> = Option::deserialize(deserializer)?;
382 let copy_directories: Option<Vec<String>> = match value {
383 Some(json_value) => Some(mlua_json_value_to_vec(json_value).map_err(de::Error::custom)?),
384 None => None,
385 };
386 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
387 match special_directories
388 .into_iter()
389 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
390 {
391 Some(d) => Err(format!(
394 "directory '{}' in copy_directories clashes with the .rock format", d
396 )),
397 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
398 }
399 .map_err(de::Error::custom)
400}
401
402impl DisplayAsLuaKV for InstallSpec {
403 fn display_lua(&self) -> DisplayLuaKV {
404 let mut result = Vec::new();
405
406 let mut lua_entries = Vec::new();
407 self.lua.iter().for_each(|(key, value)| {
408 lua_entries.push(DisplayLuaKV {
409 key: key.to_string(),
410 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
411 });
412 });
413 if !lua_entries.is_empty() {
414 result.push(DisplayLuaKV {
415 key: "lua".to_string(),
416 value: DisplayLuaValue::Table(lua_entries),
417 });
418 }
419
420 let mut lib_entries = Vec::new();
421 self.lib.iter().for_each(|(key, value)| {
422 lib_entries.push(DisplayLuaKV {
423 key: key.to_string(),
424 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
425 });
426 });
427 if !lib_entries.is_empty() {
428 result.push(DisplayLuaKV {
429 key: "lib".to_string(),
430 value: DisplayLuaValue::Table(lib_entries),
431 });
432 }
433
434 let mut bin_entries = Vec::new();
435 self.bin.iter().for_each(|(key, value)| {
436 bin_entries.push(DisplayLuaKV {
437 key: key.clone(),
438 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
439 });
440 });
441 if !bin_entries.is_empty() {
442 result.push(DisplayLuaKV {
443 key: "bin".to_string(),
444 value: DisplayLuaValue::Table(bin_entries),
445 });
446 }
447
448 let mut conf_entries = Vec::new();
449 self.conf.iter().for_each(|(key, value)| {
450 conf_entries.push(DisplayLuaKV {
451 key: key.clone(),
452 value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
453 });
454 });
455 if !conf_entries.is_empty() {
456 result.push(DisplayLuaKV {
457 key: "conf".to_string(),
458 value: DisplayLuaValue::Table(conf_entries),
459 });
460 }
461
462 DisplayLuaKV {
463 key: "install".to_string(),
464 value: DisplayLuaValue::Table(result),
465 }
466 }
467}
468
469#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
470pub(crate) struct BuildSpecInternal {
471 #[serde(rename = "type", default)]
472 pub(crate) build_type: Option<BuildType>,
473 #[serde(rename = "modules", default)]
474 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
475 #[serde(default)]
476 pub(crate) makefile: Option<PathBuf>,
477 #[serde(rename = "build_target", default)]
478 pub(crate) make_build_target: Option<String>,
479 #[serde(default)]
480 pub(crate) build_pass: Option<bool>,
481 #[serde(rename = "install_target", default)]
482 pub(crate) make_install_target: Option<String>,
483 #[serde(default)]
484 pub(crate) install_pass: Option<bool>,
485 #[serde(rename = "build_variables", default)]
486 pub(crate) make_build_variables: Option<HashMap<String, String>>,
487 #[serde(rename = "install_variables", default)]
488 pub(crate) make_install_variables: Option<HashMap<String, String>>,
489 #[serde(default)]
490 pub(crate) variables: Option<HashMap<String, String>>,
491 #[serde(rename = "cmake", default)]
492 pub(crate) cmake_lists_content: Option<String>,
493 #[serde(default)]
494 pub(crate) build_command: Option<String>,
495 #[serde(default)]
496 pub(crate) install_command: Option<String>,
497 #[serde(default)]
498 pub(crate) install: Option<InstallSpec>,
499 #[serde(default, deserialize_with = "deserialize_copy_directories")]
500 pub(crate) copy_directories: Option<Vec<PathBuf>>,
501 #[serde(default)]
502 pub(crate) patches: Option<HashMap<PathBuf, String>>,
503 #[serde(default)]
505 pub(crate) target_path: Option<PathBuf>,
506 #[serde(default)]
507 pub(crate) default_features: Option<bool>,
508 #[serde(default)]
509 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
510 #[serde(default)]
511 pub(crate) features: Option<Vec<String>>,
512 #[serde(default)]
514 pub(crate) lang: Option<String>,
515 #[serde(default)]
516 pub(crate) parser: Option<bool>,
517 #[serde(default)]
518 pub(crate) generate: Option<bool>,
519 #[serde(default)]
520 pub(crate) location: Option<PathBuf>,
521 #[serde(default)]
522 pub(crate) queries: Option<HashMap<PathBuf, String>>,
523}
524
525impl FromLua for PerPlatform<BuildSpecInternal> {
526 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
527 match &value {
528 list @ Value::Table(tbl) => {
529 let mut per_platform = match tbl.get("platforms")? {
530 Value::Table(overrides) => Ok(lua.from_value(Value::Table(overrides))?),
531 Value::Nil => Ok(HashMap::default()),
532 val => Err(mlua::Error::DeserializeError(format!(
533 "Expected rockspec 'build' to be table or nil, but got {}",
534 val.type_name()
535 ))),
536 }?;
537 let _ = tbl.raw_remove("platforms");
538 let default = lua.from_value(list.clone())?;
539 override_platform_specs(&mut per_platform, &default)
540 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
541 Ok(PerPlatform {
542 default,
543 per_platform,
544 })
545 }
546 Value::Nil => Ok(PerPlatform::default()),
547 val => Err(mlua::Error::DeserializeError(format!(
548 "Expected rockspec 'build' to be a table or nil, but got {}",
549 val.type_name()
550 ))),
551 }
552 }
553}
554
555fn override_platform_specs(
558 per_platform: &mut HashMap<PlatformIdentifier, BuildSpecInternal>,
559 base: &BuildSpecInternal,
560) -> Result<(), ModuleSpecAmbiguousPlatformOverride> {
561 let per_platform_raw = per_platform.clone();
562 for (platform, build_spec) in per_platform.clone() {
563 per_platform.insert(platform, override_build_spec_internal(base, &build_spec)?);
565 }
566 for (platform, build_spec) in per_platform_raw {
567 for extended_platform in &platform.get_extended_platforms() {
568 let extended_spec = per_platform
569 .get(extended_platform)
570 .unwrap_or(&base.to_owned())
571 .to_owned();
572 per_platform.insert(
573 extended_platform.to_owned(),
574 override_build_spec_internal(&extended_spec, &build_spec)?,
575 );
576 }
577 }
578 Ok(())
579}
580
581fn override_build_spec_internal(
582 base: &BuildSpecInternal,
583 override_spec: &BuildSpecInternal,
584) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
585 Ok(BuildSpecInternal {
586 build_type: override_opt(&override_spec.build_type, &base.build_type),
587 builtin_spec: match (
588 override_spec.builtin_spec.clone(),
589 base.builtin_spec.clone(),
590 ) {
591 (Some(override_val), Some(base_spec_map)) => {
592 Some(base_spec_map.into_iter().chain(override_val).try_fold(
593 HashMap::default(),
594 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
595 (k, module_spec_override)|
596 -> Result<
597 HashMap<LuaTableKey, ModuleSpecInternal>,
598 ModuleSpecAmbiguousPlatformOverride,
599 > {
600 let overridden = match acc.get(&k) {
601 None => module_spec_override,
602 Some(base_module_spec) => {
603 base_module_spec.apply_overrides(&module_spec_override)?
604 }
605 };
606 acc.insert(k, overridden);
607 Ok(acc)
608 },
609 )?)
610 }
611 (override_val @ Some(_), _) => override_val,
612 (_, base_val @ Some(_)) => base_val,
613 _ => None,
614 },
615 makefile: override_opt(&override_spec.makefile, &base.makefile),
616 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
617 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
618 make_install_target: override_opt(
619 &override_spec.make_install_target,
620 &base.make_install_target,
621 ),
622 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
623 make_build_variables: merge_map_opts(
624 &override_spec.make_build_variables,
625 &base.make_build_variables,
626 ),
627 make_install_variables: merge_map_opts(
628 &override_spec.make_install_variables,
629 &base.make_build_variables,
630 ),
631 variables: merge_map_opts(&override_spec.variables, &base.variables),
632 cmake_lists_content: override_opt(
633 &override_spec.cmake_lists_content,
634 &base.cmake_lists_content,
635 ),
636 build_command: override_opt(&override_spec.build_command, &base.build_command),
637 install_command: override_opt(&override_spec.install_command, &base.install_command),
638 install: override_opt(&override_spec.install, &base.install),
639 copy_directories: match (
640 override_spec.copy_directories.clone(),
641 base.copy_directories.clone(),
642 ) {
643 (Some(override_vec), Some(base_vec)) => {
644 let merged: Vec<PathBuf> =
645 base_vec.into_iter().chain(override_vec).unique().collect();
646 Some(merged)
647 }
648 (None, base_vec @ Some(_)) => base_vec,
649 (override_vec @ Some(_), None) => override_vec,
650 _ => None,
651 },
652 patches: override_opt(&override_spec.patches, &base.patches),
653 target_path: override_opt(&override_spec.target_path, &base.target_path),
654 default_features: override_opt(&override_spec.default_features, &base.default_features),
655 features: override_opt(&override_spec.features, &base.features),
656 include: merge_map_opts(&override_spec.include, &base.include),
657 lang: override_opt(&override_spec.lang, &base.lang),
658 parser: override_opt(&override_spec.parser, &base.parser),
659 generate: override_opt(&override_spec.generate, &base.generate),
660 location: override_opt(&override_spec.location, &base.location),
661 queries: merge_map_opts(&override_spec.queries, &base.queries),
662 })
663}
664
665fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
666 match override_opt.clone() {
667 override_val @ Some(_) => override_val,
668 None => base.clone(),
669 }
670}
671
672fn merge_map_opts<K, V>(
673 override_map: &Option<HashMap<K, V>>,
674 base_map: &Option<HashMap<K, V>>,
675) -> Option<HashMap<K, V>>
676where
677 K: Clone,
678 K: Eq,
679 K: std::hash::Hash,
680 V: Clone,
681{
682 match (override_map.clone(), base_map.clone()) {
683 (Some(override_map), Some(base_map)) => {
684 Some(base_map.into_iter().chain(override_map).collect())
685 }
686 (_, base_map @ Some(_)) => base_map,
687 (override_map @ Some(_), _) => override_map,
688 _ => None,
689 }
690}
691
692impl DisplayAsLuaKV for BuildSpecInternal {
693 fn display_lua(&self) -> DisplayLuaKV {
694 let mut result = Vec::new();
695
696 if let Some(build_type) = &self.build_type {
697 result.push(DisplayLuaKV {
698 key: "type".to_string(),
699 value: DisplayLuaValue::String(build_type.to_string()),
700 });
701 }
702 if let Some(builtin_spec) = &self.builtin_spec {
703 result.push(DisplayLuaKV {
704 key: "modules".to_string(),
705 value: DisplayLuaValue::Table(
706 builtin_spec
707 .iter()
708 .map(|(key, value)| DisplayLuaKV {
709 key: match key {
710 LuaTableKey::StringKey(s) => s.clone(),
711 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
712 },
713 value: value.display_lua_value(),
714 })
715 .collect(),
716 ),
717 });
718 }
719 if let Some(makefile) = &self.makefile {
720 result.push(DisplayLuaKV {
721 key: "makefile".to_string(),
722 value: DisplayLuaValue::String(makefile.to_string_lossy().to_string()),
723 });
724 }
725 if let Some(make_build_target) = &self.make_build_target {
726 result.push(DisplayLuaKV {
727 key: "build_target".to_string(),
728 value: DisplayLuaValue::String(make_build_target.clone()),
729 });
730 }
731 if let Some(build_pass) = &self.build_pass {
732 result.push(DisplayLuaKV {
733 key: "build_pass".to_string(),
734 value: DisplayLuaValue::Boolean(*build_pass),
735 });
736 }
737 if let Some(make_install_target) = &self.make_install_target {
738 result.push(DisplayLuaKV {
739 key: "install_target".to_string(),
740 value: DisplayLuaValue::String(make_install_target.clone()),
741 });
742 }
743 if let Some(install_pass) = &self.install_pass {
744 result.push(DisplayLuaKV {
745 key: "install_pass".to_string(),
746 value: DisplayLuaValue::Boolean(*install_pass),
747 });
748 }
749 if let Some(make_build_variables) = &self.make_build_variables {
750 result.push(DisplayLuaKV {
751 key: "build_variables".to_string(),
752 value: DisplayLuaValue::Table(
753 make_build_variables
754 .iter()
755 .map(|(key, value)| DisplayLuaKV {
756 key: key.clone(),
757 value: DisplayLuaValue::String(value.clone()),
758 })
759 .collect(),
760 ),
761 });
762 }
763 if let Some(make_install_variables) = &self.make_install_variables {
764 result.push(DisplayLuaKV {
765 key: "install_variables".to_string(),
766 value: DisplayLuaValue::Table(
767 make_install_variables
768 .iter()
769 .map(|(key, value)| DisplayLuaKV {
770 key: key.clone(),
771 value: DisplayLuaValue::String(value.clone()),
772 })
773 .collect(),
774 ),
775 });
776 }
777 if let Some(variables) = &self.variables {
778 result.push(DisplayLuaKV {
779 key: "variables".to_string(),
780 value: DisplayLuaValue::Table(
781 variables
782 .iter()
783 .map(|(key, value)| DisplayLuaKV {
784 key: key.clone(),
785 value: DisplayLuaValue::String(value.clone()),
786 })
787 .collect(),
788 ),
789 });
790 }
791 if let Some(cmake_lists_content) = &self.cmake_lists_content {
792 result.push(DisplayLuaKV {
793 key: "cmake".to_string(),
794 value: DisplayLuaValue::String(cmake_lists_content.clone()),
795 });
796 }
797 if let Some(build_command) = &self.build_command {
798 result.push(DisplayLuaKV {
799 key: "build_command".to_string(),
800 value: DisplayLuaValue::String(build_command.clone()),
801 });
802 }
803 if let Some(install_command) = &self.install_command {
804 result.push(DisplayLuaKV {
805 key: "install_command".to_string(),
806 value: DisplayLuaValue::String(install_command.clone()),
807 });
808 }
809 if let Some(install) = &self.install {
810 result.push(install.display_lua());
811 }
812 if let Some(copy_directories) = &self.copy_directories {
813 result.push(DisplayLuaKV {
814 key: "copy_directories".to_string(),
815 value: DisplayLuaValue::List(
816 copy_directories
817 .iter()
818 .map(|path_buf| {
819 DisplayLuaValue::String(path_buf.to_string_lossy().to_string())
820 })
821 .collect(),
822 ),
823 });
824 }
825 if let Some(patches) = &self.patches {
826 result.push(DisplayLuaKV {
827 key: "patches".to_string(),
828 value: DisplayLuaValue::Table(
829 patches
830 .iter()
831 .map(|(key, value)| DisplayLuaKV {
832 key: key.to_string_lossy().to_string(),
833 value: DisplayLuaValue::String(value.clone()),
834 })
835 .collect(),
836 ),
837 });
838 }
839 if let Some(target_path) = &self.target_path {
840 result.push(DisplayLuaKV {
841 key: "target_path".to_string(),
842 value: DisplayLuaValue::String(target_path.to_string_lossy().to_string()),
843 });
844 }
845 if let Some(default_features) = &self.default_features {
846 result.push(DisplayLuaKV {
847 key: "default_features".to_string(),
848 value: DisplayLuaValue::Boolean(*default_features),
849 });
850 }
851 if let Some(include) = &self.include {
852 result.push(DisplayLuaKV {
853 key: "include".to_string(),
854 value: DisplayLuaValue::Table(
855 include
856 .iter()
857 .map(|(key, value)| DisplayLuaKV {
858 key: match key {
859 LuaTableKey::StringKey(s) => s.clone(),
860 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
861 },
862 value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
863 })
864 .collect(),
865 ),
866 });
867 }
868 if let Some(features) = &self.features {
869 result.push(DisplayLuaKV {
870 key: "features".to_string(),
871 value: DisplayLuaValue::List(
872 features
873 .iter()
874 .map(|feature| DisplayLuaValue::String(feature.clone()))
875 .collect(),
876 ),
877 });
878 }
879 if let Some(lang) = &self.lang {
880 result.push(DisplayLuaKV {
881 key: "lang".to_string(),
882 value: DisplayLuaValue::String(lang.to_string()),
883 });
884 }
885 if let Some(parser) = &self.parser {
886 result.push(DisplayLuaKV {
887 key: "parser".to_string(),
888 value: DisplayLuaValue::Boolean(*parser),
889 });
890 }
891 if let Some(generate) = &self.generate {
892 result.push(DisplayLuaKV {
893 key: "generate".to_string(),
894 value: DisplayLuaValue::Boolean(*generate),
895 });
896 }
897 if let Some(location) = &self.location {
898 result.push(DisplayLuaKV {
899 key: "location".to_string(),
900 value: DisplayLuaValue::String(location.to_string_lossy().to_string()),
901 });
902 }
903 if let Some(queries) = &self.queries {
904 result.push(DisplayLuaKV {
905 key: "queries".to_string(),
906 value: DisplayLuaValue::Table(
907 queries
908 .iter()
909 .map(|(key, value)| DisplayLuaKV {
910 key: key.to_string_lossy().to_string(),
911 value: DisplayLuaValue::String(value.to_string()),
912 })
913 .collect(),
914 ),
915 });
916 }
917
918 DisplayLuaKV {
919 key: "build".to_string(),
920 value: DisplayLuaValue::Table(result),
921 }
922 }
923}
924
925#[derive(Debug, PartialEq, Deserialize, Clone)]
927#[serde(rename_all = "lowercase", remote = "BuildType")]
928pub(crate) enum BuildType {
929 Builtin,
931 Make,
933 CMake,
935 Command,
937 None,
939 LuaRock(String),
941 #[serde(rename = "rust-mlua")]
942 RustMlua,
943 #[serde(rename = "treesitter-parser")]
944 TreesitterParser,
945 Source,
946}
947
948impl<'de> Deserialize<'de> for BuildType {
951 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
952 where
953 D: Deserializer<'de>,
954 {
955 let s = String::deserialize(deserializer)?;
956 if s == "builtin" || s == "module" {
957 Ok(Self::Builtin)
958 } else {
959 match Self::deserialize(s.clone().into_deserializer()) {
960 Err(_) => Ok(Self::LuaRock(s)),
961 ok => ok,
962 }
963 }
964 }
965}
966
967impl Display for BuildType {
968 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
969 match self {
970 BuildType::Builtin => write!(f, "builtin"),
971 BuildType::Make => write!(f, "make"),
972 BuildType::CMake => write!(f, "cmake"),
973 BuildType::Command => write!(f, "command"),
974 BuildType::None => write!(f, "none"),
975 BuildType::LuaRock(s) => write!(f, "{}", s),
976 BuildType::RustMlua => write!(f, "rust-mlua"),
977 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
978 BuildType::Source => write!(f, "source"),
979 }
980 }
981}
982
983impl Default for BuildType {
984 fn default() -> Self {
985 Self::Builtin
986 }
987}
988
989#[cfg(test)]
990mod tests {
991
992 use super::*;
993
994 #[tokio::test]
995 pub async fn deserialize_build_type() {
996 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
997 assert_eq!(build_type, BuildType::Builtin);
998 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
999 assert_eq!(build_type, BuildType::Builtin);
1000 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1001 assert_eq!(build_type, BuildType::Make);
1002 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1003 assert_eq!(
1004 build_type,
1005 BuildType::LuaRock("custom_build_backend".into())
1006 );
1007 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1008 assert_eq!(build_type, BuildType::RustMlua);
1009 }
1010}