Source code

Revision control

Copy as Markdown

Other Tools

#!/usr/bin/env vpython3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Updates the Fuchsia product bundles to the given revision. Should be used
in a 'hooks_os' entry so that it only runs when .gclient's target_os includes
'fuchsia'."""
import argparse
import json
import logging
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
'test')))
import common
import update_sdk
from compatible_utils import running_unattended
# TODO(crbug.com/40863468): Remove when the old scripts have been deprecated.
_IMAGE_TO_PRODUCT_BUNDLE = {
'qemu.arm64': 'terminal.qemu-arm64',
'qemu.x64': 'terminal.x64',
}
# TODO(crbug.com/40863468): Remove when the old scripts have been deprecated.
def convert_to_products(images_list):
"""Convert image names in the SDK to product bundle names."""
product_bundle_list = []
for image in images_list:
if image in _IMAGE_TO_PRODUCT_BUNDLE:
logging.warning(f'Image name {image} has been deprecated. Use '
f'{_IMAGE_TO_PRODUCT_BUNDLE.get(image)} instead.')
product_bundle_list.append(_IMAGE_TO_PRODUCT_BUNDLE[image])
else:
if image.endswith('-release'):
image = image[:-len('-release')]
logging.warning(f'Image name {image}-release has been deprecated. Use '
f'{image} instead.')
product_bundle_list.append(image)
return product_bundle_list
def remove_repositories(repo_names_to_remove):
"""Removes given repos from repo list.
Repo MUST be present in list to succeed.
Args:
repo_names_to_remove: List of repo names (as strings) to remove.
"""
for repo_name in repo_names_to_remove:
common.run_ffx_command(cmd=('repository', 'remove', repo_name))
def get_repositories():
"""Lists repositories that are available on disk.
Also prunes repositories that are listed, but do not have an actual packages
directory.
Returns:
List of dictionaries containing info about the repositories. They have the
following structure:
{
'name': <repo name>,
'spec': {
'type': <type, usually pm>,
'path': <path to packages directory>
},
}
"""
repos = json.loads(
common.run_ffx_command(cmd=('--machine', 'json', 'repository', 'list'),
capture_output=True).stdout.strip())
to_prune = set()
sdk_root_abspath = os.path.abspath(os.path.dirname(common.SDK_ROOT))
for repo in repos:
# Confirm the path actually exists. If not, prune list.
# Also assert the product-bundle repository is for the current repo
# (IE within the same directory).
if not os.path.exists(repo['spec']['path']):
to_prune.add(repo['name'])
if not repo['spec']['path'].startswith(sdk_root_abspath):
to_prune.add(repo['name'])
repos = [repo for repo in repos if repo['name'] not in to_prune]
remove_repositories(to_prune)
return repos
def get_current_signature(image_dir):
"""Determines the current version of the image, if it exists.
Returns:
The current version, or None if the image is non-existent.
"""
version_file = os.path.join(image_dir, 'product_bundle.json')
if os.path.exists(version_file):
with open(version_file) as f:
try:
data = json.load(f)
except json.decoder.JSONDecodeError:
logging.warning('product_bundle.json is not at the JSON format and may be empty.')
return None
if 'product_version' not in data:
logging.warning('The key "product_version" does not exist in product_bundle.json')
return None
return data['product_version']
return None
# VisibleForTesting
def internal_hash():
hash_filename = os.path.join(os.path.dirname(__file__),
'linux_internal.sdk.sha1')
return (open(hash_filename, 'r').read().strip()
if os.path.exists(hash_filename) else '')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--verbose',
'-v',
action='store_true',
help='Enable debug-level logging.')
parser.add_argument(
'products',
type=str,
help='List of product bundles to download, represented as a comma '
'separated list.')
parser.add_argument(
'--internal',
action='store_true',
help='Whether the images are coming from internal, it impacts version '
'file, bucket and download location.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
# Check whether there's Fuchsia support for this platform.
common.get_host_os()
new_products = convert_to_products(args.products.split(','))
logging.debug('Searching for the following products: %s', str(new_products))
logging.debug('Getting new SDK hash')
if args.internal:
new_hash = internal_hash()
else:
new_hash = common.get_hash_from_sdk()
auth_args = [
'--auth',
os.path.join(os.path.dirname(__file__), 'get_auth_token.py')
] if running_unattended() else []
for product in new_products:
prod, board = product.split('.', 1)
if prod.startswith('smart_display_') and board in [
'astro', 'sherlock', 'nelson'
]:
# This is a hacky way of keeping the files into the folders matching
# the original image name, since the definition is unfortunately in
# src-internal. Likely we can download two copies for a smooth
# transition, but it would be easier to keep it as-is during the ffx
# product v2 migration.
# TODO(crbug.com/40938340): Migrate the image download folder away from
# the following hack.
prod, board = board + '-release', prod
if args.internal:
# sdk_override.txt does not work for internal images.
override_url = None
image_dir = os.path.join(common.INTERNAL_IMAGES_ROOT, prod, board)
else:
override_url = update_sdk.GetSDKOverrideGCSPath()
if override_url:
# TODO(zijiehe): Convert to removesuffix once python 3.9 is supported.
if override_url.endswith('/sdk'):
override_url = override_url[:-len('/sdk')]
logging.debug(f'Using {override_url} from override file.')
image_dir = os.path.join(common.IMAGES_ROOT, prod, board)
curr_signature = get_current_signature(image_dir)
if not override_url and curr_signature == new_hash:
continue
common.make_clean_directory(image_dir)
base_url = override_url or 'gs://{bucket}/development/{new_hash}'.format(
bucket='fuchsia-sdk' if args.internal else 'fuchsia', new_hash=new_hash)
effective_auth_args = auth_args if base_url.startswith(
'gs://fuchsia-artifacts-internal/') or base_url.startswith(
'gs://fuchsia-sdk/') else []
lookup_output = common.run_ffx_command(cmd=[
'--machine', 'json', 'product', 'lookup', product, new_hash,
'--base-url', base_url
] + effective_auth_args,
capture_output=True).stdout.strip()
download_url = json.loads(lookup_output)['transfer_manifest_url']
# The download_url is purely a timestamp based gs location and is fairly
# meaningless, so we log the base_url instead which contains the sdk version
# if it's not coming from the sdk_override.txt file.
logging.info(f'Downloading {product} from {base_url} and {download_url}.')
common.run_ffx_command(
cmd=['product', 'download', download_url, image_dir] +
effective_auth_args)
return 0
if __name__ == '__main__':
sys.exit(main())