blob: f5c0148ac098a3a9995538af5c39bcd961deac1e [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
Achuith Bhandarkar16ce7902019-09-26 11:17:01 +020019import six
20
Chris Sosa76e44b92013-01-31 12:11:38 -080021import artifact_info
22import common_util
joychen3cb228e2013-06-12 12:13:13 -070023import devserver_constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070024import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070025
Don Garrettfb15e322016-06-21 19:12:08 -070026# We do a number of things with args/kwargs arguments that confuse pylint.
27# pylint: disable=docstring-misnamed-args
Chris Sosa47a7d4e2012-03-28 11:26:55 -070028
Chris Sosa76e44b92013-01-31 12:11:38 -080029_AU_BASE = 'au'
30_NTON_DIR_SUFFIX = '_nton'
31_MTON_DIR_SUFFIX = '_mton'
32
33############ Actual filenames of artifacts in Google Storage ############
34
35AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070036PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080037AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070038CONTROL_FILES_FILE = 'control_files.tar'
39AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080040AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080041AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080042DEBUG_SYMBOLS_FILE = 'debug.tgz'
Dan Shi55d0f972016-10-04 11:45:00 -070043DEBUG_SYMBOLS_ONLY_FILE = 'debug_breakpad.tar.xz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080044FACTORY_FILE = 'ChromeOS-factory*.zip'
Mike Frysingera0e6a282016-09-01 17:29:08 -040045FACTORY_SHIM_FILE = 'factory_image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080046FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
47IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080048TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080049BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
50TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
51RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
Nick Sandersa995c9d2019-05-07 17:45:43 -070052SIGNED_RECOVERY_IMAGE_FILE = 'chromeos_*recovery*mp*.bin'
Justin Giorgib7590522017-02-07 13:36:24 -080053LIBIOTA_TEST_BINARIES_FILE = 'test_binaries.tar.gz'
54LIBIOTA_BOARD_UTILS_FILE = 'board_utils.tar.gz'
David Riley0655bab2017-11-02 10:44:26 -070055QUICK_PROVISION_FILE = 'full_dev_part_*.bin.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080056
Gabe Black3b567202015-09-23 14:07:59 -070057############ Actual filenames of Android build artifacts ############
58
Dan Shiba4e00f2015-10-27 12:03:53 -070059ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070060ANDROID_RADIO_IMAGE = 'radio.img'
61ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
62ANDROID_FASTBOOT = 'fastboot'
63ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080064ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080065ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
66ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
67ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070068ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070069ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070070ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
71 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070072ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070073ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
74ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Satoshi Niwa5e3ed5e2018-06-25 16:03:33 +090075ANDROID_PUSH_TO_DEVICE_ZIP = 'push_to_device.zip'
Qijiang Fan31beb7f2018-06-26 17:09:00 +090076ANDROID_SEPOLICY_ZIP = 'sepolicy.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080077
78_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070079
80
81class ArtifactDownloadError(Exception):
82 """Error used to signify an issue processing an artifact."""
83 pass
84
85
Gabe Black3b567202015-09-23 14:07:59 -070086class ArtifactMeta(type):
87 """metaclass for an artifact type.
88
89 This metaclass is for class Artifact and its subclasses to have a meaningful
90 string composed of class name and the corresponding artifact name, e.g.,
91 `Artifact_full_payload`. This helps to better logging, refer to logging in
92 method Downloader.Download.
93 """
94
95 ARTIFACT_NAME = None
96
97 def __str__(cls):
98 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
99
100 def __repr__(cls):
101 return str(cls)
102
103
Achuith Bhandarkar16ce7902019-09-26 11:17:01 +0200104@six.add_metaclass(ArtifactMeta)
Gabe Black3b567202015-09-23 14:07:59 -0700105class Artifact(log_util.Loggable):
106 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700107
108 The purpose of this class is to download objects from Google Storage
109 and install them to a local directory. There are two main functions, one to
110 download/prepare the artifacts in to a temporary staging area and the second
111 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800112
Gilad Arnold950569b2013-08-27 14:38:01 -0700113 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
114 attentive when adding new artifacts; (ii) name matching semantics differ
115 between a glob (full name string match) and a regex (partial match).
116
Chris Sosa76e44b92013-01-31 12:11:38 -0800117 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700118 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700119 name: Name given for artifact; in fact, it is a pattern that captures the
120 names of files contained in the artifact. This can either be an
121 ordinary shell-style glob (the default), or a regular expression (if
122 is_regex_name is True).
123 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800124 build: The version of the build i.e. R26-2342.0.0.
125 marker_name: Name used to define the lock marker for the artifacts to
126 prevent it from being re-downloaded. By default based on name
127 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700128 exception_file_path: Path to a file containing the serialized exception,
129 which was raised in Process method. The file is located
130 in the parent folder of install_dir, since the
131 install_dir will be deleted if the build does not
132 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700133 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700134 install_subdir: Directory within install_path where the artifact is actually
135 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800136 install_dir: The final location where the artifact should be staged to.
137 single_name: If True the name given should only match one item. Note, if not
138 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800139 installed_files: A list of files that were the final result of downloading
140 and setting up the artifact.
141 store_installed_files: Whether the list of installed files is stored in the
142 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700143 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700144
Gabe Black3b567202015-09-23 14:07:59 -0700145 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800146 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700147 """Constructor.
148
149 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800150 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800151 name: Identifying name to be used to find/store the artifact.
152 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700153 install_subdir: Directory within install_path where the artifact is
154 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700155 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800156 optional_name: An alternative name to find the artifact, which can lead
157 to faster download. Unlike |name|, there is no guarantee that an
158 artifact named |optional_name| is/will be on Google Storage. If it
159 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700160 """
Gabe Black3b567202015-09-23 14:07:59 -0700161 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162
Chris Sosa76e44b92013-01-31 12:11:38 -0800163 # In-memory lock to keep the devserver from colliding with itself while
164 # attempting to stage the same artifact.
165 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700166
Chris Sosa76e44b92013-01-31 12:11:38 -0800167 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800168 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700169 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800170 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700171
Chris Sosa76e44b92013-01-31 12:11:38 -0800172 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700173
Dan Shi6e50c722013-08-19 15:05:06 -0700174 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
175 '.exception')
176 # The exception file needs to be located in parent folder, since the
177 # install_dir will be deleted is the build does not exist.
178 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700179 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700180
joychen0a8e34e2013-06-24 17:58:36 -0700181 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700182
Chris Sosa76e44b92013-01-31 12:11:38 -0800183 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700184 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800185
186 self.single_name = True
187
Gilad Arnold1638d822013-11-07 23:38:16 -0800188 self.installed_files = []
189 self.store_installed_files = True
190
Chris Sosa76e44b92013-01-31 12:11:38 -0800191 @staticmethod
192 def _SanitizeName(name):
193 """Sanitizes name to be used for creating a file on the filesystem.
194
195 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700196
197 Args:
198 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800199
Gilad Arnold950569b2013-08-27 14:38:01 -0700200 Returns:
201 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800202 """
203 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
204
Dan Shif8eb0d12013-08-01 17:52:06 -0700205 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800206 """Returns True if artifact is already staged.
207
208 This checks for (1) presence of the artifact marker file, and (2) the
209 presence of each installed file listed in this marker. Both must hold for
210 the artifact to be considered staged. Note that this method is safe for use
211 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700212 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800213 files actually installed into the marker.
214 """
215 marker_file = os.path.join(self.install_dir, self.marker_name)
216
217 # If the marker is missing, it's definitely not staged.
218 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700219 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800220 return False
221
222 # We want to ensure that every file listed in the marker is actually there.
223 if self.store_installed_files:
224 with open(marker_file) as f:
225 files = [line.strip() for line in f]
226
227 # Check to see if any of the purportedly installed files are missing, in
228 # which case the marker is outdated and should be removed.
229 missing_files = [fname for fname in files if not os.path.exists(fname)]
230 if missing_files:
231 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
232 'All' if len(files) == len(missing_files) else 'Some',
233 marker_file, '\n'.join(missing_files))
234 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700235 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800236 return False
237
Aviv Keshet57d18172016-06-18 20:39:09 -0700238 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800239 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800240
241 def _MarkArtifactStaged(self):
242 """Marks the artifact as staged."""
243 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800244 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800245
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800246 def _UpdateName(self, names):
247 if self.single_name and len(names) > 1:
248 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800249
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800250 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800251
joychen0a8e34e2013-06-24 17:58:36 -0700252 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800253 """Process the downloaded content, update the list of installed files."""
254 # In this primitive case, what was downloaded (has to be a single file) is
255 # what's installed.
256 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700257
Dan Shi6e50c722013-08-19 15:05:06 -0700258 def _ClearException(self):
259 """Delete any existing exception saved for this artifact."""
260 if os.path.exists(self.exception_file_path):
261 os.remove(self.exception_file_path)
262
263 def _SaveException(self, e):
xixuan56252ff2017-03-09 15:40:31 -0800264 """Save the exception and traceback to a file for downloader.IsStaged.
Dan Shi6e50c722013-08-19 15:05:06 -0700265
Gilad Arnold950569b2013-08-27 14:38:01 -0700266 Args:
267 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700268 """
269 with open(self.exception_file_path, 'w') as f:
xixuan56252ff2017-03-09 15:40:31 -0800270 pickle.dump('%s\n%s' % (e, str(traceback.format_exc())), f)
Dan Shi6e50c722013-08-19 15:05:06 -0700271
272 def GetException(self):
273 """Retrieve any exception that was raised in Process method.
274
Gilad Arnold950569b2013-08-27 14:38:01 -0700275 Returns:
276 An Exception object that was raised when trying to process the artifact.
277 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700278 """
279 if not os.path.exists(self.exception_file_path):
280 return None
281 with open(self.exception_file_path, 'r') as f:
282 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800283
Gabe Black3b567202015-09-23 14:07:59 -0700284 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800285 """Main call point to all artifacts. Downloads and Stages artifact.
286
287 Downloads and Stages artifact from Google Storage to the install directory
288 specified in the constructor. It multi-thread safe and does not overwrite
289 the artifact if it's already been downloaded or being downloaded. After
290 processing, leaves behind a marker to indicate to future invocations that
291 the artifact has already been staged based on the name of the artifact.
292
293 Do not override as it modifies important private variables, ensures thread
294 safety, and maintains cache semantics.
295
296 Note: this may be a blocking call when the artifact is already in the
297 process of being staged.
298
299 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700300 downloader: A downloader instance containing the logic to download
301 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800302 no_wait: If True, don't block waiting for artifact to exist if we fail to
303 immediately find it.
304
305 Raises:
306 ArtifactDownloadError: If the artifact fails to download from Google
307 Storage for any reason or that the regexp
308 defined by name is not specific enough.
309 """
310 if not self._process_lock:
311 self._process_lock = _build_artifact_locks.lock(
312 os.path.join(self.install_dir, self.name))
313
Gabe Black3b567202015-09-23 14:07:59 -0700314 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800315 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700316 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700317 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800318 # Delete any existing exception saved for this artifact.
319 self._ClearException()
320 found_artifact = False
321 if self.optional_name:
322 try:
Gabe Black3b567202015-09-23 14:07:59 -0700323 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800324 # Because this artifact may not always exist, don't bother
325 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700326 new_names = downloader.Wait(
327 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800328 self._UpdateName(new_names)
329
330 except ArtifactDownloadError:
331 self._Log('Unable to download %s; fall back to download %s',
332 self.optional_name, self.name)
333 else:
334 found_artifact = True
335
Dan Shi6e50c722013-08-19 15:05:06 -0700336 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700337 # If the artifact should already have been uploaded, don't waste
338 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800339 if not found_artifact:
340 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700341 new_names = downloader.Wait(
342 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800343 self._UpdateName(new_names)
344
David Rileye131a0f2017-11-02 10:42:34 -0700345
346 files = self.name if isinstance(self.name, list) else [self.name]
347 for filename in files:
348 self._Log('Downloading file %s', filename)
349 self.install_path = downloader.Fetch(filename, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700350 self._Setup()
351 self._MarkArtifactStaged()
352 except Exception as e:
353 # Save the exception to a file for downloader.IsStaged to retrieve.
354 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800355
356 # Convert an unknown exception into an ArtifactDownloadError.
Achuith Bhandarkar16ce7902019-09-26 11:17:01 +0200357 if isinstance(e, ArtifactDownloadError):
Gilad Arnold02dc6552013-11-14 11:27:54 -0800358 raise
359 else:
360 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800361 else:
362 self._Log('%s is already staged.', self)
363
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700364 def __str__(self):
365 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700366 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700367
Chris Sosab26b1202013-08-16 16:40:55 -0700368 def __repr__(self):
369 return str(self)
370
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700371
Gabe Black3b567202015-09-23 14:07:59 -0700372class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700373 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700374
joychen0a8e34e2013-06-24 17:58:36 -0700375 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700376 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700377
Chris Sosa76e44b92013-01-31 12:11:38 -0800378 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700379 install_path = os.path.join(self.install_dir, self.install_subdir,
380 self.name)
381 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700382 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800383 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700384
Gilad Arnold1638d822013-11-07 23:38:16 -0800385 # Reflect the rename in the list of installed files.
386 self.installed_files.remove(install_path)
387 self.installed_files = [new_install_path]
388
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700389
David Rileye131a0f2017-11-02 10:42:34 -0700390class MultiArtifact(Artifact):
391 """Wrapper for artifacts where name matches multiple items.."""
392
393 def __init__(self, *args, **kwargs):
394 """Takes Artifact args.
395
396 Args:
397 *args: See Artifact documentation.
398 **kwargs: See Artifact documentation.
399 """
400 super(MultiArtifact, self).__init__(*args, **kwargs)
401 self.single_name = False
402
403 def _UpdateName(self, names):
404 self.name = names if isinstance(names, list) else [names]
405
406 def _Setup(self):
407 super(MultiArtifact, self)._Setup()
408
409 self.installed_files = [os.path.join(self.install_dir, self.install_subdir,
410 name) for name in self.name]
411
412
Gabe Black3b567202015-09-23 14:07:59 -0700413class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800414 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700415
Gabe Black3b567202015-09-23 14:07:59 -0700416 These artifacts are super strange. They custom handle directories and
417 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800418 because unlike other artifacts, this one does not conform to something a
419 client might know. The client doesn't know the version of n-1 or whether it
420 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700421
422 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700423 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800424 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700425
joychen0a8e34e2013-06-24 17:58:36 -0700426 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700427 super(DeltaPayloadBase, self)._Setup()
428 # Setup symlink so that AU will work for this payload.
429 stateful_update_symlink = os.path.join(
430 self.install_dir, self.install_subdir,
431 devserver_constants.STATEFUL_FILE)
432 os.symlink(os.path.join(os.pardir, os.pardir,
433 devserver_constants.STATEFUL_FILE),
434 stateful_update_symlink)
435 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700436
Chris Sosa76e44b92013-01-31 12:11:38 -0800437
Gabe Black3b567202015-09-23 14:07:59 -0700438class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800439 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800440
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800441 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700442 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700443
444 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700445 *args: See Artifact documentation.
446 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700447 files_to_extract: A list of files to extract. If set to None, extract
448 all files.
449 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800450 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800451 self._files_to_extract = kwargs.pop('files_to_extract', None)
452 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700453 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800454
455 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800456 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800457 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800458 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800459
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800460 def _RunUnzip(self, list_only):
461 # Unzip is weird. It expects its args before any excludes and expects its
462 # excludes in a list following the -x.
463 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
464 if not list_only:
465 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800466
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800467 if self._files_to_extract:
468 cmd.extend(self._files_to_extract)
469
470 if self._exclude:
471 cmd.append('-x')
472 cmd.extend(self._exclude)
473
474 try:
475 return subprocess.check_output(cmd).strip('\n').splitlines()
476 except subprocess.CalledProcessError, e:
477 raise ArtifactDownloadError(
478 'An error occurred when attempting to unzip %s:\n%s' %
479 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800480
joychen0a8e34e2013-06-24 17:58:36 -0700481 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800482 extract_result = self._Extract()
483 if self.store_installed_files:
484 # List both the archive and the extracted files.
485 self.installed_files.append(self.install_path)
486 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800487
Chris Sosa76e44b92013-01-31 12:11:38 -0800488 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800489 """Extracts files into the install path."""
490 if self.name.endswith('.zip'):
491 return self._ExtractZipfile()
492 else:
493 return self._ExtractTarball()
494
495 def _ExtractZipfile(self):
496 """Extracts a zip file using unzip."""
497 file_list = [os.path.join(self.install_dir, line[30:].strip())
498 for line in self._RunUnzip(True)
499 if not line.endswith('/')]
500 if file_list:
501 self._RunUnzip(False)
502
503 return file_list
504
505 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800506 """Extracts a tarball using tar.
507
508 Detects whether the tarball is compressed or not based on the file
509 extension and extracts the tarball into the install_path.
510 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700511 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800512 return common_util.ExtractTarball(self.install_path, self.install_dir,
513 files_to_extract=self._files_to_extract,
514 excluded_files=self._exclude,
515 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800516 except common_util.CommonUtilError as e:
517 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700518
519
Gabe Black3b567202015-09-23 14:07:59 -0700520class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700521 """Wrapper around the autotest tarball to download from gsutil."""
522
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800523 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700524 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800525 # We don't store/check explicit file lists in Autotest tarball markers;
526 # this can get huge and unwieldy, and generally make little sense.
527 self.store_installed_files = False
528
joychen0a8e34e2013-06-24 17:58:36 -0700529 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800530 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700531 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700532
Chris Sosa76e44b92013-01-31 12:11:38 -0800533 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700534 autotest_dir = os.path.join(self.install_dir,
535 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700536 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
537 if not os.path.exists(autotest_pkgs_dir):
538 os.makedirs(autotest_pkgs_dir)
539
540 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Prashant Malani20e83712017-02-28 01:30:41 -0800541 cmd = ['autotest/utils/packager.py', '--action=upload', '--repository',
Chris Sosa76e44b92013-01-31 12:11:38 -0800542 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700543 try:
joychen0a8e34e2013-06-24 17:58:36 -0700544 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700545 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800546 raise ArtifactDownloadError(
547 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700548 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700549 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700550
Chris Masone816e38c2012-05-02 12:22:36 -0700551
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700552class SignedArtifact(Artifact):
553 """Wrapper for signed artifacts which need a path translation."""
554
555 def _Setup(self):
556 super(SignedArtifact, self)._Setup()
557
558 # Rename to signed_image.bin.
559 install_path = os.path.join(self.install_dir, self.install_subdir,
560 self.name)
561 new_install_path = os.path.join(self.install_dir, self.install_subdir,
562 devserver_constants.SIGNED_IMAGE_FILE)
563 shutil.move(install_path, new_install_path)
564
565 # Reflect the rename in the list of installed files.
566 self.installed_files.remove(install_path)
567 self.installed_files = [new_install_path]
568
569
Gabe Black3b567202015-09-23 14:07:59 -0700570def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
571 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700572
Gabe Black3b567202015-09-23 14:07:59 -0700573 Args:
574 tag: Tag of the artifact, defined in artifact_info.
575 base: Class of the artifact, e.g., BundledArtifact.
576 name: Name of the artifact, e.g., image.zip.
577 *fixed_args: Fixed arguments that are additional to the one used in base
578 class.
579 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
580 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800581
Gabe Black3b567202015-09-23 14:07:59 -0700582 Returns:
583 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700584 """
Don Garrettfb15e322016-06-21 19:12:08 -0700585 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700586 class NewArtifact(base):
587 """A data wrapper that describes an artifact's implementation."""
588 ARTIFACT_TAG = tag
589 ARTIFACT_NAME = name
590
591 def __init__(self, *args, **kwargs):
592 all_args = fixed_args + args
593 all_kwargs = {}
594 all_kwargs.update(fixed_kwargs)
595 all_kwargs.update(kwargs)
596 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
597 *all_args, **all_kwargs)
598
599 NewArtifact.__name__ = base.__name__
600 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700601
Chris Sosa76e44b92013-01-31 12:11:38 -0800602
Gabe Black3b567202015-09-23 14:07:59 -0700603# TODO(dshi): Refactor the code here to split out the logic of creating the
604# artifacts mapping to a different module.
605chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800606
Chris Sosa76e44b92013-01-31 12:11:38 -0800607
Gabe Black3b567202015-09-23 14:07:59 -0700608def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700609 """Add a data wrapper for ChromeOS artifacts.
610
611 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700612 chromeos_artifact_map.
613 """
614 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
615 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800616
beepsc3d0f872013-07-31 21:50:40 -0700617
Amin Hassanie9596af2019-02-13 13:48:30 -0800618_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*bin')
Gabe Black3b567202015-09-23 14:07:59 -0700619
620
621class DeltaPayloadNtoN(DeltaPayloadBase):
622 """ChromeOS Delta payload artifact for updating from version N to N."""
623 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
624 ARTIFACT_NAME = 'NOT_APPLICABLE'
625
626 def __init__(self, install_dir, build, *args, **kwargs):
627 name = 'chromeos_%s*_delta_*' % build
628 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
629 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
630 install_subdir=install_subdir,
631 **kwargs)
632
633
634class DeltaPayloadMtoN(DeltaPayloadBase):
635 """ChromeOS Delta payload artifact for updating from version M to N."""
636 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
637 ARTIFACT_NAME = 'NOT_APPLICABLE'
638
639 def __init__(self, install_dir, build, *args, **kwargs):
640 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
641 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
642 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
643 install_subdir=install_subdir,
644 is_regex_name=True, **kwargs)
645
646
647chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
648 DeltaPayloadMtoN]
649
650
651_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
652 devserver_constants.STATEFUL_FILE)
653_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
654 optional_name=BASE_IMAGE_FILE,
655 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
656_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
657 optional_name=RECOVERY_IMAGE_FILE,
658 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
Nick Sandersa995c9d2019-05-07 17:45:43 -0700659_AddCrOSArtifact(artifact_info.SIGNED_IMAGE, SignedArtifact,
660 SIGNED_RECOVERY_IMAGE_FILE)
Gabe Black3b567202015-09-23 14:07:59 -0700661_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
662 files_to_extract=[devserver_constants.IMAGE_FILE])
663_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
664 optional_name=TEST_IMAGE_FILE,
665 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
666_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
667 files_to_extract=None, exclude=['autotest/test_suites'])
668_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
669 CONTROL_FILES_FILE)
670_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
671 AUTOTEST_PACKAGES_FILE)
672_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
673_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
674_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
675 AUTOTEST_SERVER_PACKAGE_FILE)
676_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
677_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
678 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700679_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
680 DEBUG_SYMBOLS_ONLY_FILE,
681 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700682_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
683 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400684_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
685 FACTORY_SHIM_FILE,
686 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
David Riley0655bab2017-11-02 10:44:26 -0700687_AddCrOSArtifact(artifact_info.QUICK_PROVISION, MultiArtifact,
688 QUICK_PROVISION_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800689
Chris Sosa968a1062013-08-02 17:42:50 -0700690# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700691for c in devserver_constants.CHANNELS:
692 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
693 BundledArtifact,
694 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
695
Justin Giorgib7590522017-02-07 13:36:24 -0800696#### Libiota Artifacts ####
697_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
698 LIBIOTA_TEST_BINARIES_FILE)
699_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
700 LIBIOTA_BOARD_UTILS_FILE)
701
Gabe Black3b567202015-09-23 14:07:59 -0700702android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700703
Chris Sosa76e44b92013-01-31 12:11:38 -0800704
Gabe Black3b567202015-09-23 14:07:59 -0700705def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700706 """Add a data wrapper for android artifacts.
707
708 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700709 android_artifact_map.
710 """
711 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
712 android_artifact_map.setdefault(tag, []).append(artifact)
713
714
Dan Shiba4e00f2015-10-27 12:03:53 -0700715_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700716 ANDROID_IMAGE_ZIP, is_regex_name=True)
717_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
718 ANDROID_RADIO_IMAGE)
719_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
720 ANDROID_BOOTLOADER_IMAGE)
721_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
722_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
723 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800724_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
725 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800726_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
727 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
728_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
729 ANDROID_TEST_SUITES, is_regex_name=True)
730_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
731 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700732_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
733 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700734_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
735 BundledArtifact,
736 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
737 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700738_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
739 BundledArtifact,
740 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
741 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700742_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
743 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700744_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
745 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
746_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
747 ANDROID_DTB_ZIP, is_regex_name=True)
Satoshi Niwa5e3ed5e2018-06-25 16:03:33 +0900748_AddAndroidArtifact(artifact_info.ANDROID_PUSH_TO_DEVICE_ZIP,
749 Artifact, ANDROID_PUSH_TO_DEVICE_ZIP)
Qijiang Fan31beb7f2018-06-26 17:09:00 +0900750_AddAndroidArtifact(artifact_info.ANDROID_SEPOLICY_ZIP,
751 Artifact, ANDROID_SEPOLICY_ZIP)
Gabe Black3b567202015-09-23 14:07:59 -0700752
753class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800754 """A factory class that generates build artifacts from artifact names."""
755
Dan Shi6c2b2a22016-03-04 15:52:19 -0800756 def __init__(self, artifact_map, download_dir, artifacts, files, build,
757 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800758 """Initalizes the member variables for the factory.
759
760 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700761 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700762 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700763 artifacts: List of artifacts to stage. These artifacts must be
764 defined in artifact_info.py and have a mapping in the
765 ARTIFACT_IMPLEMENTATION_MAP.
766 files: List of files to stage. These files are just downloaded and staged
767 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800768 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800769 requested_to_optional_map: A map between an artifact X to a list of
770 artifacts Y. If X is requested, all items in Y should also get
771 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800772 """
Gabe Black3b567202015-09-23 14:07:59 -0700773 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700774 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700775 self.artifacts = artifacts
776 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800777 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800778 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800779
Chris Sosa6b0c6172013-08-05 17:01:33 -0700780 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700781 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700782
783 If is_artifact is true, then these names define artifacts that must exist in
784 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
785 basic BuildArtifacts.
786
Gilad Arnold950569b2013-08-27 14:38:01 -0700787 Args:
788 names: A sequence of artifact names.
789 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800790
Gilad Arnold950569b2013-08-27 14:38:01 -0700791 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700792 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800793
Gilad Arnold950569b2013-08-27 14:38:01 -0700794 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700795 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700796 """
Gabe Black3b567202015-09-23 14:07:59 -0700797 if is_artifact:
798 classes = itertools.chain(*(self.artifact_map[name] for name in names))
799 return list(cls(self.download_dir, self.build) for cls in classes)
800 else:
801 return list(Artifact(name, self.download_dir, self.build)
802 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800803
804 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700805 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700806
Gilad Arnold950569b2013-08-27 14:38:01 -0700807 Returns:
808 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800809
Gilad Arnold950569b2013-08-27 14:38:01 -0700810 Raises:
811 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700812 """
813 artifacts = []
814 if self.artifacts:
815 artifacts.extend(self._Artifacts(self.artifacts, True))
816 if self.files:
817 artifacts.extend(self._Artifacts(self.files, False))
818
819 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800820
821 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700822 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700823
Gilad Arnold950569b2013-08-27 14:38:01 -0700824 Returns:
825 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800826
Gilad Arnold950569b2013-08-27 14:38:01 -0700827 Raises:
828 KeyError: if an optional artifact doesn't exist in
829 ARTIFACT_IMPLEMENTATION_MAP yet defined in
830 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700831 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800832 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800833 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800834 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700835 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800836 optional_names = optional_names.union(optional_list)
837
Chris Sosa6b0c6172013-08-05 17:01:33 -0700838 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700839
840
Gabe Black3b567202015-09-23 14:07:59 -0700841class ChromeOSArtifactFactory(BaseArtifactFactory):
842 """A factory class that generates ChromeOS build artifacts from names."""
843
844 def __init__(self, download_dir, artifacts, files, build):
845 """Pass the ChromeOS artifact map to the base class."""
846 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800847 chromeos_artifact_map, download_dir, artifacts, files, build,
848 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700849
850
851class AndroidArtifactFactory(BaseArtifactFactory):
852 """A factory class that generates Android build artifacts from names."""
853
854 def __init__(self, download_dir, artifacts, files, build):
855 """Pass the Android artifact map to the base class."""
856 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800857 android_artifact_map, download_dir, artifacts, files, build,
858 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700859
860
Chris Sosa968a1062013-08-02 17:42:50 -0700861# A simple main to verify correctness of the artifact map when making simple
862# name changes.
863if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700864 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
865 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
866 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
867 ('ANDROID', android_artifact_map)):
868 print('%s:' % label)
869 for key, value in sorted(mapping.items()):
870 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))