mirror of
https://github.com/hermitcore/libhermit.git
synced 2025-03-30 00:00:15 +01:00
205 lines
7.4 KiB
Python
Executable file
205 lines
7.4 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# Copyright 2017 Obsidian Research Corp.
|
|
# Licensed under BSD (MIT variant) or GPLv2. See COPYING.
|
|
"""check-build - Run static checks on a build"""
|
|
import argparse
|
|
import inspect
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from contextlib import contextmanager;
|
|
|
|
def get_src_dir():
|
|
"""Get the source directory using git"""
|
|
git_top = subprocess.check_output(["git","rev-parse","--git-dir"]).strip();
|
|
if git_top == ".git":
|
|
return ".";
|
|
return os.path.dirname(git_top);
|
|
|
|
def get_package_version(args):
|
|
"""Return PACKAGE_VERSION from CMake"""
|
|
with open(os.path.join(args.SRC,"CMakeLists.txt")) as F:
|
|
for ln in F:
|
|
g = re.match(r'^set\(PACKAGE_VERSION "(.+)"\)',ln)
|
|
if g is None:
|
|
continue;
|
|
return g.group(1);
|
|
raise RuntimeError("Could not find version");
|
|
|
|
@contextmanager
|
|
def inDirectory(dir):
|
|
cdir = os.getcwd();
|
|
try:
|
|
os.chdir(dir);
|
|
yield True;
|
|
finally:
|
|
os.chdir(cdir);
|
|
|
|
@contextmanager
|
|
def private_tmp():
|
|
"""Simple version of Python 3's tempfile.TemporaryDirectory"""
|
|
dfn = tempfile.mkdtemp();
|
|
try:
|
|
yield dfn;
|
|
finally:
|
|
shutil.rmtree(dfn);
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
def get_symbol_vers(fn,exported=True):
|
|
"""Return the symbol version suffixes from the ELF file, eg IB_VERBS_1.0, etc"""
|
|
syms = subprocess.check_output(["readelf","--wide","-s",fn]);
|
|
go = False;
|
|
res = set();
|
|
for I in syms.splitlines():
|
|
if I.startswith("Symbol table '.dynsym'"):
|
|
go = True;
|
|
continue;
|
|
|
|
if I.startswith(" ") and go:
|
|
itms = I.split();
|
|
if exported:
|
|
if (len(itms) == 8 and itms[3] == "OBJECT" and
|
|
itms[4] == "GLOBAL" and itms[6] == "ABS"):
|
|
res.add(itms[7]);
|
|
else:
|
|
if (len(itms) >= 8 and itms[3] == "FUNC" and
|
|
itms[4] == "GLOBAL" and itms[6] == "UND"):
|
|
res.add(itms[7]);
|
|
else:
|
|
go = False;
|
|
if not res:
|
|
raise ValueError("Failed to read ELF symbol versions from %r"%(fn));
|
|
return res;
|
|
|
|
def check_lib_symver(args,fn):
|
|
g = re.match(r"lib([^.]+)\.so\.(\d+)\.(\d+)\.(.*)",fn);
|
|
if g.group(4) != args.PACKAGE_VERSION:
|
|
raise ValueError("Shared Library filename %r does not have the package version %r (%r)%"(
|
|
fn,args.PACKAGE_VERSION,g.groups()));
|
|
|
|
# umad used the wrong symbol version name when they moved to soname 3.0
|
|
if g.group(1) == "ibumad":
|
|
newest_symver = "%s_%s.%s"%(g.group(1).upper(),'1',g.group(3));
|
|
else:
|
|
newest_symver = "%s_%s.%s"%(g.group(1).upper(),g.group(2),g.group(3));
|
|
|
|
syms = get_symbol_vers(fn);
|
|
if newest_symver not in syms:
|
|
raise ValueError("Symbol version %r implied by filename %r not in ELF (%r)"%(
|
|
newest_symver,fn,syms));
|
|
|
|
# The private symbol tag should also be older than the package version
|
|
private = set(I for I in syms if "PRIVATE" in I)
|
|
if len(private) > 1:
|
|
raise ValueError("Too many private symbol versions in ELF %r (%r)"%(fn,private));
|
|
if private:
|
|
private_rel = list(private)[0].split('_')[-1];
|
|
if private_rel > args.PACKAGE_VERSION:
|
|
raise ValueError("Private Symbol Version %r is newer than the package version %r"%(
|
|
private,args.PACKAGE_VERSION));
|
|
|
|
syms = list(syms - private);
|
|
syms.sort(key=lambda x:re.split('[._]',x));
|
|
if newest_symver != syms[-1]:
|
|
raise ValueError("Symbol version %r implied by filename %r not the newest in ELF (%r)"%(
|
|
newest_symver,fn,syms));
|
|
|
|
def test_lib_names(args):
|
|
"""Check that the library filename matches the symbol versions"""
|
|
libd = os.path.join(args.BUILD,"lib");
|
|
|
|
# List of shlibs that follow the ABI guidelines
|
|
libs = {};
|
|
with inDirectory(libd):
|
|
for fn in os.listdir("."):
|
|
if os.path.islink(fn):
|
|
lfn = os.readlink(fn);
|
|
if not os.path.islink(lfn):
|
|
check_lib_symver(args,lfn);
|
|
# -------------------------------------------------------------------------
|
|
|
|
def check_verbs_abi(args,fn):
|
|
g = re.match(r"lib([^-]+)-rdmav(\d+).so",fn);
|
|
if g is None:
|
|
raise ValueError("Provider library has unknown file name format %r"%(fn));
|
|
|
|
private_ver = int(g.group(2));
|
|
syms = get_symbol_vers(fn,exported=False);
|
|
syms = {I.partition("@")[2] for I in syms};
|
|
assert "IBVERBS_PRIVATE_%u"%(private_ver) in syms;
|
|
assert len([I for I in syms if I.startswith("IBVERBS_PRIVATE")]) == 1;
|
|
|
|
def test_verbs_private(args):
|
|
"""Check that the IBVERBS_PRIVATE symbols match the library name, eg that the
|
|
map file and the cmake stuff are in sync."""
|
|
libd = os.path.join(args.BUILD,"lib");
|
|
with inDirectory(libd):
|
|
for fn in os.listdir("."):
|
|
if not os.path.islink(fn) and "rdmav" in fn:
|
|
check_verbs_abi(args,fn);
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
def is_obsolete(fn):
|
|
"""True if the header is obsolete and should not be compiled anyhow."""
|
|
with open(fn) as F:
|
|
for ln in F.readlines():
|
|
if re.search(r"#warning.*This header is obsolete",ln):
|
|
return True;
|
|
return False;
|
|
|
|
def is_fixup(fn):
|
|
"""True if this is a fixup header, fixup headers are exempted because they
|
|
required includes are not the same for kernel headers (eg netinet/in.h)"""
|
|
if os.path.islink(fn):
|
|
return "buildlib/fixup-include/" in os.readlink(fn);
|
|
return False;
|
|
|
|
def test_public_headers(args):
|
|
"""Test that every header file can be included on its own, and has no obvious
|
|
implicit dependencies. This is mainly intended to check the public
|
|
headers, but this sweeps in published internal headers too."""
|
|
incdir = os.path.abspath(os.path.join(args.BUILD,"include"));
|
|
includes = set();
|
|
for root,dirs,files in os.walk(incdir):
|
|
for I in files:
|
|
if I.endswith(".h"):
|
|
includes.add(os.path.join(root,I));
|
|
|
|
# Make a little ninja file to compile each header
|
|
with private_tmp() as tmpd:
|
|
with open(os.path.join(tmpd,"build.ninja"),"wt") as F:
|
|
print >> F,"rule comp";
|
|
print >> F," command = %s -Werror -c -I %s $in -o $out"%(args.CC,incdir);
|
|
print >> F," description=Header check for $in";
|
|
count = 0;
|
|
for I in sorted(includes):
|
|
if is_obsolete(I) or is_fixup(I):
|
|
continue;
|
|
print >> F,"build %s : comp %s"%("out%d.o"%(count),I);
|
|
print >> F,"default %s"%("out%d.o"%(count));
|
|
count = count + 1;
|
|
subprocess.check_call(["ninja"],cwd=tmpd);
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
parser = argparse.ArgumentParser(description='Run build time tests')
|
|
parser.add_argument("--build",default=os.getcwd(),dest="BUILD",
|
|
help="Build directory to inpsect");
|
|
parser.add_argument("--src",default=None,dest="SRC",
|
|
help="Top of the source tree");
|
|
parser.add_argument("--cc",default="cc",dest="CC",
|
|
help="C compiler to use");
|
|
args = parser.parse_args();
|
|
|
|
if args.SRC is None:
|
|
args.SRC = get_src_dir();
|
|
args.PACKAGE_VERSION = get_package_version(args);
|
|
|
|
funcs = globals();
|
|
for k,v in funcs.items():
|
|
if k.startswith("test_") and inspect.isfunction(v):
|
|
v(args);
|