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