blob: 2d57c2d82285a440a6e087384a67690f0a179886 [file] [log] [blame]
#!/usr/bin/python -u
# Copyright 2018 The Dart project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This script automates the Dart SDK roll steps, including:
# - Updating the Dart revision in DEPS
# - Updating the Dart dependencies in DEPS
# - Syncing dependencies with 'gclient sync'
# - Generating GN files for relevant engine configurations
# - Building relevant engine configurations
# - Running tests in 'example/flutter_gallery' and 'packages/flutter'
# - Launching flutter_gallery in release and debug mode
# - Running license.sh and updating license files in
# 'flutter/ci/licenses_golden'
# - Generating a commit with relevant Dart SDK commit logs (optional)
#
# The following environment variables can be set instead of being passed as
# arguments:
# - FLUTTER_HOME: the absolute path to the 'flutter' directory
# - ENGINE_HOME: the absolute path to the 'engine/src' directory
# - DART_SDK_HOME: the absolute path to the root of a Dart SDK project
from dart_roll_utils import *
import argparse
import atexit
import datetime
import fileinput
import os
import platform
import signal
import shutil
import subprocess
import sys
DART_REVISION_ENTRY = 'dart_revision'
FLUTTER = '{}/bin/flutter'.format(flutter_home())
FLUTTER_DOCTOR = [FLUTTER, 'doctor']
FLUTTER_RUN = [FLUTTER, 'run']
FLUTTER_TEST = [FLUTTER, 'test']
MAX_GCLIENT_RETRIES = 3
# Returned when licenses do not require updating.
LICENSE_SCRIPT_OKAY = 0
# Returned when licenses require updating.
LICENSE_SCRIPT_UPDATES = 1
# Returned when either 'pub' or 'dart' isn't in the path.
LICENSE_SCRIPT_EXIT_ERROR = 127
CURRENT_SUBPROCESS = None
def update_dart_revision(dart_revision):
original_revision = ''
print_status('Updating Dart revision to {}'.format(dart_revision))
content = get_deps()
for idx, line in enumerate(content):
if DART_REVISION_ENTRY in line:
original_revision = line.strip().split(' ')[1][1:-2]
if not is_ancestor_commit(original_revision,
dart_revision,
dart_sdk_home()):
print_error('Dart revision {} is older than existing revision, {}.' +
' Aborting roll.'.format(dart_revision, original_revision))
sys.exit(ERROR_OLD_COMMIT_PROVIDED)
content[idx] = " 'dart_revision': '" + dart_revision + "',\n"
break
write_deps(content)
return original_revision
def run_process(args, cwd=None, stdout=None):
global CURRENT_SUBPROCESS
CURRENT_SUBPROCESS = subprocess.Popen(args, cwd=cwd, stdout=stdout)
exit_code = CURRENT_SUBPROCESS.wait()
CURRENT_SUBPROCESS = None
return exit_code
def gclient_sync():
global CURRENT_SUBPROCESS
exit_code = None
num_retries = 0
while ((exit_code != 0) and not (num_retries >= MAX_GCLIENT_RETRIES)):
print_status('Running gclient sync (Attempt {}/{})'
.format(num_retries + 1, MAX_GCLIENT_RETRIES))
exit_code = run_process(['gclient', 'sync', '--delete_unversioned_trees'],
cwd=engine_home())
if exit_code != 0:
num_retries += 1
if num_retries == MAX_GCLIENT_RETRIES:
print_error('Max number of gclient sync retries attempted. Aborting roll.')
sys.exit(ERROR_GCLIENT_SYNC_FAILED)
def get_deps():
with open(flutter_deps_path(), 'r') as f:
content = f.readlines()
return content
def update_deps():
print_status('Updating Dart dependencies')
run_process([update_dart_deps_path()], cwd=engine_home())
def write_deps(newdeps):
with open(flutter_deps_path(), 'w') as f:
f.write(''.join(newdeps))
def run_gn():
print_status('Generating build files')
common = [os.path.join('flutter', 'tools', 'gn'), '--goma', '--full-dart-sdk']
debug = ['--runtime-mode=debug']
profile = ['--runtime-mode=profile']
release = ['--runtime-mode=release']
runtime_modes = [debug, profile, release]
unopt = ['--unoptimized']
android = ['--android']
fuchsia = ['--fuchsia']
for mode in runtime_modes:
if set(mode) != set(release):
run_process(common + android + unopt + mode, cwd=engine_home())
run_process(common + android + mode, cwd=engine_home())
host = common[:]
if set(mode) == set(debug):
host += unopt
run_process(host + mode, cwd=engine_home())
run_process(common + fuchsia + debug + unopt, cwd=engine_home())
def build():
print_status('Building Flutter engine')
command = ['ninja', '-j1000']
configs = [
'host_debug_unopt',
'host_release',
'host_profile',
'android_debug_unopt',
'android_profile',
'android_release',
'fuchsia_debug_unopt_x64',
]
build_dir = 'out'
if platform.system() == 'Darwin':
build_dir = 'xcodebuild'
for config in configs:
error_code = run_process(command + ['-C', os.path.join(build_dir, config)],
cwd=engine_home())
if error_code != 0:
print_error('Build failure for configuration "' +
config +
'". Aborting roll.')
sys.exit(ERROR_BUILD_FAILED)
def run_flutter_doctor():
print_status('Running flutter doctor')
engine_src_path = '--local-engine-src-path={}'.format(engine_home())
result = run_process(FLUTTER_DOCTOR + ['--local-engine=host_debug_unopt',
engine_src_path],
cwd=package_flutter_path())
if result != 0:
print_error('flutter doctor failed. Aborting roll.')
sys.exit(ERROR_FLUTTER_DOCTOR_FAILED)
def run_tests():
print_status('Running tests in packages/flutter')
engine_src_path = '--local-engine-src-path={}'.format(engine_home())
result = run_process(FLUTTER_TEST + ['--local-engine=host_debug_unopt',
engine_src_path],
cwd=package_flutter_path())
if result != 0:
print_error('package/flutter tests failed. Aborting roll.')
sys.exit(ERROR_PKG_FLUTTER_FAILED)
print_status('Running tests in examples/flutter_gallery')
result = run_process(FLUTTER_TEST + ['--local-engine=host_debug_unopt',
engine_src_path,
'--disable-service-auth-codes'],
cwd=flutter_gallery_path());
if result != 0:
print_error('flutter_gallery tests failed. Aborting roll.')
sys.exit(ERROR_FLUTTER_GALLERY_FAILED)
def run_hot_reload_configurations():
print_status('Running flutter gallery release')
engine_src_path = '--local-engine-src-path={}'.format(engine_home())
run_process(FLUTTER_RUN + ['--release',
'--local-engine=android_release',
engine_src_path],
cwd=flutter_gallery_path())
print_status('Running flutter gallery debug')
run_process(FLUTTER_RUN + ['--local-engine=android_debug_unopt',
engine_src_path],
cwd=flutter_gallery_path())
def update_licenses():
print_status('Updating Flutter licenses')
result = run_process([engine_license_script_path()], cwd=engine_home())
if result == LICENSE_SCRIPT_EXIT_ERROR:
print_error('License script failed to run. Is the Dart SDK (specifically' +
' dart and pub) in your path? Aborting roll.')
sys.exit(ERROR_LICENSE_SCRIPT_FAILED)
elif (result != LICENSE_SCRIPT_OKAY) and (result != LICENSE_SCRIPT_UPDATES):
print_error('Unknown license script error: {}. Aborting roll.'
.format(result))
sys.exit(ERROR_LICENSE_SCRIPT_FAILED)
# Ignore 'licenses_skia' as they shouldn't change during a Dart SDK roll.
src_files = ['licenses_flutter', 'licenses_third_party', 'tool_signature']
for f in src_files:
path = os.path.join(license_script_output_path(), f)
if os.path.isfile(path):
shutil.copy(path, engine_golden_licenses_path())
run_process(['pub', 'get'], cwd=engine_license_script_package_path())
gclient_sync()
# Update the LICENSE file.
with open(sky_license_file_path(), 'w') as sky_license:
run_process(['dart', os.path.join('lib', 'main.dart'),
'--release', '--src', engine_home(),
'--out', engine_license_script_output_path()],
cwd=engine_license_script_package_path(),
stdout=sky_license)
def get_commit_range(start, finish):
range_str = '{}..{}'.format(start, finish)
command = ['git', 'log', '--oneline', range_str]
orig_dir = os.getcwd()
os.chdir(dart_sdk_home())
result = subprocess.check_output(command).decode('utf-8')
result = '\n'.join(['dart-lang/sdk@' + l for l in result.splitlines()])
os.chdir(orig_dir)
return result
def get_short_rev(rev):
command = ['git', 'rev-parse', '--short', rev]
orig_dir = os.getcwd()
os.chdir(dart_sdk_home())
result = subprocess.check_output(command).decode('utf-8').rstrip()
os.chdir(orig_dir)
return result
def git_commit(original_revision, updated_revision):
print_status('Committing Dart SDK roll')
current_date = datetime.date.today()
sdk_log = get_commit_range(original_revision, updated_revision)
num_commits = len(sdk_log.splitlines())
commit_msg = ('Roll src/third_party/dart {}..{} ({} commits)'
.format(get_short_rev(original_revision),
get_short_rev(updated_revision), num_commits))
commit_msg += '\n\n' + sdk_log
commit_cmd = ['git', 'commit', '-a', '-m', commit_msg]
run_process(commit_cmd, cwd=engine_flutter_path())
def update_roots(args):
if args.flutter_home:
set_flutter_home(args.flutter_home)
if args.engine_home:
set_engine_home(args.engine_home)
if args.dart_sdk_home:
set_dart_sdk_home(args.dart_sdk_home)
if flutter_home() == '':
print_error('Either "--flutter-home" must be provided or FLUTTER_HOME must' +
' be set. Aborting roll.')
sys.exit(ERROR_MISSING_ROOTS)
if engine_home() == '':
print_error('Either "--engine-home" must be provided or ENGINE_HOME must' +
' be set. Aborting roll.')
sys.exit(ERROR_MISSING_ROOTS)
if dart_sdk_home() == '':
print_error('Either "--dart-sdk-home" must be provided or DART_SDK_HOME ' +
'must be set. Aborting roll.')
sys.exit(ERROR_MISSING_ROOTS)
def sys_exit(signal, frame):
sys.exit()
def cleanup_children():
if CURRENT_SUBPROCESS != None:
CURRENT_SUBPROCESS.terminate()
def main():
parser = argparse.ArgumentParser(description='Automate most Dart SDK roll tasks.')
parser.add_argument('--dart-sdk-home', help='Path to the Dart SDK ' +
'repository. Overrides DART_SDK_HOME environment variable')
parser.add_argument('dart_sdk_revision', help='Target Dart SDK revision')
parser.add_argument('--create-commit', action='store_true',
help='Create the engine commit with Dart SDK commit log')
parser.add_argument('--engine-home', help='Path to the Flutter engine ' +
'repository. Overrides ENGINE_HOME environment variable')
parser.add_argument('--flutter-home', help='Path to the Flutter framework ' +
'repository. Overrides FLUTTER_HOME environment variable')
parser.add_argument('--no-build', action='store_true',
help='Skip rebuilding the Flutter engine')
parser.add_argument('--no-hot-reload', action='store_true',
help="Skip hot reload testing")
parser.add_argument('--no-test', action='store_true',
help='Skip running host tests for package/flutter and ' +
'flutter_gallery')
parser.add_argument('--no-update-deps', action='store_true',
help='Skip updating DEPS file')
parser.add_argument('--no-update-licenses', action='store_true',
help='Skip updating licenses')
args = parser.parse_args()
atexit.register(cleanup_children)
signal.signal(signal.SIGTERM, sys_exit)
original_revision = None
updated_revision = args.dart_sdk_revision
# Disable buffering of log output
os.environ["PYTHONUNBUFFERED"] = "1"
update_roots(args)
print_status('Starting Dart SDK roll')
if not args.no_update_deps:
original_revision = update_dart_revision(updated_revision)
gclient_sync()
update_deps()
gclient_sync()
if not args.no_build:
run_gn()
build()
if ((not args.no_test) or (not args.no_hot_reload)):
run_flutter_doctor()
if not args.no_test:
run_tests()
if not args.no_hot_reload:
run_hot_reload_configurations()
if not args.no_update_licenses:
update_licenses()
if args.create_commit:
if original_revision == None:
print_warning('"original_revision" not specified. Skipping commit.')
print_warning('This happens when the "--no_update_deps" argument is ' +
'provided')
else:
git_commit(original_revision, updated_revision)
print_status('Dart SDK roll complete!')
if __name__ == '__main__':
main()