lux_lib/operations/
exec.rs1use std::{io, process::ExitStatus};
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 execute `{name}`.\n\n{status}\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}")]
65 RunCommandFailure {
66 name: String,
67 status: ExitStatus,
68 stdout: String,
69 stderr: String,
70 },
71 #[error(transparent)]
72 LuaVersionUnset(#[from] LuaVersionUnset),
73 #[error(transparent)]
74 Tree(#[from] TreeError),
75 #[error(transparent)]
76 Paths(#[from] PathsError),
77 #[error(transparent)]
78 LuaVersionError(#[from] LuaVersionError),
79 #[error(transparent)]
80 ProjectTreeError(#[from] ProjectTreeError),
81 #[error("failed to execute `{0}`:\n{1}")]
82 Io(String, io::Error),
83}
84
85async fn exec(run: Exec<'_>) -> Result<(), ExecError> {
86 let lua_version = run
87 .project
88 .map(|project| project.lua_version(run.config))
89 .transpose()?
90 .unwrap_or(LuaVersion::from(run.config)?.clone());
91
92 let user_tree = run.config.user_tree(lua_version)?;
93 let mut paths = Paths::new(&user_tree)?;
94
95 if let Some(project) = run.project {
96 paths.prepend(&Paths::new(&project.tree(run.config)?)?);
97 }
98
99 let lua_init = if run.disable_loader.unwrap_or(false) {
100 None
101 } else if user_tree.version().lux_lib_dir().is_none() {
102 eprintln!(
103 "⚠️ WARNING: lux-lua library not found.
104 Cannot use the `lux.loader`.
105 To suppress this warning, set the `--no-loader` option.
106 "
107 );
108 None
109 } else {
110 Some(paths.init())
111 };
112
113 match Command::new(run.command)
114 .args(run.args)
115 .env("PATH", paths.path_prepended().joined())
116 .env("LUA_INIT", lua_init.unwrap_or_default())
117 .env("LUA_PATH", paths.package_path().joined())
118 .env("LUA_CPATH", paths.package_cpath().joined())
119 .output()
120 .await
121 {
122 Ok(output) if output.status.success() => Ok(()),
123 Ok(output) => Err(ExecError::RunCommandFailure {
124 name: run.command.to_string(),
125 status: output.status,
126 stdout: String::from_utf8_lossy(&output.stdout).into(),
127 stderr: String::from_utf8_lossy(&output.stderr).into(),
128 }),
129 Err(err) => Err(ExecError::Io(run.command.to_string(), err)),
130 }
131}
132
133#[derive(Error, Debug)]
134#[error(transparent)]
135pub enum InstallCmdError {
136 InstallError(#[from] InstallError),
137 PackageVersionReqError(#[from] PackageVersionReqError),
138 RemotePackageDBError(#[from] RemotePackageDBError),
139 Tree(#[from] TreeError),
140 LuaVersionUnset(#[from] LuaVersionUnset),
141}
142
143pub async fn install_command(command: &str, config: &Config) -> Result<(), InstallCmdError> {
146 let install_spec = PackageInstallSpec::new(
147 PackageReq::new(command.into(), None)?,
148 tree::EntryType::Entrypoint,
149 )
150 .build();
151 let tree = config.user_tree(LuaVersion::from(config)?.clone())?;
152 Install::new(config)
153 .package(install_spec)
154 .tree(tree)
155 .install()
156 .await?;
157 Ok(())
158}