Skip to main content

xtask/tasks/fmt/lints/
cfg_target_arch.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Checks that code uses `guest_arch` instead of `target_arch` for
5//! guest-architecture-specific conditionals.
6
7use super::Lint;
8use super::LintCtx;
9use super::Lintable;
10use std::path::Path;
11use toml_edit::DocumentMut;
12
13const SUPPRESS: &str = "xtask-fmt allow-target-arch";
14
15/// Using `target_arch` in order to execute CPU-specific intrinsics
16const SUPPRESS_REASON_CPU_INTRINSIC: &str = "cpu-intrinsic";
17/// Using `target_arch` in order to implement a '*-sys'-like crate (where the
18/// structure changes depending on the host-arch)
19const SUPPRESS_REASON_SYS_CRATE: &str = "sys-crate";
20/// A dependency of this crate will not build on other target architectures
21const SUPPRESS_REASON_DEPENDENCY: &str = "dependency";
22/// One off - support for the auto-arch selection logic in
23/// `build_rs_guest_arch`.
24const SUPPRESS_REASON_ONEOFF_GUEST_ARCH_IMPL: &str = "oneoff-guest-arch-impl";
25/// One off - used as part of flowey CI infra
26const SUPPRESS_REASON_ONEOFF_FLOWEY: &str = "oneoff-flowey";
27/// One off - used by petri to select native test dependencies
28const SUPPRESS_REASON_ONEOFF_PETRI_NATIVE_TEST_DEPS: &str = "oneoff-petri-native-test-deps";
29/// One off - used by petri to return the host architecture for test filtering
30const SUPPRESS_REASON_ONEOFF_PETRI_HOST_ARCH: &str = "oneoff-petri-host-arch";
31
32fn has_suppress(s: &str) -> bool {
33    let Some((_, after)) = s.split_once(SUPPRESS) else {
34        return false;
35    };
36
37    let after = after.trim();
38    let justification = after.split(' ').next().unwrap();
39
40    matches!(
41        justification,
42        SUPPRESS_REASON_CPU_INTRINSIC
43            | SUPPRESS_REASON_SYS_CRATE
44            | SUPPRESS_REASON_DEPENDENCY
45            | SUPPRESS_REASON_ONEOFF_GUEST_ARCH_IMPL
46            | SUPPRESS_REASON_ONEOFF_FLOWEY
47            | SUPPRESS_REASON_ONEOFF_PETRI_NATIVE_TEST_DEPS
48            | SUPPRESS_REASON_ONEOFF_PETRI_HOST_ARCH
49    )
50}
51
52/// Paths that are exempt from the target_arch lint.
53fn is_exempt(path: &Path) -> bool {
54    // guest_test_uefi is a guest-side crate (the code runs in the guest), so
55    // target_arch here is actually referring to the guest_arch
56    //
57    // openhcl_boot uses target_arch liberally, since it runs in VTL2 entirely
58    // in-service to the VTL2 linux kernel, which will always be native-arch.
59    // Similar for the sidecar kernel and TMKs. And minimal_rt provides the
60    // (arch-specific) runtime for both of them.
61    //
62    // support crates are not VM specific, so guest_arch doesn't make sense
63    // there.
64    //
65    // the whp/kvm crates are inherently arch-specific, as they contain
66    // low-level bindings to a particular platform's virtualization APIs
67    //
68    // The TMK-related crates run in the guest and are inherently arch-specific.
69    path.starts_with("guest_test_uefi")
70        || path.starts_with("openhcl/openhcl_boot")
71        || path.starts_with("openhcl/minimal_rt")
72        || path.starts_with("openhcl/minimal_rt_reloc")
73        || path.starts_with("openhcl/sidecar")
74        || path.starts_with("support")
75        || path.starts_with("tmk/simple_tmk")
76        || path.starts_with("tmk/tmk_core")
77        || path.starts_with("vm/whp")
78        || path.starts_with("vm/kvm")
79}
80
81pub struct CfgTargetArch;
82
83impl Lint for CfgTargetArch {
84    fn new(_ctx: &LintCtx) -> Self {
85        CfgTargetArch
86    }
87
88    fn enter_workspace(&mut self, _content: &Lintable<DocumentMut>) {}
89    fn enter_crate(&mut self, _content: &Lintable<DocumentMut>) {}
90
91    fn visit_file(&mut self, content: &mut Lintable<String>) {
92        let path = content.path();
93
94        // Exclude ourselves from the lint (we mention the patterns in strings).
95        if path.ends_with(file!()) || is_exempt(path) {
96            return;
97        }
98
99        let mut prev_line = "";
100        for (i, line) in content.lines().enumerate() {
101            if line.contains("target_arch =") || line.contains("CARGO_CFG_TARGET_ARCH") {
102                // check if current line contains valid suppress, or is commented out
103                if !line.trim().starts_with("//") && !has_suppress(line) && !has_suppress(prev_line)
104                {
105                    content.unfixable(&format!(
106                        "unjustified `cfg(target_arch = ...)` at line {}",
107                        i + 1
108                    ));
109                }
110            }
111            prev_line = line;
112        }
113    }
114
115    fn exit_crate(&mut self, _content: &mut Lintable<DocumentMut>) {}
116    fn exit_workspace(&mut self, _content: &mut Lintable<DocumentMut>) {}
117}