1#!/usr/bin/env python
2# Copyright 2020 The Chromium Authors. All rights reserved.
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 SDK to the given revision. Should be used in a 'hooks_os'
6entry so that it only runs when .gclient's target_os includes 'fuchsia'."""
7
8import argparse
9import itertools
10import logging
11import os
12import re
13import shutil
14import subprocess
15import sys
16import tarfile
17
18from common import GetHostOsFromPlatform, GetHostArchFromPlatform, \
19                   DIR_SOURCE_ROOT, IMAGES_ROOT
20from update_sdk import DownloadAndUnpackFromCloudStorage, \
21                       GetOverrideCloudStorageBucket, GetSdkHash, \
22                       MakeCleanDirectory, SDK_SIGNATURE_FILE
23
24
25def GetSdkSignature(sdk_hash, boot_images):
26  return 'gn:{sdk_hash}:{boot_images}:'.format(sdk_hash=sdk_hash,
27                                               boot_images=boot_images)
28
29
30def GetAllImages(boot_image_names):
31  if not boot_image_names:
32    return
33
34  all_device_types = ['generic', 'qemu']
35  all_archs = ['x64', 'arm64']
36
37  images_to_download = set()
38
39  for boot_image in boot_image_names.split(','):
40    components = boot_image.split('.')
41    if len(components) != 2:
42      continue
43
44    device_type, arch = components
45    device_images = all_device_types if device_type == '*' else [device_type]
46    arch_images = all_archs if arch == '*' else [arch]
47    images_to_download.update(itertools.product(device_images, arch_images))
48  return images_to_download
49
50
51def DownloadSdkBootImages(bucket, sdk_hash, boot_image_names, image_root_dir):
52  images_to_download = GetAllImages(boot_image_names)
53  for image_to_download in images_to_download:
54    device_type = image_to_download[0]
55    arch = image_to_download[1]
56    image_output_dir = os.path.join(image_root_dir, arch, device_type)
57    if os.path.exists(image_output_dir):
58      continue
59
60    logging.info('Downloading Fuchsia boot images for %s.%s...' %
61                 (device_type, arch))
62
63    # Images use different formats. See fxbug.dev/85552.
64    if bucket == 'fuchsia-sdk' or device_type == "workstation":
65      type_arch_connector = '.'
66    else:
67      type_arch_connector = '-'
68
69    images_tarball_url = 'gs://{bucket}/development/{sdk_hash}/images/'\
70        '{device_type}{type_arch_connector}{arch}.tgz'.format(
71            bucket=bucket, sdk_hash=sdk_hash, device_type=device_type,
72            type_arch_connector=type_arch_connector, arch=arch)
73    DownloadAndUnpackFromCloudStorage(images_tarball_url, image_output_dir)
74
75
76def GetNewSignature(sdk_hash, boot_images):
77  return GetSdkSignature(sdk_hash, boot_images)
78
79
80def main():
81  parser = argparse.ArgumentParser()
82  parser.add_argument('--verbose',
83                      '-v',
84                      action='store_true',
85                      help='Enable debug-level logging.')
86  parser.add_argument(
87      '--boot-images',
88      type=str,
89      required=True,
90      help='List of boot images to download, represented as a comma separated '
91      'list. Wildcards are allowed. ')
92  parser.add_argument(
93      '--default-bucket',
94      type=str,
95      default='fuchsia',
96      help='The Google Cloud Storage bucket in which the Fuchsia images are '
97      'stored. Entry in sdk-bucket.txt will override this flag.')
98  parser.add_argument(
99      '--image-root-dir',
100      default=IMAGES_ROOT,
101      help='Specify the root directory of the downloaded images. Optional')
102  args = parser.parse_args()
103
104  logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
105
106  # If no boot images need to be downloaded, exit.
107  if not args.boot_images:
108    return 0
109
110  # Check whether there's SDK support for this platform.
111  GetHostOsFromPlatform()
112
113  # Use the bucket in sdk-bucket.txt if an entry exists.
114  # Otherwise use the default bucket.
115  bucket = GetOverrideCloudStorageBucket() or args.default_bucket
116
117  sdk_hash = GetSdkHash(bucket)
118  if not sdk_hash:
119    return 1
120
121  signature_filename = os.path.join(args.image_root_dir, SDK_SIGNATURE_FILE)
122  current_signature = (open(signature_filename, 'r').read().strip()
123                       if os.path.exists(signature_filename) else '')
124  new_signature = GetNewSignature(sdk_hash, args.boot_images)
125  if current_signature != new_signature:
126    logging.info('Downloading Fuchsia images %s...' % sdk_hash)
127    MakeCleanDirectory(args.image_root_dir)
128
129    try:
130      DownloadSdkBootImages(bucket, sdk_hash, args.boot_images,
131                            args.image_root_dir)
132      with open(signature_filename, 'w') as f:
133        f.write(new_signature)
134
135    except subprocess.CalledProcessError as e:
136      logging.error(("command '%s' failed with status %d.%s"), " ".join(e.cmd),
137                    e.returncode, " Details: " + e.output if e.output else "")
138
139  return 0
140
141
142if __name__ == '__main__':
143  sys.exit(main())
144