blob: 6feed5c68e09334be6c301d51ed642ad46928712 [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'
Chris Sosa76e44b92013-01-31 12:11:38 -080061
62_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070063
64
65class ArtifactDownloadError(Exception):
66 """Error used to signify an issue processing an artifact."""
67 pass
68
69
Gabe Black3b567202015-09-23 14:07:59 -070070class ArtifactMeta(type):
71 """metaclass for an artifact type.
72
73 This metaclass is for class Artifact and its subclasses to have a meaningful
74 string composed of class name and the corresponding artifact name, e.g.,
75 `Artifact_full_payload`. This helps to better logging, refer to logging in
76 method Downloader.Download.
77 """
78
79 ARTIFACT_NAME = None
80
81 def __str__(cls):
82 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
83
84 def __repr__(cls):
85 return str(cls)
86
87
88class Artifact(log_util.Loggable):
89 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070090
91 The purpose of this class is to download objects from Google Storage
92 and install them to a local directory. There are two main functions, one to
93 download/prepare the artifacts in to a temporary staging area and the second
94 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080095
Gilad Arnold950569b2013-08-27 14:38:01 -070096 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
97 attentive when adding new artifacts; (ii) name matching semantics differ
98 between a glob (full name string match) and a regex (partial match).
99
Chris Sosa76e44b92013-01-31 12:11:38 -0800100 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700101 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700102 name: Name given for artifact; in fact, it is a pattern that captures the
103 names of files contained in the artifact. This can either be an
104 ordinary shell-style glob (the default), or a regular expression (if
105 is_regex_name is True).
106 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800107 build: The version of the build i.e. R26-2342.0.0.
108 marker_name: Name used to define the lock marker for the artifacts to
109 prevent it from being re-downloaded. By default based on name
110 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700111 exception_file_path: Path to a file containing the serialized exception,
112 which was raised in Process method. The file is located
113 in the parent folder of install_dir, since the
114 install_dir will be deleted if the build does not
115 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700116 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700117 install_subdir: Directory within install_path where the artifact is actually
118 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 install_dir: The final location where the artifact should be staged to.
120 single_name: If True the name given should only match one item. Note, if not
121 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800122 installed_files: A list of files that were the final result of downloading
123 and setting up the artifact.
124 store_installed_files: Whether the list of installed files is stored in the
125 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700126 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700127
Gabe Black3b567202015-09-23 14:07:59 -0700128 __metaclass__ = ArtifactMeta
129
130 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800131 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700132 """Constructor.
133
134 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800135 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800136 name: Identifying name to be used to find/store the artifact.
137 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700138 install_subdir: Directory within install_path where the artifact is
139 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700140 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800141 optional_name: An alternative name to find the artifact, which can lead
142 to faster download. Unlike |name|, there is no guarantee that an
143 artifact named |optional_name| is/will be on Google Storage. If it
144 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700145 """
Gabe Black3b567202015-09-23 14:07:59 -0700146 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700147
Chris Sosa76e44b92013-01-31 12:11:38 -0800148 # In-memory lock to keep the devserver from colliding with itself while
149 # attempting to stage the same artifact.
150 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700151
Chris Sosa76e44b92013-01-31 12:11:38 -0800152 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800153 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700154 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800155 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700156
Chris Sosa76e44b92013-01-31 12:11:38 -0800157 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700158
Dan Shi6e50c722013-08-19 15:05:06 -0700159 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
160 '.exception')
161 # The exception file needs to be located in parent folder, since the
162 # install_dir will be deleted is the build does not exist.
163 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700164 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700165
joychen0a8e34e2013-06-24 17:58:36 -0700166 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700167
Chris Sosa76e44b92013-01-31 12:11:38 -0800168 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700169 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800170
171 self.single_name = True
172
Gilad Arnold1638d822013-11-07 23:38:16 -0800173 self.installed_files = []
174 self.store_installed_files = True
175
Chris Sosa76e44b92013-01-31 12:11:38 -0800176 @staticmethod
177 def _SanitizeName(name):
178 """Sanitizes name to be used for creating a file on the filesystem.
179
180 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700181
182 Args:
183 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800184
Gilad Arnold950569b2013-08-27 14:38:01 -0700185 Returns:
186 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800187 """
188 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
189
Dan Shif8eb0d12013-08-01 17:52:06 -0700190 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800191 """Returns True if artifact is already staged.
192
193 This checks for (1) presence of the artifact marker file, and (2) the
194 presence of each installed file listed in this marker. Both must hold for
195 the artifact to be considered staged. Note that this method is safe for use
196 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700197 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800198 files actually installed into the marker.
199 """
200 marker_file = os.path.join(self.install_dir, self.marker_name)
201
202 # If the marker is missing, it's definitely not staged.
203 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700204 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800205 return False
206
207 # We want to ensure that every file listed in the marker is actually there.
208 if self.store_installed_files:
209 with open(marker_file) as f:
210 files = [line.strip() for line in f]
211
212 # Check to see if any of the purportedly installed files are missing, in
213 # which case the marker is outdated and should be removed.
214 missing_files = [fname for fname in files if not os.path.exists(fname)]
215 if missing_files:
216 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
217 'All' if len(files) == len(missing_files) else 'Some',
218 marker_file, '\n'.join(missing_files))
219 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700220 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800221 return False
222
Aviv Keshet57d18172016-06-18 20:39:09 -0700223 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800224 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800225
226 def _MarkArtifactStaged(self):
227 """Marks the artifact as staged."""
228 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800229 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800230
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800231 def _UpdateName(self, names):
232 if self.single_name and len(names) > 1:
233 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800234
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800235 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800236
joychen0a8e34e2013-06-24 17:58:36 -0700237 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800238 """Process the downloaded content, update the list of installed files."""
239 # In this primitive case, what was downloaded (has to be a single file) is
240 # what's installed.
241 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700242
Dan Shi6e50c722013-08-19 15:05:06 -0700243 def _ClearException(self):
244 """Delete any existing exception saved for this artifact."""
245 if os.path.exists(self.exception_file_path):
246 os.remove(self.exception_file_path)
247
248 def _SaveException(self, e):
249 """Save the exception to a file for downloader.IsStaged to retrieve.
250
Gilad Arnold950569b2013-08-27 14:38:01 -0700251 Args:
252 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700253 """
254 with open(self.exception_file_path, 'w') as f:
255 pickle.dump(e, f)
256
257 def GetException(self):
258 """Retrieve any exception that was raised in Process method.
259
Gilad Arnold950569b2013-08-27 14:38:01 -0700260 Returns:
261 An Exception object that was raised when trying to process the artifact.
262 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700263 """
264 if not os.path.exists(self.exception_file_path):
265 return None
266 with open(self.exception_file_path, 'r') as f:
267 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800268
Gabe Black3b567202015-09-23 14:07:59 -0700269 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800270 """Main call point to all artifacts. Downloads and Stages artifact.
271
272 Downloads and Stages artifact from Google Storage to the install directory
273 specified in the constructor. It multi-thread safe and does not overwrite
274 the artifact if it's already been downloaded or being downloaded. After
275 processing, leaves behind a marker to indicate to future invocations that
276 the artifact has already been staged based on the name of the artifact.
277
278 Do not override as it modifies important private variables, ensures thread
279 safety, and maintains cache semantics.
280
281 Note: this may be a blocking call when the artifact is already in the
282 process of being staged.
283
284 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700285 downloader: A downloader instance containing the logic to download
286 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800287 no_wait: If True, don't block waiting for artifact to exist if we fail to
288 immediately find it.
289
290 Raises:
291 ArtifactDownloadError: If the artifact fails to download from Google
292 Storage for any reason or that the regexp
293 defined by name is not specific enough.
294 """
295 if not self._process_lock:
296 self._process_lock = _build_artifact_locks.lock(
297 os.path.join(self.install_dir, self.name))
298
Gabe Black3b567202015-09-23 14:07:59 -0700299 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800300 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700301 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700302 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800303 # Delete any existing exception saved for this artifact.
304 self._ClearException()
305 found_artifact = False
306 if self.optional_name:
307 try:
Gabe Black3b567202015-09-23 14:07:59 -0700308 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800309 # Because this artifact may not always exist, don't bother
310 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700311 new_names = downloader.Wait(
312 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800313 self._UpdateName(new_names)
314
315 except ArtifactDownloadError:
316 self._Log('Unable to download %s; fall back to download %s',
317 self.optional_name, self.name)
318 else:
319 found_artifact = True
320
Dan Shi6e50c722013-08-19 15:05:06 -0700321 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700322 # If the artifact should already have been uploaded, don't waste
323 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800324 if not found_artifact:
325 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700326 new_names = downloader.Wait(
327 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800328 self._UpdateName(new_names)
329
330 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700331 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700332 self._Setup()
333 self._MarkArtifactStaged()
334 except Exception as e:
335 # Save the exception to a file for downloader.IsStaged to retrieve.
336 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800337
338 # Convert an unknown exception into an ArtifactDownloadError.
339 if type(e) is ArtifactDownloadError:
340 raise
341 else:
342 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800343 else:
344 self._Log('%s is already staged.', self)
345
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700346 def __str__(self):
347 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700348 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700349
Chris Sosab26b1202013-08-16 16:40:55 -0700350 def __repr__(self):
351 return str(self)
352
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700353
Gabe Black3b567202015-09-23 14:07:59 -0700354class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700355 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700356
joychen0a8e34e2013-06-24 17:58:36 -0700357 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700358 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700359
Chris Sosa76e44b92013-01-31 12:11:38 -0800360 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700361 install_path = os.path.join(self.install_dir, self.install_subdir,
362 self.name)
363 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700364 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800365 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700366
Gilad Arnold1638d822013-11-07 23:38:16 -0800367 # Reflect the rename in the list of installed files.
368 self.installed_files.remove(install_path)
369 self.installed_files = [new_install_path]
370
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700371
Gabe Black3b567202015-09-23 14:07:59 -0700372class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800373 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700374
Gabe Black3b567202015-09-23 14:07:59 -0700375 These artifacts are super strange. They custom handle directories and
376 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800377 because unlike other artifacts, this one does not conform to something a
378 client might know. The client doesn't know the version of n-1 or whether it
379 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700380
381 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700382 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800383 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700384
joychen0a8e34e2013-06-24 17:58:36 -0700385 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700386 super(DeltaPayloadBase, self)._Setup()
387 # Setup symlink so that AU will work for this payload.
388 stateful_update_symlink = os.path.join(
389 self.install_dir, self.install_subdir,
390 devserver_constants.STATEFUL_FILE)
391 os.symlink(os.path.join(os.pardir, os.pardir,
392 devserver_constants.STATEFUL_FILE),
393 stateful_update_symlink)
394 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700395
Chris Sosa76e44b92013-01-31 12:11:38 -0800396
Gabe Black3b567202015-09-23 14:07:59 -0700397class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800398 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800399
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800400 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700401 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700402
403 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700404 *args: See Artifact documentation.
405 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700406 files_to_extract: A list of files to extract. If set to None, extract
407 all files.
408 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800409 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800410 self._files_to_extract = kwargs.pop('files_to_extract', None)
411 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700412 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800413
414 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800415 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800416 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800417 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800418
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800419 def _RunUnzip(self, list_only):
420 # Unzip is weird. It expects its args before any excludes and expects its
421 # excludes in a list following the -x.
422 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
423 if not list_only:
424 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800425
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800426 if self._files_to_extract:
427 cmd.extend(self._files_to_extract)
428
429 if self._exclude:
430 cmd.append('-x')
431 cmd.extend(self._exclude)
432
433 try:
434 return subprocess.check_output(cmd).strip('\n').splitlines()
435 except subprocess.CalledProcessError, e:
436 raise ArtifactDownloadError(
437 'An error occurred when attempting to unzip %s:\n%s' %
438 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800439
joychen0a8e34e2013-06-24 17:58:36 -0700440 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800441 extract_result = self._Extract()
442 if self.store_installed_files:
443 # List both the archive and the extracted files.
444 self.installed_files.append(self.install_path)
445 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800446
Chris Sosa76e44b92013-01-31 12:11:38 -0800447 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800448 """Extracts files into the install path."""
449 if self.name.endswith('.zip'):
450 return self._ExtractZipfile()
451 else:
452 return self._ExtractTarball()
453
454 def _ExtractZipfile(self):
455 """Extracts a zip file using unzip."""
456 file_list = [os.path.join(self.install_dir, line[30:].strip())
457 for line in self._RunUnzip(True)
458 if not line.endswith('/')]
459 if file_list:
460 self._RunUnzip(False)
461
462 return file_list
463
464 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800465 """Extracts a tarball using tar.
466
467 Detects whether the tarball is compressed or not based on the file
468 extension and extracts the tarball into the install_path.
469 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700470 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800471 return common_util.ExtractTarball(self.install_path, self.install_dir,
472 files_to_extract=self._files_to_extract,
473 excluded_files=self._exclude,
474 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800475 except common_util.CommonUtilError as e:
476 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700477
478
Gabe Black3b567202015-09-23 14:07:59 -0700479class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700480 """Wrapper around the autotest tarball to download from gsutil."""
481
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800482 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700483 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800484 # We don't store/check explicit file lists in Autotest tarball markers;
485 # this can get huge and unwieldy, and generally make little sense.
486 self.store_installed_files = False
487
joychen0a8e34e2013-06-24 17:58:36 -0700488 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800489 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700490 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700491
Chris Sosa76e44b92013-01-31 12:11:38 -0800492 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700493 autotest_dir = os.path.join(self.install_dir,
494 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700495 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
496 if not os.path.exists(autotest_pkgs_dir):
497 os.makedirs(autotest_pkgs_dir)
498
499 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800500 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
501 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700502 try:
joychen0a8e34e2013-06-24 17:58:36 -0700503 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700504 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800505 raise ArtifactDownloadError(
506 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700507 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700508 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700509
Chris Masone816e38c2012-05-02 12:22:36 -0700510
Gabe Black3b567202015-09-23 14:07:59 -0700511def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
512 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700513
Gabe Black3b567202015-09-23 14:07:59 -0700514 Args:
515 tag: Tag of the artifact, defined in artifact_info.
516 base: Class of the artifact, e.g., BundledArtifact.
517 name: Name of the artifact, e.g., image.zip.
518 *fixed_args: Fixed arguments that are additional to the one used in base
519 class.
520 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
521 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800522
Gabe Black3b567202015-09-23 14:07:59 -0700523 Returns:
524 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700525 """
Don Garrettfb15e322016-06-21 19:12:08 -0700526 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700527 class NewArtifact(base):
528 """A data wrapper that describes an artifact's implementation."""
529 ARTIFACT_TAG = tag
530 ARTIFACT_NAME = name
531
532 def __init__(self, *args, **kwargs):
533 all_args = fixed_args + args
534 all_kwargs = {}
535 all_kwargs.update(fixed_kwargs)
536 all_kwargs.update(kwargs)
537 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
538 *all_args, **all_kwargs)
539
540 NewArtifact.__name__ = base.__name__
541 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700542
Chris Sosa76e44b92013-01-31 12:11:38 -0800543
Gabe Black3b567202015-09-23 14:07:59 -0700544# TODO(dshi): Refactor the code here to split out the logic of creating the
545# artifacts mapping to a different module.
546chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800547
Chris Sosa76e44b92013-01-31 12:11:38 -0800548
Gabe Black3b567202015-09-23 14:07:59 -0700549def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700550 """Add a data wrapper for ChromeOS artifacts.
551
552 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700553 chromeos_artifact_map.
554 """
555 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
556 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800557
beepsc3d0f872013-07-31 21:50:40 -0700558
Gabe Black3b567202015-09-23 14:07:59 -0700559_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
560
561
562class DeltaPayloadNtoN(DeltaPayloadBase):
563 """ChromeOS Delta payload artifact for updating from version N to N."""
564 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
565 ARTIFACT_NAME = 'NOT_APPLICABLE'
566
567 def __init__(self, install_dir, build, *args, **kwargs):
568 name = 'chromeos_%s*_delta_*' % build
569 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
570 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
571 install_subdir=install_subdir,
572 **kwargs)
573
574
575class DeltaPayloadMtoN(DeltaPayloadBase):
576 """ChromeOS Delta payload artifact for updating from version M to N."""
577 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
578 ARTIFACT_NAME = 'NOT_APPLICABLE'
579
580 def __init__(self, install_dir, build, *args, **kwargs):
581 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
582 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
583 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
584 install_subdir=install_subdir,
585 is_regex_name=True, **kwargs)
586
587
588chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
589 DeltaPayloadMtoN]
590
591
592_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
593 devserver_constants.STATEFUL_FILE)
594_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
595 optional_name=BASE_IMAGE_FILE,
596 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
597_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
598 optional_name=RECOVERY_IMAGE_FILE,
599 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
600_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
601 files_to_extract=[devserver_constants.IMAGE_FILE])
602_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
603 optional_name=TEST_IMAGE_FILE,
604 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
605_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
606 files_to_extract=None, exclude=['autotest/test_suites'])
607_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
608 CONTROL_FILES_FILE)
609_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
610 AUTOTEST_PACKAGES_FILE)
611_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
612_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
613_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
614 AUTOTEST_SERVER_PACKAGE_FILE)
615_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
616_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
617 files_to_extract=['debug/breakpad'])
618_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
619 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800620
Chris Sosa968a1062013-08-02 17:42:50 -0700621# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700622for c in devserver_constants.CHANNELS:
623 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
624 BundledArtifact,
625 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
626
627android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700628
Chris Sosa76e44b92013-01-31 12:11:38 -0800629
Gabe Black3b567202015-09-23 14:07:59 -0700630def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700631 """Add a data wrapper for android artifacts.
632
633 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700634 android_artifact_map.
635 """
636 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
637 android_artifact_map.setdefault(tag, []).append(artifact)
638
639
Dan Shiba4e00f2015-10-27 12:03:53 -0700640_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700641 ANDROID_IMAGE_ZIP, is_regex_name=True)
642_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
643 ANDROID_RADIO_IMAGE)
644_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
645 ANDROID_BOOTLOADER_IMAGE)
646_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
647_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
648 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800649_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
650 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800651_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
652 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
653_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
654 ANDROID_TEST_SUITES, is_regex_name=True)
655_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
656 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700657_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
658 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700659_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
660 BundledArtifact,
661 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
662 is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800663
Gabe Black3b567202015-09-23 14:07:59 -0700664
665class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800666 """A factory class that generates build artifacts from artifact names."""
667
Dan Shi6c2b2a22016-03-04 15:52:19 -0800668 def __init__(self, artifact_map, download_dir, artifacts, files, build,
669 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800670 """Initalizes the member variables for the factory.
671
672 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700673 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700674 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700675 artifacts: List of artifacts to stage. These artifacts must be
676 defined in artifact_info.py and have a mapping in the
677 ARTIFACT_IMPLEMENTATION_MAP.
678 files: List of files to stage. These files are just downloaded and staged
679 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800680 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800681 requested_to_optional_map: A map between an artifact X to a list of
682 artifacts Y. If X is requested, all items in Y should also get
683 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800684 """
Gabe Black3b567202015-09-23 14:07:59 -0700685 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700686 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700687 self.artifacts = artifacts
688 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800689 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800690 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800691
Chris Sosa6b0c6172013-08-05 17:01:33 -0700692 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700693 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700694
695 If is_artifact is true, then these names define artifacts that must exist in
696 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
697 basic BuildArtifacts.
698
Gilad Arnold950569b2013-08-27 14:38:01 -0700699 Args:
700 names: A sequence of artifact names.
701 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800702
Gilad Arnold950569b2013-08-27 14:38:01 -0700703 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700704 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800705
Gilad Arnold950569b2013-08-27 14:38:01 -0700706 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700707 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700708 """
Gabe Black3b567202015-09-23 14:07:59 -0700709 if is_artifact:
710 classes = itertools.chain(*(self.artifact_map[name] for name in names))
711 return list(cls(self.download_dir, self.build) for cls in classes)
712 else:
713 return list(Artifact(name, self.download_dir, self.build)
714 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800715
716 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700718
Gilad Arnold950569b2013-08-27 14:38:01 -0700719 Returns:
720 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800721
Gilad Arnold950569b2013-08-27 14:38:01 -0700722 Raises:
723 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700724 """
725 artifacts = []
726 if self.artifacts:
727 artifacts.extend(self._Artifacts(self.artifacts, True))
728 if self.files:
729 artifacts.extend(self._Artifacts(self.files, False))
730
731 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800732
733 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700734 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700735
Gilad Arnold950569b2013-08-27 14:38:01 -0700736 Returns:
737 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800738
Gilad Arnold950569b2013-08-27 14:38:01 -0700739 Raises:
740 KeyError: if an optional artifact doesn't exist in
741 ARTIFACT_IMPLEMENTATION_MAP yet defined in
742 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700743 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800744 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800745 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800746 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700747 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800748 optional_names = optional_names.union(optional_list)
749
Chris Sosa6b0c6172013-08-05 17:01:33 -0700750 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700751
752
Gabe Black3b567202015-09-23 14:07:59 -0700753class ChromeOSArtifactFactory(BaseArtifactFactory):
754 """A factory class that generates ChromeOS build artifacts from names."""
755
756 def __init__(self, download_dir, artifacts, files, build):
757 """Pass the ChromeOS artifact map to the base class."""
758 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800759 chromeos_artifact_map, download_dir, artifacts, files, build,
760 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700761
762
763class AndroidArtifactFactory(BaseArtifactFactory):
764 """A factory class that generates Android build artifacts from names."""
765
766 def __init__(self, download_dir, artifacts, files, build):
767 """Pass the Android artifact map to the base class."""
768 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800769 android_artifact_map, download_dir, artifacts, files, build,
770 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700771
772
Chris Sosa968a1062013-08-02 17:42:50 -0700773# A simple main to verify correctness of the artifact map when making simple
774# name changes.
775if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700776 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
777 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
778 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
779 ('ANDROID', android_artifact_map)):
780 print('%s:' % label)
781 for key, value in sorted(mapping.items()):
782 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))