Skip to main content

lux_lib/lua_rockspec/build/
mod.rs

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 std::{
22    collections::HashMap, convert::Infallible, env::consts::DLL_EXTENSION, fmt::Display,
23    path::PathBuf, str::FromStr,
24};
25use thiserror::Error;
26
27use serde::{de, de::IntoDeserializer, Deserialize, Deserializer};
28
29use crate::{
30    lua_rockspec::per_platform_from_intermediate,
31    package::{PackageName, PackageReq},
32    rockspec::lua_dependency::LuaDependencySpec,
33};
34
35use super::{
36    DisplayAsLuaKV, DisplayAsLuaValue, DisplayLuaKV, DisplayLuaValue, LuaTableKey, LuaValueSeed,
37    PartialOverride, PerPlatform, PlatformOverridable,
38};
39
40/// The build specification for a given rock, serialized from `rockspec.build = { ... }`.
41///
42/// See [the rockspec format](https://github.com/luarocks/luarocks/wiki/Rockspec-format) for more
43/// info.
44#[derive(Clone, Debug, PartialEq)]
45pub struct BuildSpec {
46    /// Determines the build backend to use.
47    pub build_backend: Option<BuildBackendSpec>,
48    /// A set of instructions on how/where to copy files from the project.
49    // TODO(vhyrro): While we may want to support this, we also may want to supercede this in our
50    // new Lua project rewrite.
51    pub install: InstallSpec,
52    /// A list of directories that should be copied as-is into the resulting rock.
53    pub copy_directories: Vec<PathBuf>,
54    /// A list of patches to apply to the project before packaging it.
55    // NOTE: This cannot be a diffy::Patch<'a, str>
56    // because Lua::from_value requires a DeserializeOwned
57    pub patches: HashMap<PathBuf, String>,
58}
59
60impl Default for BuildSpec {
61    fn default() -> Self {
62        Self {
63            build_backend: Some(BuildBackendSpec::default()),
64            install: InstallSpec::default(),
65            copy_directories: Vec::default(),
66            patches: HashMap::default(),
67        }
68    }
69}
70
71#[derive(Error, Debug)]
72pub enum BuildSpecInternalError {
73    #[error("'builtin' modules should not have list elements")]
74    ModulesHaveListElements,
75    #[error("no 'modules' specified for the 'rust-mlua' build backend")]
76    NoModulesSpecified,
77    #[error("no 'lang' specified for 'treesitter-parser' build backend")]
78    NoTreesitterParserLanguageSpecified,
79    #[error("invalid 'rust-mlua' modules format")]
80    InvalidRustMLuaFormat,
81    #[error(transparent)]
82    ModulePathsMissingSources(#[from] ModulePathsMissingSources),
83    #[error(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 = format!("lib{}", module.display()).into();
149                            rust_lib.set_extension(DLL_EXTENSION);
150                            Ok((module.to_string_lossy().to_string(), rust_lib))
151                        }
152                        (
153                            LuaTableKey::StringKey(module_name),
154                            ModuleSpecInternal::SourcePath(module),
155                        ) => {
156                            let mut rust_lib: PathBuf = format!("lib{}", module.display()).into();
157                            rust_lib.set_extension(DLL_EXTENSION);
158                            Ok((module_name, rust_lib))
159                        }
160                        _ => Err(BuildSpecInternalError::InvalidRustMLuaFormat),
161                    })
162                    .try_collect()?,
163                target_path: internal.target_path.unwrap_or("target".into()),
164                default_features: internal.default_features.unwrap_or(true),
165                features: internal.features.unwrap_or_default(),
166                cargo_extra_args: internal.cargo_extra_args.unwrap_or_default(),
167                include: internal
168                    .include
169                    .unwrap_or_default()
170                    .into_iter()
171                    .map(|(key, dest)| match key {
172                        LuaTableKey::IntKey(_) => (dest.clone(), dest),
173                        LuaTableKey::StringKey(src) => (src.into(), dest),
174                    })
175                    .collect(),
176            })),
177            BuildType::TreesitterParser => Some(BuildBackendSpec::TreesitterParser(
178                TreesitterParserBuildSpec {
179                    lang: internal
180                        .lang
181                        .ok_or(BuildSpecInternalError::NoTreesitterParserLanguageSpecified)?,
182                    parser: internal.parser.unwrap_or(false),
183                    generate: internal.generate.unwrap_or(false),
184                    location: internal.location,
185                    queries: internal.queries.unwrap_or_default(),
186                },
187            )),
188            BuildType::Source => Some(BuildBackendSpec::Source),
189        };
190        Ok(Self {
191            build_backend,
192            install: internal.install.unwrap_or_default(),
193            copy_directories: internal.copy_directories.unwrap_or_default(),
194            patches: internal.patches.unwrap_or_default(),
195        })
196    }
197}
198
199impl TryFrom<BuildSpecInternal> for BuildSpec {
200    type Error = BuildSpecInternalError;
201
202    fn try_from(internal: BuildSpecInternal) -> Result<Self, Self::Error> {
203        BuildSpec::from_internal_spec(internal)
204    }
205}
206
207impl<'de> Deserialize<'de> for BuildSpec {
208    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
209    where
210        D: Deserializer<'de>,
211    {
212        let internal = BuildSpecInternal::deserialize(deserializer)?;
213        BuildSpec::from_internal_spec(internal).map_err(de::Error::custom)
214    }
215}
216
217// TODO(vhyrro): Remove this when we migrate to deepmerge.
218// This is a hacky implementation that would work normally with just the above deserialization
219// strategy however since there is no PlatformOevrridable implemented for this struct this is
220// necessary.
221impl<'de> Deserialize<'de> for PerPlatform<BuildSpec> {
222    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
223    where
224        D: Deserializer<'de>,
225    {
226        per_platform_from_intermediate::<_, BuildSpecInternal, _>(deserializer)
227    }
228}
229
230impl Default for BuildBackendSpec {
231    fn default() -> Self {
232        Self::Builtin(BuiltinBuildSpec::default())
233    }
234}
235
236/// Encodes extra information about each backend.
237/// When selecting a backend, one may provide extra parameters
238/// to `build = { ... }` in order to further customize the behaviour of the build step.
239///
240/// Luarocks provides several default build types, these are also reflected in `lux`
241/// for compatibility.
242#[derive(Debug, PartialEq, Clone)]
243pub enum BuildBackendSpec {
244    Builtin(BuiltinBuildSpec),
245    Make(MakeBuildSpec),
246    CMake(CMakeBuildSpec),
247    Command(CommandBuildSpec),
248    LuaRock(String),
249    RustMlua(RustMluaBuildSpec),
250    TreesitterParser(TreesitterParserBuildSpec),
251    /// Build from the source rockspec, if present.
252    /// Otherwise, fall back to the builtin build and copy all directories.
253    /// This is currently unimplemented by luarocks, but we don't ever publish rockspecs
254    /// that implement this.
255    /// It could be implemented as a custom build backend.
256    Source,
257}
258
259impl BuildBackendSpec {
260    pub(crate) fn can_use_build_dependencies(&self) -> bool {
261        match self {
262            Self::Make(_) | Self::CMake(_) | Self::Command(_) | Self::LuaRock(_) => true,
263            Self::Builtin(_) | Self::RustMlua(_) | Self::TreesitterParser(_) | Self::Source => {
264                false
265            }
266        }
267    }
268}
269
270#[derive(Debug, PartialEq, Clone)]
271pub struct CommandBuildSpec {
272    pub build_command: Option<String>,
273    pub install_command: Option<String>,
274}
275
276#[derive(Clone, Debug)]
277struct LuaPathBufTable(HashMap<LuaTableKey, PathBuf>);
278
279impl<'de> Deserialize<'de> for LuaPathBufTable {
280    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
281        Ok(LuaPathBufTable(
282            deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
283        ))
284    }
285}
286
287impl LuaPathBufTable {
288    fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
289    where
290        S: FromStr + Eq + std::hash::Hash,
291    {
292        self.0
293            .into_iter()
294            .map(|(key, value)| {
295                let key = match key {
296                    LuaTableKey::IntKey(_) => value
297                        .with_extension("")
298                        .file_name()
299                        .unwrap_or_default()
300                        .to_string_lossy()
301                        .to_string(),
302                    LuaTableKey::StringKey(key) => key,
303                };
304                Ok((S::from_str(&key)?, value))
305            })
306            .try_collect()
307    }
308}
309
310#[derive(Clone, Debug)]
311struct LibPathBufTable(HashMap<LuaTableKey, PathBuf>);
312
313impl<'de> Deserialize<'de> for LibPathBufTable {
314    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
315        Ok(LibPathBufTable(
316            deserialize_map_or_seq(deserializer)?.unwrap_or_default(),
317        ))
318    }
319}
320
321impl LibPathBufTable {
322    fn coerce<S>(self) -> Result<HashMap<S, PathBuf>, S::Err>
323    where
324        S: FromStr + Eq + std::hash::Hash,
325    {
326        self.0
327            .into_iter()
328            .map(|(key, value)| {
329                let key = match key {
330                    LuaTableKey::IntKey(_) => value
331                        .file_name()
332                        .unwrap_or_default()
333                        .to_string_lossy()
334                        .to_string(),
335                    LuaTableKey::StringKey(key) => key,
336                };
337                Ok((S::from_str(&key)?, value))
338            })
339            .try_collect()
340    }
341}
342
343/// For packages which don't provide means to install modules
344/// and expect the user to copy the .lua or library files by hand to the proper locations.
345/// This struct contains categories of files. Each category is itself a table,
346/// where the array part is a list of filenames to be copied.
347/// For module directories only, in the hash part, other keys are identifiers in Lua module format,
348/// to indicate which subdirectory the file should be copied to.
349/// For example, build.install.lua = {["foo.bar"] = {"src/bar.lua"}} will copy src/bar.lua
350/// to the foo directory under the rock's Lua files directory.
351#[derive(Debug, PartialEq, Default, Deserialize, Clone)]
352pub struct InstallSpec {
353    /// Lua modules written in Lua.
354    #[serde(default, deserialize_with = "deserialize_module_path_map")]
355    pub lua: HashMap<LuaModule, PathBuf>,
356    /// Dynamic libraries implemented compiled Lua modules.
357    #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
358    pub lib: HashMap<String, PathBuf>,
359    /// Configuration files.
360    #[serde(default)]
361    pub conf: HashMap<String, PathBuf>,
362    /// Lua command-line scripts.
363    // TODO(vhyrro): The String component should be checked to ensure that it consists of a single
364    // path component, such that targets like `my.binary` are not allowed.
365    #[serde(default, deserialize_with = "deserialize_file_name_path_map")]
366    pub bin: HashMap<String, PathBuf>,
367}
368
369fn deserialize_module_path_map<'de, D>(
370    deserializer: D,
371) -> Result<HashMap<LuaModule, PathBuf>, D::Error>
372where
373    D: Deserializer<'de>,
374{
375    let modules = LuaPathBufTable::deserialize(deserializer)?;
376    modules.coerce().map_err(de::Error::custom)
377}
378
379fn deserialize_file_name_path_map<'de, D>(
380    deserializer: D,
381) -> Result<HashMap<String, PathBuf>, D::Error>
382where
383    D: Deserializer<'de>,
384{
385    let binaries = LibPathBufTable::deserialize(deserializer)?;
386    binaries.coerce().map_err(de::Error::custom)
387}
388
389fn deserialize_copy_directories<'de, D>(deserializer: D) -> Result<Option<Vec<PathBuf>>, D::Error>
390where
391    D: Deserializer<'de>,
392{
393    let value: Option<serde_value::Value> = Option::deserialize(deserializer)?;
394    let copy_directories: Option<Vec<String>> = match value {
395        Some(value) => Some(value.deserialize_into().map_err(de::Error::custom)?),
396        None => None,
397    };
398    let special_directories: Vec<String> = vec!["lua".into(), "lib".into(), "rock_manifest".into()];
399    match special_directories
400        .into_iter()
401        .find(|dir| copy_directories.clone().unwrap_or_default().contains(dir))
402    {
403        // NOTE(mrcjkb): There also shouldn't be a directory named the same as the rockspec,
404        // but I'm not sure how to (or if it makes sense to) enforce this here.
405        Some(d) => Err(format!(
406            "directory '{d}' in copy_directories clashes with the .rock format", // TODO(vhyrro): More informative error message.
407        )),
408        _ => Ok(copy_directories.map(|vec| vec.into_iter().map(PathBuf::from).collect())),
409    }
410    .map_err(de::Error::custom)
411}
412
413impl DisplayAsLuaKV for InstallSpec {
414    fn display_lua(&self) -> DisplayLuaKV {
415        let mut result = Vec::new();
416
417        let mut lua_entries = Vec::new();
418        self.lua.iter().for_each(|(key, value)| {
419            lua_entries.push(DisplayLuaKV {
420                key: key.to_string(),
421                value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
422            });
423        });
424        if !lua_entries.is_empty() {
425            result.push(DisplayLuaKV {
426                key: "lua".to_string(),
427                value: DisplayLuaValue::Table(lua_entries),
428            });
429        }
430
431        let mut lib_entries = Vec::new();
432        self.lib.iter().for_each(|(key, value)| {
433            lib_entries.push(DisplayLuaKV {
434                key: key.to_string(),
435                value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
436            });
437        });
438        if !lib_entries.is_empty() {
439            result.push(DisplayLuaKV {
440                key: "lib".to_string(),
441                value: DisplayLuaValue::Table(lib_entries),
442            });
443        }
444
445        let mut bin_entries = Vec::new();
446        self.bin.iter().for_each(|(key, value)| {
447            bin_entries.push(DisplayLuaKV {
448                key: key.clone(),
449                value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
450            });
451        });
452        if !bin_entries.is_empty() {
453            result.push(DisplayLuaKV {
454                key: "bin".to_string(),
455                value: DisplayLuaValue::Table(bin_entries),
456            });
457        }
458
459        let mut conf_entries = Vec::new();
460        self.conf.iter().for_each(|(key, value)| {
461            conf_entries.push(DisplayLuaKV {
462                key: key.clone(),
463                value: DisplayLuaValue::String(value.to_slash_lossy().to_string()),
464            });
465        });
466        if !conf_entries.is_empty() {
467            result.push(DisplayLuaKV {
468                key: "conf".to_string(),
469                value: DisplayLuaValue::Table(conf_entries),
470            });
471        }
472
473        DisplayLuaKV {
474            key: "install".to_string(),
475            value: DisplayLuaValue::Table(result),
476        }
477    }
478}
479
480/// Deserializes a map that may be represented as a sequence (integer-indexed Lua array).
481fn deserialize_map_or_seq<'de, D, V>(
482    deserializer: D,
483) -> Result<Option<HashMap<LuaTableKey, V>>, D::Error>
484where
485    D: Deserializer<'de>,
486    V: de::DeserializeOwned,
487{
488    match de::DeserializeSeed::deserialize(LuaValueSeed, deserializer).map_err(de::Error::custom)? {
489        serde_value::Value::Map(map) => map
490            .into_iter()
491            .map(|(k, v)| {
492                let key = match k {
493                    serde_value::Value::I64(i) => LuaTableKey::IntKey(i as u64),
494                    serde_value::Value::U64(u) => LuaTableKey::IntKey(u),
495                    serde_value::Value::String(s) => LuaTableKey::StringKey(s),
496                    other => {
497                        return Err(de::Error::custom(format!("unexpected map key: {other:?}")))
498                    }
499                };
500                let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
501                Ok((key, val))
502            })
503            .try_collect()
504            .map(Some),
505        serde_value::Value::Seq(seq) => seq
506            .into_iter()
507            .enumerate()
508            .map(|(i, v)| {
509                let val = v.deserialize_into::<V>().map_err(de::Error::custom)?;
510                Ok((LuaTableKey::IntKey(i as u64 + 1), val))
511            })
512            .try_collect()
513            .map(Some),
514        serde_value::Value::Unit => Ok(None),
515        other => Err(de::Error::custom(format!(
516            "expected a table or nil, got {other:?}"
517        ))),
518    }
519}
520
521#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
522pub(crate) struct BuildSpecInternal {
523    #[serde(rename = "type", default)]
524    pub(crate) build_type: Option<BuildType>,
525    #[serde(
526        rename = "modules",
527        default,
528        deserialize_with = "deserialize_map_or_seq"
529    )]
530    pub(crate) builtin_spec: Option<HashMap<LuaTableKey, ModuleSpecInternal>>,
531    #[serde(default)]
532    pub(crate) makefile: Option<PathBuf>,
533    #[serde(rename = "build_target", default)]
534    pub(crate) make_build_target: Option<String>,
535    #[serde(default)]
536    pub(crate) build_pass: Option<bool>,
537    #[serde(rename = "install_target", default)]
538    pub(crate) make_install_target: Option<String>,
539    #[serde(default)]
540    pub(crate) install_pass: Option<bool>,
541    #[serde(rename = "build_variables", default)]
542    pub(crate) make_build_variables: Option<HashMap<String, String>>,
543    #[serde(rename = "install_variables", default)]
544    pub(crate) make_install_variables: Option<HashMap<String, String>>,
545    #[serde(default)]
546    pub(crate) variables: Option<HashMap<String, String>>,
547    #[serde(rename = "cmake", default)]
548    pub(crate) cmake_lists_content: Option<String>,
549    #[serde(default)]
550    pub(crate) build_command: Option<String>,
551    #[serde(default)]
552    pub(crate) install_command: Option<String>,
553    #[serde(default)]
554    pub(crate) install: Option<InstallSpec>,
555    #[serde(default, deserialize_with = "deserialize_copy_directories")]
556    pub(crate) copy_directories: Option<Vec<PathBuf>>,
557    #[serde(default)]
558    pub(crate) patches: Option<HashMap<PathBuf, String>>,
559    // rust-mlua fields
560    #[serde(default)]
561    pub(crate) target_path: Option<PathBuf>,
562    #[serde(default)]
563    pub(crate) default_features: Option<bool>,
564    #[serde(default)]
565    pub(crate) features: Option<Vec<String>>,
566    pub(crate) cargo_extra_args: Option<Vec<String>>,
567    #[serde(default, deserialize_with = "deserialize_map_or_seq")]
568    pub(crate) include: Option<HashMap<LuaTableKey, PathBuf>>,
569    // treesitter-parser fields
570    #[serde(default)]
571    pub(crate) lang: Option<String>,
572    #[serde(default)]
573    pub(crate) parser: Option<bool>,
574    #[serde(default)]
575    pub(crate) generate: Option<bool>,
576    #[serde(default)]
577    pub(crate) location: Option<PathBuf>,
578    #[serde(default)]
579    pub(crate) queries: Option<HashMap<PathBuf, String>>,
580}
581
582impl PartialOverride for BuildSpecInternal {
583    type Err = ModuleSpecAmbiguousPlatformOverride;
584
585    fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
586        override_build_spec_internal(self, override_spec)
587    }
588}
589
590impl PlatformOverridable for BuildSpecInternal {
591    type Err = Infallible;
592
593    fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
594    where
595        T: PlatformOverridable,
596        T: Default,
597    {
598        Ok(PerPlatform::default())
599    }
600}
601
602fn override_build_spec_internal(
603    base: &BuildSpecInternal,
604    override_spec: &BuildSpecInternal,
605) -> Result<BuildSpecInternal, ModuleSpecAmbiguousPlatformOverride> {
606    Ok(BuildSpecInternal {
607        build_type: override_opt(&override_spec.build_type, &base.build_type),
608        builtin_spec: match (
609            override_spec.builtin_spec.clone(),
610            base.builtin_spec.clone(),
611        ) {
612            (Some(override_val), Some(base_spec_map)) => {
613                Some(base_spec_map.into_iter().chain(override_val).try_fold(
614                    HashMap::default(),
615                    |mut acc: HashMap<LuaTableKey, ModuleSpecInternal>,
616                     (k, module_spec_override)|
617                     -> Result<
618                        HashMap<LuaTableKey, ModuleSpecInternal>,
619                        ModuleSpecAmbiguousPlatformOverride,
620                    > {
621                        let overridden = match acc.get(&k) {
622                            None => module_spec_override,
623                            Some(base_module_spec) => {
624                                base_module_spec.apply_overrides(&module_spec_override)?
625                            }
626                        };
627                        acc.insert(k, overridden);
628                        Ok(acc)
629                    },
630                )?)
631            }
632            (override_val @ Some(_), _) => override_val,
633            (_, base_val @ Some(_)) => base_val,
634            _ => None,
635        },
636        makefile: override_opt(&override_spec.makefile, &base.makefile),
637        make_build_target: override_opt(&override_spec.make_build_target, &base.make_build_target),
638        build_pass: override_opt(&override_spec.build_pass, &base.build_pass),
639        make_install_target: override_opt(
640            &override_spec.make_install_target,
641            &base.make_install_target,
642        ),
643        install_pass: override_opt(&override_spec.install_pass, &base.install_pass),
644        make_build_variables: merge_map_opts(
645            &override_spec.make_build_variables,
646            &base.make_build_variables,
647        ),
648        make_install_variables: merge_map_opts(
649            &override_spec.make_install_variables,
650            &base.make_build_variables,
651        ),
652        variables: merge_map_opts(&override_spec.variables, &base.variables),
653        cmake_lists_content: override_opt(
654            &override_spec.cmake_lists_content,
655            &base.cmake_lists_content,
656        ),
657        build_command: override_opt(&override_spec.build_command, &base.build_command),
658        install_command: override_opt(&override_spec.install_command, &base.install_command),
659        install: override_opt(&override_spec.install, &base.install),
660        copy_directories: match (
661            override_spec.copy_directories.clone(),
662            base.copy_directories.clone(),
663        ) {
664            (Some(override_vec), Some(base_vec)) => {
665                let merged: Vec<PathBuf> =
666                    base_vec.into_iter().chain(override_vec).unique().collect();
667                Some(merged)
668            }
669            (None, base_vec @ Some(_)) => base_vec,
670            (override_vec @ Some(_), None) => override_vec,
671            _ => None,
672        },
673        patches: override_opt(&override_spec.patches, &base.patches),
674        target_path: override_opt(&override_spec.target_path, &base.target_path),
675        default_features: override_opt(&override_spec.default_features, &base.default_features),
676        features: override_opt(&override_spec.features, &base.features),
677        cargo_extra_args: override_opt(&override_spec.cargo_extra_args, &base.cargo_extra_args),
678        include: merge_map_opts(&override_spec.include, &base.include),
679        lang: override_opt(&override_spec.lang, &base.lang),
680        parser: override_opt(&override_spec.parser, &base.parser),
681        generate: override_opt(&override_spec.generate, &base.generate),
682        location: override_opt(&override_spec.location, &base.location),
683        queries: merge_map_opts(&override_spec.queries, &base.queries),
684    })
685}
686
687fn override_opt<T: Clone>(override_opt: &Option<T>, base: &Option<T>) -> Option<T> {
688    match override_opt.clone() {
689        override_val @ Some(_) => override_val,
690        None => base.clone(),
691    }
692}
693
694fn merge_map_opts<K, V>(
695    override_map: &Option<HashMap<K, V>>,
696    base_map: &Option<HashMap<K, V>>,
697) -> Option<HashMap<K, V>>
698where
699    K: Clone,
700    K: Eq,
701    K: std::hash::Hash,
702    V: Clone,
703{
704    match (override_map.clone(), base_map.clone()) {
705        (Some(override_map), Some(base_map)) => {
706            Some(base_map.into_iter().chain(override_map).collect())
707        }
708        (_, base_map @ Some(_)) => base_map,
709        (override_map @ Some(_), _) => override_map,
710        _ => None,
711    }
712}
713
714impl DisplayAsLuaKV for BuildSpecInternal {
715    fn display_lua(&self) -> DisplayLuaKV {
716        let mut result = Vec::new();
717
718        if let Some(build_type) = &self.build_type {
719            result.push(DisplayLuaKV {
720                key: "type".to_string(),
721                value: DisplayLuaValue::String(build_type.to_string()),
722            });
723        }
724        if let Some(builtin_spec) = &self.builtin_spec {
725            result.push(DisplayLuaKV {
726                key: "modules".to_string(),
727                value: DisplayLuaValue::Table(
728                    builtin_spec
729                        .iter()
730                        .map(|(key, value)| DisplayLuaKV {
731                            key: match key {
732                                LuaTableKey::StringKey(s) => s.clone(),
733                                LuaTableKey::IntKey(_) => unreachable!("integer key in modules"),
734                            },
735                            value: value.display_lua_value(),
736                        })
737                        .collect(),
738                ),
739            });
740        }
741        if let Some(makefile) = &self.makefile {
742            result.push(DisplayLuaKV {
743                key: "makefile".to_string(),
744                value: DisplayLuaValue::String(makefile.to_string_lossy().to_string()),
745            });
746        }
747        if let Some(make_build_target) = &self.make_build_target {
748            result.push(DisplayLuaKV {
749                key: "build_target".to_string(),
750                value: DisplayLuaValue::String(make_build_target.clone()),
751            });
752        }
753        if let Some(build_pass) = &self.build_pass {
754            result.push(DisplayLuaKV {
755                key: "build_pass".to_string(),
756                value: DisplayLuaValue::Boolean(*build_pass),
757            });
758        }
759        if let Some(make_install_target) = &self.make_install_target {
760            result.push(DisplayLuaKV {
761                key: "install_target".to_string(),
762                value: DisplayLuaValue::String(make_install_target.clone()),
763            });
764        }
765        if let Some(install_pass) = &self.install_pass {
766            result.push(DisplayLuaKV {
767                key: "install_pass".to_string(),
768                value: DisplayLuaValue::Boolean(*install_pass),
769            });
770        }
771        if let Some(make_build_variables) = &self.make_build_variables {
772            result.push(DisplayLuaKV {
773                key: "build_variables".to_string(),
774                value: DisplayLuaValue::Table(
775                    make_build_variables
776                        .iter()
777                        .map(|(key, value)| DisplayLuaKV {
778                            key: key.clone(),
779                            value: DisplayLuaValue::String(value.clone()),
780                        })
781                        .collect(),
782                ),
783            });
784        }
785        if let Some(make_install_variables) = &self.make_install_variables {
786            result.push(DisplayLuaKV {
787                key: "install_variables".to_string(),
788                value: DisplayLuaValue::Table(
789                    make_install_variables
790                        .iter()
791                        .map(|(key, value)| DisplayLuaKV {
792                            key: key.clone(),
793                            value: DisplayLuaValue::String(value.clone()),
794                        })
795                        .collect(),
796                ),
797            });
798        }
799        if let Some(variables) = &self.variables {
800            result.push(DisplayLuaKV {
801                key: "variables".to_string(),
802                value: DisplayLuaValue::Table(
803                    variables
804                        .iter()
805                        .map(|(key, value)| DisplayLuaKV {
806                            key: key.clone(),
807                            value: DisplayLuaValue::String(value.clone()),
808                        })
809                        .collect(),
810                ),
811            });
812        }
813        if let Some(cmake_lists_content) = &self.cmake_lists_content {
814            result.push(DisplayLuaKV {
815                key: "cmake".to_string(),
816                value: DisplayLuaValue::String(cmake_lists_content.clone()),
817            });
818        }
819        if let Some(build_command) = &self.build_command {
820            result.push(DisplayLuaKV {
821                key: "build_command".to_string(),
822                value: DisplayLuaValue::String(build_command.clone()),
823            });
824        }
825        if let Some(install_command) = &self.install_command {
826            result.push(DisplayLuaKV {
827                key: "install_command".to_string(),
828                value: DisplayLuaValue::String(install_command.clone()),
829            });
830        }
831        if let Some(install) = &self.install {
832            result.push(install.display_lua());
833        }
834        if let Some(copy_directories) = &self.copy_directories {
835            result.push(DisplayLuaKV {
836                key: "copy_directories".to_string(),
837                value: DisplayLuaValue::List(
838                    copy_directories
839                        .iter()
840                        .map(|path_buf| {
841                            DisplayLuaValue::String(path_buf.to_string_lossy().to_string())
842                        })
843                        .collect(),
844                ),
845            });
846        }
847        if let Some(patches) = &self.patches {
848            result.push(DisplayLuaKV {
849                key: "patches".to_string(),
850                value: DisplayLuaValue::Table(
851                    patches
852                        .iter()
853                        .map(|(key, value)| DisplayLuaKV {
854                            key: key.to_string_lossy().to_string(),
855                            value: DisplayLuaValue::String(value.clone()),
856                        })
857                        .collect(),
858                ),
859            });
860        }
861        if let Some(target_path) = &self.target_path {
862            result.push(DisplayLuaKV {
863                key: "target_path".to_string(),
864                value: DisplayLuaValue::String(target_path.to_string_lossy().to_string()),
865            });
866        }
867        if let Some(default_features) = &self.default_features {
868            result.push(DisplayLuaKV {
869                key: "default_features".to_string(),
870                value: DisplayLuaValue::Boolean(*default_features),
871            });
872        }
873        if let Some(include) = &self.include {
874            result.push(DisplayLuaKV {
875                key: "include".to_string(),
876                value: DisplayLuaValue::Table(
877                    include
878                        .iter()
879                        .map(|(key, value)| DisplayLuaKV {
880                            key: match key {
881                                LuaTableKey::StringKey(s) => s.clone(),
882                                LuaTableKey::IntKey(_) => unreachable!("integer key in include"),
883                            },
884                            value: DisplayLuaValue::String(value.to_string_lossy().to_string()),
885                        })
886                        .collect(),
887                ),
888            });
889        }
890        if let Some(features) = &self.features {
891            result.push(DisplayLuaKV {
892                key: "features".to_string(),
893                value: DisplayLuaValue::List(
894                    features
895                        .iter()
896                        .map(|feature| DisplayLuaValue::String(feature.clone()))
897                        .collect(),
898                ),
899            });
900        }
901        if let Some(lang) = &self.lang {
902            result.push(DisplayLuaKV {
903                key: "lang".to_string(),
904                value: DisplayLuaValue::String(lang.to_string()),
905            });
906        }
907        if let Some(parser) = &self.parser {
908            result.push(DisplayLuaKV {
909                key: "parser".to_string(),
910                value: DisplayLuaValue::Boolean(*parser),
911            });
912        }
913        if let Some(generate) = &self.generate {
914            result.push(DisplayLuaKV {
915                key: "generate".to_string(),
916                value: DisplayLuaValue::Boolean(*generate),
917            });
918        }
919        if let Some(location) = &self.location {
920            result.push(DisplayLuaKV {
921                key: "location".to_string(),
922                value: DisplayLuaValue::String(location.to_string_lossy().to_string()),
923            });
924        }
925        if let Some(queries) = &self.queries {
926            result.push(DisplayLuaKV {
927                key: "queries".to_string(),
928                value: DisplayLuaValue::Table(
929                    queries
930                        .iter()
931                        .map(|(key, value)| DisplayLuaKV {
932                            key: key.to_string_lossy().to_string(),
933                            value: DisplayLuaValue::String(value.to_string()),
934                        })
935                        .collect(),
936                ),
937            });
938        }
939
940        DisplayLuaKV {
941            key: "build".to_string(),
942            value: DisplayLuaValue::Table(result),
943        }
944    }
945}
946
947/// Maps `build.type` to an enum.
948#[derive(Debug, PartialEq, Deserialize, Clone)]
949#[serde(rename_all = "lowercase", remote = "BuildType")]
950#[derive(Default)]
951pub(crate) enum BuildType {
952    /// "builtin" or "module"
953    #[default]
954    Builtin,
955    /// "make"
956    Make,
957    /// "cmake"
958    CMake,
959    /// "command"
960    Command,
961    /// "none"
962    None,
963    /// external Lua rock
964    LuaRock(String),
965    #[serde(rename = "rust-mlua")]
966    RustMlua,
967    #[serde(rename = "treesitter-parser")]
968    TreesitterParser,
969    Source,
970}
971
972impl BuildType {
973    pub(crate) fn luarocks_build_backend(&self) -> Option<LuaDependencySpec> {
974        match self {
975            &BuildType::Builtin
976            | &BuildType::Make
977            | &BuildType::CMake
978            | &BuildType::Command
979            | &BuildType::None
980            | &BuildType::LuaRock(_)
981            | &BuildType::Source => None,
982            &BuildType::RustMlua => unsafe {
983                Some(
984                    PackageReq::parse("luarocks-build-rust-mlua >= 0.2.6")
985                        .unwrap_unchecked()
986                        .into(),
987                )
988            },
989            &BuildType::TreesitterParser => {
990                Some(PackageName::new("luarocks-build-treesitter-parser".into()).into())
991            } // IMPORTANT: If adding another luarocks build backend,
992              // make sure to also add it to the filters in `operations::resolve::do_get_all_dependencies`.
993        }
994    }
995}
996
997// Special Deserialize case for BuildType:
998// Both "module" and "builtin" map to `Builtin`
999impl<'de> Deserialize<'de> for BuildType {
1000    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1001    where
1002        D: Deserializer<'de>,
1003    {
1004        let s = String::deserialize(deserializer)?;
1005        if s == "builtin" || s == "module" {
1006            Ok(Self::Builtin)
1007        } else {
1008            match Self::deserialize(s.clone().into_deserializer()) {
1009                Err(_) => Ok(Self::LuaRock(s)),
1010                ok => ok,
1011            }
1012        }
1013    }
1014}
1015
1016impl Display for BuildType {
1017    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018        match self {
1019            BuildType::Builtin => write!(f, "builtin"),
1020            BuildType::Make => write!(f, "make"),
1021            BuildType::CMake => write!(f, "cmake"),
1022            BuildType::Command => write!(f, "command"),
1023            BuildType::None => write!(f, "none"),
1024            BuildType::LuaRock(s) => write!(f, "{s}"),
1025            BuildType::RustMlua => write!(f, "rust-mlua"),
1026            BuildType::TreesitterParser => write!(f, "treesitter-parser"),
1027            BuildType::Source => write!(f, "source"),
1028        }
1029    }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034
1035    use super::*;
1036
1037    #[tokio::test]
1038    pub async fn deserialize_build_type() {
1039        let build_type: BuildType = serde_json::from_str("\"builtin\"").unwrap();
1040        assert_eq!(build_type, BuildType::Builtin);
1041        let build_type: BuildType = serde_json::from_str("\"module\"").unwrap();
1042        assert_eq!(build_type, BuildType::Builtin);
1043        let build_type: BuildType = serde_json::from_str("\"make\"").unwrap();
1044        assert_eq!(build_type, BuildType::Make);
1045        let build_type: BuildType = serde_json::from_str("\"custom_build_backend\"").unwrap();
1046        assert_eq!(
1047            build_type,
1048            BuildType::LuaRock("custom_build_backend".into())
1049        );
1050        let build_type: BuildType = serde_json::from_str("\"rust-mlua\"").unwrap();
1051        assert_eq!(build_type, BuildType::RustMlua);
1052    }
1053}