1use anyhow::{bail, Context, Result};
4use std::collections::HashSet;
5use std::path::Path;
6use std::process::Command;
7
8use crate::paths::find_library;
9
10#[must_use = "library dependencies should be processed"]
24pub fn get_library_dependencies(binary_path: &Path) -> Result<Vec<String>> {
25 if !binary_path.exists() {
27 bail!("File does not exist: {}", binary_path.display());
28 }
29
30 let output = Command::new("readelf")
31 .args(["-d"])
32 .arg(binary_path)
33 .output()
34 .context("readelf command not found - install binutils")?;
35
36 if !output.status.success() {
37 let stderr = String::from_utf8_lossy(&output.stderr);
38 if stderr.contains("Not an ELF file")
40 || stderr.contains("not a dynamic executable")
41 || stderr.contains("File format not recognized")
42 {
43 return Ok(Vec::new());
44 }
45 bail!(
46 "readelf failed on {}: {}",
47 binary_path.display(),
48 stderr.trim()
49 );
50 }
51
52 let stdout = String::from_utf8_lossy(&output.stdout);
53 parse_readelf_output(&stdout)
54}
55
56pub fn parse_readelf_output(output: &str) -> Result<Vec<String>> {
66 let mut libs = Vec::new();
67
68 for line in output.lines() {
69 if line.contains("(NEEDED)") && line.contains("Shared library:") {
71 if let Some(start) = line.find('[') {
73 if let Some(end) = line.find(']') {
74 let lib_name = &line[start + 1..end];
75 libs.push(lib_name.to_string());
76 }
77 }
78 }
79 }
80
81 Ok(libs)
82}
83
84pub fn get_all_dependencies(
89 source_root: &Path,
90 binary_path: &Path,
91 extra_lib_paths: &[&str],
92) -> Result<HashSet<String>> {
93 let mut all_libs = HashSet::new();
94 let mut to_process = vec![binary_path.to_path_buf()];
95 let mut processed = HashSet::new();
96
97 while let Some(path) = to_process.pop() {
98 if processed.contains(&path) {
99 continue;
100 }
101 processed.insert(path.clone());
102
103 let deps = get_library_dependencies(&path)?;
104 for lib_name in deps {
105 if all_libs.insert(lib_name.clone()) {
106 if let Some(lib_path) = find_library(source_root, &lib_name, extra_lib_paths) {
108 to_process.push(lib_path);
109 }
110 }
111 }
112 }
113
114 Ok(all_libs)
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_parse_readelf_output() {
123 let output = r#"
124Dynamic section at offset 0x2d0e0 contains 28 entries:
125 Tag Type Name/Value
126 0x0000000000000001 (NEEDED) Shared library: [libtinfo.so.6]
127 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
128 0x000000000000000c (INIT) 0x5000
129"#;
130 let libs = parse_readelf_output(output).unwrap();
131 assert_eq!(libs, vec!["libtinfo.so.6", "libc.so.6"]);
132 }
133
134 #[test]
135 fn test_parse_readelf_empty() {
136 let output = "not an ELF file";
137 let libs = parse_readelf_output(output).unwrap();
138 assert!(libs.is_empty());
139 }
140}