lux_lib/operations/
run_lua.rs

1//! Run the `lua` binary with some given arguments.
2//!
3//! The interfaces exposed here ensure that the correct version of Lua is being used.
4
5use bon::Builder;
6
7use crate::config::Config;
8
9use std::{
10    io,
11    path::{Path, PathBuf},
12};
13
14use thiserror::Error;
15use tokio::process::Command;
16
17use crate::{
18    lua_installation::{LuaBinary, LuaBinaryError},
19    path::{Paths, PathsError},
20    tree::Tree,
21    tree::TreeError,
22};
23
24#[derive(Error, Debug)]
25pub enum RunLuaError {
26    #[error("error running lua: {0}")]
27    LuaBinary(#[from] LuaBinaryError),
28    #[error("failed to run {lua_cmd}: {source}")]
29    LuaCommandFailed {
30        lua_cmd: String,
31        #[source]
32        source: io::Error,
33    },
34    #[error("{lua_cmd} exited with non-zero exit code: {}", exit_code.map(|code| code.to_string()).unwrap_or("unknown".into()))]
35    LuaCommandNonZeroExitCode {
36        lua_cmd: String,
37        exit_code: Option<i32>,
38    },
39    #[error(transparent)]
40    Paths(#[from] PathsError),
41
42    #[error(transparent)]
43    Tree(#[from] TreeError),
44}
45
46#[derive(Builder)]
47#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
48pub struct RunLua<'a> {
49    root: &'a Path,
50    tree: &'a Tree,
51    config: &'a Config,
52    lua_cmd: LuaBinary,
53    args: &'a Vec<String>,
54    prepend_test_paths: Option<bool>,
55    prepend_build_paths: Option<bool>,
56    welcome_message: Option<String>,
57}
58
59impl<State> RunLuaBuilder<'_, State>
60where
61    State: run_lua_builder::State + run_lua_builder::IsComplete,
62{
63    pub async fn run_lua(self) -> Result<(), RunLuaError> {
64        let args = self._build();
65        let mut paths = Paths::new(args.tree)?;
66
67        if args.prepend_test_paths.unwrap_or(false) {
68            let test_tree_path = args.tree.test_tree(args.config)?;
69
70            let test_path = Paths::new(&test_tree_path)?;
71
72            paths.prepend(&test_path);
73        }
74
75        if args.prepend_build_paths.unwrap_or(false) {
76            let build_tree_path = args.tree.build_tree(args.config)?;
77
78            let build_path = Paths::new(&build_tree_path)?;
79
80            paths.prepend(&build_path);
81        }
82
83        let lua_cmd: PathBuf = args.lua_cmd.try_into()?;
84
85        let loader_init = if args.tree.version().lux_lib_dir().is_none() {
86            eprintln!(
87                "⚠️ WARNING: lux-lua library not found.
88Cannot use the `lux.loader`.
89                "
90            );
91            "".to_string()
92        } else {
93            paths.init()
94        };
95        let lua_init = format!(
96            r#"print([==[{}]==])
97            exit = os.exit
98            print([==[
99{}
100To exit type 'exit()' or <C-d>.
101]==])
102        "#,
103            args.welcome_message.unwrap_or_default(),
104            loader_init
105        );
106
107        let status = match Command::new(&lua_cmd)
108            .current_dir(args.root)
109            .args(args.args)
110            .env("PATH", paths.path_prepended().joined())
111            .env("LUA_PATH", paths.package_path().joined())
112            .env("LUA_CPATH", paths.package_cpath().joined())
113            .env("LUA_INIT", lua_init)
114            .status()
115            .await
116        {
117            Ok(status) => Ok(status),
118            Err(err) => Err(RunLuaError::LuaCommandFailed {
119                lua_cmd: lua_cmd.to_string_lossy().to_string(),
120                source: err,
121            }),
122        }?;
123        if status.success() {
124            Ok(())
125        } else {
126            Err(RunLuaError::LuaCommandNonZeroExitCode {
127                lua_cmd: lua_cmd.to_string_lossy().to_string(),
128                exit_code: status.code(),
129            })
130        }
131    }
132}