blob: d55e6c41305befd6621d25af16408bae76956a5d [file] [log] [blame]
Chris Sosa968a1062013-08-02 17:42:50 -07001#!/usr/bin/env python
2
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
Chris Sosa47a7d4e2012-03-28 11:26:55 -07009import os
10import shutil
11import subprocess
12
Chris Sosa76e44b92013-01-31 12:11:38 -080013import artifact_info
14import common_util
joychen3cb228e2013-06-12 12:13:13 -070015import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070016import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070017import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070018
19
Chris Sosa76e44b92013-01-31 12:11:38 -080020_AU_BASE = 'au'
21_NTON_DIR_SUFFIX = '_nton'
22_MTON_DIR_SUFFIX = '_mton'
23
24############ Actual filenames of artifacts in Google Storage ############
25
26AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070027PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080028AUTOTEST_FILE = 'autotest.tar'
29AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
30DEBUG_SYMBOLS_FILE = 'debug.tgz'
beepsc3d0f872013-07-31 21:50:40 -070031FACTORY_FILE = 'ChromeOS-factory.*zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080032FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
33IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080034TEST_SUITES_FILE = 'test_suites.tar.bz2'
35
36_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070037
38
39class ArtifactDownloadError(Exception):
40 """Error used to signify an issue processing an artifact."""
41 pass
42
43
Gilad Arnoldc65330c2012-09-20 15:17:48 -070044class BuildArtifact(log_util.Loggable):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070045 """Wrapper around an artifact to download from gsutil.
46
47 The purpose of this class is to download objects from Google Storage
48 and install them to a local directory. There are two main functions, one to
49 download/prepare the artifacts in to a temporary staging area and the second
50 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080051
52 Class members:
53 archive_url = archive_url
54 name: Name given for artifact -- either a regexp or name of the artifact in
55 gs. If a regexp, is modified to actual name before call to _Download.
56 build: The version of the build i.e. R26-2342.0.0.
57 marker_name: Name used to define the lock marker for the artifacts to
58 prevent it from being re-downloaded. By default based on name
59 but can be overriden by children.
joychen0a8e34e2013-06-24 17:58:36 -070060 install_path: Path to artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -080061 install_dir: The final location where the artifact should be staged to.
62 single_name: If True the name given should only match one item. Note, if not
63 True, self.name will become a list of items returned.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070064 """
Chris Sosa76e44b92013-01-31 12:11:38 -080065 def __init__(self, install_dir, archive_url, name, build):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070066 """Args:
Chris Sosa76e44b92013-01-31 12:11:38 -080067 install_dir: Where to install the artifact.
68 archive_url: The Google Storage path to find the artifact.
69 name: Identifying name to be used to find/store the artifact.
70 build: The name of the build e.g. board/release.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070071 """
Chris Sosa6a3697f2013-01-29 16:44:43 -080072 super(BuildArtifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070073
Chris Sosa76e44b92013-01-31 12:11:38 -080074 # In-memory lock to keep the devserver from colliding with itself while
75 # attempting to stage the same artifact.
76 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070077
Chris Sosa76e44b92013-01-31 12:11:38 -080078 self.archive_url = archive_url
79 self.name = name
80 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -070081
Chris Sosa76e44b92013-01-31 12:11:38 -080082 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070083
joychen0a8e34e2013-06-24 17:58:36 -070084 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070085
Chris Sosa76e44b92013-01-31 12:11:38 -080086 self.install_dir = install_dir
87
88 self.single_name = True
89
90 @staticmethod
91 def _SanitizeName(name):
92 """Sanitizes name to be used for creating a file on the filesystem.
93
94 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
95 """
96 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
97
Dan Shif8eb0d12013-08-01 17:52:06 -070098 def ArtifactStaged(self):
Chris Sosa76e44b92013-01-31 12:11:38 -080099 """Returns True if artifact is already staged."""
100 return os.path.exists(os.path.join(self.install_dir, self.marker_name))
101
102 def _MarkArtifactStaged(self):
103 """Marks the artifact as staged."""
104 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
105 f.write('')
106
Chris Sosac4e87842013-08-16 18:04:14 -0700107 def WaitForArtifactToExist(self, timeout, update_name=True):
108 """Waits for artifact to exist and sets self.name to appropriate name.
109
110 Args:
111 update_name: If False, don't actually update self.name.
112 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800113 names = gsutil_util.GetGSNamesWithWait(
114 self.name, self.archive_url, str(self), single_item=self.single_name,
115 timeout=timeout)
116 if not names:
117 raise ArtifactDownloadError('Could not find %s in Google Storage' %
118 self.name)
119
120 if self.single_name:
121 if len(names) > 1:
122 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
123
Chris Sosac4e87842013-08-16 18:04:14 -0700124 new_name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800125 else:
Chris Sosac4e87842013-08-16 18:04:14 -0700126 new_name = names
127
128 if update_name:
129 self.name = new_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800130
131 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700132 """Downloads artifact from Google Storage to a local directory."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800133 gs_path = '/'.join([self.archive_url, self.name])
joychen0a8e34e2013-06-24 17:58:36 -0700134 self.install_path = os.path.join(self.install_dir, self.name)
135 gsutil_util.DownloadFromGS(gs_path, self.install_path)
Chris Sosa76e44b92013-01-31 12:11:38 -0800136
joychen0a8e34e2013-06-24 17:58:36 -0700137 def _Setup(self):
138 """For tarball like artifacts, extracts and prepares contents."""
139 pass
140
Chris Sosa76e44b92013-01-31 12:11:38 -0800141
142 def Process(self, no_wait):
143 """Main call point to all artifacts. Downloads and Stages artifact.
144
145 Downloads and Stages artifact from Google Storage to the install directory
146 specified in the constructor. It multi-thread safe and does not overwrite
147 the artifact if it's already been downloaded or being downloaded. After
148 processing, leaves behind a marker to indicate to future invocations that
149 the artifact has already been staged based on the name of the artifact.
150
151 Do not override as it modifies important private variables, ensures thread
152 safety, and maintains cache semantics.
153
154 Note: this may be a blocking call when the artifact is already in the
155 process of being staged.
156
157 Args:
158 no_wait: If True, don't block waiting for artifact to exist if we fail to
159 immediately find it.
160
161 Raises:
162 ArtifactDownloadError: If the artifact fails to download from Google
163 Storage for any reason or that the regexp
164 defined by name is not specific enough.
165 """
166 if not self._process_lock:
167 self._process_lock = _build_artifact_locks.lock(
168 os.path.join(self.install_dir, self.name))
169
170 with self._process_lock:
171 common_util.MkDirP(self.install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700172 if not self.ArtifactStaged():
Chris Sosa76e44b92013-01-31 12:11:38 -0800173 # If the artifact should already have been uploaded, don't waste
174 # cycles waiting around for it to exist.
175 timeout = 1 if no_wait else 10
Dan Shie37f8fe2013-08-09 16:10:29 -0700176 self.WaitForArtifactToExist(timeout)
Chris Sosa76e44b92013-01-31 12:11:38 -0800177 self._Download()
joychen0a8e34e2013-06-24 17:58:36 -0700178 self._Setup()
Chris Sosa76e44b92013-01-31 12:11:38 -0800179 self._MarkArtifactStaged()
180 else:
181 self._Log('%s is already staged.', self)
182
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700183 def __str__(self):
184 """String representation for the download."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800185 return '->'.join(['%s/%s' % (self.archive_url, self.name),
joychen0a8e34e2013-06-24 17:58:36 -0700186 self.install_dir])
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700187
188
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700189class AUTestPayloadBuildArtifact(BuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700190 """Wrapper for AUTest delta payloads which need additional setup."""
joychen0a8e34e2013-06-24 17:58:36 -0700191 def _Setup(self):
192 super(AUTestPayloadBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700193
Chris Sosa76e44b92013-01-31 12:11:38 -0800194 # Rename to update.gz.
195 install_path = os.path.join(self.install_dir, self.name)
joychen3cb228e2013-06-12 12:13:13 -0700196 new_install_path = os.path.join(self.install_dir,
joychen7c2054a2013-07-25 11:14:07 -0700197 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800198 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700199
200
Chris Sosa76e44b92013-01-31 12:11:38 -0800201# TODO(sosa): Change callers to make this artifact more sane.
202class DeltaPayloadsArtifact(BuildArtifact):
203 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700204
Chris Sosa76e44b92013-01-31 12:11:38 -0800205 This artifact is super strange. It custom handles directories and
206 pulls in all delta payloads. We can't specify exactly what we want
207 because unlike other artifacts, this one does not conform to something a
208 client might know. The client doesn't know the version of n-1 or whether it
209 was even generated.
210 """
211 def __init__(self, *args):
212 super(DeltaPayloadsArtifact, self).__init__(*args)
213 self.single_name = False # Expect multiple deltas
214 nton_name = 'chromeos_%s%s' % (self.build, self.name)
215 mton_name = 'chromeos_(?!%s)%s' % (self.build, self.name)
216 nton_install_dir = os.path.join(self.install_dir, _AU_BASE,
217 self.build + _NTON_DIR_SUFFIX)
218 mton_install_dir = os.path.join(self.install_dir, _AU_BASE,
219 self.build + _MTON_DIR_SUFFIX)
220 self._sub_artifacts = [
221 AUTestPayloadBuildArtifact(mton_install_dir, self.archive_url,
222 mton_name, self.build),
223 AUTestPayloadBuildArtifact(nton_install_dir, self.archive_url,
224 nton_name, self.build)]
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700225
Chris Sosa76e44b92013-01-31 12:11:38 -0800226 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700227 """With sub-artifacts we do everything in _Setup()."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800228 pass
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700229
joychen0a8e34e2013-06-24 17:58:36 -0700230 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800231 """Process each sub-artifact. Only error out if none can be found."""
232 for artifact in self._sub_artifacts:
233 try:
234 artifact.Process(no_wait=True)
235 # Setup symlink so that AU will work for this payload.
236 os.symlink(
joychen25d25972013-07-30 14:54:16 -0700237 os.path.join(os.pardir, os.pardir,
joychen121fc9b2013-08-02 14:30:30 -0700238 devserver_constants.STATEFUL_FILE),
joychen25d25972013-07-30 14:54:16 -0700239 os.path.join(artifact.install_dir,
joychen121fc9b2013-08-02 14:30:30 -0700240 devserver_constants.STATEFUL_FILE))
Chris Sosa76e44b92013-01-31 12:11:38 -0800241 except ArtifactDownloadError as e:
242 self._Log('Could not process %s: %s', artifact, e)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700243
Chris Sosa76e44b92013-01-31 12:11:38 -0800244
245class BundledBuildArtifact(BuildArtifact):
246 """A single build artifact bundle e.g. zip file or tar file."""
247 def __init__(self, install_dir, archive_url, name, build,
248 files_to_extract=None, exclude=None):
249 """Takes BuildArtifacts are with two additional args.
250
251 Additional args:
252 files_to_extract: A list of files to extract. If set to None, extract
253 all files.
254 exclude: A list of files to exclude. If None, no files are excluded.
255 """
256 super(BundledBuildArtifact, self).__init__(install_dir, archive_url, name,
257 build)
258 self._files_to_extract = files_to_extract
259 self._exclude = exclude
260
261 # We modify the marker so that it is unique to what was staged.
262 if files_to_extract:
263 self.marker_name = self._SanitizeName(
264 '_'.join(['.' + self.name] + files_to_extract))
265
266 def _Extract(self):
267 """Extracts the bundle into install_dir. Must be overridden.
268
269 If set, uses files_to_extract to only extract those items. If set, use
270 exclude to exclude specific files.
271 """
272 raise NotImplementedError()
273
joychen0a8e34e2013-06-24 17:58:36 -0700274 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800275 self._Extract()
276
277
278class TarballBuildArtifact(BundledBuildArtifact):
279 """Artifact for tar and tarball files."""
280
281 def _Extract(self):
282 """Extracts a tarball using tar.
283
284 Detects whether the tarball is compressed or not based on the file
285 extension and extracts the tarball into the install_path.
286 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700287 try:
joychen0a8e34e2013-06-24 17:58:36 -0700288 common_util.ExtractTarball(self.install_path, self.install_dir,
Simran Basi4baad082013-02-14 13:39:18 -0800289 files_to_extract=self._files_to_extract,
290 excluded_files=self._exclude)
291 except common_util.CommonUtilError as e:
292 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700293
294
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700295class AutotestTarballBuildArtifact(TarballBuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700296 """Wrapper around the autotest tarball to download from gsutil."""
297
joychen0a8e34e2013-06-24 17:58:36 -0700298 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800299 """Extracts the tarball into the install path excluding test suites."""
joychen0a8e34e2013-06-24 17:58:36 -0700300 super(AutotestTarballBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700301
Chris Sosa76e44b92013-01-31 12:11:38 -0800302 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700303 autotest_dir = os.path.join(self.install_dir,
304 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700305 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
306 if not os.path.exists(autotest_pkgs_dir):
307 os.makedirs(autotest_pkgs_dir)
308
309 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800310 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
311 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700312 try:
joychen0a8e34e2013-06-24 17:58:36 -0700313 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700314 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800315 raise ArtifactDownloadError(
316 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700317 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700318 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700319
Chris Masone816e38c2012-05-02 12:22:36 -0700320
Chris Sosa76e44b92013-01-31 12:11:38 -0800321class ZipfileBuildArtifact(BundledBuildArtifact):
322 """A downloadable artifact that is a zipfile."""
Chris Masone816e38c2012-05-02 12:22:36 -0700323
Chris Sosa76e44b92013-01-31 12:11:38 -0800324 def _Extract(self):
325 """Extracts files into the install path."""
326 # Unzip is weird. It expects its args before any excepts and expects its
327 # excepts in a list following the -x.
joychen0a8e34e2013-06-24 17:58:36 -0700328 cmd = ['unzip', '-o', self.install_path, '-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800329 if self._files_to_extract:
330 cmd.extend(self._files_to_extract)
Chris Masone816e38c2012-05-02 12:22:36 -0700331
Chris Sosa76e44b92013-01-31 12:11:38 -0800332 if self._exclude:
333 cmd.append('-x')
334 cmd.extend(self._exclude)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700335
336 try:
Chris Sosa76e44b92013-01-31 12:11:38 -0800337 subprocess.check_call(cmd)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700338 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800339 raise ArtifactDownloadError(
340 'An error occurred when attempting to unzip %s:\n%s' %
joychen0a8e34e2013-06-24 17:58:36 -0700341 (self.install_path, e))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700342
Gilad Arnold6f99b982012-09-12 10:49:40 -0700343
Chris Sosa76e44b92013-01-31 12:11:38 -0800344class ImplDescription(object):
345 """Data wrapper that describes an artifact's implementation."""
346 def __init__(self, artifact_class, name, *additional_args):
347 """Constructor:
348
349 Args:
350 artifact_class: BuildArtifact class to use for the artifact.
351 name: name to use to identify artifact (see BuildArtifact.name)
352 additional_args: If sub-class uses additional args, these are passed
353 through to them.
354 """
355 self.artifact_class = artifact_class
356 self.name = name
357 self.additional_args = additional_args
358
Chris Sosa968a1062013-08-02 17:42:50 -0700359 def __repr__(self):
360 return '%s_%s' % (self.artifact_class, self.name)
361
Chris Sosa76e44b92013-01-31 12:11:38 -0800362
363# Maps artifact names to their implementation description.
364# Please note, it is good practice to use constants for these names if you're
365# going to re-use the names ANYWHERE else in the devserver code.
366ARTIFACT_IMPLEMENTATION_MAP = {
367 artifact_info.FULL_PAYLOAD:
368 ImplDescription(AUTestPayloadBuildArtifact, '.*_full_.*'),
369 artifact_info.DELTA_PAYLOADS:
370 ImplDescription(DeltaPayloadsArtifact, '.*_delta_.*'),
371 artifact_info.STATEFUL_PAYLOAD:
joychen121fc9b2013-08-02 14:30:30 -0700372 ImplDescription(BuildArtifact, devserver_constants.STATEFUL_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800373
374 artifact_info.BASE_IMAGE:
375 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
joychen921e1fb2013-06-28 11:12:20 -0700376 [devserver_constants.BASE_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800377 artifact_info.RECOVERY_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700378 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
379 [devserver_constants.RECOVERY_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 artifact_info.TEST_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700381 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
382 [devserver_constants.TEST_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800383
384 artifact_info.AUTOTEST:
385 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_FILE, None,
386 ['autotest/test_suites']),
387 artifact_info.TEST_SUITES:
388 ImplDescription(TarballBuildArtifact, TEST_SUITES_FILE),
389 artifact_info.AU_SUITE:
Chris Sosaa56c4032013-03-17 21:59:54 -0700390 ImplDescription(TarballBuildArtifact, AU_SUITE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800391
392 artifact_info.FIRMWARE:
393 ImplDescription(BuildArtifact, FIRMWARE_FILE),
394 artifact_info.SYMBOLS:
395 ImplDescription(TarballBuildArtifact, DEBUG_SYMBOLS_FILE,
396 ['debug/breakpad']),
beepsc3d0f872013-07-31 21:50:40 -0700397
398 artifact_info.FACTORY_IMAGE:
399 ImplDescription(ZipfileBuildArtifact, FACTORY_FILE,
400 [devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800401}
402
Chris Sosa968a1062013-08-02 17:42:50 -0700403# Add all the paygen_au artifacts in one go.
404ARTIFACT_IMPLEMENTATION_MAP.update({
405 artifact_info.PAYGEN_AU_SUITE_TEMPLATE % { 'channel': c }: ImplDescription(
406 TarballBuildArtifact, PAYGEN_AU_SUITE_FILE_TEMPLATE % { 'channel': c })
407 for c in devserver_constants.CHANNELS
408})
409
Chris Sosa76e44b92013-01-31 12:11:38 -0800410
411class ArtifactFactory(object):
412 """A factory class that generates build artifacts from artifact names."""
413
Chris Sosa6b0c6172013-08-05 17:01:33 -0700414 def __init__(self, download_dir, archive_url, artifacts, files,
415 build):
Chris Sosa76e44b92013-01-31 12:11:38 -0800416 """Initalizes the member variables for the factory.
417
418 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800419 archive_url: the Google Storage url of the bucket where the debug
420 symbols for the desired build are stored.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700421 artifacts: List of artifacts to stage. These artifacts must be
422 defined in artifact_info.py and have a mapping in the
423 ARTIFACT_IMPLEMENTATION_MAP.
424 files: List of files to stage. These files are just downloaded and staged
425 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800426 build: The name of the build.
427 """
joychen0a8e34e2013-06-24 17:58:36 -0700428 self.download_dir = download_dir
Chris Sosa76e44b92013-01-31 12:11:38 -0800429 self.archive_url = archive_url
Chris Sosa6b0c6172013-08-05 17:01:33 -0700430 self.artifacts = artifacts
431 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800432 self.build = build
433
434 @staticmethod
Chris Sosa6b0c6172013-08-05 17:01:33 -0700435 def _GetDescriptionComponents(name, is_artifact):
436 """Returns a tuple of for BuildArtifact class, name, and additional args.
437
438 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
439 """
440
441 if is_artifact:
442 description = ARTIFACT_IMPLEMENTATION_MAP[name]
443 else:
444 description = ImplDescription(BuildArtifact, name)
445
Chris Sosa76e44b92013-01-31 12:11:38 -0800446 return (description.artifact_class, description.name,
447 description.additional_args)
448
Chris Sosa6b0c6172013-08-05 17:01:33 -0700449 def _Artifacts(self, names, is_artifact):
450 """Returns an iterable of BuildArtifacts from |names|.
451
452 If is_artifact is true, then these names define artifacts that must exist in
453 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
454 basic BuildArtifacts.
455
456 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
457 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800458 artifacts = []
Chris Sosa6b0c6172013-08-05 17:01:33 -0700459 for name in names:
Chris Sosa76e44b92013-01-31 12:11:38 -0800460 artifact_class, path, args = self._GetDescriptionComponents(
Chris Sosa6b0c6172013-08-05 17:01:33 -0700461 name, is_artifact)
joychen0a8e34e2013-06-24 17:58:36 -0700462 artifacts.append(artifact_class(self.download_dir, self.archive_url, path,
Chris Sosa76e44b92013-01-31 12:11:38 -0800463 self.build, *args))
464
465 return artifacts
466
467 def RequiredArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700468 """Returns an iterable of BuildArtifacts for the factory's artifacts.
469
470 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
471 """
472 artifacts = []
473 if self.artifacts:
474 artifacts.extend(self._Artifacts(self.artifacts, True))
475 if self.files:
476 artifacts.extend(self._Artifacts(self.files, False))
477
478 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800479
480 def OptionalArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700481 """Returns an iterable of BuildArtifacts that should be cached.
482
483 Raises: KeyError if an optional artifact doesn't exist in
484 ARTIFACT_IMPLEMENTATION_MAP yet defined in
485 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
486 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800487 optional_names = set()
488 for artifact_name, optional_list in (
489 artifact_info.REQUESTED_TO_OPTIONAL_MAP.iteritems()):
490 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700491 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800492 optional_names = optional_names.union(optional_list)
493
Chris Sosa6b0c6172013-08-05 17:01:33 -0700494 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700495
496
497# A simple main to verify correctness of the artifact map when making simple
498# name changes.
499if __name__ == '__main__':
500 print 'ARTIFACT IMPLEMENTATION MAP (for debugging)'
501 print 'FORMAT: ARTIFACT -> IMPLEMENTATION (<class>_file)'
502 for key, value in sorted(ARTIFACT_IMPLEMENTATION_MAP.items()):
503 print '%s -> %s' % (key, value)