lux_lib/config/
mod.rs

1use directories::ProjectDirs;
2use external_deps::ExternalDependencySearchConfig;
3use itertools::Itertools;
4use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, UserData};
5use serde::{Deserialize, Serialize, Serializer};
6use std::{
7    collections::HashMap, env, fmt::Display, io, path::PathBuf, str::FromStr, time::Duration,
8};
9use thiserror::Error;
10use tree::RockLayoutConfig;
11use url::Url;
12
13use crate::tree::{Tree, TreeError};
14use crate::{
15    build::utils,
16    package::{PackageVersion, PackageVersionReq},
17    variables::HasVariables,
18};
19
20pub mod external_deps;
21pub mod tree;
22
23const DEV_PATH: &str = "dev/";
24
25#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
26pub enum LuaVersion {
27    #[serde(rename = "5.1")]
28    Lua51,
29    #[serde(rename = "5.2")]
30    Lua52,
31    #[serde(rename = "5.3")]
32    Lua53,
33    #[serde(rename = "5.4")]
34    Lua54,
35    #[serde(rename = "jit")]
36    LuaJIT,
37    #[serde(rename = "jit5.2")]
38    LuaJIT52,
39    // TODO(vhyrro): Support luau?
40    // LuaU,
41}
42
43impl FromLua for LuaVersion {
44    fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
45        let version_str: String = FromLua::from_lua(value, lua)?;
46        LuaVersion::from_str(&version_str).into_lua_err()
47    }
48}
49
50impl IntoLua for LuaVersion {
51    fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
52        self.to_string().into_lua(lua)
53    }
54}
55
56#[derive(Debug, Error)]
57pub enum LuaVersionError {
58    #[error("unsupported Lua version: {0}")]
59    UnsupportedLuaVersion(PackageVersion),
60}
61
62impl LuaVersion {
63    pub fn as_version(&self) -> PackageVersion {
64        match self {
65            LuaVersion::Lua51 => "5.1.0".parse().unwrap(),
66            LuaVersion::Lua52 => "5.2.0".parse().unwrap(),
67            LuaVersion::Lua53 => "5.3.0".parse().unwrap(),
68            LuaVersion::Lua54 => "5.4.0".parse().unwrap(),
69            LuaVersion::LuaJIT => "5.1.0".parse().unwrap(),
70            LuaVersion::LuaJIT52 => "5.2.0".parse().unwrap(),
71        }
72    }
73    pub fn version_compatibility_str(&self) -> String {
74        match self {
75            LuaVersion::Lua51 | LuaVersion::LuaJIT => "5.1".into(),
76            LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "5.2".into(),
77            LuaVersion::Lua53 => "5.3".into(),
78            LuaVersion::Lua54 => "5.4".into(),
79        }
80    }
81    pub fn as_version_req(&self) -> PackageVersionReq {
82        format!("~> {}", self.version_compatibility_str())
83            .parse()
84            .unwrap()
85    }
86
87    /// Get the LuaVersion from a version that has been parsed from the `lua -v` output
88    pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
89        // NOTE: Special case. luajit -v outputs 2.x.y as a version
90        let luajit_version_req: PackageVersionReq = "~> 2".parse().unwrap();
91        if luajit_version_req.matches(&version) {
92            Ok(LuaVersion::LuaJIT)
93        } else if LuaVersion::Lua51.as_version_req().matches(&version) {
94            Ok(LuaVersion::Lua51)
95        } else if LuaVersion::Lua52.as_version_req().matches(&version) {
96            Ok(LuaVersion::Lua52)
97        } else if LuaVersion::Lua53.as_version_req().matches(&version) {
98            Ok(LuaVersion::Lua53)
99        } else if LuaVersion::Lua54.as_version_req().matches(&version) {
100            Ok(LuaVersion::Lua54)
101        } else {
102            Err(LuaVersionError::UnsupportedLuaVersion(version))
103        }
104    }
105
106    pub(crate) fn is_luajit(&self) -> bool {
107        matches!(self, Self::LuaJIT | Self::LuaJIT52)
108    }
109
110    /// Searches for the path to the lux-lua library for this version
111    pub fn lux_lib_dir(&self) -> Option<PathBuf> {
112        let lib_name = format!("lux-lua{}", self);
113        option_env!("LUX_LIB_DIR")
114            .map(PathBuf::from)
115            .or_else(|| {
116                pkg_config::Config::new()
117                    .print_system_libs(false)
118                    .cargo_metadata(false)
119                    .env_metadata(false)
120                    .probe(&lib_name)
121                    .ok()
122                    .and_then(|library| library.link_paths.first().cloned())
123            })
124            .map(|path| path.join(self.to_string()))
125    }
126}
127
128#[derive(Error, Debug)]
129#[error("lua version not set! Please provide a version through `lx --lua-version <ver> <cmd>`\nValid versions are: '5.1', '5.2', '5.3', '5.4', 'jit' and 'jit52'.")]
130pub struct LuaVersionUnset;
131
132impl LuaVersion {
133    pub fn from(config: &Config) -> Result<&Self, LuaVersionUnset> {
134        config.lua_version.as_ref().ok_or(LuaVersionUnset)
135    }
136}
137
138impl FromStr for LuaVersion {
139    type Err = String;
140
141    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
142        match s {
143            "5.1" | "51" => Ok(LuaVersion::Lua51),
144            "5.2" | "52" => Ok(LuaVersion::Lua52),
145            "5.3" | "53" => Ok(LuaVersion::Lua53),
146            "5.4" | "54" => Ok(LuaVersion::Lua54),
147            "jit" | "luajit" => Ok(LuaVersion::LuaJIT),
148            "jit52" | "luajit52" => Ok(LuaVersion::LuaJIT52),
149            _ => Err(
150                "unrecognized Lua version. Allowed versions: '5.1', '5.2', '5.3', '5.4', 'jit', 'jit52'."
151                    .into(),
152            ),
153        }
154    }
155}
156
157impl Display for LuaVersion {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        f.write_str(match self {
160            LuaVersion::Lua51 => "5.1",
161            LuaVersion::Lua52 => "5.2",
162            LuaVersion::Lua53 => "5.3",
163            LuaVersion::Lua54 => "5.4",
164            LuaVersion::LuaJIT => "jit",
165            LuaVersion::LuaJIT52 => "jit52",
166        })
167    }
168}
169
170#[derive(Error, Debug)]
171#[error("could not find a valid home directory")]
172pub struct NoValidHomeDirectory;
173
174#[derive(Debug, Clone, FromLua)]
175pub struct Config {
176    enable_development_packages: bool,
177    server: Url,
178    extra_servers: Vec<Url>,
179    only_sources: Option<String>,
180    namespace: Option<String>,
181    lua_dir: Option<PathBuf>,
182    lua_version: Option<LuaVersion>,
183    user_tree: PathBuf,
184    no_project: bool,
185    verbose: bool,
186    timeout: Duration,
187    variables: HashMap<String, String>,
188    external_deps: ExternalDependencySearchConfig,
189    /// The rock layout for entrypoints of new install trees.
190    /// Does not affect existing install trees or dependency rock layouts.
191    entrypoint_layout: RockLayoutConfig,
192
193    cache_dir: PathBuf,
194    data_dir: PathBuf,
195}
196
197impl Config {
198    pub fn get_project_dirs() -> Result<ProjectDirs, NoValidHomeDirectory> {
199        directories::ProjectDirs::from("org", "neorocks", "lux").ok_or(NoValidHomeDirectory)
200    }
201
202    pub fn get_default_cache_path() -> Result<PathBuf, NoValidHomeDirectory> {
203        let project_dirs = Config::get_project_dirs()?;
204        Ok(project_dirs.cache_dir().to_path_buf())
205    }
206
207    pub fn get_default_data_path() -> Result<PathBuf, NoValidHomeDirectory> {
208        let project_dirs = Config::get_project_dirs()?;
209        Ok(project_dirs.data_local_dir().to_path_buf())
210    }
211
212    pub fn with_lua_version(self, lua_version: LuaVersion) -> Self {
213        Self {
214            lua_version: Some(lua_version),
215            ..self
216        }
217    }
218
219    pub fn with_tree(self, tree: PathBuf) -> Self {
220        Self {
221            user_tree: tree,
222            ..self
223        }
224    }
225
226    pub fn server(&self) -> &Url {
227        &self.server
228    }
229
230    pub fn extra_servers(&self) -> &Vec<Url> {
231        self.extra_servers.as_ref()
232    }
233
234    pub fn enabled_dev_servers(&self) -> Result<Vec<Url>, ConfigError> {
235        let mut enabled_dev_servers = Vec::new();
236        if self.enable_development_packages {
237            enabled_dev_servers.push(self.server().join(DEV_PATH)?);
238            for server in self.extra_servers() {
239                enabled_dev_servers.push(server.join(DEV_PATH)?);
240            }
241        }
242        Ok(enabled_dev_servers)
243    }
244
245    pub fn only_sources(&self) -> Option<&String> {
246        self.only_sources.as_ref()
247    }
248
249    pub fn namespace(&self) -> Option<&String> {
250        self.namespace.as_ref()
251    }
252
253    pub fn lua_dir(&self) -> Option<&PathBuf> {
254        self.lua_dir.as_ref()
255    }
256
257    #[cfg(test)]
258    pub(crate) fn lua_version(&self) -> Option<&LuaVersion> {
259        self.lua_version.as_ref()
260    }
261
262    /// The tree in which to install rocks.
263    /// If installing packges for a project, use `Project::tree` instead.
264    pub fn user_tree(&self, version: LuaVersion) -> Result<Tree, TreeError> {
265        Tree::new(self.user_tree.clone(), version, self)
266    }
267
268    pub fn no_project(&self) -> bool {
269        self.no_project
270    }
271
272    pub fn verbose(&self) -> bool {
273        self.verbose
274    }
275
276    pub fn timeout(&self) -> &Duration {
277        &self.timeout
278    }
279
280    pub fn make_cmd(&self) -> String {
281        match self.variables.get("MAKE") {
282            Some(make) => make.clone(),
283            None => "make".into(),
284        }
285    }
286
287    pub fn cmake_cmd(&self) -> String {
288        match self.variables.get("CMAKE") {
289            Some(cmake) => cmake.clone(),
290            None => "cmake".into(),
291        }
292    }
293
294    pub fn variables(&self) -> &HashMap<String, String> {
295        &self.variables
296    }
297
298    pub fn external_deps(&self) -> &ExternalDependencySearchConfig {
299        &self.external_deps
300    }
301
302    pub fn entrypoint_layout(&self) -> &RockLayoutConfig {
303        &self.entrypoint_layout
304    }
305
306    pub fn cache_dir(&self) -> &PathBuf {
307        &self.cache_dir
308    }
309
310    pub fn data_dir(&self) -> &PathBuf {
311        &self.data_dir
312    }
313}
314
315impl HasVariables for Config {
316    fn get_variable(&self, input: &str) -> Option<String> {
317        self.variables.get(input).cloned()
318    }
319}
320
321#[derive(Error, Debug)]
322pub enum ConfigError {
323    #[error(transparent)]
324    Io(#[from] io::Error),
325    #[error(transparent)]
326    NoValidHomeDirectory(#[from] NoValidHomeDirectory),
327    #[error("error deserializing lux config: {0}")]
328    Deserialize(#[from] toml::de::Error),
329    #[error("error parsing URL: {0}")]
330    UrlParseError(#[from] url::ParseError),
331    #[error("error initializing compiler toolchain: {0}")]
332    CompilerToolchain(#[from] cc::Error),
333}
334
335#[derive(Clone, Default, Deserialize, Serialize)]
336pub struct ConfigBuilder {
337    #[serde(
338        default,
339        deserialize_with = "deserialize_url",
340        serialize_with = "serialize_url"
341    )]
342    server: Option<Url>,
343    #[serde(
344        default,
345        deserialize_with = "deserialize_url_vec",
346        serialize_with = "serialize_url_vec"
347    )]
348    extra_servers: Option<Vec<Url>>,
349    only_sources: Option<String>,
350    namespace: Option<String>,
351    lua_version: Option<LuaVersion>,
352    user_tree: Option<PathBuf>,
353    lua_dir: Option<PathBuf>,
354    cache_dir: Option<PathBuf>,
355    data_dir: Option<PathBuf>,
356    no_project: Option<bool>,
357    enable_development_packages: Option<bool>,
358    verbose: Option<bool>,
359    timeout: Option<Duration>,
360    variables: Option<HashMap<String, String>>,
361    #[serde(default)]
362    external_deps: ExternalDependencySearchConfig,
363    /// The rock layout for new install trees.
364    /// Does not affect existing install trees.
365    #[serde(default)]
366    entrypoint_layout: RockLayoutConfig,
367}
368
369impl ConfigBuilder {
370    /// Create a new `ConfigBuilder` from a config file by deserializing from a config file
371    /// if present, or otherwise by instantiating the default config.
372    pub fn new() -> Result<Self, ConfigError> {
373        let config_file = Self::config_file()?;
374        if config_file.is_file() {
375            Ok(toml::from_str(&std::fs::read_to_string(&config_file)?)?)
376        } else {
377            Ok(Self::default())
378        }
379    }
380
381    /// Get the path to the lux config file.
382    pub fn config_file() -> Result<PathBuf, NoValidHomeDirectory> {
383        let project_dirs =
384            directories::ProjectDirs::from("org", "neorocks", "lux").ok_or(NoValidHomeDirectory)?;
385        Ok(project_dirs.config_dir().join("config.toml").to_path_buf())
386    }
387
388    pub fn dev(self, dev: Option<bool>) -> Self {
389        Self {
390            enable_development_packages: dev,
391            ..self
392        }
393    }
394
395    pub fn server(self, server: Option<Url>) -> Self {
396        Self { server, ..self }
397    }
398
399    pub fn extra_servers(self, extra_servers: Option<Vec<Url>>) -> Self {
400        Self {
401            extra_servers,
402            ..self
403        }
404    }
405
406    pub fn only_sources(self, sources: Option<String>) -> Self {
407        Self {
408            only_sources: sources,
409            ..self
410        }
411    }
412
413    pub fn namespace(self, namespace: Option<String>) -> Self {
414        Self { namespace, ..self }
415    }
416
417    pub fn lua_dir(self, lua_dir: Option<PathBuf>) -> Self {
418        Self { lua_dir, ..self }
419    }
420
421    pub fn lua_version(self, lua_version: Option<LuaVersion>) -> Self {
422        Self {
423            lua_version,
424            ..self
425        }
426    }
427
428    pub fn user_tree(self, tree: Option<PathBuf>) -> Self {
429        Self {
430            user_tree: tree,
431            ..self
432        }
433    }
434
435    pub fn no_project(self, no_project: Option<bool>) -> Self {
436        Self { no_project, ..self }
437    }
438
439    pub fn variables(self, variables: Option<HashMap<String, String>>) -> Self {
440        Self { variables, ..self }
441    }
442
443    pub fn verbose(self, verbose: Option<bool>) -> Self {
444        Self { verbose, ..self }
445    }
446
447    pub fn timeout(self, timeout: Option<Duration>) -> Self {
448        Self { timeout, ..self }
449    }
450
451    pub fn cache_dir(self, cache_dir: Option<PathBuf>) -> Self {
452        Self { cache_dir, ..self }
453    }
454
455    pub fn data_dir(self, data_dir: Option<PathBuf>) -> Self {
456        Self { data_dir, ..self }
457    }
458
459    pub fn entrypoint_layout(self, rock_layout: RockLayoutConfig) -> Self {
460        Self {
461            entrypoint_layout: rock_layout,
462            ..self
463        }
464    }
465
466    pub fn build(self) -> Result<Config, ConfigError> {
467        let data_dir = self.data_dir.unwrap_or(Config::get_default_data_path()?);
468        let cache_dir = self.cache_dir.unwrap_or(Config::get_default_cache_path()?);
469        let user_tree = self.user_tree.unwrap_or(data_dir.join("tree"));
470
471        let lua_version = self
472            .lua_version
473            .or(crate::lua_installation::detect_installed_lua_version());
474
475        Ok(Config {
476            enable_development_packages: self.enable_development_packages.unwrap_or(false),
477            server: self
478                .server
479                .unwrap_or_else(|| Url::parse("https://luarocks.org/").unwrap()),
480            extra_servers: self.extra_servers.unwrap_or_default(),
481            only_sources: self.only_sources,
482            namespace: self.namespace,
483            lua_dir: self.lua_dir,
484            lua_version,
485            user_tree,
486            no_project: self.no_project.unwrap_or(false),
487            verbose: self.verbose.unwrap_or(false),
488            timeout: self.timeout.unwrap_or_else(|| Duration::from_secs(30)),
489            variables: default_variables()
490                .chain(self.variables.unwrap_or_default())
491                .collect(),
492            external_deps: self.external_deps,
493            entrypoint_layout: self.entrypoint_layout,
494            cache_dir,
495            data_dir,
496        })
497    }
498}
499
500/// Useful for printing the current config
501impl From<Config> for ConfigBuilder {
502    fn from(value: Config) -> Self {
503        ConfigBuilder {
504            enable_development_packages: Some(value.enable_development_packages),
505            server: Some(value.server),
506            extra_servers: Some(value.extra_servers),
507            only_sources: value.only_sources,
508            namespace: value.namespace,
509            lua_dir: value.lua_dir,
510            lua_version: value.lua_version,
511            user_tree: Some(value.user_tree),
512            no_project: Some(value.no_project),
513            verbose: Some(value.verbose),
514            timeout: Some(value.timeout),
515            variables: Some(value.variables),
516            cache_dir: Some(value.cache_dir),
517            data_dir: Some(value.data_dir),
518            external_deps: value.external_deps,
519            entrypoint_layout: value.entrypoint_layout,
520        }
521    }
522}
523
524fn default_variables() -> impl Iterator<Item = (String, String)> {
525    let cflags = env::var("CFLAGS").unwrap_or(utils::default_cflags().into());
526    vec![
527        ("MAKE".into(), "make".into()),
528        ("CMAKE".into(), "cmake".into()),
529        ("LIB_EXTENSION".into(), utils::c_dylib_extension().into()),
530        ("OBJ_EXTENSION".into(), utils::c_obj_extension().into()),
531        ("CFLAGS".into(), cflags),
532        ("LIBFLAG".into(), utils::default_libflag().into()),
533    ]
534    .into_iter()
535}
536
537fn deserialize_url<'de, D>(deserializer: D) -> Result<Option<Url>, D::Error>
538where
539    D: serde::Deserializer<'de>,
540{
541    let s = Option::<String>::deserialize(deserializer)?;
542    s.map(|s| Url::parse(&s).map_err(serde::de::Error::custom))
543        .transpose()
544}
545
546fn serialize_url<S>(url: &Option<Url>, serializer: S) -> Result<S::Ok, S::Error>
547where
548    S: Serializer,
549{
550    match url {
551        Some(url) => serializer.serialize_some(url.as_str()),
552        None => serializer.serialize_none(),
553    }
554}
555
556fn deserialize_url_vec<'de, D>(deserializer: D) -> Result<Option<Vec<Url>>, D::Error>
557where
558    D: serde::Deserializer<'de>,
559{
560    let s = Option::<Vec<String>>::deserialize(deserializer)?;
561    s.map(|v| {
562        v.into_iter()
563            .map(|s| Url::parse(&s).map_err(serde::de::Error::custom))
564            .try_collect()
565    })
566    .transpose()
567}
568
569fn serialize_url_vec<S>(urls: &Option<Vec<Url>>, serializer: S) -> Result<S::Ok, S::Error>
570where
571    S: Serializer,
572{
573    match urls {
574        Some(urls) => {
575            let url_strings: Vec<String> = urls.iter().map(|url| url.to_string()).collect();
576            serializer.serialize_some(&url_strings)
577        }
578        None => serializer.serialize_none(),
579    }
580}
581
582struct LuaUrl(Url);
583
584impl FromLua for LuaUrl {
585    fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
586        let url_str: String = FromLua::from_lua(value, lua)?;
587
588        Url::parse(&url_str).map(LuaUrl).into_lua_err()
589    }
590}
591
592impl UserData for Config {
593    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
594        methods.add_function("default", |_, _: ()| {
595            ConfigBuilder::default()
596                .build()
597                .map_err(|err| err.into_lua_err())
598        });
599
600        methods.add_function("builder", |_, ()| ConfigBuilder::new().into_lua_err());
601
602        methods.add_method("server", |_, this, ()| Ok(this.server().to_string()));
603        methods.add_method("extra_servers", |_, this, ()| {
604            Ok(this
605                .extra_servers()
606                .iter()
607                .map(|url| url.to_string())
608                .collect_vec())
609        });
610        methods.add_method("only_sources", |_, this, ()| {
611            Ok(this.only_sources().cloned())
612        });
613        methods.add_method("namespace", |_, this, ()| Ok(this.namespace().cloned()));
614        methods.add_method("lua_dir", |_, this, ()| Ok(this.lua_dir().cloned()));
615        methods.add_method("user_tree", |_, this, lua_version: LuaVersion| {
616            this.user_tree(lua_version).into_lua_err()
617        });
618        methods.add_method("no_project", |_, this, ()| Ok(this.no_project()));
619        methods.add_method("verbose", |_, this, ()| Ok(this.verbose()));
620        methods.add_method("timeout", |_, this, ()| Ok(this.timeout().as_secs()));
621        methods.add_method("cache_dir", |_, this, ()| Ok(this.cache_dir().clone()));
622        methods.add_method("data_dir", |_, this, ()| Ok(this.data_dir().clone()));
623        methods.add_method("entrypoint_layout", |_, this, ()| {
624            Ok(this.entrypoint_layout().clone())
625        });
626        methods.add_method("variables", |_, this, ()| Ok(this.variables().clone()));
627        // FIXME: This is a temporary workaround to get the external_deps hooked up to Lua
628        // methods.add_method("external_deps", |_, this, ()| {
629        //     Ok(this.external_deps().clone())
630        // });
631        methods.add_method("make_cmd", |_, this, ()| Ok(this.make_cmd()));
632        methods.add_method("cmake_cmd", |_, this, ()| Ok(this.cmake_cmd()));
633        methods.add_method("enabled_dev_servers", |_, this, ()| {
634            Ok(this
635                .enabled_dev_servers()
636                .into_lua_err()?
637                .into_iter()
638                .map(|url| url.to_string())
639                .collect_vec())
640        });
641    }
642}
643
644impl UserData for ConfigBuilder {
645    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
646        methods.add_method("dev", |_, this, dev: Option<bool>| {
647            Ok(this.clone().dev(dev))
648        });
649        methods.add_method("server", |_, this, server: Option<LuaUrl>| {
650            Ok(this.clone().server(server.map(|url| url.0)))
651        });
652        methods.add_method("extra_servers", |_, this, servers: Option<Vec<LuaUrl>>| {
653            Ok(this
654                .clone()
655                .extra_servers(servers.map(|urls| urls.into_iter().map(|url| url.0).collect())))
656        });
657        methods.add_method("only_sources", |_, this, sources: Option<String>| {
658            Ok(this.clone().only_sources(sources))
659        });
660        methods.add_method("namespace", |_, this, namespace: Option<String>| {
661            Ok(this.clone().namespace(namespace))
662        });
663        methods.add_method("lua_dir", |_, this, lua_dir: Option<PathBuf>| {
664            Ok(this.clone().lua_dir(lua_dir))
665        });
666        methods.add_method("lua_version", |_, this, lua_version: Option<LuaVersion>| {
667            Ok(this.clone().lua_version(lua_version))
668        });
669        methods.add_method("user_tree", |_, this, tree: Option<PathBuf>| {
670            Ok(this.clone().user_tree(tree))
671        });
672        methods.add_method("no_project", |_, this, no_project: Option<bool>| {
673            Ok(this.clone().no_project(no_project))
674        });
675        methods.add_method("verbose", |_, this, verbose: Option<bool>| {
676            Ok(this.clone().verbose(verbose))
677        });
678        methods.add_method("timeout", |_, this, timeout: Option<u64>| {
679            Ok(this.clone().timeout(timeout.map(Duration::from_secs)))
680        });
681        methods.add_method("cache_dir", |_, this, cache_dir: Option<PathBuf>| {
682            Ok(this.clone().cache_dir(cache_dir))
683        });
684        methods.add_method("data_dir", |_, this, data_dir: Option<PathBuf>| {
685            Ok(this.clone().data_dir(data_dir))
686        });
687        methods.add_method(
688            "entrypoint_layout",
689            |_, this, entrypoint_layout: Option<RockLayoutConfig>| {
690                Ok(this
691                    .clone()
692                    .entrypoint_layout(entrypoint_layout.unwrap_or_default()))
693            },
694        );
695        methods.add_method("build", |_, this, ()| this.clone().build().into_lua_err());
696    }
697}