Skip to main content

normalize_languages/
c_cpp.rs

1//! Shared C/C++ external package resolution.
2
3use crate::external_packages::ResolvedPackage;
4use std::path::PathBuf;
5use std::process::Command;
6
7/// Get GCC version.
8pub fn get_gcc_version() -> Option<String> {
9    let output = Command::new("gcc").args(["--version"]).output().ok()?;
10
11    if output.status.success() {
12        let version_str = String::from_utf8_lossy(&output.stdout);
13        // "gcc (GCC) 13.2.0" or "gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
14        for line in version_str.lines() {
15            // Look for version number pattern
16            for part in line.split_whitespace() {
17                if part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
18                    let ver_parts: Vec<&str> = part.split('.').collect();
19                    if ver_parts.len() >= 2 {
20                        return Some(format!("{}.{}", ver_parts[0], ver_parts[1]));
21                    }
22                }
23            }
24        }
25    }
26
27    // Try clang as fallback
28    let output = Command::new("clang").args(["--version"]).output().ok()?;
29    if output.status.success() {
30        let version_str = String::from_utf8_lossy(&output.stdout);
31        for line in version_str.lines() {
32            if line.contains("clang version") {
33                for part in line.split_whitespace() {
34                    if part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
35                        let ver_parts: Vec<&str> = part.split('.').collect();
36                        if ver_parts.len() >= 2 {
37                            return Some(format!("{}.{}", ver_parts[0], ver_parts[1]));
38                        }
39                    }
40                }
41            }
42        }
43    }
44
45    None
46}
47
48/// Find C/C++ system include directories.
49pub fn find_cpp_include_paths() -> Vec<PathBuf> {
50    let mut paths = Vec::new();
51
52    // Standard system include paths
53    let system_paths = [
54        "/usr/include",
55        "/usr/local/include",
56        "/usr/include/c++",
57        "/usr/include/x86_64-linux-gnu",
58        "/usr/include/aarch64-linux-gnu",
59    ];
60
61    for path in system_paths {
62        let p = PathBuf::from(path);
63        if p.is_dir() {
64            paths.push(p);
65        }
66    }
67
68    // Try to get GCC include paths
69    if let Ok(output) = Command::new("gcc")
70        .args(["-E", "-Wp,-v", "-xc", "/dev/null"])
71        .output()
72    {
73        let stderr = String::from_utf8_lossy(&output.stderr);
74        let mut in_search_list = false;
75
76        for line in stderr.lines() {
77            if line.contains("#include <...> search starts here:") {
78                in_search_list = true;
79                continue;
80            }
81            if line.contains("End of search list.") {
82                break;
83            }
84            if in_search_list {
85                let path = PathBuf::from(line.trim());
86                if path.is_dir() && !paths.contains(&path) {
87                    paths.push(path);
88                }
89            }
90        }
91    }
92
93    // Try clang as well
94    if let Ok(output) = Command::new("clang")
95        .args(["-E", "-Wp,-v", "-xc", "/dev/null"])
96        .output()
97    {
98        let stderr = String::from_utf8_lossy(&output.stderr);
99        let mut in_search_list = false;
100
101        for line in stderr.lines() {
102            if line.contains("#include <...> search starts here:") {
103                in_search_list = true;
104                continue;
105            }
106            if line.contains("End of search list.") {
107                break;
108            }
109            if in_search_list {
110                let path = PathBuf::from(line.trim());
111                if path.is_dir() && !paths.contains(&path) {
112                    paths.push(path);
113                }
114            }
115        }
116    }
117
118    // macOS specific paths
119    #[cfg(target_os = "macos")]
120    {
121        // Xcode command line tools
122        let xcode_paths = [
123            "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include",
124            "/Library/Developer/CommandLineTools/usr/include",
125            "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include",
126        ];
127        for path in xcode_paths {
128            let p = PathBuf::from(path);
129            if p.is_dir() && !paths.contains(&p) {
130                paths.push(p);
131            }
132        }
133
134        // Homebrew
135        let homebrew_paths = ["/opt/homebrew/include", "/usr/local/include"];
136        for path in homebrew_paths {
137            let p = PathBuf::from(path);
138            if p.is_dir() && !paths.contains(&p) {
139                paths.push(p);
140            }
141        }
142    }
143
144    paths
145}
146
147/// Resolve a C/C++ include to a header file.
148pub fn resolve_cpp_include(include: &str, include_paths: &[PathBuf]) -> Option<ResolvedPackage> {
149    // Strip angle brackets or quotes
150    let header = include
151        .trim_start_matches('<')
152        .trim_end_matches('>')
153        .trim_start_matches('"')
154        .trim_end_matches('"');
155
156    // Search through include paths
157    for base_path in include_paths {
158        let full_path = base_path.join(header);
159        if full_path.is_file() {
160            return Some(ResolvedPackage {
161                path: full_path,
162                name: header.to_string(),
163                is_namespace: false,
164            });
165        }
166
167        // For C++ standard library, might be without extension
168        if !header.contains('.') {
169            // Try with common extensions
170            for ext in &["", ".h", ".hpp", ".hxx"] {
171                let with_ext = if ext.is_empty() {
172                    base_path.join(header)
173                } else {
174                    base_path.join(format!("{}{}", header, ext))
175                };
176                if with_ext.is_file() {
177                    return Some(ResolvedPackage {
178                        path: with_ext,
179                        name: header.to_string(),
180                        is_namespace: false,
181                    });
182                }
183            }
184        }
185    }
186
187    None
188}