blob: d9ccb1615160b93742b43a16bc9eee2498dd0740 [file] [log] [blame]
David Riley0655bab2017-11-02 10:44:26 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
Chris Sosa47a7d4e2012-03-28 11:26:55 -07003# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Module containing classes that wrap artifact downloads."""
8
Gabe Black3b567202015-09-23 14:07:59 -07009from __future__ import print_function
10
11import itertools
Chris Sosa47a7d4e2012-03-28 11:26:55 -070012import os
Dan Shi6e50c722013-08-19 15:05:06 -070013import pickle
Gilad Arnold950569b2013-08-27 14:38:01 -070014import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -070015import shutil
16import subprocess
xixuan56252ff2017-03-09 15:40:31 -080017import traceback
Chris Sosa47a7d4e2012-03-28 11:26:55 -070018
Chris Sosa76e44b92013-01-31 12:11:38 -080019import artifact_info
20import common_util
joychen3cb228e2013-06-12 12:13:13 -070021import devserver_constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070022import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070023
Don Garrettfb15e322016-06-21 19:12:08 -070024# We do a number of things with args/kwargs arguments that confuse pylint.
25# pylint: disable=docstring-misnamed-args
Chris Sosa47a7d4e2012-03-28 11:26:55 -070026
Chris Sosa76e44b92013-01-31 12:11:38 -080027_AU_BASE = 'au'
28_NTON_DIR_SUFFIX = '_nton'
29_MTON_DIR_SUFFIX = '_mton'
30
31############ Actual filenames of artifacts in Google Storage ############
32
33AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070034PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080035AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070036CONTROL_FILES_FILE = 'control_files.tar'
37AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080038AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080039AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080040DEBUG_SYMBOLS_FILE = 'debug.tgz'
Dan Shi55d0f972016-10-04 11:45:00 -070041DEBUG_SYMBOLS_ONLY_FILE = 'debug_breakpad.tar.xz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080042FACTORY_FILE = 'ChromeOS-factory*.zip'
Mike Frysingera0e6a282016-09-01 17:29:08 -040043FACTORY_SHIM_FILE = 'factory_image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080044FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
45IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080046TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080047BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
48TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
49RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
Nick Sandersa995c9d2019-05-07 17:45:43 -070050SIGNED_RECOVERY_IMAGE_FILE = 'chromeos_*recovery*mp*.bin'
Justin Giorgib7590522017-02-07 13:36:24 -080051LIBIOTA_TEST_BINARIES_FILE = 'test_binaries.tar.gz'
52LIBIOTA_BOARD_UTILS_FILE = 'board_utils.tar.gz'
David Riley0655bab2017-11-02 10:44:26 -070053QUICK_PROVISION_FILE = 'full_dev_part_*.bin.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080054
Gabe Black3b567202015-09-23 14:07:59 -070055############ Actual filenames of Android build artifacts ############
56
Dan Shiba4e00f2015-10-27 12:03:53 -070057ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070058ANDROID_RADIO_IMAGE = 'radio.img'
59ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
60ANDROID_FASTBOOT = 'fastboot'
61ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080062ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080063ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
64ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
65ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070066ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070067ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070068ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
69 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070070ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070071ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
72ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Satoshi Niwa5e3ed5e2018-06-25 16:03:33 +090073ANDROID_PUSH_TO_DEVICE_ZIP = 'push_to_device.zip'
Qijiang Fan31beb7f2018-06-26 17:09:00 +090074ANDROID_SEPOLICY_ZIP = 'sepolicy.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080075
76_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070077
78
79class ArtifactDownloadError(Exception):
80 """Error used to signify an issue processing an artifact."""
81 pass
82
83
Gabe Black3b567202015-09-23 14:07:59 -070084class ArtifactMeta(type):
85 """metaclass for an artifact type.
86
87 This metaclass is for class Artifact and its subclasses to have a meaningful
88 string composed of class name and the corresponding artifact name, e.g.,
89 `Artifact_full_payload`. This helps to better logging, refer to logging in
90 method Downloader.Download.
91 """
92
93 ARTIFACT_NAME = None
94
95 def __str__(cls):
96 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
97
98 def __repr__(cls):
99 return str(cls)
100
101
102class Artifact(log_util.Loggable):
103 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700104
105 The purpose of this class is to download objects from Google Storage
106 and install them to a local directory. There are two main functions, one to
107 download/prepare the artifacts in to a temporary staging area and the second
108 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800109
Gilad Arnold950569b2013-08-27 14:38:01 -0700110 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
111 attentive when adding new artifacts; (ii) name matching semantics differ
112 between a glob (full name string match) and a regex (partial match).
113
Chris Sosa76e44b92013-01-31 12:11:38 -0800114 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700115 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700116 name: Name given for artifact; in fact, it is a pattern that captures the
117 names of files contained in the artifact. This can either be an
118 ordinary shell-style glob (the default), or a regular expression (if
119 is_regex_name is True).
120 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800121 build: The version of the build i.e. R26-2342.0.0.
122 marker_name: Name used to define the lock marker for the artifacts to
123 prevent it from being re-downloaded. By default based on name
124 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700125 exception_file_path: Path to a file containing the serialized exception,
126 which was raised in Process method. The file is located
127 in the parent folder of install_dir, since the
128 install_dir will be deleted if the build does not
129 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700130 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700131 install_subdir: Directory within install_path where the artifact is actually
132 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800133 install_dir: The final location where the artifact should be staged to.
134 single_name: If True the name given should only match one item. Note, if not
135 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800136 installed_files: A list of files that were the final result of downloading
137 and setting up the artifact.
138 store_installed_files: Whether the list of installed files is stored in the
139 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700140 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700141
Gabe Black3b567202015-09-23 14:07:59 -0700142 __metaclass__ = ArtifactMeta
143
144 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800145 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700146 """Constructor.
147
148 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800149 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800150 name: Identifying name to be used to find/store the artifact.
151 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700152 install_subdir: Directory within install_path where the artifact is
153 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700154 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800155 optional_name: An alternative name to find the artifact, which can lead
156 to faster download. Unlike |name|, there is no guarantee that an
157 artifact named |optional_name| is/will be on Google Storage. If it
158 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700159 """
Gabe Black3b567202015-09-23 14:07:59 -0700160 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700161
Chris Sosa76e44b92013-01-31 12:11:38 -0800162 # In-memory lock to keep the devserver from colliding with itself while
163 # attempting to stage the same artifact.
164 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700165
Chris Sosa76e44b92013-01-31 12:11:38 -0800166 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800167 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700168 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800169 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700170
Chris Sosa76e44b92013-01-31 12:11:38 -0800171 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700172
Dan Shi6e50c722013-08-19 15:05:06 -0700173 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
174 '.exception')
175 # The exception file needs to be located in parent folder, since the
176 # install_dir will be deleted is the build does not exist.
177 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700178 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700179
joychen0a8e34e2013-06-24 17:58:36 -0700180 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700181
Chris Sosa76e44b92013-01-31 12:11:38 -0800182 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700183 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800184
185 self.single_name = True
186
Gilad Arnold1638d822013-11-07 23:38:16 -0800187 self.installed_files = []
188 self.store_installed_files = True
189
Chris Sosa76e44b92013-01-31 12:11:38 -0800190 @staticmethod
191 def _SanitizeName(name):
192 """Sanitizes name to be used for creating a file on the filesystem.
193
194 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700195
196 Args:
197 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800198
Gilad Arnold950569b2013-08-27 14:38:01 -0700199 Returns:
200 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800201 """
202 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
203
Dan Shif8eb0d12013-08-01 17:52:06 -0700204 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800205 """Returns True if artifact is already staged.
206
207 This checks for (1) presence of the artifact marker file, and (2) the
208 presence of each installed file listed in this marker. Both must hold for
209 the artifact to be considered staged. Note that this method is safe for use
210 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700211 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800212 files actually installed into the marker.
213 """
214 marker_file = os.path.join(self.install_dir, self.marker_name)
215
216 # If the marker is missing, it's definitely not staged.
217 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700218 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800219 return False
220
221 # We want to ensure that every file listed in the marker is actually there.
222 if self.store_installed_files:
223 with open(marker_file) as f:
224 files = [line.strip() for line in f]
225
226 # Check to see if any of the purportedly installed files are missing, in
227 # which case the marker is outdated and should be removed.
228 missing_files = [fname for fname in files if not os.path.exists(fname)]
229 if missing_files:
230 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
231 'All' if len(files) == len(missing_files) else 'Some',
232 marker_file, '\n'.join(missing_files))
233 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700234 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800235 return False
236
Aviv Keshet57d18172016-06-18 20:39:09 -0700237 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800238 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800239
240 def _MarkArtifactStaged(self):
241 """Marks the artifact as staged."""
242 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800243 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800244
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800245 def _UpdateName(self, names):
246 if self.single_name and len(names) > 1:
247 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800248
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800249 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800250
joychen0a8e34e2013-06-24 17:58:36 -0700251 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800252 """Process the downloaded content, update the list of installed files."""
253 # In this primitive case, what was downloaded (has to be a single file) is
254 # what's installed.
255 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700256
Dan Shi6e50c722013-08-19 15:05:06 -0700257 def _ClearException(self):
258 """Delete any existing exception saved for this artifact."""
259 if os.path.exists(self.exception_file_path):
260 os.remove(self.exception_file_path)
261
262 def _SaveException(self, e):
xixuan56252ff2017-03-09 15:40:31 -0800263 """Save the exception and traceback to a file for downloader.IsStaged.
Dan Shi6e50c722013-08-19 15:05:06 -0700264
Gilad Arnold950569b2013-08-27 14:38:01 -0700265 Args:
266 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700267 """
268 with open(self.exception_file_path, 'w') as f:
xixuan56252ff2017-03-09 15:40:31 -0800269 pickle.dump('%s\n%s' % (e, str(traceback.format_exc())), f)
Dan Shi6e50c722013-08-19 15:05:06 -0700270
271 def GetException(self):
272 """Retrieve any exception that was raised in Process method.
273
Gilad Arnold950569b2013-08-27 14:38:01 -0700274 Returns:
275 An Exception object that was raised when trying to process the artifact.
276 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700277 """
278 if not os.path.exists(self.exception_file_path):
279 return None
280 with open(self.exception_file_path, 'r') as f:
281 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800282
Gabe Black3b567202015-09-23 14:07:59 -0700283 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800284 """Main call point to all artifacts. Downloads and Stages artifact.
285
286 Downloads and Stages artifact from Google Storage to the install directory
287 specified in the constructor. It multi-thread safe and does not overwrite
288 the artifact if it's already been downloaded or being downloaded. After
289 processing, leaves behind a marker to indicate to future invocations that
290 the artifact has already been staged based on the name of the artifact.
291
292 Do not override as it modifies important private variables, ensures thread
293 safety, and maintains cache semantics.
294
295 Note: this may be a blocking call when the artifact is already in the
296 process of being staged.
297
298 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700299 downloader: A downloader instance containing the logic to download
300 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800301 no_wait: If True, don't block waiting for artifact to exist if we fail to
302 immediately find it.
303
304 Raises:
305 ArtifactDownloadError: If the artifact fails to download from Google
306 Storage for any reason or that the regexp
307 defined by name is not specific enough.
308 """
309 if not self._process_lock:
310 self._process_lock = _build_artifact_locks.lock(
311 os.path.join(self.install_dir, self.name))
312
Gabe Black3b567202015-09-23 14:07:59 -0700313 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800314 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700315 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700316 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800317 # Delete any existing exception saved for this artifact.
318 self._ClearException()
319 found_artifact = False
320 if self.optional_name:
321 try:
Gabe Black3b567202015-09-23 14:07:59 -0700322 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800323 # Because this artifact may not always exist, don't bother
324 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700325 new_names = downloader.Wait(
326 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800327 self._UpdateName(new_names)
328
329 except ArtifactDownloadError:
330 self._Log('Unable to download %s; fall back to download %s',
331 self.optional_name, self.name)
332 else:
333 found_artifact = True
334
Dan Shi6e50c722013-08-19 15:05:06 -0700335 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700336 # If the artifact should already have been uploaded, don't waste
337 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800338 if not found_artifact:
339 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700340 new_names = downloader.Wait(
341 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800342 self._UpdateName(new_names)
343
David Rileye131a0f2017-11-02 10:42:34 -0700344
345 files = self.name if isinstance(self.name, list) else [self.name]
346 for filename in files:
347 self._Log('Downloading file %s', filename)
348 self.install_path = downloader.Fetch(filename, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700349 self._Setup()
350 self._MarkArtifactStaged()
351 except Exception as e:
352 # Save the exception to a file for downloader.IsStaged to retrieve.
353 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800354
355 # Convert an unknown exception into an ArtifactDownloadError.
356 if type(e) is ArtifactDownloadError:
357 raise
358 else:
359 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800360 else:
361 self._Log('%s is already staged.', self)
362
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700363 def __str__(self):
364 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700365 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700366
Chris Sosab26b1202013-08-16 16:40:55 -0700367 def __repr__(self):
368 return str(self)
369
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700370
Gabe Black3b567202015-09-23 14:07:59 -0700371class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700372 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700373
joychen0a8e34e2013-06-24 17:58:36 -0700374 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700375 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700376
Chris Sosa76e44b92013-01-31 12:11:38 -0800377 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700378 install_path = os.path.join(self.install_dir, self.install_subdir,
379 self.name)
380 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700381 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800382 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700383
Gilad Arnold1638d822013-11-07 23:38:16 -0800384 # Reflect the rename in the list of installed files.
385 self.installed_files.remove(install_path)
386 self.installed_files = [new_install_path]
387
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700388
David Rileye131a0f2017-11-02 10:42:34 -0700389class MultiArtifact(Artifact):
390 """Wrapper for artifacts where name matches multiple items.."""
391
392 def __init__(self, *args, **kwargs):
393 """Takes Artifact args.
394
395 Args:
396 *args: See Artifact documentation.
397 **kwargs: See Artifact documentation.
398 """
399 super(MultiArtifact, self).__init__(*args, **kwargs)
400 self.single_name = False
401
402 def _UpdateName(self, names):
403 self.name = names if isinstance(names, list) else [names]
404
405 def _Setup(self):
406 super(MultiArtifact, self)._Setup()
407
408 self.installed_files = [os.path.join(self.install_dir, self.install_subdir,
409 name) for name in self.name]
410
411
Gabe Black3b567202015-09-23 14:07:59 -0700412class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800413 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700414
Gabe Black3b567202015-09-23 14:07:59 -0700415 These artifacts are super strange. They custom handle directories and
416 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800417 because unlike other artifacts, this one does not conform to something a
418 client might know. The client doesn't know the version of n-1 or whether it
419 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700420
421 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700422 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800423 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700424
joychen0a8e34e2013-06-24 17:58:36 -0700425 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700426 super(DeltaPayloadBase, self)._Setup()
427 # Setup symlink so that AU will work for this payload.
428 stateful_update_symlink = os.path.join(
429 self.install_dir, self.install_subdir,
430 devserver_constants.STATEFUL_FILE)
431 os.symlink(os.path.join(os.pardir, os.pardir,
432 devserver_constants.STATEFUL_FILE),
433 stateful_update_symlink)
434 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700435
Chris Sosa76e44b92013-01-31 12:11:38 -0800436
Gabe Black3b567202015-09-23 14:07:59 -0700437class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800438 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800439
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800440 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700441 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700442
443 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700444 *args: See Artifact documentation.
445 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700446 files_to_extract: A list of files to extract. If set to None, extract
447 all files.
448 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800449 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800450 self._files_to_extract = kwargs.pop('files_to_extract', None)
451 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700452 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800453
454 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800455 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800456 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800457 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800458
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800459 def _RunUnzip(self, list_only):
460 # Unzip is weird. It expects its args before any excludes and expects its
461 # excludes in a list following the -x.
462 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
463 if not list_only:
464 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800465
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800466 if self._files_to_extract:
467 cmd.extend(self._files_to_extract)
468
469 if self._exclude:
470 cmd.append('-x')
471 cmd.extend(self._exclude)
472
473 try:
474 return subprocess.check_output(cmd).strip('\n').splitlines()
475 except subprocess.CalledProcessError, e:
476 raise ArtifactDownloadError(
477 'An error occurred when attempting to unzip %s:\n%s' %
478 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800479
joychen0a8e34e2013-06-24 17:58:36 -0700480 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800481 extract_result = self._Extract()
482 if self.store_installed_files:
483 # List both the archive and the extracted files.
484 self.installed_files.append(self.install_path)
485 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800486
Chris Sosa76e44b92013-01-31 12:11:38 -0800487 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800488 """Extracts files into the install path."""
489 if self.name.endswith('.zip'):
490 return self._ExtractZipfile()
491 else:
492 return self._ExtractTarball()
493
494 def _ExtractZipfile(self):
495 """Extracts a zip file using unzip."""
496 file_list = [os.path.join(self.install_dir, line[30:].strip())
497 for line in self._RunUnzip(True)
498 if not line.endswith('/')]
499 if file_list:
500 self._RunUnzip(False)
501
502 return file_list
503
504 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800505 """Extracts a tarball using tar.
506
507 Detects whether the tarball is compressed or not based on the file
508 extension and extracts the tarball into the install_path.
509 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700510 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800511 return common_util.ExtractTarball(self.install_path, self.install_dir,
512 files_to_extract=self._files_to_extract,
513 excluded_files=self._exclude,
514 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800515 except common_util.CommonUtilError as e:
516 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700517
518
Gabe Black3b567202015-09-23 14:07:59 -0700519class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700520 """Wrapper around the autotest tarball to download from gsutil."""
521
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800522 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700523 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800524 # We don't store/check explicit file lists in Autotest tarball markers;
525 # this can get huge and unwieldy, and generally make little sense.
526 self.store_installed_files = False
527
joychen0a8e34e2013-06-24 17:58:36 -0700528 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800529 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700530 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700531
Chris Sosa76e44b92013-01-31 12:11:38 -0800532 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700533 autotest_dir = os.path.join(self.install_dir,
534 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700535 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
536 if not os.path.exists(autotest_pkgs_dir):
537 os.makedirs(autotest_pkgs_dir)
538
539 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Prashant Malani20e83712017-02-28 01:30:41 -0800540 cmd = ['autotest/utils/packager.py', '--action=upload', '--repository',
Chris Sosa76e44b92013-01-31 12:11:38 -0800541 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700542 try:
joychen0a8e34e2013-06-24 17:58:36 -0700543 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700544 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800545 raise ArtifactDownloadError(
546 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700547 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700548 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700549
Chris Masone816e38c2012-05-02 12:22:36 -0700550
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700551class SignedArtifact(Artifact):
552 """Wrapper for signed artifacts which need a path translation."""
553
554 def _Setup(self):
555 super(SignedArtifact, self)._Setup()
556
557 # Rename to signed_image.bin.
558 install_path = os.path.join(self.install_dir, self.install_subdir,
559 self.name)
560 new_install_path = os.path.join(self.install_dir, self.install_subdir,
561 devserver_constants.SIGNED_IMAGE_FILE)
562 shutil.move(install_path, new_install_path)
563
564 # Reflect the rename in the list of installed files.
565 self.installed_files.remove(install_path)
566 self.installed_files = [new_install_path]
567
568
Gabe Black3b567202015-09-23 14:07:59 -0700569def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
570 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700571
Gabe Black3b567202015-09-23 14:07:59 -0700572 Args:
573 tag: Tag of the artifact, defined in artifact_info.
574 base: Class of the artifact, e.g., BundledArtifact.
575 name: Name of the artifact, e.g., image.zip.
576 *fixed_args: Fixed arguments that are additional to the one used in base
577 class.
578 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
579 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800580
Gabe Black3b567202015-09-23 14:07:59 -0700581 Returns:
582 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700583 """
Don Garrettfb15e322016-06-21 19:12:08 -0700584 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700585 class NewArtifact(base):
586 """A data wrapper that describes an artifact's implementation."""
587 ARTIFACT_TAG = tag
588 ARTIFACT_NAME = name
589
590 def __init__(self, *args, **kwargs):
591 all_args = fixed_args + args
592 all_kwargs = {}
593 all_kwargs.update(fixed_kwargs)
594 all_kwargs.update(kwargs)
595 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
596 *all_args, **all_kwargs)
597
598 NewArtifact.__name__ = base.__name__
599 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700600
Chris Sosa76e44b92013-01-31 12:11:38 -0800601
Gabe Black3b567202015-09-23 14:07:59 -0700602# TODO(dshi): Refactor the code here to split out the logic of creating the
603# artifacts mapping to a different module.
604chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800605
Chris Sosa76e44b92013-01-31 12:11:38 -0800606
Gabe Black3b567202015-09-23 14:07:59 -0700607def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700608 """Add a data wrapper for ChromeOS artifacts.
609
610 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700611 chromeos_artifact_map.
612 """
613 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
614 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800615
beepsc3d0f872013-07-31 21:50:40 -0700616
Amin Hassanie9596af2019-02-13 13:48:30 -0800617_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*bin')
Gabe Black3b567202015-09-23 14:07:59 -0700618
619
620class DeltaPayloadNtoN(DeltaPayloadBase):
621 """ChromeOS Delta payload artifact for updating from version N to N."""
622 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
623 ARTIFACT_NAME = 'NOT_APPLICABLE'
624
625 def __init__(self, install_dir, build, *args, **kwargs):
626 name = 'chromeos_%s*_delta_*' % build
627 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
628 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
629 install_subdir=install_subdir,
630 **kwargs)
631
632
633class DeltaPayloadMtoN(DeltaPayloadBase):
634 """ChromeOS Delta payload artifact for updating from version M to N."""
635 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
636 ARTIFACT_NAME = 'NOT_APPLICABLE'
637
638 def __init__(self, install_dir, build, *args, **kwargs):
639 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
640 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
641 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
642 install_subdir=install_subdir,
643 is_regex_name=True, **kwargs)
644
645
646chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
647 DeltaPayloadMtoN]
648
649
650_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
651 devserver_constants.STATEFUL_FILE)
652_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
653 optional_name=BASE_IMAGE_FILE,
654 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
655_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
656 optional_name=RECOVERY_IMAGE_FILE,
657 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
Nick Sandersa995c9d2019-05-07 17:45:43 -0700658_AddCrOSArtifact(artifact_info.SIGNED_IMAGE, SignedArtifact,
659 SIGNED_RECOVERY_IMAGE_FILE)
Gabe Black3b567202015-09-23 14:07:59 -0700660_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
661 files_to_extract=[devserver_constants.IMAGE_FILE])
662_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
663 optional_name=TEST_IMAGE_FILE,
664 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
665_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
666 files_to_extract=None, exclude=['autotest/test_suites'])
667_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
668 CONTROL_FILES_FILE)
669_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
670 AUTOTEST_PACKAGES_FILE)
671_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
672_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
673_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
674 AUTOTEST_SERVER_PACKAGE_FILE)
675_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
676_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
677 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700678_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
679 DEBUG_SYMBOLS_ONLY_FILE,
680 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700681_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
682 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400683_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
684 FACTORY_SHIM_FILE,
685 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
David Riley0655bab2017-11-02 10:44:26 -0700686_AddCrOSArtifact(artifact_info.QUICK_PROVISION, MultiArtifact,
687 QUICK_PROVISION_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800688
Chris Sosa968a1062013-08-02 17:42:50 -0700689# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700690for c in devserver_constants.CHANNELS:
691 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
692 BundledArtifact,
693 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
694
Justin Giorgib7590522017-02-07 13:36:24 -0800695#### Libiota Artifacts ####
696_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
697 LIBIOTA_TEST_BINARIES_FILE)
698_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
699 LIBIOTA_BOARD_UTILS_FILE)
700
Gabe Black3b567202015-09-23 14:07:59 -0700701android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700702
Chris Sosa76e44b92013-01-31 12:11:38 -0800703
Gabe Black3b567202015-09-23 14:07:59 -0700704def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700705 """Add a data wrapper for android artifacts.
706
707 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700708 android_artifact_map.
709 """
710 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
711 android_artifact_map.setdefault(tag, []).append(artifact)
712
713
Dan Shiba4e00f2015-10-27 12:03:53 -0700714_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700715 ANDROID_IMAGE_ZIP, is_regex_name=True)
716_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
717 ANDROID_RADIO_IMAGE)
718_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
719 ANDROID_BOOTLOADER_IMAGE)
720_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
721_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
722 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800723_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
724 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800725_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
726 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
727_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
728 ANDROID_TEST_SUITES, is_regex_name=True)
729_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
730 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700731_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
732 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700733_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
734 BundledArtifact,
735 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
736 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700737_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
738 BundledArtifact,
739 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
740 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700741_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
742 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700743_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
744 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
745_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
746 ANDROID_DTB_ZIP, is_regex_name=True)
Satoshi Niwa5e3ed5e2018-06-25 16:03:33 +0900747_AddAndroidArtifact(artifact_info.ANDROID_PUSH_TO_DEVICE_ZIP,
748 Artifact, ANDROID_PUSH_TO_DEVICE_ZIP)
Qijiang Fan31beb7f2018-06-26 17:09:00 +0900749_AddAndroidArtifact(artifact_info.ANDROID_SEPOLICY_ZIP,
750 Artifact, ANDROID_SEPOLICY_ZIP)
Gabe Black3b567202015-09-23 14:07:59 -0700751
752class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800753 """A factory class that generates build artifacts from artifact names."""
754
Dan Shi6c2b2a22016-03-04 15:52:19 -0800755 def __init__(self, artifact_map, download_dir, artifacts, files, build,
756 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800757 """Initalizes the member variables for the factory.
758
759 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700760 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700761 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700762 artifacts: List of artifacts to stage. These artifacts must be
763 defined in artifact_info.py and have a mapping in the
764 ARTIFACT_IMPLEMENTATION_MAP.
765 files: List of files to stage. These files are just downloaded and staged
766 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800767 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800768 requested_to_optional_map: A map between an artifact X to a list of
769 artifacts Y. If X is requested, all items in Y should also get
770 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800771 """
Gabe Black3b567202015-09-23 14:07:59 -0700772 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700773 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700774 self.artifacts = artifacts
775 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800776 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800777 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800778
Chris Sosa6b0c6172013-08-05 17:01:33 -0700779 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700780 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700781
782 If is_artifact is true, then these names define artifacts that must exist in
783 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
784 basic BuildArtifacts.
785
Gilad Arnold950569b2013-08-27 14:38:01 -0700786 Args:
787 names: A sequence of artifact names.
788 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800789
Gilad Arnold950569b2013-08-27 14:38:01 -0700790 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700791 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800792
Gilad Arnold950569b2013-08-27 14:38:01 -0700793 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700794 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700795 """
Gabe Black3b567202015-09-23 14:07:59 -0700796 if is_artifact:
797 classes = itertools.chain(*(self.artifact_map[name] for name in names))
798 return list(cls(self.download_dir, self.build) for cls in classes)
799 else:
800 return list(Artifact(name, self.download_dir, self.build)
801 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800802
803 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700804 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700805
Gilad Arnold950569b2013-08-27 14:38:01 -0700806 Returns:
807 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800808
Gilad Arnold950569b2013-08-27 14:38:01 -0700809 Raises:
810 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700811 """
812 artifacts = []
813 if self.artifacts:
814 artifacts.extend(self._Artifacts(self.artifacts, True))
815 if self.files:
816 artifacts.extend(self._Artifacts(self.files, False))
817
818 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800819
820 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700821 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700822
Gilad Arnold950569b2013-08-27 14:38:01 -0700823 Returns:
824 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800825
Gilad Arnold950569b2013-08-27 14:38:01 -0700826 Raises:
827 KeyError: if an optional artifact doesn't exist in
828 ARTIFACT_IMPLEMENTATION_MAP yet defined in
829 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700830 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800831 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800832 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800833 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700834 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800835 optional_names = optional_names.union(optional_list)
836
Chris Sosa6b0c6172013-08-05 17:01:33 -0700837 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700838
839
Gabe Black3b567202015-09-23 14:07:59 -0700840class ChromeOSArtifactFactory(BaseArtifactFactory):
841 """A factory class that generates ChromeOS build artifacts from names."""
842
843 def __init__(self, download_dir, artifacts, files, build):
844 """Pass the ChromeOS artifact map to the base class."""
845 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800846 chromeos_artifact_map, download_dir, artifacts, files, build,
847 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700848
849
850class AndroidArtifactFactory(BaseArtifactFactory):
851 """A factory class that generates Android build artifacts from names."""
852
853 def __init__(self, download_dir, artifacts, files, build):
854 """Pass the Android artifact map to the base class."""
855 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800856 android_artifact_map, download_dir, artifacts, files, build,
857 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700858
859
Chris Sosa968a1062013-08-02 17:42:50 -0700860# A simple main to verify correctness of the artifact map when making simple
861# name changes.
862if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700863 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
864 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
865 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
866 ('ANDROID', android_artifact_map)):
867 print('%s:' % label)
868 for key, value in sorted(mapping.items()):
869 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))