blob: 22e056024750e14a2d91f85f127598a41eb8a603 [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'
Mike Frysingera0e6a282016-09-01 17:29:08 -040041FACTORY_SHIM_FILE = 'factory_image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080042FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
43IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080044TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080045BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
46TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
47RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
48
Gabe Black3b567202015-09-23 14:07:59 -070049############ Actual filenames of Android build artifacts ############
50
Dan Shiba4e00f2015-10-27 12:03:53 -070051ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070052ANDROID_RADIO_IMAGE = 'radio.img'
53ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
54ANDROID_FASTBOOT = 'fastboot'
55ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080056ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080057ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
58ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
59ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070060ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070061ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070062ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
63 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070064ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070065ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
66ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080067
68_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070069
70
71class ArtifactDownloadError(Exception):
72 """Error used to signify an issue processing an artifact."""
73 pass
74
75
Gabe Black3b567202015-09-23 14:07:59 -070076class ArtifactMeta(type):
77 """metaclass for an artifact type.
78
79 This metaclass is for class Artifact and its subclasses to have a meaningful
80 string composed of class name and the corresponding artifact name, e.g.,
81 `Artifact_full_payload`. This helps to better logging, refer to logging in
82 method Downloader.Download.
83 """
84
85 ARTIFACT_NAME = None
86
87 def __str__(cls):
88 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
89
90 def __repr__(cls):
91 return str(cls)
92
93
94class Artifact(log_util.Loggable):
95 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070096
97 The purpose of this class is to download objects from Google Storage
98 and install them to a local directory. There are two main functions, one to
99 download/prepare the artifacts in to a temporary staging area and the second
100 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800101
Gilad Arnold950569b2013-08-27 14:38:01 -0700102 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
103 attentive when adding new artifacts; (ii) name matching semantics differ
104 between a glob (full name string match) and a regex (partial match).
105
Chris Sosa76e44b92013-01-31 12:11:38 -0800106 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700107 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700108 name: Name given for artifact; in fact, it is a pattern that captures the
109 names of files contained in the artifact. This can either be an
110 ordinary shell-style glob (the default), or a regular expression (if
111 is_regex_name is True).
112 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800113 build: The version of the build i.e. R26-2342.0.0.
114 marker_name: Name used to define the lock marker for the artifacts to
115 prevent it from being re-downloaded. By default based on name
116 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700117 exception_file_path: Path to a file containing the serialized exception,
118 which was raised in Process method. The file is located
119 in the parent folder of install_dir, since the
120 install_dir will be deleted if the build does not
121 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700122 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700123 install_subdir: Directory within install_path where the artifact is actually
124 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800125 install_dir: The final location where the artifact should be staged to.
126 single_name: If True the name given should only match one item. Note, if not
127 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800128 installed_files: A list of files that were the final result of downloading
129 and setting up the artifact.
130 store_installed_files: Whether the list of installed files is stored in the
131 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700132 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700133
Gabe Black3b567202015-09-23 14:07:59 -0700134 __metaclass__ = ArtifactMeta
135
136 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800137 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700138 """Constructor.
139
140 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800141 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800142 name: Identifying name to be used to find/store the artifact.
143 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700144 install_subdir: Directory within install_path where the artifact is
145 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700146 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800147 optional_name: An alternative name to find the artifact, which can lead
148 to faster download. Unlike |name|, there is no guarantee that an
149 artifact named |optional_name| is/will be on Google Storage. If it
150 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700151 """
Gabe Black3b567202015-09-23 14:07:59 -0700152 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700153
Chris Sosa76e44b92013-01-31 12:11:38 -0800154 # In-memory lock to keep the devserver from colliding with itself while
155 # attempting to stage the same artifact.
156 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157
Chris Sosa76e44b92013-01-31 12:11:38 -0800158 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800159 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700160 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800161 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162
Chris Sosa76e44b92013-01-31 12:11:38 -0800163 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700164
Dan Shi6e50c722013-08-19 15:05:06 -0700165 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
166 '.exception')
167 # The exception file needs to be located in parent folder, since the
168 # install_dir will be deleted is the build does not exist.
169 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700170 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700171
joychen0a8e34e2013-06-24 17:58:36 -0700172 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700173
Chris Sosa76e44b92013-01-31 12:11:38 -0800174 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700175 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800176
177 self.single_name = True
178
Gilad Arnold1638d822013-11-07 23:38:16 -0800179 self.installed_files = []
180 self.store_installed_files = True
181
Chris Sosa76e44b92013-01-31 12:11:38 -0800182 @staticmethod
183 def _SanitizeName(name):
184 """Sanitizes name to be used for creating a file on the filesystem.
185
186 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700187
188 Args:
189 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800190
Gilad Arnold950569b2013-08-27 14:38:01 -0700191 Returns:
192 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800193 """
194 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
195
Dan Shif8eb0d12013-08-01 17:52:06 -0700196 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800197 """Returns True if artifact is already staged.
198
199 This checks for (1) presence of the artifact marker file, and (2) the
200 presence of each installed file listed in this marker. Both must hold for
201 the artifact to be considered staged. Note that this method is safe for use
202 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700203 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800204 files actually installed into the marker.
205 """
206 marker_file = os.path.join(self.install_dir, self.marker_name)
207
208 # If the marker is missing, it's definitely not staged.
209 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700210 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800211 return False
212
213 # We want to ensure that every file listed in the marker is actually there.
214 if self.store_installed_files:
215 with open(marker_file) as f:
216 files = [line.strip() for line in f]
217
218 # Check to see if any of the purportedly installed files are missing, in
219 # which case the marker is outdated and should be removed.
220 missing_files = [fname for fname in files if not os.path.exists(fname)]
221 if missing_files:
222 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
223 'All' if len(files) == len(missing_files) else 'Some',
224 marker_file, '\n'.join(missing_files))
225 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700226 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800227 return False
228
Aviv Keshet57d18172016-06-18 20:39:09 -0700229 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800230 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800231
232 def _MarkArtifactStaged(self):
233 """Marks the artifact as staged."""
234 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800235 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800236
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800237 def _UpdateName(self, names):
238 if self.single_name and len(names) > 1:
239 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800240
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800241 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800242
joychen0a8e34e2013-06-24 17:58:36 -0700243 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800244 """Process the downloaded content, update the list of installed files."""
245 # In this primitive case, what was downloaded (has to be a single file) is
246 # what's installed.
247 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700248
Dan Shi6e50c722013-08-19 15:05:06 -0700249 def _ClearException(self):
250 """Delete any existing exception saved for this artifact."""
251 if os.path.exists(self.exception_file_path):
252 os.remove(self.exception_file_path)
253
254 def _SaveException(self, e):
255 """Save the exception to a file for downloader.IsStaged to retrieve.
256
Gilad Arnold950569b2013-08-27 14:38:01 -0700257 Args:
258 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700259 """
260 with open(self.exception_file_path, 'w') as f:
261 pickle.dump(e, f)
262
263 def GetException(self):
264 """Retrieve any exception that was raised in Process method.
265
Gilad Arnold950569b2013-08-27 14:38:01 -0700266 Returns:
267 An Exception object that was raised when trying to process the artifact.
268 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700269 """
270 if not os.path.exists(self.exception_file_path):
271 return None
272 with open(self.exception_file_path, 'r') as f:
273 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800274
Gabe Black3b567202015-09-23 14:07:59 -0700275 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800276 """Main call point to all artifacts. Downloads and Stages artifact.
277
278 Downloads and Stages artifact from Google Storage to the install directory
279 specified in the constructor. It multi-thread safe and does not overwrite
280 the artifact if it's already been downloaded or being downloaded. After
281 processing, leaves behind a marker to indicate to future invocations that
282 the artifact has already been staged based on the name of the artifact.
283
284 Do not override as it modifies important private variables, ensures thread
285 safety, and maintains cache semantics.
286
287 Note: this may be a blocking call when the artifact is already in the
288 process of being staged.
289
290 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700291 downloader: A downloader instance containing the logic to download
292 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800293 no_wait: If True, don't block waiting for artifact to exist if we fail to
294 immediately find it.
295
296 Raises:
297 ArtifactDownloadError: If the artifact fails to download from Google
298 Storage for any reason or that the regexp
299 defined by name is not specific enough.
300 """
301 if not self._process_lock:
302 self._process_lock = _build_artifact_locks.lock(
303 os.path.join(self.install_dir, self.name))
304
Gabe Black3b567202015-09-23 14:07:59 -0700305 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800306 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700307 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700308 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800309 # Delete any existing exception saved for this artifact.
310 self._ClearException()
311 found_artifact = False
312 if self.optional_name:
313 try:
Gabe Black3b567202015-09-23 14:07:59 -0700314 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800315 # Because this artifact may not always exist, don't bother
316 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700317 new_names = downloader.Wait(
318 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800319 self._UpdateName(new_names)
320
321 except ArtifactDownloadError:
322 self._Log('Unable to download %s; fall back to download %s',
323 self.optional_name, self.name)
324 else:
325 found_artifact = True
326
Dan Shi6e50c722013-08-19 15:05:06 -0700327 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700328 # If the artifact should already have been uploaded, don't waste
329 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800330 if not found_artifact:
331 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700332 new_names = downloader.Wait(
333 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800334 self._UpdateName(new_names)
335
336 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700337 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700338 self._Setup()
339 self._MarkArtifactStaged()
340 except Exception as e:
341 # Save the exception to a file for downloader.IsStaged to retrieve.
342 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800343
344 # Convert an unknown exception into an ArtifactDownloadError.
345 if type(e) is ArtifactDownloadError:
346 raise
347 else:
348 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800349 else:
350 self._Log('%s is already staged.', self)
351
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700352 def __str__(self):
353 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700354 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700355
Chris Sosab26b1202013-08-16 16:40:55 -0700356 def __repr__(self):
357 return str(self)
358
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700359
Gabe Black3b567202015-09-23 14:07:59 -0700360class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700361 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700362
joychen0a8e34e2013-06-24 17:58:36 -0700363 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700364 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700365
Chris Sosa76e44b92013-01-31 12:11:38 -0800366 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700367 install_path = os.path.join(self.install_dir, self.install_subdir,
368 self.name)
369 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700370 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800371 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700372
Gilad Arnold1638d822013-11-07 23:38:16 -0800373 # Reflect the rename in the list of installed files.
374 self.installed_files.remove(install_path)
375 self.installed_files = [new_install_path]
376
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700377
Gabe Black3b567202015-09-23 14:07:59 -0700378class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800379 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700380
Gabe Black3b567202015-09-23 14:07:59 -0700381 These artifacts are super strange. They custom handle directories and
382 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800383 because unlike other artifacts, this one does not conform to something a
384 client might know. The client doesn't know the version of n-1 or whether it
385 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700386
387 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700388 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800389 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700390
joychen0a8e34e2013-06-24 17:58:36 -0700391 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700392 super(DeltaPayloadBase, self)._Setup()
393 # Setup symlink so that AU will work for this payload.
394 stateful_update_symlink = os.path.join(
395 self.install_dir, self.install_subdir,
396 devserver_constants.STATEFUL_FILE)
397 os.symlink(os.path.join(os.pardir, os.pardir,
398 devserver_constants.STATEFUL_FILE),
399 stateful_update_symlink)
400 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700401
Chris Sosa76e44b92013-01-31 12:11:38 -0800402
Gabe Black3b567202015-09-23 14:07:59 -0700403class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800404 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800405
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800406 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700407 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700408
409 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700410 *args: See Artifact documentation.
411 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700412 files_to_extract: A list of files to extract. If set to None, extract
413 all files.
414 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800416 self._files_to_extract = kwargs.pop('files_to_extract', None)
417 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700418 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800419
420 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800421 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800422 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800423 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800424
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800425 def _RunUnzip(self, list_only):
426 # Unzip is weird. It expects its args before any excludes and expects its
427 # excludes in a list following the -x.
428 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
429 if not list_only:
430 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800431
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800432 if self._files_to_extract:
433 cmd.extend(self._files_to_extract)
434
435 if self._exclude:
436 cmd.append('-x')
437 cmd.extend(self._exclude)
438
439 try:
440 return subprocess.check_output(cmd).strip('\n').splitlines()
441 except subprocess.CalledProcessError, e:
442 raise ArtifactDownloadError(
443 'An error occurred when attempting to unzip %s:\n%s' %
444 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800445
joychen0a8e34e2013-06-24 17:58:36 -0700446 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800447 extract_result = self._Extract()
448 if self.store_installed_files:
449 # List both the archive and the extracted files.
450 self.installed_files.append(self.install_path)
451 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800452
Chris Sosa76e44b92013-01-31 12:11:38 -0800453 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800454 """Extracts files into the install path."""
455 if self.name.endswith('.zip'):
456 return self._ExtractZipfile()
457 else:
458 return self._ExtractTarball()
459
460 def _ExtractZipfile(self):
461 """Extracts a zip file using unzip."""
462 file_list = [os.path.join(self.install_dir, line[30:].strip())
463 for line in self._RunUnzip(True)
464 if not line.endswith('/')]
465 if file_list:
466 self._RunUnzip(False)
467
468 return file_list
469
470 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800471 """Extracts a tarball using tar.
472
473 Detects whether the tarball is compressed or not based on the file
474 extension and extracts the tarball into the install_path.
475 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700476 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800477 return common_util.ExtractTarball(self.install_path, self.install_dir,
478 files_to_extract=self._files_to_extract,
479 excluded_files=self._exclude,
480 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800481 except common_util.CommonUtilError as e:
482 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700483
484
Gabe Black3b567202015-09-23 14:07:59 -0700485class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700486 """Wrapper around the autotest tarball to download from gsutil."""
487
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800488 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700489 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800490 # We don't store/check explicit file lists in Autotest tarball markers;
491 # this can get huge and unwieldy, and generally make little sense.
492 self.store_installed_files = False
493
joychen0a8e34e2013-06-24 17:58:36 -0700494 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800495 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700496 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700497
Chris Sosa76e44b92013-01-31 12:11:38 -0800498 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700499 autotest_dir = os.path.join(self.install_dir,
500 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700501 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
502 if not os.path.exists(autotest_pkgs_dir):
503 os.makedirs(autotest_pkgs_dir)
504
505 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800506 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
507 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700508 try:
joychen0a8e34e2013-06-24 17:58:36 -0700509 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700510 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800511 raise ArtifactDownloadError(
512 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700513 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700514 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700515
Chris Masone816e38c2012-05-02 12:22:36 -0700516
Gabe Black3b567202015-09-23 14:07:59 -0700517def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
518 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700519
Gabe Black3b567202015-09-23 14:07:59 -0700520 Args:
521 tag: Tag of the artifact, defined in artifact_info.
522 base: Class of the artifact, e.g., BundledArtifact.
523 name: Name of the artifact, e.g., image.zip.
524 *fixed_args: Fixed arguments that are additional to the one used in base
525 class.
526 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
527 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800528
Gabe Black3b567202015-09-23 14:07:59 -0700529 Returns:
530 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700531 """
Don Garrettfb15e322016-06-21 19:12:08 -0700532 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700533 class NewArtifact(base):
534 """A data wrapper that describes an artifact's implementation."""
535 ARTIFACT_TAG = tag
536 ARTIFACT_NAME = name
537
538 def __init__(self, *args, **kwargs):
539 all_args = fixed_args + args
540 all_kwargs = {}
541 all_kwargs.update(fixed_kwargs)
542 all_kwargs.update(kwargs)
543 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
544 *all_args, **all_kwargs)
545
546 NewArtifact.__name__ = base.__name__
547 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700548
Chris Sosa76e44b92013-01-31 12:11:38 -0800549
Gabe Black3b567202015-09-23 14:07:59 -0700550# TODO(dshi): Refactor the code here to split out the logic of creating the
551# artifacts mapping to a different module.
552chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800553
Chris Sosa76e44b92013-01-31 12:11:38 -0800554
Gabe Black3b567202015-09-23 14:07:59 -0700555def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700556 """Add a data wrapper for ChromeOS artifacts.
557
558 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700559 chromeos_artifact_map.
560 """
561 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
562 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800563
beepsc3d0f872013-07-31 21:50:40 -0700564
Gabe Black3b567202015-09-23 14:07:59 -0700565_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
566
567
568class DeltaPayloadNtoN(DeltaPayloadBase):
569 """ChromeOS Delta payload artifact for updating from version N to N."""
570 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
571 ARTIFACT_NAME = 'NOT_APPLICABLE'
572
573 def __init__(self, install_dir, build, *args, **kwargs):
574 name = 'chromeos_%s*_delta_*' % build
575 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
576 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
577 install_subdir=install_subdir,
578 **kwargs)
579
580
581class DeltaPayloadMtoN(DeltaPayloadBase):
582 """ChromeOS Delta payload artifact for updating from version M to N."""
583 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
584 ARTIFACT_NAME = 'NOT_APPLICABLE'
585
586 def __init__(self, install_dir, build, *args, **kwargs):
587 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
588 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
589 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
590 install_subdir=install_subdir,
591 is_regex_name=True, **kwargs)
592
593
594chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
595 DeltaPayloadMtoN]
596
597
598_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
599 devserver_constants.STATEFUL_FILE)
600_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
601 optional_name=BASE_IMAGE_FILE,
602 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
603_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
604 optional_name=RECOVERY_IMAGE_FILE,
605 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
606_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
607 files_to_extract=[devserver_constants.IMAGE_FILE])
608_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
609 optional_name=TEST_IMAGE_FILE,
610 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
611_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
612 files_to_extract=None, exclude=['autotest/test_suites'])
613_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
614 CONTROL_FILES_FILE)
615_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
616 AUTOTEST_PACKAGES_FILE)
617_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
618_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
619_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
620 AUTOTEST_SERVER_PACKAGE_FILE)
621_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
622_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
623 files_to_extract=['debug/breakpad'])
624_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
625 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400626_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
627 FACTORY_SHIM_FILE,
628 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800629
Chris Sosa968a1062013-08-02 17:42:50 -0700630# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700631for c in devserver_constants.CHANNELS:
632 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
633 BundledArtifact,
634 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
635
636android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700637
Chris Sosa76e44b92013-01-31 12:11:38 -0800638
Gabe Black3b567202015-09-23 14:07:59 -0700639def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700640 """Add a data wrapper for android artifacts.
641
642 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700643 android_artifact_map.
644 """
645 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
646 android_artifact_map.setdefault(tag, []).append(artifact)
647
648
Dan Shiba4e00f2015-10-27 12:03:53 -0700649_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700650 ANDROID_IMAGE_ZIP, is_regex_name=True)
651_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
652 ANDROID_RADIO_IMAGE)
653_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
654 ANDROID_BOOTLOADER_IMAGE)
655_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
656_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
657 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800658_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
659 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800660_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
661 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
662_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
663 ANDROID_TEST_SUITES, is_regex_name=True)
664_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
665 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700666_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
667 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700668_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
669 BundledArtifact,
670 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
671 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700672_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
673 BundledArtifact,
674 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
675 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700676_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
677 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700678_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
679 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
680_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
681 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700682
683class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800684 """A factory class that generates build artifacts from artifact names."""
685
Dan Shi6c2b2a22016-03-04 15:52:19 -0800686 def __init__(self, artifact_map, download_dir, artifacts, files, build,
687 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800688 """Initalizes the member variables for the factory.
689
690 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700691 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700692 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700693 artifacts: List of artifacts to stage. These artifacts must be
694 defined in artifact_info.py and have a mapping in the
695 ARTIFACT_IMPLEMENTATION_MAP.
696 files: List of files to stage. These files are just downloaded and staged
697 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800698 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800699 requested_to_optional_map: A map between an artifact X to a list of
700 artifacts Y. If X is requested, all items in Y should also get
701 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800702 """
Gabe Black3b567202015-09-23 14:07:59 -0700703 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700704 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700705 self.artifacts = artifacts
706 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800707 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800708 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800709
Chris Sosa6b0c6172013-08-05 17:01:33 -0700710 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700711 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700712
713 If is_artifact is true, then these names define artifacts that must exist in
714 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
715 basic BuildArtifacts.
716
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 Args:
718 names: A sequence of artifact names.
719 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800720
Gilad Arnold950569b2013-08-27 14:38:01 -0700721 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700722 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800723
Gilad Arnold950569b2013-08-27 14:38:01 -0700724 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700725 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700726 """
Gabe Black3b567202015-09-23 14:07:59 -0700727 if is_artifact:
728 classes = itertools.chain(*(self.artifact_map[name] for name in names))
729 return list(cls(self.download_dir, self.build) for cls in classes)
730 else:
731 return list(Artifact(name, self.download_dir, self.build)
732 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800733
734 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700735 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700736
Gilad Arnold950569b2013-08-27 14:38:01 -0700737 Returns:
738 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800739
Gilad Arnold950569b2013-08-27 14:38:01 -0700740 Raises:
741 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700742 """
743 artifacts = []
744 if self.artifacts:
745 artifacts.extend(self._Artifacts(self.artifacts, True))
746 if self.files:
747 artifacts.extend(self._Artifacts(self.files, False))
748
749 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800750
751 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700752 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700753
Gilad Arnold950569b2013-08-27 14:38:01 -0700754 Returns:
755 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800756
Gilad Arnold950569b2013-08-27 14:38:01 -0700757 Raises:
758 KeyError: if an optional artifact doesn't exist in
759 ARTIFACT_IMPLEMENTATION_MAP yet defined in
760 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700761 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800762 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800763 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800764 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700765 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800766 optional_names = optional_names.union(optional_list)
767
Chris Sosa6b0c6172013-08-05 17:01:33 -0700768 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700769
770
Gabe Black3b567202015-09-23 14:07:59 -0700771class ChromeOSArtifactFactory(BaseArtifactFactory):
772 """A factory class that generates ChromeOS build artifacts from names."""
773
774 def __init__(self, download_dir, artifacts, files, build):
775 """Pass the ChromeOS artifact map to the base class."""
776 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800777 chromeos_artifact_map, download_dir, artifacts, files, build,
778 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700779
780
781class AndroidArtifactFactory(BaseArtifactFactory):
782 """A factory class that generates Android build artifacts from names."""
783
784 def __init__(self, download_dir, artifacts, files, build):
785 """Pass the Android artifact map to the base class."""
786 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800787 android_artifact_map, download_dir, artifacts, files, build,
788 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700789
790
Chris Sosa968a1062013-08-02 17:42:50 -0700791# A simple main to verify correctness of the artifact map when making simple
792# name changes.
793if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700794 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
795 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
796 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
797 ('ANDROID', android_artifact_map)):
798 print('%s:' % label)
799 for key, value in sorted(mapping.items()):
800 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))