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