tidy/
target_specific_tests.rs1use std::collections::BTreeMap;
5use std::path::Path;
6
7use crate::diagnostics::{CheckId, TidyCtx};
8use crate::iter_header::{HeaderLine, iter_header};
9use crate::walk::filter_not_rust;
10
11const LLVM_COMPONENTS_HEADER: &str = "needs-llvm-components:";
12const COMPILE_FLAGS_HEADER: &str = "compile-flags:";
13
14#[derive(Default, Debug)]
15struct RevisionInfo<'a> {
16 target_arch: Option<Option<&'a str>>,
17 llvm_components: Option<Vec<&'a str>>,
18}
19
20pub fn check(tests_path: &Path, tidy_ctx: TidyCtx) {
21 let mut check = tidy_ctx.start_check(CheckId::new("target-specific-tests").path(tests_path));
22
23 crate::walk::walk(tests_path, |path, _is_dir| filter_not_rust(path), &mut |entry, content| {
24 if content.contains("// ignore-tidy-target-specific-tests") {
25 return;
26 }
27
28 let file = entry.path().display();
29 let mut header_map = BTreeMap::new();
30 iter_header(content, &mut |HeaderLine { revision, directive, .. }| {
31 if let Some(value) = directive.strip_prefix(LLVM_COMPONENTS_HEADER) {
32 let info = header_map.entry(revision).or_insert(RevisionInfo::default());
33 let comp_vec = info.llvm_components.get_or_insert(Vec::new());
34 for component in value.split(' ') {
35 let component = component.trim();
36 if !component.is_empty() {
37 comp_vec.push(component);
38 }
39 }
40 } else if let Some(compile_flags) = directive.strip_prefix(COMPILE_FLAGS_HEADER)
41 && let Some((_, v)) = compile_flags.split_once("--target")
42 {
43 let v = v.trim_start_matches([' ', '=']);
44 let info = header_map.entry(revision).or_insert(RevisionInfo::default());
45 if v.starts_with("{{") {
46 info.target_arch.replace(None);
47 } else if let Some((arch, _)) = v.split_once("-") {
48 info.target_arch.replace(Some(arch));
49 } else {
50 check.error(format!("{file}: seems to have a malformed --target value"));
51 }
52 }
53 });
54
55 if entry
57 .path()
58 .strip_prefix(tests_path)
59 .is_ok_and(|rest| rest.starts_with("run-make") || rest.starts_with("run-make-cargo"))
60 {
61 return;
62 }
63
64 for (rev, RevisionInfo { target_arch, llvm_components }) in &header_map {
65 let rev = rev.unwrap_or("[unspecified]");
66 match (target_arch, llvm_components) {
67 (None, None) => {}
68 (Some(target_arch), None) => {
69 let llvm_component =
70 target_arch.map_or_else(|| "<arch>".to_string(), arch_to_llvm_component);
71 check.error(format!(
72 "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
73 ));
74 }
75 (None, Some(_)) => {
76 check.error(format!(
77 "{file}: revision {rev} should not specify `{LLVM_COMPONENTS_HEADER}` as it doesn't need `--target`"
78 ));
79 }
80 (Some(target_arch), Some(llvm_components)) => {
81 if let Some(target_arch) = target_arch {
82 let llvm_component = arch_to_llvm_component(target_arch);
83 if !llvm_components.contains(&llvm_component.as_str()) {
84 check.error(format!(
85 "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
86 ));
87 }
88 }
89 }
90 }
91 }
92 });
93}
94
95fn arch_to_llvm_component(arch: &str) -> String {
96 match arch {
100 "amdgcn" => "amdgpu".into(),
101 "aarch64_be" | "arm64_32" | "arm64e" | "arm64ec" => "aarch64".into(),
102 "i386" | "i586" | "i686" | "x86" | "x86_64" | "x86_64h" => "x86".into(),
103 "loongarch32" | "loongarch64" => "loongarch".into(),
104 "nvptx64" => "nvptx".into(),
105 "s390x" => "systemz".into(),
106 "sparc64" | "sparcv9" => "sparc".into(),
107 "wasm32" | "wasm32v1" | "wasm64" => "webassembly".into(),
108 _ if arch.starts_with("armeb")
109 || arch.starts_with("armv")
110 || arch.starts_with("thumbv") =>
111 {
112 "arm".into()
113 }
114 _ if arch.starts_with("bpfe") => "bpf".into(),
115 _ if arch.starts_with("mips") => "mips".into(),
116 _ if arch.starts_with("powerpc") => "powerpc".into(),
117 _ if arch.starts_with("riscv") => "riscv".into(),
118 _ => arch.to_ascii_lowercase(),
119 }
120}