| #!/usr/bin/env python3 | |
| # Copyright 2018 Mellanox Technologies Inc. | |
| # Licensed under BSD (MIT variant) or GPLv2. See COPYING. | |
| # PYTHON_ARGCOMPLETE_OK | |
| """This script takes a commitish from a kernel tree and synchronizes the RDMA | |
| headers we use with that tree. | |
| During development, before commits are accepted to the official kernel git | |
| tree, the --not-final option should be used. Once finalized the commit should | |
| be revised using --amend, eg using the exec feature of 'git rebase'""" | |
| import argparse | |
| import subprocess | |
| import tempfile | |
| import os | |
| import contextlib | |
| import textwrap | |
| import email.utils | |
| import collections | |
| def git_call(args): | |
| """Run git and display the output to the terminal""" | |
| return subprocess.check_call(['git',] + args); | |
| def git_output(args,mode=None,input=None): | |
| """Run git and return the output""" | |
| o = subprocess.check_output(['git',] + args,input=input); | |
| if mode == "raw": | |
| return o; | |
| return o.strip(); | |
| @contextlib.contextmanager | |
| def in_directory(dir): | |
| """Context manager that chdirs into a directory and restores the original | |
| directory when closed.""" | |
| cdir = os.getcwd(); | |
| old_env = {}; | |
| try: | |
| # git rebase invokes its exec with a bunch of git variables set that | |
| # prevent us from invoking git in another tree, blow them away. | |
| for k in list(os.environ.keys()): | |
| if k.startswith("GIT"): | |
| old_env[k] = os.environ[k]; | |
| del os.environ[k]; | |
| os.chdir(dir); | |
| yield True; | |
| finally: | |
| os.chdir(cdir); | |
| os.environ.update(old_env); | |
| def copy_objects(args): | |
| """Copy the uapi header objects from the kernel repo at the commit indicated | |
| into our repo. This is done by having git copy the tree object and blobs from the | |
| kernel tree into this tree and then revising our index. This is a simple way | |
| to ensure they match exactly.""" | |
| with in_directory(args.KERNEL_GIT): | |
| if args.not_final: | |
| fmt = "--format=?? (\"%s\")"; | |
| else: | |
| fmt = "--format=%h (\"%s\")"; | |
| kernel_desc = git_output(["log", | |
| "--abbrev=12","-1", | |
| fmt, | |
| args.COMMIT]); | |
| ntree = git_output(["rev-parse", | |
| "%s:include/uapi/rdma"%(args.COMMIT)]); | |
| pack = git_output(["pack-objects", | |
| "-q","--revs","--stdout"], | |
| mode="raw", | |
| input=ntree); | |
| git_output(["unpack-objects","-q"],input=pack); | |
| return (ntree,kernel_desc); | |
| def update_cmake(args,ntree): | |
| """Create a new CMakeLists.txt that lists all of the kernel headers | |
| for installation.""" | |
| # We need to expand to a publish_internal_headers for each directory | |
| fns = git_output(["ls-tree","--name-only","--full-tree","-r",ntree]).splitlines(); | |
| groups = collections.defaultdict(list); | |
| for I in fns: | |
| d,p = os.path.split(os.path.join("rdma",I.decode())); | |
| groups[d].append(p); | |
| data = subprocess.check_output(['git',"cat-file","blob", | |
| ":kernel-headers/CMakeLists.txt"]); | |
| data = data.decode(); | |
| # Build a new CMakeLists.txt in a temporary file | |
| with tempfile.NamedTemporaryFile("wt") as F: | |
| # Emit the headers lists | |
| for I,vals in sorted(groups.items()): | |
| F.write("publish_internal_headers(%s\n"%(I)); | |
| for J in sorted(vals): | |
| F.write(" %s\n"%(os.path.join(I,J))); | |
| F.write(" )\n"); | |
| F.write("\n"); | |
| # Throw away the old header lists | |
| cur = iter(data.splitlines()); | |
| for ln in cur: | |
| if not ln: | |
| continue; | |
| if ln.startswith("publish_internal_headers(rdma"): | |
| while not next(cur).startswith(" )"): | |
| pass; | |
| continue; | |
| F.write(ln + '\n'); | |
| break; | |
| # and copy the remaining lines | |
| for ln in cur: | |
| F.write(ln + '\n'); | |
| F.flush(); | |
| blob = git_output(["hash-object","-w",F.name]); | |
| git_call(["update-index","--cacheinfo", | |
| b"0644,%s,kernel-headers/CMakeLists.txt"%(blob)]); | |
| def make_commit(args,ntree,kernel_desc): | |
| """Make the rdma-core commit that syncs the kernel header directory.""" | |
| head_id = git_output(["rev-parse","HEAD"]); | |
| old_tree_id = git_output(["rev-parse",b"%s^{tree}"%(head_id)]); | |
| if args.amend: | |
| subject = git_output(["log","-1","--format=%s"]).decode(); | |
| if subject != "Update kernel headers": | |
| raise ValueError("In amend mode, but current HEAD does not seem to be a kernel update with subject %r"%( | |
| subject)); | |
| parent = git_output(["rev-parse",head_id + b"^"]); | |
| else: | |
| parent = head_id; | |
| emaila = email.utils.formataddr((git_output(["config","user.name"]).decode(), | |
| git_output(["config","user.email"]).decode())); | |
| # Build a new tree object that replaces the kernel headers directory | |
| with tempfile.NamedTemporaryFile() as F: | |
| os.environ["GIT_INDEX_FILE"] = F.name; | |
| git_call(["read-tree",head_id]); | |
| git_call(["rm","-r","--quiet","--cached", | |
| "kernel-headers/rdma"]); | |
| git_call(["read-tree","--prefix=kernel-headers/rdma",ntree]); | |
| update_cmake(args,ntree); | |
| all_tree = git_output(["write-tree"]); | |
| del os.environ["GIT_INDEX_FILE"]; | |
| if not args.amend and old_tree_id == all_tree: | |
| raise ValueError("Commit is empty, aborting"); | |
| # And now create the commit | |
| msg="Update kernel headers\n\n"; | |
| p = textwrap.fill("To commit %s."%(kernel_desc.decode()), | |
| width=74) | |
| msg = msg + p; | |
| msg = msg + "\n\nSigned-off-by: %s\n"%(emaila); | |
| commit = git_output(["commit-tree",all_tree,"-p",parent, | |
| "-F","-"], | |
| input=msg.encode()); | |
| return commit,head_id; | |
| parser = argparse.ArgumentParser(description='Update kernel headers from the kernel tree') | |
| parser.add_argument("--amend", | |
| action="store_true", | |
| default=False, | |
| help="Replace the top commit with the the kernel header commit"); | |
| parser.add_argument("--not-final", | |
| action="store_true", | |
| default=False, | |
| help="Use if the git commit given is not part of the official kernel git tree. This option should be used during development."); | |
| parser.add_argument("KERNEL_GIT", | |
| action="store", | |
| help="Kernel git directory"); | |
| parser.add_argument("COMMIT", | |
| action="store", | |
| help="Kernel commitish to synchronize headers with"); | |
| try: | |
| import argcomplete; | |
| argcomplete.autocomplete(parser); | |
| except ImportError: | |
| pass; | |
| args = parser.parse_args(); | |
| ntree,kernel_desc = copy_objects(args); | |
| commit,head_id = make_commit(args,ntree,kernel_desc); | |
| # Finalize | |
| if args.amend: | |
| print("Commit amended"); | |
| git_call(["--no-pager","diff","--stat",head_id,commit]); | |
| git_call(["reset","--merge",commit]); | |
| else: | |
| git_call(["merge","--ff","--ff-only",commit]); |