alpm_pkgbuild/bridge/
mod.rs1pub(crate) mod parser;
4
5use std::{
6 io::ErrorKind,
7 path::Path,
8 process::{Command, Stdio},
9};
10
11use log::debug;
12pub use parser::{BridgeOutput, ClearableValue, Keyword, RawPackageName, Value};
13use which::which;
14
15use crate::error::Error;
16
17const DEFAULT_SCRIPT_NAME: &str = "alpm-pkgbuild-bridge";
18
19pub fn run_bridge_script(pkgbuild_path: &Path) -> Result<String, Error> {
59 if !pkgbuild_path.exists() {
61 let source = std::io::Error::new(ErrorKind::NotFound, "No such file or directory.");
62 return Err(Error::IoPath {
63 path: pkgbuild_path.to_path_buf(),
64 context: "checking for PKGBUILD",
65 source,
66 });
67 }
68
69 let Some(filename) = pkgbuild_path.file_name() else {
71 return Err(Error::InvalidFile {
72 path: pkgbuild_path.to_owned(),
73 context: "No filename provided in path",
74 });
75 };
76
77 let metadata = pkgbuild_path.metadata().map_err(|source| Error::IoPath {
79 path: pkgbuild_path.to_owned(),
80 context: "getting metadata of file",
81 source,
82 })?;
83 if !metadata.file_type().is_file() {
84 return Err(Error::InvalidFile {
85 path: pkgbuild_path.to_owned(),
86 context: "Path doesn't point to a file.",
87 });
88 };
89
90 let script_path = which(DEFAULT_SCRIPT_NAME).map_err(|source| Error::ScriptNotFound {
91 script_name: DEFAULT_SCRIPT_NAME.to_string(),
92 source,
93 })?;
94
95 let mut command = Command::new(script_path);
96 if let Some(parent) = pkgbuild_path.parent() {
98 if parent != Path::new("") {
100 command.current_dir(parent);
101 }
102 }
103
104 let parameters = vec![filename.to_string_lossy().to_string()];
105 command.args(¶meters);
106
107 command.stdout(Stdio::piped());
108 command.stderr(Stdio::piped());
109
110 debug!(
111 "Spawning command '{DEFAULT_SCRIPT_NAME} {}'",
112 parameters.join(" ")
113 );
114 let child = command.spawn().map_err(|source| Error::ScriptError {
115 context: "spawn",
116 parameters: parameters.clone(),
117 source,
118 })?;
119
120 debug!("Waiting for '{DEFAULT_SCRIPT_NAME}' to finish");
121 let output = child
122 .wait_with_output()
123 .map_err(|source| Error::ScriptError {
124 context: "finish",
125 parameters: parameters.clone(),
126 source,
127 })?;
128
129 if !output.status.success() {
130 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
131 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
132 return Err(Error::ScriptExecutionError {
133 parameters,
134 stdout,
135 stderr,
136 });
137 }
138
139 String::from_utf8(output.stdout).map_err(Error::from)
140}
141
142#[cfg(test)]
143mod tests {
144 use std::{fs::File, io::Write};
145
146 use tempfile::tempdir;
147 use testresult::{TestError, TestResult};
148
149 use super::*;
150
151 #[test]
153 fn fail_on_directory() -> TestResult {
154 let tempdir = tempdir()?;
156 let temp_path = tempdir.path();
157
158 let result = run_bridge_script(temp_path);
159 let Err(error) = result else {
160 return Err(TestError::from(
161 "Expected an error, got {result:?} instead.",
162 ));
163 };
164
165 let Error::InvalidFile { path, context } = error else {
166 return Err(TestError::from(format!(
167 "Expected an InvalidFile error, got {error:?} instead."
168 )));
169 };
170
171 assert_eq!(temp_path, path);
172 assert_eq!(context, "Path doesn't point to a file.");
173
174 Ok(())
175 }
176
177 #[test]
179 fn fail_on_missing_file() -> TestResult {
180 let tempdir = tempdir()?;
182 let temp_path = tempdir.path().join("Nonexistent");
183
184 let result = run_bridge_script(&temp_path);
185 let Err(error) = result else {
186 return Err(TestError::from(
187 "Expected an error, got {result:?} instead.",
188 ));
189 };
190
191 let Error::IoPath { path, context, .. } = error else {
192 return Err(TestError::from(format!(
193 "Expected an IoPath error, got {error:?} instead."
194 )));
195 };
196
197 assert_eq!(temp_path, path);
198 assert_eq!(context, "checking for PKGBUILD");
199
200 Ok(())
201 }
202
203 #[test]
205 fn fail_on_no_filename() -> TestResult {
206 let tempdir = tempdir()?;
208 let temp_path = tempdir.path().join("..");
210
211 let result = run_bridge_script(&temp_path);
212 let Err(error) = result else {
213 return Err(TestError::from(
214 "Expected an error, got {result:?} instead.",
215 ));
216 };
217
218 let Error::InvalidFile { path, context } = error else {
219 return Err(TestError::from(format!(
220 "Expected an InvalidFile error, got {error:?} instead."
221 )));
222 };
223
224 assert_eq!(temp_path, path);
225 assert_eq!(context, "No filename provided in path");
226
227 Ok(())
228 }
229
230 #[test]
232 fn fail_on_bridge_failure() -> TestResult {
233 let tempdir = tempdir()?;
235 let temp_path = tempdir.path().join("PKGBUILd");
236
237 let mut file = File::create_new(&temp_path)?;
238 file.write_all("<->#!%@!Definitely some invalid bash syntax.".as_bytes())?;
239
240 let result = run_bridge_script(&temp_path);
241 let Err(error) = result else {
242 return Err(TestError::from(
243 "Expected an error, got {result:?} instead.",
244 ));
245 };
246
247 let Error::ScriptExecutionError { .. } = error else {
248 return Err(TestError::from(format!(
249 "Expected an ScriptExecutionError error, got {error:?} instead."
250 )));
251 };
252
253 Ok(())
254 }
255}