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