blob: 46cb4a88ffec67aa0c4b0b5daabf6b2c8f2e4977 [file] [log] [blame]
David Riley0655bab2017-11-02 10:44:26 -07001#!/usr/bin/env 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'
David Riley0655bab2017-11-02 10:44:26 -070052QUICK_PROVISION_FILE = 'full_dev_part_*.bin.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080053
Gabe Black3b567202015-09-23 14:07:59 -070054############ Actual filenames of Android build artifacts ############
55
Dan Shiba4e00f2015-10-27 12:03:53 -070056ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070057ANDROID_RADIO_IMAGE = 'radio.img'
58ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
59ANDROID_FASTBOOT = 'fastboot'
60ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080061ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080062ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
63ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
64ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070065ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070066ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070067ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
68 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070069ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070070ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
71ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080072
73_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070074
75
76class ArtifactDownloadError(Exception):
77 """Error used to signify an issue processing an artifact."""
78 pass
79
80
Gabe Black3b567202015-09-23 14:07:59 -070081class ArtifactMeta(type):
82 """metaclass for an artifact type.
83
84 This metaclass is for class Artifact and its subclasses to have a meaningful
85 string composed of class name and the corresponding artifact name, e.g.,
86 `Artifact_full_payload`. This helps to better logging, refer to logging in
87 method Downloader.Download.
88 """
89
90 ARTIFACT_NAME = None
91
92 def __str__(cls):
93 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
94
95 def __repr__(cls):
96 return str(cls)
97
98
99class Artifact(log_util.Loggable):
100 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700101
102 The purpose of this class is to download objects from Google Storage
103 and install them to a local directory. There are two main functions, one to
104 download/prepare the artifacts in to a temporary staging area and the second
105 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800106
Gilad Arnold950569b2013-08-27 14:38:01 -0700107 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
108 attentive when adding new artifacts; (ii) name matching semantics differ
109 between a glob (full name string match) and a regex (partial match).
110
Chris Sosa76e44b92013-01-31 12:11:38 -0800111 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700112 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700113 name: Name given for artifact; in fact, it is a pattern that captures the
114 names of files contained in the artifact. This can either be an
115 ordinary shell-style glob (the default), or a regular expression (if
116 is_regex_name is True).
117 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800118 build: The version of the build i.e. R26-2342.0.0.
119 marker_name: Name used to define the lock marker for the artifacts to
120 prevent it from being re-downloaded. By default based on name
121 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700122 exception_file_path: Path to a file containing the serialized exception,
123 which was raised in Process method. The file is located
124 in the parent folder of install_dir, since the
125 install_dir will be deleted if the build does not
126 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700127 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700128 install_subdir: Directory within install_path where the artifact is actually
129 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800130 install_dir: The final location where the artifact should be staged to.
131 single_name: If True the name given should only match one item. Note, if not
132 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800133 installed_files: A list of files that were the final result of downloading
134 and setting up the artifact.
135 store_installed_files: Whether the list of installed files is stored in the
136 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700137 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700138
Gabe Black3b567202015-09-23 14:07:59 -0700139 __metaclass__ = ArtifactMeta
140
141 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800142 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700143 """Constructor.
144
145 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800146 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800147 name: Identifying name to be used to find/store the artifact.
148 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700149 install_subdir: Directory within install_path where the artifact is
150 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700151 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800152 optional_name: An alternative name to find the artifact, which can lead
153 to faster download. Unlike |name|, there is no guarantee that an
154 artifact named |optional_name| is/will be on Google Storage. If it
155 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700156 """
Gabe Black3b567202015-09-23 14:07:59 -0700157 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700158
Chris Sosa76e44b92013-01-31 12:11:38 -0800159 # In-memory lock to keep the devserver from colliding with itself while
160 # attempting to stage the same artifact.
161 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162
Chris Sosa76e44b92013-01-31 12:11:38 -0800163 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800164 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700165 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800166 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700167
Chris Sosa76e44b92013-01-31 12:11:38 -0800168 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700169
Dan Shi6e50c722013-08-19 15:05:06 -0700170 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
171 '.exception')
172 # The exception file needs to be located in parent folder, since the
173 # install_dir will be deleted is the build does not exist.
174 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700175 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700176
joychen0a8e34e2013-06-24 17:58:36 -0700177 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700178
Chris Sosa76e44b92013-01-31 12:11:38 -0800179 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700180 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800181
182 self.single_name = True
183
Gilad Arnold1638d822013-11-07 23:38:16 -0800184 self.installed_files = []
185 self.store_installed_files = True
186
Chris Sosa76e44b92013-01-31 12:11:38 -0800187 @staticmethod
188 def _SanitizeName(name):
189 """Sanitizes name to be used for creating a file on the filesystem.
190
191 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700192
193 Args:
194 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800195
Gilad Arnold950569b2013-08-27 14:38:01 -0700196 Returns:
197 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800198 """
199 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
200
Dan Shif8eb0d12013-08-01 17:52:06 -0700201 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800202 """Returns True if artifact is already staged.
203
204 This checks for (1) presence of the artifact marker file, and (2) the
205 presence of each installed file listed in this marker. Both must hold for
206 the artifact to be considered staged. Note that this method is safe for use
207 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700208 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800209 files actually installed into the marker.
210 """
211 marker_file = os.path.join(self.install_dir, self.marker_name)
212
213 # If the marker is missing, it's definitely not staged.
214 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700215 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800216 return False
217
218 # We want to ensure that every file listed in the marker is actually there.
219 if self.store_installed_files:
220 with open(marker_file) as f:
221 files = [line.strip() for line in f]
222
223 # Check to see if any of the purportedly installed files are missing, in
224 # which case the marker is outdated and should be removed.
225 missing_files = [fname for fname in files if not os.path.exists(fname)]
226 if missing_files:
227 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
228 'All' if len(files) == len(missing_files) else 'Some',
229 marker_file, '\n'.join(missing_files))
230 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700231 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800232 return False
233
Aviv Keshet57d18172016-06-18 20:39:09 -0700234 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800235 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800236
237 def _MarkArtifactStaged(self):
238 """Marks the artifact as staged."""
239 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800240 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800241
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800242 def _UpdateName(self, names):
243 if self.single_name and len(names) > 1:
244 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800245
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800246 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800247
joychen0a8e34e2013-06-24 17:58:36 -0700248 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800249 """Process the downloaded content, update the list of installed files."""
250 # In this primitive case, what was downloaded (has to be a single file) is
251 # what's installed.
252 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700253
Dan Shi6e50c722013-08-19 15:05:06 -0700254 def _ClearException(self):
255 """Delete any existing exception saved for this artifact."""
256 if os.path.exists(self.exception_file_path):
257 os.remove(self.exception_file_path)
258
259 def _SaveException(self, e):
xixuan56252ff2017-03-09 15:40:31 -0800260 """Save the exception and traceback to a file for downloader.IsStaged.
Dan Shi6e50c722013-08-19 15:05:06 -0700261
Gilad Arnold950569b2013-08-27 14:38:01 -0700262 Args:
263 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700264 """
265 with open(self.exception_file_path, 'w') as f:
xixuan56252ff2017-03-09 15:40:31 -0800266 pickle.dump('%s\n%s' % (e, str(traceback.format_exc())), f)
Dan Shi6e50c722013-08-19 15:05:06 -0700267
268 def GetException(self):
269 """Retrieve any exception that was raised in Process method.
270
Gilad Arnold950569b2013-08-27 14:38:01 -0700271 Returns:
272 An Exception object that was raised when trying to process the artifact.
273 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700274 """
275 if not os.path.exists(self.exception_file_path):
276 return None
277 with open(self.exception_file_path, 'r') as f:
278 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800279
Gabe Black3b567202015-09-23 14:07:59 -0700280 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800281 """Main call point to all artifacts. Downloads and Stages artifact.
282
283 Downloads and Stages artifact from Google Storage to the install directory
284 specified in the constructor. It multi-thread safe and does not overwrite
285 the artifact if it's already been downloaded or being downloaded. After
286 processing, leaves behind a marker to indicate to future invocations that
287 the artifact has already been staged based on the name of the artifact.
288
289 Do not override as it modifies important private variables, ensures thread
290 safety, and maintains cache semantics.
291
292 Note: this may be a blocking call when the artifact is already in the
293 process of being staged.
294
295 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700296 downloader: A downloader instance containing the logic to download
297 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800298 no_wait: If True, don't block waiting for artifact to exist if we fail to
299 immediately find it.
300
301 Raises:
302 ArtifactDownloadError: If the artifact fails to download from Google
303 Storage for any reason or that the regexp
304 defined by name is not specific enough.
305 """
306 if not self._process_lock:
307 self._process_lock = _build_artifact_locks.lock(
308 os.path.join(self.install_dir, self.name))
309
Gabe Black3b567202015-09-23 14:07:59 -0700310 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800311 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700312 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700313 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800314 # Delete any existing exception saved for this artifact.
315 self._ClearException()
316 found_artifact = False
317 if self.optional_name:
318 try:
Gabe Black3b567202015-09-23 14:07:59 -0700319 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800320 # Because this artifact may not always exist, don't bother
321 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700322 new_names = downloader.Wait(
323 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800324 self._UpdateName(new_names)
325
326 except ArtifactDownloadError:
327 self._Log('Unable to download %s; fall back to download %s',
328 self.optional_name, self.name)
329 else:
330 found_artifact = True
331
Dan Shi6e50c722013-08-19 15:05:06 -0700332 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700333 # If the artifact should already have been uploaded, don't waste
334 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800335 if not found_artifact:
336 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700337 new_names = downloader.Wait(
338 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800339 self._UpdateName(new_names)
340
David Rileye131a0f2017-11-02 10:42:34 -0700341
342 files = self.name if isinstance(self.name, list) else [self.name]
343 for filename in files:
344 self._Log('Downloading file %s', filename)
345 self.install_path = downloader.Fetch(filename, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700346 self._Setup()
347 self._MarkArtifactStaged()
348 except Exception as e:
349 # Save the exception to a file for downloader.IsStaged to retrieve.
350 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800351
352 # Convert an unknown exception into an ArtifactDownloadError.
353 if type(e) is ArtifactDownloadError:
354 raise
355 else:
356 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800357 else:
358 self._Log('%s is already staged.', self)
359
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700360 def __str__(self):
361 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700362 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700363
Chris Sosab26b1202013-08-16 16:40:55 -0700364 def __repr__(self):
365 return str(self)
366
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700367
Gabe Black3b567202015-09-23 14:07:59 -0700368class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700369 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700370
joychen0a8e34e2013-06-24 17:58:36 -0700371 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700372 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700373
Chris Sosa76e44b92013-01-31 12:11:38 -0800374 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700375 install_path = os.path.join(self.install_dir, self.install_subdir,
376 self.name)
377 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700378 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800379 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700380
Gilad Arnold1638d822013-11-07 23:38:16 -0800381 # Reflect the rename in the list of installed files.
382 self.installed_files.remove(install_path)
383 self.installed_files = [new_install_path]
384
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700385
David Rileye131a0f2017-11-02 10:42:34 -0700386class MultiArtifact(Artifact):
387 """Wrapper for artifacts where name matches multiple items.."""
388
389 def __init__(self, *args, **kwargs):
390 """Takes Artifact args.
391
392 Args:
393 *args: See Artifact documentation.
394 **kwargs: See Artifact documentation.
395 """
396 super(MultiArtifact, self).__init__(*args, **kwargs)
397 self.single_name = False
398
399 def _UpdateName(self, names):
400 self.name = names if isinstance(names, list) else [names]
401
402 def _Setup(self):
403 super(MultiArtifact, self)._Setup()
404
405 self.installed_files = [os.path.join(self.install_dir, self.install_subdir,
406 name) for name in self.name]
407
408
Gabe Black3b567202015-09-23 14:07:59 -0700409class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800410 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700411
Gabe Black3b567202015-09-23 14:07:59 -0700412 These artifacts are super strange. They custom handle directories and
413 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800414 because unlike other artifacts, this one does not conform to something a
415 client might know. The client doesn't know the version of n-1 or whether it
416 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700417
418 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700419 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800420 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700421
joychen0a8e34e2013-06-24 17:58:36 -0700422 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700423 super(DeltaPayloadBase, self)._Setup()
424 # Setup symlink so that AU will work for this payload.
425 stateful_update_symlink = os.path.join(
426 self.install_dir, self.install_subdir,
427 devserver_constants.STATEFUL_FILE)
428 os.symlink(os.path.join(os.pardir, os.pardir,
429 devserver_constants.STATEFUL_FILE),
430 stateful_update_symlink)
431 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700432
Chris Sosa76e44b92013-01-31 12:11:38 -0800433
Gabe Black3b567202015-09-23 14:07:59 -0700434class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800435 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800436
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800437 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700438 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700439
440 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700441 *args: See Artifact documentation.
442 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700443 files_to_extract: A list of files to extract. If set to None, extract
444 all files.
445 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800446 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800447 self._files_to_extract = kwargs.pop('files_to_extract', None)
448 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700449 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800450
451 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800452 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800453 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800454 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800455
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800456 def _RunUnzip(self, list_only):
457 # Unzip is weird. It expects its args before any excludes and expects its
458 # excludes in a list following the -x.
459 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
460 if not list_only:
461 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800462
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800463 if self._files_to_extract:
464 cmd.extend(self._files_to_extract)
465
466 if self._exclude:
467 cmd.append('-x')
468 cmd.extend(self._exclude)
469
470 try:
471 return subprocess.check_output(cmd).strip('\n').splitlines()
472 except subprocess.CalledProcessError, e:
473 raise ArtifactDownloadError(
474 'An error occurred when attempting to unzip %s:\n%s' %
475 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800476
joychen0a8e34e2013-06-24 17:58:36 -0700477 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800478 extract_result = self._Extract()
479 if self.store_installed_files:
480 # List both the archive and the extracted files.
481 self.installed_files.append(self.install_path)
482 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800483
Chris Sosa76e44b92013-01-31 12:11:38 -0800484 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800485 """Extracts files into the install path."""
486 if self.name.endswith('.zip'):
487 return self._ExtractZipfile()
488 else:
489 return self._ExtractTarball()
490
491 def _ExtractZipfile(self):
492 """Extracts a zip file using unzip."""
493 file_list = [os.path.join(self.install_dir, line[30:].strip())
494 for line in self._RunUnzip(True)
495 if not line.endswith('/')]
496 if file_list:
497 self._RunUnzip(False)
498
499 return file_list
500
501 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800502 """Extracts a tarball using tar.
503
504 Detects whether the tarball is compressed or not based on the file
505 extension and extracts the tarball into the install_path.
506 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700507 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800508 return common_util.ExtractTarball(self.install_path, self.install_dir,
509 files_to_extract=self._files_to_extract,
510 excluded_files=self._exclude,
511 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800512 except common_util.CommonUtilError as e:
513 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700514
515
Gabe Black3b567202015-09-23 14:07:59 -0700516class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700517 """Wrapper around the autotest tarball to download from gsutil."""
518
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800519 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700520 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800521 # We don't store/check explicit file lists in Autotest tarball markers;
522 # this can get huge and unwieldy, and generally make little sense.
523 self.store_installed_files = False
524
joychen0a8e34e2013-06-24 17:58:36 -0700525 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800526 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700527 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700528
Chris Sosa76e44b92013-01-31 12:11:38 -0800529 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700530 autotest_dir = os.path.join(self.install_dir,
531 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700532 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
533 if not os.path.exists(autotest_pkgs_dir):
534 os.makedirs(autotest_pkgs_dir)
535
536 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Prashant Malani20e83712017-02-28 01:30:41 -0800537 cmd = ['autotest/utils/packager.py', '--action=upload', '--repository',
Chris Sosa76e44b92013-01-31 12:11:38 -0800538 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700539 try:
joychen0a8e34e2013-06-24 17:58:36 -0700540 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700541 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800542 raise ArtifactDownloadError(
543 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700544 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700545 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700546
Chris Masone816e38c2012-05-02 12:22:36 -0700547
Gabe Black3b567202015-09-23 14:07:59 -0700548def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
549 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700550
Gabe Black3b567202015-09-23 14:07:59 -0700551 Args:
552 tag: Tag of the artifact, defined in artifact_info.
553 base: Class of the artifact, e.g., BundledArtifact.
554 name: Name of the artifact, e.g., image.zip.
555 *fixed_args: Fixed arguments that are additional to the one used in base
556 class.
557 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
558 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800559
Gabe Black3b567202015-09-23 14:07:59 -0700560 Returns:
561 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700562 """
Don Garrettfb15e322016-06-21 19:12:08 -0700563 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700564 class NewArtifact(base):
565 """A data wrapper that describes an artifact's implementation."""
566 ARTIFACT_TAG = tag
567 ARTIFACT_NAME = name
568
569 def __init__(self, *args, **kwargs):
570 all_args = fixed_args + args
571 all_kwargs = {}
572 all_kwargs.update(fixed_kwargs)
573 all_kwargs.update(kwargs)
574 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
575 *all_args, **all_kwargs)
576
577 NewArtifact.__name__ = base.__name__
578 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700579
Chris Sosa76e44b92013-01-31 12:11:38 -0800580
Gabe Black3b567202015-09-23 14:07:59 -0700581# TODO(dshi): Refactor the code here to split out the logic of creating the
582# artifacts mapping to a different module.
583chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800584
Chris Sosa76e44b92013-01-31 12:11:38 -0800585
Gabe Black3b567202015-09-23 14:07:59 -0700586def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700587 """Add a data wrapper for ChromeOS artifacts.
588
589 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700590 chromeos_artifact_map.
591 """
592 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
593 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800594
beepsc3d0f872013-07-31 21:50:40 -0700595
Gabe Black3b567202015-09-23 14:07:59 -0700596_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
597
598
599class DeltaPayloadNtoN(DeltaPayloadBase):
600 """ChromeOS Delta payload artifact for updating from version N to N."""
601 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
602 ARTIFACT_NAME = 'NOT_APPLICABLE'
603
604 def __init__(self, install_dir, build, *args, **kwargs):
605 name = 'chromeos_%s*_delta_*' % build
606 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
607 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
608 install_subdir=install_subdir,
609 **kwargs)
610
611
612class DeltaPayloadMtoN(DeltaPayloadBase):
613 """ChromeOS Delta payload artifact for updating from version M to N."""
614 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
615 ARTIFACT_NAME = 'NOT_APPLICABLE'
616
617 def __init__(self, install_dir, build, *args, **kwargs):
618 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
619 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
620 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
621 install_subdir=install_subdir,
622 is_regex_name=True, **kwargs)
623
624
625chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
626 DeltaPayloadMtoN]
627
628
629_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
630 devserver_constants.STATEFUL_FILE)
631_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
632 optional_name=BASE_IMAGE_FILE,
633 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
634_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
635 optional_name=RECOVERY_IMAGE_FILE,
636 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
637_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
638 files_to_extract=[devserver_constants.IMAGE_FILE])
639_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
640 optional_name=TEST_IMAGE_FILE,
641 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
642_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
643 files_to_extract=None, exclude=['autotest/test_suites'])
644_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
645 CONTROL_FILES_FILE)
646_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
647 AUTOTEST_PACKAGES_FILE)
648_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
649_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
650_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
651 AUTOTEST_SERVER_PACKAGE_FILE)
652_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
653_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
654 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700655_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
656 DEBUG_SYMBOLS_ONLY_FILE,
657 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700658_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
659 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400660_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
661 FACTORY_SHIM_FILE,
662 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
David Riley0655bab2017-11-02 10:44:26 -0700663_AddCrOSArtifact(artifact_info.QUICK_PROVISION, MultiArtifact,
664 QUICK_PROVISION_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800665
Chris Sosa968a1062013-08-02 17:42:50 -0700666# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700667for c in devserver_constants.CHANNELS:
668 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
669 BundledArtifact,
670 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
671
Justin Giorgib7590522017-02-07 13:36:24 -0800672#### Libiota Artifacts ####
673_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
674 LIBIOTA_TEST_BINARIES_FILE)
675_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
676 LIBIOTA_BOARD_UTILS_FILE)
677
Gabe Black3b567202015-09-23 14:07:59 -0700678android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700679
Chris Sosa76e44b92013-01-31 12:11:38 -0800680
Gabe Black3b567202015-09-23 14:07:59 -0700681def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700682 """Add a data wrapper for android artifacts.
683
684 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700685 android_artifact_map.
686 """
687 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
688 android_artifact_map.setdefault(tag, []).append(artifact)
689
690
Dan Shiba4e00f2015-10-27 12:03:53 -0700691_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700692 ANDROID_IMAGE_ZIP, is_regex_name=True)
693_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
694 ANDROID_RADIO_IMAGE)
695_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
696 ANDROID_BOOTLOADER_IMAGE)
697_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
698_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
699 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800700_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
701 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800702_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
703 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
704_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
705 ANDROID_TEST_SUITES, is_regex_name=True)
706_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
707 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700708_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
709 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700710_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
711 BundledArtifact,
712 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
713 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700714_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
715 BundledArtifact,
716 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
717 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700718_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
719 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700720_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
721 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
722_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
723 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700724
725class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800726 """A factory class that generates build artifacts from artifact names."""
727
Dan Shi6c2b2a22016-03-04 15:52:19 -0800728 def __init__(self, artifact_map, download_dir, artifacts, files, build,
729 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800730 """Initalizes the member variables for the factory.
731
732 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700733 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700734 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700735 artifacts: List of artifacts to stage. These artifacts must be
736 defined in artifact_info.py and have a mapping in the
737 ARTIFACT_IMPLEMENTATION_MAP.
738 files: List of files to stage. These files are just downloaded and staged
739 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800740 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800741 requested_to_optional_map: A map between an artifact X to a list of
742 artifacts Y. If X is requested, all items in Y should also get
743 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800744 """
Gabe Black3b567202015-09-23 14:07:59 -0700745 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700746 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700747 self.artifacts = artifacts
748 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800749 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800750 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800751
Chris Sosa6b0c6172013-08-05 17:01:33 -0700752 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700753 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700754
755 If is_artifact is true, then these names define artifacts that must exist in
756 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
757 basic BuildArtifacts.
758
Gilad Arnold950569b2013-08-27 14:38:01 -0700759 Args:
760 names: A sequence of artifact names.
761 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800762
Gilad Arnold950569b2013-08-27 14:38:01 -0700763 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700764 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800765
Gilad Arnold950569b2013-08-27 14:38:01 -0700766 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700767 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700768 """
Gabe Black3b567202015-09-23 14:07:59 -0700769 if is_artifact:
770 classes = itertools.chain(*(self.artifact_map[name] for name in names))
771 return list(cls(self.download_dir, self.build) for cls in classes)
772 else:
773 return list(Artifact(name, self.download_dir, self.build)
774 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800775
776 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700777 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700778
Gilad Arnold950569b2013-08-27 14:38:01 -0700779 Returns:
780 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800781
Gilad Arnold950569b2013-08-27 14:38:01 -0700782 Raises:
783 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700784 """
785 artifacts = []
786 if self.artifacts:
787 artifacts.extend(self._Artifacts(self.artifacts, True))
788 if self.files:
789 artifacts.extend(self._Artifacts(self.files, False))
790
791 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800792
793 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700794 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700795
Gilad Arnold950569b2013-08-27 14:38:01 -0700796 Returns:
797 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800798
Gilad Arnold950569b2013-08-27 14:38:01 -0700799 Raises:
800 KeyError: if an optional artifact doesn't exist in
801 ARTIFACT_IMPLEMENTATION_MAP yet defined in
802 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700803 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800804 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800805 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800806 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700807 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800808 optional_names = optional_names.union(optional_list)
809
Chris Sosa6b0c6172013-08-05 17:01:33 -0700810 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700811
812
Gabe Black3b567202015-09-23 14:07:59 -0700813class ChromeOSArtifactFactory(BaseArtifactFactory):
814 """A factory class that generates ChromeOS build artifacts from names."""
815
816 def __init__(self, download_dir, artifacts, files, build):
817 """Pass the ChromeOS artifact map to the base class."""
818 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800819 chromeos_artifact_map, download_dir, artifacts, files, build,
820 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700821
822
823class AndroidArtifactFactory(BaseArtifactFactory):
824 """A factory class that generates Android build artifacts from names."""
825
826 def __init__(self, download_dir, artifacts, files, build):
827 """Pass the Android artifact map to the base class."""
828 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800829 android_artifact_map, download_dir, artifacts, files, build,
830 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700831
832
Chris Sosa968a1062013-08-02 17:42:50 -0700833# A simple main to verify correctness of the artifact map when making simple
834# name changes.
835if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700836 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
837 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
838 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
839 ('ANDROID', android_artifact_map)):
840 print('%s:' % label)
841 for key, value in sorted(mapping.items()):
842 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))