blob: 432c4b2d05eb5866b59d48f9c15ab59da4302458 [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
Chris Sosab26b1202013-08-16 16:40:55 -0700188 def __repr__(self):
189 return str(self)
190
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700191
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700192class AUTestPayloadBuildArtifact(BuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700193 """Wrapper for AUTest delta payloads which need additional setup."""
joychen0a8e34e2013-06-24 17:58:36 -0700194 def _Setup(self):
195 super(AUTestPayloadBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700196
Chris Sosa76e44b92013-01-31 12:11:38 -0800197 # Rename to update.gz.
198 install_path = os.path.join(self.install_dir, self.name)
joychen3cb228e2013-06-12 12:13:13 -0700199 new_install_path = os.path.join(self.install_dir,
joychen7c2054a2013-07-25 11:14:07 -0700200 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800201 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700202
203
Chris Sosa76e44b92013-01-31 12:11:38 -0800204# TODO(sosa): Change callers to make this artifact more sane.
205class DeltaPayloadsArtifact(BuildArtifact):
206 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700207
Chris Sosa76e44b92013-01-31 12:11:38 -0800208 This artifact is super strange. It custom handles directories and
209 pulls in all delta payloads. We can't specify exactly what we want
210 because unlike other artifacts, this one does not conform to something a
211 client might know. The client doesn't know the version of n-1 or whether it
212 was even generated.
213 """
214 def __init__(self, *args):
215 super(DeltaPayloadsArtifact, self).__init__(*args)
216 self.single_name = False # Expect multiple deltas
217 nton_name = 'chromeos_%s%s' % (self.build, self.name)
218 mton_name = 'chromeos_(?!%s)%s' % (self.build, self.name)
219 nton_install_dir = os.path.join(self.install_dir, _AU_BASE,
220 self.build + _NTON_DIR_SUFFIX)
221 mton_install_dir = os.path.join(self.install_dir, _AU_BASE,
222 self.build + _MTON_DIR_SUFFIX)
223 self._sub_artifacts = [
224 AUTestPayloadBuildArtifact(mton_install_dir, self.archive_url,
225 mton_name, self.build),
226 AUTestPayloadBuildArtifact(nton_install_dir, self.archive_url,
227 nton_name, self.build)]
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700228
Chris Sosa76e44b92013-01-31 12:11:38 -0800229 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700230 """With sub-artifacts we do everything in _Setup()."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800231 pass
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700232
joychen0a8e34e2013-06-24 17:58:36 -0700233 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800234 """Process each sub-artifact. Only error out if none can be found."""
235 for artifact in self._sub_artifacts:
236 try:
237 artifact.Process(no_wait=True)
238 # Setup symlink so that AU will work for this payload.
239 os.symlink(
joychen25d25972013-07-30 14:54:16 -0700240 os.path.join(os.pardir, os.pardir,
joychen121fc9b2013-08-02 14:30:30 -0700241 devserver_constants.STATEFUL_FILE),
joychen25d25972013-07-30 14:54:16 -0700242 os.path.join(artifact.install_dir,
joychen121fc9b2013-08-02 14:30:30 -0700243 devserver_constants.STATEFUL_FILE))
Chris Sosa76e44b92013-01-31 12:11:38 -0800244 except ArtifactDownloadError as e:
245 self._Log('Could not process %s: %s', artifact, e)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700246
Chris Sosa76e44b92013-01-31 12:11:38 -0800247
248class BundledBuildArtifact(BuildArtifact):
249 """A single build artifact bundle e.g. zip file or tar file."""
250 def __init__(self, install_dir, archive_url, name, build,
251 files_to_extract=None, exclude=None):
252 """Takes BuildArtifacts are with two additional args.
253
254 Additional args:
255 files_to_extract: A list of files to extract. If set to None, extract
256 all files.
257 exclude: A list of files to exclude. If None, no files are excluded.
258 """
259 super(BundledBuildArtifact, self).__init__(install_dir, archive_url, name,
260 build)
261 self._files_to_extract = files_to_extract
262 self._exclude = exclude
263
264 # We modify the marker so that it is unique to what was staged.
265 if files_to_extract:
266 self.marker_name = self._SanitizeName(
267 '_'.join(['.' + self.name] + files_to_extract))
268
269 def _Extract(self):
270 """Extracts the bundle into install_dir. Must be overridden.
271
272 If set, uses files_to_extract to only extract those items. If set, use
273 exclude to exclude specific files.
274 """
275 raise NotImplementedError()
276
joychen0a8e34e2013-06-24 17:58:36 -0700277 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800278 self._Extract()
279
280
281class TarballBuildArtifact(BundledBuildArtifact):
282 """Artifact for tar and tarball files."""
283
284 def _Extract(self):
285 """Extracts a tarball using tar.
286
287 Detects whether the tarball is compressed or not based on the file
288 extension and extracts the tarball into the install_path.
289 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700290 try:
joychen0a8e34e2013-06-24 17:58:36 -0700291 common_util.ExtractTarball(self.install_path, self.install_dir,
Simran Basi4baad082013-02-14 13:39:18 -0800292 files_to_extract=self._files_to_extract,
293 excluded_files=self._exclude)
294 except common_util.CommonUtilError as e:
295 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700296
297
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700298class AutotestTarballBuildArtifact(TarballBuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700299 """Wrapper around the autotest tarball to download from gsutil."""
300
joychen0a8e34e2013-06-24 17:58:36 -0700301 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800302 """Extracts the tarball into the install path excluding test suites."""
joychen0a8e34e2013-06-24 17:58:36 -0700303 super(AutotestTarballBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700304
Chris Sosa76e44b92013-01-31 12:11:38 -0800305 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700306 autotest_dir = os.path.join(self.install_dir,
307 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700308 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
309 if not os.path.exists(autotest_pkgs_dir):
310 os.makedirs(autotest_pkgs_dir)
311
312 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800313 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
314 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700315 try:
joychen0a8e34e2013-06-24 17:58:36 -0700316 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700317 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800318 raise ArtifactDownloadError(
319 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700320 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700321 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700322
Chris Masone816e38c2012-05-02 12:22:36 -0700323
Chris Sosa76e44b92013-01-31 12:11:38 -0800324class ZipfileBuildArtifact(BundledBuildArtifact):
325 """A downloadable artifact that is a zipfile."""
Chris Masone816e38c2012-05-02 12:22:36 -0700326
Chris Sosa76e44b92013-01-31 12:11:38 -0800327 def _Extract(self):
328 """Extracts files into the install path."""
329 # Unzip is weird. It expects its args before any excepts and expects its
330 # excepts in a list following the -x.
joychen0a8e34e2013-06-24 17:58:36 -0700331 cmd = ['unzip', '-o', self.install_path, '-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800332 if self._files_to_extract:
333 cmd.extend(self._files_to_extract)
Chris Masone816e38c2012-05-02 12:22:36 -0700334
Chris Sosa76e44b92013-01-31 12:11:38 -0800335 if self._exclude:
336 cmd.append('-x')
337 cmd.extend(self._exclude)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700338
339 try:
Chris Sosa76e44b92013-01-31 12:11:38 -0800340 subprocess.check_call(cmd)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700341 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800342 raise ArtifactDownloadError(
343 'An error occurred when attempting to unzip %s:\n%s' %
joychen0a8e34e2013-06-24 17:58:36 -0700344 (self.install_path, e))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700345
Gilad Arnold6f99b982012-09-12 10:49:40 -0700346
Chris Sosa76e44b92013-01-31 12:11:38 -0800347class ImplDescription(object):
348 """Data wrapper that describes an artifact's implementation."""
349 def __init__(self, artifact_class, name, *additional_args):
350 """Constructor:
351
352 Args:
353 artifact_class: BuildArtifact class to use for the artifact.
354 name: name to use to identify artifact (see BuildArtifact.name)
355 additional_args: If sub-class uses additional args, these are passed
356 through to them.
357 """
358 self.artifact_class = artifact_class
359 self.name = name
360 self.additional_args = additional_args
361
Chris Sosa968a1062013-08-02 17:42:50 -0700362 def __repr__(self):
363 return '%s_%s' % (self.artifact_class, self.name)
364
Chris Sosa76e44b92013-01-31 12:11:38 -0800365
366# Maps artifact names to their implementation description.
367# Please note, it is good practice to use constants for these names if you're
368# going to re-use the names ANYWHERE else in the devserver code.
369ARTIFACT_IMPLEMENTATION_MAP = {
370 artifact_info.FULL_PAYLOAD:
371 ImplDescription(AUTestPayloadBuildArtifact, '.*_full_.*'),
372 artifact_info.DELTA_PAYLOADS:
373 ImplDescription(DeltaPayloadsArtifact, '.*_delta_.*'),
374 artifact_info.STATEFUL_PAYLOAD:
joychen121fc9b2013-08-02 14:30:30 -0700375 ImplDescription(BuildArtifact, devserver_constants.STATEFUL_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800376
377 artifact_info.BASE_IMAGE:
378 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
joychen921e1fb2013-06-28 11:12:20 -0700379 [devserver_constants.BASE_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 artifact_info.RECOVERY_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700381 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
382 [devserver_constants.RECOVERY_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800383 artifact_info.TEST_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700384 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
385 [devserver_constants.TEST_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800386
387 artifact_info.AUTOTEST:
388 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_FILE, None,
389 ['autotest/test_suites']),
390 artifact_info.TEST_SUITES:
391 ImplDescription(TarballBuildArtifact, TEST_SUITES_FILE),
392 artifact_info.AU_SUITE:
Chris Sosaa56c4032013-03-17 21:59:54 -0700393 ImplDescription(TarballBuildArtifact, AU_SUITE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800394
395 artifact_info.FIRMWARE:
396 ImplDescription(BuildArtifact, FIRMWARE_FILE),
397 artifact_info.SYMBOLS:
398 ImplDescription(TarballBuildArtifact, DEBUG_SYMBOLS_FILE,
399 ['debug/breakpad']),
beepsc3d0f872013-07-31 21:50:40 -0700400
401 artifact_info.FACTORY_IMAGE:
402 ImplDescription(ZipfileBuildArtifact, FACTORY_FILE,
403 [devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800404}
405
Chris Sosa968a1062013-08-02 17:42:50 -0700406# Add all the paygen_au artifacts in one go.
407ARTIFACT_IMPLEMENTATION_MAP.update({
408 artifact_info.PAYGEN_AU_SUITE_TEMPLATE % { 'channel': c }: ImplDescription(
409 TarballBuildArtifact, PAYGEN_AU_SUITE_FILE_TEMPLATE % { 'channel': c })
410 for c in devserver_constants.CHANNELS
411})
412
Chris Sosa76e44b92013-01-31 12:11:38 -0800413
414class ArtifactFactory(object):
415 """A factory class that generates build artifacts from artifact names."""
416
Chris Sosa6b0c6172013-08-05 17:01:33 -0700417 def __init__(self, download_dir, archive_url, artifacts, files,
418 build):
Chris Sosa76e44b92013-01-31 12:11:38 -0800419 """Initalizes the member variables for the factory.
420
421 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800422 archive_url: the Google Storage url of the bucket where the debug
423 symbols for the desired build are stored.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700424 artifacts: List of artifacts to stage. These artifacts must be
425 defined in artifact_info.py and have a mapping in the
426 ARTIFACT_IMPLEMENTATION_MAP.
427 files: List of files to stage. These files are just downloaded and staged
428 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800429 build: The name of the build.
430 """
joychen0a8e34e2013-06-24 17:58:36 -0700431 self.download_dir = download_dir
Chris Sosa76e44b92013-01-31 12:11:38 -0800432 self.archive_url = archive_url
Chris Sosa6b0c6172013-08-05 17:01:33 -0700433 self.artifacts = artifacts
434 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800435 self.build = build
436
437 @staticmethod
Chris Sosa6b0c6172013-08-05 17:01:33 -0700438 def _GetDescriptionComponents(name, is_artifact):
439 """Returns a tuple of for BuildArtifact class, name, and additional args.
440
441 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
442 """
443
444 if is_artifact:
445 description = ARTIFACT_IMPLEMENTATION_MAP[name]
446 else:
447 description = ImplDescription(BuildArtifact, name)
448
Chris Sosa76e44b92013-01-31 12:11:38 -0800449 return (description.artifact_class, description.name,
450 description.additional_args)
451
Chris Sosa6b0c6172013-08-05 17:01:33 -0700452 def _Artifacts(self, names, is_artifact):
453 """Returns an iterable of BuildArtifacts from |names|.
454
455 If is_artifact is true, then these names define artifacts that must exist in
456 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
457 basic BuildArtifacts.
458
459 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
460 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800461 artifacts = []
Chris Sosa6b0c6172013-08-05 17:01:33 -0700462 for name in names:
Chris Sosa76e44b92013-01-31 12:11:38 -0800463 artifact_class, path, args = self._GetDescriptionComponents(
Chris Sosa6b0c6172013-08-05 17:01:33 -0700464 name, is_artifact)
joychen0a8e34e2013-06-24 17:58:36 -0700465 artifacts.append(artifact_class(self.download_dir, self.archive_url, path,
Chris Sosa76e44b92013-01-31 12:11:38 -0800466 self.build, *args))
467
468 return artifacts
469
470 def RequiredArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700471 """Returns an iterable of BuildArtifacts for the factory's artifacts.
472
473 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
474 """
475 artifacts = []
476 if self.artifacts:
477 artifacts.extend(self._Artifacts(self.artifacts, True))
478 if self.files:
479 artifacts.extend(self._Artifacts(self.files, False))
480
481 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800482
483 def OptionalArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700484 """Returns an iterable of BuildArtifacts that should be cached.
485
486 Raises: KeyError if an optional artifact doesn't exist in
487 ARTIFACT_IMPLEMENTATION_MAP yet defined in
488 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
489 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800490 optional_names = set()
491 for artifact_name, optional_list in (
492 artifact_info.REQUESTED_TO_OPTIONAL_MAP.iteritems()):
493 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700494 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800495 optional_names = optional_names.union(optional_list)
496
Chris Sosa6b0c6172013-08-05 17:01:33 -0700497 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700498
499
500# A simple main to verify correctness of the artifact map when making simple
501# name changes.
502if __name__ == '__main__':
503 print 'ARTIFACT IMPLEMENTATION MAP (for debugging)'
504 print 'FORMAT: ARTIFACT -> IMPLEMENTATION (<class>_file)'
505 for key, value in sorted(ARTIFACT_IMPLEMENTATION_MAP.items()):
506 print '%s -> %s' % (key, value)