| #!/usr/bin/env python3 | |
| # Copyright 2015-2017 Obsidian Research Corp. | |
| # Licensed under BSD (MIT variant) or GPLv2. See COPYING. | |
| import argparse | |
| import subprocess | |
| import os | |
| import collections | |
| import re | |
| import itertools | |
| headers = { | |
| "bits/sysmacros.h", | |
| "endian.h", | |
| "netinet/in.h", | |
| "pthread.h", | |
| "stdatomic.h", | |
| "stdlib.h", | |
| "sys/socket.h", | |
| }; | |
| def norm_header(fn): | |
| for I in headers: | |
| flat = I.replace("/","-"); | |
| if fn.endswith(flat): | |
| return I; | |
| if fn.endswith(flat + ".diff"): | |
| return I; | |
| return None; | |
| def find_system_header(args,hdr): | |
| """/usr/include is not always where the include files are, particularly if we | |
| are running full multi-arch as the azure_pipeline container does. Get gcc | |
| to tell us where /usr/include is""" | |
| if "incpath" not in args: | |
| cpp = subprocess.check_output([args.cc, "-print-prog-name=cpp"],universal_newlines=True).strip() | |
| data = subprocess.check_output([cpp, "-v"],universal_newlines=True,stdin=subprocess.DEVNULL, | |
| stderr=subprocess.STDOUT) | |
| args.incpath = []; | |
| for incdir in re.finditer(r"^ (/\S+)$", data, re.MULTILINE): | |
| incdir = incdir.group(1) | |
| if "fixed" in incdir: | |
| continue; | |
| args.incpath.append(incdir) | |
| for incdir in args.incpath: | |
| fn = os.path.join(incdir,hdr) | |
| if os.path.exists(fn): | |
| return fn | |
| return None; | |
| def get_buildlib_patches(dfn): | |
| """Within the buildlib directory we store patches for the glibc headers. Each | |
| patch is in a numbered sub directory that indicates the order to try, the | |
| number should match the glibc version used to make the diff.""" | |
| ver_hdrs = []; | |
| all_hdrs = [] | |
| for d,_,files in os.walk(dfn): | |
| for I in files: | |
| if d != dfn: | |
| bn = int(os.path.basename(d)); | |
| else: | |
| bn = 0; | |
| if bn == 0: | |
| all_hdrs.append(os.path.join(d,I)); | |
| else: | |
| ver_hdrs.append((bn,os.path.join(d,I))); | |
| ver_hdrs.sort(reverse=True); | |
| def add_to_dict(d,lst): | |
| for I in lst: | |
| nh = norm_header(I) | |
| if nh is None: | |
| continue; | |
| assert nh not in d | |
| d[nh] = (I, find_system_header(args,nh)) | |
| ret = [] | |
| for k,g in itertools.groupby(ver_hdrs,key=lambda x:x[0]): | |
| dd = {} | |
| ret.append(dd) | |
| add_to_dict(dd,(I for _,I in g)) | |
| add_to_dict(dd,all_hdrs) | |
| return ret; | |
| def is_patch(fn): | |
| with open(fn) as F: | |
| return F.read(10).startswith("-- /"); | |
| def apply_patch(src,patch,dest): | |
| """Patch a single system header. The output goes into our include search path | |
| and takes precedence over the system version.""" | |
| if src is None: | |
| return False | |
| dfn = os.path.dirname(dest); | |
| if not os.path.isdir(dfn): | |
| os.makedirs(dfn); | |
| if not patch.endswith(".diff"): | |
| if not os.path.exists(dest): | |
| os.symlink(patch,dest); | |
| return True; | |
| try: | |
| if os.path.exists(dest + ".rej"): | |
| os.unlink(dest + ".rej"); | |
| subprocess.check_output(["patch","-f","--follow-symlinks","-V","never","-i",patch,"-o",dest,src]); | |
| if os.path.exists(dest + ".rej"): | |
| print("Patch from %r failed"%(patch)); | |
| return False; | |
| except subprocess.CalledProcessError: | |
| print("Patch from %r failed"%(patch)); | |
| return False; | |
| return True; | |
| def replace_headers(suite): | |
| # Local system does not have the reference system header, this suite is | |
| # not supported | |
| for fn,pfn in suite.items(): | |
| if pfn[1] is None: | |
| return False; | |
| for fn,pfn in suite.items(): | |
| if not apply_patch(pfn[1],pfn[0],os.path.join(args.INCLUDE,fn)): | |
| break; | |
| else: | |
| return True; | |
| for fn,_ in suite.items(): | |
| try: | |
| os.unlink(os.path.join(args.INCLUDE,fn)) | |
| except OSError: | |
| continue; | |
| return False; | |
| def save(fn,outdir): | |
| """Diff the header file in our include directory against the system header and | |
| store the diff into buildlib. This makes it fairly easy to maintain the | |
| replacement headers.""" | |
| if os.path.islink(os.path.join(args.INCLUDE,fn)): | |
| return; | |
| flatfn = fn.replace("/","-") + ".diff"; | |
| flatfn = os.path.join(outdir,flatfn); | |
| includefn = os.path.join(args.INCLUDE,fn) | |
| if not os.path.exists(includefn): | |
| return | |
| cwd = os.getcwd() | |
| with open(flatfn,"wt") as F: | |
| os.chdir(os.path.join(args.INCLUDE,"..")) | |
| try: | |
| subprocess.check_call(["diff","-u", | |
| find_system_header(args,fn), | |
| os.path.join("include",fn)], | |
| stdout=F); | |
| except subprocess.CalledProcessError as ex: | |
| if ex.returncode == 1: | |
| return; | |
| raise; | |
| finally: | |
| os.chdir(cwd) | |
| parser = argparse.ArgumentParser(description='Produce sparse shim header files') | |
| parser.add_argument("--out",dest="INCLUDE",required=True, | |
| help="Directory to write header files to"); | |
| parser.add_argument("--src",dest="SRC",required=True, | |
| help="Top of the source tree"); | |
| parser.add_argument("--cc",default="gcc", | |
| help="System compiler to use to locate the default system headers"); | |
| parser.add_argument("--save",action="store_true",default=False, | |
| help="Save mode will write the current content of the headers to buildlib as a diff."); | |
| args = parser.parse_args(); | |
| if args.save: | |
| # Get the glibc version string | |
| ver = subprocess.check_output(["ldd","--version"]).decode() | |
| ver = ver.splitlines()[0].split(' ')[-1]; | |
| ver = ver.partition(".")[-1]; | |
| outdir = os.path.join(args.SRC,"buildlib","sparse-include",ver); | |
| if not os.path.isdir(outdir): | |
| os.makedirs(outdir); | |
| for I in headers: | |
| save(I,outdir); | |
| else: | |
| failed = False; | |
| suites = get_buildlib_patches(os.path.join(args.SRC,"buildlib","sparse-include")); | |
| for I in suites: | |
| if replace_headers(I): | |
| break; | |
| else: | |
| raise ValueError("Patch applications failed"); |