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 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 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 SourcePath(PathBuf),
136 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#[derive(Debug, PartialEq, Clone)]
304pub struct ModulePaths {
305 pub sources: Vec<PathBuf>,
307 pub libraries: Vec<PathBuf>,
309 pub defines: Vec<(String, Option<String>)>,
311 pub incdirs: Vec<PathBuf>,
313 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}