1use crate::action::Action;
166use crate::sandbox::Sandbox;
167use crate::scan::ResolvedPackage;
168use anyhow::{Context, Result, anyhow, bail};
169use mlua::{Lua, RegistryKey, Result as LuaResult, Table, Value};
170use pkgsrc::PkgPath;
171use std::collections::HashMap;
172use std::path::{Path, PathBuf};
173use std::sync::{Arc, Mutex};
174
175#[derive(Clone, Debug)]
181pub struct PkgsrcEnv {
182 pub packages: PathBuf,
184 pub pkgtools: PathBuf,
186 pub prefix: PathBuf,
188 pub pkg_dbdir: PathBuf,
190 pub pkg_refcount_dbdir: PathBuf,
192 pub cachevars: HashMap<String, String>,
194}
195
196impl PkgsrcEnv {
197 pub fn fetch(config: &Config, sandbox: &Sandbox) -> Result<Self> {
202 const REQUIRED_VARS: &[&str] = &[
203 "PACKAGES",
204 "PKG_DBDIR",
205 "PKG_REFCOUNT_DBDIR",
206 "PKG_TOOLS_BIN",
207 "PREFIX",
208 ];
209
210 let user_cachevars = config.cachevars();
211 let mut all_varnames: Vec<&str> = REQUIRED_VARS.to_vec();
212 for v in user_cachevars {
213 all_varnames.push(v.as_str());
214 }
215
216 let varnames_arg = all_varnames.join(" ");
217 let script = format!(
218 "cd {}/pkgtools/pkg_install && {} show-vars VARNAMES=\"{}\"\n",
219 config.pkgsrc().display(),
220 config.make().display(),
221 varnames_arg
222 );
223
224 let child = sandbox.execute_script(0, &script, vec![])?;
225 let output = child
226 .wait_with_output()
227 .context("Failed to execute bmake show-vars")?;
228
229 if !output.status.success() {
230 let stderr = String::from_utf8_lossy(&output.stderr);
231 bail!("Failed to query pkgsrc variables: {}", stderr.trim());
232 }
233
234 let stdout = String::from_utf8_lossy(&output.stdout);
235 let lines: Vec<&str> = stdout.lines().collect();
236
237 if lines.len() != all_varnames.len() {
238 bail!(
239 "Expected {} variables from pkgsrc, got {}",
240 all_varnames.len(),
241 lines.len()
242 );
243 }
244
245 let mut values: HashMap<&str, &str> = HashMap::new();
246 for (varname, value) in all_varnames.iter().zip(&lines) {
247 values.insert(varname, value);
248 }
249
250 for varname in REQUIRED_VARS {
251 if values.get(varname).is_none_or(|v| v.is_empty()) {
252 bail!("pkgsrc returned empty value for {}", varname);
253 }
254 }
255
256 let mut cachevars: HashMap<String, String> = HashMap::new();
257 for varname in user_cachevars {
258 if let Some(value) = values.get(varname.as_str()) {
259 if !value.is_empty() {
260 cachevars.insert(varname.clone(), (*value).to_string());
261 }
262 }
263 }
264
265 Ok(PkgsrcEnv {
266 packages: PathBuf::from(values["PACKAGES"]),
267 pkgtools: PathBuf::from(values["PKG_TOOLS_BIN"]),
268 prefix: PathBuf::from(values["PREFIX"]),
269 pkg_dbdir: PathBuf::from(values["PKG_DBDIR"]),
270 pkg_refcount_dbdir: PathBuf::from(values["PKG_REFCOUNT_DBDIR"]),
271 cachevars,
272 })
273 }
274}
275
276#[derive(Clone)]
278pub struct LuaEnv {
279 lua: Arc<Mutex<Lua>>,
280 env_key: Option<Arc<RegistryKey>>,
281}
282
283impl std::fmt::Debug for LuaEnv {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 f.debug_struct("LuaEnv")
286 .field("has_env", &self.env_key.is_some())
287 .finish()
288 }
289}
290
291impl Default for LuaEnv {
292 fn default() -> Self {
293 Self {
294 lua: Arc::new(Mutex::new(Lua::new())),
295 env_key: None,
296 }
297 }
298}
299
300impl LuaEnv {
301 pub fn get_env(&self, pkg: &ResolvedPackage) -> Result<HashMap<String, String>, String> {
304 let Some(env_key) = &self.env_key else {
305 return Ok(HashMap::new());
306 };
307
308 let lua = self
309 .lua
310 .lock()
311 .map_err(|e| format!("Lua lock error: {}", e))?;
312
313 let env_value: Value = lua
315 .registry_value(env_key)
316 .map_err(|e| format!("Failed to get env from registry: {}", e))?;
317
318 let idx = &pkg.index;
319
320 let result_table: Table = match env_value {
321 Value::Function(func) => {
323 let pkg_table = lua
324 .create_table()
325 .map_err(|e| format!("Failed to create table: {}", e))?;
326
327 pkg_table
329 .set("pkgname", idx.pkgname.to_string())
330 .map_err(|e| format!("Failed to set pkgname: {}", e))?;
331 pkg_table
332 .set("pkgpath", pkg.pkgpath.as_path().display().to_string())
333 .map_err(|e| format!("Failed to set pkgpath: {}", e))?;
334 pkg_table
335 .set(
336 "all_depends",
337 idx.all_depends
338 .as_ref()
339 .map(|deps| {
340 deps.iter()
341 .map(|d| d.pkgpath().as_path().display().to_string())
342 .collect::<Vec<_>>()
343 .join(" ")
344 })
345 .unwrap_or_default(),
346 )
347 .map_err(|e| format!("Failed to set all_depends: {}", e))?;
348 pkg_table
349 .set(
350 "pkg_skip_reason",
351 idx.pkg_skip_reason.clone().unwrap_or_default(),
352 )
353 .map_err(|e| format!("Failed to set pkg_skip_reason: {}", e))?;
354 pkg_table
355 .set(
356 "pkg_fail_reason",
357 idx.pkg_fail_reason.clone().unwrap_or_default(),
358 )
359 .map_err(|e| format!("Failed to set pkg_fail_reason: {}", e))?;
360 pkg_table
361 .set(
362 "no_bin_on_ftp",
363 idx.no_bin_on_ftp.clone().unwrap_or_default(),
364 )
365 .map_err(|e| format!("Failed to set no_bin_on_ftp: {}", e))?;
366 pkg_table
367 .set("restricted", idx.restricted.clone().unwrap_or_default())
368 .map_err(|e| format!("Failed to set restricted: {}", e))?;
369 pkg_table
370 .set("categories", idx.categories.clone().unwrap_or_default())
371 .map_err(|e| format!("Failed to set categories: {}", e))?;
372 pkg_table
373 .set("maintainer", idx.maintainer.clone().unwrap_or_default())
374 .map_err(|e| format!("Failed to set maintainer: {}", e))?;
375 pkg_table
376 .set("use_destdir", idx.use_destdir.clone().unwrap_or_default())
377 .map_err(|e| format!("Failed to set use_destdir: {}", e))?;
378 pkg_table
379 .set(
380 "bootstrap_pkg",
381 idx.bootstrap_pkg.clone().unwrap_or_default(),
382 )
383 .map_err(|e| format!("Failed to set bootstrap_pkg: {}", e))?;
384 pkg_table
385 .set(
386 "usergroup_phase",
387 idx.usergroup_phase.clone().unwrap_or_default(),
388 )
389 .map_err(|e| format!("Failed to set usergroup_phase: {}", e))?;
390 pkg_table
391 .set(
392 "scan_depends",
393 idx.scan_depends
394 .as_ref()
395 .map(|deps| {
396 deps.iter()
397 .map(|p| p.display().to_string())
398 .collect::<Vec<_>>()
399 .join(" ")
400 })
401 .unwrap_or_default(),
402 )
403 .map_err(|e| format!("Failed to set scan_depends: {}", e))?;
404 pkg_table
405 .set("pbulk_weight", idx.pbulk_weight.clone().unwrap_or_default())
406 .map_err(|e| format!("Failed to set pbulk_weight: {}", e))?;
407 pkg_table
408 .set(
409 "multi_version",
410 idx.multi_version
411 .as_ref()
412 .map(|v| v.join(" "))
413 .unwrap_or_default(),
414 )
415 .map_err(|e| format!("Failed to set multi_version: {}", e))?;
416 pkg_table
417 .set(
418 "depends",
419 pkg.depends()
420 .iter()
421 .map(|d| d.to_string())
422 .collect::<Vec<_>>()
423 .join(" "),
424 )
425 .map_err(|e| format!("Failed to set depends: {}", e))?;
426
427 func.call(pkg_table)
428 .map_err(|e| format!("Failed to call env function: {}", e))?
429 }
430 Value::Table(t) => t,
432 Value::Nil => return Ok(HashMap::new()),
433 _ => return Err("env must be a function or table".to_string()),
434 };
435
436 let mut env = HashMap::new();
438 for pair in result_table.pairs::<String, String>() {
439 let (k, v) = pair.map_err(|e| format!("Failed to iterate env table: {}", e))?;
440 env.insert(k, v);
441 }
442
443 Ok(env)
444 }
445}
446
447#[derive(Clone, Debug, Default)]
449pub struct Config {
450 file: ConfigFile,
451 log_level: String,
452 lua_env: LuaEnv,
453}
454
455#[derive(Clone, Debug, Default)]
457pub struct ConfigFile {
458 pub options: Option<Options>,
460 pub pkgsrc: Pkgsrc,
462 pub scripts: HashMap<String, PathBuf>,
464 pub sandboxes: Option<Sandboxes>,
466 pub environment: Option<Environment>,
468}
469
470#[derive(Clone, Debug, Default)]
477pub struct Options {
478 pub build_threads: Option<usize>,
480 pub scan_threads: Option<usize>,
482 pub strict_scan: Option<bool>,
484 pub log_level: Option<String>,
486}
487
488#[derive(Clone, Debug, Default)]
504pub struct Pkgsrc {
505 pub basedir: PathBuf,
507 pub bootstrap: Option<PathBuf>,
509 pub build_user: Option<String>,
511 pub logdir: PathBuf,
513 pub make: PathBuf,
515 pub pkgpaths: Option<Vec<PkgPath>>,
517 pub save_wrkdir_patterns: Vec<String>,
519 pub cachevars: Vec<String>,
521 pub tar: Option<PathBuf>,
523}
524
525#[derive(Clone, Debug)]
544pub struct Environment {
545 pub clear: bool,
548 pub inherit: Vec<String>,
550 pub set: HashMap<String, String>,
552}
553
554impl Default for Environment {
555 fn default() -> Self {
556 Self {
557 clear: true,
558 inherit: Vec::new(),
559 set: HashMap::new(),
560 }
561 }
562}
563
564#[derive(Clone, Debug, Default)]
581pub struct Sandboxes {
582 pub basedir: PathBuf,
587 pub actions: Vec<Action>,
591 pub bindfs: String,
593}
594
595impl Config {
596 pub fn load(config_path: Option<&Path>) -> Result<Config> {
607 let filename = if let Some(path) = config_path {
611 path.to_path_buf()
612 } else {
613 std::env::current_dir()
614 .context("Unable to determine current directory")?
615 .join("config.lua")
616 };
617
618 if !filename.exists() {
620 anyhow::bail!("Configuration file {} does not exist", filename.display());
621 }
622
623 let (mut file, lua_env) =
627 load_lua(&filename)
628 .map_err(|e| anyhow!(e))
629 .with_context(|| {
630 format!(
631 "Unable to parse Lua configuration file {}",
632 filename.display()
633 )
634 })?;
635
636 let base_dir = filename.parent().unwrap_or_else(|| Path::new("."));
641 let mut newscripts: HashMap<String, PathBuf> = HashMap::new();
642 for (k, v) in &file.scripts {
643 let fullpath = if v.is_relative() {
644 base_dir.join(v)
645 } else {
646 v.clone()
647 };
648 newscripts.insert(k.clone(), fullpath);
649 }
650 file.scripts = newscripts;
651
652 if let Some(ref bootstrap) = file.pkgsrc.bootstrap {
656 if !bootstrap.exists() {
657 anyhow::bail!(
658 "pkgsrc.bootstrap file {} does not exist",
659 bootstrap.display()
660 );
661 }
662 }
663
664 let log_level = if let Some(opts) = &file.options {
668 opts.log_level.clone().unwrap_or_else(|| "info".to_string())
669 } else {
670 "info".to_string()
671 };
672
673 Ok(Config {
674 file,
675 log_level,
676 lua_env,
677 })
678 }
679
680 pub fn build_threads(&self) -> usize {
681 if let Some(opts) = &self.file.options {
682 opts.build_threads.unwrap_or(1)
683 } else {
684 1
685 }
686 }
687
688 pub fn scan_threads(&self) -> usize {
689 if let Some(opts) = &self.file.options {
690 opts.scan_threads.unwrap_or(1)
691 } else {
692 1
693 }
694 }
695
696 pub fn strict_scan(&self) -> bool {
697 if let Some(opts) = &self.file.options {
698 opts.strict_scan.unwrap_or(false)
699 } else {
700 false
701 }
702 }
703
704 pub fn script(&self, key: &str) -> Option<&PathBuf> {
705 self.file.scripts.get(key)
706 }
707
708 pub fn make(&self) -> &PathBuf {
709 &self.file.pkgsrc.make
710 }
711
712 pub fn pkgpaths(&self) -> &Option<Vec<PkgPath>> {
713 &self.file.pkgsrc.pkgpaths
714 }
715
716 pub fn pkgsrc(&self) -> &PathBuf {
717 &self.file.pkgsrc.basedir
718 }
719
720 pub fn sandboxes(&self) -> &Option<Sandboxes> {
721 &self.file.sandboxes
722 }
723
724 pub fn environment(&self) -> Option<&Environment> {
725 self.file.environment.as_ref()
726 }
727
728 pub fn bindfs(&self) -> &str {
729 self.file
730 .sandboxes
731 .as_ref()
732 .map(|s| s.bindfs.as_str())
733 .unwrap_or("bindfs")
734 }
735
736 pub fn log_level(&self) -> &str {
737 &self.log_level
738 }
739
740 pub fn logdir(&self) -> &PathBuf {
741 &self.file.pkgsrc.logdir
742 }
743
744 pub fn save_wrkdir_patterns(&self) -> &[String] {
745 self.file.pkgsrc.save_wrkdir_patterns.as_slice()
746 }
747
748 pub fn tar(&self) -> Option<&PathBuf> {
749 self.file.pkgsrc.tar.as_ref()
750 }
751
752 pub fn build_user(&self) -> Option<&str> {
753 self.file.pkgsrc.build_user.as_deref()
754 }
755
756 pub fn bootstrap(&self) -> Option<&PathBuf> {
757 self.file.pkgsrc.bootstrap.as_ref()
758 }
759
760 pub fn cachevars(&self) -> &[String] {
762 self.file.pkgsrc.cachevars.as_slice()
763 }
764
765 pub fn get_pkg_env(
767 &self,
768 pkg: &ResolvedPackage,
769 ) -> Result<std::collections::HashMap<String, String>, String> {
770 self.lua_env.get_env(pkg)
771 }
772
773 pub fn script_env(&self, pkgsrc_env: Option<&PkgsrcEnv>) -> Vec<(String, String)> {
783 let mut envs = vec![
784 (
785 "bob_logdir".to_string(),
786 format!("{}", self.logdir().display()),
787 ),
788 ("bob_make".to_string(), format!("{}", self.make().display())),
789 (
790 "bob_pkgsrc".to_string(),
791 format!("{}", self.pkgsrc().display()),
792 ),
793 ];
794 if let Some(env) = pkgsrc_env {
795 envs.push((
796 "bob_packages".to_string(),
797 env.packages.display().to_string(),
798 ));
799 envs.push((
800 "bob_pkgtools".to_string(),
801 env.pkgtools.display().to_string(),
802 ));
803 envs.push(("bob_prefix".to_string(), env.prefix.display().to_string()));
804 envs.push((
805 "bob_pkg_dbdir".to_string(),
806 env.pkg_dbdir.display().to_string(),
807 ));
808 envs.push((
809 "bob_pkg_refcount_dbdir".to_string(),
810 env.pkg_refcount_dbdir.display().to_string(),
811 ));
812 for (key, value) in &env.cachevars {
813 envs.push((key.clone(), value.clone()));
814 }
815 }
816 let tar_value = self
817 .tar()
818 .map(|t| t.display().to_string())
819 .unwrap_or_else(|| "tar".to_string());
820 envs.push(("bob_tar".to_string(), tar_value));
821 if let Some(build_user) = self.build_user() {
822 envs.push(("bob_build_user".to_string(), build_user.to_string()));
823 }
824 if let Some(bootstrap) = self.bootstrap() {
825 envs.push((
826 "bob_bootstrap".to_string(),
827 format!("{}", bootstrap.display()),
828 ));
829 }
830 envs
831 }
832
833 pub fn validate(&self) -> Result<(), Vec<String>> {
835 let mut errors: Vec<String> = Vec::new();
836
837 if !self.file.pkgsrc.basedir.exists() {
839 errors.push(format!(
840 "pkgsrc basedir does not exist: {}",
841 self.file.pkgsrc.basedir.display()
842 ));
843 }
844
845 if self.file.sandboxes.is_none() && !self.file.pkgsrc.make.exists() {
848 errors.push(format!(
849 "make binary does not exist: {}",
850 self.file.pkgsrc.make.display()
851 ));
852 }
853
854 for (name, path) in &self.file.scripts {
856 if !path.exists() {
857 errors.push(format!(
858 "Script '{}' does not exist: {}",
859 name,
860 path.display()
861 ));
862 } else if !path.is_file() {
863 errors.push(format!(
864 "Script '{}' is not a file: {}",
865 name,
866 path.display()
867 ));
868 }
869 }
870
871 if let Some(sandboxes) = &self.file.sandboxes {
873 if let Some(parent) = sandboxes.basedir.parent() {
875 if !parent.exists() {
876 errors.push(format!(
877 "Sandbox basedir parent does not exist: {}",
878 parent.display()
879 ));
880 }
881 }
882 }
883
884 if let Some(parent) = self.file.pkgsrc.logdir.parent() {
886 if !parent.exists() {
887 errors.push(format!(
888 "logdir parent directory does not exist: {}",
889 parent.display()
890 ));
891 }
892 }
893
894 if errors.is_empty() {
895 Ok(())
896 } else {
897 Err(errors)
898 }
899 }
900}
901
902fn load_lua(filename: &Path) -> Result<(ConfigFile, LuaEnv), String> {
904 let lua = Lua::new();
905
906 if let Some(config_dir) = filename.parent() {
908 let path_setup = format!(
909 "package.path = '{}' .. '/?.lua;' .. package.path",
910 config_dir.display()
911 );
912 lua.load(&path_setup)
913 .exec()
914 .map_err(|e| format!("Failed to set package.path: {}", e))?;
915 }
916
917 lua.load(include_str!("funcs.lua"))
919 .exec()
920 .map_err(|e| format!("Failed to load helper functions: {}", e))?;
921
922 lua.load(filename)
923 .exec()
924 .map_err(|e| format!("Lua execution error: {}", e))?;
925
926 let globals = lua.globals();
928
929 let options =
931 parse_options(&globals).map_err(|e| format!("Error parsing options config: {}", e))?;
932 let pkgsrc_table: Table = globals
933 .get("pkgsrc")
934 .map_err(|e| format!("Error getting pkgsrc config: {}", e))?;
935 let pkgsrc =
936 parse_pkgsrc(&globals).map_err(|e| format!("Error parsing pkgsrc config: {}", e))?;
937 let scripts =
938 parse_scripts(&globals).map_err(|e| format!("Error parsing scripts config: {}", e))?;
939 let sandboxes =
940 parse_sandboxes(&globals).map_err(|e| format!("Error parsing sandboxes config: {}", e))?;
941 let environment = parse_environment(&globals)
942 .map_err(|e| format!("Error parsing environment config: {}", e))?;
943
944 let env_key = if let Ok(env_value) = pkgsrc_table.get::<Value>("env") {
946 if !env_value.is_nil() {
947 let key = lua
948 .create_registry_value(env_value)
949 .map_err(|e| format!("Failed to store env in registry: {}", e))?;
950 Some(Arc::new(key))
951 } else {
952 None
953 }
954 } else {
955 None
956 };
957
958 let lua_env = LuaEnv {
959 lua: Arc::new(Mutex::new(lua)),
960 env_key,
961 };
962
963 let config = ConfigFile {
964 options,
965 pkgsrc,
966 scripts,
967 sandboxes,
968 environment,
969 };
970
971 Ok((config, lua_env))
972}
973
974fn parse_options(globals: &Table) -> LuaResult<Option<Options>> {
975 let options: Value = globals.get("options")?;
976 if options.is_nil() {
977 return Ok(None);
978 }
979
980 let table = options
981 .as_table()
982 .ok_or_else(|| mlua::Error::runtime("'options' must be a table"))?;
983
984 const KNOWN_KEYS: &[&str] = &["build_threads", "scan_threads", "strict_scan", "log_level"];
985 warn_unknown_keys(table, "options", KNOWN_KEYS);
986
987 Ok(Some(Options {
988 build_threads: table.get("build_threads").ok(),
989 scan_threads: table.get("scan_threads").ok(),
990 strict_scan: table.get("strict_scan").ok(),
991 log_level: table.get("log_level").ok(),
992 }))
993}
994
995fn warn_unknown_keys(table: &Table, table_name: &str, known_keys: &[&str]) {
997 for (key, _) in table.pairs::<String, Value>().flatten() {
998 if !known_keys.contains(&key.as_str()) {
999 eprintln!("Warning: unknown config key '{}.{}'", table_name, key);
1000 }
1001 }
1002}
1003
1004fn get_required_string(table: &Table, field: &str) -> LuaResult<String> {
1005 let value: Value = table.get(field)?;
1006 match value {
1007 Value::String(s) => Ok(s.to_str()?.to_string()),
1008 Value::Integer(n) => Ok(n.to_string()),
1009 Value::Number(n) => Ok(n.to_string()),
1010 Value::Nil => Err(mlua::Error::runtime(format!(
1011 "missing required field '{}'",
1012 field
1013 ))),
1014 _ => Err(mlua::Error::runtime(format!(
1015 "field '{}' must be a string, got {}",
1016 field,
1017 value.type_name()
1018 ))),
1019 }
1020}
1021
1022fn parse_pkgsrc(globals: &Table) -> LuaResult<Pkgsrc> {
1023 let pkgsrc: Table = globals.get("pkgsrc")?;
1024
1025 const KNOWN_KEYS: &[&str] = &[
1026 "basedir",
1027 "bootstrap",
1028 "build_user",
1029 "cachevars",
1030 "env",
1031 "logdir",
1032 "make",
1033 "pkgpaths",
1034 "save_wrkdir_patterns",
1035 "tar",
1036 ];
1037 warn_unknown_keys(&pkgsrc, "pkgsrc", KNOWN_KEYS);
1038
1039 let basedir = get_required_string(&pkgsrc, "basedir")?;
1040 let bootstrap: Option<PathBuf> = pkgsrc
1041 .get::<Option<String>>("bootstrap")?
1042 .map(PathBuf::from);
1043 let build_user: Option<String> = pkgsrc.get::<Option<String>>("build_user")?;
1044 let logdir = get_required_string(&pkgsrc, "logdir")?;
1045 let make = get_required_string(&pkgsrc, "make")?;
1046 let tar: Option<PathBuf> = pkgsrc.get::<Option<String>>("tar")?.map(PathBuf::from);
1047
1048 let pkgpaths: Option<Vec<PkgPath>> = match pkgsrc.get::<Value>("pkgpaths")? {
1049 Value::Nil => None,
1050 Value::Table(t) => {
1051 let paths: Vec<PkgPath> = t
1052 .sequence_values::<String>()
1053 .filter_map(|r| r.ok())
1054 .filter_map(|s| PkgPath::new(&s).ok())
1055 .collect();
1056 if paths.is_empty() { None } else { Some(paths) }
1057 }
1058 _ => None,
1059 };
1060
1061 let save_wrkdir_patterns: Vec<String> = match pkgsrc.get::<Value>("save_wrkdir_patterns")? {
1062 Value::Nil => Vec::new(),
1063 Value::Table(t) => t
1064 .sequence_values::<String>()
1065 .filter_map(|r| r.ok())
1066 .collect(),
1067 _ => Vec::new(),
1068 };
1069
1070 let cachevars: Vec<String> = match pkgsrc.get::<Value>("cachevars")? {
1071 Value::Nil => Vec::new(),
1072 Value::Table(t) => t
1073 .sequence_values::<String>()
1074 .filter_map(|r| r.ok())
1075 .collect(),
1076 _ => Vec::new(),
1077 };
1078
1079 Ok(Pkgsrc {
1080 basedir: PathBuf::from(basedir),
1081 bootstrap,
1082 build_user,
1083 cachevars,
1084 logdir: PathBuf::from(logdir),
1085 make: PathBuf::from(make),
1086 pkgpaths,
1087 save_wrkdir_patterns,
1088 tar,
1089 })
1090}
1091
1092fn parse_scripts(globals: &Table) -> LuaResult<HashMap<String, PathBuf>> {
1093 let scripts: Value = globals.get("scripts")?;
1094 if scripts.is_nil() {
1095 return Ok(HashMap::new());
1096 }
1097
1098 let table = scripts
1099 .as_table()
1100 .ok_or_else(|| mlua::Error::runtime("'scripts' must be a table"))?;
1101
1102 let mut result = HashMap::new();
1103 for pair in table.pairs::<String, String>() {
1104 let (k, v) = pair?;
1105 result.insert(k, PathBuf::from(v));
1106 }
1107
1108 Ok(result)
1109}
1110
1111fn parse_sandboxes(globals: &Table) -> LuaResult<Option<Sandboxes>> {
1112 let sandboxes: Value = globals.get("sandboxes")?;
1113 if sandboxes.is_nil() {
1114 return Ok(None);
1115 }
1116
1117 let table = sandboxes
1118 .as_table()
1119 .ok_or_else(|| mlua::Error::runtime("'sandboxes' must be a table"))?;
1120
1121 const KNOWN_KEYS: &[&str] = &["actions", "basedir", "bindfs"];
1122 warn_unknown_keys(table, "sandboxes", KNOWN_KEYS);
1123
1124 let basedir: String = table.get("basedir")?;
1125 let bindfs: String = table
1126 .get::<Option<String>>("bindfs")?
1127 .unwrap_or_else(|| String::from("bindfs"));
1128
1129 let actions_value: Value = table.get("actions")?;
1130 let actions = if actions_value.is_nil() {
1131 Vec::new()
1132 } else {
1133 let actions_table = actions_value
1134 .as_table()
1135 .ok_or_else(|| mlua::Error::runtime("'sandboxes.actions' must be a table"))?;
1136 parse_actions(actions_table, globals)?
1137 };
1138
1139 Ok(Some(Sandboxes {
1140 basedir: PathBuf::from(basedir),
1141 actions,
1142 bindfs,
1143 }))
1144}
1145
1146fn parse_actions(table: &Table, globals: &Table) -> LuaResult<Vec<Action>> {
1147 let mut actions = Vec::new();
1148 for v in table.sequence_values::<Table>() {
1149 let mut action = Action::from_lua(&v?)?;
1150 if let Some(varpath) = action.ifset().map(String::from) {
1151 match resolve_lua_var(globals, &varpath) {
1152 Some(val) => action.substitute_var(&varpath, &val),
1153 None => continue,
1154 }
1155 }
1156 actions.push(action);
1157 }
1158 Ok(actions)
1159}
1160
1161fn resolve_lua_var(globals: &Table, path: &str) -> Option<String> {
1166 let mut parts = path.split('.');
1167 let first = parts.next()?;
1168 let mut current: Value = globals.get(first).ok()?;
1169 for key in parts {
1170 match current {
1171 Value::Table(t) => {
1172 current = t.get(key).ok()?;
1173 }
1174 _ => return None,
1175 }
1176 }
1177 match current {
1178 Value::String(s) => Some(s.to_str().ok()?.to_string()),
1179 Value::Integer(n) => Some(n.to_string()),
1180 Value::Number(n) => Some(n.to_string()),
1181 _ => None,
1182 }
1183}
1184
1185fn parse_environment(globals: &Table) -> LuaResult<Option<Environment>> {
1186 let environment: Value = globals.get("environment")?;
1187 if environment.is_nil() {
1188 return Ok(None);
1189 }
1190
1191 let table = environment
1192 .as_table()
1193 .ok_or_else(|| mlua::Error::runtime("'environment' must be a table"))?;
1194
1195 const KNOWN_KEYS: &[&str] = &["clear", "inherit", "set"];
1196 warn_unknown_keys(table, "environment", KNOWN_KEYS);
1197
1198 let clear: bool = table.get::<Option<bool>>("clear")?.unwrap_or(true);
1199
1200 let inherit: Vec<String> = match table.get::<Value>("inherit")? {
1201 Value::Nil => Vec::new(),
1202 Value::Table(t) => t
1203 .sequence_values::<String>()
1204 .filter_map(|r| r.ok())
1205 .collect(),
1206 _ => {
1207 return Err(mlua::Error::runtime(
1208 "'environment.inherit' must be a table",
1209 ));
1210 }
1211 };
1212
1213 let set: HashMap<String, String> = match table.get::<Value>("set")? {
1214 Value::Nil => HashMap::new(),
1215 Value::Table(t) => {
1216 let mut map = HashMap::new();
1217 for pair in t.pairs::<String, String>() {
1218 let (k, v) = pair?;
1219 map.insert(k, v);
1220 }
1221 map
1222 }
1223 _ => return Err(mlua::Error::runtime("'environment.set' must be a table")),
1224 };
1225
1226 Ok(Some(Environment {
1227 clear,
1228 inherit,
1229 set,
1230 }))
1231}