lux-lib 0.12.0

Library for the lux package manager for Lua
Documentation
use shell_words::split;
use std::{
    collections::HashMap,
    io,
    path::Path,
    process::{ExitStatus, Stdio},
};
use thiserror::Error;
use tokio::process::Command;

use crate::{
    build::backend::{BuildBackend, BuildInfo, RunBuildArgs},
    config::Config,
    lua_installation::LuaInstallation,
    lua_rockspec::CommandBuildSpec,
    tree::RockLayout,
    variables::VariableSubstitutionError,
};

use super::external_dependency::ExternalDependencyInfo;
use super::utils;

#[derive(Error, Debug)]
pub enum CommandError {
    #[error("'build_command' and 'install_command' cannot be empty.")]
    EmptyCommand,
    #[error("error parsing command:\n{command}\n\nerror: {err}")]
    ParseError {
        err: shell_words::ParseError,
        command: String,
    },
    #[error("error executing command:\n{command}\n\nerror: {err}")]
    Io { err: io::Error, command: String },
    #[error("failed to execute command:\n{command}\n\nstatus: {status}\nstdout: {stdout}\nstderr: {stderr}")]
    CommandFailure {
        command: String,
        status: ExitStatus,
        stdout: String,
        stderr: String,
    },
    #[error(transparent)]
    VariableSubstitutionError(#[from] VariableSubstitutionError),
}

impl BuildBackend for CommandBuildSpec {
    type Err = CommandError;

    async fn run(self, args: RunBuildArgs<'_>) -> Result<BuildInfo, Self::Err> {
        let output_paths = args.output_paths;
        let no_install = args.no_install;
        let lua = args.lua;
        let external_dependencies = args.external_dependencies;
        let config = args.config;
        let build_dir = args.build_dir;
        let progress = args.progress;

        progress.map(|bar| bar.set_message("Running build_command..."));
        if let Some(build_command) = &self.build_command {
            progress.map(|bar| bar.set_message("Running build_command..."));
            run_command(
                build_command,
                output_paths,
                lua,
                external_dependencies,
                config,
                build_dir,
            )
            .await?;
        }
        if !no_install {
            if let Some(install_command) = &self.install_command {
                progress.map(|bar| bar.set_message("Running install_command..."));
                run_command(
                    install_command,
                    output_paths,
                    lua,
                    external_dependencies,
                    config,
                    build_dir,
                )
                .await?;
            }
        }
        Ok(BuildInfo::default())
    }
}

async fn run_command(
    command: &str,
    output_paths: &RockLayout,
    lua: &LuaInstallation,
    external_dependencies: &HashMap<String, ExternalDependencyInfo>,
    config: &Config,
    build_dir: &Path,
) -> Result<(), CommandError> {
    let substituted_cmd =
        utils::substitute_variables(command, output_paths, lua, external_dependencies, config)?;
    let cmd_parts = split(&substituted_cmd).map_err(|err| CommandError::ParseError {
        err,
        command: substituted_cmd.clone(),
    })?;
    let (program, args) = cmd_parts.split_first().ok_or(CommandError::EmptyCommand)?;
    match Command::new(program)
        .args(args)
        .current_dir(build_dir)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
    {
        Err(err) => {
            return Err(CommandError::Io {
                err,
                command: substituted_cmd,
            })
        }
        Ok(child) => match child.wait_with_output().await {
            Ok(output) if output.status.success() => {}
            Ok(output) => {
                return Err(CommandError::CommandFailure {
                    command: substituted_cmd,
                    status: output.status,
                    stdout: String::from_utf8_lossy(&output.stdout).into(),
                    stderr: String::from_utf8_lossy(&output.stderr).into(),
                });
            }
            Err(err) => {
                return Err(CommandError::Io {
                    err,
                    command: substituted_cmd,
                })
            }
        },
    }
    Ok(())
}