blob: b209e66101d0b79eba2751bc04e2ecc09a79ce1e [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Switcher for chromeos localbuild bisecting."""
from __future__ import print_function
import argparse
import logging
import os
import sys
import typing
from bisect_kit import bisector_cli
from bisect_kit import cli
from bisect_kit import codechange
from bisect_kit import common
from bisect_kit import configure
from bisect_kit import cros_lab_util
from bisect_kit import cros_util
from bisect_kit import errors
from bisect_kit import repo_util
from bisect_kit import util
logger = logging.getLogger(__name__)
def add_build_and_deploy_arguments(parser_group):
parser_group.add_argument(
'--goma_chromeos_dir',
type=cli.argtype_dir_path,
default=configure.get('GOMA_CHROMEOS_DIR'),
help='Goma-chromeos installed directory to mount into the Chrome OS '
'chroot')
parser_group.add_argument(
'--clobber-stateful',
'--clobber_stateful',
action='store_true',
help='Clobber stateful partition when performing update')
parser_group.add_argument(
'--no-disable-rootfs-verification',
'--no_disable_rootfs_verification',
dest='disable_rootfs_verification',
action='store_false',
help="Don't disable rootfs verification after update is completed")
parser_group.add_argument(
'--no-mark-as-stable',
'--no_mark_as_stable',
dest='mark_as_stable',
action='store_false',
help="Don't mark repo stable")
def create_argument_parser():
parents = [common.common_argument_parser, common.session_optional_parser]
parser = argparse.ArgumentParser(parents=parents)
cli.patching_argparser_exit(parser)
parser.add_argument(
'--rich_result',
action='store_true',
help='Instead of mere exit code, output detailed information in json')
parser.add_argument(
'--dut',
type=cli.argtype_notempty,
metavar='DUT',
default=configure.get('DUT'),
help='DUT address')
parser.add_argument(
'rev',
nargs='?',
type=cli.argtype_notempty,
metavar='CROS_VERSION',
default=configure.get('CROS_VERSION', ''),
help='ChromeOS local build version string, in format short version, '
'full version, or "full,full+N"')
parser.add_argument(
'--board',
metavar='BOARD',
default=configure.get('BOARD', ''),
help='ChromeOS board name')
parser.add_argument(
'--chromeos_root',
type=cli.argtype_dir_path,
metavar='CHROMEOS_ROOT',
default=configure.get('CHROMEOS_ROOT', ''),
help='ChromeOS tree root (default: %(default)s)')
parser.add_argument(
'--chromeos_mirror',
type=cli.argtype_dir_path,
default=configure.get('CHROMEOS_MIRROR', ''),
help='ChromeOS repo mirror path')
parser.add_argument(
'--skip_sync_code',
action='store_true',
help='Skip the step syncing source code')
parser.add_argument(
'--nobuild',
action='store_true',
help='Stop after source code sync; do not build; imply --noimage')
parser.add_argument(
'--noimage',
action='store_true',
help='Build packages only; do not build image; imply --nodeploy')
parser.add_argument(
'--nodeploy', action='store_true', help='Do not deploy after build')
group = parser.add_argument_group(title='Build and deploy options')
add_build_and_deploy_arguments(group)
return parser
def mark_as_stable(opts):
overlays = util.check_output(
'chromite/bin/cros_list_overlays', '--all',
cwd=opts.chromeos_root).splitlines()
# Get overlays listed in manifest file.
known_projects = []
for path in repo_util.list_projects(opts.chromeos_root):
known_projects.append(
os.path.realpath(os.path.join(opts.chromeos_root, path)))
logger.debug('known_projects %s', known_projects)
# Skip recent added overlays.
# cros_mark_as_stable expects all overlays is recorded in manifest file
# but we haven't synthesized manifest files for each intra versions yet.
# TODO(kcwu): synthesize manifest file
for overlay in list(overlays):
if 'private-overlays/' in overlay and overlay not in known_projects:
logger.warning(
'bisect-kit cannot handle recently added overlay %s yet; ignore',
overlay)
overlays.remove(overlay)
continue
cmd = [
'chromite/bin/cros_mark_as_stable',
'-b', opts.board,
'--list_revisions',
'--overlays', ':'.join(overlays),
'--debug',
'--all',
'commit',
] # yapf: disable
util.check_call(*cmd, cwd=opts.chromeos_root)
def cleanup(opts):
logger.info('clean up leftover in the source tree from the last build')
repo_util.cleanup_unexpected_files(opts.chromeos_root)
logger.info('clean up previous result of "mark as stable"')
repo_util.abandon(opts.chromeos_root, 'stabilizing_branch')
def _build_packages(opts):
logger.info('build packages')
# chrome_root is only defined if called from chrome's switcher.
# This means using chromeos build_packages flow to build chrome during chrome
# bisection.
chrome_root = getattr(opts, 'chrome_root', None)
if not opts.goma_chromeos_dir:
default_goma_chromeos_dir = os.path.expanduser('~/goma')
if os.path.exists(default_goma_chromeos_dir):
logger.debug('found %s available, use goma automatically',
default_goma_chromeos_dir)
opts.goma_chromeos_dir = default_goma_chromeos_dir
cros_util.build_packages(
opts.chromeos_root,
opts.board,
chrome_root=chrome_root,
goma_dir=opts.goma_chromeos_dir,
afdo_use=True)
def build(opts, do_mark_as_stable=True):
# Used many times in this function, shorten it.
chromeos_root = opts.chromeos_root
# create and hotfix chroot if necessary
cros_util.prepare_chroot(chromeos_root)
if do_mark_as_stable:
logger.info('mark as stable')
mark_as_stable(opts)
if opts.noimage:
_build_packages(opts)
return None
cached_name = 'bisect-%s' % util.escape_rev(opts.rev)
image_folder = os.path.join(cros_util.cached_images_dir, opts.board,
cached_name)
image_path = os.path.join(image_folder, cros_util.test_image_filename)
# If the given version is already built, reuse it.
chromeos_root_image_folder = os.path.join(chromeos_root, image_folder)
chromeos_root_image_path = os.path.join(chromeos_root, image_path)
if not os.path.exists(chromeos_root_image_path):
_build_packages(opts)
built_image_folder = cros_util.build_image(chromeos_root, opts.board)
os.makedirs(os.path.dirname(chromeos_root_image_folder), exist_ok=True)
if os.path.exists(chromeos_root_image_folder):
os.unlink(chromeos_root_image_folder)
# Create a relative symlink, so the condition actually only comes in here if
# the image folder is actually missing.
os.symlink(
os.path.join('../../..', built_image_folder),
chromeos_root_image_folder)
return image_path
def build_and_deploy(opts):
try:
image_path = build(opts, do_mark_as_stable=opts.mark_as_stable)
except cros_util.NeedRecreateChrootException as e:
logger.warning('recreate chroot and retry again, reason: %s', e)
util.check_output(
'chromite/bin/cros_sdk', '--delete', cwd=opts.chromeos_root)
image_path = build(opts, do_mark_as_stable=opts.mark_as_stable)
if opts.noimage or opts.nodeploy:
return
image_info = cros_util.ImageInfo()
image_info[cros_util.ImageType.DISK_IMAGE] = image_path
cros_util.provision_image_with_retry(
opts.chromeos_root,
opts.dut,
opts.board,
image_info,
clobber_stateful=opts.clobber_stateful,
disable_rootfs_verification=opts.disable_rootfs_verification,
repair_callback=cros_lab_util.repair,
force_reboot_callback=cros_lab_util.reboot_via_servo)
def switch(opts):
cleanup(opts)
if not opts.skip_sync_code:
logger.info('switch source code')
config = dict(
board=opts.board,
chromeos_root=opts.chromeos_root,
chromeos_mirror=opts.chromeos_mirror)
spec_manager = cros_util.ChromeOSSpecManager(config)
cache = repo_util.RepoMirror(opts.chromeos_mirror)
code_manager = codechange.CodeManager(opts.chromeos_root, spec_manager,
cache)
code_manager.switch(opts.rev)
if opts.nobuild:
return
build_and_deploy(opts)
def parse_args(args):
parser = create_argument_parser()
return parser.parse_args(args)
def inner_main(opts):
# --nobuild imply --nodeploy
if opts.nobuild:
opts.nodeploy = True
if opts.skip_sync_code and opts.nobuild:
raise errors.ArgumentError('--skip_sync_code and --nobuild',
'nothing to do')
if not opts.dut:
if not opts.nodeploy:
raise errors.ArgumentError('--dut',
'DUT can be omitted only if --nodeploy')
if not opts.board:
raise errors.ArgumentError('--board',
'board must be specified if no --dut')
if not opts.nodeploy:
if not cros_util.is_good_dut(opts.dut):
logger.fatal('%r is not a good DUT', opts.dut)
if not cros_lab_util.repair(opts.dut):
raise errors.BrokenDutException('%r is not a good DUT' % opts.dut)
if not opts.board:
opts.board = cros_util.query_dut_board(opts.dut)
if cros_util.is_cros_short_version(opts.rev):
opts.rev = cros_util.version_to_full(opts.board, opts.rev)
cros_util.prepare_chroot(opts.chromeos_root)
try:
switch(opts)
finally:
if not opts.nodeploy:
# No matter switching succeeded or not, DUT must be in good state.
# switch() already tried repairing if possible, no repair here.
if not cros_util.is_good_dut(opts.dut):
raise errors.BrokenDutException('%r is not a good DUT' % opts.dut)
logger.info('done')
def switch_main(args: typing.Optional[tuple[str]]):
common.init()
opts = parse_args(args)
common.config_logging(opts)
inner_main(opts)
def main(args: typing.Optional[tuple[str]] = None) -> int:
return bisector_cli.switch_main_wrapper(switch_main, args)
if __name__ == '__main__':
sys.exit(main())