use std::collections::HashSet;
use std::path::Path;
use std::process::Command;
use crate::state::types::Source;
use super::command::run_command;
pub(super) fn collect_binaries_for_package(
package: &str,
source: &Source,
) -> Result<Vec<String>, String> {
match source {
Source::Official { .. } => {
let output = run_command(
"pacman",
&["-Fl", package],
&format!("pacman -Fl {package}"),
)?;
let binaries = extract_binaries_from_file_list(&output, package);
Ok(binaries)
}
Source::Aur => {
if let Ok(installed_files) = crate::logic::files::get_installed_file_list(package)
&& !installed_files.is_empty()
{
let binaries = extract_binaries_from_file_list(
&installed_files
.iter()
.map(|f| format!("{package} {f}"))
.collect::<Vec<_>>()
.join("\n"),
package,
);
if !binaries.is_empty() {
tracing::debug!(
"Found {} binaries from installed AUR package {}",
binaries.len(),
package
);
return Ok(binaries);
}
}
let has_paru = Command::new("paru").args(["--version"]).output().is_ok();
let has_yay = Command::new("yay").args(["--version"]).output().is_ok();
if has_paru {
tracing::debug!("Trying paru -Fl {} for AUR package binaries", package);
if let Ok(output) = Command::new("paru")
.args(["-Fl", package])
.env("LC_ALL", "C")
.env("LANG", "C")
.output()
&& output.status.success()
{
let text = String::from_utf8_lossy(&output.stdout);
let binaries = extract_binaries_from_file_list(&text, package);
if !binaries.is_empty() {
tracing::debug!(
"Found {} binaries from paru -Fl for {}",
binaries.len(),
package
);
return Ok(binaries);
}
}
}
if has_yay {
tracing::debug!("Trying yay -Fl {} for AUR package binaries", package);
if let Ok(output) = Command::new("yay")
.args(["-Fl", package])
.env("LC_ALL", "C")
.env("LANG", "C")
.output()
&& output.status.success()
{
let text = String::from_utf8_lossy(&output.stdout);
let binaries = extract_binaries_from_file_list(&text, package);
if !binaries.is_empty() {
tracing::debug!(
"Found {} binaries from yay -Fl for {}",
binaries.len(),
package
);
return Ok(binaries);
}
}
}
match crate::logic::files::fetch_pkgbuild_sync(package) {
Ok(pkgbuild) => {
let entry = crate::logic::files::parse_pkgbuild_cached(
package,
None,
crate::logic::files::PkgbuildSourceKind::Aur,
&pkgbuild,
);
let files = entry.install_paths;
let binaries: Vec<String> = files
.into_iter()
.filter(|f| {
f.starts_with("/usr/bin/")
|| f.starts_with("/usr/sbin/")
|| f.starts_with("/bin/")
|| f.starts_with("/sbin/")
|| f.starts_with("/usr/local/bin/")
})
.collect();
if !binaries.is_empty() {
tracing::debug!(
"Found {} binaries from PKGBUILD parsing for {}",
binaries.len(),
package
);
return Ok(binaries);
}
}
Err(e) => {
tracing::debug!("Failed to fetch PKGBUILD for {}: {}", package, e);
}
}
Ok(Vec::new())
}
}
}
pub(super) fn extract_binaries_from_file_list(file_list: &str, package: &str) -> Vec<String> {
const BINARY_PREFIXES: [&str; 5] = [
"/usr/bin/",
"/usr/sbin/",
"/bin/",
"/sbin/",
"/usr/local/bin/",
];
let mut seen = HashSet::new();
let mut binaries = Vec::new();
for line in file_list.lines() {
let Some((pkg, raw_path)) = line.split_once(' ') else {
continue;
};
if pkg != package {
continue;
}
let path = raw_path.strip_suffix('/').unwrap_or(raw_path);
let is_binary = BINARY_PREFIXES
.iter()
.any(|prefix| path.starts_with(prefix));
if is_binary {
if let Some(binary_name) = Path::new(path)
.file_name()
.and_then(|name| name.to_str())
.map(ToString::to_string)
{
if seen.insert(path.to_string()) {
binaries.push(path.to_string());
}
if seen.insert(binary_name.clone()) {
binaries.push(binary_name);
}
}
}
}
binaries
}