kernel_abi_check/manylinux/
mod.rs

1use std::collections::{BTreeSet, HashSet};
2use std::str;
3use std::{collections::HashMap, str::FromStr};
4
5use eyre::{bail, Context, ContextCompat, OptionExt, Result};
6use object::{Architecture, Endianness, ObjectSymbol, Symbol};
7use once_cell::sync::Lazy;
8use serde::Deserialize;
9
10use crate::Version;
11
12#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
13#[serde(untagged)]
14enum ManyLinuxSymbolVersion {
15    Version(Version),
16    // Work with symbol versions like `TM_1`.
17    Raw(String),
18}
19
20#[derive(Debug, Deserialize)]
21struct ManyLinux {
22    name: String,
23    #[allow(dead_code)]
24    aliases: Vec<String>,
25    #[allow(dead_code)]
26    priority: u32,
27    symbol_versions: HashMap<String, HashMap<String, HashSet<ManyLinuxSymbolVersion>>>,
28    #[allow(dead_code)]
29    lib_whitelist: Vec<String>,
30    #[allow(dead_code)]
31    blacklist: HashMap<String, Vec<String>>,
32}
33
34static MANYLINUX_POLICY_JSON: &str = include_str!("manylinux-policy.json");
35
36static MANYLINUX_VERSIONS: Lazy<HashMap<String, ManyLinux>> = Lazy::new(|| {
37    let deserialized: Vec<ManyLinux> = serde_json::from_str(MANYLINUX_POLICY_JSON).unwrap();
38    deserialized
39        .into_iter()
40        .map(|manylinux| (manylinux.name.clone(), manylinux))
41        .collect()
42});
43
44/// A violation of the manylinux policy.
45#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
46pub enum ManylinuxViolation {
47    /// A symbol is not allowed in the manylinux version.
48    Symbol {
49        name: String,
50        dep: String,
51        version: String,
52    },
53}
54
55pub fn check_manylinux<'a>(
56    manylinux_version: &str,
57    architecture: Architecture,
58    endianness: Endianness,
59    symbols: impl IntoIterator<Item = Symbol<'a, 'a>>,
60) -> Result<BTreeSet<ManylinuxViolation>> {
61    let arch_str = architecture.arch_str(endianness)?;
62    let symbol_versions = MANYLINUX_VERSIONS
63        .get(manylinux_version)
64        .context(format!("Unknown manylinux version: {}", manylinux_version))?
65        .symbol_versions
66        .get(&arch_str)
67        .context(format!(
68            "Cannot find arch `{}` for: {}`",
69            arch_str, manylinux_version
70        ))?;
71
72    let mut violations = BTreeSet::new();
73
74    for symbol in symbols {
75        if symbol.is_undefined() {
76            let symbol = symbol.name_bytes().context("Cannot get symbol name")?;
77            let symbol = str::from_utf8(symbol).context("Cannot parse symbol name as UTF-8")?;
78
79            let mut symbol_parts = symbol.split('@');
80            let symbol_name = symbol_parts.next().context("Cannot get symbol name")?;
81
82            let version_info = match symbol_parts.next() {
83                Some(version_info) => version_info,
84                None => continue,
85            };
86
87            let mut version_parts = version_info.split('_');
88
89            let dep = version_parts
90                .next()
91                .ok_or_eyre("Cannot get symbol version name")?;
92
93            let version = match version_parts.next() {
94                Some(version) => Version::from_str(version)?,
95                // We also get symbol versions like: libcudart.so.12
96                None => continue,
97            };
98
99            if let Some(versions) = symbol_versions.get(dep) {
100                if !versions.contains(&ManyLinuxSymbolVersion::Version(version.clone())) {
101                    violations.insert(ManylinuxViolation::Symbol {
102                        name: symbol_name.to_string(),
103                        dep: dep.to_string(),
104                        version: version.to_string(),
105                    });
106                }
107            }
108        }
109    }
110
111    Ok(violations)
112}
113
114pub trait ArchStr {
115    fn arch_str(&self, endiannes: Endianness) -> Result<String>;
116}
117
118impl ArchStr for Architecture {
119    fn arch_str(&self, endiannes: Endianness) -> Result<String> {
120        Ok(match self {
121            Architecture::Aarch64 => "aarch64",
122            Architecture::I386 => "i686",
123            Architecture::PowerPc64 if matches!(endiannes, Endianness::Big) => "ppc64",
124            Architecture::PowerPc64 if matches!(endiannes, Endianness::Little) => "ppc64le",
125            Architecture::S390x => "s390x",
126            Architecture::X86_64 => "x86_64",
127            _ => bail!("Unsupported architecture: {:?}", self),
128        }
129        .to_string())
130    }
131}