blob: 0e63fb29f9d1d27bd57de55071550bdbb2f3c541 [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
107 def _WaitForArtifactToExist(self, timeout):
108 """Waits for artifact to exist and sets self.name to appropriate name."""
109 names = gsutil_util.GetGSNamesWithWait(
110 self.name, self.archive_url, str(self), single_item=self.single_name,
111 timeout=timeout)
112 if not names:
113 raise ArtifactDownloadError('Could not find %s in Google Storage' %
114 self.name)
115
116 if self.single_name:
117 if len(names) > 1:
118 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
119
120 self.name = names[0]
121 else:
122 self.name = names
123
124 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700125 """Downloads artifact from Google Storage to a local directory."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800126 gs_path = '/'.join([self.archive_url, self.name])
joychen0a8e34e2013-06-24 17:58:36 -0700127 self.install_path = os.path.join(self.install_dir, self.name)
128 gsutil_util.DownloadFromGS(gs_path, self.install_path)
Chris Sosa76e44b92013-01-31 12:11:38 -0800129
joychen0a8e34e2013-06-24 17:58:36 -0700130 def _Setup(self):
131 """For tarball like artifacts, extracts and prepares contents."""
132 pass
133
Chris Sosa76e44b92013-01-31 12:11:38 -0800134
135 def Process(self, no_wait):
136 """Main call point to all artifacts. Downloads and Stages artifact.
137
138 Downloads and Stages artifact from Google Storage to the install directory
139 specified in the constructor. It multi-thread safe and does not overwrite
140 the artifact if it's already been downloaded or being downloaded. After
141 processing, leaves behind a marker to indicate to future invocations that
142 the artifact has already been staged based on the name of the artifact.
143
144 Do not override as it modifies important private variables, ensures thread
145 safety, and maintains cache semantics.
146
147 Note: this may be a blocking call when the artifact is already in the
148 process of being staged.
149
150 Args:
151 no_wait: If True, don't block waiting for artifact to exist if we fail to
152 immediately find it.
153
154 Raises:
155 ArtifactDownloadError: If the artifact fails to download from Google
156 Storage for any reason or that the regexp
157 defined by name is not specific enough.
158 """
159 if not self._process_lock:
160 self._process_lock = _build_artifact_locks.lock(
161 os.path.join(self.install_dir, self.name))
162
163 with self._process_lock:
164 common_util.MkDirP(self.install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700165 if not self.ArtifactStaged():
Chris Sosa76e44b92013-01-31 12:11:38 -0800166 # If the artifact should already have been uploaded, don't waste
167 # cycles waiting around for it to exist.
168 timeout = 1 if no_wait else 10
169 self._WaitForArtifactToExist(timeout)
170 self._Download()
joychen0a8e34e2013-06-24 17:58:36 -0700171 self._Setup()
Chris Sosa76e44b92013-01-31 12:11:38 -0800172 self._MarkArtifactStaged()
173 else:
174 self._Log('%s is already staged.', self)
175
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700176 def __str__(self):
177 """String representation for the download."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800178 return '->'.join(['%s/%s' % (self.archive_url, self.name),
joychen0a8e34e2013-06-24 17:58:36 -0700179 self.install_dir])
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700180
181
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700182class AUTestPayloadBuildArtifact(BuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700183 """Wrapper for AUTest delta payloads which need additional setup."""
joychen0a8e34e2013-06-24 17:58:36 -0700184 def _Setup(self):
185 super(AUTestPayloadBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700186
Chris Sosa76e44b92013-01-31 12:11:38 -0800187 # Rename to update.gz.
188 install_path = os.path.join(self.install_dir, self.name)
joychen3cb228e2013-06-12 12:13:13 -0700189 new_install_path = os.path.join(self.install_dir,
joychen7c2054a2013-07-25 11:14:07 -0700190 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800191 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700192
193
Chris Sosa76e44b92013-01-31 12:11:38 -0800194# TODO(sosa): Change callers to make this artifact more sane.
195class DeltaPayloadsArtifact(BuildArtifact):
196 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700197
Chris Sosa76e44b92013-01-31 12:11:38 -0800198 This artifact is super strange. It custom handles directories and
199 pulls in all delta payloads. We can't specify exactly what we want
200 because unlike other artifacts, this one does not conform to something a
201 client might know. The client doesn't know the version of n-1 or whether it
202 was even generated.
203 """
204 def __init__(self, *args):
205 super(DeltaPayloadsArtifact, self).__init__(*args)
206 self.single_name = False # Expect multiple deltas
207 nton_name = 'chromeos_%s%s' % (self.build, self.name)
208 mton_name = 'chromeos_(?!%s)%s' % (self.build, self.name)
209 nton_install_dir = os.path.join(self.install_dir, _AU_BASE,
210 self.build + _NTON_DIR_SUFFIX)
211 mton_install_dir = os.path.join(self.install_dir, _AU_BASE,
212 self.build + _MTON_DIR_SUFFIX)
213 self._sub_artifacts = [
214 AUTestPayloadBuildArtifact(mton_install_dir, self.archive_url,
215 mton_name, self.build),
216 AUTestPayloadBuildArtifact(nton_install_dir, self.archive_url,
217 nton_name, self.build)]
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700218
Chris Sosa76e44b92013-01-31 12:11:38 -0800219 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700220 """With sub-artifacts we do everything in _Setup()."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800221 pass
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700222
joychen0a8e34e2013-06-24 17:58:36 -0700223 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800224 """Process each sub-artifact. Only error out if none can be found."""
225 for artifact in self._sub_artifacts:
226 try:
227 artifact.Process(no_wait=True)
228 # Setup symlink so that AU will work for this payload.
229 os.symlink(
joychen25d25972013-07-30 14:54:16 -0700230 os.path.join(os.pardir, os.pardir,
231 devserver_constants.STATEFUL_UPDATE_FILE),
232 os.path.join(artifact.install_dir,
233 devserver_constants.STATEFUL_UPDATE_FILE))
Chris Sosa76e44b92013-01-31 12:11:38 -0800234 except ArtifactDownloadError as e:
235 self._Log('Could not process %s: %s', artifact, e)
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700236
Chris Sosa76e44b92013-01-31 12:11:38 -0800237
238class BundledBuildArtifact(BuildArtifact):
239 """A single build artifact bundle e.g. zip file or tar file."""
240 def __init__(self, install_dir, archive_url, name, build,
241 files_to_extract=None, exclude=None):
242 """Takes BuildArtifacts are with two additional args.
243
244 Additional args:
245 files_to_extract: A list of files to extract. If set to None, extract
246 all files.
247 exclude: A list of files to exclude. If None, no files are excluded.
248 """
249 super(BundledBuildArtifact, self).__init__(install_dir, archive_url, name,
250 build)
251 self._files_to_extract = files_to_extract
252 self._exclude = exclude
253
254 # We modify the marker so that it is unique to what was staged.
255 if files_to_extract:
256 self.marker_name = self._SanitizeName(
257 '_'.join(['.' + self.name] + files_to_extract))
258
259 def _Extract(self):
260 """Extracts the bundle into install_dir. Must be overridden.
261
262 If set, uses files_to_extract to only extract those items. If set, use
263 exclude to exclude specific files.
264 """
265 raise NotImplementedError()
266
joychen0a8e34e2013-06-24 17:58:36 -0700267 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800268 self._Extract()
269
270
271class TarballBuildArtifact(BundledBuildArtifact):
272 """Artifact for tar and tarball files."""
273
274 def _Extract(self):
275 """Extracts a tarball using tar.
276
277 Detects whether the tarball is compressed or not based on the file
278 extension and extracts the tarball into the install_path.
279 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700280 try:
joychen0a8e34e2013-06-24 17:58:36 -0700281 common_util.ExtractTarball(self.install_path, self.install_dir,
Simran Basi4baad082013-02-14 13:39:18 -0800282 files_to_extract=self._files_to_extract,
283 excluded_files=self._exclude)
284 except common_util.CommonUtilError as e:
285 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700286
287
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700288class AutotestTarballBuildArtifact(TarballBuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700289 """Wrapper around the autotest tarball to download from gsutil."""
290
joychen0a8e34e2013-06-24 17:58:36 -0700291 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800292 """Extracts the tarball into the install path excluding test suites."""
joychen0a8e34e2013-06-24 17:58:36 -0700293 super(AutotestTarballBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700294
Chris Sosa76e44b92013-01-31 12:11:38 -0800295 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700296 autotest_dir = os.path.join(self.install_dir,
297 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700298 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
299 if not os.path.exists(autotest_pkgs_dir):
300 os.makedirs(autotest_pkgs_dir)
301
302 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800303 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
304 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700305 try:
joychen0a8e34e2013-06-24 17:58:36 -0700306 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700307 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800308 raise ArtifactDownloadError(
309 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700310 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700311 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700312
Chris Masone816e38c2012-05-02 12:22:36 -0700313
Chris Sosa76e44b92013-01-31 12:11:38 -0800314class ZipfileBuildArtifact(BundledBuildArtifact):
315 """A downloadable artifact that is a zipfile."""
Chris Masone816e38c2012-05-02 12:22:36 -0700316
Chris Sosa76e44b92013-01-31 12:11:38 -0800317 def _Extract(self):
318 """Extracts files into the install path."""
319 # Unzip is weird. It expects its args before any excepts and expects its
320 # excepts in a list following the -x.
joychen0a8e34e2013-06-24 17:58:36 -0700321 cmd = ['unzip', '-o', self.install_path, '-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800322 if self._files_to_extract:
323 cmd.extend(self._files_to_extract)
Chris Masone816e38c2012-05-02 12:22:36 -0700324
Chris Sosa76e44b92013-01-31 12:11:38 -0800325 if self._exclude:
326 cmd.append('-x')
327 cmd.extend(self._exclude)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700328
329 try:
Chris Sosa76e44b92013-01-31 12:11:38 -0800330 subprocess.check_call(cmd)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700331 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800332 raise ArtifactDownloadError(
333 'An error occurred when attempting to unzip %s:\n%s' %
joychen0a8e34e2013-06-24 17:58:36 -0700334 (self.install_path, e))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700335
Gilad Arnold6f99b982012-09-12 10:49:40 -0700336
Chris Sosa76e44b92013-01-31 12:11:38 -0800337class ImplDescription(object):
338 """Data wrapper that describes an artifact's implementation."""
339 def __init__(self, artifact_class, name, *additional_args):
340 """Constructor:
341
342 Args:
343 artifact_class: BuildArtifact class to use for the artifact.
344 name: name to use to identify artifact (see BuildArtifact.name)
345 additional_args: If sub-class uses additional args, these are passed
346 through to them.
347 """
348 self.artifact_class = artifact_class
349 self.name = name
350 self.additional_args = additional_args
351
Chris Sosa968a1062013-08-02 17:42:50 -0700352 def __repr__(self):
353 return '%s_%s' % (self.artifact_class, self.name)
354
Chris Sosa76e44b92013-01-31 12:11:38 -0800355
356# Maps artifact names to their implementation description.
357# Please note, it is good practice to use constants for these names if you're
358# going to re-use the names ANYWHERE else in the devserver code.
359ARTIFACT_IMPLEMENTATION_MAP = {
360 artifact_info.FULL_PAYLOAD:
361 ImplDescription(AUTestPayloadBuildArtifact, '.*_full_.*'),
362 artifact_info.DELTA_PAYLOADS:
363 ImplDescription(DeltaPayloadsArtifact, '.*_delta_.*'),
364 artifact_info.STATEFUL_PAYLOAD:
joychen25d25972013-07-30 14:54:16 -0700365 ImplDescription(BuildArtifact, devserver_constants.STATEFUL_UPDATE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800366
367 artifact_info.BASE_IMAGE:
368 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
joychen921e1fb2013-06-28 11:12:20 -0700369 [devserver_constants.BASE_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800370 artifact_info.RECOVERY_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700371 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
372 [devserver_constants.RECOVERY_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800373 artifact_info.TEST_IMAGE:
joychen921e1fb2013-06-28 11:12:20 -0700374 ImplDescription(ZipfileBuildArtifact, IMAGE_FILE,
375 [devserver_constants.TEST_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800376
377 artifact_info.AUTOTEST:
378 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_FILE, None,
379 ['autotest/test_suites']),
380 artifact_info.TEST_SUITES:
381 ImplDescription(TarballBuildArtifact, TEST_SUITES_FILE),
382 artifact_info.AU_SUITE:
Chris Sosaa56c4032013-03-17 21:59:54 -0700383 ImplDescription(TarballBuildArtifact, AU_SUITE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800384
385 artifact_info.FIRMWARE:
386 ImplDescription(BuildArtifact, FIRMWARE_FILE),
387 artifact_info.SYMBOLS:
388 ImplDescription(TarballBuildArtifact, DEBUG_SYMBOLS_FILE,
389 ['debug/breakpad']),
beepsc3d0f872013-07-31 21:50:40 -0700390
391 artifact_info.FACTORY_IMAGE:
392 ImplDescription(ZipfileBuildArtifact, FACTORY_FILE,
393 [devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800394}
395
Chris Sosa968a1062013-08-02 17:42:50 -0700396# Add all the paygen_au artifacts in one go.
397ARTIFACT_IMPLEMENTATION_MAP.update({
398 artifact_info.PAYGEN_AU_SUITE_TEMPLATE % { 'channel': c }: ImplDescription(
399 TarballBuildArtifact, PAYGEN_AU_SUITE_FILE_TEMPLATE % { 'channel': c })
400 for c in devserver_constants.CHANNELS
401})
402
Chris Sosa76e44b92013-01-31 12:11:38 -0800403
404class ArtifactFactory(object):
405 """A factory class that generates build artifacts from artifact names."""
406
Chris Sosa6b0c6172013-08-05 17:01:33 -0700407 def __init__(self, download_dir, archive_url, artifacts, files,
408 build):
Chris Sosa76e44b92013-01-31 12:11:38 -0800409 """Initalizes the member variables for the factory.
410
411 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800412 archive_url: the Google Storage url of the bucket where the debug
413 symbols for the desired build are stored.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700414 artifacts: List of artifacts to stage. These artifacts must be
415 defined in artifact_info.py and have a mapping in the
416 ARTIFACT_IMPLEMENTATION_MAP.
417 files: List of files to stage. These files are just downloaded and staged
418 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800419 build: The name of the build.
420 """
joychen0a8e34e2013-06-24 17:58:36 -0700421 self.download_dir = download_dir
Chris Sosa76e44b92013-01-31 12:11:38 -0800422 self.archive_url = archive_url
Chris Sosa6b0c6172013-08-05 17:01:33 -0700423 self.artifacts = artifacts
424 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800425 self.build = build
426
427 @staticmethod
Chris Sosa6b0c6172013-08-05 17:01:33 -0700428 def _GetDescriptionComponents(name, is_artifact):
429 """Returns a tuple of for BuildArtifact class, name, and additional args.
430
431 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
432 """
433
434 if is_artifact:
435 description = ARTIFACT_IMPLEMENTATION_MAP[name]
436 else:
437 description = ImplDescription(BuildArtifact, name)
438
Chris Sosa76e44b92013-01-31 12:11:38 -0800439 return (description.artifact_class, description.name,
440 description.additional_args)
441
Chris Sosa6b0c6172013-08-05 17:01:33 -0700442 def _Artifacts(self, names, is_artifact):
443 """Returns an iterable of BuildArtifacts from |names|.
444
445 If is_artifact is true, then these names define artifacts that must exist in
446 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
447 basic BuildArtifacts.
448
449 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
450 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800451 artifacts = []
Chris Sosa6b0c6172013-08-05 17:01:33 -0700452 for name in names:
Chris Sosa76e44b92013-01-31 12:11:38 -0800453 artifact_class, path, args = self._GetDescriptionComponents(
Chris Sosa6b0c6172013-08-05 17:01:33 -0700454 name, is_artifact)
joychen0a8e34e2013-06-24 17:58:36 -0700455 artifacts.append(artifact_class(self.download_dir, self.archive_url, path,
Chris Sosa76e44b92013-01-31 12:11:38 -0800456 self.build, *args))
457
458 return artifacts
459
460 def RequiredArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700461 """Returns an iterable of BuildArtifacts for the factory's artifacts.
462
463 Raises: KeyError if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
464 """
465 artifacts = []
466 if self.artifacts:
467 artifacts.extend(self._Artifacts(self.artifacts, True))
468 if self.files:
469 artifacts.extend(self._Artifacts(self.files, False))
470
471 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800472
473 def OptionalArtifacts(self):
Chris Sosa6b0c6172013-08-05 17:01:33 -0700474 """Returns an iterable of BuildArtifacts that should be cached.
475
476 Raises: KeyError if an optional artifact doesn't exist in
477 ARTIFACT_IMPLEMENTATION_MAP yet defined in
478 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
479 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800480 optional_names = set()
481 for artifact_name, optional_list in (
482 artifact_info.REQUESTED_TO_OPTIONAL_MAP.iteritems()):
483 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700484 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800485 optional_names = optional_names.union(optional_list)
486
Chris Sosa6b0c6172013-08-05 17:01:33 -0700487 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700488
489
490# A simple main to verify correctness of the artifact map when making simple
491# name changes.
492if __name__ == '__main__':
493 print 'ARTIFACT IMPLEMENTATION MAP (for debugging)'
494 print 'FORMAT: ARTIFACT -> IMPLEMENTATION (<class>_file)'
495 for key, value in sorted(ARTIFACT_IMPLEMENTATION_MAP.items()):
496 print '%s -> %s' % (key, value)