lux_lib/operations/
exec.rs1use std::io;
2use tokio::process::Command;
3
4use crate::{
5 config::{Config, LuaVersion, LuaVersionUnset},
6 lua_rockspec::LuaVersionError,
7 operations::Install,
8 package::{PackageReq, PackageVersionReqError},
9 path::{Paths, PathsError},
10 project::{Project, ProjectTreeError},
11 remote_package_db::RemotePackageDBError,
12 tree::{self, TreeError},
13};
14use bon::Builder;
15use itertools::Itertools;
16use thiserror::Error;
17
18use super::{InstallError, PackageInstallSpec};
19
20#[derive(Builder)]
23#[builder(start_fn = new, finish_fn(name = _exec, vis = ""))]
24pub struct Exec<'a> {
25 #[builder(start_fn)]
26 command: &'a str,
27 #[builder(start_fn)]
28 project: Option<&'a Project>,
29 #[builder(start_fn)]
30 config: &'a Config,
31
32 #[builder(field)]
33 args: Vec<String>,
34
35 disable_loader: Option<bool>,
36}
37
38impl<State: exec_builder::State> ExecBuilder<'_, State> {
39 pub fn arg(mut self, arg: impl Into<String>) -> Self {
40 self.args.push(arg.into());
41 self
42 }
43
44 pub fn args(mut self, args: impl IntoIterator<Item: Into<String>>) -> Self {
45 self.args.extend(args.into_iter().map_into());
46 self
47 }
48}
49
50impl<State> ExecBuilder<'_, State>
51where
52 State: exec_builder::State + exec_builder::IsComplete,
53{
54 pub async fn exec(self) -> Result<(), ExecError>
55 where
56 State: exec_builder::IsComplete,
57 {
58 exec(self._exec()).await
59 }
60}
61
62#[derive(Error, Debug)]
63pub enum ExecError {
64 #[error("failed to run {cmd}: {source}")]
65 RunCommandFailed {
66 cmd: String,
67 #[source]
68 source: io::Error,
69 },
70 #[error("{cmd} exited with non-zero exit code: {}", exit_code.map(|code| code.to_string()).unwrap_or("unknown".into()))]
71 RunCommandNonZeroExitCode { cmd: String, exit_code: Option<i32> },
72 #[error(transparent)]
73 LuaVersionUnset(#[from] LuaVersionUnset),
74 #[error(transparent)]
75 Tree(#[from] TreeError),
76 #[error(transparent)]
77 Paths(#[from] PathsError),
78 #[error(transparent)]
79 LuaVersionError(#[from] LuaVersionError),
80 #[error(transparent)]
81 ProjectTreeError(#[from] ProjectTreeError),
82 #[error("failed to execute `{0}`:\n{1}")]
83 Io(String, io::Error),
84}
85
86async fn exec(run: Exec<'_>) -> Result<(), ExecError> {
87 let lua_version = run
88 .project
89 .map(|project| project.lua_version(run.config))
90 .transpose()?
91 .unwrap_or(LuaVersion::from(run.config)?.clone());
92
93 let user_tree = run.config.user_tree(lua_version)?;
94 let mut paths = Paths::new(&user_tree)?;
95
96 if let Some(project) = run.project {
97 paths.prepend(&Paths::new(&project.tree(run.config)?)?);
98 }
99
100 let lua_init = if run.disable_loader.unwrap_or(false) {
101 None
102 } else if user_tree.version().lux_lib_dir().is_none() {
103 eprintln!(
104 "⚠️ WARNING: lux-lua library not found.
105 Cannot use the `lux.loader`.
106 To suppress this warning, set the `--no-loader` option.
107 "
108 );
109 None
110 } else {
111 Some(paths.init())
112 };
113
114 let status = match Command::new(run.command)
115 .args(run.args)
116 .env("PATH", paths.path_prepended().joined())
117 .env("LUA_INIT", lua_init.unwrap_or_default())
118 .env("LUA_PATH", paths.package_path().joined())
119 .env("LUA_CPATH", paths.package_cpath().joined())
120 .status()
121 .await
122 {
123 Ok(status) => Ok(status),
124 Err(err) => Err(ExecError::RunCommandFailed {
125 cmd: run.command.to_string(),
126 source: err,
127 }),
128 }?;
129 if status.success() {
130 Ok(())
131 } else {
132 Err(ExecError::RunCommandNonZeroExitCode {
133 cmd: run.command.to_string(),
134 exit_code: status.code(),
135 })
136 }
137}
138
139#[derive(Error, Debug)]
140#[error(transparent)]
141pub enum InstallCmdError {
142 InstallError(#[from] InstallError),
143 PackageVersionReqError(#[from] PackageVersionReqError),
144 RemotePackageDBError(#[from] RemotePackageDBError),
145 Tree(#[from] TreeError),
146 LuaVersionUnset(#[from] LuaVersionUnset),
147}
148
149pub async fn install_command(command: &str, config: &Config) -> Result<(), InstallCmdError> {
152 let install_spec = PackageInstallSpec::new(
153 PackageReq::new(command.into(), None)?,
154 tree::EntryType::Entrypoint,
155 )
156 .build();
157 let tree = config.user_tree(LuaVersion::from(config)?.clone())?;
158 Install::new(config)
159 .package(install_spec)
160 .tree(tree)
161 .install()
162 .await?;
163 Ok(())
164}