"""This script takes a Clang git revision as an argument, it then
creates a feature branch, puts this revision into update.py, uploads
a CL, triggers Clang Upload try bots, and tells what to do next"""
from __future__ import print_function
import argparse
import itertools
import os
import re
import subprocess
import sys
import urllib.request
from build import (CheckoutGitRepo, GetCommitDescription, GetLatestLLVMCommit,
LLVM_DIR, LLVM_GIT_URL, RunCommand)
from update import CHROMIUM_DIR, DownloadAndUnpack
sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..',
'rust'))
from build_rust import RUST_GIT_URL, RUST_SRC_DIR, GetLatestRustCommit
THIS_DIR = os.path.dirname(__file__)
CHROMIUM_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '..', '..'))
CLANG_UPDATE_PY_PATH = os.path.join(THIS_DIR, 'update.py')
RUST_UPDATE_PY_PATH = os.path.join(THIS_DIR, '..', '..', 'rust',
'update_rust.py')
BUILD_RUST_PY_PATH = os.path.join(THIS_DIR, '..', '..', 'rust', 'build_rust.py')
BUILD_CLANG_BOTS = [
'linux_upload_clang',
'mac_upload_clang',
'mac_upload_clang_arm',
'win_upload_clang',
]
BUILD_RUST_BOTS = [
'linux_upload_rust',
'mac_upload_rust',
'mac_upload_rust_arm',
'win_upload_rust',
]
COMMIT_FOOTER = \
'''
Bug: TODO. Remove the Tricium: line below when filling this in.
Tricium: skip
Disable-Rts: True
Cq-Include-Trybots: chromium/try:android-cronet-riscv64-dbg
Cq-Include-Trybots: chromium/try:android-cronet-riscv64-rel
Cq-Include-Trybots: chromium/try:chromeos-amd64-generic-cfi-thin-lto-rel
Cq-Include-Trybots: chromium/try:dawn-win10-x86-deps-rel
Cq-Include-Trybots: chromium/try:fuchsia-arm64-cast-receiver-rel
Cq-Include-Trybots: chromium/try:gpu-fyi-try-mac-intel-asan
Cq-Include-Trybots: chromium/try:ios-catalyst,win-asan,android-official
Cq-Include-Trybots: chromium/try:linux-cast-x64-rel
Cq-Include-Trybots: chromium/try:linux-chromeos-dbg
Cq-Include-Trybots: chromium/try:linux-swangle-try-x64,win-swangle-try-x86
Cq-Include-Trybots: chromium/try:linux-v4l2-codec-rel
Cq-Include-Trybots: chromium/try:linux-wayland-mutter-rel
Cq-Include-Trybots: chromium/try:linux_chromium_cfi_rel_ng
Cq-Include-Trybots: chromium/try:linux_chromium_chromeos_msan_rel_ng
Cq-Include-Trybots: chromium/try:linux_chromium_msan_rel_ng
Cq-Include-Trybots: chromium/try:mac-official,linux-official
Cq-Include-Trybots: chromium/try:mac12-arm64-rel,mac_chromium_asan_rel_ng
Cq-Include-Trybots: chromium/try:win-arm64-rel
Cq-Include-Trybots: chromium/try:win-official,win32-official
Cq-Include-Trybots: chrome/try:android-arm32-pgo,android-arm64-pgo
Cq-Include-Trybots: chrome/try:android-x64-rel-ready
Cq-Include-Trybots: chrome/try:chromeos-brya-chrome,chromeos-eve-chrome
Cq-Include-Trybots: chrome/try:chromeos-volteer-chrome
Cq-Include-Trybots: chrome/try:iphone-device,ipad-device
Cq-Include-Trybots: chrome/try:linux-chromeos-chrome
Cq-Include-Trybots: chrome/try:linux-pgo,mac-pgo,win32-pgo,win64-pgo
Cq-Include-Trybots: chrome/try:win-chrome,win64-chrome,linux-chrome,mac-chrome'''
RUST_BOTS = \
'''Cq-Include-Trybots: chromium/try:android-rust-arm32-rel
Cq-Include-Trybots: chromium/try:android-rust-arm64-dbg
Cq-Include-Trybots: chromium/try:android-rust-arm64-rel
Cq-Include-Trybots: chromium/try:linux-rust-x64-dbg
Cq-Include-Trybots: chromium/try:linux-rust-x64-rel
Cq-Include-Trybots: chromium/try:mac-rust-x64-dbg
Cq-Include-Trybots: chromium/try:win-rust-x64-dbg
Cq-Include-Trybots: chromium/try:win-rust-x64-rel'''
is_win = sys.platform.startswith('win32')
class RustVersion:
"""Holds the nightly Rust version in an explicit format."""
def __init__(self, git_hash: str, sub_revision: int):
self.git_hash = git_hash
self.short_git_hash = git_hash[slice(0, 12)]
self.sub_revision = sub_revision
def __str__(self) -> str:
"""A string containing the Rust version and sub revision.
The string is useful for humans, it contains all info needed to identify
the Rust version being built. It is also unique to a given Rust version and
subversion.
"""
return f'{self.git_hash}-{self.sub_revision}'
def __eq__(self, o) -> bool:
return (self.git_hash == o.git_hash and self.sub_revision == o.sub_revision)
class ClangVersion:
"""Holds the Clang version in an explicit format."""
def __init__(self, git_describe: str, sub_revision: str):
self.git_describe = git_describe
self.short_git_hash = re.search('-g([0-9a-f]+)', git_describe).group(1)
self.sub_revision = int(sub_revision)
def __str__(self) -> str:
"""A string containing the Clang version and sub revision.
The string is useful for humans, it contains all info needed to identify
the Clang version being built. It is also unique to a given Clang version
and subversion.
"""
return f'{self.git_describe}-{self.sub_revision}'
def __eq__(self, o) -> bool:
return (self.git_describe == o.git_describe
and self.sub_revision == o.sub_revision)
def PatchClangRevision(new_version: ClangVersion) -> ClangVersion:
with open(CLANG_UPDATE_PY_PATH) as f:
content = f.read()
REV = '\'([0-9a-z-]+)\''
SUB_REV = '([0-9]+)'
git_describe = re.search(f'CLANG_REVISION = {REV}', content).group(1)
sub_revision = re.search(f'CLANG_SUB_REVISION = {SUB_REV}', content).group(1)
old_version = ClangVersion(git_describe, sub_revision)
content = re.sub(f'CLANG_REVISION = {REV}',
f'CLANG_REVISION = \'{new_version.git_describe}\'',
content,
count=1)
content = re.sub(f'CLANG_SUB_REVISION = {SUB_REV}',
f'CLANG_SUB_REVISION = {new_version.sub_revision}',
content,
count=1)
with open(CLANG_UPDATE_PY_PATH, 'w') as f:
f.write(content)
return old_version
def PatchRustRevision(new_version: RustVersion) -> RustVersion:
with open(RUST_UPDATE_PY_PATH) as f:
content = f.read()
REV = '\'([0-9a-z-]+)\''
SUB_REV = '([0-9]+)'
git_hash = re.search(f'RUST_REVISION = {REV}', content).group(1)
sub_revision = re.search(f'RUST_SUB_REVISION = {SUB_REV}', content).group(1)
old_version = RustVersion(git_hash, sub_revision)
content = re.sub(f'RUST_REVISION = {REV}',
f'RUST_REVISION = \'{new_version.git_hash}\'',
content,
count=1)
content = re.sub(f'RUST_SUB_REVISION = {SUB_REV}',
f'RUST_SUB_REVISION = {new_version.sub_revision}',
content,
count=1)
with open(RUST_UPDATE_PY_PATH, 'w') as f:
f.write(content)
return old_version
def PatchRustStage0():
verify_stage0 = subprocess.run(
[sys.executable, BUILD_RUST_PY_PATH, '--verify-stage0-hash'],
capture_output=True,
text=True)
if verify_stage0.returncode == 0:
return
print(verify_stage0.stdout)
lines = verify_stage0.stdout.splitlines()
m = re.match('Actual hash: +([0-9a-z]+)', lines[-1])
new_stage0_hash = m.group(1)
with open(RUST_UPDATE_PY_PATH) as f:
content = f.read()
STAGE0_HASH = '\'([0-9a-z]+)\''
content = re.sub(f'STAGE0_JSON_SHA256 = {STAGE0_HASH}',
f'STAGE0_JSON_SHA256 = \'{new_stage0_hash}\'',
content,
count=1)
with open(RUST_UPDATE_PY_PATH, 'w') as f:
f.write(content)
def PatchRustRemoveOverride():
with open(RUST_UPDATE_PY_PATH) as f:
content = f.read()
REV = '([0-9a-z-]+)'
content = re.sub(f'OVERRIDE_CLANG_REVISION = \'{REV}\'',
f'OVERRIDE_CLANG_REVISION = None',
content,
count=1)
with open(RUST_UPDATE_PY_PATH, 'w') as f:
f.write(content)
def Git(*args, no_run: bool):
"""Runs a git command, or just prints it out if `no_run` is True."""
if no_run:
print('\033[91m', end='')
print('Skipped running: ', end='')
print('\033[0m', end='')
print(*['git'] + [f'\'{i}\'' for i in list(args)], end='')
print()
else:
subprocess.check_call(['git'] + list(args), shell=is_win)
def main():
parser = argparse.ArgumentParser(description='upload new clang revision')
parser.add_argument(
'ignored',
nargs='?',
help='Ignored argument to handle the cron job passing a clang SHA')
parser.add_argument('--clang-git-hash',
type=str,
metavar='SHA1',
help='Clang git hash to build the toolchain for.')
parser.add_argument(
'--clang-sub-revision',
type=int,
default=1,
metavar='NUM',
help='Clang sub-revision to build the toolchain for. Defaults to 1.')
parser.add_argument('--rust-git-hash',
type=str,
metavar='SHA1',
help='Rust git hash to build the toolchain for.')
parser.add_argument(
'--rust-sub-revision',
type=int,
default=1,
metavar='NUM',
help='Rust sub-revision to build the toolchain for. Defaults to 1.')
parser.add_argument(
'--no-git',
action='store_true',
default=False,
help=('Print out `git` commands instead of running them. Still generates '
'a local diff for debugging purposes.'))
parser.add_argument('--skip-rust',
action='store_true',
default=False,
help=('Skip updating the rust revision.'))
parser.add_argument('--skip-clang',
action='store_true',
default=False,
help=('Skip updating the clang revision.'))
args = parser.parse_args()
if args.skip_clang and args.skip_rust:
print('Cannot set both --skip-clang and --skip-rust.')
sys.exit(1)
if args.skip_clang:
clang_version = '-skipped-'
else:
if args.clang_git_hash:
clang_git_hash = args.clang_git_hash
else:
clang_git_hash = GetLatestLLVMCommit()
CheckoutGitRepo("LLVM", LLVM_GIT_URL, clang_git_hash, LLVM_DIR)
clang_version = ClangVersion(GetCommitDescription(clang_git_hash),
args.clang_sub_revision)
os.chdir(CHROMIUM_DIR)
if args.skip_rust:
rust_version = '-skipped-'
else:
if args.rust_git_hash:
rust_git_hash = args.rust_git_hash
else:
rust_git_hash = GetLatestRustCommit()
CheckoutGitRepo("Rust", RUST_GIT_URL, rust_git_hash, RUST_SRC_DIR)
rust_version = RustVersion(rust_git_hash, args.rust_sub_revision)
os.chdir(CHROMIUM_DIR)
print(f'Making a patch for Clang {clang_version} and Rust {rust_version}')
branch_name = f'clang-{clang_version}_rust-{rust_version}'
Git('checkout', 'origin/main', '-b', branch_name, no_run=args.no_git)
old_clang_version = clang_version
if not args.skip_clang:
old_clang_version = PatchClangRevision(clang_version)
if args.skip_rust:
assert (clang_version !=
old_clang_version), ('Change the sub-revision of Clang if there is '
'no major version change.')
else:
old_rust_version = PatchRustRevision(rust_version)
assert (clang_version != old_clang_version
or rust_version != old_rust_version), (
'Change the sub-revision of Clang or Rust if there is '
'no major version change.')
PatchRustStage0()
if not args.skip_clang:
PatchRustRemoveOverride()
if args.skip_clang:
clang_change = '[skipping Clang]'
clang_change_log = ''
else:
clang_change = f'{old_clang_version} : {clang_version}'
clang_change_log = (
f'{LLVM_GIT_URL}/+log/'
f'{old_clang_version.short_git_hash}..{clang_version.short_git_hash}'
f'\n\n')
if args.skip_rust:
rust_change = '[skipping Rust]'
rust_change_log = ''
else:
rust_change = f'{old_rust_version} : {rust_version}'
rust_change_log = (f'{RUST_GIT_URL}/+log/'
f'{old_rust_version.short_git_hash}..'
f'{rust_version.short_git_hash}'
f'\n\n')
title = f'Roll clang+rust {clang_change} / {rust_change}'
cmd = ' '.join(sys.argv)
body = f'{clang_change_log}{rust_change_log}Ran: {cmd}'
commit_message = f'{title}\n\n{body}\n{COMMIT_FOOTER}'
if not args.skip_rust:
commit_message += f'\n{RUST_BOTS}'
Git('add',
CLANG_UPDATE_PY_PATH,
RUST_UPDATE_PY_PATH,
no_run=args.no_git)
Git('commit', '-m', commit_message, no_run=args.no_git)
Git('cl', 'upload', '-f', '--bypass-hooks', '--squash', no_run=args.no_git)
if not args.skip_clang:
Git('cl',
'try',
'-B',
"chromium/try",
*itertools.chain(*[['-b', bot] for bot in BUILD_CLANG_BOTS]),
no_run=args.no_git)
Git('cl',
'try',
'-B',
"chromium/try",
*itertools.chain(*[['-b', bot] for bot in BUILD_RUST_BOTS]),
no_run=args.no_git)
print('Please, wait until the try bots succeeded '
'and then push the binaries to RBE.')
print()
print('To update the Clang/Rust DEPS entries, run:\n '
'tools/clang/scripts/sync_deps.py')
print()
print('To regenerate BUILD.gn rules for Rust stdlib (needed if dep versions '
'in the stdlib change for example), run:\n tools/rust/gnrt_stdlib.py.')
print()
print('To update Abseil .def files, run:\n '
'third_party/abseil-cpp/generate_def_files.py')
if __name__ == '__main__':
sys.exit(main())