lux_lib/operations/
run.rs1use std::ops::Deref;
2
3use itertools::Itertools;
4use nonempty::NonEmpty;
5use serde::Deserialize;
6use thiserror::Error;
7use tokio::process::Command;
8
9use crate::{
10 config::Config,
11 lua_installation::LuaBinary,
12 lua_rockspec::LuaVersionError,
13 operations::run_lua::RunLua,
14 path::{Paths, PathsError},
15 project::{project_toml::LocalProjectTomlValidationError, Project, ProjectTreeError},
16};
17
18use super::RunLuaError;
19
20#[derive(Debug, Error)]
21#[error("`{0}` should not be used as a `command` as it is not cross-platform.
22You should only change the default `command` if it is a different Lua interpreter that behaves identically on all platforms.
23Consider removing the `command` field and letting Lux choose the default Lua interpreter instead.")]
24pub struct RunCommandError(String);
25
26#[derive(Debug, Clone)]
27pub struct RunCommand(String);
28
29impl RunCommand {
30 pub fn from(command: String) -> Result<Self, RunCommandError> {
31 match command.as_str() {
32 "lua" | "lua5.1" | "lua5.2" | "lua5.3" | "lua5.4" | "luajit" => {
35 Err(RunCommandError(command))
36 }
37 _ => Ok(Self(command)),
38 }
39 }
40}
41
42impl<'de> Deserialize<'de> for RunCommand {
43 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
44 where
45 D: serde::Deserializer<'de>,
46 {
47 let command = String::deserialize(deserializer)?;
48
49 RunCommand::from(command).map_err(serde::de::Error::custom)
50 }
51}
52
53impl Deref for RunCommand {
54 type Target = String;
55
56 fn deref(&self) -> &Self::Target {
57 &self.0
58 }
59}
60
61#[derive(Debug, Error)]
62#[error(transparent)]
63pub enum RunError {
64 Toml(#[from] LocalProjectTomlValidationError),
65 RunCommand(#[from] RunCommandError),
66 LuaVersion(#[from] LuaVersionError),
67 RunLua(#[from] RunLuaError),
68 ProjectTree(#[from] ProjectTreeError),
69 Io(#[from] std::io::Error),
70 Paths(#[from] PathsError),
71 #[error("No `run` field found in `lux.toml`")]
72 NoRunField,
73}
74
75async fn run_with_local_lua(
76 project: &Project,
77 args: &NonEmpty<String>,
78 config: &Config,
79) -> Result<(), RunError> {
80 let version = project.lua_version(config)?;
81
82 let tree = project.tree(config)?;
83 let args = &args.into_iter().cloned().collect();
84
85 RunLua::new()
86 .root(project.root())
87 .tree(&tree)
88 .config(config)
89 .lua_cmd(LuaBinary::new(version, config))
90 .args(args)
91 .run_lua()
92 .await?;
93
94 Ok(())
95}
96
97async fn run_with_command(
98 project: &Project,
99 command: &RunCommand,
100 args: &NonEmpty<String>,
101 config: &Config,
102) -> Result<(), RunError> {
103 let tree = project.tree(config)?;
104 let paths = Paths::new(&tree)?;
105
106 match Command::new(command.deref())
107 .args(args.into_iter().cloned().collect_vec())
108 .current_dir(project.root().deref())
109 .env("PATH", paths.path_prepended().joined())
110 .env("LUA_PATH", paths.package_path().joined())
111 .env("LUA_CPATH", paths.package_cpath().joined())
112 .status()
113 .await?
114 .code()
115 {
116 Some(0) => Ok(()),
117 code => Err(RunLuaError::LuaCommandNonZeroExitCode {
118 lua_cmd: command.to_string(),
119 exit_code: code,
120 }
121 .into()),
122 }
123}
124
125pub async fn run(
126 project: &Project,
127 extra_args: &[String],
128 config: &Config,
129) -> Result<(), RunError> {
130 let toml = project.toml().into_local()?;
131
132 let run_spec = toml
133 .run()
134 .ok_or(RunError::NoRunField)?
135 .current_platform()
136 .clone();
137
138 let mut args = run_spec.args.unwrap_or_default();
139
140 if !extra_args.is_empty() {
141 args.extend(extra_args.iter().cloned());
142 }
143
144 match &run_spec.command {
145 Some(command) => run_with_command(project, command, &args, config).await,
146 None => run_with_local_lua(project, &args, config).await,
147 }
148}