xref: /aosp_15_r20/external/cronet/third_party/boringssl/roll_boringssl.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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