lux_lib/build/
command.rs

1use shell_words::split;
2use std::{
3    collections::HashMap,
4    io,
5    path::Path,
6    process::{ExitStatus, Stdio},
7};
8use thiserror::Error;
9use tokio::process::Command;
10
11use crate::{
12    config::Config,
13    lua_installation::LuaInstallation,
14    lua_rockspec::{Build, BuildInfo, CommandBuildSpec},
15    progress::{Progress, ProgressBar},
16    tree::{RockLayout, Tree},
17    variables::VariableSubstitutionError,
18};
19
20use super::external_dependency::ExternalDependencyInfo;
21use super::utils;
22
23#[derive(Error, Debug)]
24pub enum CommandError {
25    #[error("'build_command' and 'install_command' cannot be empty.")]
26    EmptyCommand,
27    #[error("error parsing command:\n{command}\n\nerror: {err}")]
28    ParseError {
29        err: shell_words::ParseError,
30        command: String,
31    },
32    #[error("error executing command:\n{command}\n\nerror: {err}")]
33    Io { err: io::Error, command: String },
34    #[error("failed to execute command:\n{command}\n\nstatus: {status}\nstdout: {stdout}\nstderr: {stderr}")]
35    CommandFailure {
36        command: String,
37        status: ExitStatus,
38        stdout: String,
39        stderr: String,
40    },
41    #[error(transparent)]
42    VariableSubstitutionError(#[from] VariableSubstitutionError),
43}
44
45impl Build for CommandBuildSpec {
46    type Err = CommandError;
47
48    async fn run(
49        self,
50        output_paths: &RockLayout,
51        no_install: bool,
52        lua: &LuaInstallation,
53        external_dependencies: &HashMap<String, ExternalDependencyInfo>,
54        config: &Config,
55        _tree: &Tree,
56        build_dir: &Path,
57        progress: &Progress<ProgressBar>,
58    ) -> Result<BuildInfo, Self::Err> {
59        progress.map(|bar| bar.set_message("Running build_command..."));
60        run_command(
61            &self.build_command,
62            output_paths,
63            lua,
64            external_dependencies,
65            config,
66            build_dir,
67        )
68        .await?;
69        if !no_install {
70            progress.map(|bar| bar.set_message("Running install_command..."));
71            run_command(
72                &self.install_command,
73                output_paths,
74                lua,
75                external_dependencies,
76                config,
77                build_dir,
78            )
79            .await?;
80        }
81        Ok(BuildInfo::default())
82    }
83}
84
85async fn run_command(
86    command: &str,
87    output_paths: &RockLayout,
88    lua: &LuaInstallation,
89    external_dependencies: &HashMap<String, ExternalDependencyInfo>,
90    config: &Config,
91    build_dir: &Path,
92) -> Result<(), CommandError> {
93    let substituted_cmd =
94        utils::substitute_variables(command, output_paths, lua, external_dependencies, config)?;
95    let cmd_parts = split(&substituted_cmd).map_err(|err| CommandError::ParseError {
96        err,
97        command: substituted_cmd.clone(),
98    })?;
99    let (program, args) = cmd_parts.split_first().ok_or(CommandError::EmptyCommand)?;
100    match Command::new(program)
101        .args(args)
102        .current_dir(build_dir)
103        .stdout(Stdio::piped())
104        .stderr(Stdio::piped())
105        .spawn()
106    {
107        Err(err) => {
108            return Err(CommandError::Io {
109                err,
110                command: substituted_cmd,
111            })
112        }
113        Ok(child) => match child.wait_with_output().await {
114            Ok(output) if output.status.success() => {}
115            Ok(output) => {
116                return Err(CommandError::CommandFailure {
117                    command: substituted_cmd,
118                    status: output.status,
119                    stdout: String::from_utf8_lossy(&output.stdout).into(),
120                    stderr: String::from_utf8_lossy(&output.stderr).into(),
121                });
122            }
123            Err(err) => {
124                return Err(CommandError::Io {
125                    err,
126                    command: substituted_cmd,
127                })
128            }
129        },
130    }
131    Ok(())
132}