219 lines · python
1#!/usr/bin/env python32import argparse3import os4import re5import shlex6import subprocess7import sys8import textwrap9 10msg_prefix = "\n> NFC-Mode:"11 12def get_relevant_bolt_changes(dir: str) -> str:13 # Return a list of bolt source changes that are relevant to testing.14 all_changes = subprocess.run(15 shlex.split("git show HEAD --name-only --pretty=''"),16 cwd=dir,17 text=True,18 stdout=subprocess.PIPE,19 )20 keep_bolt = subprocess.run(21 shlex.split("grep '^bolt'"),22 input=all_changes.stdout,23 text=True,24 stdout=subprocess.PIPE,25 )26 keep_relevant = subprocess.run(27 shlex.split(28 "grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'"29 ),30 input=keep_bolt.stdout,31 text=True,32 stdout=subprocess.PIPE,33 )34 return keep_relevant.stdout35 36def get_git_ref_or_rev(dir: str) -> str:37 # Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD'38 cmd_ref = "git symbolic-ref -q --short HEAD"39 ref = subprocess.run(40 shlex.split(cmd_ref), cwd=dir, text=True, stdout=subprocess.PIPE41 )42 if not ref.returncode:43 return ref.stdout.strip()44 cmd_rev = "git rev-parse --short HEAD"45 return subprocess.check_output(shlex.split(cmd_rev), cwd=dir, text=True).strip()46 47def switch_back(48 switch_back: bool, stash: bool, source_dir: str, old_ref: str, new_ref: str49):50 # Switch back to the current revision if needed and inform the user of where51 # the HEAD is. Must be called after checking out the previous commit on all52 # exit paths.53 if switch_back:54 print(f"{msg_prefix} Switching back to current revision..")55 if stash:56 subprocess.run(shlex.split("git stash pop"), cwd=source_dir)57 subprocess.run(shlex.split(f"git checkout {old_ref}"), cwd=source_dir)58 else:59 print(60 f"The repository {source_dir} has been switched from {old_ref} "61 f"to {new_ref}. Local changes were stashed. Switch back using\n\t"62 f"git checkout {old_ref}\n"63 )64 65def main():66 parser = argparse.ArgumentParser(67 description=textwrap.dedent(68 """69 This script builds two versions of BOLT:70 llvm-bolt.new, using the current revision, and llvm-bolt.old using71 the previous revision. These can be used to check whether the72 current revision changes BOLT's functional behavior.73 """74 )75 )76 parser.add_argument(77 "build_dir",78 nargs="?",79 default=os.getcwd(),80 help="Path to BOLT build directory, default is current " "directory",81 )82 parser.add_argument(83 "--create-wrapper",84 default=False,85 action="store_true",86 help="Sets up llvm-bolt as a symlink to llvm-bolt-wrapper. Passes the options through to llvm-bolt-wrapper.",87 )88 parser.add_argument(89 "--check-bolt-sources",90 default=False,91 action="store_true",92 help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified",93 )94 parser.add_argument(95 "--switch-back",96 default=False,97 action="store_true",98 help="Checkout back to the starting revision",99 )100 parser.add_argument(101 "--cmp-rev",102 default="HEAD^",103 help="Revision to checkout to compare vs HEAD",104 )105 106 # When creating a wrapper, pass any unknown arguments to it. Otherwise, die.107 args, wrapper_args = parser.parse_known_args()108 if not args.create_wrapper and len(wrapper_args) > 0:109 parser.parse_args()110 111 # Find the repo directory.112 source_dir = None113 try:114 CMCacheFilename = f"{args.build_dir}/CMakeCache.txt"115 with open(CMCacheFilename) as f:116 for line in f:117 m = re.match(r"LLVM_SOURCE_DIR:STATIC=(.*)", line)118 if m:119 source_dir = m.groups()[0]120 if not source_dir:121 raise Exception(f"Source directory not found: '{CMCacheFilename}'")122 except Exception as e:123 sys.exit(e)124 125 # Clean the previous llvm-bolt if it exists.126 bolt_path = f"{args.build_dir}/bin/llvm-bolt"127 if os.path.exists(bolt_path):128 os.remove(bolt_path)129 130 # Build the current commit.131 print(f"{msg_prefix} Building current revision..")132 subprocess.run(133 shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir134 )135 136 if not os.path.exists(bolt_path):137 sys.exit(f"Failed to build the current revision: '{bolt_path}'")138 139 # Rename llvm-bolt and memorize the old hash for logging.140 os.replace(bolt_path, f"{bolt_path}.new")141 old_ref = get_git_ref_or_rev(source_dir)142 143 if args.check_bolt_sources:144 marker = f"{args.build_dir}/.llvm-bolt.changes"145 if os.path.exists(marker):146 os.remove(marker)147 file_changes = get_relevant_bolt_changes(source_dir)148 # Create a marker file if any relevant BOLT source files changed.149 if len(file_changes) > 0:150 print(f"BOLT source changes were found:\n{file_changes}")151 open(marker, "a").close()152 153 # Determine whether a stash is needed.154 stash = subprocess.run(155 shlex.split("git status --porcelain"),156 cwd=source_dir,157 stdout=subprocess.PIPE,158 stderr=subprocess.STDOUT,159 text=True,160 ).stdout161 if stash:162 # Save local changes before checkout.163 subprocess.run(shlex.split("git stash push -u"), cwd=source_dir)164 165 # Check out the previous/cmp commit and get its commit hash for logging.166 subprocess.run(shlex.split(f"git checkout -f {args.cmp_rev}"), cwd=source_dir)167 new_ref = get_git_ref_or_rev(source_dir)168 169 # Build the previous commit.170 print(f"{msg_prefix} Building previous revision..")171 subprocess.run(172 shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir173 )174 175 # Rename llvm-bolt.176 if not os.path.exists(bolt_path):177 print(f"Failed to build the previous revision: '{bolt_path}'")178 switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)179 sys.exit(1)180 os.replace(bolt_path, f"{bolt_path}.old")181 182 # Symlink llvm-bolt-wrapper183 if args.create_wrapper:184 print(f"{msg_prefix} Creating llvm-bolt wrapper..")185 script_dir = os.path.dirname(os.path.abspath(__file__))186 wrapper_path = f"{script_dir}/llvm-bolt-wrapper.py"187 try:188 # Set up llvm-bolt-wrapper.ini189 ini = subprocess.check_output(190 shlex.split(f"{wrapper_path} {bolt_path}.old {bolt_path}.new")191 + wrapper_args,192 text=True,193 )194 with open(f"{args.build_dir}/bin/llvm-bolt-wrapper.ini", "w") as f:195 f.write(ini)196 os.symlink(wrapper_path, bolt_path)197 except Exception as e:198 print("Failed to create a wrapper:\n" + str(e))199 switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)200 sys.exit(1)201 202 switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)203 204 print(205 f"{msg_prefix} Completed!\nBuild directory {args.build_dir} is ready for"206 " NFC-Mode comparison between the two revisions."207 )208 209 if args.create_wrapper:210 print(211 "Can run BOLT tests using:\n"212 "\tbin/llvm-lit -sv tools/bolt/test\nor\n"213 "\tbin/llvm-lit -sv tools/bolttests"214 )215 216 217if __name__ == "__main__":218 main()219