lux_lib/build/
make.rs

1use itertools::Itertools;
2use path_slash::PathBufExt;
3use std::{
4    collections::HashMap,
5    io,
6    path::{Path, PathBuf},
7    process::{ExitStatus, Stdio},
8};
9use thiserror::Error;
10use tokio::process::Command;
11
12use crate::{
13    build::utils,
14    config::Config,
15    lua_installation::LuaInstallation,
16    lua_rockspec::{Build, BuildInfo, MakeBuildSpec},
17    progress::{Progress, ProgressBar},
18    tree::{RockLayout, Tree},
19    variables::VariableSubstitutionError,
20};
21
22use super::external_dependency::ExternalDependencyInfo;
23
24#[derive(Error, Debug)]
25pub enum MakeError {
26    #[error("{name} step failed.\n\n{status}\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}")]
27    CommandFailure {
28        name: String,
29        status: ExitStatus,
30        stdout: String,
31        stderr: String,
32    },
33    #[error("failed to run `make` step: {0}")]
34    Io(io::Error),
35    #[error("failed to run `make` step: `{0}` command not found!")]
36    CommandNotFound(String),
37    #[error(transparent)]
38    VariableSubstitutionError(#[from] VariableSubstitutionError),
39    #[error("{0} not found")]
40    MakefileNotFound(PathBuf),
41}
42
43impl Build for MakeBuildSpec {
44    type Err = MakeError;
45
46    async fn run(
47        self,
48        output_paths: &RockLayout,
49        no_install: bool,
50        lua: &LuaInstallation,
51        external_dependencies: &HashMap<String, ExternalDependencyInfo>,
52        config: &Config,
53        _tree: &Tree,
54        build_dir: &Path,
55        _progress: &Progress<ProgressBar>,
56    ) -> Result<BuildInfo, Self::Err> {
57        // Build step
58        if self.build_pass {
59            let build_args = self
60                .variables
61                .iter()
62                .chain(&self.build_variables)
63                .filter(|(_, value)| !value.is_empty())
64                .map(|(key, value)| {
65                    let substituted_value = utils::substitute_variables(
66                        value,
67                        output_paths,
68                        lua,
69                        external_dependencies,
70                        config,
71                    )?;
72                    Ok(format!("{key}={substituted_value}").trim().to_string())
73                })
74                .try_collect::<_, Vec<_>, Self::Err>()?;
75            let mut cmd = Command::new(config.make_cmd());
76            if let Some(build_target) = &self.build_target {
77                cmd.arg(build_target);
78            }
79            match cmd
80                .current_dir(build_dir)
81                .args(["-f", &self.makefile.to_slash_lossy()])
82                .stdout(Stdio::piped())
83                .stderr(Stdio::piped())
84                .args(build_args)
85                .spawn()
86            {
87                Ok(child) => match child.wait_with_output().await {
88                    Ok(output) if output.status.success() => {}
89                    Ok(output) => {
90                        return Err(MakeError::CommandFailure {
91                            name: match self.build_target {
92                                Some(build_target) => {
93                                    format!("{} {}", config.make_cmd(), build_target)
94                                }
95                                None => config.make_cmd(),
96                            },
97
98                            status: output.status,
99                            stdout: String::from_utf8_lossy(&output.stdout).into(),
100                            stderr: String::from_utf8_lossy(&output.stderr).into(),
101                        });
102                    }
103                    Err(err) => return Err(MakeError::Io(err)),
104                },
105                Err(_) => return Err(MakeError::CommandNotFound(config.make_cmd().clone())),
106            }
107        };
108
109        // Install step
110        if self.install_pass && !no_install {
111            let install_args = self
112                .variables
113                .iter()
114                .chain(&self.install_variables)
115                .filter(|(_, value)| !value.is_empty())
116                .map(|(key, value)| {
117                    let substituted_value = utils::substitute_variables(
118                        value,
119                        output_paths,
120                        lua,
121                        external_dependencies,
122                        config,
123                    )?;
124                    Ok(format!("{key}={substituted_value}").trim().to_string())
125                })
126                .try_collect::<_, Vec<_>, Self::Err>()?;
127            match Command::new(config.make_cmd())
128                .current_dir(build_dir)
129                .arg(&self.install_target)
130                .args(["-f", &self.makefile.to_slash_lossy()])
131                .args(install_args)
132                .output()
133                .await
134            {
135                Ok(output) if output.status.success() => {}
136                Ok(output) => {
137                    return Err(MakeError::CommandFailure {
138                        name: format!("{} {}", config.make_cmd(), self.install_target),
139                        status: output.status,
140                        stdout: String::from_utf8_lossy(&output.stdout).into(),
141                        stderr: String::from_utf8_lossy(&output.stderr).into(),
142                    })
143                }
144                Err(err) => return Err(MakeError::Io(err)),
145            }
146        };
147
148        Ok(BuildInfo::default())
149    }
150}