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