blob: d5a70393ce69bf9d0549ca4173257dd05827ac30 [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'
49
Gabe Black3b567202015-09-23 14:07:59 -070050############ Actual filenames of Android build artifacts ############
51
Dan Shiba4e00f2015-10-27 12:03:53 -070052ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070053ANDROID_RADIO_IMAGE = 'radio.img'
54ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
55ANDROID_FASTBOOT = 'fastboot'
56ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080057ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080058ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
59ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
60ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070061ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070062ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070063ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
64 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070065ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070066ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
67ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080068
69_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070070
71
72class ArtifactDownloadError(Exception):
73 """Error used to signify an issue processing an artifact."""
74 pass
75
76
Gabe Black3b567202015-09-23 14:07:59 -070077class ArtifactMeta(type):
78 """metaclass for an artifact type.
79
80 This metaclass is for class Artifact and its subclasses to have a meaningful
81 string composed of class name and the corresponding artifact name, e.g.,
82 `Artifact_full_payload`. This helps to better logging, refer to logging in
83 method Downloader.Download.
84 """
85
86 ARTIFACT_NAME = None
87
88 def __str__(cls):
89 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
90
91 def __repr__(cls):
92 return str(cls)
93
94
95class Artifact(log_util.Loggable):
96 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070097
98 The purpose of this class is to download objects from Google Storage
99 and install them to a local directory. There are two main functions, one to
100 download/prepare the artifacts in to a temporary staging area and the second
101 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800102
Gilad Arnold950569b2013-08-27 14:38:01 -0700103 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
104 attentive when adding new artifacts; (ii) name matching semantics differ
105 between a glob (full name string match) and a regex (partial match).
106
Chris Sosa76e44b92013-01-31 12:11:38 -0800107 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700108 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700109 name: Name given for artifact; in fact, it is a pattern that captures the
110 names of files contained in the artifact. This can either be an
111 ordinary shell-style glob (the default), or a regular expression (if
112 is_regex_name is True).
113 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800114 build: The version of the build i.e. R26-2342.0.0.
115 marker_name: Name used to define the lock marker for the artifacts to
116 prevent it from being re-downloaded. By default based on name
117 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700118 exception_file_path: Path to a file containing the serialized exception,
119 which was raised in Process method. The file is located
120 in the parent folder of install_dir, since the
121 install_dir will be deleted if the build does not
122 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700123 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700124 install_subdir: Directory within install_path where the artifact is actually
125 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800126 install_dir: The final location where the artifact should be staged to.
127 single_name: If True the name given should only match one item. Note, if not
128 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800129 installed_files: A list of files that were the final result of downloading
130 and setting up the artifact.
131 store_installed_files: Whether the list of installed files is stored in the
132 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700133 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700134
Gabe Black3b567202015-09-23 14:07:59 -0700135 __metaclass__ = ArtifactMeta
136
137 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800138 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700139 """Constructor.
140
141 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800142 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800143 name: Identifying name to be used to find/store the artifact.
144 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700145 install_subdir: Directory within install_path where the artifact is
146 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700147 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800148 optional_name: An alternative name to find the artifact, which can lead
149 to faster download. Unlike |name|, there is no guarantee that an
150 artifact named |optional_name| is/will be on Google Storage. If it
151 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700152 """
Gabe Black3b567202015-09-23 14:07:59 -0700153 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700154
Chris Sosa76e44b92013-01-31 12:11:38 -0800155 # In-memory lock to keep the devserver from colliding with itself while
156 # attempting to stage the same artifact.
157 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700158
Chris Sosa76e44b92013-01-31 12:11:38 -0800159 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800160 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700161 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800162 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700163
Chris Sosa76e44b92013-01-31 12:11:38 -0800164 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700165
Dan Shi6e50c722013-08-19 15:05:06 -0700166 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
167 '.exception')
168 # The exception file needs to be located in parent folder, since the
169 # install_dir will be deleted is the build does not exist.
170 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700171 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700172
joychen0a8e34e2013-06-24 17:58:36 -0700173 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700174
Chris Sosa76e44b92013-01-31 12:11:38 -0800175 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700176 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800177
178 self.single_name = True
179
Gilad Arnold1638d822013-11-07 23:38:16 -0800180 self.installed_files = []
181 self.store_installed_files = True
182
Chris Sosa76e44b92013-01-31 12:11:38 -0800183 @staticmethod
184 def _SanitizeName(name):
185 """Sanitizes name to be used for creating a file on the filesystem.
186
187 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700188
189 Args:
190 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800191
Gilad Arnold950569b2013-08-27 14:38:01 -0700192 Returns:
193 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800194 """
195 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
196
Dan Shif8eb0d12013-08-01 17:52:06 -0700197 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800198 """Returns True if artifact is already staged.
199
200 This checks for (1) presence of the artifact marker file, and (2) the
201 presence of each installed file listed in this marker. Both must hold for
202 the artifact to be considered staged. Note that this method is safe for use
203 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700204 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800205 files actually installed into the marker.
206 """
207 marker_file = os.path.join(self.install_dir, self.marker_name)
208
209 # If the marker is missing, it's definitely not staged.
210 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700211 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800212 return False
213
214 # We want to ensure that every file listed in the marker is actually there.
215 if self.store_installed_files:
216 with open(marker_file) as f:
217 files = [line.strip() for line in f]
218
219 # Check to see if any of the purportedly installed files are missing, in
220 # which case the marker is outdated and should be removed.
221 missing_files = [fname for fname in files if not os.path.exists(fname)]
222 if missing_files:
223 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
224 'All' if len(files) == len(missing_files) else 'Some',
225 marker_file, '\n'.join(missing_files))
226 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700227 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800228 return False
229
Aviv Keshet57d18172016-06-18 20:39:09 -0700230 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800231 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800232
233 def _MarkArtifactStaged(self):
234 """Marks the artifact as staged."""
235 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800236 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800237
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800238 def _UpdateName(self, names):
239 if self.single_name and len(names) > 1:
240 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800241
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800242 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800243
joychen0a8e34e2013-06-24 17:58:36 -0700244 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800245 """Process the downloaded content, update the list of installed files."""
246 # In this primitive case, what was downloaded (has to be a single file) is
247 # what's installed.
248 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700249
Dan Shi6e50c722013-08-19 15:05:06 -0700250 def _ClearException(self):
251 """Delete any existing exception saved for this artifact."""
252 if os.path.exists(self.exception_file_path):
253 os.remove(self.exception_file_path)
254
255 def _SaveException(self, e):
256 """Save the exception to a file for downloader.IsStaged to retrieve.
257
Gilad Arnold950569b2013-08-27 14:38:01 -0700258 Args:
259 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700260 """
261 with open(self.exception_file_path, 'w') as f:
262 pickle.dump(e, f)
263
264 def GetException(self):
265 """Retrieve any exception that was raised in Process method.
266
Gilad Arnold950569b2013-08-27 14:38:01 -0700267 Returns:
268 An Exception object that was raised when trying to process the artifact.
269 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700270 """
271 if not os.path.exists(self.exception_file_path):
272 return None
273 with open(self.exception_file_path, 'r') as f:
274 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800275
Gabe Black3b567202015-09-23 14:07:59 -0700276 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800277 """Main call point to all artifacts. Downloads and Stages artifact.
278
279 Downloads and Stages artifact from Google Storage to the install directory
280 specified in the constructor. It multi-thread safe and does not overwrite
281 the artifact if it's already been downloaded or being downloaded. After
282 processing, leaves behind a marker to indicate to future invocations that
283 the artifact has already been staged based on the name of the artifact.
284
285 Do not override as it modifies important private variables, ensures thread
286 safety, and maintains cache semantics.
287
288 Note: this may be a blocking call when the artifact is already in the
289 process of being staged.
290
291 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700292 downloader: A downloader instance containing the logic to download
293 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800294 no_wait: If True, don't block waiting for artifact to exist if we fail to
295 immediately find it.
296
297 Raises:
298 ArtifactDownloadError: If the artifact fails to download from Google
299 Storage for any reason or that the regexp
300 defined by name is not specific enough.
301 """
302 if not self._process_lock:
303 self._process_lock = _build_artifact_locks.lock(
304 os.path.join(self.install_dir, self.name))
305
Gabe Black3b567202015-09-23 14:07:59 -0700306 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800307 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700308 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700309 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800310 # Delete any existing exception saved for this artifact.
311 self._ClearException()
312 found_artifact = False
313 if self.optional_name:
314 try:
Gabe Black3b567202015-09-23 14:07:59 -0700315 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800316 # Because this artifact may not always exist, don't bother
317 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700318 new_names = downloader.Wait(
319 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800320 self._UpdateName(new_names)
321
322 except ArtifactDownloadError:
323 self._Log('Unable to download %s; fall back to download %s',
324 self.optional_name, self.name)
325 else:
326 found_artifact = True
327
Dan Shi6e50c722013-08-19 15:05:06 -0700328 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700329 # If the artifact should already have been uploaded, don't waste
330 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800331 if not found_artifact:
332 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700333 new_names = downloader.Wait(
334 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800335 self._UpdateName(new_names)
336
337 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700338 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700339 self._Setup()
340 self._MarkArtifactStaged()
341 except Exception as e:
342 # Save the exception to a file for downloader.IsStaged to retrieve.
343 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800344
345 # Convert an unknown exception into an ArtifactDownloadError.
346 if type(e) is ArtifactDownloadError:
347 raise
348 else:
349 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800350 else:
351 self._Log('%s is already staged.', self)
352
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700353 def __str__(self):
354 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700355 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700356
Chris Sosab26b1202013-08-16 16:40:55 -0700357 def __repr__(self):
358 return str(self)
359
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700360
Gabe Black3b567202015-09-23 14:07:59 -0700361class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700362 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700363
joychen0a8e34e2013-06-24 17:58:36 -0700364 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700365 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700366
Chris Sosa76e44b92013-01-31 12:11:38 -0800367 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700368 install_path = os.path.join(self.install_dir, self.install_subdir,
369 self.name)
370 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700371 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800372 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700373
Gilad Arnold1638d822013-11-07 23:38:16 -0800374 # Reflect the rename in the list of installed files.
375 self.installed_files.remove(install_path)
376 self.installed_files = [new_install_path]
377
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700378
Gabe Black3b567202015-09-23 14:07:59 -0700379class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700381
Gabe Black3b567202015-09-23 14:07:59 -0700382 These artifacts are super strange. They custom handle directories and
383 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800384 because unlike other artifacts, this one does not conform to something a
385 client might know. The client doesn't know the version of n-1 or whether it
386 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700387
388 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700389 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800390 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700391
joychen0a8e34e2013-06-24 17:58:36 -0700392 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700393 super(DeltaPayloadBase, self)._Setup()
394 # Setup symlink so that AU will work for this payload.
395 stateful_update_symlink = os.path.join(
396 self.install_dir, self.install_subdir,
397 devserver_constants.STATEFUL_FILE)
398 os.symlink(os.path.join(os.pardir, os.pardir,
399 devserver_constants.STATEFUL_FILE),
400 stateful_update_symlink)
401 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700402
Chris Sosa76e44b92013-01-31 12:11:38 -0800403
Gabe Black3b567202015-09-23 14:07:59 -0700404class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800405 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800406
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800407 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700408 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700409
410 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700411 *args: See Artifact documentation.
412 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700413 files_to_extract: A list of files to extract. If set to None, extract
414 all files.
415 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800416 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800417 self._files_to_extract = kwargs.pop('files_to_extract', None)
418 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700419 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800420
421 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800422 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800423 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800424 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800425
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800426 def _RunUnzip(self, list_only):
427 # Unzip is weird. It expects its args before any excludes and expects its
428 # excludes in a list following the -x.
429 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
430 if not list_only:
431 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800432
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800433 if self._files_to_extract:
434 cmd.extend(self._files_to_extract)
435
436 if self._exclude:
437 cmd.append('-x')
438 cmd.extend(self._exclude)
439
440 try:
441 return subprocess.check_output(cmd).strip('\n').splitlines()
442 except subprocess.CalledProcessError, e:
443 raise ArtifactDownloadError(
444 'An error occurred when attempting to unzip %s:\n%s' %
445 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800446
joychen0a8e34e2013-06-24 17:58:36 -0700447 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800448 extract_result = self._Extract()
449 if self.store_installed_files:
450 # List both the archive and the extracted files.
451 self.installed_files.append(self.install_path)
452 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800453
Chris Sosa76e44b92013-01-31 12:11:38 -0800454 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800455 """Extracts files into the install path."""
456 if self.name.endswith('.zip'):
457 return self._ExtractZipfile()
458 else:
459 return self._ExtractTarball()
460
461 def _ExtractZipfile(self):
462 """Extracts a zip file using unzip."""
463 file_list = [os.path.join(self.install_dir, line[30:].strip())
464 for line in self._RunUnzip(True)
465 if not line.endswith('/')]
466 if file_list:
467 self._RunUnzip(False)
468
469 return file_list
470
471 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800472 """Extracts a tarball using tar.
473
474 Detects whether the tarball is compressed or not based on the file
475 extension and extracts the tarball into the install_path.
476 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700477 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800478 return common_util.ExtractTarball(self.install_path, self.install_dir,
479 files_to_extract=self._files_to_extract,
480 excluded_files=self._exclude,
481 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800482 except common_util.CommonUtilError as e:
483 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700484
485
Gabe Black3b567202015-09-23 14:07:59 -0700486class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700487 """Wrapper around the autotest tarball to download from gsutil."""
488
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800489 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700490 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800491 # We don't store/check explicit file lists in Autotest tarball markers;
492 # this can get huge and unwieldy, and generally make little sense.
493 self.store_installed_files = False
494
joychen0a8e34e2013-06-24 17:58:36 -0700495 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800496 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700497 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700498
Chris Sosa76e44b92013-01-31 12:11:38 -0800499 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700500 autotest_dir = os.path.join(self.install_dir,
501 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700502 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
503 if not os.path.exists(autotest_pkgs_dir):
504 os.makedirs(autotest_pkgs_dir)
505
506 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800507 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
508 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700509 try:
joychen0a8e34e2013-06-24 17:58:36 -0700510 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700511 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800512 raise ArtifactDownloadError(
513 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700514 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700515 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700516
Chris Masone816e38c2012-05-02 12:22:36 -0700517
Gabe Black3b567202015-09-23 14:07:59 -0700518def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
519 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700520
Gabe Black3b567202015-09-23 14:07:59 -0700521 Args:
522 tag: Tag of the artifact, defined in artifact_info.
523 base: Class of the artifact, e.g., BundledArtifact.
524 name: Name of the artifact, e.g., image.zip.
525 *fixed_args: Fixed arguments that are additional to the one used in base
526 class.
527 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
528 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800529
Gabe Black3b567202015-09-23 14:07:59 -0700530 Returns:
531 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700532 """
Don Garrettfb15e322016-06-21 19:12:08 -0700533 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700534 class NewArtifact(base):
535 """A data wrapper that describes an artifact's implementation."""
536 ARTIFACT_TAG = tag
537 ARTIFACT_NAME = name
538
539 def __init__(self, *args, **kwargs):
540 all_args = fixed_args + args
541 all_kwargs = {}
542 all_kwargs.update(fixed_kwargs)
543 all_kwargs.update(kwargs)
544 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
545 *all_args, **all_kwargs)
546
547 NewArtifact.__name__ = base.__name__
548 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700549
Chris Sosa76e44b92013-01-31 12:11:38 -0800550
Gabe Black3b567202015-09-23 14:07:59 -0700551# TODO(dshi): Refactor the code here to split out the logic of creating the
552# artifacts mapping to a different module.
553chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800554
Chris Sosa76e44b92013-01-31 12:11:38 -0800555
Gabe Black3b567202015-09-23 14:07:59 -0700556def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700557 """Add a data wrapper for ChromeOS artifacts.
558
559 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700560 chromeos_artifact_map.
561 """
562 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
563 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800564
beepsc3d0f872013-07-31 21:50:40 -0700565
Gabe Black3b567202015-09-23 14:07:59 -0700566_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
567
568
569class DeltaPayloadNtoN(DeltaPayloadBase):
570 """ChromeOS Delta payload artifact for updating from version N to N."""
571 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
572 ARTIFACT_NAME = 'NOT_APPLICABLE'
573
574 def __init__(self, install_dir, build, *args, **kwargs):
575 name = 'chromeos_%s*_delta_*' % build
576 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
577 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
578 install_subdir=install_subdir,
579 **kwargs)
580
581
582class DeltaPayloadMtoN(DeltaPayloadBase):
583 """ChromeOS Delta payload artifact for updating from version M to N."""
584 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
585 ARTIFACT_NAME = 'NOT_APPLICABLE'
586
587 def __init__(self, install_dir, build, *args, **kwargs):
588 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
589 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
590 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
591 install_subdir=install_subdir,
592 is_regex_name=True, **kwargs)
593
594
595chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
596 DeltaPayloadMtoN]
597
598
599_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
600 devserver_constants.STATEFUL_FILE)
601_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
602 optional_name=BASE_IMAGE_FILE,
603 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
604_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
605 optional_name=RECOVERY_IMAGE_FILE,
606 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
607_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
608 files_to_extract=[devserver_constants.IMAGE_FILE])
609_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
610 optional_name=TEST_IMAGE_FILE,
611 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
612_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
613 files_to_extract=None, exclude=['autotest/test_suites'])
614_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
615 CONTROL_FILES_FILE)
616_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
617 AUTOTEST_PACKAGES_FILE)
618_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
619_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
620_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
621 AUTOTEST_SERVER_PACKAGE_FILE)
622_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
623_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
624 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700625_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
626 DEBUG_SYMBOLS_ONLY_FILE,
627 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700628_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
629 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400630_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
631 FACTORY_SHIM_FILE,
632 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800633
Chris Sosa968a1062013-08-02 17:42:50 -0700634# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700635for c in devserver_constants.CHANNELS:
636 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
637 BundledArtifact,
638 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
639
640android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700641
Chris Sosa76e44b92013-01-31 12:11:38 -0800642
Gabe Black3b567202015-09-23 14:07:59 -0700643def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700644 """Add a data wrapper for android artifacts.
645
646 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700647 android_artifact_map.
648 """
649 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
650 android_artifact_map.setdefault(tag, []).append(artifact)
651
652
Dan Shiba4e00f2015-10-27 12:03:53 -0700653_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700654 ANDROID_IMAGE_ZIP, is_regex_name=True)
655_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
656 ANDROID_RADIO_IMAGE)
657_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
658 ANDROID_BOOTLOADER_IMAGE)
659_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
660_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
661 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800662_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
663 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800664_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
665 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
666_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
667 ANDROID_TEST_SUITES, is_regex_name=True)
668_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
669 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700670_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
671 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700672_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
673 BundledArtifact,
674 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
675 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700676_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
677 BundledArtifact,
678 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
679 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700680_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
681 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700682_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
683 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
684_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
685 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700686
687class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800688 """A factory class that generates build artifacts from artifact names."""
689
Dan Shi6c2b2a22016-03-04 15:52:19 -0800690 def __init__(self, artifact_map, download_dir, artifacts, files, build,
691 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800692 """Initalizes the member variables for the factory.
693
694 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700695 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700696 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700697 artifacts: List of artifacts to stage. These artifacts must be
698 defined in artifact_info.py and have a mapping in the
699 ARTIFACT_IMPLEMENTATION_MAP.
700 files: List of files to stage. These files are just downloaded and staged
701 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800702 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800703 requested_to_optional_map: A map between an artifact X to a list of
704 artifacts Y. If X is requested, all items in Y should also get
705 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800706 """
Gabe Black3b567202015-09-23 14:07:59 -0700707 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700708 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700709 self.artifacts = artifacts
710 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800711 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800712 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800713
Chris Sosa6b0c6172013-08-05 17:01:33 -0700714 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700715 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700716
717 If is_artifact is true, then these names define artifacts that must exist in
718 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
719 basic BuildArtifacts.
720
Gilad Arnold950569b2013-08-27 14:38:01 -0700721 Args:
722 names: A sequence of artifact names.
723 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800724
Gilad Arnold950569b2013-08-27 14:38:01 -0700725 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700726 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800727
Gilad Arnold950569b2013-08-27 14:38:01 -0700728 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700729 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700730 """
Gabe Black3b567202015-09-23 14:07:59 -0700731 if is_artifact:
732 classes = itertools.chain(*(self.artifact_map[name] for name in names))
733 return list(cls(self.download_dir, self.build) for cls in classes)
734 else:
735 return list(Artifact(name, self.download_dir, self.build)
736 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800737
738 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700739 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700740
Gilad Arnold950569b2013-08-27 14:38:01 -0700741 Returns:
742 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800743
Gilad Arnold950569b2013-08-27 14:38:01 -0700744 Raises:
745 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700746 """
747 artifacts = []
748 if self.artifacts:
749 artifacts.extend(self._Artifacts(self.artifacts, True))
750 if self.files:
751 artifacts.extend(self._Artifacts(self.files, False))
752
753 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800754
755 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700756 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700757
Gilad Arnold950569b2013-08-27 14:38:01 -0700758 Returns:
759 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800760
Gilad Arnold950569b2013-08-27 14:38:01 -0700761 Raises:
762 KeyError: if an optional artifact doesn't exist in
763 ARTIFACT_IMPLEMENTATION_MAP yet defined in
764 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700765 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800766 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800767 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800768 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700769 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800770 optional_names = optional_names.union(optional_list)
771
Chris Sosa6b0c6172013-08-05 17:01:33 -0700772 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700773
774
Gabe Black3b567202015-09-23 14:07:59 -0700775class ChromeOSArtifactFactory(BaseArtifactFactory):
776 """A factory class that generates ChromeOS build artifacts from names."""
777
778 def __init__(self, download_dir, artifacts, files, build):
779 """Pass the ChromeOS artifact map to the base class."""
780 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800781 chromeos_artifact_map, download_dir, artifacts, files, build,
782 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700783
784
785class AndroidArtifactFactory(BaseArtifactFactory):
786 """A factory class that generates Android build artifacts from names."""
787
788 def __init__(self, download_dir, artifacts, files, build):
789 """Pass the Android artifact map to the base class."""
790 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800791 android_artifact_map, download_dir, artifacts, files, build,
792 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700793
794
Chris Sosa968a1062013-08-02 17:42:50 -0700795# A simple main to verify correctness of the artifact map when making simple
796# name changes.
797if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700798 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
799 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
800 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
801 ('ANDROID', android_artifact_map)):
802 print('%s:' % label)
803 for key, value in sorted(mapping.items()):
804 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))