#!/usr/bin/env python3 import argparse import os import re import shlex import subprocess import sys import textwrap msg_prefix = "\n> NFC-Mode:" def get_relevant_bolt_changes(dir: str) -> str: # Return a list of bolt source changes that are relevant to testing. all_changes = subprocess.run( shlex.split("git show HEAD --name-only --pretty=''"), cwd=dir, text=True, stdout=subprocess.PIPE, ) keep_bolt = subprocess.run( shlex.split("grep '^bolt'"), input=all_changes.stdout, text=True, stdout=subprocess.PIPE, ) keep_relevant = subprocess.run( shlex.split( "grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'" ), input=keep_bolt.stdout, text=True, stdout=subprocess.PIPE, ) return keep_relevant.stdout def get_git_ref_or_rev(dir: str) -> str: # Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD' cmd_ref = "git symbolic-ref -q --short HEAD" ref = subprocess.run( shlex.split(cmd_ref), cwd=dir, text=True, stdout=subprocess.PIPE ) if not ref.returncode: return ref.stdout.strip() cmd_rev = "git rev-parse --short HEAD" return subprocess.check_output(shlex.split(cmd_rev), cwd=dir, text=True).strip() def switch_back( switch_back: bool, stash: bool, source_dir: str, old_ref: str, new_ref: str ): # Switch back to the current revision if needed and inform the user of where # the HEAD is. Must be called after checking out the previous commit on all # exit paths. if switch_back: print(f"{msg_prefix} Switching back to current revision..") if stash: subprocess.run(shlex.split("git stash pop"), cwd=source_dir) subprocess.run(shlex.split(f"git checkout {old_ref}"), cwd=source_dir) else: print( f"The repository {source_dir} has been switched from {old_ref} " f"to {new_ref}. Local changes were stashed. Switch back using\n\t" f"git checkout {old_ref}\n" ) def main(): parser = argparse.ArgumentParser( description=textwrap.dedent( """ This script builds two versions of BOLT: llvm-bolt.new, using the current revision, and llvm-bolt.old using the previous revision. These can be used to check whether the current revision changes BOLT's functional behavior. """ ) ) parser.add_argument( "build_dir", nargs="?", default=os.getcwd(), help="Path to BOLT build directory, default is current " "directory", ) parser.add_argument( "--create-wrapper", default=False, action="store_true", help="Sets up llvm-bolt as a symlink to llvm-bolt-wrapper. Passes the options through to llvm-bolt-wrapper.", ) parser.add_argument( "--check-bolt-sources", default=False, action="store_true", help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified", ) parser.add_argument( "--switch-back", default=False, action="store_true", help="Checkout back to the starting revision", ) parser.add_argument( "--cmp-rev", default="HEAD^", help="Revision to checkout to compare vs HEAD", ) # When creating a wrapper, pass any unknown arguments to it. Otherwise, die. args, wrapper_args = parser.parse_known_args() if not args.create_wrapper and len(wrapper_args) > 0: parser.parse_args() # Find the repo directory. source_dir = None try: CMCacheFilename = f"{args.build_dir}/CMakeCache.txt" with open(CMCacheFilename) as f: for line in f: m = re.match(r"LLVM_SOURCE_DIR:STATIC=(.*)", line) if m: source_dir = m.groups()[0] if not source_dir: raise Exception(f"Source directory not found: '{CMCacheFilename}'") except Exception as e: sys.exit(e) # Clean the previous llvm-bolt if it exists. bolt_path = f"{args.build_dir}/bin/llvm-bolt" if os.path.exists(bolt_path): os.remove(bolt_path) # Build the current commit. print(f"{msg_prefix} Building current revision..") subprocess.run( shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir ) if not os.path.exists(bolt_path): sys.exit(f"Failed to build the current revision: '{bolt_path}'") # Rename llvm-bolt and memorize the old hash for logging. os.replace(bolt_path, f"{bolt_path}.new") old_ref = get_git_ref_or_rev(source_dir) if args.check_bolt_sources: marker = f"{args.build_dir}/.llvm-bolt.changes" if os.path.exists(marker): os.remove(marker) file_changes = get_relevant_bolt_changes(source_dir) # Create a marker file if any relevant BOLT source files changed. if len(file_changes) > 0: print(f"BOLT source changes were found:\n{file_changes}") open(marker, "a").close() # Determine whether a stash is needed. stash = subprocess.run( shlex.split("git status --porcelain"), cwd=source_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, ).stdout if stash: # Save local changes before checkout. subprocess.run(shlex.split("git stash push -u"), cwd=source_dir) # Check out the previous/cmp commit and get its commit hash for logging. subprocess.run(shlex.split(f"git checkout -f {args.cmp_rev}"), cwd=source_dir) new_ref = get_git_ref_or_rev(source_dir) # Build the previous commit. print(f"{msg_prefix} Building previous revision..") subprocess.run( shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir ) # Rename llvm-bolt. if not os.path.exists(bolt_path): print(f"Failed to build the previous revision: '{bolt_path}'") switch_back(args.switch_back, stash, source_dir, old_ref, new_ref) sys.exit(1) os.replace(bolt_path, f"{bolt_path}.old") # Symlink llvm-bolt-wrapper if args.create_wrapper: print(f"{msg_prefix} Creating llvm-bolt wrapper..") script_dir = os.path.dirname(os.path.abspath(__file__)) wrapper_path = f"{script_dir}/llvm-bolt-wrapper.py" try: # Set up llvm-bolt-wrapper.ini ini = subprocess.check_output( shlex.split(f"{wrapper_path} {bolt_path}.old {bolt_path}.new") + wrapper_args, text=True, ) with open(f"{args.build_dir}/bin/llvm-bolt-wrapper.ini", "w") as f: f.write(ini) os.symlink(wrapper_path, bolt_path) except Exception as e: print("Failed to create a wrapper:\n" + str(e)) switch_back(args.switch_back, stash, source_dir, old_ref, new_ref) sys.exit(1) switch_back(args.switch_back, stash, source_dir, old_ref, new_ref) print( f"{msg_prefix} Completed!\nBuild directory {args.build_dir} is ready for" " NFC-Mode comparison between the two revisions." ) if args.create_wrapper: print( "Can run BOLT tests using:\n" "\tbin/llvm-lit -sv tools/bolt/test\nor\n" "\tbin/llvm-lit -sv tools/bolttests" ) if __name__ == "__main__": main()