blob: 2970ea178d1a4340046dc0eccb3f0cf913993655 [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'
31FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
32IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080033TEST_SUITES_FILE = 'test_suites.tar.bz2'
34
35_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070036
37
38class ArtifactDownloadError(Exception):
39 """Error used to signify an issue processing an artifact."""
40 pass
41
42
Gilad Arnoldc65330c2012-09-20 15:17:48 -070043class BuildArtifact(log_util.Loggable):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070044 """Wrapper around an artifact to download from gsutil.
45
46 The purpose of this class is to download objects from Google Storage
47 and install them to a local directory. There are two main functions, one to
48 download/prepare the artifacts in to a temporary staging area and the second
49 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080050
51 Class members:
52 archive_url = archive_url
53 name: Name given for artifact -- either a regexp or name of the artifact in
54 gs. If a regexp, is modified to actual name before call to _Download.
55 build: The version of the build i.e. R26-2342.0.0.
56 marker_name: Name used to define the lock marker for the artifacts to
57 prevent it from being re-downloaded. By default based on name
58 but can be overriden by children.
joychen0a8e34e2013-06-24 17:58:36 -070059 install_path: Path to artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -080060 install_dir: The final location where the artifact should be staged to.
61 single_name: If True the name given should only match one item. Note, if not
62 True, self.name will become a list of items returned.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070063 """
Chris Sosa76e44b92013-01-31 12:11:38 -080064 def __init__(self, install_dir, archive_url, name, build):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070065 """Args:
Chris Sosa76e44b92013-01-31 12:11:38 -080066 install_dir: Where to install the artifact.
67 archive_url: The Google Storage path to find the artifact.
68 name: Identifying name to be used to find/store the artifact.
69 build: The name of the build e.g. board/release.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070070 """
Chris Sosa6a3697f2013-01-29 16:44:43 -080071 super(BuildArtifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070072
Chris Sosa76e44b92013-01-31 12:11:38 -080073 # In-memory lock to keep the devserver from colliding with itself while
74 # attempting to stage the same artifact.
75 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070076
Chris Sosa76e44b92013-01-31 12:11:38 -080077 self.archive_url = archive_url
78 self.name = name
79 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -070080
Chris Sosa76e44b92013-01-31 12:11:38 -080081 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -070082
joychen0a8e34e2013-06-24 17:58:36 -070083 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -070084
Chris Sosa76e44b92013-01-31 12:11:38 -080085 self.install_dir = install_dir
86
87 self.single_name = True
88
89 @staticmethod
90 def _SanitizeName(name):
91 """Sanitizes name to be used for creating a file on the filesystem.
92
93 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
94 """
95 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
96
97 def _ArtifactStaged(self):
98 """Returns True if artifact is already staged."""
99 return os.path.exists(os.path.join(self.install_dir, self.marker_name))
100
101 def _MarkArtifactStaged(self):
102 """Marks the artifact as staged."""
103 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
104 f.write('')
105
106 def _WaitForArtifactToExist(self, timeout):
107 """Waits for artifact to exist and sets self.name to appropriate name."""
108 names = gsutil_util.GetGSNamesWithWait(
109 self.name, self.archive_url, str(self), single_item=self.single_name,
110 timeout=timeout)
111 if not names:
112 raise ArtifactDownloadError('Could not find %s in Google Storage' %
113 self.name)
114
115 if self.single_name:
116 if len(names) > 1:
117 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
118
119 self.name = names[0]
120 else:
121 self.name = names
122
123 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700124 """Downloads artifact from Google Storage to a local directory."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800125 gs_path = '/'.join([self.archive_url, self.name])
joychen0a8e34e2013-06-24 17:58:36 -0700126 self.install_path = os.path.join(self.install_dir, self.name)
127 gsutil_util.DownloadFromGS(gs_path, self.install_path)
Chris Sosa76e44b92013-01-31 12:11:38 -0800128
joychen0a8e34e2013-06-24 17:58:36 -0700129 def _Setup(self):
130 """For tarball like artifacts, extracts and prepares contents."""
131 pass
132
Chris Sosa76e44b92013-01-31 12:11:38 -0800133
134 def Process(self, no_wait):
135 """Main call point to all artifacts. Downloads and Stages artifact.
136
137 Downloads and Stages artifact from Google Storage to the install directory
138 specified in the constructor. It multi-thread safe and does not overwrite
139 the artifact if it's already been downloaded or being downloaded. After
140 processing, leaves behind a marker to indicate to future invocations that
141 the artifact has already been staged based on the name of the artifact.
142
143 Do not override as it modifies important private variables, ensures thread
144 safety, and maintains cache semantics.
145
146 Note: this may be a blocking call when the artifact is already in the
147 process of being staged.
148
149 Args:
150 no_wait: If True, don't block waiting for artifact to exist if we fail to
151 immediately find it.
152
153 Raises:
154 ArtifactDownloadError: If the artifact fails to download from Google
155 Storage for any reason or that the regexp
156 defined by name is not specific enough.
157 """
158 if not self._process_lock:
159 self._process_lock = _build_artifact_locks.lock(
160 os.path.join(self.install_dir, self.name))
161
162 with self._process_lock:
163 common_util.MkDirP(self.install_dir)
164 if not self._ArtifactStaged():
165 # If the artifact should already have been uploaded, don't waste
166 # cycles waiting around for it to exist.
167 timeout = 1 if no_wait else 10
168 self._WaitForArtifactToExist(timeout)
169 self._Download()
joychen0a8e34e2013-06-24 17:58:36 -0700170 self._Setup()
Chris Sosa76e44b92013-01-31 12:11:38 -0800171 self._MarkArtifactStaged()
172 else:
173 self._Log('%s is already staged.', self)
174
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700175 def __str__(self):
176 """String representation for the download."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800177 return '->'.join(['%s/%s' % (self.archive_url, self.name),
joychen0a8e34e2013-06-24 17:58:36 -0700178 self.install_dir])
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700179
180
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700181class AUTestPayloadBuildArtifact(BuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700182 """Wrapper for AUTest delta payloads which need additional setup."""
joychen0a8e34e2013-06-24 17:58:36 -0700183 def _Setup(self):
184 super(AUTestPayloadBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700185
Chris Sosa76e44b92013-01-31 12:11:38 -0800186 # Rename to update.gz.
187 install_path = os.path.join(self.install_dir, self.name)
joychen3cb228e2013-06-12 12:13:13 -0700188 new_install_path = os.path.join(self.install_dir,
joychen7c2054a2013-07-25 11:14:07 -0700189 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800190 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700191
192
Chris Sosa76e44b92013-01-31 12:11:38 -0800193# TODO(sosa): Change callers to make this artifact more sane.
194class DeltaPayloadsArtifact(BuildArtifact):
195 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700196
Chris Sosa76e44b92013-01-31 12:11:38 -0800197 This artifact is super strange. It custom handles directories and
198 pulls in all delta payloads. We can't specify exactly what we want
199 because unlike other artifacts, this one does not conform to something a
200 client might know. The client doesn't know the version of n-1 or whether it
201 was even generated.
202 """
203 def __init__(self, *args):
204 super(DeltaPayloadsArtifact, self).__init__(*args)
205 self.single_name = False # Expect multiple deltas
206 nton_name = 'chromeos_%s%s' % (self.build, self.name)
207 mton_name = 'chromeos_(?!%s)%s' % (self.build, self.name)
208 nton_install_dir = os.path.join(self.install_dir, _AU_BASE,
209 self.build + _NTON_DIR_SUFFIX)
210 mton_install_dir = os.path.join(self.install_dir, _AU_BASE,
211 self.build + _MTON_DIR_SUFFIX)
212 self._sub_artifacts = [
213 AUTestPayloadBuildArtifact(mton_install_dir, self.archive_url,
214 mton_name, self.build),
215 AUTestPayloadBuildArtifact(nton_install_dir, self.archive_url,
216 nton_name, self.build)]
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700217
Chris Sosa76e44b92013-01-31 12:11:38 -0800218 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700219 """With sub-artifacts we do everything in _Setup()."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800220 pass
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700221
joychen0a8e34e2013-06-24 17:58:36 -0700222 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800223 """Process each sub-artifact. Only error out if none can be found."""
224 for artifact in self._sub_artifacts:
225 try:
226 artifact.Process(no_wait=True)
227 # Setup symlink so that AU will work for this payload.
228 os.symlink(
joychen25d25972013-07-30 14:54:16 -0700229 os.path.join(os.pardir, os.pardir,
230 devserver_constants.STATEFUL_UPDATE_FILE),
231 os.path.join(artifact.install_dir,
232 devserver_constants.STATEFUL_UPDATE_FILE))
Chris Sosa76e44b92013-01-31 12:11:38 -0800233 except ArtifactDownloadError as e:
234 self._Log('Could not process %s: %s', artifact, e)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700235
Chris Sosa76e44b92013-01-31 12:11:38 -0800236
237class BundledBuildArtifact(BuildArtifact):
238 """A single build artifact bundle e.g. zip file or tar file."""
239 def __init__(self, install_dir, archive_url, name, build,
240 files_to_extract=None, exclude=None):
241 """Takes BuildArtifacts are with two additional args.
242
243 Additional args:
244 files_to_extract: A list of files to extract. If set to None, extract
245 all files.
246 exclude: A list of files to exclude. If None, no files are excluded.
247 """
248 super(BundledBuildArtifact, self).__init__(install_dir, archive_url, name,
249 build)
250 self._files_to_extract = files_to_extract
251 self._exclude = exclude
252
253 # We modify the marker so that it is unique to what was staged.
254 if files_to_extract:
255 self.marker_name = self._SanitizeName(
256 '_'.join(['.' + self.name] + files_to_extract))
257
258 def _Extract(self):
259 """Extracts the bundle into install_dir. Must be overridden.
260
261 If set, uses files_to_extract to only extract those items. If set, use
262 exclude to exclude specific files.
263 """
264 raise NotImplementedError()
265
joychen0a8e34e2013-06-24 17:58:36 -0700266 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800267 self._Extract()
268
269
270class TarballBuildArtifact(BundledBuildArtifact):
271 """Artifact for tar and tarball files."""
272
273 def _Extract(self):
274 """Extracts a tarball using tar.
275
276 Detects whether the tarball is compressed or not based on the file
277 extension and extracts the tarball into the install_path.
278 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700279 try:
joychen0a8e34e2013-06-24 17:58:36 -0700280 common_util.ExtractTarball(self.install_path, self.install_dir,
Simran Basi4baad082013-02-14 13:39:18 -0800281 files_to_extract=self._files_to_extract,
282 excluded_files=self._exclude)
283 except common_util.CommonUtilError as e:
284 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700285
286
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700287class AutotestTarballBuildArtifact(TarballBuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700288 """Wrapper around the autotest tarball to download from gsutil."""
289
joychen0a8e34e2013-06-24 17:58:36 -0700290 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800291 """Extracts the tarball into the install path excluding test suites."""
joychen0a8e34e2013-06-24 17:58:36 -0700292 super(AutotestTarballBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700293
Chris Sosa76e44b92013-01-31 12:11:38 -0800294 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700295 autotest_dir = os.path.join(self.install_dir,
296 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700297 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
298 if not os.path.exists(autotest_pkgs_dir):
299 os.makedirs(autotest_pkgs_dir)
300
301 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800302 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
303 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700304 try:
joychen0a8e34e2013-06-24 17:58:36 -0700305 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700306 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800307 raise ArtifactDownloadError(
308 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700309 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700310 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700311
Chris Masone816e38c2012-05-02 12:22:36 -0700312
Chris Sosa76e44b92013-01-31 12:11:38 -0800313class ZipfileBuildArtifact(BundledBuildArtifact):
314 """A downloadable artifact that is a zipfile."""
Chris Masone816e38c2012-05-02 12:22:36 -0700315
Chris Sosa76e44b92013-01-31 12:11:38 -0800316 def _Extract(self):
317 """Extracts files into the install path."""
318 # Unzip is weird. It expects its args before any excepts and expects its
319 # excepts in a list following the -x.
joychen0a8e34e2013-06-24 17:58:36 -0700320 cmd = ['unzip', '-o', self.install_path, '-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800321 if self._files_to_extract:
322 cmd.extend(self._files_to_extract)
Chris Masone816e38c2012-05-02 12:22:36 -0700323
Chris Sosa76e44b92013-01-31 12:11:38 -0800324 if self._exclude:
325 cmd.append('-x')
326 cmd.extend(self._exclude)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700327
328 try:
Chris Sosa76e44b92013-01-31 12:11:38 -0800329 subprocess.check_call(cmd)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700330 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800331 raise ArtifactDownloadError(
332 'An error occurred when attempting to unzip %s:\n%s' %
joychen0a8e34e2013-06-24 17:58:36 -0700333 (self.install_path, e))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700334
Gilad Arnold6f99b982012-09-12 10:49:40 -0700335
Chris Sosa76e44b92013-01-31 12:11:38 -0800336class ImplDescription(object):
337 """Data wrapper that describes an artifact's implementation."""
338 def __init__(self, artifact_class, name, *additional_args):
339 """Constructor:
340
341 Args:
342 artifact_class: BuildArtifact class to use for the artifact.
343 name: name to use to identify artifact (see BuildArtifact.name)
344 additional_args: If sub-class uses additional args, these are passed
345 through to them.
346 """
347 self.artifact_class = artifact_class
348 self.name = name
349 self.additional_args = additional_args
350
Chris Sosa968a1062013-08-02 17:42:50 -0700351 def __repr__(self):
352 return '%s_%s' % (self.artifact_class, self.name)
353
Chris Sosa76e44b92013-01-31 12:11:38 -0800354
355# Maps artifact names to their implementation description.
356# Please note, it is good practice to use constants for these names if you're
357# going to re-use the names ANYWHERE else in the devserver code.
358ARTIFACT_IMPLEMENTATION_MAP = {
359 artifact_info.FULL_PAYLOAD:
360 ImplDescription(AUTestPayloadBuildArtifact, '.*_full_.*'),
361 artifact_info.DELTA_PAYLOADS:
362 ImplDescription(DeltaPayloadsArtifact, '.*_delta_.*'),
363 artifact_info.STATEFUL_PAYLOAD:
joychen25d25972013-07-30 14:54:16 -0700364 ImplDescription(BuildArtifact, devserver_constants.STATEFUL_UPDATE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800365
366 artifact_info.BASE_IMAGE:
367 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
joychen921e1fb2013-06-28 11:12:20 -0700368 [devserver_constants.BASE_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800369 artifact_info.RECOVERY_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700370 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
371 [devserver_constants.RECOVERY_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800372 artifact_info.TEST_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700373 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
374 [devserver_constants.TEST_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800375
376 artifact_info.AUTOTEST:
377 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_FILE, None,
378 ['autotest/test_suites']),
379 artifact_info.TEST_SUITES:
380 ImplDescription(TarballBuildArtifact, TEST_SUITES_FILE),
381 artifact_info.AU_SUITE:
Chris Sosaa56c4032013-03-17 21:59:54 -0700382 ImplDescription(TarballBuildArtifact, AU_SUITE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800383
384 artifact_info.FIRMWARE:
385 ImplDescription(BuildArtifact, FIRMWARE_FILE),
386 artifact_info.SYMBOLS:
387 ImplDescription(TarballBuildArtifact, DEBUG_SYMBOLS_FILE,
388 ['debug/breakpad']),
389}
390
Chris Sosa968a1062013-08-02 17:42:50 -0700391# Add all the paygen_au artifacts in one go.
392ARTIFACT_IMPLEMENTATION_MAP.update({
393 artifact_info.PAYGEN_AU_SUITE_TEMPLATE % { 'channel': c }: ImplDescription(
394 TarballBuildArtifact, PAYGEN_AU_SUITE_FILE_TEMPLATE % { 'channel': c })
395 for c in devserver_constants.CHANNELS
396})
397
Chris Sosa76e44b92013-01-31 12:11:38 -0800398
399class ArtifactFactory(object):
400 """A factory class that generates build artifacts from artifact names."""
401
joychen0a8e34e2013-06-24 17:58:36 -0700402 def __init__(self, download_dir, archive_url, artifact_names, build):
Chris Sosa76e44b92013-01-31 12:11:38 -0800403 """Initalizes the member variables for the factory.
404
405 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800406 archive_url: the Google Storage url of the bucket where the debug
407 symbols for the desired build are stored.
408 artifact_names: List of artifact names to stage.
409 build: The name of the build.
410 """
joychen0a8e34e2013-06-24 17:58:36 -0700411 self.download_dir = download_dir
Chris Sosa76e44b92013-01-31 12:11:38 -0800412 self.archive_url = archive_url
413 self.artifact_names = artifact_names
414 self.build = build
415
416 @staticmethod
417 def _GetDescriptionComponents(artifact_name):
418 """Returns a tuple of for BuildArtifact class, name, and additional args."""
419 description = ARTIFACT_IMPLEMENTATION_MAP[artifact_name]
420 return (description.artifact_class, description.name,
421 description.additional_args)
422
423 def _Artifacts(self, artifact_names):
424 """Returns an iterable of BuildArtifacts from |artifact_names|."""
425 artifacts = []
426 for artifact_name in artifact_names:
427 artifact_class, path, args = self._GetDescriptionComponents(
428 artifact_name)
joychen0a8e34e2013-06-24 17:58:36 -0700429 artifacts.append(artifact_class(self.download_dir, self.archive_url, path,
Chris Sosa76e44b92013-01-31 12:11:38 -0800430 self.build, *args))
431
432 return artifacts
433
434 def RequiredArtifacts(self):
435 """Returns an iterable of BuildArtifacts for the factory's artifacts."""
436 return self._Artifacts(self.artifact_names)
437
438 def OptionalArtifacts(self):
439 """Returns an iterable of BuildArtifacts that should be cached."""
440 optional_names = set()
441 for artifact_name, optional_list in (
442 artifact_info.REQUESTED_TO_OPTIONAL_MAP.iteritems()):
443 # We are already downloading it.
444 if artifact_name in self.artifact_names:
445 optional_names = optional_names.union(optional_list)
446
447 return self._Artifacts(optional_names - set(self.artifact_names))
Chris Sosa968a1062013-08-02 17:42:50 -0700448
449
450# A simple main to verify correctness of the artifact map when making simple
451# name changes.
452if __name__ == '__main__':
453 print 'ARTIFACT IMPLEMENTATION MAP (for debugging)'
454 print 'FORMAT: ARTIFACT -> IMPLEMENTATION (<class>_file)'
455 for key, value in sorted(ARTIFACT_IMPLEMENTATION_MAP.items()):
456 print '%s -> %s' % (key, value)