1#!/usr/bin/env python3 2# Copyright 2015 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Rolls third_party/boringssl/src in DEPS and updates generated build files.""" 7 8import importlib 9import os 10import os.path 11import shutil 12import subprocess 13import sys 14 15 16SCRIPT_PATH = os.path.abspath(__file__) 17SRC_PATH = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_PATH))) 18DEPS_PATH = os.path.join(SRC_PATH, 'DEPS') 19BORINGSSL_PATH = os.path.join(SRC_PATH, 'third_party', 'boringssl') 20BORINGSSL_SRC_PATH = os.path.join(BORINGSSL_PATH, 'src') 21 22if not os.path.isfile(DEPS_PATH) or not os.path.isdir(BORINGSSL_SRC_PATH): 23 raise Exception('Could not find Chromium checkout') 24 25# Pull OS_ARCH_COMBOS out of the BoringSSL script. 26sys.path.append(os.path.join(BORINGSSL_SRC_PATH, 'util')) 27import generate_build_files 28 29GENERATED_FILES = [ 30 'BUILD.generated.gni', 31 'BUILD.generated_tests.gni', 32 'err_data.c', 33] 34 35 36def IsPristine(repo): 37 """Returns True if a git checkout is pristine.""" 38 cmd = ['git', 'diff', '--ignore-submodules'] 39 return not (subprocess.check_output(cmd, cwd=repo).strip() or 40 subprocess.check_output(cmd + ['--cached'], cwd=repo).strip()) 41 42 43def RevParse(repo, rev): 44 """Resolves a string to a git commit.""" 45 # Use text to get the revision as a string. We assume rev-parse is always 46 # valid UTF-8. 47 return subprocess.check_output(['git', 'rev-parse', rev], cwd=repo, 48 text=True).strip() 49 50 51def UpdateDEPS(deps, from_hash, to_hash): 52 """Updates all references of |from_hash| to |to_hash| in |deps|.""" 53 from_hash_bytes = from_hash.encode('utf-8') 54 to_hash_bytes = to_hash.encode('utf-8') 55 with open(deps, 'rb') as f: 56 contents = f.read() 57 if from_hash_bytes not in contents: 58 raise Exception('%s not in DEPS' % from_hash_bytes) 59 contents = contents.replace(from_hash_bytes, to_hash_bytes) 60 with open(deps, 'wb') as f: 61 f.write(contents) 62 63 64def Log(repo, revspec): 65 """Returns the commits in |repo| covered by |revspec|.""" 66 # The commit message may not be valid UTF-8, so convert decode errors to 67 # replacement characters. 68 data = subprocess.check_output(['git', 'log', '--pretty=raw', revspec], 69 cwd=repo, text=True, errors='replace') 70 commits = [] 71 chunks = data.split('\n\n') 72 if len(chunks) % 2 != 0: 73 raise ValueError('Invalid log format') 74 for i in range(0, len(chunks), 2): 75 commit = {} 76 # Parse commit properties. 77 for line in chunks[i].split('\n'): 78 name, value = line.split(' ', 1) 79 commit[name] = value 80 if 'commit' not in commit: 81 raise ValueError('Missing commit line') 82 # Parse commit message. 83 message = '' 84 lines = chunks[i+1].split('\n') 85 # Removing the trailing empty entry. 86 if lines and not lines[-1]: 87 lines.pop() 88 for line in lines: 89 INDENT = ' ' 90 if not line.startswith(INDENT): 91 raise ValueError('Missing indent') 92 message += line[len(INDENT):] + '\n' 93 commit['message'] = message 94 commits.append(commit) 95 return commits 96 97 98def FormatCommit(commit): 99 """Returns a commit formatted into a single line.""" 100 rev = commit['commit'][:9] 101 line, _ = commit['message'].split('\n', 1) 102 return '%s %s' % (rev, line) 103 104 105def main(): 106 if len(sys.argv) > 2: 107 print('Usage: %s [COMMIT]' % sys.argv[0], file=sys.stderr) 108 return 1 109 110 if not IsPristine(SRC_PATH): 111 print('Chromium checkout not pristine.', file=sys.stderr) 112 return 0 113 if not IsPristine(BORINGSSL_SRC_PATH): 114 print('BoringSSL checkout not pristine.', file=sys.stderr) 115 return 0 116 117 if len(sys.argv) > 1: 118 new_head = RevParse(BORINGSSL_SRC_PATH, sys.argv[1]) 119 else: 120 subprocess.check_call(['git', 'fetch', 'origin'], cwd=BORINGSSL_SRC_PATH) 121 new_head = RevParse(BORINGSSL_SRC_PATH, 'origin/master') 122 123 old_head = RevParse(BORINGSSL_SRC_PATH, 'HEAD') 124 if old_head == new_head: 125 print('BoringSSL already up to date.') 126 return 0 127 128 print('Rolling BoringSSL from %s to %s...' % (old_head, new_head)) 129 130 # Look for commits with associated Chromium bugs. 131 crbugs = set() 132 crbug_commits = [] 133 update_note_commits = [] 134 log = Log(BORINGSSL_SRC_PATH, '%s..%s' % (old_head, new_head)) 135 for commit in log: 136 has_bugs = False 137 has_update_note = False 138 for line in commit['message'].split('\n'): 139 lower = line.lower() 140 if lower.startswith('bug:') or lower.startswith('bug='): 141 for bug in lower[4:].split(','): 142 bug = bug.strip() 143 if bug.startswith('chromium:'): 144 crbugs.add(int(bug[len('chromium:'):])) 145 has_bugs = True 146 if lower.startswith('update-note:'): 147 has_update_note = True 148 if has_bugs: 149 crbug_commits.append(commit) 150 if has_update_note: 151 update_note_commits.append(commit) 152 153 UpdateDEPS(DEPS_PATH, old_head, new_head) 154 155 # Checkout third_party/boringssl/src to generate new files. 156 subprocess.check_call(['git', 'checkout', new_head], cwd=BORINGSSL_SRC_PATH) 157 158 # Clear the old generated files. 159 for (osname, arch, _, _, _) in generate_build_files.OS_ARCH_COMBOS: 160 path = os.path.join(BORINGSSL_PATH, osname + '-' + arch) 161 shutil.rmtree(path) 162 for f in GENERATED_FILES: 163 path = os.path.join(BORINGSSL_PATH, f) 164 os.unlink(path) 165 166 # Generate new ones. 167 subprocess.check_call(['python3', 168 os.path.join(BORINGSSL_SRC_PATH, 'util', 169 'generate_build_files.py'), 170 '--embed_test_data=false', 171 'gn'], 172 cwd=BORINGSSL_PATH) 173 174 # Commit everything. 175 importlib.reload(generate_build_files) 176 subprocess.check_call(['git', 'add', DEPS_PATH], cwd=SRC_PATH) 177 for (osname, arch, _, _, _) in generate_build_files.OS_ARCH_COMBOS: 178 path = os.path.join(BORINGSSL_PATH, osname + '-' + arch) 179 subprocess.check_call(['git', 'add', path], cwd=SRC_PATH) 180 for f in GENERATED_FILES: 181 path = os.path.join(BORINGSSL_PATH, f) 182 subprocess.check_call(['git', 'add', path], cwd=SRC_PATH) 183 184 message = """Roll src/third_party/boringssl/src %s..%s 185 186https://boringssl.googlesource.com/boringssl/+log/%s..%s 187 188""" % (old_head[:9], new_head[:9], old_head, new_head) 189 if crbug_commits: 190 message += 'The following commits have Chromium bugs associated:\n' 191 for commit in crbug_commits: 192 message += ' ' + FormatCommit(commit) + '\n' 193 message += '\n' 194 if update_note_commits: 195 message += 'The following commits have update notes:\n' 196 for commit in update_note_commits: 197 message += ' ' + FormatCommit(commit) + '\n' 198 message += '\n' 199 if crbugs: 200 message += 'Bug: %s\n' % (', '.join(str(bug) for bug in sorted(crbugs)),) 201 else: 202 message += 'Bug: none\n' 203 204 subprocess.check_call(['git', 'commit', '-m', message], cwd=SRC_PATH) 205 206 # Print update notes. 207 notes = subprocess.check_output( 208 ['git', 'log', '--grep', '^Update-Note:', '-i', 209 '%s..%s' % (old_head, new_head)], cwd=BORINGSSL_SRC_PATH, text=True, 210 errors='replace').strip() 211 if len(notes) > 0: 212 print("\x1b[1mThe following changes contain updating notes\x1b[0m:\n\n") 213 print(notes) 214 215 return 0 216 217 218if __name__ == '__main__': 219 sys.exit(main()) 220