xref: /aosp_15_r20/external/cronet/build/fuchsia/update_images.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6777b538SAndroid Build Coastguard Worker# Copyright 2020 The Chromium Authors
3*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
5*6777b538SAndroid Build Coastguard Worker"""Updates the Fuchsia images to the given revision. Should be used in a
6*6777b538SAndroid Build Coastguard Worker'hooks_os' entry so that it only runs when .gclient's target_os includes
7*6777b538SAndroid Build Coastguard Worker'fuchsia'. Note, for a smooth transition, this file automatically adds
8*6777b538SAndroid Build Coastguard Worker'-release' at the end of the image gcs file name to eliminate the difference
9*6777b538SAndroid Build Coastguard Workerbetween product bundle v2 and gce files."""
10*6777b538SAndroid Build Coastguard Worker
11*6777b538SAndroid Build Coastguard Worker# TODO(crbug.com/1496426): Remove this file.
12*6777b538SAndroid Build Coastguard Worker
13*6777b538SAndroid Build Coastguard Workerimport argparse
14*6777b538SAndroid Build Coastguard Workerimport itertools
15*6777b538SAndroid Build Coastguard Workerimport logging
16*6777b538SAndroid Build Coastguard Workerimport os
17*6777b538SAndroid Build Coastguard Workerimport re
18*6777b538SAndroid Build Coastguard Workerimport subprocess
19*6777b538SAndroid Build Coastguard Workerimport sys
20*6777b538SAndroid Build Coastguard Workerfrom typing import Dict, Optional
21*6777b538SAndroid Build Coastguard Worker
22*6777b538SAndroid Build Coastguard Workersys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
23*6777b538SAndroid Build Coastguard Worker                                             'test')))
24*6777b538SAndroid Build Coastguard Worker
25*6777b538SAndroid Build Coastguard Workerfrom common import DIR_SRC_ROOT, IMAGES_ROOT, get_host_os, \
26*6777b538SAndroid Build Coastguard Worker                   make_clean_directory
27*6777b538SAndroid Build Coastguard Worker
28*6777b538SAndroid Build Coastguard Workerfrom gcs_download import DownloadAndUnpackFromCloudStorage
29*6777b538SAndroid Build Coastguard Worker
30*6777b538SAndroid Build Coastguard Workerfrom update_sdk import GetSDKOverrideGCSPath
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard WorkerIMAGE_SIGNATURE_FILE = '.hash'
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Worker
35*6777b538SAndroid Build Coastguard Worker# TODO(crbug.com/1138433): Investigate whether we can deprecate
36*6777b538SAndroid Build Coastguard Worker# use of sdk_bucket.txt.
37*6777b538SAndroid Build Coastguard Workerdef GetOverrideCloudStorageBucket():
38*6777b538SAndroid Build Coastguard Worker  """Read bucket entry from sdk_bucket.txt"""
39*6777b538SAndroid Build Coastguard Worker  return ReadFile('sdk-bucket.txt').strip()
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker
42*6777b538SAndroid Build Coastguard Workerdef ReadFile(filename):
43*6777b538SAndroid Build Coastguard Worker  """Read a file in this directory."""
44*6777b538SAndroid Build Coastguard Worker  with open(os.path.join(os.path.dirname(__file__), filename), 'r') as f:
45*6777b538SAndroid Build Coastguard Worker    return f.read()
46*6777b538SAndroid Build Coastguard Worker
47*6777b538SAndroid Build Coastguard Worker
48*6777b538SAndroid Build Coastguard Workerdef StrExpansion():
49*6777b538SAndroid Build Coastguard Worker  return lambda str_value: str_value
50*6777b538SAndroid Build Coastguard Worker
51*6777b538SAndroid Build Coastguard Worker
52*6777b538SAndroid Build Coastguard Workerdef VarLookup(local_scope):
53*6777b538SAndroid Build Coastguard Worker  return lambda var_name: local_scope['vars'][var_name]
54*6777b538SAndroid Build Coastguard Worker
55*6777b538SAndroid Build Coastguard Worker
56*6777b538SAndroid Build Coastguard Workerdef GetImageHashList(bucket):
57*6777b538SAndroid Build Coastguard Worker  """Read filename entries from sdk-hash-files.list (one per line), substitute
58*6777b538SAndroid Build Coastguard Worker  {platform} in each entry if present, and read from each filename."""
59*6777b538SAndroid Build Coastguard Worker  assert (get_host_os() == 'linux')
60*6777b538SAndroid Build Coastguard Worker  filenames = [
61*6777b538SAndroid Build Coastguard Worker      line.strip() for line in ReadFile('sdk-hash-files.list').replace(
62*6777b538SAndroid Build Coastguard Worker          '{platform}', 'linux_internal').splitlines()
63*6777b538SAndroid Build Coastguard Worker  ]
64*6777b538SAndroid Build Coastguard Worker  image_hashes = [ReadFile(filename).strip() for filename in filenames]
65*6777b538SAndroid Build Coastguard Worker  return image_hashes
66*6777b538SAndroid Build Coastguard Worker
67*6777b538SAndroid Build Coastguard Worker
68*6777b538SAndroid Build Coastguard Workerdef ParseDepsDict(deps_content):
69*6777b538SAndroid Build Coastguard Worker  local_scope = {}
70*6777b538SAndroid Build Coastguard Worker  global_scope = {
71*6777b538SAndroid Build Coastguard Worker      'Str': StrExpansion(),
72*6777b538SAndroid Build Coastguard Worker      'Var': VarLookup(local_scope),
73*6777b538SAndroid Build Coastguard Worker      'deps_os': {},
74*6777b538SAndroid Build Coastguard Worker  }
75*6777b538SAndroid Build Coastguard Worker  exec(deps_content, global_scope, local_scope)
76*6777b538SAndroid Build Coastguard Worker  return local_scope
77*6777b538SAndroid Build Coastguard Worker
78*6777b538SAndroid Build Coastguard Worker
79*6777b538SAndroid Build Coastguard Workerdef ParseDepsFile(filename):
80*6777b538SAndroid Build Coastguard Worker  with open(filename, 'rb') as f:
81*6777b538SAndroid Build Coastguard Worker    deps_content = f.read()
82*6777b538SAndroid Build Coastguard Worker  return ParseDepsDict(deps_content)
83*6777b538SAndroid Build Coastguard Worker
84*6777b538SAndroid Build Coastguard Worker
85*6777b538SAndroid Build Coastguard Workerdef GetImageHash(bucket):
86*6777b538SAndroid Build Coastguard Worker  """Gets the hash identifier of the newest generation of images."""
87*6777b538SAndroid Build Coastguard Worker  if bucket == 'fuchsia-sdk':
88*6777b538SAndroid Build Coastguard Worker    hashes = GetImageHashList(bucket)
89*6777b538SAndroid Build Coastguard Worker    return max(hashes)
90*6777b538SAndroid Build Coastguard Worker  deps_file = os.path.join(DIR_SRC_ROOT, 'DEPS')
91*6777b538SAndroid Build Coastguard Worker  return ParseDepsFile(deps_file)['vars']['fuchsia_version'].split(':')[1]
92*6777b538SAndroid Build Coastguard Worker
93*6777b538SAndroid Build Coastguard Worker
94*6777b538SAndroid Build Coastguard Workerdef GetImageSignature(image_hash, boot_images):
95*6777b538SAndroid Build Coastguard Worker  return 'gn:{image_hash}:{boot_images}:'.format(image_hash=image_hash,
96*6777b538SAndroid Build Coastguard Worker                                                 boot_images=boot_images)
97*6777b538SAndroid Build Coastguard Worker
98*6777b538SAndroid Build Coastguard Worker
99*6777b538SAndroid Build Coastguard Workerdef GetAllImages(boot_image_names):
100*6777b538SAndroid Build Coastguard Worker  if not boot_image_names:
101*6777b538SAndroid Build Coastguard Worker    return
102*6777b538SAndroid Build Coastguard Worker
103*6777b538SAndroid Build Coastguard Worker  all_device_types = ['generic', 'qemu']
104*6777b538SAndroid Build Coastguard Worker  all_archs = ['x64', 'arm64']
105*6777b538SAndroid Build Coastguard Worker
106*6777b538SAndroid Build Coastguard Worker  images_to_download = set()
107*6777b538SAndroid Build Coastguard Worker
108*6777b538SAndroid Build Coastguard Worker  for boot_image in boot_image_names.split(','):
109*6777b538SAndroid Build Coastguard Worker    components = boot_image.split('.')
110*6777b538SAndroid Build Coastguard Worker    if len(components) != 2:
111*6777b538SAndroid Build Coastguard Worker      continue
112*6777b538SAndroid Build Coastguard Worker
113*6777b538SAndroid Build Coastguard Worker    device_type, arch = components
114*6777b538SAndroid Build Coastguard Worker    device_images = all_device_types if device_type == '*' else [device_type]
115*6777b538SAndroid Build Coastguard Worker    arch_images = all_archs if arch == '*' else [arch]
116*6777b538SAndroid Build Coastguard Worker    images_to_download.update(itertools.product(device_images, arch_images))
117*6777b538SAndroid Build Coastguard Worker  return images_to_download
118*6777b538SAndroid Build Coastguard Worker
119*6777b538SAndroid Build Coastguard Worker
120*6777b538SAndroid Build Coastguard Workerdef DownloadBootImages(bucket, image_hash, boot_image_names, image_root_dir):
121*6777b538SAndroid Build Coastguard Worker  images_to_download = GetAllImages(boot_image_names)
122*6777b538SAndroid Build Coastguard Worker  for image_to_download in images_to_download:
123*6777b538SAndroid Build Coastguard Worker    device_type = image_to_download[0]
124*6777b538SAndroid Build Coastguard Worker    arch = image_to_download[1]
125*6777b538SAndroid Build Coastguard Worker    image_output_dir = os.path.join(image_root_dir, arch, device_type)
126*6777b538SAndroid Build Coastguard Worker    if os.path.exists(image_output_dir):
127*6777b538SAndroid Build Coastguard Worker      continue
128*6777b538SAndroid Build Coastguard Worker
129*6777b538SAndroid Build Coastguard Worker    logging.info('Downloading Fuchsia boot images for %s.%s...', device_type,
130*6777b538SAndroid Build Coastguard Worker                 arch)
131*6777b538SAndroid Build Coastguard Worker
132*6777b538SAndroid Build Coastguard Worker    # Legacy images use different naming conventions. See fxbug.dev/85552.
133*6777b538SAndroid Build Coastguard Worker    legacy_delimiter_device_types = ['qemu', 'generic']
134*6777b538SAndroid Build Coastguard Worker    if bucket == 'fuchsia-sdk' or \
135*6777b538SAndroid Build Coastguard Worker       device_type not in legacy_delimiter_device_types:
136*6777b538SAndroid Build Coastguard Worker      type_arch_connector = '.'
137*6777b538SAndroid Build Coastguard Worker    else:
138*6777b538SAndroid Build Coastguard Worker      type_arch_connector = '-'
139*6777b538SAndroid Build Coastguard Worker
140*6777b538SAndroid Build Coastguard Worker    images_tarball_url = 'gs://{bucket}/development/{image_hash}/images/'\
141*6777b538SAndroid Build Coastguard Worker        '{device_type}{type_arch_connector}{arch}.tgz'.format(
142*6777b538SAndroid Build Coastguard Worker            bucket=bucket, image_hash=image_hash, device_type=device_type,
143*6777b538SAndroid Build Coastguard Worker            type_arch_connector=type_arch_connector, arch=arch)
144*6777b538SAndroid Build Coastguard Worker    try:
145*6777b538SAndroid Build Coastguard Worker      DownloadAndUnpackFromCloudStorage(images_tarball_url, image_output_dir)
146*6777b538SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
147*6777b538SAndroid Build Coastguard Worker      logging.exception('Failed to download image %s from URL: %s',
148*6777b538SAndroid Build Coastguard Worker                        image_to_download, images_tarball_url)
149*6777b538SAndroid Build Coastguard Worker      raise e
150*6777b538SAndroid Build Coastguard Worker
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Workerdef _GetImageOverrideInfo() -> Optional[Dict[str, str]]:
153*6777b538SAndroid Build Coastguard Worker  """Get the bucket location from sdk_override.txt."""
154*6777b538SAndroid Build Coastguard Worker  location = GetSDKOverrideGCSPath()
155*6777b538SAndroid Build Coastguard Worker  if not location:
156*6777b538SAndroid Build Coastguard Worker    return None
157*6777b538SAndroid Build Coastguard Worker
158*6777b538SAndroid Build Coastguard Worker  m = re.match(r'gs://([^/]+)/development/([^/]+)/?(?:sdk)?', location)
159*6777b538SAndroid Build Coastguard Worker  if not m:
160*6777b538SAndroid Build Coastguard Worker    raise ValueError('Badly formatted image override location %s' % location)
161*6777b538SAndroid Build Coastguard Worker
162*6777b538SAndroid Build Coastguard Worker  return {
163*6777b538SAndroid Build Coastguard Worker      'bucket': m.group(1),
164*6777b538SAndroid Build Coastguard Worker      'image_hash': m.group(2),
165*6777b538SAndroid Build Coastguard Worker  }
166*6777b538SAndroid Build Coastguard Worker
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Workerdef GetImageLocationInfo(default_bucket: str,
169*6777b538SAndroid Build Coastguard Worker                         allow_override: bool = True) -> Dict[str, str]:
170*6777b538SAndroid Build Coastguard Worker  """Figures out where to pull the image from.
171*6777b538SAndroid Build Coastguard Worker
172*6777b538SAndroid Build Coastguard Worker  Defaults to the provided default bucket and generates the hash from defaults.
173*6777b538SAndroid Build Coastguard Worker  If sdk_override.txt exists (and is allowed) it uses that bucket instead.
174*6777b538SAndroid Build Coastguard Worker
175*6777b538SAndroid Build Coastguard Worker  Args:
176*6777b538SAndroid Build Coastguard Worker    default_bucket: a given default for what bucket to use
177*6777b538SAndroid Build Coastguard Worker    allow_override: allow SDK override to be used.
178*6777b538SAndroid Build Coastguard Worker
179*6777b538SAndroid Build Coastguard Worker  Returns:
180*6777b538SAndroid Build Coastguard Worker    A dictionary containing the bucket and image_hash
181*6777b538SAndroid Build Coastguard Worker  """
182*6777b538SAndroid Build Coastguard Worker  # if sdk_override.txt exists (and is allowed) use the image from that bucket.
183*6777b538SAndroid Build Coastguard Worker  if allow_override:
184*6777b538SAndroid Build Coastguard Worker    override = _GetImageOverrideInfo()
185*6777b538SAndroid Build Coastguard Worker    if override:
186*6777b538SAndroid Build Coastguard Worker      return override
187*6777b538SAndroid Build Coastguard Worker
188*6777b538SAndroid Build Coastguard Worker  # Use the bucket in sdk-bucket.txt if an entry exists.
189*6777b538SAndroid Build Coastguard Worker  # Otherwise use the default bucket.
190*6777b538SAndroid Build Coastguard Worker  bucket = GetOverrideCloudStorageBucket() or default_bucket
191*6777b538SAndroid Build Coastguard Worker  return {
192*6777b538SAndroid Build Coastguard Worker      'bucket': bucket,
193*6777b538SAndroid Build Coastguard Worker      'image_hash': GetImageHash(bucket),
194*6777b538SAndroid Build Coastguard Worker  }
195*6777b538SAndroid Build Coastguard Worker
196*6777b538SAndroid Build Coastguard Worker
197*6777b538SAndroid Build Coastguard Workerdef main():
198*6777b538SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
199*6777b538SAndroid Build Coastguard Worker  parser.add_argument('--verbose',
200*6777b538SAndroid Build Coastguard Worker                      '-v',
201*6777b538SAndroid Build Coastguard Worker                      action='store_true',
202*6777b538SAndroid Build Coastguard Worker                      help='Enable debug-level logging.')
203*6777b538SAndroid Build Coastguard Worker  parser.add_argument(
204*6777b538SAndroid Build Coastguard Worker      '--boot-images',
205*6777b538SAndroid Build Coastguard Worker      type=str,
206*6777b538SAndroid Build Coastguard Worker      required=True,
207*6777b538SAndroid Build Coastguard Worker      help='List of boot images to download, represented as a comma separated '
208*6777b538SAndroid Build Coastguard Worker      'list. Wildcards are allowed. ')
209*6777b538SAndroid Build Coastguard Worker  parser.add_argument(
210*6777b538SAndroid Build Coastguard Worker      '--default-bucket',
211*6777b538SAndroid Build Coastguard Worker      type=str,
212*6777b538SAndroid Build Coastguard Worker      default='fuchsia',
213*6777b538SAndroid Build Coastguard Worker      help='The Google Cloud Storage bucket in which the Fuchsia images are '
214*6777b538SAndroid Build Coastguard Worker      'stored. Entry in sdk-bucket.txt will override this flag.')
215*6777b538SAndroid Build Coastguard Worker  parser.add_argument(
216*6777b538SAndroid Build Coastguard Worker      '--image-root-dir',
217*6777b538SAndroid Build Coastguard Worker      default=IMAGES_ROOT,
218*6777b538SAndroid Build Coastguard Worker      help='Specify the root directory of the downloaded images. Optional')
219*6777b538SAndroid Build Coastguard Worker  parser.add_argument(
220*6777b538SAndroid Build Coastguard Worker      '--allow-override',
221*6777b538SAndroid Build Coastguard Worker      action='store_true',
222*6777b538SAndroid Build Coastguard Worker      help='Whether sdk_override.txt can be used for fetching the image, if '
223*6777b538SAndroid Build Coastguard Worker      'it exists.')
224*6777b538SAndroid Build Coastguard Worker  args = parser.parse_args()
225*6777b538SAndroid Build Coastguard Worker
226*6777b538SAndroid Build Coastguard Worker  logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
227*6777b538SAndroid Build Coastguard Worker
228*6777b538SAndroid Build Coastguard Worker  # If no boot images need to be downloaded, exit.
229*6777b538SAndroid Build Coastguard Worker  if not args.boot_images:
230*6777b538SAndroid Build Coastguard Worker    return 0
231*6777b538SAndroid Build Coastguard Worker
232*6777b538SAndroid Build Coastguard Worker  for index, item in enumerate(args.boot_images):
233*6777b538SAndroid Build Coastguard Worker    # The gclient configuration is in the src-internal and cannot be changed
234*6777b538SAndroid Build Coastguard Worker    # atomically with the test script in src. So the update_images.py needs to
235*6777b538SAndroid Build Coastguard Worker    # support both scenarios before being fully deprecated for older milestones.
236*6777b538SAndroid Build Coastguard Worker    if not item.endswith('-release'):
237*6777b538SAndroid Build Coastguard Worker      args.boot_images[index] = item + '-release'
238*6777b538SAndroid Build Coastguard Worker
239*6777b538SAndroid Build Coastguard Worker  # Check whether there's Fuchsia support for this platform.
240*6777b538SAndroid Build Coastguard Worker  get_host_os()
241*6777b538SAndroid Build Coastguard Worker  image_info = GetImageLocationInfo(args.default_bucket, args.allow_override)
242*6777b538SAndroid Build Coastguard Worker
243*6777b538SAndroid Build Coastguard Worker  bucket = image_info['bucket']
244*6777b538SAndroid Build Coastguard Worker  image_hash = image_info['image_hash']
245*6777b538SAndroid Build Coastguard Worker
246*6777b538SAndroid Build Coastguard Worker  if not image_hash:
247*6777b538SAndroid Build Coastguard Worker    return 1
248*6777b538SAndroid Build Coastguard Worker
249*6777b538SAndroid Build Coastguard Worker  signature_filename = os.path.join(args.image_root_dir, IMAGE_SIGNATURE_FILE)
250*6777b538SAndroid Build Coastguard Worker  current_signature = (open(signature_filename, 'r').read().strip()
251*6777b538SAndroid Build Coastguard Worker                       if os.path.exists(signature_filename) else '')
252*6777b538SAndroid Build Coastguard Worker  new_signature = GetImageSignature(image_hash, args.boot_images)
253*6777b538SAndroid Build Coastguard Worker  if current_signature != new_signature:
254*6777b538SAndroid Build Coastguard Worker    logging.info('Downloading Fuchsia images %s from bucket %s...', image_hash,
255*6777b538SAndroid Build Coastguard Worker                 bucket)
256*6777b538SAndroid Build Coastguard Worker    make_clean_directory(args.image_root_dir)
257*6777b538SAndroid Build Coastguard Worker
258*6777b538SAndroid Build Coastguard Worker    try:
259*6777b538SAndroid Build Coastguard Worker      DownloadBootImages(bucket, image_hash, args.boot_images,
260*6777b538SAndroid Build Coastguard Worker                         args.image_root_dir)
261*6777b538SAndroid Build Coastguard Worker      with open(signature_filename, 'w') as f:
262*6777b538SAndroid Build Coastguard Worker        f.write(new_signature)
263*6777b538SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
264*6777b538SAndroid Build Coastguard Worker      logging.exception("command '%s' failed with status %d.%s",
265*6777b538SAndroid Build Coastguard Worker                        ' '.join(e.cmd), e.returncode,
266*6777b538SAndroid Build Coastguard Worker                        ' Details: ' + e.output if e.output else '')
267*6777b538SAndroid Build Coastguard Worker      raise e
268*6777b538SAndroid Build Coastguard Worker  else:
269*6777b538SAndroid Build Coastguard Worker    logging.info('Signatures matched! Got %s', new_signature)
270*6777b538SAndroid Build Coastguard Worker
271*6777b538SAndroid Build Coastguard Worker  return 0
272*6777b538SAndroid Build Coastguard Worker
273*6777b538SAndroid Build Coastguard Worker
274*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__':
275*6777b538SAndroid Build Coastguard Worker  sys.exit(main())
276