blob: 1896fdccf699d61c8f3b6e32f01a02b7ef248573 [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'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080040FACTORY_FILE = 'ChromeOS-factory*.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080041FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
42IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080043TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080044BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
45TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
46RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
47
Gabe Black3b567202015-09-23 14:07:59 -070048############ Actual filenames of Android build artifacts ############
49
Dan Shiba4e00f2015-10-27 12:03:53 -070050ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070051ANDROID_RADIO_IMAGE = 'radio.img'
52ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
53ANDROID_FASTBOOT = 'fastboot'
54ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080055ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080056ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
57ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
58ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070059ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070060ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070061ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
62 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070063ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070064ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
65ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080066
67_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070068
69
70class ArtifactDownloadError(Exception):
71 """Error used to signify an issue processing an artifact."""
72 pass
73
74
Gabe Black3b567202015-09-23 14:07:59 -070075class ArtifactMeta(type):
76 """metaclass for an artifact type.
77
78 This metaclass is for class Artifact and its subclasses to have a meaningful
79 string composed of class name and the corresponding artifact name, e.g.,
80 `Artifact_full_payload`. This helps to better logging, refer to logging in
81 method Downloader.Download.
82 """
83
84 ARTIFACT_NAME = None
85
86 def __str__(cls):
87 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
88
89 def __repr__(cls):
90 return str(cls)
91
92
93class Artifact(log_util.Loggable):
94 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070095
96 The purpose of this class is to download objects from Google Storage
97 and install them to a local directory. There are two main functions, one to
98 download/prepare the artifacts in to a temporary staging area and the second
99 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800100
Gilad Arnold950569b2013-08-27 14:38:01 -0700101 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
102 attentive when adding new artifacts; (ii) name matching semantics differ
103 between a glob (full name string match) and a regex (partial match).
104
Chris Sosa76e44b92013-01-31 12:11:38 -0800105 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700106 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700107 name: Name given for artifact; in fact, it is a pattern that captures the
108 names of files contained in the artifact. This can either be an
109 ordinary shell-style glob (the default), or a regular expression (if
110 is_regex_name is True).
111 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800112 build: The version of the build i.e. R26-2342.0.0.
113 marker_name: Name used to define the lock marker for the artifacts to
114 prevent it from being re-downloaded. By default based on name
115 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700116 exception_file_path: Path to a file containing the serialized exception,
117 which was raised in Process method. The file is located
118 in the parent folder of install_dir, since the
119 install_dir will be deleted if the build does not
120 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700121 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700122 install_subdir: Directory within install_path where the artifact is actually
123 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800124 install_dir: The final location where the artifact should be staged to.
125 single_name: If True the name given should only match one item. Note, if not
126 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800127 installed_files: A list of files that were the final result of downloading
128 and setting up the artifact.
129 store_installed_files: Whether the list of installed files is stored in the
130 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700131 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700132
Gabe Black3b567202015-09-23 14:07:59 -0700133 __metaclass__ = ArtifactMeta
134
135 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800136 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700137 """Constructor.
138
139 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800140 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800141 name: Identifying name to be used to find/store the artifact.
142 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700143 install_subdir: Directory within install_path where the artifact is
144 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700145 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800146 optional_name: An alternative name to find the artifact, which can lead
147 to faster download. Unlike |name|, there is no guarantee that an
148 artifact named |optional_name| is/will be on Google Storage. If it
149 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700150 """
Gabe Black3b567202015-09-23 14:07:59 -0700151 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700152
Chris Sosa76e44b92013-01-31 12:11:38 -0800153 # In-memory lock to keep the devserver from colliding with itself while
154 # attempting to stage the same artifact.
155 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700156
Chris Sosa76e44b92013-01-31 12:11:38 -0800157 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800158 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700159 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800160 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700161
Chris Sosa76e44b92013-01-31 12:11:38 -0800162 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700163
Dan Shi6e50c722013-08-19 15:05:06 -0700164 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
165 '.exception')
166 # The exception file needs to be located in parent folder, since the
167 # install_dir will be deleted is the build does not exist.
168 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700169 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700170
joychen0a8e34e2013-06-24 17:58:36 -0700171 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700172
Chris Sosa76e44b92013-01-31 12:11:38 -0800173 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700174 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800175
176 self.single_name = True
177
Gilad Arnold1638d822013-11-07 23:38:16 -0800178 self.installed_files = []
179 self.store_installed_files = True
180
Chris Sosa76e44b92013-01-31 12:11:38 -0800181 @staticmethod
182 def _SanitizeName(name):
183 """Sanitizes name to be used for creating a file on the filesystem.
184
185 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700186
187 Args:
188 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800189
Gilad Arnold950569b2013-08-27 14:38:01 -0700190 Returns:
191 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800192 """
193 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
194
Dan Shif8eb0d12013-08-01 17:52:06 -0700195 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800196 """Returns True if artifact is already staged.
197
198 This checks for (1) presence of the artifact marker file, and (2) the
199 presence of each installed file listed in this marker. Both must hold for
200 the artifact to be considered staged. Note that this method is safe for use
201 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700202 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800203 files actually installed into the marker.
204 """
205 marker_file = os.path.join(self.install_dir, self.marker_name)
206
207 # If the marker is missing, it's definitely not staged.
208 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700209 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800210 return False
211
212 # We want to ensure that every file listed in the marker is actually there.
213 if self.store_installed_files:
214 with open(marker_file) as f:
215 files = [line.strip() for line in f]
216
217 # Check to see if any of the purportedly installed files are missing, in
218 # which case the marker is outdated and should be removed.
219 missing_files = [fname for fname in files if not os.path.exists(fname)]
220 if missing_files:
221 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
222 'All' if len(files) == len(missing_files) else 'Some',
223 marker_file, '\n'.join(missing_files))
224 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700225 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800226 return False
227
Aviv Keshet57d18172016-06-18 20:39:09 -0700228 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800229 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800230
231 def _MarkArtifactStaged(self):
232 """Marks the artifact as staged."""
233 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800234 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800235
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800236 def _UpdateName(self, names):
237 if self.single_name and len(names) > 1:
238 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800239
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800240 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800241
joychen0a8e34e2013-06-24 17:58:36 -0700242 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800243 """Process the downloaded content, update the list of installed files."""
244 # In this primitive case, what was downloaded (has to be a single file) is
245 # what's installed.
246 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700247
Dan Shi6e50c722013-08-19 15:05:06 -0700248 def _ClearException(self):
249 """Delete any existing exception saved for this artifact."""
250 if os.path.exists(self.exception_file_path):
251 os.remove(self.exception_file_path)
252
253 def _SaveException(self, e):
254 """Save the exception to a file for downloader.IsStaged to retrieve.
255
Gilad Arnold950569b2013-08-27 14:38:01 -0700256 Args:
257 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700258 """
259 with open(self.exception_file_path, 'w') as f:
260 pickle.dump(e, f)
261
262 def GetException(self):
263 """Retrieve any exception that was raised in Process method.
264
Gilad Arnold950569b2013-08-27 14:38:01 -0700265 Returns:
266 An Exception object that was raised when trying to process the artifact.
267 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700268 """
269 if not os.path.exists(self.exception_file_path):
270 return None
271 with open(self.exception_file_path, 'r') as f:
272 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800273
Gabe Black3b567202015-09-23 14:07:59 -0700274 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800275 """Main call point to all artifacts. Downloads and Stages artifact.
276
277 Downloads and Stages artifact from Google Storage to the install directory
278 specified in the constructor. It multi-thread safe and does not overwrite
279 the artifact if it's already been downloaded or being downloaded. After
280 processing, leaves behind a marker to indicate to future invocations that
281 the artifact has already been staged based on the name of the artifact.
282
283 Do not override as it modifies important private variables, ensures thread
284 safety, and maintains cache semantics.
285
286 Note: this may be a blocking call when the artifact is already in the
287 process of being staged.
288
289 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700290 downloader: A downloader instance containing the logic to download
291 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800292 no_wait: If True, don't block waiting for artifact to exist if we fail to
293 immediately find it.
294
295 Raises:
296 ArtifactDownloadError: If the artifact fails to download from Google
297 Storage for any reason or that the regexp
298 defined by name is not specific enough.
299 """
300 if not self._process_lock:
301 self._process_lock = _build_artifact_locks.lock(
302 os.path.join(self.install_dir, self.name))
303
Gabe Black3b567202015-09-23 14:07:59 -0700304 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800305 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700306 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700307 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800308 # Delete any existing exception saved for this artifact.
309 self._ClearException()
310 found_artifact = False
311 if self.optional_name:
312 try:
Gabe Black3b567202015-09-23 14:07:59 -0700313 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800314 # Because this artifact may not always exist, don't bother
315 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700316 new_names = downloader.Wait(
317 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800318 self._UpdateName(new_names)
319
320 except ArtifactDownloadError:
321 self._Log('Unable to download %s; fall back to download %s',
322 self.optional_name, self.name)
323 else:
324 found_artifact = True
325
Dan Shi6e50c722013-08-19 15:05:06 -0700326 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700327 # If the artifact should already have been uploaded, don't waste
328 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800329 if not found_artifact:
330 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700331 new_names = downloader.Wait(
332 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800333 self._UpdateName(new_names)
334
335 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700336 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700337 self._Setup()
338 self._MarkArtifactStaged()
339 except Exception as e:
340 # Save the exception to a file for downloader.IsStaged to retrieve.
341 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800342
343 # Convert an unknown exception into an ArtifactDownloadError.
344 if type(e) is ArtifactDownloadError:
345 raise
346 else:
347 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800348 else:
349 self._Log('%s is already staged.', self)
350
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700351 def __str__(self):
352 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700353 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700354
Chris Sosab26b1202013-08-16 16:40:55 -0700355 def __repr__(self):
356 return str(self)
357
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700358
Gabe Black3b567202015-09-23 14:07:59 -0700359class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700360 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700361
joychen0a8e34e2013-06-24 17:58:36 -0700362 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700363 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700364
Chris Sosa76e44b92013-01-31 12:11:38 -0800365 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700366 install_path = os.path.join(self.install_dir, self.install_subdir,
367 self.name)
368 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700369 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800370 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700371
Gilad Arnold1638d822013-11-07 23:38:16 -0800372 # Reflect the rename in the list of installed files.
373 self.installed_files.remove(install_path)
374 self.installed_files = [new_install_path]
375
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700376
Gabe Black3b567202015-09-23 14:07:59 -0700377class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800378 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700379
Gabe Black3b567202015-09-23 14:07:59 -0700380 These artifacts are super strange. They custom handle directories and
381 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800382 because unlike other artifacts, this one does not conform to something a
383 client might know. The client doesn't know the version of n-1 or whether it
384 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700385
386 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700387 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800388 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700389
joychen0a8e34e2013-06-24 17:58:36 -0700390 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700391 super(DeltaPayloadBase, self)._Setup()
392 # Setup symlink so that AU will work for this payload.
393 stateful_update_symlink = os.path.join(
394 self.install_dir, self.install_subdir,
395 devserver_constants.STATEFUL_FILE)
396 os.symlink(os.path.join(os.pardir, os.pardir,
397 devserver_constants.STATEFUL_FILE),
398 stateful_update_symlink)
399 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700400
Chris Sosa76e44b92013-01-31 12:11:38 -0800401
Gabe Black3b567202015-09-23 14:07:59 -0700402class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800403 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800404
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800405 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700406 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700407
408 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700409 *args: See Artifact documentation.
410 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700411 files_to_extract: A list of files to extract. If set to None, extract
412 all files.
413 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800414 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800415 self._files_to_extract = kwargs.pop('files_to_extract', None)
416 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700417 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800418
419 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800420 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800421 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800422 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800423
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800424 def _RunUnzip(self, list_only):
425 # Unzip is weird. It expects its args before any excludes and expects its
426 # excludes in a list following the -x.
427 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
428 if not list_only:
429 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800430
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800431 if self._files_to_extract:
432 cmd.extend(self._files_to_extract)
433
434 if self._exclude:
435 cmd.append('-x')
436 cmd.extend(self._exclude)
437
438 try:
439 return subprocess.check_output(cmd).strip('\n').splitlines()
440 except subprocess.CalledProcessError, e:
441 raise ArtifactDownloadError(
442 'An error occurred when attempting to unzip %s:\n%s' %
443 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800444
joychen0a8e34e2013-06-24 17:58:36 -0700445 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800446 extract_result = self._Extract()
447 if self.store_installed_files:
448 # List both the archive and the extracted files.
449 self.installed_files.append(self.install_path)
450 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800451
Chris Sosa76e44b92013-01-31 12:11:38 -0800452 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800453 """Extracts files into the install path."""
454 if self.name.endswith('.zip'):
455 return self._ExtractZipfile()
456 else:
457 return self._ExtractTarball()
458
459 def _ExtractZipfile(self):
460 """Extracts a zip file using unzip."""
461 file_list = [os.path.join(self.install_dir, line[30:].strip())
462 for line in self._RunUnzip(True)
463 if not line.endswith('/')]
464 if file_list:
465 self._RunUnzip(False)
466
467 return file_list
468
469 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800470 """Extracts a tarball using tar.
471
472 Detects whether the tarball is compressed or not based on the file
473 extension and extracts the tarball into the install_path.
474 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700475 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800476 return common_util.ExtractTarball(self.install_path, self.install_dir,
477 files_to_extract=self._files_to_extract,
478 excluded_files=self._exclude,
479 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800480 except common_util.CommonUtilError as e:
481 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700482
483
Gabe Black3b567202015-09-23 14:07:59 -0700484class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700485 """Wrapper around the autotest tarball to download from gsutil."""
486
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800487 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700488 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800489 # We don't store/check explicit file lists in Autotest tarball markers;
490 # this can get huge and unwieldy, and generally make little sense.
491 self.store_installed_files = False
492
joychen0a8e34e2013-06-24 17:58:36 -0700493 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800494 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700495 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700496
Chris Sosa76e44b92013-01-31 12:11:38 -0800497 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700498 autotest_dir = os.path.join(self.install_dir,
499 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700500 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
501 if not os.path.exists(autotest_pkgs_dir):
502 os.makedirs(autotest_pkgs_dir)
503
504 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800505 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
506 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700507 try:
joychen0a8e34e2013-06-24 17:58:36 -0700508 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700509 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800510 raise ArtifactDownloadError(
511 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700512 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700513 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700514
Chris Masone816e38c2012-05-02 12:22:36 -0700515
Gabe Black3b567202015-09-23 14:07:59 -0700516def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
517 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700518
Gabe Black3b567202015-09-23 14:07:59 -0700519 Args:
520 tag: Tag of the artifact, defined in artifact_info.
521 base: Class of the artifact, e.g., BundledArtifact.
522 name: Name of the artifact, e.g., image.zip.
523 *fixed_args: Fixed arguments that are additional to the one used in base
524 class.
525 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
526 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800527
Gabe Black3b567202015-09-23 14:07:59 -0700528 Returns:
529 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700530 """
Don Garrettfb15e322016-06-21 19:12:08 -0700531 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700532 class NewArtifact(base):
533 """A data wrapper that describes an artifact's implementation."""
534 ARTIFACT_TAG = tag
535 ARTIFACT_NAME = name
536
537 def __init__(self, *args, **kwargs):
538 all_args = fixed_args + args
539 all_kwargs = {}
540 all_kwargs.update(fixed_kwargs)
541 all_kwargs.update(kwargs)
542 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
543 *all_args, **all_kwargs)
544
545 NewArtifact.__name__ = base.__name__
546 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700547
Chris Sosa76e44b92013-01-31 12:11:38 -0800548
Gabe Black3b567202015-09-23 14:07:59 -0700549# TODO(dshi): Refactor the code here to split out the logic of creating the
550# artifacts mapping to a different module.
551chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800552
Chris Sosa76e44b92013-01-31 12:11:38 -0800553
Gabe Black3b567202015-09-23 14:07:59 -0700554def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700555 """Add a data wrapper for ChromeOS artifacts.
556
557 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700558 chromeos_artifact_map.
559 """
560 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
561 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800562
beepsc3d0f872013-07-31 21:50:40 -0700563
Gabe Black3b567202015-09-23 14:07:59 -0700564_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
565
566
567class DeltaPayloadNtoN(DeltaPayloadBase):
568 """ChromeOS Delta payload artifact for updating from version N to N."""
569 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
570 ARTIFACT_NAME = 'NOT_APPLICABLE'
571
572 def __init__(self, install_dir, build, *args, **kwargs):
573 name = 'chromeos_%s*_delta_*' % build
574 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
575 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
576 install_subdir=install_subdir,
577 **kwargs)
578
579
580class DeltaPayloadMtoN(DeltaPayloadBase):
581 """ChromeOS Delta payload artifact for updating from version M to N."""
582 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
583 ARTIFACT_NAME = 'NOT_APPLICABLE'
584
585 def __init__(self, install_dir, build, *args, **kwargs):
586 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
587 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
588 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
589 install_subdir=install_subdir,
590 is_regex_name=True, **kwargs)
591
592
593chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
594 DeltaPayloadMtoN]
595
596
597_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
598 devserver_constants.STATEFUL_FILE)
599_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
600 optional_name=BASE_IMAGE_FILE,
601 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
602_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
603 optional_name=RECOVERY_IMAGE_FILE,
604 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
605_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
606 files_to_extract=[devserver_constants.IMAGE_FILE])
607_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
608 optional_name=TEST_IMAGE_FILE,
609 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
610_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
611 files_to_extract=None, exclude=['autotest/test_suites'])
612_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
613 CONTROL_FILES_FILE)
614_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
615 AUTOTEST_PACKAGES_FILE)
616_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
617_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
618_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
619 AUTOTEST_SERVER_PACKAGE_FILE)
620_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
621_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
622 files_to_extract=['debug/breakpad'])
623_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
624 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800625
Chris Sosa968a1062013-08-02 17:42:50 -0700626# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700627for c in devserver_constants.CHANNELS:
628 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
629 BundledArtifact,
630 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
631
632android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700633
Chris Sosa76e44b92013-01-31 12:11:38 -0800634
Gabe Black3b567202015-09-23 14:07:59 -0700635def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700636 """Add a data wrapper for android artifacts.
637
638 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700639 android_artifact_map.
640 """
641 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
642 android_artifact_map.setdefault(tag, []).append(artifact)
643
644
Dan Shiba4e00f2015-10-27 12:03:53 -0700645_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700646 ANDROID_IMAGE_ZIP, is_regex_name=True)
647_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
648 ANDROID_RADIO_IMAGE)
649_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
650 ANDROID_BOOTLOADER_IMAGE)
651_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
652_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
653 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800654_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
655 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800656_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
657 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
658_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
659 ANDROID_TEST_SUITES, is_regex_name=True)
660_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
661 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700662_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
663 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700664_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
665 BundledArtifact,
666 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
667 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700668_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
669 BundledArtifact,
670 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
671 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700672_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
673 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700674_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
675 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
676_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
677 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700678
679class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800680 """A factory class that generates build artifacts from artifact names."""
681
Dan Shi6c2b2a22016-03-04 15:52:19 -0800682 def __init__(self, artifact_map, download_dir, artifacts, files, build,
683 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800684 """Initalizes the member variables for the factory.
685
686 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700687 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700688 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700689 artifacts: List of artifacts to stage. These artifacts must be
690 defined in artifact_info.py and have a mapping in the
691 ARTIFACT_IMPLEMENTATION_MAP.
692 files: List of files to stage. These files are just downloaded and staged
693 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800694 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800695 requested_to_optional_map: A map between an artifact X to a list of
696 artifacts Y. If X is requested, all items in Y should also get
697 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800698 """
Gabe Black3b567202015-09-23 14:07:59 -0700699 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700700 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700701 self.artifacts = artifacts
702 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800703 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800704 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800705
Chris Sosa6b0c6172013-08-05 17:01:33 -0700706 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700707 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700708
709 If is_artifact is true, then these names define artifacts that must exist in
710 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
711 basic BuildArtifacts.
712
Gilad Arnold950569b2013-08-27 14:38:01 -0700713 Args:
714 names: A sequence of artifact names.
715 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800716
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700718 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800719
Gilad Arnold950569b2013-08-27 14:38:01 -0700720 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700721 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700722 """
Gabe Black3b567202015-09-23 14:07:59 -0700723 if is_artifact:
724 classes = itertools.chain(*(self.artifact_map[name] for name in names))
725 return list(cls(self.download_dir, self.build) for cls in classes)
726 else:
727 return list(Artifact(name, self.download_dir, self.build)
728 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800729
730 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700731 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700732
Gilad Arnold950569b2013-08-27 14:38:01 -0700733 Returns:
734 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800735
Gilad Arnold950569b2013-08-27 14:38:01 -0700736 Raises:
737 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700738 """
739 artifacts = []
740 if self.artifacts:
741 artifacts.extend(self._Artifacts(self.artifacts, True))
742 if self.files:
743 artifacts.extend(self._Artifacts(self.files, False))
744
745 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800746
747 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700748 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700749
Gilad Arnold950569b2013-08-27 14:38:01 -0700750 Returns:
751 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800752
Gilad Arnold950569b2013-08-27 14:38:01 -0700753 Raises:
754 KeyError: if an optional artifact doesn't exist in
755 ARTIFACT_IMPLEMENTATION_MAP yet defined in
756 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700757 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800758 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800759 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800760 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700761 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800762 optional_names = optional_names.union(optional_list)
763
Chris Sosa6b0c6172013-08-05 17:01:33 -0700764 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700765
766
Gabe Black3b567202015-09-23 14:07:59 -0700767class ChromeOSArtifactFactory(BaseArtifactFactory):
768 """A factory class that generates ChromeOS build artifacts from names."""
769
770 def __init__(self, download_dir, artifacts, files, build):
771 """Pass the ChromeOS artifact map to the base class."""
772 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800773 chromeos_artifact_map, download_dir, artifacts, files, build,
774 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700775
776
777class AndroidArtifactFactory(BaseArtifactFactory):
778 """A factory class that generates Android build artifacts from names."""
779
780 def __init__(self, download_dir, artifacts, files, build):
781 """Pass the Android artifact map to the base class."""
782 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800783 android_artifact_map, download_dir, artifacts, files, build,
784 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700785
786
Chris Sosa968a1062013-08-02 17:42:50 -0700787# A simple main to verify correctness of the artifact map when making simple
788# name changes.
789if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700790 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
791 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
792 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
793 ('ANDROID', android_artifact_map)):
794 print('%s:' % label)
795 for key, value in sorted(mapping.items()):
796 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))