roll_boringssl.py 7.1 KB

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