Skip to main content

apple_bindgen/deps/
analyzer.rs

1//! Analyze framework dependencies from umbrella headers.
2
3use regex::Regex;
4use std::collections::HashSet;
5use std::path::Path;
6
7/// Analyzes framework dependencies by parsing umbrella headers.
8pub struct DependencyAnalyzer {
9    sdk_path: std::path::PathBuf,
10}
11
12impl DependencyAnalyzer {
13    /// Create a new analyzer for the given SDK path.
14    pub fn new(sdk_path: impl AsRef<Path>) -> Self {
15        Self {
16            sdk_path: sdk_path.as_ref().to_path_buf(),
17        }
18    }
19
20    /// Get the path to a framework's umbrella header.
21    pub fn umbrella_header_path(&self, framework: &str) -> std::path::PathBuf {
22        self.sdk_path
23            .join("System/Library/Frameworks")
24            .join(format!("{}.framework", framework))
25            .join("Headers")
26            .join(format!("{}.h", framework))
27    }
28
29    /// Analyze dependencies of a framework by parsing its umbrella header.
30    ///
31    /// Returns a list of framework names that this framework imports.
32    pub fn analyze(&self, framework: &str) -> std::io::Result<Vec<String>> {
33        let header_path = self.umbrella_header_path(framework);
34        let content = std::fs::read_to_string(&header_path)?;
35        Ok(extract_framework_imports(&content, framework))
36    }
37}
38
39/// Extract framework names from import statements in header content.
40///
41/// Parses:
42/// - `#import <Foundation/Foundation.h>` → "Foundation"
43/// - `#include <CoreFoundation/CoreFoundation.h>` → "CoreFoundation"
44/// - `@import Foundation;` → "Foundation"
45fn extract_framework_imports(content: &str, self_framework: &str) -> Vec<String> {
46    let mut frameworks = HashSet::new();
47
48    // #import <Framework/...> or #include <Framework/...>
49    let import_re = Regex::new(r#"#(?:import|include)\s+<(\w+)/"#).unwrap();
50    for cap in import_re.captures_iter(content) {
51        let fw = &cap[1];
52        if fw != self_framework {
53            frameworks.insert(fw.to_string());
54        }
55    }
56
57    // @import Framework;
58    let module_re = Regex::new(r"@import\s+(\w+)\s*;").unwrap();
59    for cap in module_re.captures_iter(content) {
60        let fw = &cap[1];
61        if fw != self_framework {
62            frameworks.insert(fw.to_string());
63        }
64    }
65
66    let mut result: Vec<_> = frameworks.into_iter().collect();
67    result.sort();
68    result
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_extract_import() {
77        let content = r#"
78            #import <Foundation/Foundation.h>
79            #import <CoreGraphics/CoreGraphics.h>
80            #import <AppKit/NSView.h>
81        "#;
82        let deps = extract_framework_imports(content, "AppKit");
83        assert!(deps.contains(&"Foundation".to_string()));
84        assert!(deps.contains(&"CoreGraphics".to_string()));
85        // Should not include self
86        assert!(!deps.contains(&"AppKit".to_string()));
87    }
88
89    #[test]
90    fn test_extract_include() {
91        let content = r#"
92            #include <CoreFoundation/CoreFoundation.h>
93        "#;
94        let deps = extract_framework_imports(content, "Foundation");
95        assert!(deps.contains(&"CoreFoundation".to_string()));
96    }
97
98    #[test]
99    fn test_extract_module_import() {
100        let content = r#"
101            @import Foundation;
102            @import CoreGraphics;
103        "#;
104        let deps = extract_framework_imports(content, "AppKit");
105        assert!(deps.contains(&"Foundation".to_string()));
106        assert!(deps.contains(&"CoreGraphics".to_string()));
107    }
108}