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