blob: 8515f39b0b51edc2b16a52816b52641a81bd20ec [file] [log] [blame]
Gabe Black3b567202015-09-23 14:07:59 -07001#!/usr/bin/python2
Chris Sosa968a1062013-08-02 17:42:50 -07002
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
17
Chris Sosa76e44b92013-01-31 12:11:38 -080018import artifact_info
19import common_util
joychen3cb228e2013-06-12 12:13:13 -070020import devserver_constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070021import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070022
Don Garrettfb15e322016-06-21 19:12:08 -070023# We do a number of things with args/kwargs arguments that confuse pylint.
24# pylint: disable=docstring-misnamed-args
Chris Sosa47a7d4e2012-03-28 11:26:55 -070025
Chris Sosa76e44b92013-01-31 12:11:38 -080026_AU_BASE = 'au'
27_NTON_DIR_SUFFIX = '_nton'
28_MTON_DIR_SUFFIX = '_mton'
29
30############ Actual filenames of artifacts in Google Storage ############
31
32AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070033PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080034AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070035CONTROL_FILES_FILE = 'control_files.tar'
36AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080037AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080038AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080039DEBUG_SYMBOLS_FILE = 'debug.tgz'
Dan Shi55d0f972016-10-04 11:45:00 -070040DEBUG_SYMBOLS_ONLY_FILE = 'debug_breakpad.tar.xz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080041FACTORY_FILE = 'ChromeOS-factory*.zip'
Mike Frysingera0e6a282016-09-01 17:29:08 -040042FACTORY_SHIM_FILE = 'factory_image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080043FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
44IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080045TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080046BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
47TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
48RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
Justin Giorgib7590522017-02-07 13:36:24 -080049LIBIOTA_TEST_BINARIES_FILE = 'test_binaries.tar.gz'
50LIBIOTA_BOARD_UTILS_FILE = 'board_utils.tar.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080051
Gabe Black3b567202015-09-23 14:07:59 -070052############ Actual filenames of Android build artifacts ############
53
Dan Shiba4e00f2015-10-27 12:03:53 -070054ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070055ANDROID_RADIO_IMAGE = 'radio.img'
56ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
57ANDROID_FASTBOOT = 'fastboot'
58ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080059ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080060ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
61ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
62ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070063ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070064ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070065ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
66 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070067ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070068ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
69ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080070
71_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070072
73
74class ArtifactDownloadError(Exception):
75 """Error used to signify an issue processing an artifact."""
76 pass
77
78
Gabe Black3b567202015-09-23 14:07:59 -070079class ArtifactMeta(type):
80 """metaclass for an artifact type.
81
82 This metaclass is for class Artifact and its subclasses to have a meaningful
83 string composed of class name and the corresponding artifact name, e.g.,
84 `Artifact_full_payload`. This helps to better logging, refer to logging in
85 method Downloader.Download.
86 """
87
88 ARTIFACT_NAME = None
89
90 def __str__(cls):
91 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
92
93 def __repr__(cls):
94 return str(cls)
95
96
97class Artifact(log_util.Loggable):
98 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070099
100 The purpose of this class is to download objects from Google Storage
101 and install them to a local directory. There are two main functions, one to
102 download/prepare the artifacts in to a temporary staging area and the second
103 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800104
Gilad Arnold950569b2013-08-27 14:38:01 -0700105 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
106 attentive when adding new artifacts; (ii) name matching semantics differ
107 between a glob (full name string match) and a regex (partial match).
108
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700110 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700111 name: Name given for artifact; in fact, it is a pattern that captures the
112 names of files contained in the artifact. This can either be an
113 ordinary shell-style glob (the default), or a regular expression (if
114 is_regex_name is True).
115 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800116 build: The version of the build i.e. R26-2342.0.0.
117 marker_name: Name used to define the lock marker for the artifacts to
118 prevent it from being re-downloaded. By default based on name
119 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700120 exception_file_path: Path to a file containing the serialized exception,
121 which was raised in Process method. The file is located
122 in the parent folder of install_dir, since the
123 install_dir will be deleted if the build does not
124 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700125 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700126 install_subdir: Directory within install_path where the artifact is actually
127 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800128 install_dir: The final location where the artifact should be staged to.
129 single_name: If True the name given should only match one item. Note, if not
130 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800131 installed_files: A list of files that were the final result of downloading
132 and setting up the artifact.
133 store_installed_files: Whether the list of installed files is stored in the
134 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700135 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700136
Gabe Black3b567202015-09-23 14:07:59 -0700137 __metaclass__ = ArtifactMeta
138
139 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800140 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700141 """Constructor.
142
143 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800144 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800145 name: Identifying name to be used to find/store the artifact.
146 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700147 install_subdir: Directory within install_path where the artifact is
148 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700149 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800150 optional_name: An alternative name to find the artifact, which can lead
151 to faster download. Unlike |name|, there is no guarantee that an
152 artifact named |optional_name| is/will be on Google Storage. If it
153 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700154 """
Gabe Black3b567202015-09-23 14:07:59 -0700155 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700156
Chris Sosa76e44b92013-01-31 12:11:38 -0800157 # In-memory lock to keep the devserver from colliding with itself while
158 # attempting to stage the same artifact.
159 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700160
Chris Sosa76e44b92013-01-31 12:11:38 -0800161 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800162 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700163 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800164 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700165
Chris Sosa76e44b92013-01-31 12:11:38 -0800166 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700167
Dan Shi6e50c722013-08-19 15:05:06 -0700168 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
169 '.exception')
170 # The exception file needs to be located in parent folder, since the
171 # install_dir will be deleted is the build does not exist.
172 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700173 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700174
joychen0a8e34e2013-06-24 17:58:36 -0700175 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700176
Chris Sosa76e44b92013-01-31 12:11:38 -0800177 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700178 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800179
180 self.single_name = True
181
Gilad Arnold1638d822013-11-07 23:38:16 -0800182 self.installed_files = []
183 self.store_installed_files = True
184
Chris Sosa76e44b92013-01-31 12:11:38 -0800185 @staticmethod
186 def _SanitizeName(name):
187 """Sanitizes name to be used for creating a file on the filesystem.
188
189 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700190
191 Args:
192 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800193
Gilad Arnold950569b2013-08-27 14:38:01 -0700194 Returns:
195 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800196 """
197 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
198
Dan Shif8eb0d12013-08-01 17:52:06 -0700199 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800200 """Returns True if artifact is already staged.
201
202 This checks for (1) presence of the artifact marker file, and (2) the
203 presence of each installed file listed in this marker. Both must hold for
204 the artifact to be considered staged. Note that this method is safe for use
205 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700206 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800207 files actually installed into the marker.
208 """
209 marker_file = os.path.join(self.install_dir, self.marker_name)
210
211 # If the marker is missing, it's definitely not staged.
212 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700213 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800214 return False
215
216 # We want to ensure that every file listed in the marker is actually there.
217 if self.store_installed_files:
218 with open(marker_file) as f:
219 files = [line.strip() for line in f]
220
221 # Check to see if any of the purportedly installed files are missing, in
222 # which case the marker is outdated and should be removed.
223 missing_files = [fname for fname in files if not os.path.exists(fname)]
224 if missing_files:
225 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
226 'All' if len(files) == len(missing_files) else 'Some',
227 marker_file, '\n'.join(missing_files))
228 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700229 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800230 return False
231
Aviv Keshet57d18172016-06-18 20:39:09 -0700232 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800233 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800234
235 def _MarkArtifactStaged(self):
236 """Marks the artifact as staged."""
237 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800238 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800239
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800240 def _UpdateName(self, names):
241 if self.single_name and len(names) > 1:
242 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800243
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800244 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800245
joychen0a8e34e2013-06-24 17:58:36 -0700246 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800247 """Process the downloaded content, update the list of installed files."""
248 # In this primitive case, what was downloaded (has to be a single file) is
249 # what's installed.
250 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700251
Dan Shi6e50c722013-08-19 15:05:06 -0700252 def _ClearException(self):
253 """Delete any existing exception saved for this artifact."""
254 if os.path.exists(self.exception_file_path):
255 os.remove(self.exception_file_path)
256
257 def _SaveException(self, e):
258 """Save the exception to a file for downloader.IsStaged to retrieve.
259
Gilad Arnold950569b2013-08-27 14:38:01 -0700260 Args:
261 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700262 """
263 with open(self.exception_file_path, 'w') as f:
264 pickle.dump(e, f)
265
266 def GetException(self):
267 """Retrieve any exception that was raised in Process method.
268
Gilad Arnold950569b2013-08-27 14:38:01 -0700269 Returns:
270 An Exception object that was raised when trying to process the artifact.
271 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700272 """
273 if not os.path.exists(self.exception_file_path):
274 return None
275 with open(self.exception_file_path, 'r') as f:
276 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800277
Gabe Black3b567202015-09-23 14:07:59 -0700278 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800279 """Main call point to all artifacts. Downloads and Stages artifact.
280
281 Downloads and Stages artifact from Google Storage to the install directory
282 specified in the constructor. It multi-thread safe and does not overwrite
283 the artifact if it's already been downloaded or being downloaded. After
284 processing, leaves behind a marker to indicate to future invocations that
285 the artifact has already been staged based on the name of the artifact.
286
287 Do not override as it modifies important private variables, ensures thread
288 safety, and maintains cache semantics.
289
290 Note: this may be a blocking call when the artifact is already in the
291 process of being staged.
292
293 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700294 downloader: A downloader instance containing the logic to download
295 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800296 no_wait: If True, don't block waiting for artifact to exist if we fail to
297 immediately find it.
298
299 Raises:
300 ArtifactDownloadError: If the artifact fails to download from Google
301 Storage for any reason or that the regexp
302 defined by name is not specific enough.
303 """
304 if not self._process_lock:
305 self._process_lock = _build_artifact_locks.lock(
306 os.path.join(self.install_dir, self.name))
307
Gabe Black3b567202015-09-23 14:07:59 -0700308 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800309 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700310 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700311 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800312 # Delete any existing exception saved for this artifact.
313 self._ClearException()
314 found_artifact = False
315 if self.optional_name:
316 try:
Gabe Black3b567202015-09-23 14:07:59 -0700317 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800318 # Because this artifact may not always exist, don't bother
319 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700320 new_names = downloader.Wait(
321 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800322 self._UpdateName(new_names)
323
324 except ArtifactDownloadError:
325 self._Log('Unable to download %s; fall back to download %s',
326 self.optional_name, self.name)
327 else:
328 found_artifact = True
329
Dan Shi6e50c722013-08-19 15:05:06 -0700330 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700331 # If the artifact should already have been uploaded, don't waste
332 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800333 if not found_artifact:
334 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700335 new_names = downloader.Wait(
336 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800337 self._UpdateName(new_names)
338
339 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700340 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700341 self._Setup()
342 self._MarkArtifactStaged()
343 except Exception as e:
344 # Save the exception to a file for downloader.IsStaged to retrieve.
345 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800346
347 # Convert an unknown exception into an ArtifactDownloadError.
348 if type(e) is ArtifactDownloadError:
349 raise
350 else:
351 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800352 else:
353 self._Log('%s is already staged.', self)
354
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700355 def __str__(self):
356 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700357 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700358
Chris Sosab26b1202013-08-16 16:40:55 -0700359 def __repr__(self):
360 return str(self)
361
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700362
Gabe Black3b567202015-09-23 14:07:59 -0700363class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700364 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700365
joychen0a8e34e2013-06-24 17:58:36 -0700366 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700367 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700368
Chris Sosa76e44b92013-01-31 12:11:38 -0800369 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700370 install_path = os.path.join(self.install_dir, self.install_subdir,
371 self.name)
372 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700373 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800374 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700375
Gilad Arnold1638d822013-11-07 23:38:16 -0800376 # Reflect the rename in the list of installed files.
377 self.installed_files.remove(install_path)
378 self.installed_files = [new_install_path]
379
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700380
Gabe Black3b567202015-09-23 14:07:59 -0700381class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800382 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700383
Gabe Black3b567202015-09-23 14:07:59 -0700384 These artifacts are super strange. They custom handle directories and
385 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800386 because unlike other artifacts, this one does not conform to something a
387 client might know. The client doesn't know the version of n-1 or whether it
388 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700389
390 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700391 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800392 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700393
joychen0a8e34e2013-06-24 17:58:36 -0700394 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700395 super(DeltaPayloadBase, self)._Setup()
396 # Setup symlink so that AU will work for this payload.
397 stateful_update_symlink = os.path.join(
398 self.install_dir, self.install_subdir,
399 devserver_constants.STATEFUL_FILE)
400 os.symlink(os.path.join(os.pardir, os.pardir,
401 devserver_constants.STATEFUL_FILE),
402 stateful_update_symlink)
403 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700404
Chris Sosa76e44b92013-01-31 12:11:38 -0800405
Gabe Black3b567202015-09-23 14:07:59 -0700406class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800407 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800408
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800409 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700410 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700411
412 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700413 *args: See Artifact documentation.
414 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700415 files_to_extract: A list of files to extract. If set to None, extract
416 all files.
417 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800418 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800419 self._files_to_extract = kwargs.pop('files_to_extract', None)
420 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700421 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800422
423 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800424 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800425 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800426 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800427
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800428 def _RunUnzip(self, list_only):
429 # Unzip is weird. It expects its args before any excludes and expects its
430 # excludes in a list following the -x.
431 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
432 if not list_only:
433 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800434
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800435 if self._files_to_extract:
436 cmd.extend(self._files_to_extract)
437
438 if self._exclude:
439 cmd.append('-x')
440 cmd.extend(self._exclude)
441
442 try:
443 return subprocess.check_output(cmd).strip('\n').splitlines()
444 except subprocess.CalledProcessError, e:
445 raise ArtifactDownloadError(
446 'An error occurred when attempting to unzip %s:\n%s' %
447 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800448
joychen0a8e34e2013-06-24 17:58:36 -0700449 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800450 extract_result = self._Extract()
451 if self.store_installed_files:
452 # List both the archive and the extracted files.
453 self.installed_files.append(self.install_path)
454 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800455
Chris Sosa76e44b92013-01-31 12:11:38 -0800456 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800457 """Extracts files into the install path."""
458 if self.name.endswith('.zip'):
459 return self._ExtractZipfile()
460 else:
461 return self._ExtractTarball()
462
463 def _ExtractZipfile(self):
464 """Extracts a zip file using unzip."""
465 file_list = [os.path.join(self.install_dir, line[30:].strip())
466 for line in self._RunUnzip(True)
467 if not line.endswith('/')]
468 if file_list:
469 self._RunUnzip(False)
470
471 return file_list
472
473 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800474 """Extracts a tarball using tar.
475
476 Detects whether the tarball is compressed or not based on the file
477 extension and extracts the tarball into the install_path.
478 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700479 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800480 return common_util.ExtractTarball(self.install_path, self.install_dir,
481 files_to_extract=self._files_to_extract,
482 excluded_files=self._exclude,
483 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800484 except common_util.CommonUtilError as e:
485 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700486
487
Gabe Black3b567202015-09-23 14:07:59 -0700488class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700489 """Wrapper around the autotest tarball to download from gsutil."""
490
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800491 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700492 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800493 # We don't store/check explicit file lists in Autotest tarball markers;
494 # this can get huge and unwieldy, and generally make little sense.
495 self.store_installed_files = False
496
joychen0a8e34e2013-06-24 17:58:36 -0700497 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800498 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700499 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700500
Chris Sosa76e44b92013-01-31 12:11:38 -0800501 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700502 autotest_dir = os.path.join(self.install_dir,
503 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700504 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
505 if not os.path.exists(autotest_pkgs_dir):
506 os.makedirs(autotest_pkgs_dir)
507
508 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800509 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
510 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700511 try:
joychen0a8e34e2013-06-24 17:58:36 -0700512 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700513 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800514 raise ArtifactDownloadError(
515 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700516 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700517 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700518
Chris Masone816e38c2012-05-02 12:22:36 -0700519
Gabe Black3b567202015-09-23 14:07:59 -0700520def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
521 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700522
Gabe Black3b567202015-09-23 14:07:59 -0700523 Args:
524 tag: Tag of the artifact, defined in artifact_info.
525 base: Class of the artifact, e.g., BundledArtifact.
526 name: Name of the artifact, e.g., image.zip.
527 *fixed_args: Fixed arguments that are additional to the one used in base
528 class.
529 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
530 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800531
Gabe Black3b567202015-09-23 14:07:59 -0700532 Returns:
533 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700534 """
Don Garrettfb15e322016-06-21 19:12:08 -0700535 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700536 class NewArtifact(base):
537 """A data wrapper that describes an artifact's implementation."""
538 ARTIFACT_TAG = tag
539 ARTIFACT_NAME = name
540
541 def __init__(self, *args, **kwargs):
542 all_args = fixed_args + args
543 all_kwargs = {}
544 all_kwargs.update(fixed_kwargs)
545 all_kwargs.update(kwargs)
546 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
547 *all_args, **all_kwargs)
548
549 NewArtifact.__name__ = base.__name__
550 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700551
Chris Sosa76e44b92013-01-31 12:11:38 -0800552
Gabe Black3b567202015-09-23 14:07:59 -0700553# TODO(dshi): Refactor the code here to split out the logic of creating the
554# artifacts mapping to a different module.
555chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800556
Chris Sosa76e44b92013-01-31 12:11:38 -0800557
Gabe Black3b567202015-09-23 14:07:59 -0700558def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700559 """Add a data wrapper for ChromeOS artifacts.
560
561 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700562 chromeos_artifact_map.
563 """
564 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
565 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800566
beepsc3d0f872013-07-31 21:50:40 -0700567
Gabe Black3b567202015-09-23 14:07:59 -0700568_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
569
570
571class DeltaPayloadNtoN(DeltaPayloadBase):
572 """ChromeOS Delta payload artifact for updating from version N to N."""
573 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
574 ARTIFACT_NAME = 'NOT_APPLICABLE'
575
576 def __init__(self, install_dir, build, *args, **kwargs):
577 name = 'chromeos_%s*_delta_*' % build
578 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
579 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
580 install_subdir=install_subdir,
581 **kwargs)
582
583
584class DeltaPayloadMtoN(DeltaPayloadBase):
585 """ChromeOS Delta payload artifact for updating from version M to N."""
586 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
587 ARTIFACT_NAME = 'NOT_APPLICABLE'
588
589 def __init__(self, install_dir, build, *args, **kwargs):
590 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
591 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
592 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
593 install_subdir=install_subdir,
594 is_regex_name=True, **kwargs)
595
596
597chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
598 DeltaPayloadMtoN]
599
600
601_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
602 devserver_constants.STATEFUL_FILE)
603_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
604 optional_name=BASE_IMAGE_FILE,
605 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
606_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
607 optional_name=RECOVERY_IMAGE_FILE,
608 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
609_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
610 files_to_extract=[devserver_constants.IMAGE_FILE])
611_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
612 optional_name=TEST_IMAGE_FILE,
613 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
614_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
615 files_to_extract=None, exclude=['autotest/test_suites'])
616_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
617 CONTROL_FILES_FILE)
618_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
619 AUTOTEST_PACKAGES_FILE)
620_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
621_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
622_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
623 AUTOTEST_SERVER_PACKAGE_FILE)
624_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
625_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
626 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700627_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
628 DEBUG_SYMBOLS_ONLY_FILE,
629 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700630_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
631 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400632_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
633 FACTORY_SHIM_FILE,
634 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800635
Chris Sosa968a1062013-08-02 17:42:50 -0700636# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700637for c in devserver_constants.CHANNELS:
638 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
639 BundledArtifact,
640 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
641
Justin Giorgib7590522017-02-07 13:36:24 -0800642#### Libiota Artifacts ####
643_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
644 LIBIOTA_TEST_BINARIES_FILE)
645_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
646 LIBIOTA_BOARD_UTILS_FILE)
647
Gabe Black3b567202015-09-23 14:07:59 -0700648android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700649
Chris Sosa76e44b92013-01-31 12:11:38 -0800650
Gabe Black3b567202015-09-23 14:07:59 -0700651def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700652 """Add a data wrapper for android artifacts.
653
654 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700655 android_artifact_map.
656 """
657 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
658 android_artifact_map.setdefault(tag, []).append(artifact)
659
660
Dan Shiba4e00f2015-10-27 12:03:53 -0700661_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700662 ANDROID_IMAGE_ZIP, is_regex_name=True)
663_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
664 ANDROID_RADIO_IMAGE)
665_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
666 ANDROID_BOOTLOADER_IMAGE)
667_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
668_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
669 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800670_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
671 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800672_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
673 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
674_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
675 ANDROID_TEST_SUITES, is_regex_name=True)
676_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
677 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700678_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
679 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700680_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
681 BundledArtifact,
682 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
683 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700684_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
685 BundledArtifact,
686 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
687 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700688_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
689 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700690_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
691 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
692_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
693 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700694
695class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800696 """A factory class that generates build artifacts from artifact names."""
697
Dan Shi6c2b2a22016-03-04 15:52:19 -0800698 def __init__(self, artifact_map, download_dir, artifacts, files, build,
699 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800700 """Initalizes the member variables for the factory.
701
702 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700703 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700704 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700705 artifacts: List of artifacts to stage. These artifacts must be
706 defined in artifact_info.py and have a mapping in the
707 ARTIFACT_IMPLEMENTATION_MAP.
708 files: List of files to stage. These files are just downloaded and staged
709 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800710 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800711 requested_to_optional_map: A map between an artifact X to a list of
712 artifacts Y. If X is requested, all items in Y should also get
713 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800714 """
Gabe Black3b567202015-09-23 14:07:59 -0700715 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700716 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700717 self.artifacts = artifacts
718 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800719 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800720 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800721
Chris Sosa6b0c6172013-08-05 17:01:33 -0700722 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700723 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700724
725 If is_artifact is true, then these names define artifacts that must exist in
726 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
727 basic BuildArtifacts.
728
Gilad Arnold950569b2013-08-27 14:38:01 -0700729 Args:
730 names: A sequence of artifact names.
731 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800732
Gilad Arnold950569b2013-08-27 14:38:01 -0700733 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700734 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800735
Gilad Arnold950569b2013-08-27 14:38:01 -0700736 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700737 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700738 """
Gabe Black3b567202015-09-23 14:07:59 -0700739 if is_artifact:
740 classes = itertools.chain(*(self.artifact_map[name] for name in names))
741 return list(cls(self.download_dir, self.build) for cls in classes)
742 else:
743 return list(Artifact(name, self.download_dir, self.build)
744 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800745
746 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700747 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700748
Gilad Arnold950569b2013-08-27 14:38:01 -0700749 Returns:
750 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800751
Gilad Arnold950569b2013-08-27 14:38:01 -0700752 Raises:
753 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700754 """
755 artifacts = []
756 if self.artifacts:
757 artifacts.extend(self._Artifacts(self.artifacts, True))
758 if self.files:
759 artifacts.extend(self._Artifacts(self.files, False))
760
761 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800762
763 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700764 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700765
Gilad Arnold950569b2013-08-27 14:38:01 -0700766 Returns:
767 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800768
Gilad Arnold950569b2013-08-27 14:38:01 -0700769 Raises:
770 KeyError: if an optional artifact doesn't exist in
771 ARTIFACT_IMPLEMENTATION_MAP yet defined in
772 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700773 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800774 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800775 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800776 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700777 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800778 optional_names = optional_names.union(optional_list)
779
Chris Sosa6b0c6172013-08-05 17:01:33 -0700780 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700781
782
Gabe Black3b567202015-09-23 14:07:59 -0700783class ChromeOSArtifactFactory(BaseArtifactFactory):
784 """A factory class that generates ChromeOS build artifacts from names."""
785
786 def __init__(self, download_dir, artifacts, files, build):
787 """Pass the ChromeOS artifact map to the base class."""
788 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800789 chromeos_artifact_map, download_dir, artifacts, files, build,
790 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700791
792
793class AndroidArtifactFactory(BaseArtifactFactory):
794 """A factory class that generates Android build artifacts from names."""
795
796 def __init__(self, download_dir, artifacts, files, build):
797 """Pass the Android artifact map to the base class."""
798 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800799 android_artifact_map, download_dir, artifacts, files, build,
800 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700801
802
Chris Sosa968a1062013-08-02 17:42:50 -0700803# A simple main to verify correctness of the artifact map when making simple
804# name changes.
805if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700806 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
807 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
808 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
809 ('ANDROID', android_artifact_map)):
810 print('%s:' % label)
811 for key, value in sorted(mapping.items()):
812 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))