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 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 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}