blob: 80d6289adf0e16e1186e3733eefcea4184cc6f92 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2021 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.
import argparse
from collections import defaultdict
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from chromite.lib.cros_build_lib import IsInsideChroot
user_conf = '/etc/make.conf.user'
old_conf_line = 'source ${ROOT}/etc/make.conf.binhost'
conf_line = 'source /etc/binhost/${BOARD_USE}'
def run_prechecks():
if not IsInsideChroot():
print('This script must be run inside the CrOS chroot')
sys.exit(1)
with open(user_conf, 'r') as f:
has_new_conf = False
has_old_conf = False
for line in f.read().split('\n'):
if line == conf_line:
has_new_conf = True
if line == old_conf_line:
has_old_conf = True
if not has_new_conf:
print(f'Add "{conf_line}" to {user_conf} and rerun.')
print('')
if has_old_conf:
print(f'You have config from a previous version of this '
f'script in {user_conf}.')
print(f'Remove the line "{old_conf_line}" and rerun.')
print('')
if not has_new_conf or has_old_conf:
sys.exit(1)
subprocess.run(
['sudo', 'mkdir', '-p', '/etc/binhost'],
check=True)
def write_binhost(board, uri):
if uri:
data = f'PORTAGE_BINHOST="{uri}"'
else:
# Make sure to clear the file if there's no BINHOST found.
data = ''
subprocess.run(
['sudo', 'tee', f'/etc/binhost/{board}'],
input=data.encode(), stdout=subprocess.DEVNULL, check=True)
def is_internal():
url = subprocess.run(
['git', 'config', '--get', 'remote.origin.url'],
stdout=subprocess.PIPE,
cwd='/mnt/host/source/.repo/manifests/',
check=True).stdout.decode().strip()
if 'chrome-internal.googlesource.com/chromeos/manifest-internal' in url:
return True
if 'chromium.googlesource.com/chromiumos/manifest' in url:
return False
print(f'Unknown manifest source {url}')
sys.exit(1)
def get_snapshot_hashes():
# 48 snapshots = 24 hours
commit_range = 'HEAD~48..HEAD'
internal = subprocess.run(
['git', 'log', commit_range, '--format=%H'],
stdout=subprocess.PIPE,
cwd='/mnt/host/source/.repo/manifests/',
check=True).stdout.decode().split()
if not is_internal():
return internal
external = subprocess.run(
['git', 'log', commit_range,
'--format=%(trailers:key=Cr-External-Snapshot'
',separator=,valueonly)'],
stdout=subprocess.PIPE,
cwd='/mnt/host/source/.repo/manifests/',
check=True).stdout.decode().split()
# Flatten the list of (internal, external) pairs. This gives us a
# priority order where newer snapshots > older, and then internal
# snapshots > external snapshots.
return [rev for pair in zip(internal, external) for rev in pair]
def download_json(tmp, rev):
subprocess.run(['gsutil', '-m', 'cp', '-r',
f'gs://chromeos-prebuilt/snapshot/{rev}',
tmp],
stderr=subprocess.DEVNULL)
# Don't check the return code because gsutil returns non-zero if
# some of the paths don't exist.
def parse_json(tmp, rev):
result = []
for curr, _, files in os.walk(tmp / rev):
for f in files:
with open(Path(curr) / f, 'r') as f:
result.append(json.loads(f.read()))
return result
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=f"""Find up-to-date binary package sources
Chrome OS tries to speed up builds by using binary packages taken from
CI builds. However, the configuration file that controls which build
these packages are taken from is itself a part of the git repo, which
means it is effectivly always out of date. See crbug.com/1073565 for
more details.
This script tries to solve this by looking up recent binary prebuilts
for all boards. This is most useful if you sync to the "stable"
manifest branch, which will always have up to date prebuilts. If you
haven't used this script before, you must add the line
{conf_line}
to the file {user_conf} in your chroot.""")
parser.add_argument(
'board', nargs='?', default='', type=str,
help='By default, we try to configure prebuilts for all '
'available boards. This can be slow. If you only want to '
'configure a single board, set this option.')
parser.add_argument(
'profile', nargs='?', default='base', type=str,
help='Portage allows each board to have many profiles which '
'can have different build options configured, and therefore '
'require different prebuilts. This script can only configure '
'a board to use prebuilts from one profile at a time. By '
'default we use "base" for everything, and this is usually '
'correct, but if you set up your sysroot by passing the '
'"--profile" option to setup_board you will need to set this. '
'Note that this is not persistent. If you run the default '
'form of this command any binhosts you set with this '
'will be overwritten.')
args = parser.parse_args()
run_prechecks()
# Snapshot hashes are listed newest-to-oldest, so in descending
# order of priority.
revs = get_snapshot_hashes()
# Files in /etc/binhost overwrite the normal BINHOST selection, so
# we don't want to leave stale data in there ever. Make sure
# binhost_map contains an entry for every file we manage. If we
# didn't get any results, we will clear the file in write_binhost.
#
# If we're only querying a single builder, don't worry about
# it. Not getting a result for a board doesn't mean that file is
# stale.
binhost_map = defaultdict(str)
if not args.board:
for path in os.listdir('/etc/binhost'):
binhost_map[path] = ''
else:
binhost_map[args.board] = ''
tmp = tempfile.TemporaryDirectory()
tmp_path = Path(tmp.name)
binhosts_found = 0
for rev in revs:
download_json(tmp_path, rev)
builds = parse_json(tmp_path, rev)
for build in builds:
if build['profile']['name'] != args.profile:
continue
if args.board and build['buildTarget']['name'] != args.board:
continue
# Higher priority build already found
if binhost_map[build['buildTarget']['name']]:
continue
binhost_map[build['buildTarget']['name']] = build['location']
binhosts_found += 1
# if args.board is set, we only need to find one build.
if args.board and binhosts_found:
break
# Rough estimate of the total number of boards
if binhosts_found > 135:
break
for board, uri in binhost_map.items():
write_binhost(board, uri)
print(f'Found binhosts for {binhosts_found} boards')
if __name__ == '__main__':
main()