1
0
Fork 0
mirror of https://github.com/hermitcore/libhermit.git synced 2025-03-30 00:00:15 +01:00
libhermit/usr/rdma-core/buildlib/check-build
2017-10-06 15:13:04 +02:00

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);