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