1 #![crate_name = "compiletest"]
2 // The `test` crate is the only unstable feature
3 // allowed here, just to share similar code.
8 use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
9 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, Pretty, TestPaths};
10 use crate::util::logv;
14 use std::ffi::OsString;
16 use std::io::{self, ErrorKind};
17 use std::path::{Path, PathBuf};
18 use std::process::Command;
19 use std::time::SystemTime;
20 use test::ColorConfig;
23 use self::header::EarlyProps;
40 let config = parse_config(env::args().collect());
42 if config.valgrind_path.is_none() && config.force_valgrind {
43 panic!("Can't find Valgrind to run Valgrind tests");
50 pub fn parse_config(args: Vec<String>) -> Config {
51 let mut opts = Options::new();
52 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
53 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
54 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
55 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
56 .reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
57 .reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
58 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
59 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
60 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
61 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
62 .reqopt("", "src-base", "directory to scan for test files", "PATH")
63 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
64 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
68 "which sort of compile tests to run",
69 "compile-fail | run-fail | run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
70 codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
75 "force {check,build,run}-pass tests to this mode.",
76 "check | build | run",
78 .optflag("", "ignored", "run tests marked as ignored")
79 .optflag("", "exact", "filters match exactly")
83 "supervisor program to run tests under \
84 (eg. emulator, valgrind)",
87 .optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
88 .optopt("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
89 .optflag("", "verbose", "run tests verbosely, showing all output")
93 "overwrite stderr/stdout files instead of complaining about a mismatch",
95 .optflag("", "quiet", "print one character per test instead of one line")
96 .optopt("", "color", "coloring: auto, always, never", "WHEN")
97 .optopt("", "logfile", "file to log test execution to", "FILE")
98 .optopt("", "target", "the target to build for", "TARGET")
99 .optopt("", "host", "the host to build for", "HOST")
100 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
101 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
102 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
103 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
104 .optflag("", "system-llvm", "is LLVM the system LLVM")
105 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
106 .optopt("", "adb-path", "path to the android debugger", "PATH")
107 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
108 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
109 .reqopt("", "cc", "path to a C compiler", "PATH")
110 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
111 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
112 .optopt("", "ar", "path to an archiver", "PATH")
113 .optopt("", "linker", "path to a linker", "PATH")
114 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
115 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
116 .optopt("", "nodejs", "the name of nodejs", "PATH")
117 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
121 "mode describing what file the actual ui output will be compared to",
127 "enable this to generate a Rustfix coverage file, which is saved in \
128 `./<build_base>/rustfix_missing_coverage.txt`",
130 .optflag("h", "help", "show this message");
132 let (argv0, args_) = args.split_first().unwrap();
133 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
134 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
135 println!("{}", opts.usage(&message));
140 let matches = &match opts.parse(args_) {
142 Err(f) => panic!("{:?}", f),
145 if matches.opt_present("h") || matches.opt_present("help") {
146 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
147 println!("{}", opts.usage(&message));
152 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
153 match m.opt_str(nm) {
154 Some(s) => PathBuf::from(&s),
155 None => panic!("no option (=path) found for {}", nm),
159 fn make_absolute(path: PathBuf) -> PathBuf {
160 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
163 let target = opt_str2(matches.opt_str("target"));
164 let android_cross_path = opt_path(matches, "android-cross-path");
165 let cdb = analyze_cdb(matches.opt_str("cdb"), &target);
166 let (gdb, gdb_version, gdb_native_rust) =
167 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
168 let (lldb_version, lldb_native_rust) = matches
169 .opt_str("lldb-version")
171 .and_then(extract_lldb_version)
172 .map(|(v, b)| (Some(v), b))
173 .unwrap_or((None, false));
174 let color = match matches.opt_str("color").as_deref() {
175 Some("auto") | None => ColorConfig::AutoColor,
176 Some("always") => ColorConfig::AlwaysColor,
177 Some("never") => ColorConfig::NeverColor,
178 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
181 let src_base = opt_path(matches, "src-base");
182 let run_ignored = matches.opt_present("ignored");
184 bless: matches.opt_present("bless"),
185 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
186 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
187 rustc_path: opt_path(matches, "rustc-path"),
188 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
189 lldb_python: matches.opt_str("lldb-python").unwrap(),
190 docck_python: matches.opt_str("docck-python").unwrap(),
191 valgrind_path: matches.opt_str("valgrind-path"),
192 force_valgrind: matches.opt_present("force-valgrind"),
193 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
194 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
195 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
197 build_base: opt_path(matches, "build-base"),
198 stage_id: matches.opt_str("stage-id").unwrap(),
199 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 nodejs: matches.opt_str("nodejs"),
246 pub fn log_config(config: &Config) {
248 logv(c, "configuration:".to_string());
249 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
250 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
251 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
252 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
253 logv(c, format!("src_base: {:?}", config.src_base.display()));
254 logv(c, format!("build_base: {:?}", config.build_base.display()));
255 logv(c, format!("stage_id: {}", config.stage_id));
256 logv(c, format!("mode: {}", config.mode));
257 logv(c, format!("run_ignored: {}", config.run_ignored));
258 logv(c, format!("filter: {}", opt_str(&config.filter)));
259 logv(c, format!("filter_exact: {}", config.filter_exact));
262 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
264 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
265 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
266 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
267 logv(c, format!("target: {}", config.target));
268 logv(c, format!("host: {}", config.host));
269 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
270 logv(c, format!("adb_path: {:?}", config.adb_path));
271 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
272 logv(c, format!("adb_device_status: {}", config.adb_device_status));
273 logv(c, format!("ar: {}", config.ar));
274 logv(c, format!("linker: {:?}", config.linker));
275 logv(c, format!("verbose: {}", config.verbose));
276 logv(c, format!("quiet: {}", config.quiet));
277 logv(c, "\n".to_string());
280 pub fn opt_str(maybestr: &Option<String>) -> &str {
287 pub fn opt_str2(maybestr: Option<String>) -> String {
289 None => "(none)".to_owned(),
294 pub fn run_tests(config: Config) {
295 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
296 if let Mode::CodegenUnits = config.mode {
297 let _ = fs::remove_dir_all("tmp/partitioning-tests");
300 // If we want to collect rustfix coverage information,
301 // we first make sure that the coverage file does not exist.
302 // It will be created later on.
303 if config.rustfix_coverage {
304 let mut coverage_file_path = config.build_base.clone();
305 coverage_file_path.push("rustfix_missing_coverage.txt");
306 if coverage_file_path.exists() {
307 if let Err(e) = fs::remove_file(&coverage_file_path) {
308 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
313 // sadly osx needs some file descriptor limits raised for running tests in
314 // parallel (especially when we have lots and lots of child processes).
315 // For context, see #8904
317 raise_fd_limit::raise_fd_limit();
319 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
320 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
321 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
323 // Let tests know which target they're running as
324 env::set_var("TARGET", &config.target);
326 let opts = test_opts(&config);
328 let mut configs = Vec::new();
329 if let Mode::DebugInfo = config.mode {
330 // Debugging emscripten code doesn't make sense today
331 if !config.target.contains("emscripten") {
332 configs.extend(configure_cdb(&config));
333 configs.extend(configure_gdb(&config));
334 configs.extend(configure_lldb(&config));
337 configs.push(config);
340 let mut tests = Vec::new();
342 make_tests(c, &mut tests);
345 let res = test::run_tests_console(&opts, tests);
348 Ok(false) => panic!("Some tests failed"),
350 // We don't know if tests passed or not, but if there was an error
351 // during testing we don't want to just suceeed (we may not have
352 // tested something), so fail.
353 panic!("I/O failure during tests: {:?}", e);
358 fn configure_cdb(config: &Config) -> Option<Config> {
359 if config.cdb.is_none() {
363 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
366 fn configure_gdb(config: &Config) -> Option<Config> {
367 if config.gdb_version.is_none() {
371 if util::matches_env(&config.target, "msvc") {
375 if config.remote_test_client.is_some() && !config.target.contains("android") {
377 "WARNING: debuginfo tests are not available when \
383 if config.target.contains("android") {
385 "{} debug-info test uses tcp 5039 port.\
390 // android debug-info test uses remote debugger so, we test 1 thread
391 // at once as they're all sharing the same TCP port to communicate
394 // we should figure out how to lift this restriction! (run them all
395 // on different ports allocated dynamically).
396 env::set_var("RUST_TEST_THREADS", "1");
399 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
402 fn configure_lldb(config: &Config) -> Option<Config> {
403 if config.lldb_python_dir.is_none() {
407 if let Some(350) = config.lldb_version {
409 "WARNING: The used version of LLDB (350) has a \
410 known issue that breaks debuginfo tests. See \
411 issue #32520 for more information. Skipping all \
417 // Some older versions of LLDB seem to have problems with multiple
418 // instances running in parallel, so only run one test thread at a
420 env::set_var("RUST_TEST_THREADS", "1");
422 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
425 pub fn test_opts(config: &Config) -> test::TestOpts {
427 exclude_should_panic: false,
428 filter: config.filter.clone(),
429 filter_exact: config.filter_exact,
430 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
431 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
432 logfile: config.logfile.clone(),
434 bench_benchmarks: true,
435 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
436 Ok(val) => &val != "0",
443 options: test::Options::new(),
445 force_run_in_process: false,
449 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
450 debug!("making tests from {:?}", config.src_base.display());
451 let inputs = common_inputs_stamp(config);
452 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
453 .expect(&format!("Could not read tests from {}", config.src_base.display()));
456 /// Returns a stamp constructed from input files common to all test cases.
457 fn common_inputs_stamp(config: &Config) -> Stamp {
458 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
460 let mut stamp = Stamp::from_path(&config.rustc_path);
462 // Relevant pretty printer files
463 let pretty_printer_files = [
464 "src/etc/rust_types.py",
465 "src/etc/gdb_load_rust_pretty_printers.py",
466 "src/etc/gdb_lookup.py",
467 "src/etc/gdb_providers.py",
468 "src/etc/lldb_batchmode.py",
469 "src/etc/lldb_lookup.py",
470 "src/etc/lldb_providers.py",
472 for file in &pretty_printer_files {
473 let path = rust_src_dir.join(file);
474 stamp.add_path(&path);
477 stamp.add_dir(&config.run_lib_path);
479 if let Some(ref rustdoc_path) = config.rustdoc_path {
480 stamp.add_path(&rustdoc_path);
481 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
484 // Compiletest itself.
485 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
490 fn collect_tests_from_dir(
493 relative_dir_path: &Path,
495 tests: &mut Vec<test::TestDescAndFn>,
496 ) -> io::Result<()> {
497 // Ignore directories that contain a file named `compiletest-ignore-dir`.
498 if dir.join("compiletest-ignore-dir").exists() {
502 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
503 let paths = TestPaths {
504 file: dir.to_path_buf(),
505 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
507 tests.extend(make_test(config, &paths, inputs));
511 // If we find a test foo/bar.rs, we have to build the
512 // output directory `$build/foo` so we can write
513 // `$build/foo/bar` into it. We do this *now* in this
514 // sequential loop because otherwise, if we do it in the
515 // tests themselves, they race for the privilege of
516 // creating the directories and sometimes fail randomly.
517 let build_dir = output_relative_path(config, relative_dir_path);
518 fs::create_dir_all(&build_dir).unwrap();
520 // Add each `.rs` file as a test, and recurse further on any
521 // subdirectories we find, except for `aux` directories.
522 for file in fs::read_dir(dir)? {
524 let file_path = file.path();
525 let file_name = file.file_name();
526 if is_test(&file_name) {
527 debug!("found test file: {:?}", file_path.display());
529 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
530 tests.extend(make_test(config, &paths, inputs))
531 } else if file_path.is_dir() {
532 let relative_file_path = relative_dir_path.join(file.file_name());
533 if &file_name != "auxiliary" {
534 debug!("found directory: {:?}", file_path.display());
535 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
538 debug!("found other file/directory: {:?}", file_path.display());
544 /// Returns true if `file_name` looks like a proper test file name.
545 pub fn is_test(file_name: &OsString) -> bool {
546 let file_name = file_name.to_str().unwrap();
548 if !file_name.ends_with(".rs") {
552 // `.`, `#`, and `~` are common temp-file prefixes.
553 let invalid_prefixes = &[".", "#", "~"];
554 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
557 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
558 let early_props = if config.mode == Mode::RunMake {
559 // Allow `ignore` directives to be in the Makefile.
560 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
562 EarlyProps::from_file(config, &testpaths.file)
565 // The `should-fail` annotation doesn't apply to pretty tests,
566 // since we run the pretty printer across all tests by default.
567 // If desired, we could add a `should-fail-pretty` annotation.
568 let should_panic = match config.mode {
569 Pretty => test::ShouldPanic::No,
571 if early_props.should_fail {
572 test::ShouldPanic::Yes
574 test::ShouldPanic::No
579 // Incremental tests are special, they inherently cannot be run in parallel.
580 // `runtest::run` will be responsible for iterating over revisions.
581 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
584 early_props.revisions.iter().map(|r| Some(r)).collect()
589 let ignore = early_props.ignore
590 // Ignore tests that already run and are up to date with respect to inputs.
595 revision.map(|s| s.as_str()),
598 test::TestDescAndFn {
599 desc: test::TestDesc {
600 name: make_test_name(config, testpaths, revision),
604 test_type: test::TestType::Unknown,
606 testfn: make_test_closure(config, testpaths, revision),
612 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
613 output_base_dir(config, testpaths, revision).join("stamp")
618 testpaths: &TestPaths,
620 revision: Option<&str>,
623 let stamp_name = stamp(config, testpaths, revision);
625 let contents = match fs::read_to_string(&stamp_name) {
627 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
628 Err(_) => return false,
630 let expected_hash = runtest::compute_stamp_hash(config);
631 if contents != expected_hash {
636 let mut inputs = inputs.clone();
637 // Use `add_dir` to account for run-make tests, which use their individual directory
638 inputs.add_dir(&testpaths.file);
640 for aux in &props.aux {
641 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
642 inputs.add_path(&path);
646 for extension in UI_EXTENSIONS {
647 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
648 inputs.add_path(path);
651 inputs < Stamp::from_path(&stamp_name)
654 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
660 fn from_path(path: &Path) -> Self {
661 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
662 stamp.add_path(path);
666 fn add_path(&mut self, path: &Path) {
667 let modified = fs::metadata(path)
668 .and_then(|metadata| metadata.modified())
669 .unwrap_or(SystemTime::UNIX_EPOCH);
670 self.time = self.time.max(modified);
673 fn add_dir(&mut self, path: &Path) {
674 for entry in WalkDir::new(path) {
675 let entry = entry.unwrap();
676 if entry.file_type().is_file() {
680 .and_then(|metadata| metadata.modified().ok())
681 .unwrap_or(SystemTime::UNIX_EPOCH);
682 self.time = self.time.max(modified);
690 testpaths: &TestPaths,
691 revision: Option<&String>,
692 ) -> test::TestName {
693 // Convert a complete path to something like
696 let path = PathBuf::from(config.src_base.file_name().unwrap())
697 .join(&testpaths.relative_dir)
698 .join(&testpaths.file.file_name().unwrap());
699 let debugger = match config.debugger {
700 Some(d) => format!("-{}", d),
701 None => String::new(),
703 let mode_suffix = match config.compare_mode {
704 Some(ref mode) => format!(" ({})", mode.to_str()),
705 None => String::new(),
708 test::DynTestName(format!(
714 revision.map_or("".to_string(), |rev| format!("#{}", rev))
718 fn make_test_closure(
720 testpaths: &TestPaths,
721 revision: Option<&String>,
723 let config = config.clone();
724 let testpaths = testpaths.clone();
725 let revision = revision.cloned();
726 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
729 /// Returns `true` if the given target is an Android target for the
730 /// purposes of GDB testing.
731 fn is_android_gdb_target(target: &String) -> bool {
733 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
738 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
739 fn is_pc_windows_msvc_target(target: &String) -> bool {
740 target.ends_with("-pc-windows-msvc")
743 fn find_cdb(target: &String) -> Option<OsString> {
744 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
748 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
749 let cdb_arch = if cfg!(target_arch = "x86") {
751 } else if cfg!(target_arch = "x86_64") {
753 } else if cfg!(target_arch = "aarch64") {
755 } else if cfg!(target_arch = "arm") {
758 return None; // No compatible CDB.exe in the Windows 10 SDK
761 let mut path = PathBuf::new();
763 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
765 path.push(r"cdb.exe");
771 Some(path.into_os_string())
774 /// Returns Path to CDB
775 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
776 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
779 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
783 android_cross_path: &PathBuf,
784 ) -> (Option<String>, Option<u32>, bool) {
786 const GDB_FALLBACK: &str = "gdb";
788 const GDB_FALLBACK: &str = "gdb.exe";
790 const MIN_GDB_WITH_RUST: u32 = 7011010;
792 let fallback_gdb = || {
793 if is_android_gdb_target(target) {
794 let mut gdb_path = match android_cross_path.to_str() {
795 Some(x) => x.to_owned(),
796 None => panic!("cannot find android cross path"),
798 gdb_path.push_str("/bin/gdb");
801 GDB_FALLBACK.to_owned()
805 let gdb = match gdb {
806 None => fallback_gdb(),
807 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
808 Some(ref s) => s.to_owned(),
811 let mut version_line = None;
812 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
813 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
814 version_line = Some(first_line.to_string());
818 let version = match version_line {
819 Some(line) => extract_gdb_version(&line),
820 None => return (None, None, false),
823 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
825 (Some(gdb), version, gdb_native_rust)
828 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
829 let full_version_line = full_version_line.trim();
831 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
832 // of the ? sections being optional
834 // We will parse up to 3 digits for each component, ignoring the date
836 // We skip text in parentheses. This avoids accidentally parsing
837 // the openSUSE version, which looks like:
838 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
839 // This particular form is documented in the GNU coding standards:
840 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
842 let mut splits = full_version_line.rsplit(' ');
843 let version_string = splits.next().unwrap();
845 let mut splits = version_string.split('.');
846 let major = splits.next().unwrap();
847 let minor = splits.next().unwrap();
848 let patch = splits.next();
850 let major: u32 = major.parse().unwrap();
851 let (minor, patch): (u32, u32) = match minor.find(|c: char| !c.is_digit(10)) {
853 let minor = minor.parse().unwrap();
854 let patch: u32 = match patch {
855 Some(patch) => match patch.find(|c: char| !c.is_digit(10)) {
856 None => patch.parse().unwrap(),
857 Some(idx) if idx > 3 => 0,
858 Some(idx) => patch[..idx].parse().unwrap(),
864 // There is no patch version after minor-date (e.g. "4-2012").
866 let minor = minor[..idx].parse().unwrap();
871 Some(((major * 1000) + minor) * 1000 + patch)
874 /// Returns (LLDB version, LLDB is rust-enabled)
875 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
876 // Extract the major LLDB version from the given version string.
877 // LLDB version strings are different for Apple and non-Apple platforms.
878 // The Apple variant looks like this:
880 // LLDB-179.5 (older versions)
881 // lldb-300.2.51 (new versions)
883 // We are only interested in the major version number, so this function
884 // will return `Some(179)` and `Some(300)` respectively.
886 // Upstream versions look like:
887 // lldb version 6.0.1
889 // There doesn't seem to be a way to correlate the Apple version
890 // with the upstream version, and since the tests were originally
891 // written against Apple versions, we make a fake Apple version by
892 // multiplying the first number by 100. This is a hack, but
893 // normally fine because the only non-Apple version we test is
896 let full_version_line = full_version_line.trim();
898 if let Some(apple_ver) =
899 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
901 if let Some(idx) = apple_ver.find(|c: char| !c.is_digit(10)) {
902 let version: u32 = apple_ver[..idx].parse().unwrap();
903 return Some((version, full_version_line.contains("rust-enabled")));
905 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
906 if let Some(idx) = lldb_ver.find(|c: char| !c.is_digit(10)) {
907 let version: u32 = lldb_ver[..idx].parse().unwrap();
908 return Some((version * 100, full_version_line.contains("rust-enabled")));