blob: ac7b513f413c4833f8ae2cb1ddf145b026bffcac [file]
"""Build step class for setting up a clean build environment."""
import logging
import os
import re
from helpers import gclient_utils
from slave import base_step
from slave.step import shell_step
_GCLIENT_CWD = 'chromium'
_GCLIENT_ENV = {
# Set libassistant retries to wait for binaries to be compiled
# Expected wait time is about 900 sec.
'LIBASSISTANT_FINDARCHIVE_RETRY_COUNT':
'15',
'LIBASSISTANT_FINDARCHIVE_RETRY_INTERVAL':
'300',
# Export CHROME_HEADLESS so that the license agreement is
# automatically accepted in //build/android/play_services/update.py.
'CHROME_HEADLESS':
'1',
# Set this so `gclient sync` does not update the ninja files with GYP.
# This call is very expensive and is unecessary: On GN builds, GN is
# used to generate the ninja files, and on GYP builds, we do this as a
# later step.
'GYP_CHROMIUM_NO_ACTION':
'1',
# Set this so `gclient sync` will pick up the prebuilt,
# instrumented libraries that msan needs to run effectively.
# Specifically, gclient sync will run
# //third_party/intrumented_libraries/scripts/download_binaries.py$
# to download the prebuilt library binaries.
'GYP_DEFINES': ('use_prebuilt_instrumented_libraries=1 '
'msan=1 '
'msan_track_origins=2'),
}
_FUCHSIA_SYNC_ENV_VAR = 'FUCHSIA_SYNC'
_INSERT_SHA = '{INSERT_SHA}'
_INSERT_PROJECT = '{INSERT_PROJECT}'
_GCLIENT_ERROR_RECOVERY = {
'Cannot fast-forward merge, attempt to rebase?': [
'git', 'reset', '--hard', _INSERT_SHA
],
'Conflict while rebasing this branch.': ['git', 'am', '--abort'],
'Already in a conflict, i.e. (no branch).': ['git', 'am', '--abort'],
'Command \'git rev-list -n 1 HEAD\' returned non-zero exit status 128': [
'git', 'pull'
],
'subprocess.CalledProcessError': [
'git', 'submodule', 'deinit', '--all', '-f'
],
'fetch_post_sync_builddeps.SubmoduleSyncError': [
'rm', '-rf', _INSERT_PROJECT
],
}
UNIX_SUCCESS_CODE = 0
class UnhandledGClientSyncError(base_step.StepError):
"""Exception raised when an unrecognized gclient sync failure happens."""
class GclientSyncStep(shell_step.ShellStep):
"""Runs `gclient sync` after patches have been applied."""
def __init__(self, **kwargs):
"""Creates a GclientSyncStep instance.
Args:
**kwargs: Any additional args to pass to BaseStep.
"""
shell_step.ShellStep.__init__(
self, name='gclient sync', halt_on_failure=True, **kwargs)
self._gclient_root = kwargs.get('properties',
{}).get('gclient_root', _GCLIENT_CWD)
self._fuchsia_sync = kwargs.get('properties', {}).get('fuchsia_sync', False)
self._gclient_extra_args = kwargs.get('properties',
{}).get('gclient_extra_args')
def _setup_for_repo_sync_retry(self, returncode, stdout, stderr):
"""A function to run before retrying a failed gclient sync."""
del returncode # Unused.
for recoverable in _GCLIENT_ERROR_RECOVERY:
if recoverable in stderr:
reg_exp = r'^(?:\d+> )?_+ (?P<project_dir>[^\s]+) at (?P<sha>.*)$'
match = re.search(reg_exp, stderr, re.MULTILINE)
# See b/112112992 for context on the error.
if match:
project_dir = match.group('project_dir')
project_dir = os.path.join(self._gclient_root, project_dir)
sha = match.group('sha')
command = self._insert_template(_GCLIENT_ERROR_RECOVERY[recoverable],
_INSERT_SHA, sha)
self.exec_subprocess(command, cwd=project_dir)
break
reg_exp = (r'Command \'git rev-list -n 1 HEAD\' '
r'returned non-zero exit status 128 in (?P<project_dir>.*)')
match = re.search(reg_exp, stderr, re.MULTILINE)
if match:
project_dir = match.group('project_dir')
self.exec_subprocess(
_GCLIENT_ERROR_RECOVERY[recoverable], cwd=project_dir)
break
logging.info('Found error in stderr but fail to recover: %s',
recoverable)
elif recoverable in stdout:
reg_exp = (r'subprocess\.CalledProcessError: Command '
r'\'\[\'git\', \'-C\', \'(?P<project_dir>.+)\', '
r'\'submodule\', \'sync\'\]\' returned non-zero exit status')
match = re.search(reg_exp, stdout, re.MULTILINE)
if match:
project_dir = match.group('project_dir')
self.exec_subprocess(
_GCLIENT_ERROR_RECOVERY[recoverable], cwd=project_dir)
break
# See b/135531243 for context on the error.
reg_exp = r'(?P<project_name>.+) submodules failed to sync'
match = re.search(reg_exp, stdout, re.MULTILINE)
if match:
project_dir = self.get_project_path(match.group('project_name'))
command = self._insert_template(_GCLIENT_ERROR_RECOVERY[recoverable],
_INSERT_PROJECT, project_dir)
self.exec_subprocess(command)
break
logging.info('Found error in stdout but fail to recover: %s',
recoverable)
def _insert_template(self, command, template, value):
return [w if w != template else value for w in command]
def get_commands(self):
# Ensures that additions to the gclient env here will remain deterministic.
env = dict(_GCLIENT_ENV)
if self._fuchsia_sync:
env[_FUCHSIA_SYNC_ENV_VAR] = '1'
return [
# Bazel uses a shared location for extracting its installation.
# Sometimes those installations get corrupt. This forces Bazel to
# extract itself for every build.
self.ShellCommand(['rm', '-rf',
os.path.expanduser('~/.cache/bazel')]),
self.ShellCommand(gclient_utils.disable_depot_tools_auto_update_cmd()),
self.ShellCommand( # pylint: disable=unexpected-keyword-arg
gclient_utils.gclient_sync_cmd(
self._executor,
jobs=self.get_num_jobs(),
extra_args=self._gclient_extra_args,
),
cwd=self._gclient_root,
env=env,
num_retries=3,
setup_for_retry=self._setup_for_repo_sync_retry,
halt_on_failure=True),
]