blob: 586a7369e99463e2c7dbfef4d571a6cd9a577525 [file] [log] [blame]
Gabe Black3b567202015-09-23 14:07:59 -07001#!/usr/bin/python2
Chris Sosa968a1062013-08-02 17:42:50 -07002
Chris Sosa47a7d4e2012-03-28 11:26:55 -07003# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Module containing classes that wrap artifact downloads."""
8
Gabe Black3b567202015-09-23 14:07:59 -07009from __future__ import print_function
10
11import itertools
Chris Sosa47a7d4e2012-03-28 11:26:55 -070012import os
Dan Shi6e50c722013-08-19 15:05:06 -070013import pickle
Gilad Arnold950569b2013-08-27 14:38:01 -070014import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -070015import shutil
16import subprocess
17
Chris Sosa76e44b92013-01-31 12:11:38 -080018import artifact_info
19import common_util
joychen3cb228e2013-06-12 12:13:13 -070020import devserver_constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070021import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070022
Don Garrettfb15e322016-06-21 19:12:08 -070023# We do a number of things with args/kwargs arguments that confuse pylint.
24# pylint: disable=docstring-misnamed-args
Chris Sosa47a7d4e2012-03-28 11:26:55 -070025
Chris Sosa76e44b92013-01-31 12:11:38 -080026_AU_BASE = 'au'
27_NTON_DIR_SUFFIX = '_nton'
28_MTON_DIR_SUFFIX = '_mton'
29
30############ Actual filenames of artifacts in Google Storage ############
31
32AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070033PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080034AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070035CONTROL_FILES_FILE = 'control_files.tar'
36AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080037AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080038AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080039DEBUG_SYMBOLS_FILE = 'debug.tgz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080040FACTORY_FILE = 'ChromeOS-factory*.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080041FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
42IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080043TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080044BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
45TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
46RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
47
Gabe Black3b567202015-09-23 14:07:59 -070048############ Actual filenames of Android build artifacts ############
49
Dan Shiba4e00f2015-10-27 12:03:53 -070050ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070051ANDROID_RADIO_IMAGE = 'radio.img'
52ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
53ANDROID_FASTBOOT = 'fastboot'
54ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080055ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080056ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
57ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
58ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070059ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070060ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070061ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
62ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080063
64_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070065
66
67class ArtifactDownloadError(Exception):
68 """Error used to signify an issue processing an artifact."""
69 pass
70
71
Gabe Black3b567202015-09-23 14:07:59 -070072class ArtifactMeta(type):
73 """metaclass for an artifact type.
74
75 This metaclass is for class Artifact and its subclasses to have a meaningful
76 string composed of class name and the corresponding artifact name, e.g.,
77 `Artifact_full_payload`. This helps to better logging, refer to logging in
78 method Downloader.Download.
79 """
80
81 ARTIFACT_NAME = None
82
83 def __str__(cls):
84 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
85
86 def __repr__(cls):
87 return str(cls)
88
89
90class Artifact(log_util.Loggable):
91 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070092
93 The purpose of this class is to download objects from Google Storage
94 and install them to a local directory. There are two main functions, one to
95 download/prepare the artifacts in to a temporary staging area and the second
96 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080097
Gilad Arnold950569b2013-08-27 14:38:01 -070098 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
99 attentive when adding new artifacts; (ii) name matching semantics differ
100 between a glob (full name string match) and a regex (partial match).
101
Chris Sosa76e44b92013-01-31 12:11:38 -0800102 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700103 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700104 name: Name given for artifact; in fact, it is a pattern that captures the
105 names of files contained in the artifact. This can either be an
106 ordinary shell-style glob (the default), or a regular expression (if
107 is_regex_name is True).
108 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 build: The version of the build i.e. R26-2342.0.0.
110 marker_name: Name used to define the lock marker for the artifacts to
111 prevent it from being re-downloaded. By default based on name
112 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700113 exception_file_path: Path to a file containing the serialized exception,
114 which was raised in Process method. The file is located
115 in the parent folder of install_dir, since the
116 install_dir will be deleted if the build does not
117 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700118 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700119 install_subdir: Directory within install_path where the artifact is actually
120 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800121 install_dir: The final location where the artifact should be staged to.
122 single_name: If True the name given should only match one item. Note, if not
123 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800124 installed_files: A list of files that were the final result of downloading
125 and setting up the artifact.
126 store_installed_files: Whether the list of installed files is stored in the
127 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700128 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700129
Gabe Black3b567202015-09-23 14:07:59 -0700130 __metaclass__ = ArtifactMeta
131
132 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800133 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700134 """Constructor.
135
136 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800137 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800138 name: Identifying name to be used to find/store the artifact.
139 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700140 install_subdir: Directory within install_path where the artifact is
141 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700142 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800143 optional_name: An alternative name to find the artifact, which can lead
144 to faster download. Unlike |name|, there is no guarantee that an
145 artifact named |optional_name| is/will be on Google Storage. If it
146 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700147 """
Gabe Black3b567202015-09-23 14:07:59 -0700148 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700149
Chris Sosa76e44b92013-01-31 12:11:38 -0800150 # In-memory lock to keep the devserver from colliding with itself while
151 # attempting to stage the same artifact.
152 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700153
Chris Sosa76e44b92013-01-31 12:11:38 -0800154 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800155 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700156 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800157 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700158
Chris Sosa76e44b92013-01-31 12:11:38 -0800159 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700160
Dan Shi6e50c722013-08-19 15:05:06 -0700161 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
162 '.exception')
163 # The exception file needs to be located in parent folder, since the
164 # install_dir will be deleted is the build does not exist.
165 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700166 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700167
joychen0a8e34e2013-06-24 17:58:36 -0700168 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700169
Chris Sosa76e44b92013-01-31 12:11:38 -0800170 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700171 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800172
173 self.single_name = True
174
Gilad Arnold1638d822013-11-07 23:38:16 -0800175 self.installed_files = []
176 self.store_installed_files = True
177
Chris Sosa76e44b92013-01-31 12:11:38 -0800178 @staticmethod
179 def _SanitizeName(name):
180 """Sanitizes name to be used for creating a file on the filesystem.
181
182 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700183
184 Args:
185 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800186
Gilad Arnold950569b2013-08-27 14:38:01 -0700187 Returns:
188 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800189 """
190 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
191
Dan Shif8eb0d12013-08-01 17:52:06 -0700192 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800193 """Returns True if artifact is already staged.
194
195 This checks for (1) presence of the artifact marker file, and (2) the
196 presence of each installed file listed in this marker. Both must hold for
197 the artifact to be considered staged. Note that this method is safe for use
198 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700199 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800200 files actually installed into the marker.
201 """
202 marker_file = os.path.join(self.install_dir, self.marker_name)
203
204 # If the marker is missing, it's definitely not staged.
205 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700206 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800207 return False
208
209 # We want to ensure that every file listed in the marker is actually there.
210 if self.store_installed_files:
211 with open(marker_file) as f:
212 files = [line.strip() for line in f]
213
214 # Check to see if any of the purportedly installed files are missing, in
215 # which case the marker is outdated and should be removed.
216 missing_files = [fname for fname in files if not os.path.exists(fname)]
217 if missing_files:
218 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
219 'All' if len(files) == len(missing_files) else 'Some',
220 marker_file, '\n'.join(missing_files))
221 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700222 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800223 return False
224
Aviv Keshet57d18172016-06-18 20:39:09 -0700225 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800226 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800227
228 def _MarkArtifactStaged(self):
229 """Marks the artifact as staged."""
230 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800231 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800232
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800233 def _UpdateName(self, names):
234 if self.single_name and len(names) > 1:
235 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800236
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800237 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800238
joychen0a8e34e2013-06-24 17:58:36 -0700239 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800240 """Process the downloaded content, update the list of installed files."""
241 # In this primitive case, what was downloaded (has to be a single file) is
242 # what's installed.
243 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700244
Dan Shi6e50c722013-08-19 15:05:06 -0700245 def _ClearException(self):
246 """Delete any existing exception saved for this artifact."""
247 if os.path.exists(self.exception_file_path):
248 os.remove(self.exception_file_path)
249
250 def _SaveException(self, e):
251 """Save the exception to a file for downloader.IsStaged to retrieve.
252
Gilad Arnold950569b2013-08-27 14:38:01 -0700253 Args:
254 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700255 """
256 with open(self.exception_file_path, 'w') as f:
257 pickle.dump(e, f)
258
259 def GetException(self):
260 """Retrieve any exception that was raised in Process method.
261
Gilad Arnold950569b2013-08-27 14:38:01 -0700262 Returns:
263 An Exception object that was raised when trying to process the artifact.
264 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700265 """
266 if not os.path.exists(self.exception_file_path):
267 return None
268 with open(self.exception_file_path, 'r') as f:
269 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800270
Gabe Black3b567202015-09-23 14:07:59 -0700271 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800272 """Main call point to all artifacts. Downloads and Stages artifact.
273
274 Downloads and Stages artifact from Google Storage to the install directory
275 specified in the constructor. It multi-thread safe and does not overwrite
276 the artifact if it's already been downloaded or being downloaded. After
277 processing, leaves behind a marker to indicate to future invocations that
278 the artifact has already been staged based on the name of the artifact.
279
280 Do not override as it modifies important private variables, ensures thread
281 safety, and maintains cache semantics.
282
283 Note: this may be a blocking call when the artifact is already in the
284 process of being staged.
285
286 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700287 downloader: A downloader instance containing the logic to download
288 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800289 no_wait: If True, don't block waiting for artifact to exist if we fail to
290 immediately find it.
291
292 Raises:
293 ArtifactDownloadError: If the artifact fails to download from Google
294 Storage for any reason or that the regexp
295 defined by name is not specific enough.
296 """
297 if not self._process_lock:
298 self._process_lock = _build_artifact_locks.lock(
299 os.path.join(self.install_dir, self.name))
300
Gabe Black3b567202015-09-23 14:07:59 -0700301 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800302 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700303 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700304 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800305 # Delete any existing exception saved for this artifact.
306 self._ClearException()
307 found_artifact = False
308 if self.optional_name:
309 try:
Gabe Black3b567202015-09-23 14:07:59 -0700310 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800311 # Because this artifact may not always exist, don't bother
312 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700313 new_names = downloader.Wait(
314 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800315 self._UpdateName(new_names)
316
317 except ArtifactDownloadError:
318 self._Log('Unable to download %s; fall back to download %s',
319 self.optional_name, self.name)
320 else:
321 found_artifact = True
322
Dan Shi6e50c722013-08-19 15:05:06 -0700323 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700324 # If the artifact should already have been uploaded, don't waste
325 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800326 if not found_artifact:
327 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700328 new_names = downloader.Wait(
329 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800330 self._UpdateName(new_names)
331
332 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700333 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700334 self._Setup()
335 self._MarkArtifactStaged()
336 except Exception as e:
337 # Save the exception to a file for downloader.IsStaged to retrieve.
338 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800339
340 # Convert an unknown exception into an ArtifactDownloadError.
341 if type(e) is ArtifactDownloadError:
342 raise
343 else:
344 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800345 else:
346 self._Log('%s is already staged.', self)
347
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700348 def __str__(self):
349 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700350 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700351
Chris Sosab26b1202013-08-16 16:40:55 -0700352 def __repr__(self):
353 return str(self)
354
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700355
Gabe Black3b567202015-09-23 14:07:59 -0700356class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700357 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700358
joychen0a8e34e2013-06-24 17:58:36 -0700359 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700360 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700361
Chris Sosa76e44b92013-01-31 12:11:38 -0800362 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700363 install_path = os.path.join(self.install_dir, self.install_subdir,
364 self.name)
365 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700366 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800367 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700368
Gilad Arnold1638d822013-11-07 23:38:16 -0800369 # Reflect the rename in the list of installed files.
370 self.installed_files.remove(install_path)
371 self.installed_files = [new_install_path]
372
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700373
Gabe Black3b567202015-09-23 14:07:59 -0700374class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800375 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700376
Gabe Black3b567202015-09-23 14:07:59 -0700377 These artifacts are super strange. They custom handle directories and
378 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800379 because unlike other artifacts, this one does not conform to something a
380 client might know. The client doesn't know the version of n-1 or whether it
381 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700382
383 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700384 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800385 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700386
joychen0a8e34e2013-06-24 17:58:36 -0700387 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700388 super(DeltaPayloadBase, self)._Setup()
389 # Setup symlink so that AU will work for this payload.
390 stateful_update_symlink = os.path.join(
391 self.install_dir, self.install_subdir,
392 devserver_constants.STATEFUL_FILE)
393 os.symlink(os.path.join(os.pardir, os.pardir,
394 devserver_constants.STATEFUL_FILE),
395 stateful_update_symlink)
396 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700397
Chris Sosa76e44b92013-01-31 12:11:38 -0800398
Gabe Black3b567202015-09-23 14:07:59 -0700399class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800400 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800401
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800402 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700403 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700404
405 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700406 *args: See Artifact documentation.
407 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700408 files_to_extract: A list of files to extract. If set to None, extract
409 all files.
410 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800411 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800412 self._files_to_extract = kwargs.pop('files_to_extract', None)
413 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700414 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800415
416 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800417 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800418 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800419 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800420
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800421 def _RunUnzip(self, list_only):
422 # Unzip is weird. It expects its args before any excludes and expects its
423 # excludes in a list following the -x.
424 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
425 if not list_only:
426 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800427
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800428 if self._files_to_extract:
429 cmd.extend(self._files_to_extract)
430
431 if self._exclude:
432 cmd.append('-x')
433 cmd.extend(self._exclude)
434
435 try:
436 return subprocess.check_output(cmd).strip('\n').splitlines()
437 except subprocess.CalledProcessError, e:
438 raise ArtifactDownloadError(
439 'An error occurred when attempting to unzip %s:\n%s' %
440 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800441
joychen0a8e34e2013-06-24 17:58:36 -0700442 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800443 extract_result = self._Extract()
444 if self.store_installed_files:
445 # List both the archive and the extracted files.
446 self.installed_files.append(self.install_path)
447 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800448
Chris Sosa76e44b92013-01-31 12:11:38 -0800449 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800450 """Extracts files into the install path."""
451 if self.name.endswith('.zip'):
452 return self._ExtractZipfile()
453 else:
454 return self._ExtractTarball()
455
456 def _ExtractZipfile(self):
457 """Extracts a zip file using unzip."""
458 file_list = [os.path.join(self.install_dir, line[30:].strip())
459 for line in self._RunUnzip(True)
460 if not line.endswith('/')]
461 if file_list:
462 self._RunUnzip(False)
463
464 return file_list
465
466 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800467 """Extracts a tarball using tar.
468
469 Detects whether the tarball is compressed or not based on the file
470 extension and extracts the tarball into the install_path.
471 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700472 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800473 return common_util.ExtractTarball(self.install_path, self.install_dir,
474 files_to_extract=self._files_to_extract,
475 excluded_files=self._exclude,
476 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800477 except common_util.CommonUtilError as e:
478 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700479
480
Gabe Black3b567202015-09-23 14:07:59 -0700481class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700482 """Wrapper around the autotest tarball to download from gsutil."""
483
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800484 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700485 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800486 # We don't store/check explicit file lists in Autotest tarball markers;
487 # this can get huge and unwieldy, and generally make little sense.
488 self.store_installed_files = False
489
joychen0a8e34e2013-06-24 17:58:36 -0700490 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800491 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700492 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700493
Chris Sosa76e44b92013-01-31 12:11:38 -0800494 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700495 autotest_dir = os.path.join(self.install_dir,
496 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700497 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
498 if not os.path.exists(autotest_pkgs_dir):
499 os.makedirs(autotest_pkgs_dir)
500
501 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800502 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
503 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700504 try:
joychen0a8e34e2013-06-24 17:58:36 -0700505 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700506 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800507 raise ArtifactDownloadError(
508 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700509 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700510 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700511
Chris Masone816e38c2012-05-02 12:22:36 -0700512
Gabe Black3b567202015-09-23 14:07:59 -0700513def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
514 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700515
Gabe Black3b567202015-09-23 14:07:59 -0700516 Args:
517 tag: Tag of the artifact, defined in artifact_info.
518 base: Class of the artifact, e.g., BundledArtifact.
519 name: Name of the artifact, e.g., image.zip.
520 *fixed_args: Fixed arguments that are additional to the one used in base
521 class.
522 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
523 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800524
Gabe Black3b567202015-09-23 14:07:59 -0700525 Returns:
526 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700527 """
Don Garrettfb15e322016-06-21 19:12:08 -0700528 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700529 class NewArtifact(base):
530 """A data wrapper that describes an artifact's implementation."""
531 ARTIFACT_TAG = tag
532 ARTIFACT_NAME = name
533
534 def __init__(self, *args, **kwargs):
535 all_args = fixed_args + args
536 all_kwargs = {}
537 all_kwargs.update(fixed_kwargs)
538 all_kwargs.update(kwargs)
539 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
540 *all_args, **all_kwargs)
541
542 NewArtifact.__name__ = base.__name__
543 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700544
Chris Sosa76e44b92013-01-31 12:11:38 -0800545
Gabe Black3b567202015-09-23 14:07:59 -0700546# TODO(dshi): Refactor the code here to split out the logic of creating the
547# artifacts mapping to a different module.
548chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800549
Chris Sosa76e44b92013-01-31 12:11:38 -0800550
Gabe Black3b567202015-09-23 14:07:59 -0700551def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700552 """Add a data wrapper for ChromeOS artifacts.
553
554 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700555 chromeos_artifact_map.
556 """
557 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
558 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800559
beepsc3d0f872013-07-31 21:50:40 -0700560
Gabe Black3b567202015-09-23 14:07:59 -0700561_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
562
563
564class DeltaPayloadNtoN(DeltaPayloadBase):
565 """ChromeOS Delta payload artifact for updating from version N to N."""
566 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
567 ARTIFACT_NAME = 'NOT_APPLICABLE'
568
569 def __init__(self, install_dir, build, *args, **kwargs):
570 name = 'chromeos_%s*_delta_*' % build
571 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
572 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
573 install_subdir=install_subdir,
574 **kwargs)
575
576
577class DeltaPayloadMtoN(DeltaPayloadBase):
578 """ChromeOS Delta payload artifact for updating from version M to N."""
579 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
580 ARTIFACT_NAME = 'NOT_APPLICABLE'
581
582 def __init__(self, install_dir, build, *args, **kwargs):
583 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
584 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
585 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
586 install_subdir=install_subdir,
587 is_regex_name=True, **kwargs)
588
589
590chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
591 DeltaPayloadMtoN]
592
593
594_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
595 devserver_constants.STATEFUL_FILE)
596_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
597 optional_name=BASE_IMAGE_FILE,
598 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
599_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
600 optional_name=RECOVERY_IMAGE_FILE,
601 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
602_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
603 files_to_extract=[devserver_constants.IMAGE_FILE])
604_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
605 optional_name=TEST_IMAGE_FILE,
606 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
607_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
608 files_to_extract=None, exclude=['autotest/test_suites'])
609_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
610 CONTROL_FILES_FILE)
611_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
612 AUTOTEST_PACKAGES_FILE)
613_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
614_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
615_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
616 AUTOTEST_SERVER_PACKAGE_FILE)
617_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
618_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
619 files_to_extract=['debug/breakpad'])
620_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
621 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800622
Chris Sosa968a1062013-08-02 17:42:50 -0700623# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700624for c in devserver_constants.CHANNELS:
625 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
626 BundledArtifact,
627 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
628
629android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700630
Chris Sosa76e44b92013-01-31 12:11:38 -0800631
Gabe Black3b567202015-09-23 14:07:59 -0700632def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700633 """Add a data wrapper for android artifacts.
634
635 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700636 android_artifact_map.
637 """
638 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
639 android_artifact_map.setdefault(tag, []).append(artifact)
640
641
Dan Shiba4e00f2015-10-27 12:03:53 -0700642_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700643 ANDROID_IMAGE_ZIP, is_regex_name=True)
644_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
645 ANDROID_RADIO_IMAGE)
646_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
647 ANDROID_BOOTLOADER_IMAGE)
648_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
649_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
650 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800651_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
652 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800653_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
654 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
655_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
656 ANDROID_TEST_SUITES, is_regex_name=True)
657_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
658 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700659_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
660 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700661_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
662 BundledArtifact,
663 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
664 is_regex_name=True)
Justin Giorgi576c1542016-06-24 08:24:20 -0700665_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
666 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
667_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
668 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700669
670class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800671 """A factory class that generates build artifacts from artifact names."""
672
Dan Shi6c2b2a22016-03-04 15:52:19 -0800673 def __init__(self, artifact_map, download_dir, artifacts, files, build,
674 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800675 """Initalizes the member variables for the factory.
676
677 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700678 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700679 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700680 artifacts: List of artifacts to stage. These artifacts must be
681 defined in artifact_info.py and have a mapping in the
682 ARTIFACT_IMPLEMENTATION_MAP.
683 files: List of files to stage. These files are just downloaded and staged
684 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800685 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800686 requested_to_optional_map: A map between an artifact X to a list of
687 artifacts Y. If X is requested, all items in Y should also get
688 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800689 """
Gabe Black3b567202015-09-23 14:07:59 -0700690 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700691 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700692 self.artifacts = artifacts
693 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800694 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800695 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800696
Chris Sosa6b0c6172013-08-05 17:01:33 -0700697 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700698 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700699
700 If is_artifact is true, then these names define artifacts that must exist in
701 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
702 basic BuildArtifacts.
703
Gilad Arnold950569b2013-08-27 14:38:01 -0700704 Args:
705 names: A sequence of artifact names.
706 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800707
Gilad Arnold950569b2013-08-27 14:38:01 -0700708 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700709 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800710
Gilad Arnold950569b2013-08-27 14:38:01 -0700711 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700712 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700713 """
Gabe Black3b567202015-09-23 14:07:59 -0700714 if is_artifact:
715 classes = itertools.chain(*(self.artifact_map[name] for name in names))
716 return list(cls(self.download_dir, self.build) for cls in classes)
717 else:
718 return list(Artifact(name, self.download_dir, self.build)
719 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800720
721 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700722 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700723
Gilad Arnold950569b2013-08-27 14:38:01 -0700724 Returns:
725 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800726
Gilad Arnold950569b2013-08-27 14:38:01 -0700727 Raises:
728 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700729 """
730 artifacts = []
731 if self.artifacts:
732 artifacts.extend(self._Artifacts(self.artifacts, True))
733 if self.files:
734 artifacts.extend(self._Artifacts(self.files, False))
735
736 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800737
738 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700739 """Returns BuildArtifacts that should be cached.
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 an optional artifact doesn't exist in
746 ARTIFACT_IMPLEMENTATION_MAP yet defined in
747 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700748 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800749 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800750 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800751 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700752 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800753 optional_names = optional_names.union(optional_list)
754
Chris Sosa6b0c6172013-08-05 17:01:33 -0700755 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700756
757
Gabe Black3b567202015-09-23 14:07:59 -0700758class ChromeOSArtifactFactory(BaseArtifactFactory):
759 """A factory class that generates ChromeOS build artifacts from names."""
760
761 def __init__(self, download_dir, artifacts, files, build):
762 """Pass the ChromeOS artifact map to the base class."""
763 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800764 chromeos_artifact_map, download_dir, artifacts, files, build,
765 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700766
767
768class AndroidArtifactFactory(BaseArtifactFactory):
769 """A factory class that generates Android build artifacts from names."""
770
771 def __init__(self, download_dir, artifacts, files, build):
772 """Pass the Android artifact map to the base class."""
773 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800774 android_artifact_map, download_dir, artifacts, files, build,
775 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700776
777
Chris Sosa968a1062013-08-02 17:42:50 -0700778# A simple main to verify correctness of the artifact map when making simple
779# name changes.
780if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700781 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
782 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
783 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
784 ('ANDROID', android_artifact_map)):
785 print('%s:' % label)
786 for key, value in sorted(mapping.items()):
787 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))