xref: /aosp_15_r20/external/angle/build/fuchsia/update_product_bundles.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/env vpython3
2# Copyright 2022 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Updates the Fuchsia product bundles to the given revision. Should be used
6in a 'hooks_os' entry so that it only runs when .gclient's target_os includes
7'fuchsia'."""
8
9import argparse
10import json
11import logging
12import os
13import sys
14
15sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
16                                             'test')))
17
18import common
19import update_sdk
20from compatible_utils import running_unattended
21
22
23# TODO(crbug.com/40863468): Remove when the old scripts have been deprecated.
24_IMAGE_TO_PRODUCT_BUNDLE = {
25    'qemu.arm64': 'terminal.qemu-arm64',
26    'qemu.x64': 'terminal.x64',
27}
28
29
30# TODO(crbug.com/40863468): Remove when the old scripts have been deprecated.
31def convert_to_products(images_list):
32  """Convert image names in the SDK to product bundle names."""
33
34  product_bundle_list = []
35  for image in images_list:
36    if image in _IMAGE_TO_PRODUCT_BUNDLE:
37      logging.warning(f'Image name {image} has been deprecated. Use '
38                      f'{_IMAGE_TO_PRODUCT_BUNDLE.get(image)} instead.')
39      product_bundle_list.append(_IMAGE_TO_PRODUCT_BUNDLE[image])
40    else:
41      if image.endswith('-release'):
42        image = image[:-len('-release')]
43        logging.warning(f'Image name {image}-release has been deprecated. Use '
44                        f'{image} instead.')
45      product_bundle_list.append(image)
46  return product_bundle_list
47
48
49def remove_repositories(repo_names_to_remove):
50  """Removes given repos from repo list.
51  Repo MUST be present in list to succeed.
52
53  Args:
54    repo_names_to_remove: List of repo names (as strings) to remove.
55  """
56  for repo_name in repo_names_to_remove:
57    common.run_ffx_command(cmd=('repository', 'remove', repo_name))
58
59
60def get_repositories():
61  """Lists repositories that are available on disk.
62
63  Also prunes repositories that are listed, but do not have an actual packages
64  directory.
65
66  Returns:
67    List of dictionaries containing info about the repositories. They have the
68    following structure:
69    {
70      'name': <repo name>,
71      'spec': {
72        'type': <type, usually pm>,
73        'path': <path to packages directory>
74      },
75    }
76  """
77
78  repos = json.loads(
79      common.run_ffx_command(cmd=('--machine', 'json', 'repository', 'list'),
80                             capture_output=True).stdout.strip())
81  to_prune = set()
82  sdk_root_abspath = os.path.abspath(os.path.dirname(common.SDK_ROOT))
83  for repo in repos:
84    # Confirm the path actually exists. If not, prune list.
85    # Also assert the product-bundle repository is for the current repo
86    # (IE within the same directory).
87    if not os.path.exists(repo['spec']['path']):
88      to_prune.add(repo['name'])
89
90    if not repo['spec']['path'].startswith(sdk_root_abspath):
91      to_prune.add(repo['name'])
92
93  repos = [repo for repo in repos if repo['name'] not in to_prune]
94
95  remove_repositories(to_prune)
96  return repos
97
98
99def get_current_signature(image_dir):
100  """Determines the current version of the image, if it exists.
101
102  Returns:
103    The current version, or None if the image is non-existent.
104  """
105
106  version_file = os.path.join(image_dir, 'product_bundle.json')
107  if os.path.exists(version_file):
108    with open(version_file) as f:
109      return json.load(f)['product_version']
110  return None
111
112
113# VisibleForTesting
114def internal_hash():
115  hash_filename = os.path.join(os.path.dirname(__file__),
116                               'linux_internal.sdk.sha1')
117  return (open(hash_filename, 'r').read().strip()
118          if os.path.exists(hash_filename) else '')
119
120
121def main():
122  parser = argparse.ArgumentParser()
123  parser.add_argument('--verbose',
124                      '-v',
125                      action='store_true',
126                      help='Enable debug-level logging.')
127  parser.add_argument(
128      'products',
129      type=str,
130      help='List of product bundles to download, represented as a comma '
131      'separated list.')
132  parser.add_argument(
133      '--internal',
134      action='store_true',
135      help='Whether the images are coming from internal, it impacts version '
136      'file, bucket and download location.')
137  args = parser.parse_args()
138
139  logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
140
141  # Check whether there's Fuchsia support for this platform.
142  common.get_host_os()
143
144  new_products = convert_to_products(args.products.split(','))
145  logging.debug('Searching for the following products: %s', str(new_products))
146
147  logging.debug('Getting new SDK hash')
148  if args.internal:
149    new_hash = internal_hash()
150  else:
151    new_hash = common.get_hash_from_sdk()
152
153  auth_args = [
154      '--auth',
155      os.path.join(os.path.dirname(__file__), 'get_auth_token.py')
156  ] if running_unattended() else []
157  for product in new_products:
158    prod, board = product.split('.', 1)
159    if prod.startswith('smart_display_') and board in [
160        'astro', 'sherlock', 'nelson'
161    ]:
162      # This is a hacky way of keeping the files into the folders matching
163      # the original image name, since the definition is unfortunately in
164      # src-internal. Likely we can download two copies for a smooth
165      # transition, but it would be easier to keep it as-is during the ffx
166      # product v2 migration.
167      # TODO(crbug.com/40938340): Migrate the image download folder away from
168      # the following hack.
169      prod, board = board + '-release', prod
170    if args.internal:
171      # sdk_override.txt does not work for internal images.
172      override_url = None
173      image_dir = os.path.join(common.INTERNAL_IMAGES_ROOT, prod, board)
174    else:
175      override_url = update_sdk.GetSDKOverrideGCSPath()
176      if override_url:
177        # TODO(zijiehe): Convert to removesuffix once python 3.9 is supported.
178        if override_url.endswith('/sdk'):
179          override_url = override_url[:-len('/sdk')]
180        logging.debug(f'Using {override_url} from override file.')
181      image_dir = os.path.join(common.IMAGES_ROOT, prod, board)
182    curr_signature = get_current_signature(image_dir)
183
184    if not override_url and curr_signature == new_hash:
185      continue
186
187    common.make_clean_directory(image_dir)
188    base_url = override_url or 'gs://{bucket}/development/{new_hash}'.format(
189        bucket='fuchsia-sdk' if args.internal else 'fuchsia', new_hash=new_hash)
190    effective_auth_args = auth_args if base_url.startswith(
191        'gs://fuchsia-artifacts-internal/') or base_url.startswith(
192            'gs://fuchsia-sdk/') else []
193    lookup_output = common.run_ffx_command(cmd=[
194        '--machine', 'json', 'product', 'lookup', product, new_hash,
195        '--base-url', base_url
196    ] + effective_auth_args,
197                                           capture_output=True).stdout.strip()
198    download_url = json.loads(lookup_output)['transfer_manifest_url']
199    # The download_url is purely a timestamp based gs location and is fairly
200    # meaningless, so we log the base_url instead which contains the sdk version
201    # if it's not coming from the sdk_override.txt file.
202    logging.info(f'Downloading {product} from {base_url} and {download_url}.')
203    common.run_ffx_command(
204        cmd=['product', 'download', download_url, image_dir] +
205        effective_auth_args)
206
207  return 0
208
209
210if __name__ == '__main__':
211  sys.exit(main())
212