blob: abec2f6728961db90c3f56dccf881f91cbfb631d [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 Giorgi576c1542016-06-24 08:24:20 -070063ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
64ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080065
66_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070067
68
69class ArtifactDownloadError(Exception):
70 """Error used to signify an issue processing an artifact."""
71 pass
72
73
Gabe Black3b567202015-09-23 14:07:59 -070074class ArtifactMeta(type):
75 """metaclass for an artifact type.
76
77 This metaclass is for class Artifact and its subclasses to have a meaningful
78 string composed of class name and the corresponding artifact name, e.g.,
79 `Artifact_full_payload`. This helps to better logging, refer to logging in
80 method Downloader.Download.
81 """
82
83 ARTIFACT_NAME = None
84
85 def __str__(cls):
86 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
87
88 def __repr__(cls):
89 return str(cls)
90
91
92class Artifact(log_util.Loggable):
93 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070094
95 The purpose of this class is to download objects from Google Storage
96 and install them to a local directory. There are two main functions, one to
97 download/prepare the artifacts in to a temporary staging area and the second
98 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080099
Gilad Arnold950569b2013-08-27 14:38:01 -0700100 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
101 attentive when adding new artifacts; (ii) name matching semantics differ
102 between a glob (full name string match) and a regex (partial match).
103
Chris Sosa76e44b92013-01-31 12:11:38 -0800104 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700105 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700106 name: Name given for artifact; in fact, it is a pattern that captures the
107 names of files contained in the artifact. This can either be an
108 ordinary shell-style glob (the default), or a regular expression (if
109 is_regex_name is True).
110 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800111 build: The version of the build i.e. R26-2342.0.0.
112 marker_name: Name used to define the lock marker for the artifacts to
113 prevent it from being re-downloaded. By default based on name
114 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700115 exception_file_path: Path to a file containing the serialized exception,
116 which was raised in Process method. The file is located
117 in the parent folder of install_dir, since the
118 install_dir will be deleted if the build does not
119 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700120 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700121 install_subdir: Directory within install_path where the artifact is actually
122 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800123 install_dir: The final location where the artifact should be staged to.
124 single_name: If True the name given should only match one item. Note, if not
125 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800126 installed_files: A list of files that were the final result of downloading
127 and setting up the artifact.
128 store_installed_files: Whether the list of installed files is stored in the
129 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700130 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700131
Gabe Black3b567202015-09-23 14:07:59 -0700132 __metaclass__ = ArtifactMeta
133
134 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800135 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700136 """Constructor.
137
138 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800139 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800140 name: Identifying name to be used to find/store the artifact.
141 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700142 install_subdir: Directory within install_path where the artifact is
143 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700144 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800145 optional_name: An alternative name to find the artifact, which can lead
146 to faster download. Unlike |name|, there is no guarantee that an
147 artifact named |optional_name| is/will be on Google Storage. If it
148 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700149 """
Gabe Black3b567202015-09-23 14:07:59 -0700150 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700151
Chris Sosa76e44b92013-01-31 12:11:38 -0800152 # In-memory lock to keep the devserver from colliding with itself while
153 # attempting to stage the same artifact.
154 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700155
Chris Sosa76e44b92013-01-31 12:11:38 -0800156 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800157 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700158 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800159 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700160
Chris Sosa76e44b92013-01-31 12:11:38 -0800161 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162
Dan Shi6e50c722013-08-19 15:05:06 -0700163 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
164 '.exception')
165 # The exception file needs to be located in parent folder, since the
166 # install_dir will be deleted is the build does not exist.
167 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700168 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700169
joychen0a8e34e2013-06-24 17:58:36 -0700170 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700171
Chris Sosa76e44b92013-01-31 12:11:38 -0800172 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700173 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800174
175 self.single_name = True
176
Gilad Arnold1638d822013-11-07 23:38:16 -0800177 self.installed_files = []
178 self.store_installed_files = True
179
Chris Sosa76e44b92013-01-31 12:11:38 -0800180 @staticmethod
181 def _SanitizeName(name):
182 """Sanitizes name to be used for creating a file on the filesystem.
183
184 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700185
186 Args:
187 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800188
Gilad Arnold950569b2013-08-27 14:38:01 -0700189 Returns:
190 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800191 """
192 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
193
Dan Shif8eb0d12013-08-01 17:52:06 -0700194 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800195 """Returns True if artifact is already staged.
196
197 This checks for (1) presence of the artifact marker file, and (2) the
198 presence of each installed file listed in this marker. Both must hold for
199 the artifact to be considered staged. Note that this method is safe for use
200 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700201 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800202 files actually installed into the marker.
203 """
204 marker_file = os.path.join(self.install_dir, self.marker_name)
205
206 # If the marker is missing, it's definitely not staged.
207 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700208 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800209 return False
210
211 # We want to ensure that every file listed in the marker is actually there.
212 if self.store_installed_files:
213 with open(marker_file) as f:
214 files = [line.strip() for line in f]
215
216 # Check to see if any of the purportedly installed files are missing, in
217 # which case the marker is outdated and should be removed.
218 missing_files = [fname for fname in files if not os.path.exists(fname)]
219 if missing_files:
220 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
221 'All' if len(files) == len(missing_files) else 'Some',
222 marker_file, '\n'.join(missing_files))
223 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700224 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800225 return False
226
Aviv Keshet57d18172016-06-18 20:39:09 -0700227 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800228 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800229
230 def _MarkArtifactStaged(self):
231 """Marks the artifact as staged."""
232 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800233 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800234
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800235 def _UpdateName(self, names):
236 if self.single_name and len(names) > 1:
237 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800238
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800239 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800240
joychen0a8e34e2013-06-24 17:58:36 -0700241 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800242 """Process the downloaded content, update the list of installed files."""
243 # In this primitive case, what was downloaded (has to be a single file) is
244 # what's installed.
245 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700246
Dan Shi6e50c722013-08-19 15:05:06 -0700247 def _ClearException(self):
248 """Delete any existing exception saved for this artifact."""
249 if os.path.exists(self.exception_file_path):
250 os.remove(self.exception_file_path)
251
252 def _SaveException(self, e):
253 """Save the exception to a file for downloader.IsStaged to retrieve.
254
Gilad Arnold950569b2013-08-27 14:38:01 -0700255 Args:
256 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700257 """
258 with open(self.exception_file_path, 'w') as f:
259 pickle.dump(e, f)
260
261 def GetException(self):
262 """Retrieve any exception that was raised in Process method.
263
Gilad Arnold950569b2013-08-27 14:38:01 -0700264 Returns:
265 An Exception object that was raised when trying to process the artifact.
266 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700267 """
268 if not os.path.exists(self.exception_file_path):
269 return None
270 with open(self.exception_file_path, 'r') as f:
271 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800272
Gabe Black3b567202015-09-23 14:07:59 -0700273 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800274 """Main call point to all artifacts. Downloads and Stages artifact.
275
276 Downloads and Stages artifact from Google Storage to the install directory
277 specified in the constructor. It multi-thread safe and does not overwrite
278 the artifact if it's already been downloaded or being downloaded. After
279 processing, leaves behind a marker to indicate to future invocations that
280 the artifact has already been staged based on the name of the artifact.
281
282 Do not override as it modifies important private variables, ensures thread
283 safety, and maintains cache semantics.
284
285 Note: this may be a blocking call when the artifact is already in the
286 process of being staged.
287
288 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700289 downloader: A downloader instance containing the logic to download
290 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800291 no_wait: If True, don't block waiting for artifact to exist if we fail to
292 immediately find it.
293
294 Raises:
295 ArtifactDownloadError: If the artifact fails to download from Google
296 Storage for any reason or that the regexp
297 defined by name is not specific enough.
298 """
299 if not self._process_lock:
300 self._process_lock = _build_artifact_locks.lock(
301 os.path.join(self.install_dir, self.name))
302
Gabe Black3b567202015-09-23 14:07:59 -0700303 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800304 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700305 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700306 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800307 # Delete any existing exception saved for this artifact.
308 self._ClearException()
309 found_artifact = False
310 if self.optional_name:
311 try:
Gabe Black3b567202015-09-23 14:07:59 -0700312 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800313 # Because this artifact may not always exist, don't bother
314 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700315 new_names = downloader.Wait(
316 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800317 self._UpdateName(new_names)
318
319 except ArtifactDownloadError:
320 self._Log('Unable to download %s; fall back to download %s',
321 self.optional_name, self.name)
322 else:
323 found_artifact = True
324
Dan Shi6e50c722013-08-19 15:05:06 -0700325 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700326 # If the artifact should already have been uploaded, don't waste
327 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800328 if not found_artifact:
329 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700330 new_names = downloader.Wait(
331 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800332 self._UpdateName(new_names)
333
334 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700335 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700336 self._Setup()
337 self._MarkArtifactStaged()
338 except Exception as e:
339 # Save the exception to a file for downloader.IsStaged to retrieve.
340 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800341
342 # Convert an unknown exception into an ArtifactDownloadError.
343 if type(e) is ArtifactDownloadError:
344 raise
345 else:
346 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800347 else:
348 self._Log('%s is already staged.', self)
349
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700350 def __str__(self):
351 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700352 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700353
Chris Sosab26b1202013-08-16 16:40:55 -0700354 def __repr__(self):
355 return str(self)
356
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700357
Gabe Black3b567202015-09-23 14:07:59 -0700358class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700359 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700360
joychen0a8e34e2013-06-24 17:58:36 -0700361 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700362 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700363
Chris Sosa76e44b92013-01-31 12:11:38 -0800364 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700365 install_path = os.path.join(self.install_dir, self.install_subdir,
366 self.name)
367 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700368 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800369 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700370
Gilad Arnold1638d822013-11-07 23:38:16 -0800371 # Reflect the rename in the list of installed files.
372 self.installed_files.remove(install_path)
373 self.installed_files = [new_install_path]
374
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700375
Gabe Black3b567202015-09-23 14:07:59 -0700376class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800377 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700378
Gabe Black3b567202015-09-23 14:07:59 -0700379 These artifacts are super strange. They custom handle directories and
380 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800381 because unlike other artifacts, this one does not conform to something a
382 client might know. The client doesn't know the version of n-1 or whether it
383 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700384
385 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700386 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800387 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700388
joychen0a8e34e2013-06-24 17:58:36 -0700389 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700390 super(DeltaPayloadBase, self)._Setup()
391 # Setup symlink so that AU will work for this payload.
392 stateful_update_symlink = os.path.join(
393 self.install_dir, self.install_subdir,
394 devserver_constants.STATEFUL_FILE)
395 os.symlink(os.path.join(os.pardir, os.pardir,
396 devserver_constants.STATEFUL_FILE),
397 stateful_update_symlink)
398 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700399
Chris Sosa76e44b92013-01-31 12:11:38 -0800400
Gabe Black3b567202015-09-23 14:07:59 -0700401class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800402 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800403
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800404 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700405 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700406
407 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700408 *args: See Artifact documentation.
409 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700410 files_to_extract: A list of files to extract. If set to None, extract
411 all files.
412 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800413 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800414 self._files_to_extract = kwargs.pop('files_to_extract', None)
415 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700416 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800417
418 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800419 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800420 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800421 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800422
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800423 def _RunUnzip(self, list_only):
424 # Unzip is weird. It expects its args before any excludes and expects its
425 # excludes in a list following the -x.
426 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
427 if not list_only:
428 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800429
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800430 if self._files_to_extract:
431 cmd.extend(self._files_to_extract)
432
433 if self._exclude:
434 cmd.append('-x')
435 cmd.extend(self._exclude)
436
437 try:
438 return subprocess.check_output(cmd).strip('\n').splitlines()
439 except subprocess.CalledProcessError, e:
440 raise ArtifactDownloadError(
441 'An error occurred when attempting to unzip %s:\n%s' %
442 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800443
joychen0a8e34e2013-06-24 17:58:36 -0700444 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800445 extract_result = self._Extract()
446 if self.store_installed_files:
447 # List both the archive and the extracted files.
448 self.installed_files.append(self.install_path)
449 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800450
Chris Sosa76e44b92013-01-31 12:11:38 -0800451 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800452 """Extracts files into the install path."""
453 if self.name.endswith('.zip'):
454 return self._ExtractZipfile()
455 else:
456 return self._ExtractTarball()
457
458 def _ExtractZipfile(self):
459 """Extracts a zip file using unzip."""
460 file_list = [os.path.join(self.install_dir, line[30:].strip())
461 for line in self._RunUnzip(True)
462 if not line.endswith('/')]
463 if file_list:
464 self._RunUnzip(False)
465
466 return file_list
467
468 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800469 """Extracts a tarball using tar.
470
471 Detects whether the tarball is compressed or not based on the file
472 extension and extracts the tarball into the install_path.
473 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700474 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800475 return common_util.ExtractTarball(self.install_path, self.install_dir,
476 files_to_extract=self._files_to_extract,
477 excluded_files=self._exclude,
478 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800479 except common_util.CommonUtilError as e:
480 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700481
482
Gabe Black3b567202015-09-23 14:07:59 -0700483class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700484 """Wrapper around the autotest tarball to download from gsutil."""
485
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800486 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700487 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800488 # We don't store/check explicit file lists in Autotest tarball markers;
489 # this can get huge and unwieldy, and generally make little sense.
490 self.store_installed_files = False
491
joychen0a8e34e2013-06-24 17:58:36 -0700492 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800493 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700494 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700495
Chris Sosa76e44b92013-01-31 12:11:38 -0800496 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700497 autotest_dir = os.path.join(self.install_dir,
498 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700499 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
500 if not os.path.exists(autotest_pkgs_dir):
501 os.makedirs(autotest_pkgs_dir)
502
503 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800504 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
505 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700506 try:
joychen0a8e34e2013-06-24 17:58:36 -0700507 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700508 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800509 raise ArtifactDownloadError(
510 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700511 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700512 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700513
Chris Masone816e38c2012-05-02 12:22:36 -0700514
Gabe Black3b567202015-09-23 14:07:59 -0700515def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
516 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700517
Gabe Black3b567202015-09-23 14:07:59 -0700518 Args:
519 tag: Tag of the artifact, defined in artifact_info.
520 base: Class of the artifact, e.g., BundledArtifact.
521 name: Name of the artifact, e.g., image.zip.
522 *fixed_args: Fixed arguments that are additional to the one used in base
523 class.
524 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
525 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800526
Gabe Black3b567202015-09-23 14:07:59 -0700527 Returns:
528 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700529 """
Don Garrettfb15e322016-06-21 19:12:08 -0700530 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700531 class NewArtifact(base):
532 """A data wrapper that describes an artifact's implementation."""
533 ARTIFACT_TAG = tag
534 ARTIFACT_NAME = name
535
536 def __init__(self, *args, **kwargs):
537 all_args = fixed_args + args
538 all_kwargs = {}
539 all_kwargs.update(fixed_kwargs)
540 all_kwargs.update(kwargs)
541 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
542 *all_args, **all_kwargs)
543
544 NewArtifact.__name__ = base.__name__
545 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700546
Chris Sosa76e44b92013-01-31 12:11:38 -0800547
Gabe Black3b567202015-09-23 14:07:59 -0700548# TODO(dshi): Refactor the code here to split out the logic of creating the
549# artifacts mapping to a different module.
550chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800551
Chris Sosa76e44b92013-01-31 12:11:38 -0800552
Gabe Black3b567202015-09-23 14:07:59 -0700553def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700554 """Add a data wrapper for ChromeOS artifacts.
555
556 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700557 chromeos_artifact_map.
558 """
559 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
560 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800561
beepsc3d0f872013-07-31 21:50:40 -0700562
Gabe Black3b567202015-09-23 14:07:59 -0700563_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
564
565
566class DeltaPayloadNtoN(DeltaPayloadBase):
567 """ChromeOS Delta payload artifact for updating from version N to N."""
568 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
569 ARTIFACT_NAME = 'NOT_APPLICABLE'
570
571 def __init__(self, install_dir, build, *args, **kwargs):
572 name = 'chromeos_%s*_delta_*' % build
573 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
574 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
575 install_subdir=install_subdir,
576 **kwargs)
577
578
579class DeltaPayloadMtoN(DeltaPayloadBase):
580 """ChromeOS Delta payload artifact for updating from version M to N."""
581 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
582 ARTIFACT_NAME = 'NOT_APPLICABLE'
583
584 def __init__(self, install_dir, build, *args, **kwargs):
585 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
586 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
587 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
588 install_subdir=install_subdir,
589 is_regex_name=True, **kwargs)
590
591
592chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
593 DeltaPayloadMtoN]
594
595
596_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
597 devserver_constants.STATEFUL_FILE)
598_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
599 optional_name=BASE_IMAGE_FILE,
600 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
601_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
602 optional_name=RECOVERY_IMAGE_FILE,
603 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
604_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
605 files_to_extract=[devserver_constants.IMAGE_FILE])
606_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
607 optional_name=TEST_IMAGE_FILE,
608 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
609_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
610 files_to_extract=None, exclude=['autotest/test_suites'])
611_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
612 CONTROL_FILES_FILE)
613_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
614 AUTOTEST_PACKAGES_FILE)
615_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
616_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
617_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
618 AUTOTEST_SERVER_PACKAGE_FILE)
619_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
620_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
621 files_to_extract=['debug/breakpad'])
622_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
623 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800624
Chris Sosa968a1062013-08-02 17:42:50 -0700625# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700626for c in devserver_constants.CHANNELS:
627 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
628 BundledArtifact,
629 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
630
631android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700632
Chris Sosa76e44b92013-01-31 12:11:38 -0800633
Gabe Black3b567202015-09-23 14:07:59 -0700634def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700635 """Add a data wrapper for android artifacts.
636
637 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700638 android_artifact_map.
639 """
640 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
641 android_artifact_map.setdefault(tag, []).append(artifact)
642
643
Dan Shiba4e00f2015-10-27 12:03:53 -0700644_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700645 ANDROID_IMAGE_ZIP, is_regex_name=True)
646_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
647 ANDROID_RADIO_IMAGE)
648_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
649 ANDROID_BOOTLOADER_IMAGE)
650_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
651_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
652 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800653_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
654 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800655_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
656 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
657_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
658 ANDROID_TEST_SUITES, is_regex_name=True)
659_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
660 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700661_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
662 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700663_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
664 BundledArtifact,
665 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
666 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700667_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
668 BundledArtifact,
669 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
670 is_regex_name=True)
Justin Giorgi576c1542016-06-24 08:24:20 -0700671_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
672 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
673_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
674 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700675
676class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800677 """A factory class that generates build artifacts from artifact names."""
678
Dan Shi6c2b2a22016-03-04 15:52:19 -0800679 def __init__(self, artifact_map, download_dir, artifacts, files, build,
680 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800681 """Initalizes the member variables for the factory.
682
683 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700684 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700685 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700686 artifacts: List of artifacts to stage. These artifacts must be
687 defined in artifact_info.py and have a mapping in the
688 ARTIFACT_IMPLEMENTATION_MAP.
689 files: List of files to stage. These files are just downloaded and staged
690 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800691 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800692 requested_to_optional_map: A map between an artifact X to a list of
693 artifacts Y. If X is requested, all items in Y should also get
694 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800695 """
Gabe Black3b567202015-09-23 14:07:59 -0700696 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700697 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700698 self.artifacts = artifacts
699 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800700 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800701 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800702
Chris Sosa6b0c6172013-08-05 17:01:33 -0700703 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700704 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700705
706 If is_artifact is true, then these names define artifacts that must exist in
707 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
708 basic BuildArtifacts.
709
Gilad Arnold950569b2013-08-27 14:38:01 -0700710 Args:
711 names: A sequence of artifact names.
712 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800713
Gilad Arnold950569b2013-08-27 14:38:01 -0700714 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700715 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800716
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700718 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700719 """
Gabe Black3b567202015-09-23 14:07:59 -0700720 if is_artifact:
721 classes = itertools.chain(*(self.artifact_map[name] for name in names))
722 return list(cls(self.download_dir, self.build) for cls in classes)
723 else:
724 return list(Artifact(name, self.download_dir, self.build)
725 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800726
727 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700728 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700729
Gilad Arnold950569b2013-08-27 14:38:01 -0700730 Returns:
731 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800732
Gilad Arnold950569b2013-08-27 14:38:01 -0700733 Raises:
734 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700735 """
736 artifacts = []
737 if self.artifacts:
738 artifacts.extend(self._Artifacts(self.artifacts, True))
739 if self.files:
740 artifacts.extend(self._Artifacts(self.files, False))
741
742 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800743
744 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700745 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700746
Gilad Arnold950569b2013-08-27 14:38:01 -0700747 Returns:
748 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800749
Gilad Arnold950569b2013-08-27 14:38:01 -0700750 Raises:
751 KeyError: if an optional artifact doesn't exist in
752 ARTIFACT_IMPLEMENTATION_MAP yet defined in
753 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700754 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800755 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800756 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800757 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700758 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800759 optional_names = optional_names.union(optional_list)
760
Chris Sosa6b0c6172013-08-05 17:01:33 -0700761 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700762
763
Gabe Black3b567202015-09-23 14:07:59 -0700764class ChromeOSArtifactFactory(BaseArtifactFactory):
765 """A factory class that generates ChromeOS build artifacts from names."""
766
767 def __init__(self, download_dir, artifacts, files, build):
768 """Pass the ChromeOS artifact map to the base class."""
769 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800770 chromeos_artifact_map, download_dir, artifacts, files, build,
771 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700772
773
774class AndroidArtifactFactory(BaseArtifactFactory):
775 """A factory class that generates Android build artifacts from names."""
776
777 def __init__(self, download_dir, artifacts, files, build):
778 """Pass the Android artifact map to the base class."""
779 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800780 android_artifact_map, download_dir, artifacts, files, build,
781 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700782
783
Chris Sosa968a1062013-08-02 17:42:50 -0700784# A simple main to verify correctness of the artifact map when making simple
785# name changes.
786if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700787 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
788 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
789 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
790 ('ANDROID', android_artifact_map)):
791 print('%s:' % label)
792 for key, value in sorted(mapping.items()):
793 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))