kernel_abi_check/manylinux/
mod.rs1use 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 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#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
46pub enum ManylinuxViolation {
47 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 `{arch_str}` for: {manylinux_version}`"
69 ))?;
70
71 let mut violations = BTreeSet::new();
72
73 for symbol in symbols {
74 if symbol.is_undefined() {
75 let symbol = symbol.name_bytes().context("Cannot get symbol name")?;
76 let symbol = str::from_utf8(symbol).context("Cannot parse symbol name as UTF-8")?;
77
78 let mut symbol_parts = symbol.split('@');
79 let symbol_name = symbol_parts.next().context("Cannot get symbol name")?;
80
81 let version_info = match symbol_parts.next() {
82 Some(version_info) => version_info,
83 None => continue,
84 };
85
86 let mut version_parts = version_info.split('_');
87
88 let dep = version_parts
89 .next()
90 .ok_or_eyre("Cannot get symbol version name")?;
91
92 let version = match version_parts.next() {
93 Some(version) => Version::from_str(version)?,
94 None => continue,
96 };
97
98 if let Some(versions) = symbol_versions.get(dep) {
99 if !versions.contains(&ManyLinuxSymbolVersion::Version(version.clone())) {
100 violations.insert(ManylinuxViolation::Symbol {
101 name: symbol_name.to_string(),
102 dep: dep.to_string(),
103 version: version.to_string(),
104 });
105 }
106 }
107 }
108 }
109
110 Ok(violations)
111}
112
113pub trait ArchStr {
114 fn arch_str(&self, endiannes: Endianness) -> Result<String>;
115}
116
117impl ArchStr for Architecture {
118 fn arch_str(&self, endiannes: Endianness) -> Result<String> {
119 Ok(match self {
120 Architecture::Aarch64 => "aarch64",
121 Architecture::I386 => "i686",
122 Architecture::PowerPc64 if matches!(endiannes, Endianness::Big) => "ppc64",
123 Architecture::PowerPc64 if matches!(endiannes, Endianness::Little) => "ppc64le",
124 Architecture::S390x => "s390x",
125 Architecture::X86_64 => "x86_64",
126 _ => bail!("Unsupported architecture: {:?}", self),
127 }
128 .to_string())
129 }
130}