xtask/tasks/fmt/house_rules/
cfg_target_arch.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use anyhow::anyhow;
5use fs_err::File;
6use std::io::BufRead;
7use std::io::BufReader;
8use std::path::Path;
9
10const SUPPRESS: &str = "xtask-fmt allow-target-arch";
11
12/// Using `target_arch` in order to execute CPU-specific intrinsics
13const SUPPRESS_REASON_CPU_INTRINSIC: &str = "cpu-intrinsic";
14/// Using `target_arch` in order to implement a '*-sys'-like crate (where the
15/// structure changes depending on the host-arch)
16const SUPPRESS_REASON_SYS_CRATE: &str = "sys-crate";
17/// A dependency of this crate will not build on other target architectures
18const SUPPRESS_REASON_DEPENDENCY: &str = "dependency";
19/// One off - support for the auto-arch selection logic in
20/// `build_rs_guest_arch`.
21const SUPPRESS_REASON_ONEOFF_GUEST_ARCH_IMPL: &str = "oneoff-guest-arch-impl";
22/// One off - considiton to check that `virt_hvf` is being used when both guest
23/// and host arch to be the same.
24const SUPPRESS_REASON_ONEOFF_VIRT_HVF: &str = "oneoff-virt-hvf";
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/// Onee 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    let ok = 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_VIRT_HVF
47            | SUPPRESS_REASON_ONEOFF_FLOWEY
48            | SUPPRESS_REASON_ONEOFF_PETRI_NATIVE_TEST_DEPS
49            | SUPPRESS_REASON_ONEOFF_PETRI_HOST_ARCH
50    );
51
52    if !ok {
53        log::error!(
54            "invalid justification '{}' (must be one of [sys-crate, cpu-intrinsic]",
55            after.split(' ').next().unwrap()
56        );
57    }
58
59    ok
60}
61
62pub fn check_cfg_target_arch(path: &Path, _fix: bool) -> anyhow::Result<()> {
63    let ext = path
64        .extension()
65        .and_then(|e| e.to_str())
66        .unwrap_or_default();
67
68    if !matches!(ext, "rs") {
69        return Ok(());
70    }
71
72    // need to exclude self (and house_rules.rs, which includes help-text) from
73    // the lint
74    if path == Path::new(file!()) || path == Path::new(super::PATH_TO_HOUSE_RULES_RS) {
75        return Ok(());
76    }
77
78    // guest_test_uefi is a guest-side crate (the code runs in the guest), so
79    // target_arch here is actually referring to the guest_arch
80    //
81    // openhcl_boot uses target_arch liberally, since it runs in VTL2 entirely
82    // in-service to the VTL2 linux kernel, which will always be native-arch.
83    // Similar for the sidecar kernel and TMKs. And minimal_rt provides the
84    // (arch-specific) runtime for both of them.
85    //
86    // safe_intrinsics performs architecture-specific operations that require
87    // the use of target_arch
88    //
89    // the whp/kvm crates are inherently arch-specific, as they contain
90    // low-level bindings to a particular platform's virtualization APIs
91    //
92    // The TMK-related crates run in the guest and are inherently arch-specific.
93    if path.starts_with("guest_test_uefi")
94        || path.starts_with("openhcl/openhcl_boot")
95        || path.starts_with("openhcl/minimal_rt")
96        || path.starts_with("openhcl/sidecar")
97        || path.starts_with("support/safe_intrinsics")
98        || path.starts_with("tmk/simple_tmk")
99        || path.starts_with("tmk/tmk_core")
100        || path.starts_with("vm/whp")
101        || path.starts_with("vm/kvm")
102    {
103        return Ok(());
104    }
105
106    let mut error = false;
107
108    // TODO: this lint really ought to be a dynlint / clippy lint
109    let f = BufReader::new(File::open(path)?);
110    let mut prev_line = String::new();
111    for (i, line) in f.lines().enumerate() {
112        let line = line?;
113        if line.contains("target_arch =") || line.contains("CARGO_CFG_TARGET_ARCH") {
114            // check if current line contains valid suppress, or is commented out
115            if !line.trim().starts_with("//") && !has_suppress(&line) && !has_suppress(&prev_line) {
116                error = true;
117                log::error!(
118                    "unjustified `cfg(target_arch = ...)`: {}:{}",
119                    path.display(),
120                    i + 1
121                );
122            }
123        }
124        prev_line = line;
125    }
126
127    if error {
128        Err(anyhow!(
129            "found unjustified uses of `cfg(target_arch = ...)` in {}",
130            path.display()
131        ))
132    } else {
133        Ok(())
134    }
135}