scx_utils/
clang_info.rs

1use std::{collections::HashMap, env, process::Command};
2
3use anyhow::{anyhow, bail, Context, Result};
4use sscanf::sscanf;
5
6lazy_static::lazy_static! {
7    // Map clang archs to the __TARGET_ARCH list in
8    // tools/lib/bpf/bpf_tracing.h in the kernel tree.
9    static ref ARCH_MAP: HashMap<&'static str, &'static str> = vec![
10    ("x86", "x86"),
11    ("x86_64", "x86"),
12    ("s390", "s390"),
13    ("s390x", "s390"),
14    ("arm", "arm"),
15    ("aarch64", "arm64"),
16    ("mips", "mips"),
17    ("mips64", "mips"),
18    ("ppc32", "powerpc"),
19    ("ppc64", "powerpc"),
20    ("ppc64le", "powerpc"),
21    ("powerpc64le", "powerpc"),
22    ("sparc", "sparc"),
23    ("sparcv9", "sparc"),
24    ("riscv32", "riscv"),
25    ("riscv64", "riscv"),
26    ("riscv64gc", "riscv"),
27    ("arc", "arc"),			// unsure this is supported
28    ("loongarch64", "loongarch"),	// ditto
29    ].into_iter().collect();
30}
31#[derive(Debug)]
32#[allow(dead_code)]
33pub struct ClangInfo {
34    pub clang: String,
35    pub ver: String,
36    pub arch: String,
37}
38
39impl ClangInfo {
40    pub fn new() -> Result<ClangInfo> {
41        let mut clang_args = vec!["--version".to_string()];
42
43        if let Ok(target) = env::var("TARGET") {
44            clang_args.push(format!("--target={}", target));
45        }
46
47        let clang = env::var("BPF_CLANG").unwrap_or("clang".into());
48        let output = Command::new(&clang)
49            .args(clang_args)
50            .output()
51            .with_context(|| format!("Failed to run \"{} --version\"", &clang))?;
52
53        let stdout = String::from_utf8(output.stdout)?;
54        let (mut ver, mut arch) = (None, None);
55        for line in stdout.lines() {
56            if let Ok(v) = sscanf!(
57                Self::skip_clang_version_prefix(line),
58                "clang version {String}"
59            ) {
60                // Version could be followed by (URL SHA1). Only take
61                // the first word.
62                ver = Some(v.split_whitespace().next().unwrap().to_string());
63                continue;
64            }
65            if let Ok(v) = sscanf!(line, "Target: {String}") {
66                arch = Some(v.split('-').next().unwrap().to_string());
67                continue;
68            }
69        }
70
71        let (ver, arch) = (
72            ver.ok_or(anyhow!("Failed to read clang version"))?,
73            arch.ok_or(anyhow!("Failed to read clang target arch"))?,
74        );
75
76        if version_compare::compare(&ver, "16") == Ok(version_compare::Cmp::Lt) {
77            bail!(
78                "clang < 16 loses high 32 bits of 64 bit enums when compiling BPF ({:?} ver={:?})",
79                &clang,
80                &ver
81            );
82        }
83        if version_compare::compare(&ver, "17") == Ok(version_compare::Cmp::Lt) {
84            println!(
85                "cargo:warning=clang >= 17 recommended ({:?} ver={:?})",
86                &clang, &ver
87            );
88        }
89
90        Ok(ClangInfo { clang, ver, arch })
91    }
92
93    fn skip_clang_version_prefix(line: &str) -> &str {
94        if let Some(index) = line.find("clang version") {
95            &line[index..]
96        } else {
97            line
98        }
99    }
100
101    pub fn kernel_target(&self) -> Result<String> {
102        // Determine kernel target arch.
103        match ARCH_MAP.get(self.arch.as_str()) {
104            Some(v) => Ok(v.to_string()),
105            None => Err(anyhow!("CPU arch {} not found in ARCH_MAP", self.arch)),
106        }
107    }
108
109    #[allow(dead_code)] // for it is not used during build script execution
110    pub fn determine_base_cflags(&self) -> Result<Vec<String>> {
111        let kernel_target = self.kernel_target()?;
112
113        // Determine system includes.
114        let output = Command::new(&self.clang)
115            .args(["-v", "-E", "-"])
116            .output()
117            .with_context(|| format!("Failed to run \"{} -v -E - < /dev/null", self.clang))?;
118        let stderr = String::from_utf8(output.stderr)?;
119
120        let mut sys_incls = None;
121        for line in stderr.lines() {
122            if line == "#include <...> search starts here:" {
123                sys_incls = Some(vec![]);
124                continue;
125            }
126            if sys_incls.is_none() {
127                continue;
128            }
129            if line == "End of search list." {
130                break;
131            }
132
133            sys_incls.as_mut().unwrap().push(line.trim());
134        }
135        let sys_incls = match sys_incls {
136            Some(v) => v,
137            None => bail!("Failed to find system includes from {:?}", self.clang),
138        };
139
140        // Determine endian.
141        let output = Command::new(&self.clang)
142            .args(["-dM", "-E", "-"])
143            .output()
144            .with_context(|| format!("Failed to run \"{} -dM E - < /dev/null", self.clang))?;
145        let stdout = String::from_utf8(output.stdout)?;
146
147        let mut endian = None;
148        for line in stdout.lines() {
149            if let Ok(v) = sscanf!(line, "#define __BYTE_ORDER__ {str}") {
150                endian = Some(match v {
151                    "__ORDER_LITTLE_ENDIAN__" => "little",
152                    "__ORDER_BIG_ENDIAN__" => "big",
153                    v => bail!("Unknown __BYTE_ORDER__ {:?}", v),
154                });
155                break;
156            }
157        }
158        let endian = match endian {
159            Some(v) => v,
160            None => bail!("Failed to find __BYTE_ORDER__ from {:?}", self.clang),
161        };
162
163        // Assemble cflags.
164        let mut cflags: Vec<String> = ["-g", "-O2", "-Wall", "-Wno-compare-distinct-pointer-types"]
165            .into_iter()
166            .map(|x| x.into())
167            .collect();
168        cflags.push(format!("-D__TARGET_ARCH_{}", &kernel_target));
169        cflags.push("-mcpu=v3".into());
170        cflags.push(format!("-m{}-endian", endian));
171        cflags.append(
172            &mut sys_incls
173                .into_iter()
174                .flat_map(|x| ["-idirafter".into(), x.into()])
175                .collect(),
176        );
177        Ok(cflags)
178    }
179}