blob: 81c25237157b33be271fd82dc1d5cf18279e7ffe [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'
Chris Sosa76e44b92013-01-31 12:11:38 -080060
61_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070062
63
64class ArtifactDownloadError(Exception):
65 """Error used to signify an issue processing an artifact."""
66 pass
67
68
Gabe Black3b567202015-09-23 14:07:59 -070069class ArtifactMeta(type):
70 """metaclass for an artifact type.
71
72 This metaclass is for class Artifact and its subclasses to have a meaningful
73 string composed of class name and the corresponding artifact name, e.g.,
74 `Artifact_full_payload`. This helps to better logging, refer to logging in
75 method Downloader.Download.
76 """
77
78 ARTIFACT_NAME = None
79
80 def __str__(cls):
81 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
82
83 def __repr__(cls):
84 return str(cls)
85
86
87class Artifact(log_util.Loggable):
88 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070089
90 The purpose of this class is to download objects from Google Storage
91 and install them to a local directory. There are two main functions, one to
92 download/prepare the artifacts in to a temporary staging area and the second
93 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080094
Gilad Arnold950569b2013-08-27 14:38:01 -070095 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
96 attentive when adding new artifacts; (ii) name matching semantics differ
97 between a glob (full name string match) and a regex (partial match).
98
Chris Sosa76e44b92013-01-31 12:11:38 -080099 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700100 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700101 name: Name given for artifact; in fact, it is a pattern that captures the
102 names of files contained in the artifact. This can either be an
103 ordinary shell-style glob (the default), or a regular expression (if
104 is_regex_name is True).
105 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800106 build: The version of the build i.e. R26-2342.0.0.
107 marker_name: Name used to define the lock marker for the artifacts to
108 prevent it from being re-downloaded. By default based on name
109 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700110 exception_file_path: Path to a file containing the serialized exception,
111 which was raised in Process method. The file is located
112 in the parent folder of install_dir, since the
113 install_dir will be deleted if the build does not
114 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700115 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700116 install_subdir: Directory within install_path where the artifact is actually
117 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800118 install_dir: The final location where the artifact should be staged to.
119 single_name: If True the name given should only match one item. Note, if not
120 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800121 installed_files: A list of files that were the final result of downloading
122 and setting up the artifact.
123 store_installed_files: Whether the list of installed files is stored in the
124 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700125 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700126
Gabe Black3b567202015-09-23 14:07:59 -0700127 __metaclass__ = ArtifactMeta
128
129 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800130 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700131 """Constructor.
132
133 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800134 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800135 name: Identifying name to be used to find/store the artifact.
136 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700137 install_subdir: Directory within install_path where the artifact is
138 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700139 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800140 optional_name: An alternative name to find the artifact, which can lead
141 to faster download. Unlike |name|, there is no guarantee that an
142 artifact named |optional_name| is/will be on Google Storage. If it
143 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700144 """
Gabe Black3b567202015-09-23 14:07:59 -0700145 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700146
Chris Sosa76e44b92013-01-31 12:11:38 -0800147 # In-memory lock to keep the devserver from colliding with itself while
148 # attempting to stage the same artifact.
149 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700150
Chris Sosa76e44b92013-01-31 12:11:38 -0800151 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800152 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700153 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800154 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700155
Chris Sosa76e44b92013-01-31 12:11:38 -0800156 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157
Dan Shi6e50c722013-08-19 15:05:06 -0700158 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
159 '.exception')
160 # The exception file needs to be located in parent folder, since the
161 # install_dir will be deleted is the build does not exist.
162 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700163 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700164
joychen0a8e34e2013-06-24 17:58:36 -0700165 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700166
Chris Sosa76e44b92013-01-31 12:11:38 -0800167 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700168 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800169
170 self.single_name = True
171
Gilad Arnold1638d822013-11-07 23:38:16 -0800172 self.installed_files = []
173 self.store_installed_files = True
174
Chris Sosa76e44b92013-01-31 12:11:38 -0800175 @staticmethod
176 def _SanitizeName(name):
177 """Sanitizes name to be used for creating a file on the filesystem.
178
179 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700180
181 Args:
182 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800183
Gilad Arnold950569b2013-08-27 14:38:01 -0700184 Returns:
185 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800186 """
187 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
188
Dan Shif8eb0d12013-08-01 17:52:06 -0700189 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800190 """Returns True if artifact is already staged.
191
192 This checks for (1) presence of the artifact marker file, and (2) the
193 presence of each installed file listed in this marker. Both must hold for
194 the artifact to be considered staged. Note that this method is safe for use
195 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700196 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800197 files actually installed into the marker.
198 """
199 marker_file = os.path.join(self.install_dir, self.marker_name)
200
201 # If the marker is missing, it's definitely not staged.
202 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700203 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800204 return False
205
206 # We want to ensure that every file listed in the marker is actually there.
207 if self.store_installed_files:
208 with open(marker_file) as f:
209 files = [line.strip() for line in f]
210
211 # Check to see if any of the purportedly installed files are missing, in
212 # which case the marker is outdated and should be removed.
213 missing_files = [fname for fname in files if not os.path.exists(fname)]
214 if missing_files:
215 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
216 'All' if len(files) == len(missing_files) else 'Some',
217 marker_file, '\n'.join(missing_files))
218 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700219 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800220 return False
221
Aviv Keshet57d18172016-06-18 20:39:09 -0700222 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800223 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800224
225 def _MarkArtifactStaged(self):
226 """Marks the artifact as staged."""
227 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800228 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800229
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800230 def _UpdateName(self, names):
231 if self.single_name and len(names) > 1:
232 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800233
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800234 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800235
joychen0a8e34e2013-06-24 17:58:36 -0700236 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800237 """Process the downloaded content, update the list of installed files."""
238 # In this primitive case, what was downloaded (has to be a single file) is
239 # what's installed.
240 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700241
Dan Shi6e50c722013-08-19 15:05:06 -0700242 def _ClearException(self):
243 """Delete any existing exception saved for this artifact."""
244 if os.path.exists(self.exception_file_path):
245 os.remove(self.exception_file_path)
246
247 def _SaveException(self, e):
248 """Save the exception to a file for downloader.IsStaged to retrieve.
249
Gilad Arnold950569b2013-08-27 14:38:01 -0700250 Args:
251 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700252 """
253 with open(self.exception_file_path, 'w') as f:
254 pickle.dump(e, f)
255
256 def GetException(self):
257 """Retrieve any exception that was raised in Process method.
258
Gilad Arnold950569b2013-08-27 14:38:01 -0700259 Returns:
260 An Exception object that was raised when trying to process the artifact.
261 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700262 """
263 if not os.path.exists(self.exception_file_path):
264 return None
265 with open(self.exception_file_path, 'r') as f:
266 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800267
Gabe Black3b567202015-09-23 14:07:59 -0700268 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800269 """Main call point to all artifacts. Downloads and Stages artifact.
270
271 Downloads and Stages artifact from Google Storage to the install directory
272 specified in the constructor. It multi-thread safe and does not overwrite
273 the artifact if it's already been downloaded or being downloaded. After
274 processing, leaves behind a marker to indicate to future invocations that
275 the artifact has already been staged based on the name of the artifact.
276
277 Do not override as it modifies important private variables, ensures thread
278 safety, and maintains cache semantics.
279
280 Note: this may be a blocking call when the artifact is already in the
281 process of being staged.
282
283 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700284 downloader: A downloader instance containing the logic to download
285 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800286 no_wait: If True, don't block waiting for artifact to exist if we fail to
287 immediately find it.
288
289 Raises:
290 ArtifactDownloadError: If the artifact fails to download from Google
291 Storage for any reason or that the regexp
292 defined by name is not specific enough.
293 """
294 if not self._process_lock:
295 self._process_lock = _build_artifact_locks.lock(
296 os.path.join(self.install_dir, self.name))
297
Gabe Black3b567202015-09-23 14:07:59 -0700298 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800299 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700300 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700301 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800302 # Delete any existing exception saved for this artifact.
303 self._ClearException()
304 found_artifact = False
305 if self.optional_name:
306 try:
Gabe Black3b567202015-09-23 14:07:59 -0700307 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800308 # Because this artifact may not always exist, don't bother
309 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700310 new_names = downloader.Wait(
311 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800312 self._UpdateName(new_names)
313
314 except ArtifactDownloadError:
315 self._Log('Unable to download %s; fall back to download %s',
316 self.optional_name, self.name)
317 else:
318 found_artifact = True
319
Dan Shi6e50c722013-08-19 15:05:06 -0700320 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700321 # If the artifact should already have been uploaded, don't waste
322 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800323 if not found_artifact:
324 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700325 new_names = downloader.Wait(
326 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800327 self._UpdateName(new_names)
328
329 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700330 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700331 self._Setup()
332 self._MarkArtifactStaged()
333 except Exception as e:
334 # Save the exception to a file for downloader.IsStaged to retrieve.
335 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800336
337 # Convert an unknown exception into an ArtifactDownloadError.
338 if type(e) is ArtifactDownloadError:
339 raise
340 else:
341 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800342 else:
343 self._Log('%s is already staged.', self)
344
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700345 def __str__(self):
346 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700347 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700348
Chris Sosab26b1202013-08-16 16:40:55 -0700349 def __repr__(self):
350 return str(self)
351
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700352
Gabe Black3b567202015-09-23 14:07:59 -0700353class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700354 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700355
joychen0a8e34e2013-06-24 17:58:36 -0700356 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700357 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700358
Chris Sosa76e44b92013-01-31 12:11:38 -0800359 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700360 install_path = os.path.join(self.install_dir, self.install_subdir,
361 self.name)
362 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700363 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800364 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700365
Gilad Arnold1638d822013-11-07 23:38:16 -0800366 # Reflect the rename in the list of installed files.
367 self.installed_files.remove(install_path)
368 self.installed_files = [new_install_path]
369
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700370
Gabe Black3b567202015-09-23 14:07:59 -0700371class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800372 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700373
Gabe Black3b567202015-09-23 14:07:59 -0700374 These artifacts are super strange. They custom handle directories and
375 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800376 because unlike other artifacts, this one does not conform to something a
377 client might know. The client doesn't know the version of n-1 or whether it
378 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700379
380 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700381 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800382 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700383
joychen0a8e34e2013-06-24 17:58:36 -0700384 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700385 super(DeltaPayloadBase, self)._Setup()
386 # Setup symlink so that AU will work for this payload.
387 stateful_update_symlink = os.path.join(
388 self.install_dir, self.install_subdir,
389 devserver_constants.STATEFUL_FILE)
390 os.symlink(os.path.join(os.pardir, os.pardir,
391 devserver_constants.STATEFUL_FILE),
392 stateful_update_symlink)
393 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700394
Chris Sosa76e44b92013-01-31 12:11:38 -0800395
Gabe Black3b567202015-09-23 14:07:59 -0700396class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800397 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800398
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800399 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700400 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700401
402 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700403 *args: See Artifact documentation.
404 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700405 files_to_extract: A list of files to extract. If set to None, extract
406 all files.
407 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800408 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800409 self._files_to_extract = kwargs.pop('files_to_extract', None)
410 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700411 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800412
413 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800414 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800416 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800417
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800418 def _RunUnzip(self, list_only):
419 # Unzip is weird. It expects its args before any excludes and expects its
420 # excludes in a list following the -x.
421 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
422 if not list_only:
423 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800424
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800425 if self._files_to_extract:
426 cmd.extend(self._files_to_extract)
427
428 if self._exclude:
429 cmd.append('-x')
430 cmd.extend(self._exclude)
431
432 try:
433 return subprocess.check_output(cmd).strip('\n').splitlines()
434 except subprocess.CalledProcessError, e:
435 raise ArtifactDownloadError(
436 'An error occurred when attempting to unzip %s:\n%s' %
437 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800438
joychen0a8e34e2013-06-24 17:58:36 -0700439 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800440 extract_result = self._Extract()
441 if self.store_installed_files:
442 # List both the archive and the extracted files.
443 self.installed_files.append(self.install_path)
444 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800445
Chris Sosa76e44b92013-01-31 12:11:38 -0800446 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800447 """Extracts files into the install path."""
448 if self.name.endswith('.zip'):
449 return self._ExtractZipfile()
450 else:
451 return self._ExtractTarball()
452
453 def _ExtractZipfile(self):
454 """Extracts a zip file using unzip."""
455 file_list = [os.path.join(self.install_dir, line[30:].strip())
456 for line in self._RunUnzip(True)
457 if not line.endswith('/')]
458 if file_list:
459 self._RunUnzip(False)
460
461 return file_list
462
463 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800464 """Extracts a tarball using tar.
465
466 Detects whether the tarball is compressed or not based on the file
467 extension and extracts the tarball into the install_path.
468 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700469 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800470 return common_util.ExtractTarball(self.install_path, self.install_dir,
471 files_to_extract=self._files_to_extract,
472 excluded_files=self._exclude,
473 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800474 except common_util.CommonUtilError as e:
475 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700476
477
Gabe Black3b567202015-09-23 14:07:59 -0700478class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700479 """Wrapper around the autotest tarball to download from gsutil."""
480
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800481 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700482 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800483 # We don't store/check explicit file lists in Autotest tarball markers;
484 # this can get huge and unwieldy, and generally make little sense.
485 self.store_installed_files = False
486
joychen0a8e34e2013-06-24 17:58:36 -0700487 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800488 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700489 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700490
Chris Sosa76e44b92013-01-31 12:11:38 -0800491 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700492 autotest_dir = os.path.join(self.install_dir,
493 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700494 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
495 if not os.path.exists(autotest_pkgs_dir):
496 os.makedirs(autotest_pkgs_dir)
497
498 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800499 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
500 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700501 try:
joychen0a8e34e2013-06-24 17:58:36 -0700502 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700503 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800504 raise ArtifactDownloadError(
505 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700506 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700507 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700508
Chris Masone816e38c2012-05-02 12:22:36 -0700509
Gabe Black3b567202015-09-23 14:07:59 -0700510def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
511 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700512
Gabe Black3b567202015-09-23 14:07:59 -0700513 Args:
514 tag: Tag of the artifact, defined in artifact_info.
515 base: Class of the artifact, e.g., BundledArtifact.
516 name: Name of the artifact, e.g., image.zip.
517 *fixed_args: Fixed arguments that are additional to the one used in base
518 class.
519 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
520 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800521
Gabe Black3b567202015-09-23 14:07:59 -0700522 Returns:
523 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700524 """
Don Garrettfb15e322016-06-21 19:12:08 -0700525 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700526 class NewArtifact(base):
527 """A data wrapper that describes an artifact's implementation."""
528 ARTIFACT_TAG = tag
529 ARTIFACT_NAME = name
530
531 def __init__(self, *args, **kwargs):
532 all_args = fixed_args + args
533 all_kwargs = {}
534 all_kwargs.update(fixed_kwargs)
535 all_kwargs.update(kwargs)
536 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
537 *all_args, **all_kwargs)
538
539 NewArtifact.__name__ = base.__name__
540 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700541
Chris Sosa76e44b92013-01-31 12:11:38 -0800542
Gabe Black3b567202015-09-23 14:07:59 -0700543# TODO(dshi): Refactor the code here to split out the logic of creating the
544# artifacts mapping to a different module.
545chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800546
Chris Sosa76e44b92013-01-31 12:11:38 -0800547
Gabe Black3b567202015-09-23 14:07:59 -0700548def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700549 """Add a data wrapper for ChromeOS artifacts.
550
551 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700552 chromeos_artifact_map.
553 """
554 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
555 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800556
beepsc3d0f872013-07-31 21:50:40 -0700557
Gabe Black3b567202015-09-23 14:07:59 -0700558_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
559
560
561class DeltaPayloadNtoN(DeltaPayloadBase):
562 """ChromeOS Delta payload artifact for updating from version N to N."""
563 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
564 ARTIFACT_NAME = 'NOT_APPLICABLE'
565
566 def __init__(self, install_dir, build, *args, **kwargs):
567 name = 'chromeos_%s*_delta_*' % build
568 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
569 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
570 install_subdir=install_subdir,
571 **kwargs)
572
573
574class DeltaPayloadMtoN(DeltaPayloadBase):
575 """ChromeOS Delta payload artifact for updating from version M to N."""
576 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
577 ARTIFACT_NAME = 'NOT_APPLICABLE'
578
579 def __init__(self, install_dir, build, *args, **kwargs):
580 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
581 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
582 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
583 install_subdir=install_subdir,
584 is_regex_name=True, **kwargs)
585
586
587chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
588 DeltaPayloadMtoN]
589
590
591_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
592 devserver_constants.STATEFUL_FILE)
593_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
594 optional_name=BASE_IMAGE_FILE,
595 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
596_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
597 optional_name=RECOVERY_IMAGE_FILE,
598 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
599_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
600 files_to_extract=[devserver_constants.IMAGE_FILE])
601_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
602 optional_name=TEST_IMAGE_FILE,
603 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
604_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
605 files_to_extract=None, exclude=['autotest/test_suites'])
606_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
607 CONTROL_FILES_FILE)
608_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
609 AUTOTEST_PACKAGES_FILE)
610_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
611_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
612_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
613 AUTOTEST_SERVER_PACKAGE_FILE)
614_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
615_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
616 files_to_extract=['debug/breakpad'])
617_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
618 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800619
Chris Sosa968a1062013-08-02 17:42:50 -0700620# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700621for c in devserver_constants.CHANNELS:
622 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
623 BundledArtifact,
624 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
625
626android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700627
Chris Sosa76e44b92013-01-31 12:11:38 -0800628
Gabe Black3b567202015-09-23 14:07:59 -0700629def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700630 """Add a data wrapper for android artifacts.
631
632 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700633 android_artifact_map.
634 """
635 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
636 android_artifact_map.setdefault(tag, []).append(artifact)
637
638
Dan Shiba4e00f2015-10-27 12:03:53 -0700639_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700640 ANDROID_IMAGE_ZIP, is_regex_name=True)
641_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
642 ANDROID_RADIO_IMAGE)
643_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
644 ANDROID_BOOTLOADER_IMAGE)
645_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
646_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
647 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800648_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
649 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800650_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
651 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
652_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
653 ANDROID_TEST_SUITES, is_regex_name=True)
654_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
655 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700656_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
657 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800658
Gabe Black3b567202015-09-23 14:07:59 -0700659
660class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800661 """A factory class that generates build artifacts from artifact names."""
662
Dan Shi6c2b2a22016-03-04 15:52:19 -0800663 def __init__(self, artifact_map, download_dir, artifacts, files, build,
664 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800665 """Initalizes the member variables for the factory.
666
667 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700668 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700669 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700670 artifacts: List of artifacts to stage. These artifacts must be
671 defined in artifact_info.py and have a mapping in the
672 ARTIFACT_IMPLEMENTATION_MAP.
673 files: List of files to stage. These files are just downloaded and staged
674 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800675 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800676 requested_to_optional_map: A map between an artifact X to a list of
677 artifacts Y. If X is requested, all items in Y should also get
678 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800679 """
Gabe Black3b567202015-09-23 14:07:59 -0700680 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700681 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700682 self.artifacts = artifacts
683 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800684 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800685 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800686
Chris Sosa6b0c6172013-08-05 17:01:33 -0700687 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700688 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700689
690 If is_artifact is true, then these names define artifacts that must exist in
691 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
692 basic BuildArtifacts.
693
Gilad Arnold950569b2013-08-27 14:38:01 -0700694 Args:
695 names: A sequence of artifact names.
696 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800697
Gilad Arnold950569b2013-08-27 14:38:01 -0700698 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700699 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800700
Gilad Arnold950569b2013-08-27 14:38:01 -0700701 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700702 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700703 """
Gabe Black3b567202015-09-23 14:07:59 -0700704 if is_artifact:
705 classes = itertools.chain(*(self.artifact_map[name] for name in names))
706 return list(cls(self.download_dir, self.build) for cls in classes)
707 else:
708 return list(Artifact(name, self.download_dir, self.build)
709 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800710
711 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700712 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700713
Gilad Arnold950569b2013-08-27 14:38:01 -0700714 Returns:
715 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800716
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 Raises:
718 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700719 """
720 artifacts = []
721 if self.artifacts:
722 artifacts.extend(self._Artifacts(self.artifacts, True))
723 if self.files:
724 artifacts.extend(self._Artifacts(self.files, False))
725
726 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800727
728 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700729 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700730
Gilad Arnold950569b2013-08-27 14:38:01 -0700731 Returns:
732 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800733
Gilad Arnold950569b2013-08-27 14:38:01 -0700734 Raises:
735 KeyError: if an optional artifact doesn't exist in
736 ARTIFACT_IMPLEMENTATION_MAP yet defined in
737 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700738 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800739 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800740 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800741 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700742 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800743 optional_names = optional_names.union(optional_list)
744
Chris Sosa6b0c6172013-08-05 17:01:33 -0700745 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700746
747
Gabe Black3b567202015-09-23 14:07:59 -0700748class ChromeOSArtifactFactory(BaseArtifactFactory):
749 """A factory class that generates ChromeOS build artifacts from names."""
750
751 def __init__(self, download_dir, artifacts, files, build):
752 """Pass the ChromeOS artifact map to the base class."""
753 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800754 chromeos_artifact_map, download_dir, artifacts, files, build,
755 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700756
757
758class AndroidArtifactFactory(BaseArtifactFactory):
759 """A factory class that generates Android build artifacts from names."""
760
761 def __init__(self, download_dir, artifacts, files, build):
762 """Pass the Android artifact map to the base class."""
763 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800764 android_artifact_map, download_dir, artifacts, files, build,
765 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700766
767
Chris Sosa968a1062013-08-02 17:42:50 -0700768# A simple main to verify correctness of the artifact map when making simple
769# name changes.
770if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700771 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
772 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
773 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
774 ('ANDROID', android_artifact_map)):
775 print('%s:' % label)
776 for key, value in sorted(mapping.items()):
777 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))