Skip to main content

lux_lib/lua_rockspec/build/
builtin.rs

1use crate::{
2    build::utils::c_dylib_extension,
3    lua_rockspec::{
4        deserialize_vec_from_lua_array_or_string, normalize_lua_value, DisplayAsLuaValue,
5        PartialOverride, PerPlatform, PlatformOverridable,
6    },
7};
8use itertools::Itertools;
9use miette::Diagnostic;
10use path_slash::PathBufExt;
11use serde::{de, Deserialize, Deserializer};
12use std::{collections::HashMap, convert::Infallible, fmt::Display, path::PathBuf, str::FromStr};
13use thiserror::Error;
14
15use super::{DisplayLuaKV, DisplayLuaValue};
16
17#[derive(Debug, PartialEq, Deserialize, Default, Clone)]
18pub struct BuiltinBuildSpec {
19    /// Keys are module names in the format normally used by the `require()` function
20    pub modules: HashMap<LuaModule, ModuleSpec>,
21}
22
23#[derive(Debug, PartialEq, Eq, Deserialize, Default, Clone, Hash)]
24pub struct LuaModule(String);
25
26impl LuaModule {
27    pub fn to_lua_path(&self) -> PathBuf {
28        self.to_file_path(".lua")
29    }
30
31    pub fn to_lua_init_path(&self) -> PathBuf {
32        self.to_path_buf().join("init.lua")
33    }
34
35    pub fn to_luau_path(&self) -> PathBuf {
36        self.to_file_path(".luau")
37    }
38
39    pub fn to_luau_init_path(&self) -> PathBuf {
40        self.to_path_buf().join("init.luau")
41    }
42
43    pub fn to_lib_path(&self) -> PathBuf {
44        self.to_file_path(&format!(".{}", c_dylib_extension()))
45    }
46
47    fn to_path_buf(&self) -> PathBuf {
48        PathBuf::from(self.0.replace('.', std::path::MAIN_SEPARATOR_STR))
49    }
50
51    pub(crate) fn to_file_path(&self, extension: &str) -> PathBuf {
52        PathBuf::from(self.0.replace('.', std::path::MAIN_SEPARATOR_STR) + extension)
53    }
54
55    pub fn from_pathbuf(path: PathBuf) -> Result<Self, ParseLuaModuleError> {
56        let extension = path
57            .extension()
58            .map(|ext| ext.to_string_lossy().to_string())
59            .unwrap_or("".into());
60        let module = path
61            .to_string_lossy()
62            .trim_end_matches(format!("init.{extension}").as_str())
63            .trim_end_matches(format!(".{extension}").as_str())
64            .trim_end_matches(std::path::MAIN_SEPARATOR_STR)
65            .replace(std::path::MAIN_SEPARATOR_STR, ".");
66        if module.is_empty() {
67            Err(ParseLuaModuleError::EmptyModule(
68                path.to_slash_lossy().to_string(),
69            ))
70        } else {
71            Ok(LuaModule(module))
72        }
73    }
74
75    pub fn join(&self, other: &LuaModule) -> LuaModule {
76        LuaModule(format!("{}.{}", self.0, other.0))
77    }
78
79    pub fn as_str(&self) -> &str {
80        self.0.as_str()
81    }
82}
83
84#[derive(Error, Debug, Diagnostic)]
85#[non_exhaustive]
86pub enum ParseLuaModuleError {
87    #[error("path '{0}' resulted in an empty Lua module.")]
88    EmptyModule(String),
89    #[error("cannot parse a Lua module from an empty string")]
90    EmptyString,
91}
92
93impl FromStr for LuaModule {
94    type Err = ParseLuaModuleError;
95
96    // NOTE: We may want to add some additional validations
97    fn from_str(s: &str) -> Result<Self, Self::Err> {
98        if s.is_empty() {
99            Err(ParseLuaModuleError::EmptyString)
100        } else {
101            Ok(LuaModule(s.into()))
102        }
103    }
104}
105
106impl Display for LuaModule {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        self.0.fmt(f)
109    }
110}
111
112impl DisplayAsLuaValue for LuaModule {
113    fn display_lua_value(&self) -> DisplayLuaValue {
114        DisplayLuaValue::String(self.to_string())
115    }
116}
117
118impl DisplayAsLuaValue for HashMap<LuaModule, PathBuf> {
119    fn display_lua_value(&self) -> DisplayLuaValue {
120        use path_slash::PathBufExt as _;
121        DisplayLuaValue::Table(
122            self.iter()
123                .map(|(k, v)| DisplayLuaKV {
124                    key: k.to_string(),
125                    value: DisplayLuaValue::String(v.to_slash_lossy().into_owned()),
126                })
127                .collect_vec(),
128        )
129    }
130}
131
132#[derive(Debug, PartialEq, Clone)]
133pub enum ModuleSpec {
134    /// Pathnames of Lua files or C sources, for modules based on a single source file.
135    SourcePath(PathBuf),
136    /// Pathnames of C sources of a simple module written in C composed of multiple files.
137    SourcePaths(Vec<PathBuf>),
138    ModulePaths(ModulePaths),
139}
140
141impl ModuleSpec {
142    pub fn from_internal(
143        internal: ModuleSpecInternal,
144    ) -> Result<ModuleSpec, ModulePathsMissingSources> {
145        match internal {
146            ModuleSpecInternal::SourcePath(path) => Ok(ModuleSpec::SourcePath(path)),
147            ModuleSpecInternal::SourcePaths(paths) => Ok(ModuleSpec::SourcePaths(paths)),
148            ModuleSpecInternal::ModulePaths(module_paths) => Ok(ModuleSpec::ModulePaths(
149                ModulePaths::from_internal(module_paths)?,
150            )),
151        }
152    }
153}
154
155impl<'de> Deserialize<'de> for ModuleSpec {
156    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
157    where
158        D: Deserializer<'de>,
159    {
160        Self::from_internal(ModuleSpecInternal::deserialize(deserializer)?)
161            .map_err(de::Error::custom)
162    }
163}
164
165impl TryFrom<ModuleSpecInternal> for ModuleSpec {
166    type Error = ModulePathsMissingSources;
167
168    fn try_from(internal: ModuleSpecInternal) -> Result<Self, Self::Error> {
169        Self::from_internal(internal)
170    }
171}
172
173impl From<ModuleSpec> for ModuleSpecInternal {
174    fn from(spec: ModuleSpec) -> Self {
175        match spec {
176            ModuleSpec::SourcePath(path) => Self::SourcePath(path),
177            ModuleSpec::SourcePaths(paths) => Self::SourcePaths(paths),
178            ModuleSpec::ModulePaths(paths) => Self::ModulePaths(paths.into()),
179        }
180    }
181}
182
183impl DisplayAsLuaValue for ModuleSpec {
184    fn display_lua_value(&self) -> DisplayLuaValue {
185        ModuleSpecInternal::from(self.clone()).display_lua_value()
186    }
187}
188
189#[derive(Debug, PartialEq, Clone)]
190pub enum ModuleSpecInternal {
191    SourcePath(PathBuf),
192    SourcePaths(Vec<PathBuf>),
193    ModulePaths(ModulePathsInternal),
194}
195
196impl<'de> Deserialize<'de> for ModuleSpecInternal {
197    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
198    where
199        D: Deserializer<'de>,
200    {
201        let value = normalize_lua_value(serde_value::Value::deserialize(deserializer)?);
202        match value {
203            serde_value::Value::String(s) => Ok(Self::SourcePath(PathBuf::from(s))),
204            serde_value::Value::Seq(_) => {
205                let src_paths: Vec<PathBuf> =
206                    value.deserialize_into().map_err(de::Error::custom)?;
207                Ok(Self::SourcePaths(src_paths))
208            }
209            serde_value::Value::Map(_) => {
210                let module_paths: ModulePathsInternal =
211                    value.deserialize_into().map_err(de::Error::custom)?;
212                Ok(Self::ModulePaths(module_paths))
213            }
214            _ => Err(de::Error::custom(format!(
215                "expected a string, list, or table for module spec, got: {value:?}"
216            ))),
217        }
218    }
219}
220
221impl DisplayAsLuaValue for ModuleSpecInternal {
222    fn display_lua_value(&self) -> DisplayLuaValue {
223        match self {
224            ModuleSpecInternal::SourcePath(path) => {
225                DisplayLuaValue::String(path.to_string_lossy().into())
226            }
227            ModuleSpecInternal::SourcePaths(paths) => DisplayLuaValue::List(
228                paths
229                    .iter()
230                    .map(|p| DisplayLuaValue::String(p.to_string_lossy().into()))
231                    .collect(),
232            ),
233            ModuleSpecInternal::ModulePaths(module_paths) => module_paths.display_lua_value(),
234        }
235    }
236}
237
238fn deserialize_definitions<'de, D>(
239    deserializer: D,
240) -> Result<Vec<(String, Option<String>)>, D::Error>
241where
242    D: Deserializer<'de>,
243{
244    let values: Vec<String> = deserialize_vec_from_lua_array_or_string(deserializer)?;
245    values
246        .iter()
247        .map(|val| {
248            if let Some((key, value)) = val.split_once('=') {
249                Ok((key.into(), Some(value.into())))
250            } else {
251                Ok((val.into(), None))
252            }
253        })
254        .try_collect()
255}
256
257#[derive(Error, Debug, Diagnostic)]
258#[non_exhaustive]
259#[error("cannot resolve ambiguous platform override for `build.modules`.")]
260pub struct ModuleSpecAmbiguousPlatformOverride;
261
262impl PartialOverride for ModuleSpecInternal {
263    type Err = ModuleSpecAmbiguousPlatformOverride;
264
265    fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
266        match (override_spec, self) {
267            (ModuleSpecInternal::SourcePath(_), b @ ModuleSpecInternal::SourcePath(_)) => {
268                Ok(b.to_owned())
269            }
270            (ModuleSpecInternal::SourcePaths(_), b @ ModuleSpecInternal::SourcePaths(_)) => {
271                Ok(b.to_owned())
272            }
273            (ModuleSpecInternal::ModulePaths(a), ModuleSpecInternal::ModulePaths(b)) => Ok(
274                ModuleSpecInternal::ModulePaths(a.apply_overrides(b).unwrap()),
275            ),
276            _ => Err(ModuleSpecAmbiguousPlatformOverride),
277        }
278    }
279}
280
281#[derive(Error, Debug, Diagnostic)]
282#[non_exhaustive]
283#[error("could not resolve platform override for `build.modules`. THIS IS A BUG!")]
284pub struct BuildModulesPlatformOverride;
285
286impl PlatformOverridable for ModuleSpecInternal {
287    type Err = BuildModulesPlatformOverride;
288
289    fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
290    where
291        T: PlatformOverridable,
292    {
293        Err(BuildModulesPlatformOverride)
294    }
295}
296
297#[derive(Error, Debug, Diagnostic)]
298#[non_exhaustive]
299#[error("missing or empty field `sources`")]
300pub struct ModulePathsMissingSources;
301
302/// Specification for building a Lua module from various sources
303#[derive(Debug, PartialEq, Clone)]
304pub struct ModulePaths {
305    /// Path names of C sources, mandatory field
306    pub sources: Vec<PathBuf>,
307    /// External libraries to be linked
308    pub libraries: Vec<PathBuf>,
309    /// C defines, e.g. { "FOO=bar", "USE_BLA" }
310    pub defines: Vec<(String, Option<String>)>,
311    /// Directories to be added to the compiler's headers lookup directory list.
312    pub incdirs: Vec<PathBuf>,
313    /// Directories to be added to the linker's library lookup directory list.
314    pub libdirs: Vec<PathBuf>,
315}
316
317impl ModulePaths {
318    fn from_internal(
319        internal: ModulePathsInternal,
320    ) -> Result<ModulePaths, ModulePathsMissingSources> {
321        if internal.sources.is_empty() {
322            Err(ModulePathsMissingSources)
323        } else {
324            Ok(ModulePaths {
325                sources: internal.sources,
326                libraries: internal.libraries,
327                defines: internal.defines,
328                incdirs: internal.incdirs,
329                libdirs: internal.libdirs,
330            })
331        }
332    }
333}
334
335impl TryFrom<ModulePathsInternal> for ModulePaths {
336    type Error = ModulePathsMissingSources;
337
338    fn try_from(internal: ModulePathsInternal) -> Result<Self, Self::Error> {
339        Self::from_internal(internal)
340    }
341}
342
343impl From<ModulePaths> for ModulePathsInternal {
344    fn from(paths: ModulePaths) -> Self {
345        Self {
346            sources: paths.sources,
347            libraries: paths.libraries,
348            defines: paths.defines,
349            incdirs: paths.incdirs,
350            libdirs: paths.libdirs,
351        }
352    }
353}
354
355impl<'de> Deserialize<'de> for ModulePaths {
356    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
357    where
358        D: Deserializer<'de>,
359    {
360        Self::from_internal(ModulePathsInternal::deserialize(deserializer)?)
361            .map_err(de::Error::custom)
362    }
363}
364
365#[derive(Debug, PartialEq, Deserialize, Clone, Default)]
366pub struct ModulePathsInternal {
367    #[serde(default, deserialize_with = "deserialize_vec_from_lua_array_or_string")]
368    pub sources: Vec<PathBuf>,
369    #[serde(default, deserialize_with = "deserialize_vec_from_lua_array_or_string")]
370    pub libraries: Vec<PathBuf>,
371    #[serde(default, deserialize_with = "deserialize_definitions")]
372    pub defines: Vec<(String, Option<String>)>,
373    #[serde(default, deserialize_with = "deserialize_vec_from_lua_array_or_string")]
374    pub incdirs: Vec<PathBuf>,
375    #[serde(default, deserialize_with = "deserialize_vec_from_lua_array_or_string")]
376    pub libdirs: Vec<PathBuf>,
377}
378
379impl DisplayAsLuaValue for ModulePathsInternal {
380    fn display_lua_value(&self) -> DisplayLuaValue {
381        DisplayLuaValue::Table(vec![
382            DisplayLuaKV {
383                key: "sources".into(),
384                value: DisplayLuaValue::List(
385                    self.sources
386                        .iter()
387                        .map(|s| DisplayLuaValue::String(s.to_string_lossy().into()))
388                        .collect(),
389                ),
390            },
391            DisplayLuaKV {
392                key: "libraries".into(),
393                value: DisplayLuaValue::List(
394                    self.libraries
395                        .iter()
396                        .map(|s| DisplayLuaValue::String(s.to_string_lossy().into()))
397                        .collect(),
398                ),
399            },
400            DisplayLuaKV {
401                key: "defines".into(),
402                value: DisplayLuaValue::List(
403                    self.defines
404                        .iter()
405                        .map(|(k, v)| {
406                            if let Some(v) = v {
407                                DisplayLuaValue::String(format!("{k}={v}"))
408                            } else {
409                                DisplayLuaValue::String(k.clone())
410                            }
411                        })
412                        .collect(),
413                ),
414            },
415            DisplayLuaKV {
416                key: "incdirs".into(),
417                value: DisplayLuaValue::List(
418                    self.incdirs
419                        .iter()
420                        .map(|s| DisplayLuaValue::String(s.to_string_lossy().into()))
421                        .collect(),
422                ),
423            },
424            DisplayLuaKV {
425                key: "libdirs".into(),
426                value: DisplayLuaValue::List(
427                    self.libdirs
428                        .iter()
429                        .map(|s| DisplayLuaValue::String(s.to_string_lossy().into()))
430                        .collect(),
431                ),
432            },
433        ])
434    }
435}
436
437impl PartialOverride for ModulePathsInternal {
438    type Err = Infallible;
439
440    fn apply_overrides(&self, override_spec: &Self) -> Result<Self, Self::Err> {
441        Ok(Self {
442            sources: override_vec(override_spec.sources.as_ref(), self.sources.as_ref()),
443            libraries: override_vec(override_spec.libraries.as_ref(), self.libraries.as_ref()),
444            defines: override_vec(override_spec.defines.as_ref(), self.defines.as_ref()),
445            incdirs: override_vec(override_spec.incdirs.as_ref(), self.incdirs.as_ref()),
446            libdirs: override_vec(override_spec.libdirs.as_ref(), self.libdirs.as_ref()),
447        })
448    }
449}
450
451impl PlatformOverridable for ModulePathsInternal {
452    type Err = Infallible;
453
454    fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
455    where
456        T: PlatformOverridable,
457        T: Default,
458    {
459        Ok(PerPlatform::default())
460    }
461}
462
463fn override_vec<T: Clone>(override_vec: &[T], base: &[T]) -> Vec<T> {
464    if override_vec.is_empty() {
465        return base.to_owned();
466    }
467    override_vec.to_owned()
468}
469
470#[cfg(test)]
471mod tests {
472    use ottavino::{Closure, Executor, Fuel, Lua};
473    use ottavino_util::serde::from_value;
474
475    use super::*;
476
477    fn exec_lua<T: serde::de::DeserializeOwned>(
478        code: &str,
479        key: &'static str,
480    ) -> Result<T, ottavino::ExternError> {
481        Lua::core().try_enter(|ctx| {
482            let closure = Closure::load(ctx, None, code.as_bytes())?;
483            let executor = Executor::start(ctx, closure.into(), ());
484            executor.step(ctx, &mut Fuel::with(i32::MAX))?;
485            from_value(ctx.globals().get_value(ctx, key)).map_err(ottavino::Error::from)
486        })
487    }
488
489    #[tokio::test]
490    pub async fn parse_lua_module_from_path() {
491        let lua_module = LuaModule::from_pathbuf("foo/init.lua".into()).unwrap();
492        assert_eq!(&lua_module.0, "foo");
493        let lua_module = LuaModule::from_pathbuf("foo/bar.lua".into()).unwrap();
494        assert_eq!(&lua_module.0, "foo.bar");
495        let lua_module = LuaModule::from_pathbuf("foo/bar/init.lua".into()).unwrap();
496        assert_eq!(&lua_module.0, "foo.bar");
497        let lua_module = LuaModule::from_pathbuf("foo/bar/baz.lua".into()).unwrap();
498        assert_eq!(&lua_module.0, "foo.bar.baz");
499    }
500
501    #[tokio::test]
502    pub async fn modules_spec_from_lua() {
503        let lua_content = "
504        build = {\n
505            modules = {\n
506                foo = 'lua/foo/init.lua',\n
507                bar = {\n
508                  'lua/bar.lua',\n
509                  'lua/bar/internal.lua',\n
510                },\n
511                baz = {\n
512                    sources = {\n
513                        'lua/baz.lua',\n
514                    },\n
515                    defines = { 'USE_BAZ' },\n
516                },\n
517                foo = 'lua/foo/init.lua',
518            },\n
519        }\n
520        ";
521        let build_spec: BuiltinBuildSpec = exec_lua(lua_content, "build").unwrap();
522        let foo = build_spec
523            .modules
524            .get(&LuaModule::from_str("foo").unwrap())
525            .unwrap();
526        assert_eq!(*foo, ModuleSpec::SourcePath("lua/foo/init.lua".into()));
527        let bar = build_spec
528            .modules
529            .get(&LuaModule::from_str("bar").unwrap())
530            .unwrap();
531        assert_eq!(
532            *bar,
533            ModuleSpec::SourcePaths(vec!["lua/bar.lua".into(), "lua/bar/internal.lua".into()])
534        );
535        let baz = build_spec
536            .modules
537            .get(&LuaModule::from_str("baz").unwrap())
538            .unwrap();
539        assert!(matches!(baz, ModuleSpec::ModulePaths { .. }));
540        let lua_content_no_sources = "
541        build = {\n
542            modules = {\n
543                baz = {\n
544                    defines = { 'USE_BAZ' },\n
545                },\n
546            },\n
547        }\n
548        ";
549        let result: Result<BuiltinBuildSpec, _> = exec_lua(lua_content_no_sources, "build");
550        let _err = result.unwrap_err();
551        let lua_content_complex_defines = "
552        build = {\n
553            modules = {\n
554                baz = {\n
555                    sources = {\n
556                        'lua/baz.lua',\n
557                    },\n
558                    defines = { 'USE_BAZ=1', 'ENABLE_LOGGING=true', 'LINK_STATIC' },\n
559                },\n
560            },\n
561        }\n
562        ";
563        let build_spec: BuiltinBuildSpec = exec_lua(lua_content_complex_defines, "build").unwrap();
564        let baz = build_spec
565            .modules
566            .get(&LuaModule::from_str("baz").unwrap())
567            .unwrap();
568        match baz {
569            ModuleSpec::ModulePaths(paths) => assert_eq!(
570                paths.defines,
571                vec![
572                    ("USE_BAZ".into(), Some("1".into())),
573                    ("ENABLE_LOGGING".into(), Some("true".into())),
574                    ("LINK_STATIC".into(), None)
575                ]
576            ),
577            _ => panic!(),
578        }
579    }
580}