blob: b9883ab2c11526333e608944b44cb158a5ebbd99 [file] [log] [blame]
David Riley0655bab2017-11-02 10:44:26 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
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'
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -070050SIGNED_IMAGE_FILE = 'chromeos_*mp*.bin'
Justin Giorgib7590522017-02-07 13:36:24 -080051LIBIOTA_TEST_BINARIES_FILE = 'test_binaries.tar.gz'
52LIBIOTA_BOARD_UTILS_FILE = 'board_utils.tar.gz'
David Riley0655bab2017-11-02 10:44:26 -070053QUICK_PROVISION_FILE = 'full_dev_part_*.bin.gz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080054
Gabe Black3b567202015-09-23 14:07:59 -070055############ Actual filenames of Android build artifacts ############
56
Dan Shiba4e00f2015-10-27 12:03:53 -070057ANDROID_IMAGE_ZIP = r'.*-img-[^-]*\.zip'
Gabe Black3b567202015-09-23 14:07:59 -070058ANDROID_RADIO_IMAGE = 'radio.img'
59ANDROID_BOOTLOADER_IMAGE = 'bootloader.img'
60ANDROID_FASTBOOT = 'fastboot'
61ANDROID_TEST_ZIP = r'[^-]*-tests-.*\.zip'
Dan Shi74136ae2015-12-01 14:40:06 -080062ANDROID_VENDOR_PARTITION_ZIP = r'[^-]*-vendor_partitions-.*\.zip'
Dan Shi6c2b2a22016-03-04 15:52:19 -080063ANDROID_AUTOTEST_SERVER_PACKAGE = r'[^-]*-autotest_server_package-.*\.tar.bz2'
64ANDROID_TEST_SUITES = r'[^-]*-test_suites-.*\.tar.bz2'
65ANDROID_CONTROL_FILES = r'[^-]*-autotest_control_files-.*\.tar'
Simran Basi05be7212016-03-16 13:08:23 -070066ANDROID_NATIVETESTS_FILE = r'[^-]*-brillo-tests-.*\.zip'
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -070067ANDROID_CONTINUOUS_NATIVE_TESTS_FILE = r'[^-]*-continuous_native_tests-.*\.zip'
Justin Giorgibb32ac42016-08-04 10:34:35 -070068ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE = (
69 r'[^-]*-continuous_instrumentation_tests-.*\.zip')
Justin Giorgi4faa8902016-08-19 19:54:30 -070070ANDROID_CTS_FILE = 'android-cts.zip'
Justin Giorgi576c1542016-06-24 08:24:20 -070071ANDROID_TARGET_FILES_ZIP = r'[^-]*-target_files-.*\.zip'
72ANDROID_DTB_ZIP = r'[^-]*-dtb-.*\.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080073
74_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070075
76
77class ArtifactDownloadError(Exception):
78 """Error used to signify an issue processing an artifact."""
79 pass
80
81
Gabe Black3b567202015-09-23 14:07:59 -070082class ArtifactMeta(type):
83 """metaclass for an artifact type.
84
85 This metaclass is for class Artifact and its subclasses to have a meaningful
86 string composed of class name and the corresponding artifact name, e.g.,
87 `Artifact_full_payload`. This helps to better logging, refer to logging in
88 method Downloader.Download.
89 """
90
91 ARTIFACT_NAME = None
92
93 def __str__(cls):
94 return '%s_%s' % (cls.__name__, cls.ARTIFACT_NAME)
95
96 def __repr__(cls):
97 return str(cls)
98
99
100class Artifact(log_util.Loggable):
101 """Wrapper around an artifact to download using a fetcher.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700102
103 The purpose of this class is to download objects from Google Storage
104 and install them to a local directory. There are two main functions, one to
105 download/prepare the artifacts in to a temporary staging area and the second
106 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -0800107
Gilad Arnold950569b2013-08-27 14:38:01 -0700108 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
109 attentive when adding new artifacts; (ii) name matching semantics differ
110 between a glob (full name string match) and a regex (partial match).
111
Chris Sosa76e44b92013-01-31 12:11:38 -0800112 Class members:
Gabe Black3b567202015-09-23 14:07:59 -0700113 fetcher: An object which knows how to fetch the artifact.
Gilad Arnold950569b2013-08-27 14:38:01 -0700114 name: Name given for artifact; in fact, it is a pattern that captures the
115 names of files contained in the artifact. This can either be an
116 ordinary shell-style glob (the default), or a regular expression (if
117 is_regex_name is True).
118 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 build: The version of the build i.e. R26-2342.0.0.
120 marker_name: Name used to define the lock marker for the artifacts to
121 prevent it from being re-downloaded. By default based on name
122 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -0700123 exception_file_path: Path to a file containing the serialized exception,
124 which was raised in Process method. The file is located
125 in the parent folder of install_dir, since the
126 install_dir will be deleted if the build does not
127 existed.
joychen0a8e34e2013-06-24 17:58:36 -0700128 install_path: Path to artifact.
Gabe Black3b567202015-09-23 14:07:59 -0700129 install_subdir: Directory within install_path where the artifact is actually
130 stored.
Chris Sosa76e44b92013-01-31 12:11:38 -0800131 install_dir: The final location where the artifact should be staged to.
132 single_name: If True the name given should only match one item. Note, if not
133 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -0800134 installed_files: A list of files that were the final result of downloading
135 and setting up the artifact.
136 store_installed_files: Whether the list of installed files is stored in the
137 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700138 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700139
Gabe Black3b567202015-09-23 14:07:59 -0700140 __metaclass__ = ArtifactMeta
141
142 def __init__(self, name, install_dir, build, install_subdir='',
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800143 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -0700144 """Constructor.
145
146 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800147 install_dir: Where to install the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -0800148 name: Identifying name to be used to find/store the artifact.
149 build: The name of the build e.g. board/release.
Gabe Black3b567202015-09-23 14:07:59 -0700150 install_subdir: Directory within install_path where the artifact is
151 actually stored.
Gilad Arnold950569b2013-08-27 14:38:01 -0700152 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800153 optional_name: An alternative name to find the artifact, which can lead
154 to faster download. Unlike |name|, there is no guarantee that an
155 artifact named |optional_name| is/will be on Google Storage. If it
156 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157 """
Gabe Black3b567202015-09-23 14:07:59 -0700158 super(Artifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700159
Chris Sosa76e44b92013-01-31 12:11:38 -0800160 # In-memory lock to keep the devserver from colliding with itself while
161 # attempting to stage the same artifact.
162 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700163
Chris Sosa76e44b92013-01-31 12:11:38 -0800164 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800165 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700166 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800167 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700168
Chris Sosa76e44b92013-01-31 12:11:38 -0800169 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700170
Dan Shi6e50c722013-08-19 15:05:06 -0700171 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
172 '.exception')
173 # The exception file needs to be located in parent folder, since the
174 # install_dir will be deleted is the build does not exist.
175 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700176 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700177
joychen0a8e34e2013-06-24 17:58:36 -0700178 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700179
Chris Sosa76e44b92013-01-31 12:11:38 -0800180 self.install_dir = install_dir
Gabe Black3b567202015-09-23 14:07:59 -0700181 self.install_subdir = install_subdir
Chris Sosa76e44b92013-01-31 12:11:38 -0800182
183 self.single_name = True
184
Gilad Arnold1638d822013-11-07 23:38:16 -0800185 self.installed_files = []
186 self.store_installed_files = True
187
Chris Sosa76e44b92013-01-31 12:11:38 -0800188 @staticmethod
189 def _SanitizeName(name):
190 """Sanitizes name to be used for creating a file on the filesystem.
191
192 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700193
194 Args:
195 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800196
Gilad Arnold950569b2013-08-27 14:38:01 -0700197 Returns:
198 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800199 """
200 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
201
Dan Shif8eb0d12013-08-01 17:52:06 -0700202 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800203 """Returns True if artifact is already staged.
204
205 This checks for (1) presence of the artifact marker file, and (2) the
206 presence of each installed file listed in this marker. Both must hold for
207 the artifact to be considered staged. Note that this method is safe for use
208 even if the artifacts were not stageed by this instance, as it is assumed
Gabe Black3b567202015-09-23 14:07:59 -0700209 that any Artifact instance that did the staging wrote the list of
Gilad Arnold1638d822013-11-07 23:38:16 -0800210 files actually installed into the marker.
211 """
212 marker_file = os.path.join(self.install_dir, self.marker_name)
213
214 # If the marker is missing, it's definitely not staged.
215 if not os.path.exists(marker_file):
Aviv Keshet57d18172016-06-18 20:39:09 -0700216 self._Log('No marker file, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800217 return False
218
219 # We want to ensure that every file listed in the marker is actually there.
220 if self.store_installed_files:
221 with open(marker_file) as f:
222 files = [line.strip() for line in f]
223
224 # Check to see if any of the purportedly installed files are missing, in
225 # which case the marker is outdated and should be removed.
226 missing_files = [fname for fname in files if not os.path.exists(fname)]
227 if missing_files:
228 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
229 'All' if len(files) == len(missing_files) else 'Some',
230 marker_file, '\n'.join(missing_files))
231 os.remove(marker_file)
Aviv Keshet57d18172016-06-18 20:39:09 -0700232 self._Log('Missing files, %s is not staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800233 return False
234
Aviv Keshet57d18172016-06-18 20:39:09 -0700235 self._Log('ArtifactStaged() -> yes, %s is staged.', self)
Gilad Arnold1638d822013-11-07 23:38:16 -0800236 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800237
238 def _MarkArtifactStaged(self):
239 """Marks the artifact as staged."""
240 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800241 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800242
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800243 def _UpdateName(self, names):
244 if self.single_name and len(names) > 1:
245 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800246
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800247 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800248
joychen0a8e34e2013-06-24 17:58:36 -0700249 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800250 """Process the downloaded content, update the list of installed files."""
251 # In this primitive case, what was downloaded (has to be a single file) is
252 # what's installed.
253 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700254
Dan Shi6e50c722013-08-19 15:05:06 -0700255 def _ClearException(self):
256 """Delete any existing exception saved for this artifact."""
257 if os.path.exists(self.exception_file_path):
258 os.remove(self.exception_file_path)
259
260 def _SaveException(self, e):
xixuan56252ff2017-03-09 15:40:31 -0800261 """Save the exception and traceback to a file for downloader.IsStaged.
Dan Shi6e50c722013-08-19 15:05:06 -0700262
Gilad Arnold950569b2013-08-27 14:38:01 -0700263 Args:
264 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700265 """
266 with open(self.exception_file_path, 'w') as f:
xixuan56252ff2017-03-09 15:40:31 -0800267 pickle.dump('%s\n%s' % (e, str(traceback.format_exc())), f)
Dan Shi6e50c722013-08-19 15:05:06 -0700268
269 def GetException(self):
270 """Retrieve any exception that was raised in Process method.
271
Gilad Arnold950569b2013-08-27 14:38:01 -0700272 Returns:
273 An Exception object that was raised when trying to process the artifact.
274 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700275 """
276 if not os.path.exists(self.exception_file_path):
277 return None
278 with open(self.exception_file_path, 'r') as f:
279 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800280
Gabe Black3b567202015-09-23 14:07:59 -0700281 def Process(self, downloader, no_wait):
Chris Sosa76e44b92013-01-31 12:11:38 -0800282 """Main call point to all artifacts. Downloads and Stages artifact.
283
284 Downloads and Stages artifact from Google Storage to the install directory
285 specified in the constructor. It multi-thread safe and does not overwrite
286 the artifact if it's already been downloaded or being downloaded. After
287 processing, leaves behind a marker to indicate to future invocations that
288 the artifact has already been staged based on the name of the artifact.
289
290 Do not override as it modifies important private variables, ensures thread
291 safety, and maintains cache semantics.
292
293 Note: this may be a blocking call when the artifact is already in the
294 process of being staged.
295
296 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700297 downloader: A downloader instance containing the logic to download
298 artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800299 no_wait: If True, don't block waiting for artifact to exist if we fail to
300 immediately find it.
301
302 Raises:
303 ArtifactDownloadError: If the artifact fails to download from Google
304 Storage for any reason or that the regexp
305 defined by name is not specific enough.
306 """
307 if not self._process_lock:
308 self._process_lock = _build_artifact_locks.lock(
309 os.path.join(self.install_dir, self.name))
310
Gabe Black3b567202015-09-23 14:07:59 -0700311 real_install_dir = os.path.join(self.install_dir, self.install_subdir)
Chris Sosa76e44b92013-01-31 12:11:38 -0800312 with self._process_lock:
Gabe Black3b567202015-09-23 14:07:59 -0700313 common_util.MkDirP(real_install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700314 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800315 # Delete any existing exception saved for this artifact.
316 self._ClearException()
317 found_artifact = False
318 if self.optional_name:
319 try:
Gabe Black3b567202015-09-23 14:07:59 -0700320 # Check if the artifact named |optional_name| exists.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800321 # Because this artifact may not always exist, don't bother
322 # to wait for it (set timeout=1).
Gabe Black3b567202015-09-23 14:07:59 -0700323 new_names = downloader.Wait(
324 self.optional_name, self.is_regex_name, timeout=1)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800325 self._UpdateName(new_names)
326
327 except ArtifactDownloadError:
328 self._Log('Unable to download %s; fall back to download %s',
329 self.optional_name, self.name)
330 else:
331 found_artifact = True
332
Dan Shi6e50c722013-08-19 15:05:06 -0700333 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700334 # If the artifact should already have been uploaded, don't waste
335 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800336 if not found_artifact:
337 timeout = 1 if no_wait else 10
Gabe Black3b567202015-09-23 14:07:59 -0700338 new_names = downloader.Wait(
339 self.name, self.is_regex_name, timeout)
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800340 self._UpdateName(new_names)
341
David Rileye131a0f2017-11-02 10:42:34 -0700342
343 files = self.name if isinstance(self.name, list) else [self.name]
344 for filename in files:
345 self._Log('Downloading file %s', filename)
346 self.install_path = downloader.Fetch(filename, real_install_dir)
Dan Shi6e50c722013-08-19 15:05:06 -0700347 self._Setup()
348 self._MarkArtifactStaged()
349 except Exception as e:
350 # Save the exception to a file for downloader.IsStaged to retrieve.
351 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800352
353 # Convert an unknown exception into an ArtifactDownloadError.
354 if type(e) is ArtifactDownloadError:
355 raise
356 else:
357 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800358 else:
359 self._Log('%s is already staged.', self)
360
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700361 def __str__(self):
362 """String representation for the download."""
Gabe Black3b567202015-09-23 14:07:59 -0700363 return '%s->%s' % (self.name, self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700364
Chris Sosab26b1202013-08-16 16:40:55 -0700365 def __repr__(self):
366 return str(self)
367
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700368
Gabe Black3b567202015-09-23 14:07:59 -0700369class AUTestPayload(Artifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700370 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700371
joychen0a8e34e2013-06-24 17:58:36 -0700372 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700373 super(AUTestPayload, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700374
Chris Sosa76e44b92013-01-31 12:11:38 -0800375 # Rename to update.gz.
Gabe Black3b567202015-09-23 14:07:59 -0700376 install_path = os.path.join(self.install_dir, self.install_subdir,
377 self.name)
378 new_install_path = os.path.join(self.install_dir, self.install_subdir,
joychen7c2054a2013-07-25 11:14:07 -0700379 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700381
Gilad Arnold1638d822013-11-07 23:38:16 -0800382 # Reflect the rename in the list of installed files.
383 self.installed_files.remove(install_path)
384 self.installed_files = [new_install_path]
385
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700386
David Rileye131a0f2017-11-02 10:42:34 -0700387class MultiArtifact(Artifact):
388 """Wrapper for artifacts where name matches multiple items.."""
389
390 def __init__(self, *args, **kwargs):
391 """Takes Artifact args.
392
393 Args:
394 *args: See Artifact documentation.
395 **kwargs: See Artifact documentation.
396 """
397 super(MultiArtifact, self).__init__(*args, **kwargs)
398 self.single_name = False
399
400 def _UpdateName(self, names):
401 self.name = names if isinstance(names, list) else [names]
402
403 def _Setup(self):
404 super(MultiArtifact, self)._Setup()
405
406 self.installed_files = [os.path.join(self.install_dir, self.install_subdir,
407 name) for name in self.name]
408
409
Gabe Black3b567202015-09-23 14:07:59 -0700410class DeltaPayloadBase(AUTestPayload):
Chris Sosa76e44b92013-01-31 12:11:38 -0800411 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700412
Gabe Black3b567202015-09-23 14:07:59 -0700413 These artifacts are super strange. They custom handle directories and
414 pull in all delta payloads. We can't specify exactly what we want
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 because unlike other artifacts, this one does not conform to something a
416 client might know. The client doesn't know the version of n-1 or whether it
417 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700418
419 IMPORTANT! Note that this artifact simply ignores the `name' argument because
Gabe Black3b567202015-09-23 14:07:59 -0700420 that name is derived internally.
Chris Sosa76e44b92013-01-31 12:11:38 -0800421 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700422
joychen0a8e34e2013-06-24 17:58:36 -0700423 def _Setup(self):
Gabe Black3b567202015-09-23 14:07:59 -0700424 super(DeltaPayloadBase, self)._Setup()
425 # Setup symlink so that AU will work for this payload.
426 stateful_update_symlink = os.path.join(
427 self.install_dir, self.install_subdir,
428 devserver_constants.STATEFUL_FILE)
429 os.symlink(os.path.join(os.pardir, os.pardir,
430 devserver_constants.STATEFUL_FILE),
431 stateful_update_symlink)
432 self.installed_files.append(stateful_update_symlink)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700433
Chris Sosa76e44b92013-01-31 12:11:38 -0800434
Gabe Black3b567202015-09-23 14:07:59 -0700435class BundledArtifact(Artifact):
Chris Sosa76e44b92013-01-31 12:11:38 -0800436 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800437
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800438 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700439 """Takes Artifact args with some additional ones.
Gilad Arnold950569b2013-08-27 14:38:01 -0700440
441 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700442 *args: See Artifact documentation.
443 **kwargs: See Artifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700444 files_to_extract: A list of files to extract. If set to None, extract
445 all files.
446 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800447 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800448 self._files_to_extract = kwargs.pop('files_to_extract', None)
449 self._exclude = kwargs.pop('exclude', None)
Gabe Black3b567202015-09-23 14:07:59 -0700450 super(BundledArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800451
452 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800453 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800454 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800455 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800456
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800457 def _RunUnzip(self, list_only):
458 # Unzip is weird. It expects its args before any excludes and expects its
459 # excludes in a list following the -x.
460 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
461 if not list_only:
462 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800463
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800464 if self._files_to_extract:
465 cmd.extend(self._files_to_extract)
466
467 if self._exclude:
468 cmd.append('-x')
469 cmd.extend(self._exclude)
470
471 try:
472 return subprocess.check_output(cmd).strip('\n').splitlines()
473 except subprocess.CalledProcessError, e:
474 raise ArtifactDownloadError(
475 'An error occurred when attempting to unzip %s:\n%s' %
476 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800477
joychen0a8e34e2013-06-24 17:58:36 -0700478 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800479 extract_result = self._Extract()
480 if self.store_installed_files:
481 # List both the archive and the extracted files.
482 self.installed_files.append(self.install_path)
483 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800484
Chris Sosa76e44b92013-01-31 12:11:38 -0800485 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800486 """Extracts files into the install path."""
487 if self.name.endswith('.zip'):
488 return self._ExtractZipfile()
489 else:
490 return self._ExtractTarball()
491
492 def _ExtractZipfile(self):
493 """Extracts a zip file using unzip."""
494 file_list = [os.path.join(self.install_dir, line[30:].strip())
495 for line in self._RunUnzip(True)
496 if not line.endswith('/')]
497 if file_list:
498 self._RunUnzip(False)
499
500 return file_list
501
502 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800503 """Extracts a tarball using tar.
504
505 Detects whether the tarball is compressed or not based on the file
506 extension and extracts the tarball into the install_path.
507 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700508 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800509 return common_util.ExtractTarball(self.install_path, self.install_dir,
510 files_to_extract=self._files_to_extract,
511 excluded_files=self._exclude,
512 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800513 except common_util.CommonUtilError as e:
514 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700515
516
Gabe Black3b567202015-09-23 14:07:59 -0700517class AutotestTarball(BundledArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700518 """Wrapper around the autotest tarball to download from gsutil."""
519
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800520 def __init__(self, *args, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700521 super(AutotestTarball, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800522 # We don't store/check explicit file lists in Autotest tarball markers;
523 # this can get huge and unwieldy, and generally make little sense.
524 self.store_installed_files = False
525
joychen0a8e34e2013-06-24 17:58:36 -0700526 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800527 """Extracts the tarball into the install path excluding test suites."""
Gabe Black3b567202015-09-23 14:07:59 -0700528 super(AutotestTarball, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700529
Chris Sosa76e44b92013-01-31 12:11:38 -0800530 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700531 autotest_dir = os.path.join(self.install_dir,
532 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700533 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
534 if not os.path.exists(autotest_pkgs_dir):
535 os.makedirs(autotest_pkgs_dir)
536
537 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Prashant Malani20e83712017-02-28 01:30:41 -0800538 cmd = ['autotest/utils/packager.py', '--action=upload', '--repository',
Chris Sosa76e44b92013-01-31 12:11:38 -0800539 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700540 try:
joychen0a8e34e2013-06-24 17:58:36 -0700541 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700542 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800543 raise ArtifactDownloadError(
544 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700545 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700546 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700547
Chris Masone816e38c2012-05-02 12:22:36 -0700548
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700549class SignedArtifact(Artifact):
550 """Wrapper for signed artifacts which need a path translation."""
551
552 def _Setup(self):
553 super(SignedArtifact, self)._Setup()
554
555 # Rename to signed_image.bin.
556 install_path = os.path.join(self.install_dir, self.install_subdir,
557 self.name)
558 new_install_path = os.path.join(self.install_dir, self.install_subdir,
559 devserver_constants.SIGNED_IMAGE_FILE)
560 shutil.move(install_path, new_install_path)
561
562 # Reflect the rename in the list of installed files.
563 self.installed_files.remove(install_path)
564 self.installed_files = [new_install_path]
565
566
Gabe Black3b567202015-09-23 14:07:59 -0700567def _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
568 """Get a data wrapper that describes an artifact's implementation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700569
Gabe Black3b567202015-09-23 14:07:59 -0700570 Args:
571 tag: Tag of the artifact, defined in artifact_info.
572 base: Class of the artifact, e.g., BundledArtifact.
573 name: Name of the artifact, e.g., image.zip.
574 *fixed_args: Fixed arguments that are additional to the one used in base
575 class.
576 **fixed_kwargs: Fixed keyword arguments that are additional to the one used
577 in base class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800578
Gabe Black3b567202015-09-23 14:07:59 -0700579 Returns:
580 A data wrapper that describes an artifact's implementation.
Gabe Black3b567202015-09-23 14:07:59 -0700581 """
Don Garrettfb15e322016-06-21 19:12:08 -0700582 # pylint: disable=super-on-old-class
Gabe Black3b567202015-09-23 14:07:59 -0700583 class NewArtifact(base):
584 """A data wrapper that describes an artifact's implementation."""
585 ARTIFACT_TAG = tag
586 ARTIFACT_NAME = name
587
588 def __init__(self, *args, **kwargs):
589 all_args = fixed_args + args
590 all_kwargs = {}
591 all_kwargs.update(fixed_kwargs)
592 all_kwargs.update(kwargs)
593 super(NewArtifact, self).__init__(self.ARTIFACT_NAME,
594 *all_args, **all_kwargs)
595
596 NewArtifact.__name__ = base.__name__
597 return NewArtifact
Chris Sosa968a1062013-08-02 17:42:50 -0700598
Chris Sosa76e44b92013-01-31 12:11:38 -0800599
Gabe Black3b567202015-09-23 14:07:59 -0700600# TODO(dshi): Refactor the code here to split out the logic of creating the
601# artifacts mapping to a different module.
602chromeos_artifact_map = {}
Chris Sosa76e44b92013-01-31 12:11:38 -0800603
Chris Sosa76e44b92013-01-31 12:11:38 -0800604
Gabe Black3b567202015-09-23 14:07:59 -0700605def _AddCrOSArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700606 """Add a data wrapper for ChromeOS artifacts.
607
608 Add a data wrapper that describes a ChromeOS artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700609 chromeos_artifact_map.
610 """
611 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
612 chromeos_artifact_map.setdefault(tag, []).append(artifact)
Chris Sosa76e44b92013-01-31 12:11:38 -0800613
beepsc3d0f872013-07-31 21:50:40 -0700614
Gabe Black3b567202015-09-23 14:07:59 -0700615_AddCrOSArtifact(artifact_info.FULL_PAYLOAD, AUTestPayload, '*_full_*')
616
617
618class DeltaPayloadNtoN(DeltaPayloadBase):
619 """ChromeOS Delta payload artifact for updating from version N to N."""
620 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
621 ARTIFACT_NAME = 'NOT_APPLICABLE'
622
623 def __init__(self, install_dir, build, *args, **kwargs):
624 name = 'chromeos_%s*_delta_*' % build
625 install_subdir = os.path.join(_AU_BASE, build + _NTON_DIR_SUFFIX)
626 super(DeltaPayloadNtoN, self).__init__(name, install_dir, build, *args,
627 install_subdir=install_subdir,
628 **kwargs)
629
630
631class DeltaPayloadMtoN(DeltaPayloadBase):
632 """ChromeOS Delta payload artifact for updating from version M to N."""
633 ARTIFACT_TAG = artifact_info.DELTA_PAYLOADS
634 ARTIFACT_NAME = 'NOT_APPLICABLE'
635
636 def __init__(self, install_dir, build, *args, **kwargs):
637 name = ('chromeos_(?!%s).*_delta_.*' % re.escape(build))
638 install_subdir = os.path.join(_AU_BASE, build + _MTON_DIR_SUFFIX)
639 super(DeltaPayloadMtoN, self).__init__(name, install_dir, build, *args,
640 install_subdir=install_subdir,
641 is_regex_name=True, **kwargs)
642
643
644chromeos_artifact_map[artifact_info.DELTA_PAYLOADS] = [DeltaPayloadNtoN,
645 DeltaPayloadMtoN]
646
647
648_AddCrOSArtifact(artifact_info.STATEFUL_PAYLOAD, Artifact,
649 devserver_constants.STATEFUL_FILE)
650_AddCrOSArtifact(artifact_info.BASE_IMAGE, BundledArtifact, IMAGE_FILE,
651 optional_name=BASE_IMAGE_FILE,
652 files_to_extract=[devserver_constants.BASE_IMAGE_FILE])
653_AddCrOSArtifact(artifact_info.RECOVERY_IMAGE, BundledArtifact, IMAGE_FILE,
654 optional_name=RECOVERY_IMAGE_FILE,
655 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE])
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700656_AddCrOSArtifact(artifact_info.SIGNED_IMAGE, SignedArtifact, SIGNED_IMAGE_FILE)
Gabe Black3b567202015-09-23 14:07:59 -0700657_AddCrOSArtifact(artifact_info.DEV_IMAGE, BundledArtifact, IMAGE_FILE,
658 files_to_extract=[devserver_constants.IMAGE_FILE])
659_AddCrOSArtifact(artifact_info.TEST_IMAGE, BundledArtifact, IMAGE_FILE,
660 optional_name=TEST_IMAGE_FILE,
661 files_to_extract=[devserver_constants.TEST_IMAGE_FILE])
662_AddCrOSArtifact(artifact_info.AUTOTEST, AutotestTarball, AUTOTEST_FILE,
663 files_to_extract=None, exclude=['autotest/test_suites'])
664_AddCrOSArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
665 CONTROL_FILES_FILE)
666_AddCrOSArtifact(artifact_info.AUTOTEST_PACKAGES, AutotestTarball,
667 AUTOTEST_PACKAGES_FILE)
668_AddCrOSArtifact(artifact_info.TEST_SUITES, BundledArtifact, TEST_SUITES_FILE)
669_AddCrOSArtifact(artifact_info.AU_SUITE, BundledArtifact, AU_SUITE_FILE)
670_AddCrOSArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
671 AUTOTEST_SERVER_PACKAGE_FILE)
672_AddCrOSArtifact(artifact_info.FIRMWARE, Artifact, FIRMWARE_FILE)
673_AddCrOSArtifact(artifact_info.SYMBOLS, BundledArtifact, DEBUG_SYMBOLS_FILE,
674 files_to_extract=['debug/breakpad'])
Dan Shi55d0f972016-10-04 11:45:00 -0700675_AddCrOSArtifact(artifact_info.SYMBOLS_ONLY, BundledArtifact,
676 DEBUG_SYMBOLS_ONLY_FILE,
677 files_to_extract=['debug/breakpad'])
Gabe Black3b567202015-09-23 14:07:59 -0700678_AddCrOSArtifact(artifact_info.FACTORY_IMAGE, BundledArtifact, FACTORY_FILE,
679 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Mike Frysingera0e6a282016-09-01 17:29:08 -0400680_AddCrOSArtifact(artifact_info.FACTORY_SHIM_IMAGE, BundledArtifact,
681 FACTORY_SHIM_FILE,
682 files_to_extract=[devserver_constants.FACTORY_SHIM_IMAGE_FILE])
David Riley0655bab2017-11-02 10:44:26 -0700683_AddCrOSArtifact(artifact_info.QUICK_PROVISION, MultiArtifact,
684 QUICK_PROVISION_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800685
Chris Sosa968a1062013-08-02 17:42:50 -0700686# Add all the paygen_au artifacts in one go.
Gabe Black3b567202015-09-23 14:07:59 -0700687for c in devserver_constants.CHANNELS:
688 _AddCrOSArtifact(artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c},
689 BundledArtifact,
690 PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
691
Justin Giorgib7590522017-02-07 13:36:24 -0800692#### Libiota Artifacts ####
693_AddCrOSArtifact(artifact_info.LIBIOTA_TEST_BINARIES, Artifact,
694 LIBIOTA_TEST_BINARIES_FILE)
695_AddCrOSArtifact(artifact_info.LIBIOTA_BOARD_UTILS, Artifact,
696 LIBIOTA_BOARD_UTILS_FILE)
697
Gabe Black3b567202015-09-23 14:07:59 -0700698android_artifact_map = {}
Chris Sosa968a1062013-08-02 17:42:50 -0700699
Chris Sosa76e44b92013-01-31 12:11:38 -0800700
Gabe Black3b567202015-09-23 14:07:59 -0700701def _AddAndroidArtifact(tag, base, name, *fixed_args, **fixed_kwargs):
Don Garrettfb15e322016-06-21 19:12:08 -0700702 """Add a data wrapper for android artifacts.
703
704 Add a data wrapper that describes an Android artifact's implementation to
Gabe Black3b567202015-09-23 14:07:59 -0700705 android_artifact_map.
706 """
707 artifact = _CreateNewArtifact(tag, base, name, *fixed_args, **fixed_kwargs)
708 android_artifact_map.setdefault(tag, []).append(artifact)
709
710
Dan Shiba4e00f2015-10-27 12:03:53 -0700711_AddAndroidArtifact(artifact_info.ANDROID_ZIP_IMAGES, Artifact,
Gabe Black3b567202015-09-23 14:07:59 -0700712 ANDROID_IMAGE_ZIP, is_regex_name=True)
713_AddAndroidArtifact(artifact_info.ANDROID_RADIO_IMAGE, Artifact,
714 ANDROID_RADIO_IMAGE)
715_AddAndroidArtifact(artifact_info.ANDROID_BOOTLOADER_IMAGE, Artifact,
716 ANDROID_BOOTLOADER_IMAGE)
717_AddAndroidArtifact(artifact_info.ANDROID_FASTBOOT, Artifact, ANDROID_FASTBOOT)
718_AddAndroidArtifact(artifact_info.ANDROID_TEST_ZIP, BundledArtifact,
719 ANDROID_TEST_ZIP, is_regex_name=True)
Dan Shi74136ae2015-12-01 14:40:06 -0800720_AddAndroidArtifact(artifact_info.ANDROID_VENDOR_PARTITION_ZIP, Artifact,
721 ANDROID_VENDOR_PARTITION_ZIP, is_regex_name=True)
Dan Shi6c2b2a22016-03-04 15:52:19 -0800722_AddAndroidArtifact(artifact_info.AUTOTEST_SERVER_PACKAGE, Artifact,
723 ANDROID_AUTOTEST_SERVER_PACKAGE, is_regex_name=True)
724_AddAndroidArtifact(artifact_info.TEST_SUITES, BundledArtifact,
725 ANDROID_TEST_SUITES, is_regex_name=True)
726_AddAndroidArtifact(artifact_info.CONTROL_FILES, BundledArtifact,
727 ANDROID_CONTROL_FILES, is_regex_name=True)
Simran Basi05be7212016-03-16 13:08:23 -0700728_AddAndroidArtifact(artifact_info.ANDROID_NATIVETESTS_ZIP, BundledArtifact,
729 ANDROID_NATIVETESTS_FILE, is_regex_name=True)
Ralph Nathan6c7fe5e2016-06-22 15:50:10 -0700730_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_NATIVE_TESTS_ZIP,
731 BundledArtifact,
732 ANDROID_CONTINUOUS_NATIVE_TESTS_FILE,
733 is_regex_name=True)
Justin Giorgibb32ac42016-08-04 10:34:35 -0700734_AddAndroidArtifact(artifact_info.ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_ZIP,
735 BundledArtifact,
736 ANDROID_CONTINUOUS_INSTRUMENTATION_TESTS_FILE,
737 is_regex_name=True)
Justin Giorgi4faa8902016-08-19 19:54:30 -0700738_AddAndroidArtifact(artifact_info.ANDROID_CTS_ZIP, BundledArtifact,
739 ANDROID_CTS_FILE)
Justin Giorgi576c1542016-06-24 08:24:20 -0700740_AddAndroidArtifact(artifact_info.ANDROID_TARGET_FILES_ZIP, Artifact,
741 ANDROID_TARGET_FILES_ZIP, is_regex_name=True)
742_AddAndroidArtifact(artifact_info.ANDROID_DTB_ZIP, Artifact,
743 ANDROID_DTB_ZIP, is_regex_name=True)
Gabe Black3b567202015-09-23 14:07:59 -0700744
745class BaseArtifactFactory(object):
Chris Sosa76e44b92013-01-31 12:11:38 -0800746 """A factory class that generates build artifacts from artifact names."""
747
Dan Shi6c2b2a22016-03-04 15:52:19 -0800748 def __init__(self, artifact_map, download_dir, artifacts, files, build,
749 requested_to_optional_map):
Chris Sosa76e44b92013-01-31 12:11:38 -0800750 """Initalizes the member variables for the factory.
751
752 Args:
Gabe Black3b567202015-09-23 14:07:59 -0700753 artifact_map: A map from artifact names to ImplDescription objects.
Gilad Arnold950569b2013-08-27 14:38:01 -0700754 download_dir: A directory to which artifacts are downloaded.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700755 artifacts: List of artifacts to stage. These artifacts must be
756 defined in artifact_info.py and have a mapping in the
757 ARTIFACT_IMPLEMENTATION_MAP.
758 files: List of files to stage. These files are just downloaded and staged
759 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800760 build: The name of the build.
Dan Shi6c2b2a22016-03-04 15:52:19 -0800761 requested_to_optional_map: A map between an artifact X to a list of
762 artifacts Y. If X is requested, all items in Y should also get
763 triggered for download.
Chris Sosa76e44b92013-01-31 12:11:38 -0800764 """
Gabe Black3b567202015-09-23 14:07:59 -0700765 self.artifact_map = artifact_map
joychen0a8e34e2013-06-24 17:58:36 -0700766 self.download_dir = download_dir
Chris Sosa6b0c6172013-08-05 17:01:33 -0700767 self.artifacts = artifacts
768 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800769 self.build = build
Dan Shi6c2b2a22016-03-04 15:52:19 -0800770 self.requested_to_optional_map = requested_to_optional_map
Chris Sosa76e44b92013-01-31 12:11:38 -0800771
Chris Sosa6b0c6172013-08-05 17:01:33 -0700772 def _Artifacts(self, names, is_artifact):
Gabe Black3b567202015-09-23 14:07:59 -0700773 """Returns the Artifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700774
775 If is_artifact is true, then these names define artifacts that must exist in
776 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
777 basic BuildArtifacts.
778
Gilad Arnold950569b2013-08-27 14:38:01 -0700779 Args:
780 names: A sequence of artifact names.
781 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800782
Gilad Arnold950569b2013-08-27 14:38:01 -0700783 Returns:
Gabe Black3b567202015-09-23 14:07:59 -0700784 An iterable of Artifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800785
Gilad Arnold950569b2013-08-27 14:38:01 -0700786 Raises:
Gabe Black3b567202015-09-23 14:07:59 -0700787 KeyError: if artifact doesn't exist in the artifact map.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700788 """
Gabe Black3b567202015-09-23 14:07:59 -0700789 if is_artifact:
790 classes = itertools.chain(*(self.artifact_map[name] for name in names))
791 return list(cls(self.download_dir, self.build) for cls in classes)
792 else:
793 return list(Artifact(name, self.download_dir, self.build)
794 for name in names)
Chris Sosa76e44b92013-01-31 12:11:38 -0800795
796 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700797 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700798
Gilad Arnold950569b2013-08-27 14:38:01 -0700799 Returns:
800 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800801
Gilad Arnold950569b2013-08-27 14:38:01 -0700802 Raises:
803 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700804 """
805 artifacts = []
806 if self.artifacts:
807 artifacts.extend(self._Artifacts(self.artifacts, True))
808 if self.files:
809 artifacts.extend(self._Artifacts(self.files, False))
810
811 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800812
813 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700814 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700815
Gilad Arnold950569b2013-08-27 14:38:01 -0700816 Returns:
817 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800818
Gilad Arnold950569b2013-08-27 14:38:01 -0700819 Raises:
820 KeyError: if an optional artifact doesn't exist in
821 ARTIFACT_IMPLEMENTATION_MAP yet defined in
822 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700823 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800824 optional_names = set()
Dan Shi6c2b2a22016-03-04 15:52:19 -0800825 for artifact_name, optional_list in self.requested_to_optional_map.items():
Chris Sosa76e44b92013-01-31 12:11:38 -0800826 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700827 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800828 optional_names = optional_names.union(optional_list)
829
Chris Sosa6b0c6172013-08-05 17:01:33 -0700830 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700831
832
Gabe Black3b567202015-09-23 14:07:59 -0700833class ChromeOSArtifactFactory(BaseArtifactFactory):
834 """A factory class that generates ChromeOS build artifacts from names."""
835
836 def __init__(self, download_dir, artifacts, files, build):
837 """Pass the ChromeOS artifact map to the base class."""
838 super(ChromeOSArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800839 chromeos_artifact_map, download_dir, artifacts, files, build,
840 artifact_info.CROS_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700841
842
843class AndroidArtifactFactory(BaseArtifactFactory):
844 """A factory class that generates Android build artifacts from names."""
845
846 def __init__(self, download_dir, artifacts, files, build):
847 """Pass the Android artifact map to the base class."""
848 super(AndroidArtifactFactory, self).__init__(
Dan Shi6c2b2a22016-03-04 15:52:19 -0800849 android_artifact_map, download_dir, artifacts, files, build,
850 artifact_info.ANDROID_REQUESTED_TO_OPTIONAL_MAP)
Gabe Black3b567202015-09-23 14:07:59 -0700851
852
Chris Sosa968a1062013-08-02 17:42:50 -0700853# A simple main to verify correctness of the artifact map when making simple
854# name changes.
855if __name__ == '__main__':
Gabe Black3b567202015-09-23 14:07:59 -0700856 print('ARTIFACT IMPLEMENTATION MAPs (for debugging)')
857 print('FORMAT: ARTIFACT -> IMPLEMENTATION (<type>_file)')
858 for label, mapping in (('CHROMEOS', chromeos_artifact_map),
859 ('ANDROID', android_artifact_map)):
860 print('%s:' % label)
861 for key, value in sorted(mapping.items()):
862 print(' %s -> %s' % (key, ', '.join(str(val) for val in value)))