blob: 6b5739c0a6eca8a87ba425a778ecbc5f6a655bcf [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
xixuan56252ff2017-03-09 15:40:31 -080017import traceback
Chris Sosa47a7d4e2012-03-28 11:26:55 -070018
Chris Sosa76e44b92013-01-31 12:11:38 -080019import artifact_info
20import common_util
joychen3cb228e2013-06-12 12:13:13 -070021import devserver_constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070022import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070023
Don Garrettfb15e322016-06-21 19:12:08 -070024# We do a number of things with args/kwargs arguments that confuse pylint.
25# pylint: disable=docstring-misnamed-args
Chris Sosa47a7d4e2012-03-28 11:26:55 -070026
Chris Sosa76e44b92013-01-31 12:11:38 -080027_AU_BASE = 'au'
28_NTON_DIR_SUFFIX = '_nton'
29_MTON_DIR_SUFFIX = '_mton'
30
31############ Actual filenames of artifacts in Google Storage ############
32
33AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070034PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080035AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070036CONTROL_FILES_FILE = 'control_files.tar'
37AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080038AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080039AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080040DEBUG_SYMBOLS_FILE = 'debug.tgz'
Dan Shi55d0f972016-10-04 11:45:00 -070041DEBUG_SYMBOLS_ONLY_FILE = 'debug_breakpad.tar.xz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080042FACTORY_FILE = 'ChromeOS-factory*.zip'
Mike Frysingera0e6a282016-09-01 17:29:08 -040043FACTORY_SHIM_FILE = 'factory_image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080044FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
45IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080046TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080047BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
48TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
49RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
Justin Giorgib7590522017-02-07 13:36:24 -080050LIBIOTA_TEST_BINARIES_FILE = 'test_binaries.tar.gz'
51LIBIOTA_BOARD_UTILS_FILE = 'board_utils.tar.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080052
Gabe Black3b567202015-09-23 14:07:59 -070053############ Actual filenames of Android build artifacts ############
54
Dan Shiba4e00f2015-10-27 12:03:53 -070055ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070056ANDROID_RADIO_IMAGE = 'radio.img'
57ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
58ANDROID_FASTBOOT = 'fastboot'
59ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080060ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080061ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
62ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
63ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070064ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070065ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070066ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
67 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070068ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070069ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
70ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080071
72_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070073
74
75class ArtifactDownloadError(Exception):
76 """Error used to signify an issue processing an artifact."""
77 pass
78
79
Gabe Black3b567202015-09-23 14:07:59 -070080class ArtifactMeta(type):
81 """metaclass for an artifact type.
82
83 This metaclass is for class Artifact and its subclasses to have a meaningful
84 string composed of class name and the corresponding artifact name, e.g.,
85 `Artifact_full_payload`. This helps to better logging, refer to logging in
86 method Downloader.Download.
87 """
88
89 ARTIFACT_NAME = None
90
91 def __str__(cls):
92 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
93
94 def __repr__(cls):
95 return str(cls)
96
97
98class Artifact(log_util.Loggable):
99 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700100
101 The purpose of this class is to download objects from Google Storage
102 and install them to a local directory. There are two main functions, one to
103 download/prepare the artifacts in to a temporary staging area and the second
104 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800105
Gilad Arnold950569b2013-08-27 14:38:01 -0700106 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
107 attentive when adding new artifacts; (ii) name matching semantics differ
108 between a glob (full name string match) and a regex (partial match).
109
Chris Sosa76e44b92013-01-31 12:11:38 -0800110 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700111 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700112 name: Name given for artifact; in fact, it is a pattern that captures the
113 names of files contained in the artifact. This can either be an
114 ordinary shell-style glob (the default), or a regular expression (if
115 is_regex_name is True).
116 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800117 build: The version of the build i.e. R26-2342.0.0.
118 marker_name: Name used to define the lock marker for the artifacts to
119 prevent it from being re-downloaded. By default based on name
120 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700121 exception_file_path: Path to a file containing the serialized exception,
122 which was raised in Process method. The file is located
123 in the parent folder of install_dir, since the
124 install_dir will be deleted if the build does not
125 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700126 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700127 install_subdir: Directory within install_path where the artifact is actually
128 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800129 install_dir: The final location where the artifact should be staged to.
130 single_name: If True the name given should only match one item. Note, if not
131 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800132 installed_files: A list of files that were the final result of downloading
133 and setting up the artifact.
134 store_installed_files: Whether the list of installed files is stored in the
135 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700136 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700137
Gabe Black3b567202015-09-23 14:07:59 -0700138 __metaclass__ = ArtifactMeta
139
140 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800141 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700142 """Constructor.
143
144 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800145 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800146 name: Identifying name to be used to find/store the artifact.
147 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700148 install_subdir: Directory within install_path where the artifact is
149 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700150 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800151 optional_name: An alternative name to find the artifact, which can lead
152 to faster download. Unlike |name|, there is no guarantee that an
153 artifact named |optional_name| is/will be on Google Storage. If it
154 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700155 """
Gabe Black3b567202015-09-23 14:07:59 -0700156 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157
Chris Sosa76e44b92013-01-31 12:11:38 -0800158 # In-memory lock to keep the devserver from colliding with itself while
159 # attempting to stage the same artifact.
160 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700161
Chris Sosa76e44b92013-01-31 12:11:38 -0800162 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800163 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700164 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800165 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700166
Chris Sosa76e44b92013-01-31 12:11:38 -0800167 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700168
Dan Shi6e50c722013-08-19 15:05:06 -0700169 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
170 '.exception')
171 # The exception file needs to be located in parent folder, since the
172 # install_dir will be deleted is the build does not exist.
173 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700174 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700175
joychen0a8e34e2013-06-24 17:58:36 -0700176 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700177
Chris Sosa76e44b92013-01-31 12:11:38 -0800178 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700179 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800180
181 self.single_name = True
182
Gilad Arnold1638d822013-11-07 23:38:16 -0800183 self.installed_files = []
184 self.store_installed_files = True
185
Chris Sosa76e44b92013-01-31 12:11:38 -0800186 @staticmethod
187 def _SanitizeName(name):
188 """Sanitizes name to be used for creating a file on the filesystem.
189
190 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700191
192 Args:
193 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800194
Gilad Arnold950569b2013-08-27 14:38:01 -0700195 Returns:
196 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800197 """
198 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
199
Dan Shif8eb0d12013-08-01 17:52:06 -0700200 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800201 """Returns True if artifact is already staged.
202
203 This checks for (1) presence of the artifact marker file, and (2) the
204 presence of each installed file listed in this marker. Both must hold for
205 the artifact to be considered staged. Note that this method is safe for use
206 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700207 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800208 files actually installed into the marker.
209 """
210 marker_file = os.path.join(self.install_dir, self.marker_name)
211
212 # If the marker is missing, it's definitely not staged.
213 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700214 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800215 return False
216
217 # We want to ensure that every file listed in the marker is actually there.
218 if self.store_installed_files:
219 with open(marker_file) as f:
220 files = [line.strip() for line in f]
221
222 # Check to see if any of the purportedly installed files are missing, in
223 # which case the marker is outdated and should be removed.
224 missing_files = [fname for fname in files if not os.path.exists(fname)]
225 if missing_files:
226 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
227 'All' if len(files) == len(missing_files) else 'Some',
228 marker_file, '\n'.join(missing_files))
229 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700230 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800231 return False
232
Aviv Keshet57d18172016-06-18 20:39:09 -0700233 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800234 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800235
236 def _MarkArtifactStaged(self):
237 """Marks the artifact as staged."""
238 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800239 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800240
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800241 def _UpdateName(self, names):
242 if self.single_name and len(names) > 1:
243 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800244
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800245 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800246
joychen0a8e34e2013-06-24 17:58:36 -0700247 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800248 """Process the downloaded content, update the list of installed files."""
249 # In this primitive case, what was downloaded (has to be a single file) is
250 # what's installed.
251 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700252
Dan Shi6e50c722013-08-19 15:05:06 -0700253 def _ClearException(self):
254 """Delete any existing exception saved for this artifact."""
255 if os.path.exists(self.exception_file_path):
256 os.remove(self.exception_file_path)
257
258 def _SaveException(self, e):
xixuan56252ff2017-03-09 15:40:31 -0800259 """Save the exception and traceback to a file for downloader.IsStaged.
Dan Shi6e50c722013-08-19 15:05:06 -0700260
Gilad Arnold950569b2013-08-27 14:38:01 -0700261 Args:
262 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700263 """
264 with open(self.exception_file_path, 'w') as f:
xixuan56252ff2017-03-09 15:40:31 -0800265 pickle.dump('%s\n%s' % (e, str(traceback.format_exc())), f)
Dan Shi6e50c722013-08-19 15:05:06 -0700266
267 def GetException(self):
268 """Retrieve any exception that was raised in Process method.
269
Gilad Arnold950569b2013-08-27 14:38:01 -0700270 Returns:
271 An Exception object that was raised when trying to process the artifact.
272 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700273 """
274 if not os.path.exists(self.exception_file_path):
275 return None
276 with open(self.exception_file_path, 'r') as f:
277 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800278
Gabe Black3b567202015-09-23 14:07:59 -0700279 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800280 """Main call point to all artifacts. Downloads and Stages artifact.
281
282 Downloads and Stages artifact from Google Storage to the install directory
283 specified in the constructor. It multi-thread safe and does not overwrite
284 the artifact if it's already been downloaded or being downloaded. After
285 processing, leaves behind a marker to indicate to future invocations that
286 the artifact has already been staged based on the name of the artifact.
287
288 Do not override as it modifies important private variables, ensures thread
289 safety, and maintains cache semantics.
290
291 Note: this may be a blocking call when the artifact is already in the
292 process of being staged.
293
294 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700295 downloader: A downloader instance containing the logic to download
296 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800297 no_wait: If True, don't block waiting for artifact to exist if we fail to
298 immediately find it.
299
300 Raises:
301 ArtifactDownloadError: If the artifact fails to download from Google
302 Storage for any reason or that the regexp
303 defined by name is not specific enough.
304 """
305 if not self._process_lock:
306 self._process_lock = _build_artifact_locks.lock(
307 os.path.join(self.install_dir, self.name))
308
Gabe Black3b567202015-09-23 14:07:59 -0700309 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800310 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700311 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700312 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800313 # Delete any existing exception saved for this artifact.
314 self._ClearException()
315 found_artifact = False
316 if self.optional_name:
317 try:
Gabe Black3b567202015-09-23 14:07:59 -0700318 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800319 # Because this artifact may not always exist, don't bother
320 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700321 new_names = downloader.Wait(
322 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800323 self._UpdateName(new_names)
324
325 except ArtifactDownloadError:
326 self._Log('Unable to download %s; fall back to download %s',
327 self.optional_name, self.name)
328 else:
329 found_artifact = True
330
Dan Shi6e50c722013-08-19 15:05:06 -0700331 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700332 # If the artifact should already have been uploaded, don't waste
333 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800334 if not found_artifact:
335 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700336 new_names = downloader.Wait(
337 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800338 self._UpdateName(new_names)
339
David Rileye131a0f2017-11-02 10:42:34 -0700340
341 files = self.name if isinstance(self.name, list) else [self.name]
342 for filename in files:
343 self._Log('Downloading file %s', filename)
344 self.install_path = downloader.Fetch(filename, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700345 self._Setup()
346 self._MarkArtifactStaged()
347 except Exception as e:
348 # Save the exception to a file for downloader.IsStaged to retrieve.
349 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800350
351 # Convert an unknown exception into an ArtifactDownloadError.
352 if type(e) is ArtifactDownloadError:
353 raise
354 else:
355 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800356 else:
357 self._Log('%s is already staged.', self)
358
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700359 def __str__(self):
360 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700361 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700362
Chris Sosab26b1202013-08-16 16:40:55 -0700363 def __repr__(self):
364 return str(self)
365
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700366
Gabe Black3b567202015-09-23 14:07:59 -0700367class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700368 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700369
joychen0a8e34e2013-06-24 17:58:36 -0700370 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700371 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700372
Chris Sosa76e44b92013-01-31 12:11:38 -0800373 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700374 install_path = os.path.join(self.install_dir, self.install_subdir,
375 self.name)
376 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700377 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800378 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700379
Gilad Arnold1638d822013-11-07 23:38:16 -0800380 # Reflect the rename in the list of installed files.
381 self.installed_files.remove(install_path)
382 self.installed_files = [new_install_path]
383
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700384
David Rileye131a0f2017-11-02 10:42:34 -0700385class MultiArtifact(Artifact):
386 """Wrapper for artifacts where name matches multiple items.."""
387
388 def __init__(self, *args, **kwargs):
389 """Takes Artifact args.
390
391 Args:
392 *args: See Artifact documentation.
393 **kwargs: See Artifact documentation.
394 """
395 super(MultiArtifact, self).__init__(*args, **kwargs)
396 self.single_name = False
397
398 def _UpdateName(self, names):
399 self.name = names if isinstance(names, list) else [names]
400
401 def _Setup(self):
402 super(MultiArtifact, self)._Setup()
403
404 self.installed_files = [os.path.join(self.install_dir, self.install_subdir,
405 name) for name in self.name]
406
407
Gabe Black3b567202015-09-23 14:07:59 -0700408class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800409 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700410
Gabe Black3b567202015-09-23 14:07:59 -0700411 These artifacts are super strange. They custom handle directories and
412 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800413 because unlike other artifacts, this one does not conform to something a
414 client might know. The client doesn't know the version of n-1 or whether it
415 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700416
417 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700418 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800419 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700420
joychen0a8e34e2013-06-24 17:58:36 -0700421 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700422 super(DeltaPayloadBase, self)._Setup()
423 # Setup symlink so that AU will work for this payload.
424 stateful_update_symlink = os.path.join(
425 self.install_dir, self.install_subdir,
426 devserver_constants.STATEFUL_FILE)
427 os.symlink(os.path.join(os.pardir, os.pardir,
428 devserver_constants.STATEFUL_FILE),
429 stateful_update_symlink)
430 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700431
Chris Sosa76e44b92013-01-31 12:11:38 -0800432
Gabe Black3b567202015-09-23 14:07:59 -0700433class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800434 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800435
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800436 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700437 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700438
439 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700440 *args: See Artifact documentation.
441 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700442 files_to_extract: A list of files to extract. If set to None, extract
443 all files.
444 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800445 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800446 self._files_to_extract = kwargs.pop('files_to_extract', None)
447 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700448 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800449
450 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800451 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800452 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800453 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800454
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800455 def _RunUnzip(self, list_only):
456 # Unzip is weird. It expects its args before any excludes and expects its
457 # excludes in a list following the -x.
458 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
459 if not list_only:
460 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800461
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800462 if self._files_to_extract:
463 cmd.extend(self._files_to_extract)
464
465 if self._exclude:
466 cmd.append('-x')
467 cmd.extend(self._exclude)
468
469 try:
470 return subprocess.check_output(cmd).strip('\n').splitlines()
471 except subprocess.CalledProcessError, e:
472 raise ArtifactDownloadError(
473 'An error occurred when attempting to unzip %s:\n%s' %
474 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800475
joychen0a8e34e2013-06-24 17:58:36 -0700476 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800477 extract_result = self._Extract()
478 if self.store_installed_files:
479 # List both the archive and the extracted files.
480 self.installed_files.append(self.install_path)
481 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800482
Chris Sosa76e44b92013-01-31 12:11:38 -0800483 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800484 """Extracts files into the install path."""
485 if self.name.endswith('.zip'):
486 return self._ExtractZipfile()
487 else:
488 return self._ExtractTarball()
489
490 def _ExtractZipfile(self):
491 """Extracts a zip file using unzip."""
492 file_list = [os.path.join(self.install_dir, line[30:].strip())
493 for line in self._RunUnzip(True)
494 if not line.endswith('/')]
495 if file_list:
496 self._RunUnzip(False)
497
498 return file_list
499
500 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800501 """Extracts a tarball using tar.
502
503 Detects whether the tarball is compressed or not based on the file
504 extension and extracts the tarball into the install_path.
505 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700506 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800507 return common_util.ExtractTarball(self.install_path, self.install_dir,
508 files_to_extract=self._files_to_extract,
509 excluded_files=self._exclude,
510 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800511 except common_util.CommonUtilError as e:
512 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700513
514
Gabe Black3b567202015-09-23 14:07:59 -0700515class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700516 """Wrapper around the autotest tarball to download from gsutil."""
517
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800518 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700519 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800520 # We don't store/check explicit file lists in Autotest tarball markers;
521 # this can get huge and unwieldy, and generally make little sense.
522 self.store_installed_files = False
523
joychen0a8e34e2013-06-24 17:58:36 -0700524 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800525 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700526 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700527
Chris Sosa76e44b92013-01-31 12:11:38 -0800528 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700529 autotest_dir = os.path.join(self.install_dir,
530 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700531 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
532 if not os.path.exists(autotest_pkgs_dir):
533 os.makedirs(autotest_pkgs_dir)
534
535 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Prashant Malani20e83712017-02-28 01:30:41 -0800536 cmd = ['autotest/utils/packager.py', '--action=upload', '--repository',
Chris Sosa76e44b92013-01-31 12:11:38 -0800537 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700538 try:
joychen0a8e34e2013-06-24 17:58:36 -0700539 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700540 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800541 raise ArtifactDownloadError(
542 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700543 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700544 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700545
Chris Masone816e38c2012-05-02 12:22:36 -0700546
Gabe Black3b567202015-09-23 14:07:59 -0700547def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
548 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700549
Gabe Black3b567202015-09-23 14:07:59 -0700550 Args:
551 tag: Tag of the artifact, defined in artifact_info.
552 base: Class of the artifact, e.g., BundledArtifact.
553 name: Name of the artifact, e.g., image.zip.
554 *fixed_args: Fixed arguments that are additional to the one used in base
555 class.
556 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
557 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800558
Gabe Black3b567202015-09-23 14:07:59 -0700559 Returns:
560 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700561 """
Don Garrettfb15e322016-06-21 19:12:08 -0700562 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700563 class NewArtifact(base):
564 """A data wrapper that describes an artifact's implementation."""
565 ARTIFACT_TAG = tag
566 ARTIFACT_NAME = name
567
568 def __init__(self, *args, **kwargs):
569 all_args = fixed_args + args
570 all_kwargs = {}
571 all_kwargs.update(fixed_kwargs)
572 all_kwargs.update(kwargs)
573 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
574 *all_args, **all_kwargs)
575
576 NewArtifact.__name__ = base.__name__
577 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700578
Chris Sosa76e44b92013-01-31 12:11:38 -0800579
Gabe Black3b567202015-09-23 14:07:59 -0700580# TODO(dshi): Refactor the code here to split out the logic of creating the
581# artifacts mapping to a different module.
582chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800583
Chris Sosa76e44b92013-01-31 12:11:38 -0800584
Gabe Black3b567202015-09-23 14:07:59 -0700585def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700586 """Add a data wrapper for ChromeOS artifacts.
587
588 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700589 chromeos_artifact_map.
590 """
591 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
592 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800593
beepsc3d0f872013-07-31 21:50:40 -0700594
Gabe Black3b567202015-09-23 14:07:59 -0700595_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
596
597
598class DeltaPayloadNtoN(DeltaPayloadBase):
599 """ChromeOS Delta payload artifact for updating from version N to N."""
600 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
601 ARTIFACT_NAME = 'NOT_APPLICABLE'
602
603 def __init__(self, install_dir, build, *args, **kwargs):
604 name = 'chromeos_%s*_delta_*' % build
605 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
606 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
607 install_subdir=install_subdir,
608 **kwargs)
609
610
611class DeltaPayloadMtoN(DeltaPayloadBase):
612 """ChromeOS Delta payload artifact for updating from version M to N."""
613 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
614 ARTIFACT_NAME = 'NOT_APPLICABLE'
615
616 def __init__(self, install_dir, build, *args, **kwargs):
617 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
618 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
619 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
620 install_subdir=install_subdir,
621 is_regex_name=True, **kwargs)
622
623
624chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
625 DeltaPayloadMtoN]
626
627
628_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
629 devserver_constants.STATEFUL_FILE)
630_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
631 optional_name=BASE_IMAGE_FILE,
632 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
633_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
634 optional_name=RECOVERY_IMAGE_FILE,
635 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
636_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
637 files_to_extract=[devserver_constants.IMAGE_FILE])
638_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
639 optional_name=TEST_IMAGE_FILE,
640 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
641_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
642 files_to_extract=None, exclude=['autotest/test_suites'])
643_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
644 CONTROL_FILES_FILE)
645_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
646 AUTOTEST_PACKAGES_FILE)
647_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
648_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
649_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
650 AUTOTEST_SERVER_PACKAGE_FILE)
651_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
652_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
653 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700654_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
655 DEBUG_SYMBOLS_ONLY_FILE,
656 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700657_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
658 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400659_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
660 FACTORY_SHIM_FILE,
661 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800662
Chris Sosa968a1062013-08-02 17:42:50 -0700663# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700664for c in devserver_constants.CHANNELS:
665 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
666 BundledArtifact,
667 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
668
Justin Giorgib7590522017-02-07 13:36:24 -0800669#### Libiota Artifacts ####
670_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
671 LIBIOTA_TEST_BINARIES_FILE)
672_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
673 LIBIOTA_BOARD_UTILS_FILE)
674
Gabe Black3b567202015-09-23 14:07:59 -0700675android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700676
Chris Sosa76e44b92013-01-31 12:11:38 -0800677
Gabe Black3b567202015-09-23 14:07:59 -0700678def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700679 """Add a data wrapper for android artifacts.
680
681 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700682 android_artifact_map.
683 """
684 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
685 android_artifact_map.setdefault(tag, []).append(artifact)
686
687
Dan Shiba4e00f2015-10-27 12:03:53 -0700688_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700689 ANDROID_IMAGE_ZIP, is_regex_name=True)
690_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
691 ANDROID_RADIO_IMAGE)
692_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
693 ANDROID_BOOTLOADER_IMAGE)
694_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
695_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
696 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800697_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
698 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800699_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
700 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
701_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
702 ANDROID_TEST_SUITES, is_regex_name=True)
703_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
704 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700705_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
706 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700707_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
708 BundledArtifact,
709 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
710 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700711_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
712 BundledArtifact,
713 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
714 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700715_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
716 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700717_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
718 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
719_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
720 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700721
722class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800723 """A factory class that generates build artifacts from artifact names."""
724
Dan Shi6c2b2a22016-03-04 15:52:19 -0800725 def __init__(self, artifact_map, download_dir, artifacts, files, build,
726 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800727 """Initalizes the member variables for the factory.
728
729 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700730 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700731 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700732 artifacts: List of artifacts to stage. These artifacts must be
733 defined in artifact_info.py and have a mapping in the
734 ARTIFACT_IMPLEMENTATION_MAP.
735 files: List of files to stage. These files are just downloaded and staged
736 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800737 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800738 requested_to_optional_map: A map between an artifact X to a list of
739 artifacts Y. If X is requested, all items in Y should also get
740 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800741 """
Gabe Black3b567202015-09-23 14:07:59 -0700742 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700743 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700744 self.artifacts = artifacts
745 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800746 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800747 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800748
Chris Sosa6b0c6172013-08-05 17:01:33 -0700749 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700750 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700751
752 If is_artifact is true, then these names define artifacts that must exist in
753 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
754 basic BuildArtifacts.
755
Gilad Arnold950569b2013-08-27 14:38:01 -0700756 Args:
757 names: A sequence of artifact names.
758 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800759
Gilad Arnold950569b2013-08-27 14:38:01 -0700760 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700761 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800762
Gilad Arnold950569b2013-08-27 14:38:01 -0700763 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700764 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700765 """
Gabe Black3b567202015-09-23 14:07:59 -0700766 if is_artifact:
767 classes = itertools.chain(*(self.artifact_map[name] for name in names))
768 return list(cls(self.download_dir, self.build) for cls in classes)
769 else:
770 return list(Artifact(name, self.download_dir, self.build)
771 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800772
773 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700774 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700775
Gilad Arnold950569b2013-08-27 14:38:01 -0700776 Returns:
777 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800778
Gilad Arnold950569b2013-08-27 14:38:01 -0700779 Raises:
780 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700781 """
782 artifacts = []
783 if self.artifacts:
784 artifacts.extend(self._Artifacts(self.artifacts, True))
785 if self.files:
786 artifacts.extend(self._Artifacts(self.files, False))
787
788 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800789
790 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700791 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700792
Gilad Arnold950569b2013-08-27 14:38:01 -0700793 Returns:
794 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800795
Gilad Arnold950569b2013-08-27 14:38:01 -0700796 Raises:
797 KeyError: if an optional artifact doesn't exist in
798 ARTIFACT_IMPLEMENTATION_MAP yet defined in
799 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700800 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800801 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800802 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800803 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700804 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800805 optional_names = optional_names.union(optional_list)
806
Chris Sosa6b0c6172013-08-05 17:01:33 -0700807 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700808
809
Gabe Black3b567202015-09-23 14:07:59 -0700810class ChromeOSArtifactFactory(BaseArtifactFactory):
811 """A factory class that generates ChromeOS build artifacts from names."""
812
813 def __init__(self, download_dir, artifacts, files, build):
814 """Pass the ChromeOS artifact map to the base class."""
815 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800816 chromeos_artifact_map, download_dir, artifacts, files, build,
817 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700818
819
820class AndroidArtifactFactory(BaseArtifactFactory):
821 """A factory class that generates Android build artifacts from names."""
822
823 def __init__(self, download_dir, artifacts, files, build):
824 """Pass the Android artifact map to the base class."""
825 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800826 android_artifact_map, download_dir, artifacts, files, build,
827 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700828
829
Chris Sosa968a1062013-08-02 17:42:50 -0700830# A simple main to verify correctness of the artifact map when making simple
831# name changes.
832if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700833 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
834 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
835 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
836 ('ANDROID', android_artifact_map)):
837 print('%s:' % label)
838 for key, value in sorted(mapping.items()):
839 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))