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::Script {
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.wait_with_output().map_err(|source| Error::Script {
123 context: t!("error-script-finish"),
124 parameters: parameters.clone(),
125 source,
126 })?;
127
128 if !output.status.success() {
129 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
130 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
131 return Err(Error::ScriptExecution {
132 parameters,
133 stdout,
134 stderr,
135 });
136 }
137
138 String::from_utf8(output.stdout).map_err(Error::from)
139}
140
141#[cfg(test)]
142mod tests {
143 use std::{fs::File, io::Write};
144
145 use tempfile::tempdir;
146 use testresult::TestResult;
147
148 use super::*;
149
150 #[test]
152 fn fail_on_directory() -> TestResult {
153 let tempdir = tempdir()?;
155 let temp_path = tempdir.path();
156
157 let result = run_bridge_script(temp_path);
158 let Err(error) = result else {
159 panic!("Expected an error, got {result:?} instead.");
160 };
161
162 let Error::InvalidFile { path, context } = error else {
163 panic!("Expected an InvalidFile error, got {error:?} instead.");
164 };
165
166 assert_eq!(temp_path, path);
167 assert_eq!(context, "Path doesn't point to a file");
168
169 Ok(())
170 }
171
172 #[test]
174 fn fail_on_missing_file() -> TestResult {
175 let tempdir = tempdir()?;
177 let temp_path = tempdir.path().join("Nonexistent");
178
179 let result = run_bridge_script(&temp_path);
180 let Err(error) = result else {
181 panic!("Expected an error, got {result:?} instead.");
182 };
183
184 let Error::IoPath { path, context, .. } = error else {
185 panic!("Expected an IoPath error, got {error:?} instead.");
186 };
187
188 assert_eq!(temp_path, path);
189 assert_eq!(context, "checking for PKGBUILD");
190
191 Ok(())
192 }
193
194 #[test]
196 fn fail_on_no_filename() -> TestResult {
197 let tempdir = tempdir()?;
199 let temp_path = tempdir.path().join("..");
201
202 let result = run_bridge_script(&temp_path);
203 let Err(error) = result else {
204 panic!("Expected an error, got {result:?} instead.");
205 };
206
207 let Error::InvalidFile { path, context } = error else {
208 panic!("Expected an InvalidFile error, got {error:?} instead.");
209 };
210
211 assert_eq!(temp_path, path);
212 assert_eq!(context, "No filename provided in path");
213
214 Ok(())
215 }
216
217 #[test]
219 fn fail_on_bridge_failure() -> TestResult {
220 let tempdir = tempdir()?;
222 let temp_path = tempdir.path().join("PKGBUILd");
223
224 let mut file = File::create_new(&temp_path)?;
225 file.write_all("<->#!%@!Definitely some invalid bash syntax.".as_bytes())?;
226
227 let result = run_bridge_script(&temp_path);
228 let Err(error) = result else {
229 panic!("Expected an error, got {result:?} instead.");
230 };
231
232 let Error::ScriptExecution { .. } = error else {
233 panic!("Expected an ScriptExecutionError error, got {error:?} instead.");
234 };
235
236 Ok(())
237 }
238}