1 #![crate_name = "compiletest"]
3 // The `test` crate is the only unstable feature
4 // allowed here, just to share similar code.
9 use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
10 use crate::common::{CompareMode, PassMode};
11 use crate::common::{Config, TestPaths};
12 use crate::common::{DebugInfoCdb, DebugInfoGdb, DebugInfoGdbLldb, DebugInfoLldb, Mode, Pretty};
13 use crate::util::logv;
19 use std::ffi::OsString;
21 use std::io::{self, ErrorKind};
22 use std::path::{Path, PathBuf};
23 use std::process::Command;
24 use std::time::SystemTime;
25 use test::ColorConfig;
28 use self::header::{EarlyProps, Ignore};
45 let config = parse_config(env::args().collect());
47 if config.valgrind_path.is_none() && config.force_valgrind {
48 panic!("Can't find Valgrind to run Valgrind tests");
55 pub fn parse_config(args: Vec<String>) -> Config {
56 let mut opts = Options::new();
57 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
58 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
59 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
60 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
61 .reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
62 .reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
63 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
64 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
65 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
66 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
67 .reqopt("", "src-base", "directory to scan for test files", "PATH")
68 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
69 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
73 "which sort of compile tests to run",
74 "(compile-fail|run-fail|run-pass-valgrind|pretty|debug-info|incremental|mir-opt)",
79 "force {check,build,run}-pass tests to this mode.",
80 "check | build | run",
82 .optflag("", "ignored", "run tests marked as ignored")
83 .optflag("", "exact", "filters match exactly")
87 "supervisor program to run tests under \
88 (eg. emulator, valgrind)",
91 .optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
92 .optopt("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
93 .optflag("", "verbose", "run tests verbosely, showing all output")
97 "overwrite stderr/stdout files instead of complaining about a mismatch",
99 .optflag("", "quiet", "print one character per test instead of one line")
100 .optopt("", "color", "coloring: auto, always, never", "WHEN")
101 .optopt("", "logfile", "file to log test execution to", "FILE")
102 .optopt("", "target", "the target to build for", "TARGET")
103 .optopt("", "host", "the host to build for", "HOST")
104 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
105 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
106 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
107 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
108 .optflag("", "system-llvm", "is LLVM the system LLVM")
109 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
110 .optopt("", "adb-path", "path to the android debugger", "PATH")
111 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
112 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
113 .reqopt("", "cc", "path to a C compiler", "PATH")
114 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
115 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
116 .optopt("", "ar", "path to an archiver", "PATH")
117 .optopt("", "linker", "path to a linker", "PATH")
118 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
119 .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
120 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
121 .optopt("", "nodejs", "the name of nodejs", "PATH")
122 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
126 "mode describing what file the actual ui output will be compared to",
132 "enable this to generate a Rustfix coverage file, which is saved in \
133 `./<build_base>/rustfix_missing_coverage.txt`",
135 .optflag("h", "help", "show this message");
137 let (argv0, args_) = args.split_first().unwrap();
138 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
139 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
140 println!("{}", opts.usage(&message));
145 let matches = &match opts.parse(args_) {
147 Err(f) => panic!("{:?}", f),
150 if matches.opt_present("h") || matches.opt_present("help") {
151 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
152 println!("{}", opts.usage(&message));
157 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
158 match m.opt_str(nm) {
159 Some(s) => PathBuf::from(&s),
160 None => panic!("no option (=path) found for {}", nm),
164 fn make_absolute(path: PathBuf) -> PathBuf {
165 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
168 let target = opt_str2(matches.opt_str("target"));
169 let android_cross_path = opt_path(matches, "android-cross-path");
170 let cdb = analyze_cdb(matches.opt_str("cdb"), &target);
171 let (gdb, gdb_version, gdb_native_rust) =
172 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
173 let (lldb_version, lldb_native_rust) = extract_lldb_version(matches.opt_str("lldb-version"));
175 let color = match matches.opt_str("color").as_ref().map(|x| &**x) {
176 Some("auto") | None => ColorConfig::AutoColor,
177 Some("always") => ColorConfig::AlwaysColor,
178 Some("never") => ColorConfig::NeverColor,
179 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
182 let src_base = opt_path(matches, "src-base");
183 let run_ignored = matches.opt_present("ignored");
185 bless: matches.opt_present("bless"),
186 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
187 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
188 rustc_path: opt_path(matches, "rustc-path"),
189 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
190 lldb_python: matches.opt_str("lldb-python").unwrap(),
191 docck_python: matches.opt_str("docck-python").unwrap(),
192 valgrind_path: matches.opt_str("valgrind-path"),
193 force_valgrind: matches.opt_present("force-valgrind"),
194 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
195 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
196 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
198 build_base: opt_path(matches, "build-base"),
199 stage_id: matches.opt_str("stage-id").unwrap(),
200 mode: matches.opt_str("mode").unwrap().parse().expect("invalid mode"),
202 filter: matches.free.first().cloned(),
203 filter_exact: matches.opt_present("exact"),
204 force_pass_mode: matches.opt_str("pass").map(|mode| {
205 mode.parse::<PassMode>()
206 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
208 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
209 runtool: matches.opt_str("runtool"),
210 host_rustcflags: matches.opt_str("host-rustcflags"),
211 target_rustcflags: matches.opt_str("target-rustcflags"),
213 host: opt_str2(matches.opt_str("host")),
220 llvm_version: matches.opt_str("llvm-version"),
221 system_llvm: matches.opt_present("system-llvm"),
223 adb_path: opt_str2(matches.opt_str("adb-path")),
224 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
225 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
226 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
227 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
228 lldb_python_dir: matches.opt_str("lldb-python-dir"),
229 verbose: matches.opt_present("verbose"),
230 quiet: matches.opt_present("quiet"),
232 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
233 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
234 rustfix_coverage: matches.opt_present("rustfix-coverage"),
236 cc: matches.opt_str("cc").unwrap(),
237 cxx: matches.opt_str("cxx").unwrap(),
238 cflags: matches.opt_str("cflags").unwrap(),
239 ar: matches.opt_str("ar").unwrap_or("ar".into()),
240 linker: matches.opt_str("linker"),
241 llvm_components: matches.opt_str("llvm-components").unwrap(),
242 llvm_cxxflags: matches.opt_str("llvm-cxxflags").unwrap(),
243 nodejs: matches.opt_str("nodejs"),
247 pub fn log_config(config: &Config) {
249 logv(c, "configuration:".to_string());
250 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
251 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
252 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
253 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
254 logv(c, format!("src_base: {:?}", config.src_base.display()));
255 logv(c, format!("build_base: {:?}", config.build_base.display()));
256 logv(c, format!("stage_id: {}", config.stage_id));
257 logv(c, format!("mode: {}", config.mode));
258 logv(c, format!("run_ignored: {}", config.run_ignored));
259 logv(c, format!("filter: {}", opt_str(&config.filter.as_ref().map(|re| re.to_owned()))));
260 logv(c, format!("filter_exact: {}", config.filter_exact));
263 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
265 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
266 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
267 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
268 logv(c, format!("target: {}", config.target));
269 logv(c, format!("host: {}", config.host));
270 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
271 logv(c, format!("adb_path: {:?}", config.adb_path));
272 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
273 logv(c, format!("adb_device_status: {}", config.adb_device_status));
274 logv(c, format!("ar: {}", config.ar));
275 logv(c, format!("linker: {:?}", config.linker));
276 logv(c, format!("verbose: {}", config.verbose));
277 logv(c, format!("quiet: {}", config.quiet));
278 logv(c, "\n".to_string());
281 pub fn opt_str(maybestr: &Option<String>) -> &str {
288 pub fn opt_str2(maybestr: Option<String>) -> String {
290 None => "(none)".to_owned(),
295 pub fn run_tests(config: &Config) {
296 if config.target.contains("android") {
297 if config.mode == DebugInfoGdb || config.mode == DebugInfoGdbLldb {
299 "{} debug-info test uses tcp 5039 port.\
304 // android debug-info test uses remote debugger so, we test 1 thread
305 // at once as they're all sharing the same TCP port to communicate
308 // we should figure out how to lift this restriction! (run them all
309 // on different ports allocated dynamically).
310 env::set_var("RUST_TEST_THREADS", "1");
315 // Note that we don't need to emit the gdb warning when
316 // DebugInfoGdbLldb, so it is ok to list that here.
317 DebugInfoGdbLldb | DebugInfoLldb => {
318 if let Some(lldb_version) = config.lldb_version.as_ref() {
319 if is_blacklisted_lldb_version(&lldb_version[..]) {
321 "WARNING: The used version of LLDB ({}) has a \
322 known issue that breaks debuginfo tests. See \
323 issue #32520 for more information. Skipping all \
331 // Some older versions of LLDB seem to have problems with multiple
332 // instances running in parallel, so only run one test thread at a
334 env::set_var("RUST_TEST_THREADS", "1");
338 if config.remote_test_client.is_some() && !config.target.contains("android") {
340 "WARNING: debuginfo tests are not available when \
347 DebugInfoCdb | _ => { /* proceed */ }
350 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
351 if let Mode::CodegenUnits = config.mode {
352 let _ = fs::remove_dir_all("tmp/partitioning-tests");
355 // If we want to collect rustfix coverage information,
356 // we first make sure that the coverage file does not exist.
357 // It will be created later on.
358 if config.rustfix_coverage {
359 let mut coverage_file_path = config.build_base.clone();
360 coverage_file_path.push("rustfix_missing_coverage.txt");
361 if coverage_file_path.exists() {
362 if let Err(e) = fs::remove_file(&coverage_file_path) {
363 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
368 let opts = test_opts(config);
369 let tests = make_tests(config);
370 // sadly osx needs some file descriptor limits raised for running tests in
371 // parallel (especially when we have lots and lots of child processes).
372 // For context, see #8904
374 raise_fd_limit::raise_fd_limit();
376 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
377 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
378 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
380 // Let tests know which target they're running as
381 env::set_var("TARGET", &config.target);
383 let res = test::run_tests_console(&opts, tests);
386 Ok(false) => panic!("Some tests failed"),
388 println!("I/O failure during tests: {:?}", e);
393 pub fn test_opts(config: &Config) -> test::TestOpts {
395 exclude_should_panic: false,
396 filter: config.filter.clone(),
397 filter_exact: config.filter_exact,
398 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
399 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
400 logfile: config.logfile.clone(),
402 bench_benchmarks: true,
403 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
404 Ok(val) => &val != "0",
411 options: test::Options::new(),
413 force_run_in_process: false,
417 pub fn make_tests(config: &Config) -> Vec<test::TestDescAndFn> {
418 debug!("making tests from {:?}", config.src_base.display());
419 let inputs = common_inputs_stamp(config);
420 let mut tests = Vec::new();
421 collect_tests_from_dir(
429 .expect(&format!("Could not read tests from {}", config.src_base.display()));
433 /// Returns a stamp constructed from input files common to all test cases.
434 fn common_inputs_stamp(config: &Config) -> Stamp {
435 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
437 let mut stamp = Stamp::from_path(&config.rustc_path);
439 // Relevant pretty printer files
440 let pretty_printer_files = [
441 "src/etc/debugger_pretty_printers_common.py",
442 "src/etc/gdb_load_rust_pretty_printers.py",
443 "src/etc/gdb_rust_pretty_printing.py",
444 "src/etc/lldb_batchmode.py",
445 "src/etc/lldb_rust_formatters.py",
447 for file in &pretty_printer_files {
448 let path = rust_src_dir.join(file);
449 stamp.add_path(&path);
452 stamp.add_dir(&config.run_lib_path);
454 if let Some(ref rustdoc_path) = config.rustdoc_path {
455 stamp.add_path(&rustdoc_path);
456 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
459 // Compiletest itself.
460 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
465 fn collect_tests_from_dir(
469 relative_dir_path: &Path,
471 tests: &mut Vec<test::TestDescAndFn>,
472 ) -> io::Result<()> {
473 // Ignore directories that contain a file named `compiletest-ignore-dir`.
474 if dir.join("compiletest-ignore-dir").exists() {
478 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
479 let paths = TestPaths {
480 file: dir.to_path_buf(),
481 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
483 tests.extend(make_test(config, &paths, inputs));
487 // If we find a test foo/bar.rs, we have to build the
488 // output directory `$build/foo` so we can write
489 // `$build/foo/bar` into it. We do this *now* in this
490 // sequential loop because otherwise, if we do it in the
491 // tests themselves, they race for the privilege of
492 // creating the directories and sometimes fail randomly.
493 let build_dir = output_relative_path(config, relative_dir_path);
494 fs::create_dir_all(&build_dir).unwrap();
496 // Add each `.rs` file as a test, and recurse further on any
497 // subdirectories we find, except for `aux` directories.
498 for file in fs::read_dir(dir)? {
500 let file_path = file.path();
501 let file_name = file.file_name();
502 if is_test(&file_name) {
503 debug!("found test file: {:?}", file_path.display());
505 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
506 tests.extend(make_test(config, &paths, inputs))
507 } else if file_path.is_dir() {
508 let relative_file_path = relative_dir_path.join(file.file_name());
509 if &file_name != "auxiliary" {
510 debug!("found directory: {:?}", file_path.display());
511 collect_tests_from_dir(
521 debug!("found other file/directory: {:?}", file_path.display());
527 /// Returns true if `file_name` looks like a proper test file name.
528 pub fn is_test(file_name: &OsString) -> bool {
529 let file_name = file_name.to_str().unwrap();
531 if !file_name.ends_with(".rs") {
535 // `.`, `#`, and `~` are common temp-file prefixes.
536 let invalid_prefixes = &[".", "#", "~"];
537 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
540 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
541 let early_props = if config.mode == Mode::RunMake {
542 // Allow `ignore` directives to be in the Makefile.
543 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
545 EarlyProps::from_file(config, &testpaths.file)
548 // The `should-fail` annotation doesn't apply to pretty tests,
549 // since we run the pretty printer across all tests by default.
550 // If desired, we could add a `should-fail-pretty` annotation.
551 let should_panic = match config.mode {
552 Pretty => test::ShouldPanic::No,
554 if early_props.should_fail {
555 test::ShouldPanic::Yes
557 test::ShouldPanic::No
562 // Incremental tests are special, they inherently cannot be run in parallel.
563 // `runtest::run` will be responsible for iterating over revisions.
564 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
567 early_props.revisions.iter().map(|r| Some(r)).collect()
572 let ignore = early_props.ignore == Ignore::Ignore
573 // Debugging emscripten code doesn't make sense today
574 || ((config.mode == DebugInfoGdbLldb || config.mode == DebugInfoCdb ||
575 config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
576 && config.target.contains("emscripten"))
577 || (config.mode == DebugInfoGdb && !early_props.ignore.can_run_gdb())
578 || (config.mode == DebugInfoLldb && !early_props.ignore.can_run_lldb())
579 // Ignore tests that already run and are up to date with respect to inputs.
584 revision.map(|s| s.as_str()),
587 test::TestDescAndFn {
588 desc: test::TestDesc {
589 name: make_test_name(config, testpaths, revision),
593 test_type: test::TestType::Unknown,
595 testfn: make_test_closure(config, early_props.ignore, testpaths, revision),
601 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
602 output_base_dir(config, testpaths, revision).join("stamp")
607 testpaths: &TestPaths,
609 revision: Option<&str>,
612 let stamp_name = stamp(config, testpaths, revision);
614 let contents = match fs::read_to_string(&stamp_name) {
616 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
617 Err(_) => return false,
619 let expected_hash = runtest::compute_stamp_hash(config);
620 if contents != expected_hash {
625 let mut inputs = inputs.clone();
626 inputs.add_path(&testpaths.file);
628 for aux in &props.aux {
629 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
630 inputs.add_path(&path);
634 for extension in UI_EXTENSIONS {
635 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
636 inputs.add_path(path);
639 inputs < Stamp::from_path(&stamp_name)
642 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
648 fn from_path(path: &Path) -> Self {
649 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
650 stamp.add_path(path);
654 fn add_path(&mut self, path: &Path) {
655 let modified = fs::metadata(path)
656 .and_then(|metadata| metadata.modified())
657 .unwrap_or(SystemTime::UNIX_EPOCH);
658 self.time = self.time.max(modified);
661 fn add_dir(&mut self, path: &Path) {
662 for entry in WalkDir::new(path) {
663 let entry = entry.unwrap();
664 if entry.file_type().is_file() {
668 .and_then(|metadata| metadata.modified().ok())
669 .unwrap_or(SystemTime::UNIX_EPOCH);
670 self.time = self.time.max(modified);
678 testpaths: &TestPaths,
679 revision: Option<&String>,
680 ) -> test::TestName {
681 // Convert a complete path to something like
684 let path = PathBuf::from(config.src_base.file_name().unwrap())
685 .join(&testpaths.relative_dir)
686 .join(&testpaths.file.file_name().unwrap());
687 let mode_suffix = match config.compare_mode {
688 Some(ref mode) => format!(" ({})", mode.to_str()),
689 None => String::new(),
691 test::DynTestName(format!(
696 revision.map_or("".to_string(), |rev| format!("#{}", rev))
700 fn make_test_closure(
703 testpaths: &TestPaths,
704 revision: Option<&String>,
706 let mut config = config.clone();
707 if config.mode == DebugInfoGdbLldb {
708 // If both gdb and lldb were ignored, then the test as a whole
710 if !ignore.can_run_gdb() {
711 config.mode = DebugInfoLldb;
712 } else if !ignore.can_run_lldb() {
713 config.mode = DebugInfoGdb;
717 let testpaths = testpaths.clone();
718 let revision = revision.cloned();
719 test::DynTestFn(Box::new(move || {
720 runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str()))
724 /// Returns `true` if the given target is an Android target for the
725 /// purposes of GDB testing.
726 fn is_android_gdb_target(target: &String) -> bool {
728 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
733 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
734 fn is_pc_windows_msvc_target(target: &String) -> bool {
735 target.ends_with("-pc-windows-msvc")
738 fn find_cdb(target: &String) -> Option<OsString> {
739 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
743 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
744 let cdb_arch = if cfg!(target_arch = "x86") {
746 } else if cfg!(target_arch = "x86_64") {
748 } else if cfg!(target_arch = "aarch64") {
750 } else if cfg!(target_arch = "arm") {
753 return None; // No compatible CDB.exe in the Windows 10 SDK
756 let mut path = PathBuf::new();
758 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
760 path.push(r"cdb.exe");
766 Some(path.into_os_string())
769 /// Returns Path to CDB
770 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
771 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
774 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
778 android_cross_path: &PathBuf,
779 ) -> (Option<String>, Option<u32>, bool) {
781 const GDB_FALLBACK: &str = "gdb";
783 const GDB_FALLBACK: &str = "gdb.exe";
785 const MIN_GDB_WITH_RUST: u32 = 7011010;
787 let fallback_gdb = || {
788 if is_android_gdb_target(target) {
789 let mut gdb_path = match android_cross_path.to_str() {
790 Some(x) => x.to_owned(),
791 None => panic!("cannot find android cross path"),
793 gdb_path.push_str("/bin/gdb");
796 GDB_FALLBACK.to_owned()
800 let gdb = match gdb {
801 None => fallback_gdb(),
802 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
803 Some(ref s) => s.to_owned(),
806 let mut version_line = None;
807 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
808 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
809 version_line = Some(first_line.to_string());
813 let version = match version_line {
814 Some(line) => extract_gdb_version(&line),
815 None => return (None, None, false),
818 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
820 (Some(gdb), version, gdb_native_rust)
823 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
824 let full_version_line = full_version_line.trim();
826 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
827 // of the ? sections being optional
829 // We will parse up to 3 digits for minor and patch, ignoring the date
830 // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
832 // don't start parsing in the middle of a number
833 let mut prev_was_digit = false;
834 for (pos, c) in full_version_line.char_indices() {
835 if prev_was_digit || !c.is_digit(10) {
836 prev_was_digit = c.is_digit(10);
840 prev_was_digit = true;
842 let line = &full_version_line[pos..];
844 let next_split = match line.find(|c: char| !c.is_digit(10)) {
846 None => continue, // no minor version
849 if line.as_bytes()[next_split] != b'.' {
850 continue; // no minor version
853 let major = &line[..next_split];
854 let line = &line[next_split + 1..];
856 let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
858 if line.as_bytes()[idx] == b'.' {
859 let patch = &line[idx + 1..];
862 patch.find(|c: char| !c.is_digit(10)).unwrap_or_else(|| patch.len());
863 let patch = &patch[..patch_len];
864 let patch = if patch_len > 3 || patch_len == 0 { None } else { Some(patch) };
866 (&line[..idx], patch)
871 None => (line, None),
874 if major.len() != 1 || minor.is_empty() {
878 let major: u32 = major.parse().unwrap();
879 let minor: u32 = minor.parse().unwrap();
880 let patch: u32 = patch.unwrap_or("0").parse().unwrap();
882 return Some(((major * 1000) + minor) * 1000 + patch);
888 /// Returns (LLDB version, LLDB is rust-enabled)
889 fn extract_lldb_version(full_version_line: Option<String>) -> (Option<String>, bool) {
890 // Extract the major LLDB version from the given version string.
891 // LLDB version strings are different for Apple and non-Apple platforms.
892 // The Apple variant looks like this:
894 // LLDB-179.5 (older versions)
895 // lldb-300.2.51 (new versions)
897 // We are only interested in the major version number, so this function
898 // will return `Some("179")` and `Some("300")` respectively.
900 // Upstream versions look like:
901 // lldb version 6.0.1
903 // There doesn't seem to be a way to correlate the Apple version
904 // with the upstream version, and since the tests were originally
905 // written against Apple versions, we make a fake Apple version by
906 // multiplying the first number by 100. This is a hack, but
907 // normally fine because the only non-Apple version we test is
910 if let Some(ref full_version_line) = full_version_line {
911 if !full_version_line.trim().is_empty() {
912 let full_version_line = full_version_line.trim();
914 for (pos, l) in full_version_line.char_indices() {
915 if l != 'l' && l != 'L' {
918 if pos + 5 >= full_version_line.len() {
921 let l = full_version_line[pos + 1..].chars().next().unwrap();
922 if l != 'l' && l != 'L' {
925 let d = full_version_line[pos + 2..].chars().next().unwrap();
926 if d != 'd' && d != 'D' {
929 let b = full_version_line[pos + 3..].chars().next().unwrap();
930 if b != 'b' && b != 'B' {
933 let dash = full_version_line[pos + 4..].chars().next().unwrap();
938 let vers = full_version_line[pos + 5..]
940 .take_while(|c| c.is_digit(10))
941 .collect::<String>();
942 if !vers.is_empty() {
943 return (Some(vers), full_version_line.contains("rust-enabled"));
947 if full_version_line.starts_with("lldb version ") {
948 let vers = full_version_line[13..]
950 .take_while(|c| c.is_digit(10))
951 .collect::<String>();
952 if !vers.is_empty() {
953 return (Some(vers + "00"), full_version_line.contains("rust-enabled"));
961 fn is_blacklisted_lldb_version(version: &str) -> bool {