1mod builtin;
2mod cmake;
3mod make;
4mod rust_binary;
5mod rust_mlua;
6mod tree_sitter;
7
8use crate::package;
9pub use builtin::{BuiltinBuildSpec, LuaModule, ModulePaths, ModuleSpec, ParseLuaModuleError};
10pub use cmake::*;
11pub use make::*;
12use path_slash::PathBufExt;
13pub use rust_binary::*;
14pub use rust_mlua::*;
15pub use tree_sitter::*;
16
17pub(crate) use builtin::ModuleSpecInternal;
18
19use builtin::{ModulePathsMissingSources, ModuleSpecAmbiguousPlatformOverride};
20
21use itertools::Itertools;
22
23use miette::Diagnostic;
24use serde::{de, de::IntoDeserializer, Deserialize, Deserializer};
25use std::{
26 collections::HashMap, convert::Infallible, env::consts::DLL_EXTENSION, env::consts::DLL_PREFIX,
27 fmt::Display, path::PathBuf, str::FromStr,
28};
29use thiserror::Error;
30
31use crate::{
32 lua_rockspec::per_platform_from_intermediate, rockspec::lua_dependency::LuaDependencySpec,
33};
34
35use super::{
36 DisplayAsLuaKV, DisplayAsLuaValue, DisplayLuaKV, DisplayLuaValue, LuaTableKey, LuaValueSeed,
37 PartialOverride, PerPlatform, PlatformOverridable,
38};
39
40#[derive(Clone, Debug, PartialEq)]
42pub struct BuildSpec {
43 pub build_backend: Option<BuildBackendSpec>,
45 pub install: InstallSpec,
49 pub copy_directories: Vec<PathBuf>,
51 pub patches: HashMap<PathBuf, String>,
55}
56
57impl Default for BuildSpec {
58 fn default() -> Self {
59 Self {
60 build_backend: Some(BuildBackendSpec::default()),
61 install: InstallSpec::default(),
62 copy_directories: Vec::default(),
63 patches: HashMap::default(),
64 }
65 }
66}
67
68#[derive(Error, Debug, Diagnostic)]
69#[non_exhaustive]
70pub enum BuildSpecInternalError {
71 #[error("'builtin' modules should not have list elements")]
72 ModulesHaveListElements,
73 #[error("no 'modules' specified for the 'rust-mlua' build backend")]
74 NoModulesSpecified,
75 #[error("no 'lang' specified for 'treesitter-parser' build backend")]
76 NoTreesitterParserLanguageSpecified,
77 #[error("invalid 'rust-mlua' modules format")]
78 InvalidRustMLuaFormat,
79 #[error(transparent)]
80 #[diagnostic(transparent)]
81 ModulePathsMissingSources(#[from] ModulePathsMissingSources),
82 #[error(transparent)]
83 #[diagnostic(transparent)]
84 ParseLuaModuleError(#[from] ParseLuaModuleError),
85}
86
87impl BuildSpec {
88 pub(crate) fn from_internal_spec(
89 internal: BuildSpecInternal,
90 ) -> Result<Self, BuildSpecInternalError> {
91 let build_backend = match internal.build_type.unwrap_or_default() {
92 BuildType::Builtin => Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
93 modules: internal
94 .builtin_spec
95 .unwrap_or_default()
96 .into_iter()
97 .map(|(key, module_spec_internal)| {
98 let key_str = match key {
99 LuaTableKey::IntKey(_) => {
100 Err(BuildSpecInternalError::ModulesHaveListElements)
101 }
102 LuaTableKey::StringKey(str) => Ok(LuaModule::from_str(str.as_str())?),
103 }?;
104 match ModuleSpec::from_internal(module_spec_internal) {
105 Ok(module_spec) => Ok((key_str, module_spec)),
106 Err(err) => Err(err.into()),
107 }
108 })
109 .collect::<Result<HashMap<LuaModule, ModuleSpec>, BuildSpecInternalError>>()?,
110 })),
111 BuildType::Make => {
112 let default = MakeBuildSpec::default();
113 Some(BuildBackendSpec::Make(MakeBuildSpec {
114 makefile: internal.makefile.unwrap_or(default.makefile),
115 build_target: internal.make_build_target,
116 build_pass: internal.build_pass.unwrap_or(default.build_pass),
117 install_target: internal
118 .make_install_target
119 .unwrap_or(default.install_target),
120 install_pass: internal.install_pass.unwrap_or(default.install_pass),
121 build_variables: internal.make_build_variables.unwrap_or_default(),
122 install_variables: internal.make_install_variables.unwrap_or_default(),
123 variables: internal.variables.unwrap_or_default(),
124 }))
125 }
126 BuildType::CMake => {
127 let default = CMakeBuildSpec::default();
128 Some(BuildBackendSpec::CMake(CMakeBuildSpec {
129 cmake_lists_content: internal.cmake_lists_content,
130 build_pass: internal.build_pass.unwrap_or(default.build_pass),
131 install_pass: internal.install_pass.unwrap_or(default.install_pass),
132 variables: internal.variables.unwrap_or_default(),
133 }))
134 }
135 BuildType::Command => Some(BuildBackendSpec::Command(CommandBuildSpec {
136 build_command: internal.build_command,
137 install_command: internal.install_command,
138 })),
139 BuildType::None => None,
140 BuildType::LuaRock(s) => Some(BuildBackendSpec::LuaRock(s)),
141 BuildType::RustMlua => Some(BuildBackendSpec::RustMlua(RustMluaBuildSpec {
142 modules: internal
143 .builtin_spec
144 .ok_or(BuildSpecInternalError::NoModulesSpecified)?
145 .into_iter()
146 .map(|(key, value)| match (key, value) {
147 (LuaTableKey::IntKey(_), ModuleSpecInternal::SourcePath(module)) => {
148 let mut rust_lib: PathBuf =
149 format!("{DLL_PREFIX}{}", module.display()).into();
150 rust_lib.set_extension(DLL_EXTENSION);
151 Ok((module.to_string_lossy().to_string(), rust_lib))
152 }
153 (
154 LuaTableKey::StringKey(module_name),
155 ModuleSpecInternal::SourcePath(module),
156 ) => {
157 let mut rust_lib: PathBuf =
158 format!("{DLL_PREFIX}{}", module.display()).into();
159 rust_lib.set_extension(DLL_EXTENSION);
160 Ok((module_name, rust_lib))
161 }
162 _ => Err(BuildSpecInternalError::InvalidRustMLuaFormat),
163 })
164 .try_collect()?,
165 target_path: internal.target_path.unwrap_or("target".into()),
166 default_features: internal.default_features.unwrap_or(true),
167 features: internal.features.unwrap_or_default(),
168 cargo_extra_args: internal.cargo_extra_args.unwrap_or_default(),
169 include: internal
170 .include
171 .unwrap_or_default()
172 .into_iter()
173 .map(|(key, dest)| match key {
174 LuaTableKey::IntKey(_) => (dest.clone(), dest),
175 LuaTableKey::StringKey(src) => (src.into(), dest),
176 })
177 .collect(),
178 })),
179 BuildType::RustBinary => Some(BuildBackendSpec::RustBinary(RustBinaryBuildSpec {
180 package: internal.package,
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(true),
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 TryFrom<BuildSpecInternal> for BuildSpec {
206 type Error = BuildSpecInternalError;
207
208 fn try_from(internal: BuildSpecInternal) -> Result<Self, Self::Error> {
209 BuildSpec::from_internal_spec(internal)
210 }
211}
212
213impl<'de> Deserialize<'de> for BuildSpec {
214 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215 where
216 D: Deserializer<'de>,
217 {
218 let internal = BuildSpecInternal::deserialize(deserializer)?;
219 BuildSpec::from_internal_spec(internal).map_err(de::Error::custom)
220 }
221}
222
223impl<'de> Deserialize<'de> for PerPlatform<BuildSpec> {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: Deserializer<'de>,
231 {
232 per_platform_from_intermediate::<_, BuildSpecInternal, _>(deserializer)
233 }
234}
235
236impl Default for BuildBackendSpec {
237 fn default() -> Self {
238 Self::Builtin(BuiltinBuildSpec::default())
239 }
240}
241
242#[derive(Debug, PartialEq, Clone)]
249pub enum BuildBackendSpec {
250 Builtin(BuiltinBuildSpec),
251 Make(MakeBuildSpec),
252 CMake(CMakeBuildSpec),
253 Command(CommandBuildSpec),
254 LuaRock(String),
255 RustMlua(RustMluaBuildSpec),
256 RustBinary(RustBinaryBuildSpec),
257 TreesitterParser(TreesitterParserBuildSpec),
258 Source,
264}
265
266impl BuildBackendSpec {
267 pub(crate) fn can_use_build_dependencies(&self) -> bool {
268 match self {
269 Self::Make(_) | Self::CMake(_) | Self::Command(_) | Self::LuaRock(_) => true,
270 Self::Builtin(_)
271 | Self::RustMlua(_)
272 | Self::RustBinary(_)
273 | Self::TreesitterParser(_)
274 | Self::Source => false,
275 }
276 }
277}
278
279#[derive(Debug, PartialEq, Clone)]
281pub struct CommandBuildSpec {
282 pub build_command: Option<String>,
283 pub install_command: Option<String>,
284}
285
286#[derive(Clone, Debug)]
287struct LuaPathBufTable(HashMap<LuaTableKey, PathBuf>);
288
289impl<'de> Deserialize<'de> for LuaPathBufTable {
290 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
291 Ok(LuaPathBufTable(
292 deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
293 ))
294 }
295}
296
297impl LuaPathBufTable {
298 fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
299 where
300 S: FromStr + Eq + std::hash::Hash,
301 {
302 self.0
303 .into_iter()
304 .map(|(key, value)| {
305 let key = match key {
306 LuaTableKey::IntKey(_) => value
307 .with_extension("")
308 .file_name()
309 .unwrap_or_default()
310 .to_string_lossy()
311 .to_string(),
312 LuaTableKey::StringKey(key) => key,
313 };
314 Ok((S::from_str(&key)?, value))
315 })
316 .try_collect()
317 }
318}
319
320#[derive(Clone, Debug)]
321struct LibPathBufTable(HashMap<LuaTableKey, PathBuf>);
322
323impl<'de> Deserialize<'de> for LibPathBufTable {
324 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
325 Ok(LibPathBufTable(
326 deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
327 ))
328 }
329}
330
331impl LibPathBufTable {
332 fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
333 where
334 S: FromStr + Eq + std::hash::Hash,
335 {
336 self.0
337 .into_iter()
338 .map(|(key, value)| {
339 let key = match key {
340 LuaTableKey::IntKey(_) => value
341 .file_name()
342 .unwrap_or_default()
343 .to_string_lossy()
344 .to_string(),
345 LuaTableKey::StringKey(key) => key,
346 };
347 Ok((S::from_str(&key)?, value))
348 })
349 .try_collect()
350 }
351}
352
353#[derive(Debug, PartialEq, Default, Deserialize, Clone, lux_macros::DisplayAsLuaKV)]
362#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
363#[display_lua(key = "install")]
364pub struct InstallSpec {
365 #[serde(default, deserialize_with = "deserialize_module_path_map")]
367 #[cfg_attr(
368 feature = "schema",
369 schemars(with = "std::collections::BTreeMap<String, String>")
370 )]
371 pub lua: HashMap<LuaModule, PathBuf>,
372 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
374 #[cfg_attr(
375 feature = "schema",
376 schemars(with = "std::collections::BTreeMap<String, String>")
377 )]
378 pub lib: HashMap<String, PathBuf>,
379 #[serde(default)]
381 pub conf: HashMap<String, PathBuf>,
382 #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
386 #[cfg_attr(
387 feature = "schema",
388 schemars(with = "std::collections::BTreeMap<String, String>")
389 )]
390 pub bin: HashMap<String, PathBuf>,
391}
392
393fn deserialize_module_path_map<'de, D>(
394 deserializer: D,
395) -> Result<HashMap<LuaModule, PathBuf>, D::Error>
396where
397 D: Deserializer<'de>,
398{
399 let modules = LuaPathBufTable::deserialize(deserializer)?;
400 modules.coerce().map_err(de::Error::custom)
401}
402
403fn deserialize_file_name_path_map<'de, D>(
404 deserializer: D,
405) -> Result<HashMap<String, PathBuf>, D::Error>
406where
407 D: Deserializer<'de>,
408{
409 let binaries = LibPathBufTable::deserialize(deserializer)?;
410 binaries.coerce().map_err(de::Error::custom)
411}
412
413fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
414where
415 D: Deserializer<'de>,
416{
417 let value: Option<serde_value::Value> = Option::deserialize(deserializer)?;
418 let copy_directories: Option<Vec<String>> = match value {
419 Some(value) => Some(value.deserialize_into().map_err(de::Error::custom)?),
420 None => None,
421 };
422 let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
423 match special_directories
424 .into_iter()
425 .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
426 {
427 Some(d) => Err(format!(
430 "directory '{d}' in copy_directories clashes with the .rock format", )),
432 _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
433 }
434 .map_err(de::Error::custom)
435}
436
437fn deserialize_map_or_seq<'de, D, V>(
439 deserializer: D,
440) -> Result<Option<HashMap<LuaTableKey, V>>, D::Error>
441where
442 D: Deserializer<'de>,
443 V: de::DeserializeOwned,
444{
445 match de::DeserializeSeed::deserialize(LuaValueSeed, deserializer).map_err(de::Error::custom)? {
446 serde_value::Value::Map(map) => map
447 .into_iter()
448 .map(|(k, v)| {
449 let key = match k {
450 serde_value::Value::I64(i) => LuaTableKey::IntKey(i as u64),
451 serde_value::Value::U64(u) => LuaTableKey::IntKey(u),
452 serde_value::Value::String(s) => LuaTableKey::StringKey(s),
453 other => {
454 return Err(de::Error::custom(format!("unexpected map key: {other:?}")))
455 }
456 };
457 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
458 Ok((key, val))
459 })
460 .try_collect()
461 .map(Some),
462 serde_value::Value::Seq(seq) => seq
463 .into_iter()
464 .enumerate()
465 .map(|(i, v)| {
466 let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
467 Ok((LuaTableKey::IntKey(i as u64 + 1), val))
468 })
469 .try_collect()
470 .map(Some),
471 serde_value::Value::Unit => Ok(None),
472 other => Err(de::Error::custom(format!(
473 "expected a table or nil, got {other:?}"
474 ))),
475 }
476}
477
478fn display_builtin_spec(spec: &HashMap<LuaTableKey, ModuleSpecInternal>) -> DisplayLuaValue {
479 DisplayLuaValue::Table(
480 spec.iter()
481 .map(|(key, value)| DisplayLuaKV {
482 key: match key {
483 LuaTableKey::StringKey(s) => s.clone(),
484 LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
485 },
486 value: value.display_lua_value(),
487 })
488 .collect(),
489 )
490}
491
492fn display_path_string_map(map: &HashMap<PathBuf, String>) -> DisplayLuaValue {
493 DisplayLuaValue::Table(
494 map.iter()
495 .map(|(k, v)| DisplayLuaKV {
496 key: k.to_slash_lossy().into_owned(),
497 value: DisplayLuaValue::String(v.clone()),
498 })
499 .collect(),
500 )
501}
502
503fn display_include(include: &HashMap<LuaTableKey, PathBuf>) -> DisplayLuaValue {
504 DisplayLuaValue::Table(
505 include
506 .iter()
507 .map(|(key, value)| DisplayLuaKV {
508 key: match key {
509 LuaTableKey::StringKey(s) => s.clone(),
510 LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
511 },
512 value: DisplayLuaValue::String(value.to_slash_lossy().into_owned()),
513 })
514 .collect(),
515 )
516}
517
518#[derive(Debug, PartialEq, Deserialize, Default, Clone, lux_macros::DisplayAsLuaKV)]
519#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
520#[display_lua(key = "build")]
521pub(crate) struct BuildSpecInternal {
522 #[serde(rename = "type", default)]
535 #[display_lua(rename = "type")]
536 #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
537 #[cfg_attr(
538 feature = "schema",
539 schemars(extend("x-lux-toml-example" = r#"
540[build]
541type = "builtin"
542"#))
543 )]
544 pub(crate) build_type: Option<BuildType>,
545 #[serde(
549 rename = "modules",
550 default,
551 deserialize_with = "deserialize_map_or_seq"
552 )]
553 #[display_lua(rename = "modules", convert_with = "display_builtin_spec")]
554 #[cfg_attr(
555 feature = "schema",
556 schemars(with = "Option<std::collections::BTreeMap<String, crate::schema::ModuleSpec>>")
557 )]
558 #[cfg_attr(
559 feature = "schema",
560 schemars(extend("x-lux-toml-example" = r#"
561[build.modules]
562"foo.main" = "lua/foo.lua"
563"foo.bla" = ["src/bla.c"]
564
565[build.modules."foo.core"]
566sources = ["src/core.c", "src/extra.c"]
567libraries = ["ssl"]
568defines = ["FOO=bar", "USE_BLA"]
569incdirs = []
570libdirs = []
571"#))
572 )]
573 pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
574 #[serde(default)]
576 #[cfg_attr(
577 feature = "schema",
578 schemars(extend("x-lux-toml-example" = r#"
579[build]
580type = "make"
581makefile = "Makefile"
582build_target = ""
583build_pass = true
584install_target = "install"
585install_pass = true
586build_variables = {}
587install_variables = {}
588variables = {}
589"#))
590 )]
591 pub(crate) makefile: Option<PathBuf>,
592 #[serde(rename = "build_target", default)]
594 #[display_lua(rename = "build_target")]
595 pub(crate) make_build_target: Option<String>,
596 #[serde(default)]
598 pub(crate) build_pass: Option<bool>,
599 #[serde(rename = "install_target", default)]
601 #[display_lua(rename = "install_target")]
602 pub(crate) make_install_target: Option<String>,
603 #[serde(default)]
605 pub(crate) install_pass: Option<bool>,
606 #[serde(rename = "build_variables", default)]
608 #[display_lua(rename = "build_variables")]
609 pub(crate) make_build_variables: Option<HashMap<String, String>>,
610 #[serde(rename = "install_variables", default)]
612 #[display_lua(rename = "install_variables")]
613 pub(crate) make_install_variables: Option<HashMap<String, String>>,
614 #[serde(default)]
616 pub(crate) variables: Option<HashMap<String, String>>,
617 #[serde(rename = "cmake", default)]
623 #[display_lua(rename = "cmake")]
624 #[cfg_attr(
625 feature = "schema",
626 schemars(extend("x-lux-toml-example" = r#"
627[build]
628type = "cmake"
629cmake = """
630<content>
631"""
632"#))
633 )]
634 pub(crate) cmake_lists_content: Option<String>,
635 #[serde(default)]
637 #[cfg_attr(
638 feature = "schema",
639 schemars(extend("x-lux-toml-example" = r#"
640[build]
641type = "command"
642build_command = "make"
643install_command = "make install"
644"#))
645 )]
646 pub(crate) build_command: Option<String>,
647 #[serde(default)]
649 pub(crate) install_command: Option<String>,
650 #[serde(default)]
652 #[cfg_attr(
653 feature = "schema",
654 schemars(extend("x-lux-toml-example" = r#"
655[build.install.lua]
656"foo.bar" = "src/bar.lua"
657
658[build.install.lib]
659"foo.bar" = "lib/bar.so"
660
661[build.install.conf]
662
663[build.install.bin]
664"#))
665 )]
666 pub(crate) install: Option<InstallSpec>,
667 #[serde(default, deserialize_with = "deserialize_copy_directories")]
670 #[cfg_attr(feature = "schema", schemars(with = "Option<Vec<String>>"))]
671 #[cfg_attr(
672 feature = "schema",
673 schemars(extend("x-lux-toml-example" = r#"
674[build]
675copy_directories = ["plugin", "ftplugin"]
676"#))
677 )]
678 pub(crate) copy_directories: Option<Vec<PathBuf>>,
679 #[serde(default)]
683 #[display_lua(convert_with = "display_path_string_map")]
684 #[cfg_attr(
685 feature = "schema",
686 schemars(with = "Option<std::collections::BTreeMap<String, String>>")
687 )]
688 #[cfg_attr(
689 feature = "schema",
690 schemars(extend("x-lux-toml-example" = r#"
691[build.patches]
692"lua51-support.diff" = """
693--- old/mymodule.c....
694"""
695"#))
696 )]
697 pub(crate) patches: Option<HashMap<PathBuf, String>>,
698 #[serde(default)]
700 #[cfg_attr(
701 feature = "schema",
702 schemars(extend("x-lux-toml-example" = r#"
703[build]
704type = "rust-mlua"
705target_path = "target"
706default_features = false
707features = []
708cargo_extra_args = []
709
710[build.modules]
711"foo.main" = "alt_name"
712
713[build.include]
714"relative/path/to/file.lua" = "target-file.lua"
715"#))
716 )]
717 pub(crate) target_path: Option<PathBuf>,
718 #[serde(default)]
720 pub(crate) default_features: Option<bool>,
721 #[serde(default)]
723 pub(crate) features: Option<Vec<String>>,
724 pub(crate) cargo_extra_args: Option<Vec<String>>,
726 #[serde(default)]
729 pub(crate) package: Option<String>,
730 #[serde(default, deserialize_with = "deserialize_map_or_seq")]
733 #[display_lua(convert_with = "display_include")]
734 #[cfg_attr(
735 feature = "schema",
736 schemars(with = "Option<std::collections::BTreeMap<String, String>>")
737 )]
738 pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
739 #[serde(default)]
741 #[cfg_attr(
742 feature = "schema",
743 schemars(extend("x-lux-toml-example" = r#"
744[build]
745type = "treesitter-parser"
746lang = "haskell"
747parser = true
748generate = true
749location = "libs/tree-sitter-haskell"
750
751[build.queries."highlights.scm"] = """
752(signature
753 name: (variable) @function)
754"""
755"#))
756 )]
757 pub(crate) lang: Option<String>,
758 #[serde(default)]
761 pub(crate) parser: Option<bool>,
762 #[serde(default)]
765 pub(crate) generate: Option<bool>,
766 #[serde(default)]
768 pub(crate) location: Option<PathBuf>,
769 #[serde(default)]
772 #[display_lua(convert_with = "display_path_string_map")]
773 #[cfg_attr(
774 feature = "schema",
775 schemars(with = "Option<std::collections::BTreeMap<String, String>>")
776 )]
777 pub(crate) queries: Option<HashMap<PathBuf, String>>,
778}
779
780impl PartialOverride for BuildSpecInternal {
781 type Err = ModuleSpecAmbiguousPlatformOverride;
782
783 fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
784 override_build_spec_internal(self, override_spec)
785 }
786}
787
788impl PlatformOverridable for BuildSpecInternal {
789 type Err = Infallible;
790
791 fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
792 where
793 T: PlatformOverridable,
794 T: Default,
795 {
796 Ok(PerPlatform::default())
797 }
798}
799
800fn override_build_spec_internal(
801 base: &BuildSpecInternal,
802 override_spec: &BuildSpecInternal,
803) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
804 Ok(BuildSpecInternal {
805 build_type: override_opt(&override_spec.build_type, &base.build_type),
806 builtin_spec: match (
807 override_spec.builtin_spec.clone(),
808 base.builtin_spec.clone(),
809 ) {
810 (Some(override_val), Some(base_spec_map)) => {
811 Some(base_spec_map.into_iter().chain(override_val).try_fold(
812 HashMap::default(),
813 |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
814 (k, module_spec_override)|
815 -> Result<
816 HashMap<LuaTableKey, ModuleSpecInternal>,
817 ModuleSpecAmbiguousPlatformOverride,
818 > {
819 let overridden = match acc.get(&k) {
820 None => module_spec_override,
821 Some(base_module_spec) => {
822 base_module_spec.apply_overrides(&module_spec_override)?
823 }
824 };
825 acc.insert(k, overridden);
826 Ok(acc)
827 },
828 )?)
829 }
830 (override_val @ Some(_), _) => override_val,
831 (_, base_val @ Some(_)) => base_val,
832 _ => None,
833 },
834 makefile: override_opt(&override_spec.makefile, &base.makefile),
835 make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
836 build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
837 make_install_target: override_opt(
838 &override_spec.make_install_target,
839 &base.make_install_target,
840 ),
841 install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
842 make_build_variables: merge_map_opts(
843 &override_spec.make_build_variables,
844 &base.make_build_variables,
845 ),
846 make_install_variables: merge_map_opts(
847 &override_spec.make_install_variables,
848 &base.make_build_variables,
849 ),
850 variables: merge_map_opts(&override_spec.variables, &base.variables),
851 cmake_lists_content: override_opt(
852 &override_spec.cmake_lists_content,
853 &base.cmake_lists_content,
854 ),
855 build_command: override_opt(&override_spec.build_command, &base.build_command),
856 install_command: override_opt(&override_spec.install_command, &base.install_command),
857 install: override_opt(&override_spec.install, &base.install),
858 copy_directories: match (
859 override_spec.copy_directories.clone(),
860 base.copy_directories.clone(),
861 ) {
862 (Some(override_vec), Some(base_vec)) => {
863 let merged: Vec<PathBuf> =
864 base_vec.into_iter().chain(override_vec).unique().collect();
865 Some(merged)
866 }
867 (None, base_vec @ Some(_)) => base_vec,
868 (override_vec @ Some(_), None) => override_vec,
869 _ => None,
870 },
871 patches: override_opt(&override_spec.patches, &base.patches),
872 target_path: override_opt(&override_spec.target_path, &base.target_path),
873 default_features: override_opt(&override_spec.default_features, &base.default_features),
874 features: override_opt(&override_spec.features, &base.features),
875 cargo_extra_args: override_opt(&override_spec.cargo_extra_args, &base.cargo_extra_args),
876 package: override_opt(&override_spec.package, &base.package),
877 include: merge_map_opts(&override_spec.include, &base.include),
878 lang: override_opt(&override_spec.lang, &base.lang),
879 parser: override_opt(&override_spec.parser, &base.parser),
880 generate: override_opt(&override_spec.generate, &base.generate),
881 location: override_opt(&override_spec.location, &base.location),
882 queries: merge_map_opts(&override_spec.queries, &base.queries),
883 })
884}
885
886fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
887 match override_opt.clone() {
888 override_val @ Some(_) => override_val,
889 None => base.clone(),
890 }
891}
892
893fn merge_map_opts<K, V>(
894 override_map: &Option<HashMap<K, V>>,
895 base_map: &Option<HashMap<K, V>>,
896) -> Option<HashMap<K, V>>
897where
898 K: Clone,
899 K: Eq,
900 K: std::hash::Hash,
901 V: Clone,
902{
903 match (override_map.clone(), base_map.clone()) {
904 (Some(override_map), Some(base_map)) => {
905 Some(base_map.into_iter().chain(override_map).collect())
906 }
907 (_, base_map @ Some(_)) => base_map,
908 (override_map @ Some(_), _) => override_map,
909 _ => None,
910 }
911}
912
913#[derive(Debug, PartialEq, Deserialize, Clone)]
915#[serde(rename_all = "lowercase", remote = "BuildType")]
916#[derive(Default)]
917pub(crate) enum BuildType {
918 #[default]
920 Builtin,
921 Make,
923 CMake,
925 Command,
927 None,
929 LuaRock(String),
931 #[serde(rename = "rust-mlua")]
932 RustMlua,
933 #[serde(rename = "rust-binary")]
934 RustBinary,
935 #[serde(rename = "treesitter-parser")]
936 TreesitterParser,
937 Source,
938}
939
940impl BuildType {
941 pub(crate) fn luarocks_build_backend(&self) -> Option<LuaDependencySpec> {
942 match self {
943 &BuildType::Builtin
944 | &BuildType::Make
945 | &BuildType::CMake
946 | &BuildType::Command
947 | &BuildType::None
948 | &BuildType::Source => None,
949 BuildType::LuaRock(s) => Some(package::Name::new(format!("luarocks-build-{s}")).into()),
950 &BuildType::RustMlua => unsafe {
951 Some(
952 package::Req::parse("luarocks-build-rust-mlua >= 0.2.6")
953 .unwrap_unchecked()
954 .into(),
955 )
956 },
957 &BuildType::RustBinary => unsafe {
958 Some(
959 package::Req::parse("luarocks-build-rust-binary >= 4.0.0")
960 .unwrap_unchecked()
961 .into(),
962 )
963 },
964 &BuildType::TreesitterParser => {
965 Some(package::Name::new("luarocks-build-treesitter-parser >= 6.1.0".into()).into())
966 } }
969 }
970}
971
972impl<'de> Deserialize<'de> for BuildType {
975 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
976 where
977 D: Deserializer<'de>,
978 {
979 let s = String::deserialize(deserializer)?;
980 if s == "builtin" || s == "module" {
981 Ok(Self::Builtin)
982 } else {
983 match Self::deserialize(s.clone().into_deserializer()) {
984 Err(_) => Ok(Self::LuaRock(s)),
985 ok => ok,
986 }
987 }
988 }
989}
990
991impl Display for BuildType {
992 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
993 match self {
994 BuildType::Builtin => write!(f, "builtin"),
995 BuildType::Make => write!(f, "make"),
996 BuildType::CMake => write!(f, "cmake"),
997 BuildType::Command => write!(f, "command"),
998 BuildType::None => write!(f, "none"),
999 BuildType::LuaRock(s) => write!(f, "{s}"),
1000 BuildType::RustMlua => write!(f, "rust-mlua"),
1001 BuildType::RustBinary => write!(f, "rust-binary"),
1002 BuildType::TreesitterParser => write!(f, "treesitter-parser"),
1003 BuildType::Source => write!(f, "source"),
1004 }
1005 }
1006}
1007
1008impl DisplayAsLuaValue for BuildType {
1009 fn display_lua_value(&self) -> DisplayLuaValue {
1010 DisplayLuaValue::String(self.to_string())
1011 }
1012}
1013
1014impl DisplayAsLuaValue for InstallSpec {
1015 fn display_lua_value(&self) -> DisplayLuaValue {
1016 self.display_lua().value
1017 }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022
1023 use super::*;
1024
1025 fn eval_lua_global<T: serde::de::DeserializeOwned>(code: &str, key: &'static str) -> T {
1026 use ottavino::{Closure, Executor, Fuel, Lua};
1027 use ottavino_util::serde::from_value;
1028 Lua::core()
1029 .try_enter(|ctx| {
1030 let closure = Closure::load(ctx, None, code.as_bytes())?;
1031 let executor = Executor::start(ctx, closure.into(), ());
1032 executor.step(ctx, &mut Fuel::with(i32::MAX))?;
1033 from_value(ctx.globals().get_value(ctx, key)).map_err(ottavino::Error::from)
1034 })
1035 .unwrap()
1036 }
1037
1038 #[tokio::test]
1039 pub async fn deserialize_build_type() {
1040 let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
1041 assert_eq!(build_type, BuildType::Builtin);
1042 let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
1043 assert_eq!(build_type, BuildType::Builtin);
1044 let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1045 assert_eq!(build_type, BuildType::Make);
1046 let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1047 assert_eq!(
1048 build_type,
1049 BuildType::LuaRock("custom_build_backend".into())
1050 );
1051 let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1052 assert_eq!(build_type, BuildType::RustMlua);
1053 }
1054
1055 #[test]
1056 pub fn install_spec_roundtrip() {
1057 let spec = InstallSpec {
1058 lua: HashMap::from([(
1059 "mymod".parse::<LuaModule>().unwrap(),
1060 "src/mymod.lua".into(),
1061 )]),
1062 lib: HashMap::from([("mylib".into(), "lib/mylib.so".into())]),
1063 conf: HashMap::from([("myconf".into(), "conf/myconf.cfg".into())]),
1064 bin: HashMap::from([("mybinary".into(), "bin/mybinary".into())]),
1065 };
1066 let lua = spec.display_lua().to_string();
1067 let restored: InstallSpec = eval_lua_global(&lua, "install");
1068 assert_eq!(spec, restored);
1069 }
1070
1071 #[test]
1072 pub fn install_spec_empty_roundtrip() {
1073 let spec = InstallSpec::default();
1074 let lua = spec.display_lua().to_string();
1075 let lua = if lua.trim().is_empty() {
1076 "install = {}".to_string()
1077 } else {
1078 lua
1079 };
1080 let restored: InstallSpec = eval_lua_global(&lua, "install");
1081 assert_eq!(spec, restored);
1082 }
1083
1084 #[test]
1085 pub fn build_spec_internal_builtin_roundtrip() {
1086 let spec = BuildSpecInternal {
1087 build_type: Some(BuildType::Builtin),
1088 builtin_spec: Some(HashMap::from([(
1089 LuaTableKey::StringKey("mymod".into()),
1090 ModuleSpecInternal::SourcePath("src/mymod.lua".into()),
1091 )])),
1092 install: Some(InstallSpec {
1093 lua: HashMap::from([(
1094 "extra".parse::<LuaModule>().unwrap(),
1095 "src/extra.lua".into(),
1096 )]),
1097 bin: HashMap::from([("mytool".into(), "bin/mytool".into())]),
1098 ..Default::default()
1099 }),
1100 copy_directories: Some(vec!["docs".into()]),
1101 ..Default::default()
1102 };
1103 let lua = spec.display_lua().to_string();
1104 let restored: BuildSpecInternal = eval_lua_global(&lua, "build");
1105 assert_eq!(spec, restored);
1106 }
1107
1108 #[test]
1109 pub fn build_spec_internal_make_roundtrip() {
1110 let spec = BuildSpecInternal {
1111 build_type: Some(BuildType::Make),
1112 makefile: Some("GNUmakefile".into()),
1113 make_build_target: Some("all".into()),
1114 make_install_target: Some("install".into()),
1115 make_build_variables: Some(HashMap::from([("CFLAGS".into(), "-O2".into())])),
1116 make_install_variables: Some(HashMap::from([("PREFIX".into(), "/usr/local".into())])),
1117 variables: Some(HashMap::from([("LUA_LIBDIR".into(), "/usr/lib".into())])),
1118 ..Default::default()
1119 };
1120 let lua = spec.display_lua().to_string();
1121 let restored: BuildSpecInternal = eval_lua_global(&lua, "build");
1122 assert_eq!(spec, restored);
1123 }
1124}