blob: 78f1baa8934546c5cd27a3c5a8d023cb40acb3e2 [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
23
Chris Sosa76e44b92013-01-31 12:11:38 -080024_AU_BASE = 'au'
25_NTON_DIR_SUFFIX = '_nton'
26_MTON_DIR_SUFFIX = '_mton'
27
28############ Actual filenames of artifacts in Google Storage ############
29
30AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070031PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080032AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070033CONTROL_FILES_FILE = 'control_files.tar'
34AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080035AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080036AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080037DEBUG_SYMBOLS_FILE = 'debug.tgz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080038FACTORY_FILE = 'ChromeOS-factory*.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080039FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
40IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080041TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080042BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
43TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
44RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
45
Gabe Black3b567202015-09-23 14:07:59 -070046############ Actual filenames of Android build artifacts ############
47
Dan Shiba4e00f2015-10-27 12:03:53 -070048ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070049ANDROID_RADIO_IMAGE = 'radio.img'
50ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
51ANDROID_FASTBOOT = 'fastboot'
52ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080053ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080054ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
55ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
56ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070057ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080058
59_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070060
61
62class ArtifactDownloadError(Exception):
63 """Error used to signify an issue processing an artifact."""
64 pass
65
66
Gabe Black3b567202015-09-23 14:07:59 -070067class ArtifactMeta(type):
68 """metaclass for an artifact type.
69
70 This metaclass is for class Artifact and its subclasses to have a meaningful
71 string composed of class name and the corresponding artifact name, e.g.,
72 `Artifact_full_payload`. This helps to better logging, refer to logging in
73 method Downloader.Download.
74 """
75
76 ARTIFACT_NAME = None
77
78 def __str__(cls):
79 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
80
81 def __repr__(cls):
82 return str(cls)
83
84
85class Artifact(log_util.Loggable):
86 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070087
88 The purpose of this class is to download objects from Google Storage
89 and install them to a local directory. There are two main functions, one to
90 download/prepare the artifacts in to a temporary staging area and the second
91 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080092
Gilad Arnold950569b2013-08-27 14:38:01 -070093 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
94 attentive when adding new artifacts; (ii) name matching semantics differ
95 between a glob (full name string match) and a regex (partial match).
96
Chris Sosa76e44b92013-01-31 12:11:38 -080097 Class members:
Gabe Black3b567202015-09-23 14:07:59 -070098 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -070099 name: Name given for artifact; in fact, it is a pattern that captures the
100 names of files contained in the artifact. This can either be an
101 ordinary shell-style glob (the default), or a regular expression (if
102 is_regex_name is True).
103 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800104 build: The version of the build i.e. R26-2342.0.0.
105 marker_name: Name used to define the lock marker for the artifacts to
106 prevent it from being re-downloaded. By default based on name
107 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700108 exception_file_path: Path to a file containing the serialized exception,
109 which was raised in Process method. The file is located
110 in the parent folder of install_dir, since the
111 install_dir will be deleted if the build does not
112 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700113 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700114 install_subdir: Directory within install_path where the artifact is actually
115 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800116 install_dir: The final location where the artifact should be staged to.
117 single_name: If True the name given should only match one item. Note, if not
118 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800119 installed_files: A list of files that were the final result of downloading
120 and setting up the artifact.
121 store_installed_files: Whether the list of installed files is stored in the
122 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700123 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700124
Gabe Black3b567202015-09-23 14:07:59 -0700125 __metaclass__ = ArtifactMeta
126
127 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800128 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700129 """Constructor.
130
131 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800132 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800133 name: Identifying name to be used to find/store the artifact.
134 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700135 install_subdir: Directory within install_path where the artifact is
136 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700137 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800138 optional_name: An alternative name to find the artifact, which can lead
139 to faster download. Unlike |name|, there is no guarantee that an
140 artifact named |optional_name| is/will be on Google Storage. If it
141 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700142 """
Gabe Black3b567202015-09-23 14:07:59 -0700143 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700144
Chris Sosa76e44b92013-01-31 12:11:38 -0800145 # In-memory lock to keep the devserver from colliding with itself while
146 # attempting to stage the same artifact.
147 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700148
Chris Sosa76e44b92013-01-31 12:11:38 -0800149 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800150 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700151 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800152 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700153
Chris Sosa76e44b92013-01-31 12:11:38 -0800154 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700155
Dan Shi6e50c722013-08-19 15:05:06 -0700156 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
157 '.exception')
158 # The exception file needs to be located in parent folder, since the
159 # install_dir will be deleted is the build does not exist.
160 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700161 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700162
joychen0a8e34e2013-06-24 17:58:36 -0700163 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700164
Chris Sosa76e44b92013-01-31 12:11:38 -0800165 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700166 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800167
168 self.single_name = True
169
Gilad Arnold1638d822013-11-07 23:38:16 -0800170 self.installed_files = []
171 self.store_installed_files = True
172
Chris Sosa76e44b92013-01-31 12:11:38 -0800173 @staticmethod
174 def _SanitizeName(name):
175 """Sanitizes name to be used for creating a file on the filesystem.
176
177 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700178
179 Args:
180 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800181
Gilad Arnold950569b2013-08-27 14:38:01 -0700182 Returns:
183 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800184 """
185 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
186
Dan Shif8eb0d12013-08-01 17:52:06 -0700187 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800188 """Returns True if artifact is already staged.
189
190 This checks for (1) presence of the artifact marker file, and (2) the
191 presence of each installed file listed in this marker. Both must hold for
192 the artifact to be considered staged. Note that this method is safe for use
193 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700194 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800195 files actually installed into the marker.
196 """
197 marker_file = os.path.join(self.install_dir, self.marker_name)
198
199 # If the marker is missing, it's definitely not staged.
200 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700201 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800202 return False
203
204 # We want to ensure that every file listed in the marker is actually there.
205 if self.store_installed_files:
206 with open(marker_file) as f:
207 files = [line.strip() for line in f]
208
209 # Check to see if any of the purportedly installed files are missing, in
210 # which case the marker is outdated and should be removed.
211 missing_files = [fname for fname in files if not os.path.exists(fname)]
212 if missing_files:
213 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
214 'All' if len(files) == len(missing_files) else 'Some',
215 marker_file, '\n'.join(missing_files))
216 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700217 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800218 return False
219
Aviv Keshet57d18172016-06-18 20:39:09 -0700220 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800221 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800222
223 def _MarkArtifactStaged(self):
224 """Marks the artifact as staged."""
225 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800226 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800227
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800228 def _UpdateName(self, names):
229 if self.single_name and len(names) > 1:
230 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800231
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800232 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800233
joychen0a8e34e2013-06-24 17:58:36 -0700234 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800235 """Process the downloaded content, update the list of installed files."""
236 # In this primitive case, what was downloaded (has to be a single file) is
237 # what's installed.
238 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700239
Dan Shi6e50c722013-08-19 15:05:06 -0700240 def _ClearException(self):
241 """Delete any existing exception saved for this artifact."""
242 if os.path.exists(self.exception_file_path):
243 os.remove(self.exception_file_path)
244
245 def _SaveException(self, e):
246 """Save the exception to a file for downloader.IsStaged to retrieve.
247
Gilad Arnold950569b2013-08-27 14:38:01 -0700248 Args:
249 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700250 """
251 with open(self.exception_file_path, 'w') as f:
252 pickle.dump(e, f)
253
254 def GetException(self):
255 """Retrieve any exception that was raised in Process method.
256
Gilad Arnold950569b2013-08-27 14:38:01 -0700257 Returns:
258 An Exception object that was raised when trying to process the artifact.
259 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700260 """
261 if not os.path.exists(self.exception_file_path):
262 return None
263 with open(self.exception_file_path, 'r') as f:
264 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800265
Gabe Black3b567202015-09-23 14:07:59 -0700266 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800267 """Main call point to all artifacts. Downloads and Stages artifact.
268
269 Downloads and Stages artifact from Google Storage to the install directory
270 specified in the constructor. It multi-thread safe and does not overwrite
271 the artifact if it's already been downloaded or being downloaded. After
272 processing, leaves behind a marker to indicate to future invocations that
273 the artifact has already been staged based on the name of the artifact.
274
275 Do not override as it modifies important private variables, ensures thread
276 safety, and maintains cache semantics.
277
278 Note: this may be a blocking call when the artifact is already in the
279 process of being staged.
280
281 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700282 downloader: A downloader instance containing the logic to download
283 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800284 no_wait: If True, don't block waiting for artifact to exist if we fail to
285 immediately find it.
286
287 Raises:
288 ArtifactDownloadError: If the artifact fails to download from Google
289 Storage for any reason or that the regexp
290 defined by name is not specific enough.
291 """
292 if not self._process_lock:
293 self._process_lock = _build_artifact_locks.lock(
294 os.path.join(self.install_dir, self.name))
295
Gabe Black3b567202015-09-23 14:07:59 -0700296 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800297 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700298 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700299 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800300 # Delete any existing exception saved for this artifact.
301 self._ClearException()
302 found_artifact = False
303 if self.optional_name:
304 try:
Gabe Black3b567202015-09-23 14:07:59 -0700305 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800306 # Because this artifact may not always exist, don't bother
307 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700308 new_names = downloader.Wait(
309 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800310 self._UpdateName(new_names)
311
312 except ArtifactDownloadError:
313 self._Log('Unable to download %s; fall back to download %s',
314 self.optional_name, self.name)
315 else:
316 found_artifact = True
317
Dan Shi6e50c722013-08-19 15:05:06 -0700318 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700319 # If the artifact should already have been uploaded, don't waste
320 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800321 if not found_artifact:
322 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700323 new_names = downloader.Wait(
324 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800325 self._UpdateName(new_names)
326
327 self._Log('Downloading file %s', self.name)
Gabe Black3b567202015-09-23 14:07:59 -0700328 self.install_path = downloader.Fetch(self.name, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700329 self._Setup()
330 self._MarkArtifactStaged()
331 except Exception as e:
332 # Save the exception to a file for downloader.IsStaged to retrieve.
333 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800334
335 # Convert an unknown exception into an ArtifactDownloadError.
336 if type(e) is ArtifactDownloadError:
337 raise
338 else:
339 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800340 else:
341 self._Log('%s is already staged.', self)
342
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700343 def __str__(self):
344 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700345 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700346
Chris Sosab26b1202013-08-16 16:40:55 -0700347 def __repr__(self):
348 return str(self)
349
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700350
Gabe Black3b567202015-09-23 14:07:59 -0700351class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700352 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700353
joychen0a8e34e2013-06-24 17:58:36 -0700354 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700355 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700356
Chris Sosa76e44b92013-01-31 12:11:38 -0800357 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700358 install_path = os.path.join(self.install_dir, self.install_subdir,
359 self.name)
360 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700361 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800362 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700363
Gilad Arnold1638d822013-11-07 23:38:16 -0800364 # Reflect the rename in the list of installed files.
365 self.installed_files.remove(install_path)
366 self.installed_files = [new_install_path]
367
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700368
Gabe Black3b567202015-09-23 14:07:59 -0700369class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800370 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700371
Gabe Black3b567202015-09-23 14:07:59 -0700372 These artifacts are super strange. They custom handle directories and
373 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800374 because unlike other artifacts, this one does not conform to something a
375 client might know. The client doesn't know the version of n-1 or whether it
376 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700377
378 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700379 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700381
joychen0a8e34e2013-06-24 17:58:36 -0700382 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700383 super(DeltaPayloadBase, self)._Setup()
384 # Setup symlink so that AU will work for this payload.
385 stateful_update_symlink = os.path.join(
386 self.install_dir, self.install_subdir,
387 devserver_constants.STATEFUL_FILE)
388 os.symlink(os.path.join(os.pardir, os.pardir,
389 devserver_constants.STATEFUL_FILE),
390 stateful_update_symlink)
391 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700392
Chris Sosa76e44b92013-01-31 12:11:38 -0800393
Gabe Black3b567202015-09-23 14:07:59 -0700394class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800395 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800396
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800397 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700398 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700399
400 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700401 *args: See Artifact documentation.
402 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700403 files_to_extract: A list of files to extract. If set to None, extract
404 all files.
405 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800406 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800407 self._files_to_extract = kwargs.pop('files_to_extract', None)
408 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700409 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800410
411 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800412 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800413 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800414 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800415
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800416 def _RunUnzip(self, list_only):
417 # Unzip is weird. It expects its args before any excludes and expects its
418 # excludes in a list following the -x.
419 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
420 if not list_only:
421 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800422
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800423 if self._files_to_extract:
424 cmd.extend(self._files_to_extract)
425
426 if self._exclude:
427 cmd.append('-x')
428 cmd.extend(self._exclude)
429
430 try:
431 return subprocess.check_output(cmd).strip('\n').splitlines()
432 except subprocess.CalledProcessError, e:
433 raise ArtifactDownloadError(
434 'An error occurred when attempting to unzip %s:\n%s' %
435 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800436
joychen0a8e34e2013-06-24 17:58:36 -0700437 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800438 extract_result = self._Extract()
439 if self.store_installed_files:
440 # List both the archive and the extracted files.
441 self.installed_files.append(self.install_path)
442 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800443
Chris Sosa76e44b92013-01-31 12:11:38 -0800444 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800445 """Extracts files into the install path."""
446 if self.name.endswith('.zip'):
447 return self._ExtractZipfile()
448 else:
449 return self._ExtractTarball()
450
451 def _ExtractZipfile(self):
452 """Extracts a zip file using unzip."""
453 file_list = [os.path.join(self.install_dir, line[30:].strip())
454 for line in self._RunUnzip(True)
455 if not line.endswith('/')]
456 if file_list:
457 self._RunUnzip(False)
458
459 return file_list
460
461 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800462 """Extracts a tarball using tar.
463
464 Detects whether the tarball is compressed or not based on the file
465 extension and extracts the tarball into the install_path.
466 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700467 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800468 return common_util.ExtractTarball(self.install_path, self.install_dir,
469 files_to_extract=self._files_to_extract,
470 excluded_files=self._exclude,
471 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800472 except common_util.CommonUtilError as e:
473 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700474
475
Gabe Black3b567202015-09-23 14:07:59 -0700476class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700477 """Wrapper around the autotest tarball to download from gsutil."""
478
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800479 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700480 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800481 # We don't store/check explicit file lists in Autotest tarball markers;
482 # this can get huge and unwieldy, and generally make little sense.
483 self.store_installed_files = False
484
joychen0a8e34e2013-06-24 17:58:36 -0700485 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800486 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700487 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700488
Chris Sosa76e44b92013-01-31 12:11:38 -0800489 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700490 autotest_dir = os.path.join(self.install_dir,
491 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700492 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
493 if not os.path.exists(autotest_pkgs_dir):
494 os.makedirs(autotest_pkgs_dir)
495
496 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800497 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
498 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700499 try:
joychen0a8e34e2013-06-24 17:58:36 -0700500 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700501 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800502 raise ArtifactDownloadError(
503 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700504 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700505 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700506
Chris Masone816e38c2012-05-02 12:22:36 -0700507
Gabe Black3b567202015-09-23 14:07:59 -0700508def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
509 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700510
Gabe Black3b567202015-09-23 14:07:59 -0700511 Args:
512 tag: Tag of the artifact, defined in artifact_info.
513 base: Class of the artifact, e.g., BundledArtifact.
514 name: Name of the artifact, e.g., image.zip.
515 *fixed_args: Fixed arguments that are additional to the one used in base
516 class.
517 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
518 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800519
Gabe Black3b567202015-09-23 14:07:59 -0700520 Returns:
521 A data wrapper that describes an artifact's implementation.
Chris Sosa76e44b92013-01-31 12:11:38 -0800522
Gabe Black3b567202015-09-23 14:07:59 -0700523 """
524 class NewArtifact(base):
525 """A data wrapper that describes an artifact's implementation."""
526 ARTIFACT_TAG = tag
527 ARTIFACT_NAME = name
528
529 def __init__(self, *args, **kwargs):
530 all_args = fixed_args + args
531 all_kwargs = {}
532 all_kwargs.update(fixed_kwargs)
533 all_kwargs.update(kwargs)
534 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
535 *all_args, **all_kwargs)
536
537 NewArtifact.__name__ = base.__name__
538 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700539
Chris Sosa76e44b92013-01-31 12:11:38 -0800540
Gabe Black3b567202015-09-23 14:07:59 -0700541# TODO(dshi): Refactor the code here to split out the logic of creating the
542# artifacts mapping to a different module.
543chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800544
Chris Sosa76e44b92013-01-31 12:11:38 -0800545
Gabe Black3b567202015-09-23 14:07:59 -0700546def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
547 """Add a data wrapper that describes a ChromeOS artifact's implementation to
548 chromeos_artifact_map.
549 """
550 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
551 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800552
beepsc3d0f872013-07-31 21:50:40 -0700553
Gabe Black3b567202015-09-23 14:07:59 -0700554_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
555
556
557class DeltaPayloadNtoN(DeltaPayloadBase):
558 """ChromeOS Delta payload artifact for updating from version N to N."""
559 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
560 ARTIFACT_NAME = 'NOT_APPLICABLE'
561
562 def __init__(self, install_dir, build, *args, **kwargs):
563 name = 'chromeos_%s*_delta_*' % build
564 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
565 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
566 install_subdir=install_subdir,
567 **kwargs)
568
569
570class DeltaPayloadMtoN(DeltaPayloadBase):
571 """ChromeOS Delta payload artifact for updating from version M to N."""
572 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
573 ARTIFACT_NAME = 'NOT_APPLICABLE'
574
575 def __init__(self, install_dir, build, *args, **kwargs):
576 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
577 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
578 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
579 install_subdir=install_subdir,
580 is_regex_name=True, **kwargs)
581
582
583chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
584 DeltaPayloadMtoN]
585
586
587_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
588 devserver_constants.STATEFUL_FILE)
589_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
590 optional_name=BASE_IMAGE_FILE,
591 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
592_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
593 optional_name=RECOVERY_IMAGE_FILE,
594 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
595_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
596 files_to_extract=[devserver_constants.IMAGE_FILE])
597_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
598 optional_name=TEST_IMAGE_FILE,
599 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
600_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
601 files_to_extract=None, exclude=['autotest/test_suites'])
602_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
603 CONTROL_FILES_FILE)
604_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
605 AUTOTEST_PACKAGES_FILE)
606_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
607_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
608_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
609 AUTOTEST_SERVER_PACKAGE_FILE)
610_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
611_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
612 files_to_extract=['debug/breakpad'])
613_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
614 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800615
Chris Sosa968a1062013-08-02 17:42:50 -0700616# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700617for c in devserver_constants.CHANNELS:
618 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
619 BundledArtifact,
620 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
621
622android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700623
Chris Sosa76e44b92013-01-31 12:11:38 -0800624
Gabe Black3b567202015-09-23 14:07:59 -0700625def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
626 """Add a data wrapper that describes an Android artifact's implementation to
627 android_artifact_map.
628 """
629 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
630 android_artifact_map.setdefault(tag, []).append(artifact)
631
632
Dan Shiba4e00f2015-10-27 12:03:53 -0700633_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700634 ANDROID_IMAGE_ZIP, is_regex_name=True)
635_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
636 ANDROID_RADIO_IMAGE)
637_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
638 ANDROID_BOOTLOADER_IMAGE)
639_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
640_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
641 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800642_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
643 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800644_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
645 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
646_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
647 ANDROID_TEST_SUITES, is_regex_name=True)
648_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
649 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700650_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
651 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800652
Gabe Black3b567202015-09-23 14:07:59 -0700653
654class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800655 """A factory class that generates build artifacts from artifact names."""
656
Dan Shi6c2b2a22016-03-04 15:52:19 -0800657 def __init__(self, artifact_map, download_dir, artifacts, files, build,
658 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800659 """Initalizes the member variables for the factory.
660
661 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700662 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700663 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700664 artifacts: List of artifacts to stage. These artifacts must be
665 defined in artifact_info.py and have a mapping in the
666 ARTIFACT_IMPLEMENTATION_MAP.
667 files: List of files to stage. These files are just downloaded and staged
668 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800669 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800670 requested_to_optional_map: A map between an artifact X to a list of
671 artifacts Y. If X is requested, all items in Y should also get
672 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800673 """
Gabe Black3b567202015-09-23 14:07:59 -0700674 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700675 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700676 self.artifacts = artifacts
677 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800678 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800679 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800680
Chris Sosa6b0c6172013-08-05 17:01:33 -0700681 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700682 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700683
684 If is_artifact is true, then these names define artifacts that must exist in
685 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
686 basic BuildArtifacts.
687
Gilad Arnold950569b2013-08-27 14:38:01 -0700688 Args:
689 names: A sequence of artifact names.
690 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800691
Gilad Arnold950569b2013-08-27 14:38:01 -0700692 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700693 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800694
Gilad Arnold950569b2013-08-27 14:38:01 -0700695 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700696 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700697 """
Gabe Black3b567202015-09-23 14:07:59 -0700698 if is_artifact:
699 classes = itertools.chain(*(self.artifact_map[name] for name in names))
700 return list(cls(self.download_dir, self.build) for cls in classes)
701 else:
702 return list(Artifact(name, self.download_dir, self.build)
703 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800704
705 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700706 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700707
Gilad Arnold950569b2013-08-27 14:38:01 -0700708 Returns:
709 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800710
Gilad Arnold950569b2013-08-27 14:38:01 -0700711 Raises:
712 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700713 """
714 artifacts = []
715 if self.artifacts:
716 artifacts.extend(self._Artifacts(self.artifacts, True))
717 if self.files:
718 artifacts.extend(self._Artifacts(self.files, False))
719
720 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800721
722 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700723 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700724
Gilad Arnold950569b2013-08-27 14:38:01 -0700725 Returns:
726 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800727
Gilad Arnold950569b2013-08-27 14:38:01 -0700728 Raises:
729 KeyError: if an optional artifact doesn't exist in
730 ARTIFACT_IMPLEMENTATION_MAP yet defined in
731 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700732 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800733 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800734 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800735 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700736 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800737 optional_names = optional_names.union(optional_list)
738
Chris Sosa6b0c6172013-08-05 17:01:33 -0700739 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700740
741
Gabe Black3b567202015-09-23 14:07:59 -0700742class ChromeOSArtifactFactory(BaseArtifactFactory):
743 """A factory class that generates ChromeOS build artifacts from names."""
744
745 def __init__(self, download_dir, artifacts, files, build):
746 """Pass the ChromeOS artifact map to the base class."""
747 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800748 chromeos_artifact_map, download_dir, artifacts, files, build,
749 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700750
751
752class AndroidArtifactFactory(BaseArtifactFactory):
753 """A factory class that generates Android build artifacts from names."""
754
755 def __init__(self, download_dir, artifacts, files, build):
756 """Pass the Android artifact map to the base class."""
757 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800758 android_artifact_map, download_dir, artifacts, files, build,
759 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700760
761
Chris Sosa968a1062013-08-02 17:42:50 -0700762# A simple main to verify correctness of the artifact map when making simple
763# name changes.
764if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700765 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
766 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
767 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
768 ('ANDROID', android_artifact_map)):
769 print('%s:' % label)
770 for key, value in sorted(mapping.items()):
771 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))