use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use crate::common::{Config, Debugger};
pub(crate) fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
config.cdb.as_ref()?;
Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
}
pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
config.gdb_version?;
if config.matches_env("msvc") {
return None;
}
if config.remote_test_client.is_some() && !config.target.contains("android") {
println!(
"WARNING: debuginfo tests are not available when \
testing with remote"
);
return None;
}
if config.target.contains("android") {
println!(
"{} debug-info test uses tcp 5039 port.\
please reserve it",
config.target
);
env::set_var("RUST_TEST_THREADS", "1");
}
Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
}
pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
config.lldb_python_dir.as_ref()?;
if let Some(350) = config.lldb_version {
println!(
"WARNING: The used version of LLDB (350) has a \
known issue that breaks debuginfo tests. See \
issue #32520 for more information. Skipping all \
LLDB-based tests!",
);
return None;
}
Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
}
pub(crate) fn is_android_gdb_target(target: &str) -> bool {
matches!(
&target[..],
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
)
}
fn is_pc_windows_msvc_target(target: &str) -> bool {
target.ends_with("-pc-windows-msvc")
}
fn find_cdb(target: &str) -> Option<OsString> {
if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
return None;
}
let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
let cdb_arch = if cfg!(target_arch = "x86") {
"x86"
} else if cfg!(target_arch = "x86_64") {
"x64"
} else if cfg!(target_arch = "aarch64") {
"arm64"
} else if cfg!(target_arch = "arm") {
"arm"
} else {
return None; };
let mut path = PathBuf::new();
path.push(pf86);
path.push(r"Windows Kits\10\Debuggers"); path.push(cdb_arch);
path.push(r"cdb.exe");
if !path.exists() {
return None;
}
Some(path.into_os_string())
}
pub(crate) fn analyze_cdb(
cdb: Option<String>,
target: &str,
) -> (Option<OsString>, Option<[u16; 4]>) {
let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
let mut version = None;
if let Some(cdb) = cdb.as_ref() {
if let Ok(output) = Command::new(cdb).arg("/version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version = extract_cdb_version(&first_line);
}
}
}
(cdb, version)
}
pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
let version = full_version_line.rsplit(' ').next()?;
let mut components = version.split('.');
let major: u16 = components.next().unwrap().parse().unwrap();
let minor: u16 = components.next().unwrap().parse().unwrap();
let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
let build: u16 = components.next().unwrap_or("0").parse().unwrap();
Some([major, minor, patch, build])
}
pub(crate) fn analyze_gdb(
gdb: Option<String>,
target: &str,
android_cross_path: &PathBuf,
) -> (Option<String>, Option<u32>) {
#[cfg(not(windows))]
const GDB_FALLBACK: &str = "gdb";
#[cfg(windows)]
const GDB_FALLBACK: &str = "gdb.exe";
let fallback_gdb = || {
if is_android_gdb_target(target) {
let mut gdb_path = match android_cross_path.to_str() {
Some(x) => x.to_owned(),
None => panic!("cannot find android cross path"),
};
gdb_path.push_str("/bin/gdb");
gdb_path
} else {
GDB_FALLBACK.to_owned()
}
};
let gdb = match gdb {
None => fallback_gdb(),
Some(ref s) if s.is_empty() => fallback_gdb(), Some(ref s) => s.to_owned(),
};
let mut version_line = None;
if let Ok(output) = Command::new(&gdb).arg("--version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version_line = Some(first_line.to_string());
}
}
let version = match version_line {
Some(line) => extract_gdb_version(&line),
None => return (None, None),
};
(Some(gdb), version)
}
pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
let full_version_line = full_version_line.trim();
let unbracketed_part = full_version_line.split('[').next().unwrap();
let mut splits = unbracketed_part.trim_end().rsplit(' ');
let version_string = splits.next().unwrap();
let mut splits = version_string.split('.');
let major = splits.next().unwrap();
let minor = splits.next().unwrap();
let patch = splits.next();
let major: u32 = major.parse().unwrap();
let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
None => {
let minor = minor.parse().unwrap();
let patch: u32 = match patch {
Some(patch) => match patch.find(not_a_digit) {
None => patch.parse().unwrap(),
Some(idx) if idx > 3 => 0,
Some(idx) => patch[..idx].parse().unwrap(),
},
None => 0,
};
(minor, patch)
}
Some(idx) => {
let minor = minor[..idx].parse().unwrap();
(minor, 0)
}
};
Some(((major * 1000) + minor) * 1000 + patch)
}
pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<u32> {
let full_version_line = full_version_line.trim();
if let Some(apple_ver) =
full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
{
if let Some(idx) = apple_ver.find(not_a_digit) {
let version: u32 = apple_ver[..idx].parse().unwrap();
return Some(version);
}
} else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
if let Some(idx) = lldb_ver.find(not_a_digit) {
let version: u32 = lldb_ver[..idx].parse().ok()?;
return Some(version * 100);
}
}
None
}
fn not_a_digit(c: char) -> bool {
!c.is_ascii_digit()
}