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
36impl<State: exec_builder::State> ExecBuilder<'_, State> {
37 pub fn arg(mut self, arg: impl Into<String>) -> Self {
38 self.args.push(arg.into());
39 self
40 }
41
42 pub fn args(mut self, args: impl IntoIterator<Item: Into<String>>) -> Self {
43 self.args.extend(args.into_iter().map_into());
44 self
45 }
46
47 pub async fn exec(self) -> Result<(), ExecError>
48 where
49 State: exec_builder::IsComplete,
50 {
51 exec(self._exec()).await
52 }
53}
54
55#[derive(Error, Debug)]
56pub enum ExecError {
57 #[error("failed to execute `{name}`.\n\n{status}\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}")]
58 RunCommandFailure {
59 name: String,
60 status: ExitStatus,
61 stdout: String,
62 stderr: String,
63 },
64 #[error(transparent)]
65 LuaVersionUnset(#[from] LuaVersionUnset),
66 #[error(transparent)]
67 Tree(#[from] TreeError),
68 #[error(transparent)]
69 Paths(#[from] PathsError),
70 #[error(transparent)]
71 LuaVersionError(#[from] LuaVersionError),
72 #[error(transparent)]
73 ProjectTreeError(#[from] ProjectTreeError),
74 #[error("failed to execute `{0}`:\n{1}")]
75 Io(String, io::Error),
76}
77
78async fn exec(run: Exec<'_>) -> Result<(), ExecError> {
79 let lua_version = run
80 .project
81 .map(|project| project.lua_version(run.config))
82 .transpose()?
83 .unwrap_or(LuaVersion::from(run.config)?.clone());
84
85 let user_tree = run.config.user_tree(lua_version)?;
86 let mut paths = Paths::new(&user_tree)?;
87
88 if let Some(project) = run.project {
89 paths.prepend(&Paths::new(&project.tree(run.config)?)?);
90 }
91
92 match Command::new(run.command)
93 .args(run.args)
94 .env("PATH", paths.path_prepended().joined())
95 .env("LUA_PATH", paths.package_path().joined())
96 .env("LUA_CPATH", paths.package_cpath().joined())
97 .output()
98 .await
99 {
100 Ok(output) if output.status.success() => Ok(()),
101 Ok(output) => Err(ExecError::RunCommandFailure {
102 name: run.command.to_string(),
103 status: output.status,
104 stdout: String::from_utf8_lossy(&output.stdout).into(),
105 stderr: String::from_utf8_lossy(&output.stderr).into(),
106 }),
107 Err(err) => Err(ExecError::Io(run.command.to_string(), err)),
108 }
109}
110
111#[derive(Error, Debug)]
112#[error(transparent)]
113pub enum InstallCmdError {
114 InstallError(#[from] InstallError),
115 PackageVersionReqError(#[from] PackageVersionReqError),
116 RemotePackageDBError(#[from] RemotePackageDBError),
117 Tree(#[from] TreeError),
118 LuaVersionUnset(#[from] LuaVersionUnset),
119}
120
121pub async fn install_command(command: &str, config: &Config) -> Result<(), InstallCmdError> {
124 let install_spec = PackageInstallSpec::new(
125 PackageReq::new(command.into(), None)?,
126 tree::EntryType::Entrypoint,
127 )
128 .build();
129 let tree = config.user_tree(LuaVersion::from(config)?.clone())?;
130 Install::new(config)
131 .package(install_spec)
132 .tree(tree)
133 .install()
134 .await?;
135 Ok(())
136}