blob: d46f829c99c47f5ad486db087d15e958923b0dd9 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import logging
import os
import re
import subprocess
import sys
import tempfile
import traceback
# Install Infra build environment.
BUILD_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(BUILD_ROOT, 'scripts'))
from common import annotator
from common import env
LOGGER = logging.getLogger('update_scripts')
IS_WINDOWS = sys.platform.startswith('win')
def _run_command(cmd, **kwargs):
LOGGER.debug('Executing command: %s', cmd)
kwargs.setdefault('stderr', subprocess.STDOUT)
proc = subprocess.Popen(cmd, **kwargs)
stdout, _ = proc.communicate()
LOGGER.debug('Process [%s] returned [%d] with output:\n%s',
cmd, proc.returncode, stdout)
return proc.returncode, stdout
def ensure_managed(dot_gclient_filename):
"""Rewrites a .gclient file to set "managed": True.
Returns:
True if the .gclient file was modified.
"""
with open(dot_gclient_filename) as fh:
contents = fh.read()
new_contents = re.sub(r'("managed"\s*:\s*)False', r'\1True', contents)
if contents != new_contents:
with open(dot_gclient_filename, 'w') as fh:
fh.write(new_contents)
return True
return False
IMPORTANT_REPOS = ['build', 'depot_tools']
REPO_TO_GITILES_PATH = {
'build': 'chromium/tools/build',
'depot_tools': 'chromium/tools/depot_tools',
}
GITILES_COMMIT_TEMPLATE = 'https://chromium.googlesource.com/%s/+/%s'
GITILES_LOG_TEMPLATE = 'https://chromium.googlesource.com/%s/+log/%s..%s'
def get_repo_head(path):
# On Windows, we need to run this in a shell so "cmd.exe" can resolve "git"
# into something on PATH (likely "git.bat").
cmd = ['git', 'rev-parse', 'HEAD']
return _run_command(cmd, cwd=path, stdout=subprocess.PIPE, shell=IS_WINDOWS)
def add_revision_links(s, repos_to_old_hashes):
"""Adds infra repo revision information to the update_scripts step.
Args:
s: The step to add the links and step text to.
repos_to_old_hashes: A dictionary mapping repo name to revision hash
before update scripts was executed.
"""
for repo, old_hash in sorted(
repos_to_old_hashes.items(), key=lambda it: it[0]):
path = os.path.join(env.Build, os.pardir, repo)
rv, out = get_repo_head(path)
if rv:
sys.stderr.write(
'error while getting revision info for project %r (return'
' code was %s):\n%s' % (repo, rv, out))
continue
new_hash = out.strip()
if new_hash == old_hash:
s.step_link(
'%s: %s' % (repo, new_hash), GITILES_COMMIT_TEMPLATE % (
REPO_TO_GITILES_PATH[repo], new_hash))
else:
gitiles_link = GITILES_LOG_TEMPLATE % (
REPO_TO_GITILES_PATH[repo], old_hash, new_hash)
s.step_link(
'%s: %s..%s' % (repo, old_hash, new_hash), gitiles_link)
def update_scripts():
if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'):
os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS')
return False
# For testing, we don't actually want to run "gclient sync" against its native
# root. However, we don't want to mock/disable it either, since we want to
# exercise this code path.
build_dir = os.environ.get(
'RUN_SLAVE_UPDATED_SCRIPTS_TEST_BUILD_DIR', env.Build)
stream = annotator.StructuredAnnotationStream()
with stream.step('update_scripts') as s:
if ensure_managed(os.path.join(build_dir, os.pardir, '.gclient')):
s.step_text('Top-level gclient solution was unmanaged, '
'changed to managed')
# Get our "gclient" file. We will use the "gclient" relative to this
# script's checkout, regardless of "build_dir".
gclient_name = 'gclient'
if sys.platform.startswith('win'):
gclient_name += '.bat'
gclient_path = os.path.join(env.Build, os.pardir, 'depot_tools',
gclient_name)
gclient_cmd = [gclient_path, 'sync',
# these two need to both be here to actually get
# `git checkout --force` to happen.
'--force', '--delete_unversioned_trees',
'--break_repo_locks', '--verbose', '--jobs=2',
# TODO(phajdan.jr): enable syntax validation (crbug/570091).
'--disable-syntax-validation']
try:
fd, output_json = tempfile.mkstemp()
os.close(fd)
gclient_cmd += ['--output-json', output_json]
except Exception:
# Super paranoia try block.
output_json = None
repos_to_old_hashes = {}
try:
for repo in sorted(IMPORTANT_REPOS):
path = os.path.join(env.Build, os.pardir, repo)
rv, out = get_repo_head(path)
if rv:
break
proj_sha = out.strip()
repos_to_old_hashes[repo] = proj_sha
except Exception:
traceback.print_exc()
cmd_dict = {
'name': 'update_scripts',
'cmd': gclient_cmd,
'cwd': build_dir,
}
annotator.print_step(cmd_dict, os.environ, stream)
rv, _ = _run_command(gclient_cmd, cwd=build_dir)
if rv != 0:
s.step_text('gclient sync failed!')
s.step_exception()
elif output_json:
try:
with open(output_json, 'r') as f:
gclient_json = json.load(f)
for line in json.dumps(
gclient_json, sort_keys=True,
indent=4, separators=(',', ': ')).splitlines():
s.step_log_line('gclient_json', line)
s.step_log_end('gclient_json')
if repos_to_old_hashes:
add_revision_links(s, repos_to_old_hashes)
else:
build_checkout = gclient_json['solutions'].get('build/')
if build_checkout:
s.step_text('%(scm)s - %(revision)s' % build_checkout)
s.set_build_property('build_scm', json.dumps(build_checkout['scm']))
s.set_build_property('build_revision',
json.dumps(build_checkout['revision']))
except Exception as e:
s.step_text('Unable to process gclient JSON %s' % repr(e))
s.step_exception()
finally:
try:
os.remove(output_json)
except Exception as e:
LOGGER.warning("LEAKED: %s", output_json, exc_info=True)
else:
s.step_text('Unable to get SCM data')
s.step_exception()
os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1'
# After running update scripts, set PYTHONIOENCODING=UTF-8 for the real
# annotated_run.
os.environ['PYTHONIOENCODING'] = 'UTF-8'
return True