Skip to main content

compiletest/
debuggers.rs

1use std::process::Command;
2
3use camino::Utf8Path;
4use semver::Version;
5
6pub(crate) fn query_cdb_version(cdb: &Utf8Path) -> Option<[u16; 4]> {
7    let mut version = None;
8    if let Ok(output) = Command::new(cdb).arg("/version").output() {
9        if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
10            version = extract_cdb_version(&first_line);
11        }
12    }
13    version
14}
15
16pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
17    // Example full_version_line: "cdb version 10.0.18362.1"
18    let version = full_version_line.rsplit(' ').next()?;
19    let mut components = version.split('.');
20    let major: u16 = components.next().unwrap().parse().unwrap();
21    let minor: u16 = components.next().unwrap().parse().unwrap();
22    let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
23    let build: u16 = components.next().unwrap_or("0").parse().unwrap();
24    Some([major, minor, patch, build])
25}
26
27pub(crate) fn query_gdb_version(gdb: &Utf8Path) -> Option<u32> {
28    let mut version_line = None;
29    if let Ok(output) = Command::new(&gdb).arg("--version").output() {
30        if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
31            version_line = Some(first_line.to_string());
32        }
33    }
34
35    let version = match version_line {
36        Some(line) => extract_gdb_version(&line),
37        None => return None,
38    };
39
40    version
41}
42
43pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
44    let full_version_line = full_version_line.trim();
45
46    // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
47    // of the ? sections being optional
48
49    // We will parse up to 3 digits for each component, ignoring the date
50
51    // We skip text in parentheses.  This avoids accidentally parsing
52    // the openSUSE version, which looks like:
53    //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
54    // This particular form is documented in the GNU coding standards:
55    // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
56
57    let unbracketed_part = full_version_line.split('[').next().unwrap();
58    let mut splits = unbracketed_part.trim_end().rsplit(' ');
59    let version_string = splits.next().unwrap();
60
61    let mut splits = version_string.split('.');
62    let major = splits.next().unwrap();
63    let minor = splits.next().unwrap();
64    let patch = splits.next();
65
66    let major: u32 = major.parse().unwrap();
67    let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
68        None => {
69            let minor = minor.parse().unwrap();
70            let patch: u32 = match patch {
71                Some(patch) => match patch.find(not_a_digit) {
72                    None => patch.parse().unwrap(),
73                    Some(idx) if idx > 3 => 0,
74                    Some(idx) => patch[..idx].parse().unwrap(),
75                },
76                None => 0,
77            };
78            (minor, patch)
79        }
80        // There is no patch version after minor-date (e.g. "4-2012").
81        Some(idx) => {
82            let minor = minor[..idx].parse().unwrap();
83            (minor, 0)
84        }
85    };
86
87    Some(((major * 1000) + minor) * 1000 + patch)
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub(crate) enum LldbVersion {
92    /// LLDB distributed by Apple as part of Xcode. Uses a unique versioning scheme that does not
93    /// match LLVM's LLDB.
94    Apple([u64; 4]),
95    /// LLDB distributed by LLVM, uses traditional semver.
96    Llvm(Version),
97}
98
99impl LldbVersion {
100    /// Takes a string consisting of 1-4 `.`-separated numbers and returns an `LldbVersion::Apple`.
101    ///
102    /// If a number fails to parse, that section and any following sections are silently converted
103    /// to `0` (e.g. `"15.6.asdf.3"` -> `[15, 6, 0, 0]`)
104    pub(crate) fn apple_from_str(version_num: &str) -> Self {
105        let mut ver: [u64; 4] = [0; 4];
106
107        for (i, val) in version_num.split('.').enumerate().take_while(|(i, _)| *i < 4) {
108            if let Ok(part) = val.parse::<u64>() {
109                ver[i] = part;
110            } else {
111                eprintln!(
112                    "Warning: Invalid LLDB version format: '{version_num}'. Falling back to version '{ver:?}'"
113                );
114                break;
115            }
116        }
117
118        Self::Apple(ver)
119    }
120
121    /// Takes a string consisting of 1-3 `.`-separated numbers and returns an `LldbVersion::Llvm`.
122    ///
123    /// If a number fails to parse, that section and any following sections are silently converted
124    /// to `0` (e.g. `"15.asdf.3"` -> `[15, 0, 0]`)
125    pub(crate) fn llvm_from_str(version_num: &str) -> Self {
126        let mut ver: [u64; 3] = [0; 3];
127
128        for (i, val) in version_num.split('.').enumerate().take_while(|(i, _)| *i < 3) {
129            if let Ok(part) = val.parse::<u64>() {
130                ver[i] = part;
131            } else {
132                eprintln!(
133                    "Warning: Invalid LLDB version number format: '{version_num}'. Falling back to version '{ver:?}'"
134                );
135                break;
136            }
137        }
138
139        Self::Llvm(Version::new(ver[0], ver[1], ver[2]))
140    }
141}
142
143/// Returns LLDB version
144pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<LldbVersion> {
145    // Extract the major LLDB version from the given version string.
146    // LLDB version strings are different for Apple and non-Apple platforms.
147    // The Apple variant looks like this:
148    //
149    // LLDB-179.5 (older versions)
150    // lldb-300.2.51 (new versions)
151    // lldb-1703.0.236.21 (even newer versions)
152    //
153    // LLVM versions look like:
154    // lldb version 6.0.1
155    //
156    // There doesn't seem to be a way to correlate the Apple version
157    // with the upstream version.
158
159    let full_version_line = full_version_line.trim();
160
161    if let Some(apple_str) =
162        full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
163    {
164        let version_str = apple_str.split_whitespace().next()?;
165
166        return Some(LldbVersion::apple_from_str(version_str));
167    }
168
169    if let Some(lldb_str) = full_version_line.strip_prefix("lldb version ") {
170        let version_str = lldb_str.split_whitespace().next()?;
171
172        return Some(LldbVersion::llvm_from_str(version_str));
173    }
174    None
175}
176
177fn not_a_digit(c: char) -> bool {
178    !c.is_ascii_digit()
179}