blob: 502b739a194fdcffcfa3fd1b104c4a3a948a1860 [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'
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -070050SIGNED_IMAGE_FILE = 'chromeos_*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])
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700658_AddCrOSArtifact(artifact_info.SIGNED_IMAGE, SignedArtifact, SIGNED_IMAGE_FILE)
Gabe Black3b567202015-09-23 14:07:59 -0700659_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
660 files_to_extract=[devserver_constants.IMAGE_FILE])
661_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
662 optional_name=TEST_IMAGE_FILE,
663 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
664_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
665 files_to_extract=None, exclude=['autotest/test_suites'])
666_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
667 CONTROL_FILES_FILE)
668_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
669 AUTOTEST_PACKAGES_FILE)
670_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
671_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
672_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
673 AUTOTEST_SERVER_PACKAGE_FILE)
674_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
675_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
676 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700677_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
678 DEBUG_SYMBOLS_ONLY_FILE,
679 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700680_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
681 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400682_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
683 FACTORY_SHIM_FILE,
684 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
David Riley0655bab2017-11-02 10:44:26 -0700685_AddCrOSArtifact(artifact_info.QUICK_PROVISION, MultiArtifact,
686 QUICK_PROVISION_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800687
Chris Sosa968a1062013-08-02 17:42:50 -0700688# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700689for c in devserver_constants.CHANNELS:
690 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
691 BundledArtifact,
692 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
693
Justin Giorgib7590522017-02-07 13:36:24 -0800694#### Libiota Artifacts ####
695_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
696 LIBIOTA_TEST_BINARIES_FILE)
697_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
698 LIBIOTA_BOARD_UTILS_FILE)
699
Gabe Black3b567202015-09-23 14:07:59 -0700700android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700701
Chris Sosa76e44b92013-01-31 12:11:38 -0800702
Gabe Black3b567202015-09-23 14:07:59 -0700703def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700704 """Add a data wrapper for android artifacts.
705
706 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700707 android_artifact_map.
708 """
709 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
710 android_artifact_map.setdefault(tag, []).append(artifact)
711
712
Dan Shiba4e00f2015-10-27 12:03:53 -0700713_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700714 ANDROID_IMAGE_ZIP, is_regex_name=True)
715_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
716 ANDROID_RADIO_IMAGE)
717_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
718 ANDROID_BOOTLOADER_IMAGE)
719_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
720_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
721 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800722_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
723 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800724_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
725 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
726_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
727 ANDROID_TEST_SUITES, is_regex_name=True)
728_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
729 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700730_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
731 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700732_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
733 BundledArtifact,
734 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
735 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700736_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
737 BundledArtifact,
738 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
739 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700740_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
741 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700742_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
743 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
744_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
745 ANDROID_DTB_ZIP, is_regex_name=True)
Satoshi Niwa5e3ed5e2018-06-25 16:03:33 +0900746_AddAndroidArtifact(artifact_info.ANDROID_PUSH_TO_DEVICE_ZIP,
747 Artifact, ANDROID_PUSH_TO_DEVICE_ZIP)
Qijiang Fan31beb7f2018-06-26 17:09:00 +0900748_AddAndroidArtifact(artifact_info.ANDROID_SEPOLICY_ZIP,
749 Artifact, ANDROID_SEPOLICY_ZIP)
Gabe Black3b567202015-09-23 14:07:59 -0700750
751class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800752 """A factory class that generates build artifacts from artifact names."""
753
Dan Shi6c2b2a22016-03-04 15:52:19 -0800754 def __init__(self, artifact_map, download_dir, artifacts, files, build,
755 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800756 """Initalizes the member variables for the factory.
757
758 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700759 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700760 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700761 artifacts: List of artifacts to stage. These artifacts must be
762 defined in artifact_info.py and have a mapping in the
763 ARTIFACT_IMPLEMENTATION_MAP.
764 files: List of files to stage. These files are just downloaded and staged
765 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800766 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800767 requested_to_optional_map: A map between an artifact X to a list of
768 artifacts Y. If X is requested, all items in Y should also get
769 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800770 """
Gabe Black3b567202015-09-23 14:07:59 -0700771 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700772 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700773 self.artifacts = artifacts
774 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800775 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800776 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800777
Chris Sosa6b0c6172013-08-05 17:01:33 -0700778 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700779 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700780
781 If is_artifact is true, then these names define artifacts that must exist in
782 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
783 basic BuildArtifacts.
784
Gilad Arnold950569b2013-08-27 14:38:01 -0700785 Args:
786 names: A sequence of artifact names.
787 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800788
Gilad Arnold950569b2013-08-27 14:38:01 -0700789 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700790 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800791
Gilad Arnold950569b2013-08-27 14:38:01 -0700792 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700793 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700794 """
Gabe Black3b567202015-09-23 14:07:59 -0700795 if is_artifact:
796 classes = itertools.chain(*(self.artifact_map[name] for name in names))
797 return list(cls(self.download_dir, self.build) for cls in classes)
798 else:
799 return list(Artifact(name, self.download_dir, self.build)
800 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800801
802 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700803 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700804
Gilad Arnold950569b2013-08-27 14:38:01 -0700805 Returns:
806 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800807
Gilad Arnold950569b2013-08-27 14:38:01 -0700808 Raises:
809 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700810 """
811 artifacts = []
812 if self.artifacts:
813 artifacts.extend(self._Artifacts(self.artifacts, True))
814 if self.files:
815 artifacts.extend(self._Artifacts(self.files, False))
816
817 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800818
819 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700820 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700821
Gilad Arnold950569b2013-08-27 14:38:01 -0700822 Returns:
823 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800824
Gilad Arnold950569b2013-08-27 14:38:01 -0700825 Raises:
826 KeyError: if an optional artifact doesn't exist in
827 ARTIFACT_IMPLEMENTATION_MAP yet defined in
828 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700829 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800830 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800831 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800832 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700833 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800834 optional_names = optional_names.union(optional_list)
835
Chris Sosa6b0c6172013-08-05 17:01:33 -0700836 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700837
838
Gabe Black3b567202015-09-23 14:07:59 -0700839class ChromeOSArtifactFactory(BaseArtifactFactory):
840 """A factory class that generates ChromeOS build artifacts from names."""
841
842 def __init__(self, download_dir, artifacts, files, build):
843 """Pass the ChromeOS artifact map to the base class."""
844 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800845 chromeos_artifact_map, download_dir, artifacts, files, build,
846 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700847
848
849class AndroidArtifactFactory(BaseArtifactFactory):
850 """A factory class that generates Android build artifacts from names."""
851
852 def __init__(self, download_dir, artifacts, files, build):
853 """Pass the Android artifact map to the base class."""
854 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800855 android_artifact_map, download_dir, artifacts, files, build,
856 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700857
858
Chris Sosa968a1062013-08-02 17:42:50 -0700859# A simple main to verify correctness of the artifact map when making simple
860# name changes.
861if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700862 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
863 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
864 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
865 ('ANDROID', android_artifact_map)):
866 print('%s:' % label)
867 for key, value in sorted(mapping.items()):
868 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))