blob: 5a77ff0cdadc99d27b6c30821a2a1e8be78a80fb [file] [log] [blame]
Gilad Arnold950569b2013-08-27 14:38:01 -07001#!/usr/bin/python
Chris Sosa968a1062013-08-02 17:42:50 -07002
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
Simran Basi4243a862014-12-12 12:48:33 -08009import glob
Chris Sosa47a7d4e2012-03-28 11:26:55 -070010import os
Dan Shi6e50c722013-08-19 15:05:06 -070011import pickle
Gilad Arnold950569b2013-08-27 14:38:01 -070012import re
Chris Sosa47a7d4e2012-03-28 11:26:55 -070013import shutil
14import subprocess
15
Chris Sosa76e44b92013-01-31 12:11:38 -080016import artifact_info
17import common_util
joychen3cb228e2013-06-12 12:13:13 -070018import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070019import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070020import log_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070021
22
Chris Sosa76e44b92013-01-31 12:11:38 -080023_AU_BASE = 'au'
24_NTON_DIR_SUFFIX = '_nton'
25_MTON_DIR_SUFFIX = '_mton'
26
27############ Actual filenames of artifacts in Google Storage ############
28
29AU_SUITE_FILE = 'au_control.tar.bz2'
Chris Sosa968a1062013-08-02 17:42:50 -070030PAYGEN_AU_SUITE_FILE_TEMPLATE = 'paygen_au_%(channel)s_control.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080031AUTOTEST_FILE = 'autotest.tar'
Simran Basiea0590d2014-10-29 11:31:26 -070032CONTROL_FILES_FILE = 'control_files.tar'
33AUTOTEST_PACKAGES_FILE = 'autotest_packages.tar'
Chris Sosa76e44b92013-01-31 12:11:38 -080034AUTOTEST_COMPRESSED_FILE = 'autotest.tar.bz2'
Simran Basi6459bba2015-02-04 14:47:23 -080035AUTOTEST_SERVER_PACKAGE_FILE = 'autotest_server_package.tar.bz2'
Chris Sosa76e44b92013-01-31 12:11:38 -080036DEBUG_SYMBOLS_FILE = 'debug.tgz'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080037FACTORY_FILE = 'ChromeOS-factory*.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080038FIRMWARE_FILE = 'firmware_from_source.tar.bz2'
39IMAGE_FILE = 'image.zip'
Chris Sosa76e44b92013-01-31 12:11:38 -080040TEST_SUITES_FILE = 'test_suites.tar.bz2'
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080041BASE_IMAGE_FILE = 'chromiumos_base_image.tar.xz'
42TEST_IMAGE_FILE = 'chromiumos_test_image.tar.xz'
43RECOVERY_IMAGE_FILE = 'recovery_image.tar.xz'
44
Chris Sosa76e44b92013-01-31 12:11:38 -080045
46_build_artifact_locks = common_util.LockDict()
Chris Sosa47a7d4e2012-03-28 11:26:55 -070047
48
49class ArtifactDownloadError(Exception):
50 """Error used to signify an issue processing an artifact."""
51 pass
52
53
Gilad Arnoldc65330c2012-09-20 15:17:48 -070054class BuildArtifact(log_util.Loggable):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070055 """Wrapper around an artifact to download from gsutil.
56
57 The purpose of this class is to download objects from Google Storage
58 and install them to a local directory. There are two main functions, one to
59 download/prepare the artifacts in to a temporary staging area and the second
60 to stage it into its final destination.
Chris Sosa76e44b92013-01-31 12:11:38 -080061
Gilad Arnold950569b2013-08-27 14:38:01 -070062 IMPORTANT! (i) `name' is a glob expression by default (and not a regex), be
63 attentive when adding new artifacts; (ii) name matching semantics differ
64 between a glob (full name string match) and a regex (partial match).
65
Chris Sosa76e44b92013-01-31 12:11:38 -080066 Class members:
Gilad Arnold950569b2013-08-27 14:38:01 -070067 archive_url: An archive URL.
68 name: Name given for artifact; in fact, it is a pattern that captures the
69 names of files contained in the artifact. This can either be an
70 ordinary shell-style glob (the default), or a regular expression (if
71 is_regex_name is True).
72 is_regex_name: Whether the name value is a regex (default: glob).
Chris Sosa76e44b92013-01-31 12:11:38 -080073 build: The version of the build i.e. R26-2342.0.0.
74 marker_name: Name used to define the lock marker for the artifacts to
75 prevent it from being re-downloaded. By default based on name
76 but can be overriden by children.
Dan Shi6e50c722013-08-19 15:05:06 -070077 exception_file_path: Path to a file containing the serialized exception,
78 which was raised in Process method. The file is located
79 in the parent folder of install_dir, since the
80 install_dir will be deleted if the build does not
81 existed.
joychen0a8e34e2013-06-24 17:58:36 -070082 install_path: Path to artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -080083 install_dir: The final location where the artifact should be staged to.
84 single_name: If True the name given should only match one item. Note, if not
85 True, self.name will become a list of items returned.
Gilad Arnold1638d822013-11-07 23:38:16 -080086 installed_files: A list of files that were the final result of downloading
87 and setting up the artifact.
88 store_installed_files: Whether the list of installed files is stored in the
89 marker file.
Chris Sosa47a7d4e2012-03-28 11:26:55 -070090 """
Gilad Arnold950569b2013-08-27 14:38:01 -070091
92 def __init__(self, install_dir, archive_url, name, build,
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -080093 is_regex_name=False, optional_name=None):
Gilad Arnold950569b2013-08-27 14:38:01 -070094 """Constructor.
95
96 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -080097 install_dir: Where to install the artifact.
Simran Basi4243a862014-12-12 12:48:33 -080098 archive_url: The Google Storage URL or local path to find the artifact.
Chris Sosa76e44b92013-01-31 12:11:38 -080099 name: Identifying name to be used to find/store the artifact.
100 build: The name of the build e.g. board/release.
Gilad Arnold950569b2013-08-27 14:38:01 -0700101 is_regex_name: Whether the name pattern is a regex (default: glob).
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800102 optional_name: An alternative name to find the artifact, which can lead
103 to faster download. Unlike |name|, there is no guarantee that an
104 artifact named |optional_name| is/will be on Google Storage. If it
105 exists, we download it. Otherwise, we fall back to wait for |name|.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700106 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800107 super(BuildArtifact, self).__init__()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700108
Chris Sosa76e44b92013-01-31 12:11:38 -0800109 # In-memory lock to keep the devserver from colliding with itself while
110 # attempting to stage the same artifact.
111 self._process_lock = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700112
Chris Sosa76e44b92013-01-31 12:11:38 -0800113 self.archive_url = archive_url
114 self.name = name
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800115 self.optional_name = optional_name
Gilad Arnold950569b2013-08-27 14:38:01 -0700116 self.is_regex_name = is_regex_name
Chris Sosa76e44b92013-01-31 12:11:38 -0800117 self.build = build
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700118
Chris Sosa76e44b92013-01-31 12:11:38 -0800119 self.marker_name = '.' + self._SanitizeName(name)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700120
Dan Shi6e50c722013-08-19 15:05:06 -0700121 exception_file_name = ('.' + self._SanitizeName(build) + self.marker_name +
122 '.exception')
123 # The exception file needs to be located in parent folder, since the
124 # install_dir will be deleted is the build does not exist.
125 self.exception_file_path = os.path.join(os.path.dirname(install_dir),
Gilad Arnold950569b2013-08-27 14:38:01 -0700126 exception_file_name)
Dan Shi6e50c722013-08-19 15:05:06 -0700127
joychen0a8e34e2013-06-24 17:58:36 -0700128 self.install_path = None
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700129
Chris Sosa76e44b92013-01-31 12:11:38 -0800130 self.install_dir = install_dir
131
132 self.single_name = True
133
Gilad Arnold1638d822013-11-07 23:38:16 -0800134 self.installed_files = []
135 self.store_installed_files = True
136
Chris Sosa76e44b92013-01-31 12:11:38 -0800137 @staticmethod
138 def _SanitizeName(name):
139 """Sanitizes name to be used for creating a file on the filesystem.
140
141 '.','/' and '*' have special meaning in FS lingo. Replace them with words.
Gilad Arnold950569b2013-08-27 14:38:01 -0700142
143 Args:
144 name: A file name/path.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800145
Gilad Arnold950569b2013-08-27 14:38:01 -0700146 Returns:
147 The sanitized name/path.
Chris Sosa76e44b92013-01-31 12:11:38 -0800148 """
149 return name.replace('*', 'STAR').replace('.', 'DOT').replace('/', 'SLASH')
150
Dan Shif8eb0d12013-08-01 17:52:06 -0700151 def ArtifactStaged(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800152 """Returns True if artifact is already staged.
153
154 This checks for (1) presence of the artifact marker file, and (2) the
155 presence of each installed file listed in this marker. Both must hold for
156 the artifact to be considered staged. Note that this method is safe for use
157 even if the artifacts were not stageed by this instance, as it is assumed
158 that any BuildArtifact instance that did the staging wrote the list of
159 files actually installed into the marker.
160 """
161 marker_file = os.path.join(self.install_dir, self.marker_name)
162
163 # If the marker is missing, it's definitely not staged.
164 if not os.path.exists(marker_file):
165 return False
166
167 # We want to ensure that every file listed in the marker is actually there.
168 if self.store_installed_files:
169 with open(marker_file) as f:
170 files = [line.strip() for line in f]
171
172 # Check to see if any of the purportedly installed files are missing, in
173 # which case the marker is outdated and should be removed.
174 missing_files = [fname for fname in files if not os.path.exists(fname)]
175 if missing_files:
176 self._Log('***ATTENTION*** %s files listed in %s are missing:\n%s',
177 'All' if len(files) == len(missing_files) else 'Some',
178 marker_file, '\n'.join(missing_files))
179 os.remove(marker_file)
180 return False
181
182 return True
Chris Sosa76e44b92013-01-31 12:11:38 -0800183
184 def _MarkArtifactStaged(self):
185 """Marks the artifact as staged."""
186 with open(os.path.join(self.install_dir, self.marker_name), 'w') as f:
Gilad Arnold1638d822013-11-07 23:38:16 -0800187 f.write('\n'.join(self.installed_files))
Chris Sosa76e44b92013-01-31 12:11:38 -0800188
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800189 def _WaitForArtifactToExist(self, name, timeout):
Simran Basi4243a862014-12-12 12:48:33 -0800190 """Waits for artifact to exist and returns the appropriate names.
Chris Sosac4e87842013-08-16 18:04:14 -0700191
192 Args:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800193 name: Name to look at.
Simran Basi4243a862014-12-12 12:48:33 -0800194 timeout: How long to wait for artifact to become available. Only matters
195 if self.archive_url is a Google Storage URL.
196
197 Returns:
198 A list of names that match.
199
200 Raises:
201 ArtifactDownloadError: An error occurred when obtaining artifact.
202 """
203 if self.archive_url.startswith('gs://'):
204 return self._WaitForGSArtifactToExist(name, timeout)
205 return self._VerifyLocalArtifactExists(name)
206
207 def _WaitForGSArtifactToExist(self, name, timeout):
208 """Waits for artifact to exist and returns the appropriate names.
209
210 Args:
211 name: Name to look at.
212 timeout: How long to wait for the artifact to become available.
213
214 Returns:
215 A list of names that match.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800216
Gilad Arnold950569b2013-08-27 14:38:01 -0700217 Raises:
218 ArtifactDownloadError: An error occurred when obtaining artifact.
Chris Sosac4e87842013-08-16 18:04:14 -0700219 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800220 names = gsutil_util.GetGSNamesWithWait(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800221 name, self.archive_url, str(self), timeout=timeout,
Gilad Arnold950569b2013-08-27 14:38:01 -0700222 is_regex_pattern=self.is_regex_name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800223 if not names:
Don Garrettef484fb2013-11-22 16:56:18 -0800224 raise ArtifactDownloadError('Could not find %s in Google Storage at %s' %
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800225 (name, self.archive_url))
226 return names
Chris Sosa76e44b92013-01-31 12:11:38 -0800227
Simran Basi4243a862014-12-12 12:48:33 -0800228 def _VerifyLocalArtifactExists(self, name):
229 """Verifies the local artifact exists and returns the appropriate names.
230
231 Args:
232 name: Name to look at.
233
234 Returns:
235 A list of names that match.
236
237 Raises:
238 ArtifactDownloadError: An error occurred when obtaining artifact.
239 """
240 local_path = os.path.join(self.archive_url, name)
241 if self.is_regex_name:
242 filter_re = re.compile(name)
243 for filename in os.listdir(self.archive_url):
244 if filter_re.match(filename):
245 return [filename]
246 else:
247 glob_search = glob.glob(local_path)
248 if glob_search and len(glob_search) == 1:
249 return [os.path.basename(glob_search[0])]
250 raise ArtifactDownloadError('Artifact not found.')
251
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800252 def _UpdateName(self, names):
253 if self.single_name and len(names) > 1:
254 raise ArtifactDownloadError('Too many artifacts match %s' % self.name)
Chris Sosa76e44b92013-01-31 12:11:38 -0800255
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800256 self.name = names[0]
Chris Sosa76e44b92013-01-31 12:11:38 -0800257
258 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700259 """Downloads artifact from Google Storage to a local directory."""
joychen0a8e34e2013-06-24 17:58:36 -0700260 self.install_path = os.path.join(self.install_dir, self.name)
Simran Basi4243a862014-12-12 12:48:33 -0800261 if self.archive_url.startswith('gs://'):
262 gs_path = '/'.join([self.archive_url, self.name])
263 gsutil_util.DownloadFromGS(gs_path, self.install_path)
264 else:
265 # It's a local path so just copy it into the staged directory.
266 shutil.copyfile(os.path.join(self.archive_url, self.name),
267 self.install_path)
268
Chris Sosa76e44b92013-01-31 12:11:38 -0800269
joychen0a8e34e2013-06-24 17:58:36 -0700270 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800271 """Process the downloaded content, update the list of installed files."""
272 # In this primitive case, what was downloaded (has to be a single file) is
273 # what's installed.
274 self.installed_files = [self.install_path]
joychen0a8e34e2013-06-24 17:58:36 -0700275
Dan Shi6e50c722013-08-19 15:05:06 -0700276 def _ClearException(self):
277 """Delete any existing exception saved for this artifact."""
278 if os.path.exists(self.exception_file_path):
279 os.remove(self.exception_file_path)
280
281 def _SaveException(self, e):
282 """Save the exception to a file for downloader.IsStaged to retrieve.
283
Gilad Arnold950569b2013-08-27 14:38:01 -0700284 Args:
285 e: Exception object to be saved.
Dan Shi6e50c722013-08-19 15:05:06 -0700286 """
287 with open(self.exception_file_path, 'w') as f:
288 pickle.dump(e, f)
289
290 def GetException(self):
291 """Retrieve any exception that was raised in Process method.
292
Gilad Arnold950569b2013-08-27 14:38:01 -0700293 Returns:
294 An Exception object that was raised when trying to process the artifact.
295 Return None if no exception was found.
Dan Shi6e50c722013-08-19 15:05:06 -0700296 """
297 if not os.path.exists(self.exception_file_path):
298 return None
299 with open(self.exception_file_path, 'r') as f:
300 return pickle.load(f)
Chris Sosa76e44b92013-01-31 12:11:38 -0800301
302 def Process(self, no_wait):
303 """Main call point to all artifacts. Downloads and Stages artifact.
304
305 Downloads and Stages artifact from Google Storage to the install directory
306 specified in the constructor. It multi-thread safe and does not overwrite
307 the artifact if it's already been downloaded or being downloaded. After
308 processing, leaves behind a marker to indicate to future invocations that
309 the artifact has already been staged based on the name of the artifact.
310
311 Do not override as it modifies important private variables, ensures thread
312 safety, and maintains cache semantics.
313
314 Note: this may be a blocking call when the artifact is already in the
315 process of being staged.
316
317 Args:
318 no_wait: If True, don't block waiting for artifact to exist if we fail to
319 immediately find it.
320
321 Raises:
322 ArtifactDownloadError: If the artifact fails to download from Google
323 Storage for any reason or that the regexp
324 defined by name is not specific enough.
325 """
326 if not self._process_lock:
327 self._process_lock = _build_artifact_locks.lock(
328 os.path.join(self.install_dir, self.name))
329
330 with self._process_lock:
331 common_util.MkDirP(self.install_dir)
Dan Shif8eb0d12013-08-01 17:52:06 -0700332 if not self.ArtifactStaged():
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800333 # Delete any existing exception saved for this artifact.
334 self._ClearException()
335 found_artifact = False
336 if self.optional_name:
337 try:
338 # Check if the artifact named |optional_name| exists on GS.
339 # Because this artifact may not always exist, don't bother
340 # to wait for it (set timeout=1).
341 new_names = self._WaitForArtifactToExist(
342 self.optional_name, timeout=1)
343 self._UpdateName(new_names)
344
345 except ArtifactDownloadError:
346 self._Log('Unable to download %s; fall back to download %s',
347 self.optional_name, self.name)
348 else:
349 found_artifact = True
350
Dan Shi6e50c722013-08-19 15:05:06 -0700351 try:
Dan Shi6e50c722013-08-19 15:05:06 -0700352 # If the artifact should already have been uploaded, don't waste
353 # cycles waiting around for it to exist.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800354 if not found_artifact:
355 timeout = 1 if no_wait else 10
356 new_names = self._WaitForArtifactToExist(self.name, timeout)
357 self._UpdateName(new_names)
358
359 self._Log('Downloading file %s', self.name)
Dan Shi6e50c722013-08-19 15:05:06 -0700360 self._Download()
361 self._Setup()
362 self._MarkArtifactStaged()
363 except Exception as e:
364 # Save the exception to a file for downloader.IsStaged to retrieve.
365 self._SaveException(e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800366
367 # Convert an unknown exception into an ArtifactDownloadError.
368 if type(e) is ArtifactDownloadError:
369 raise
370 else:
371 raise ArtifactDownloadError('An error occurred: %s' % e)
Chris Sosa76e44b92013-01-31 12:11:38 -0800372 else:
373 self._Log('%s is already staged.', self)
374
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700375 def __str__(self):
376 """String representation for the download."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800377 return '->'.join(['%s/%s' % (self.archive_url, self.name),
Gilad Arnold950569b2013-08-27 14:38:01 -0700378 self.install_dir])
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700379
Chris Sosab26b1202013-08-16 16:40:55 -0700380 def __repr__(self):
381 return str(self)
382
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700383
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700384class AUTestPayloadBuildArtifact(BuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700385 """Wrapper for AUTest delta payloads which need additional setup."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700386
joychen0a8e34e2013-06-24 17:58:36 -0700387 def _Setup(self):
388 super(AUTestPayloadBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700389
Chris Sosa76e44b92013-01-31 12:11:38 -0800390 # Rename to update.gz.
391 install_path = os.path.join(self.install_dir, self.name)
joychen3cb228e2013-06-12 12:13:13 -0700392 new_install_path = os.path.join(self.install_dir,
joychen7c2054a2013-07-25 11:14:07 -0700393 devserver_constants.UPDATE_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800394 shutil.move(install_path, new_install_path)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700395
Gilad Arnold1638d822013-11-07 23:38:16 -0800396 # Reflect the rename in the list of installed files.
397 self.installed_files.remove(install_path)
398 self.installed_files = [new_install_path]
399
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700400
Chris Sosa76e44b92013-01-31 12:11:38 -0800401# TODO(sosa): Change callers to make this artifact more sane.
402class DeltaPayloadsArtifact(BuildArtifact):
403 """Delta payloads from the archive_url.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700404
Chris Sosa76e44b92013-01-31 12:11:38 -0800405 This artifact is super strange. It custom handles directories and
406 pulls in all delta payloads. We can't specify exactly what we want
407 because unlike other artifacts, this one does not conform to something a
408 client might know. The client doesn't know the version of n-1 or whether it
409 was even generated.
Gilad Arnold950569b2013-08-27 14:38:01 -0700410
411 IMPORTANT! Note that this artifact simply ignores the `name' argument because
412 that name is derived internally in accordance with sub-artifacts. Also note
413 the different types of names (in fact, file name patterns) used for the
414 different sub-artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 """
Gilad Arnold950569b2013-08-27 14:38:01 -0700416
Chris Sosa76e44b92013-01-31 12:11:38 -0800417 def __init__(self, *args):
418 super(DeltaPayloadsArtifact, self).__init__(*args)
Gilad Arnold950569b2013-08-27 14:38:01 -0700419 # Override the name field, we know what it should be.
420 self.name = '*_delta_*'
421 self.is_regex_name = False
422 self.single_name = False # Expect multiple deltas
423
424 # We use a regular glob for the N-to-N delta payload.
425 nton_name = 'chromeos_%s*_delta_*' % self.build
426 # We use a regular expression for the M-to-N delta payload.
427 mton_name = ('chromeos_(?!%s).*_delta_.*' % re.escape(self.build))
428
Chris Sosa76e44b92013-01-31 12:11:38 -0800429 nton_install_dir = os.path.join(self.install_dir, _AU_BASE,
430 self.build + _NTON_DIR_SUFFIX)
431 mton_install_dir = os.path.join(self.install_dir, _AU_BASE,
Gilad Arnold950569b2013-08-27 14:38:01 -0700432 self.build + _MTON_DIR_SUFFIX)
Chris Sosa76e44b92013-01-31 12:11:38 -0800433 self._sub_artifacts = [
434 AUTestPayloadBuildArtifact(mton_install_dir, self.archive_url,
Gilad Arnold950569b2013-08-27 14:38:01 -0700435 mton_name, self.build, is_regex_name=True),
Chris Sosa76e44b92013-01-31 12:11:38 -0800436 AUTestPayloadBuildArtifact(nton_install_dir, self.archive_url,
437 nton_name, self.build)]
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700438
Chris Sosa76e44b92013-01-31 12:11:38 -0800439 def _Download(self):
joychen0a8e34e2013-06-24 17:58:36 -0700440 """With sub-artifacts we do everything in _Setup()."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800441 pass
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700442
joychen0a8e34e2013-06-24 17:58:36 -0700443 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800444 """Process each sub-artifact. Only error out if none can be found."""
445 for artifact in self._sub_artifacts:
446 try:
447 artifact.Process(no_wait=True)
448 # Setup symlink so that AU will work for this payload.
Gilad Arnold1638d822013-11-07 23:38:16 -0800449 stateful_update_symlink = os.path.join(
450 artifact.install_dir, devserver_constants.STATEFUL_FILE)
Chris Sosa76e44b92013-01-31 12:11:38 -0800451 os.symlink(
joychen25d25972013-07-30 14:54:16 -0700452 os.path.join(os.pardir, os.pardir,
joychen121fc9b2013-08-02 14:30:30 -0700453 devserver_constants.STATEFUL_FILE),
Gilad Arnold1638d822013-11-07 23:38:16 -0800454 stateful_update_symlink)
455
456 # Aggregate sub-artifact file lists, including stateful symlink.
457 self.installed_files += artifact.installed_files
458 self.installed_files.append(stateful_update_symlink)
Chris Sosa76e44b92013-01-31 12:11:38 -0800459 except ArtifactDownloadError as e:
460 self._Log('Could not process %s: %s', artifact, e)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800461 raise
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700462
Chris Sosa76e44b92013-01-31 12:11:38 -0800463
464class BundledBuildArtifact(BuildArtifact):
465 """A single build artifact bundle e.g. zip file or tar file."""
Chris Sosa76e44b92013-01-31 12:11:38 -0800466
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800467 def __init__(self, *args, **kwargs):
Gilad Arnold950569b2013-08-27 14:38:01 -0700468 """Takes BuildArtifact args with some additional ones.
469
470 Args:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800471 *args: See BuildArtifact documentation.
472 **kwargs: See BuildArtifact documentation.
Gilad Arnold950569b2013-08-27 14:38:01 -0700473 files_to_extract: A list of files to extract. If set to None, extract
474 all files.
475 exclude: A list of files to exclude. If None, no files are excluded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800476 """
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800477 self._files_to_extract = kwargs.pop('files_to_extract', None)
478 self._exclude = kwargs.pop('exclude', None)
479 super(BundledBuildArtifact, self).__init__(*args, **kwargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800480
481 # We modify the marker so that it is unique to what was staged.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800482 if self._files_to_extract:
Chris Sosa76e44b92013-01-31 12:11:38 -0800483 self.marker_name = self._SanitizeName(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800484 '_'.join(['.' + self.name] + self._files_to_extract))
Chris Sosa76e44b92013-01-31 12:11:38 -0800485
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800486 def _RunUnzip(self, list_only):
487 # Unzip is weird. It expects its args before any excludes and expects its
488 # excludes in a list following the -x.
489 cmd = ['unzip', '-qql' if list_only else '-o', self.install_path]
490 if not list_only:
491 cmd += ['-d', self.install_dir]
Chris Sosa76e44b92013-01-31 12:11:38 -0800492
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800493 if self._files_to_extract:
494 cmd.extend(self._files_to_extract)
495
496 if self._exclude:
497 cmd.append('-x')
498 cmd.extend(self._exclude)
499
500 try:
501 return subprocess.check_output(cmd).strip('\n').splitlines()
502 except subprocess.CalledProcessError, e:
503 raise ArtifactDownloadError(
504 'An error occurred when attempting to unzip %s:\n%s' %
505 (self.install_path, e))
Chris Sosa76e44b92013-01-31 12:11:38 -0800506
joychen0a8e34e2013-06-24 17:58:36 -0700507 def _Setup(self):
Gilad Arnold1638d822013-11-07 23:38:16 -0800508 extract_result = self._Extract()
509 if self.store_installed_files:
510 # List both the archive and the extracted files.
511 self.installed_files.append(self.install_path)
512 self.installed_files.extend(extract_result)
Chris Sosa76e44b92013-01-31 12:11:38 -0800513
Chris Sosa76e44b92013-01-31 12:11:38 -0800514 def _Extract(self):
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800515 """Extracts files into the install path."""
516 if self.name.endswith('.zip'):
517 return self._ExtractZipfile()
518 else:
519 return self._ExtractTarball()
520
521 def _ExtractZipfile(self):
522 """Extracts a zip file using unzip."""
523 file_list = [os.path.join(self.install_dir, line[30:].strip())
524 for line in self._RunUnzip(True)
525 if not line.endswith('/')]
526 if file_list:
527 self._RunUnzip(False)
528
529 return file_list
530
531 def _ExtractTarball(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800532 """Extracts a tarball using tar.
533
534 Detects whether the tarball is compressed or not based on the file
535 extension and extracts the tarball into the install_path.
536 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700537 try:
Gilad Arnold1638d822013-11-07 23:38:16 -0800538 return common_util.ExtractTarball(self.install_path, self.install_dir,
539 files_to_extract=self._files_to_extract,
540 excluded_files=self._exclude,
541 return_extracted_files=True)
Simran Basi4baad082013-02-14 13:39:18 -0800542 except common_util.CommonUtilError as e:
543 raise ArtifactDownloadError(str(e))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700544
545
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800546class AutotestTarballBuildArtifact(BundledBuildArtifact):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700547 """Wrapper around the autotest tarball to download from gsutil."""
548
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800549 def __init__(self, *args, **kwargs):
550 super(AutotestTarballBuildArtifact, self).__init__(*args, **kwargs)
Gilad Arnold1638d822013-11-07 23:38:16 -0800551 # We don't store/check explicit file lists in Autotest tarball markers;
552 # this can get huge and unwieldy, and generally make little sense.
553 self.store_installed_files = False
554
joychen0a8e34e2013-06-24 17:58:36 -0700555 def _Setup(self):
Chris Sosa76e44b92013-01-31 12:11:38 -0800556 """Extracts the tarball into the install path excluding test suites."""
joychen0a8e34e2013-06-24 17:58:36 -0700557 super(AutotestTarballBuildArtifact, self)._Setup()
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700558
Chris Sosa76e44b92013-01-31 12:11:38 -0800559 # Deal with older autotest packages that may not be bundled.
joychen3cb228e2013-06-12 12:13:13 -0700560 autotest_dir = os.path.join(self.install_dir,
561 devserver_constants.AUTOTEST_DIR)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700562 autotest_pkgs_dir = os.path.join(autotest_dir, 'packages')
563 if not os.path.exists(autotest_pkgs_dir):
564 os.makedirs(autotest_pkgs_dir)
565
566 if not os.path.exists(os.path.join(autotest_pkgs_dir, 'packages.checksum')):
Chris Sosa76e44b92013-01-31 12:11:38 -0800567 cmd = ['autotest/utils/packager.py', 'upload', '--repository',
568 autotest_pkgs_dir, '--all']
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700569 try:
joychen0a8e34e2013-06-24 17:58:36 -0700570 subprocess.check_call(cmd, cwd=self.install_dir)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700571 except subprocess.CalledProcessError, e:
Chris Sosa76e44b92013-01-31 12:11:38 -0800572 raise ArtifactDownloadError(
573 'Failed to create autotest packages!:\n%s' % e)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700574 else:
Gilad Arnoldf5843132012-09-25 00:31:20 -0700575 self._Log('Using pre-generated packages from autotest')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700576
Chris Masone816e38c2012-05-02 12:22:36 -0700577
Chris Sosa76e44b92013-01-31 12:11:38 -0800578class ImplDescription(object):
579 """Data wrapper that describes an artifact's implementation."""
Gilad Arnold950569b2013-08-27 14:38:01 -0700580
581 def __init__(self, artifact_class, name, *additional_args,
582 **additional_dargs):
583 """Constructor.
Chris Sosa76e44b92013-01-31 12:11:38 -0800584
585 Args:
586 artifact_class: BuildArtifact class to use for the artifact.
587 name: name to use to identify artifact (see BuildArtifact.name)
Gilad Arnold950569b2013-08-27 14:38:01 -0700588 *additional_args: Additional arguments to pass to artifact_class.
589 **additional_dargs: Additional named arguments to pass to artifact_class.
Chris Sosa76e44b92013-01-31 12:11:38 -0800590 """
591 self.artifact_class = artifact_class
592 self.name = name
593 self.additional_args = additional_args
Gilad Arnold950569b2013-08-27 14:38:01 -0700594 self.additional_dargs = additional_dargs
Chris Sosa76e44b92013-01-31 12:11:38 -0800595
Chris Sosa968a1062013-08-02 17:42:50 -0700596 def __repr__(self):
597 return '%s_%s' % (self.artifact_class, self.name)
598
Chris Sosa76e44b92013-01-31 12:11:38 -0800599
600# Maps artifact names to their implementation description.
601# Please note, it is good practice to use constants for these names if you're
602# going to re-use the names ANYWHERE else in the devserver code.
603ARTIFACT_IMPLEMENTATION_MAP = {
Gilad Arnold950569b2013-08-27 14:38:01 -0700604 artifact_info.FULL_PAYLOAD:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800605 ImplDescription(AUTestPayloadBuildArtifact, ('*_full_*')),
Gilad Arnold950569b2013-08-27 14:38:01 -0700606 artifact_info.DELTA_PAYLOADS:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800607 ImplDescription(DeltaPayloadsArtifact, ('DONTCARE')),
Gilad Arnold950569b2013-08-27 14:38:01 -0700608 artifact_info.STATEFUL_PAYLOAD:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800609 ImplDescription(BuildArtifact, (devserver_constants.STATEFUL_FILE)),
Chris Sosa76e44b92013-01-31 12:11:38 -0800610
Gilad Arnold950569b2013-08-27 14:38:01 -0700611 artifact_info.BASE_IMAGE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800612 ImplDescription(BundledBuildArtifact, IMAGE_FILE,
613 optional_name=BASE_IMAGE_FILE,
Gilad Arnold69878b42013-09-18 13:39:22 -0700614 files_to_extract=[devserver_constants.BASE_IMAGE_FILE]),
Gilad Arnold950569b2013-08-27 14:38:01 -0700615 artifact_info.RECOVERY_IMAGE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800616 ImplDescription(BundledBuildArtifact, IMAGE_FILE,
617 optional_name=RECOVERY_IMAGE_FILE,
Gilad Arnold69878b42013-09-18 13:39:22 -0700618 files_to_extract=[devserver_constants.RECOVERY_IMAGE_FILE]),
Chris Sosa75490802013-09-30 17:21:45 -0700619 artifact_info.DEV_IMAGE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800620 ImplDescription(BundledBuildArtifact, IMAGE_FILE,
Chris Sosa75490802013-09-30 17:21:45 -0700621 files_to_extract=[devserver_constants.IMAGE_FILE]),
Gilad Arnold950569b2013-08-27 14:38:01 -0700622 artifact_info.TEST_IMAGE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800623 ImplDescription(BundledBuildArtifact, IMAGE_FILE,
624 optional_name=TEST_IMAGE_FILE,
Gilad Arnold69878b42013-09-18 13:39:22 -0700625 files_to_extract=[devserver_constants.TEST_IMAGE_FILE]),
Chris Sosa76e44b92013-01-31 12:11:38 -0800626
Gilad Arnold950569b2013-08-27 14:38:01 -0700627 artifact_info.AUTOTEST:
628 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_FILE,
629 files_to_extract=None,
630 exclude=['autotest/test_suites']),
Simran Basiea0590d2014-10-29 11:31:26 -0700631 artifact_info.CONTROL_FILES:
632 ImplDescription(BundledBuildArtifact, CONTROL_FILES_FILE),
633 artifact_info.AUTOTEST_PACKAGES:
634 ImplDescription(AutotestTarballBuildArtifact, AUTOTEST_PACKAGES_FILE),
Gilad Arnold950569b2013-08-27 14:38:01 -0700635 artifact_info.TEST_SUITES:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800636 ImplDescription(BundledBuildArtifact, TEST_SUITES_FILE),
Gilad Arnold950569b2013-08-27 14:38:01 -0700637 artifact_info.AU_SUITE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800638 ImplDescription(BundledBuildArtifact, AU_SUITE_FILE),
Simran Basi6459bba2015-02-04 14:47:23 -0800639 artifact_info.AUTOTEST_SERVER_PACKAGE:
640 ImplDescription(BuildArtifact, AUTOTEST_SERVER_PACKAGE_FILE),
Chris Sosa76e44b92013-01-31 12:11:38 -0800641
Gilad Arnold950569b2013-08-27 14:38:01 -0700642 artifact_info.FIRMWARE:
643 ImplDescription(BuildArtifact, FIRMWARE_FILE),
644 artifact_info.SYMBOLS:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800645 ImplDescription(BundledBuildArtifact, DEBUG_SYMBOLS_FILE,
Gilad Arnold950569b2013-08-27 14:38:01 -0700646 files_to_extract=['debug/breakpad']),
beepsc3d0f872013-07-31 21:50:40 -0700647
Gilad Arnold950569b2013-08-27 14:38:01 -0700648 artifact_info.FACTORY_IMAGE:
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800649 ImplDescription(BundledBuildArtifact, FACTORY_FILE,
Gilad Arnold69878b42013-09-18 13:39:22 -0700650 files_to_extract=[devserver_constants.FACTORY_IMAGE_FILE])
Chris Sosa76e44b92013-01-31 12:11:38 -0800651}
652
Chris Sosa968a1062013-08-02 17:42:50 -0700653# Add all the paygen_au artifacts in one go.
654ARTIFACT_IMPLEMENTATION_MAP.update({
Gilad Arnold950569b2013-08-27 14:38:01 -0700655 artifact_info.PAYGEN_AU_SUITE_TEMPLATE % {'channel': c}:
656 ImplDescription(
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800657 BundledBuildArtifact, PAYGEN_AU_SUITE_FILE_TEMPLATE % {'channel': c})
Gilad Arnold950569b2013-08-27 14:38:01 -0700658 for c in devserver_constants.CHANNELS
Chris Sosa968a1062013-08-02 17:42:50 -0700659})
660
Chris Sosa76e44b92013-01-31 12:11:38 -0800661
662class ArtifactFactory(object):
663 """A factory class that generates build artifacts from artifact names."""
664
Chris Sosa6b0c6172013-08-05 17:01:33 -0700665 def __init__(self, download_dir, archive_url, artifacts, files,
666 build):
Chris Sosa76e44b92013-01-31 12:11:38 -0800667 """Initalizes the member variables for the factory.
668
669 Args:
Gilad Arnold950569b2013-08-27 14:38:01 -0700670 download_dir: A directory to which artifacts are downloaded.
Chris Sosa76e44b92013-01-31 12:11:38 -0800671 archive_url: the Google Storage url of the bucket where the debug
672 symbols for the desired build are stored.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700673 artifacts: List of artifacts to stage. These artifacts must be
674 defined in artifact_info.py and have a mapping in the
675 ARTIFACT_IMPLEMENTATION_MAP.
676 files: List of files to stage. These files are just downloaded and staged
677 as files into the download_dir.
Chris Sosa76e44b92013-01-31 12:11:38 -0800678 build: The name of the build.
679 """
joychen0a8e34e2013-06-24 17:58:36 -0700680 self.download_dir = download_dir
Chris Sosa76e44b92013-01-31 12:11:38 -0800681 self.archive_url = archive_url
Chris Sosa6b0c6172013-08-05 17:01:33 -0700682 self.artifacts = artifacts
683 self.files = files
Chris Sosa76e44b92013-01-31 12:11:38 -0800684 self.build = build
685
686 @staticmethod
Chris Sosa6b0c6172013-08-05 17:01:33 -0700687 def _GetDescriptionComponents(name, is_artifact):
Gilad Arnold950569b2013-08-27 14:38:01 -0700688 """Returns components for constructing a BuildArtifact.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700689
Gilad Arnold950569b2013-08-27 14:38:01 -0700690 Args:
691 name: The artifact name / file pattern.
692 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800693
Gilad Arnold950569b2013-08-27 14:38:01 -0700694 Returns:
695 A tuple consisting of the BuildArtifact subclass, name, and additional
696 list- and named-arguments.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800697
Gilad Arnold950569b2013-08-27 14:38:01 -0700698 Raises:
699 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700700 """
701
702 if is_artifact:
703 description = ARTIFACT_IMPLEMENTATION_MAP[name]
704 else:
705 description = ImplDescription(BuildArtifact, name)
706
Chris Sosa76e44b92013-01-31 12:11:38 -0800707 return (description.artifact_class, description.name,
Gilad Arnold950569b2013-08-27 14:38:01 -0700708 description.additional_args, description.additional_dargs)
Chris Sosa76e44b92013-01-31 12:11:38 -0800709
Chris Sosa6b0c6172013-08-05 17:01:33 -0700710 def _Artifacts(self, names, is_artifact):
Gilad Arnold950569b2013-08-27 14:38:01 -0700711 """Returns the BuildArtifacts from |names|.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700712
713 If is_artifact is true, then these names define artifacts that must exist in
714 the ARTIFACT_IMPLEMENTATION_MAP. Otherwise, treat as filenames to stage as
715 basic BuildArtifacts.
716
Gilad Arnold950569b2013-08-27 14:38:01 -0700717 Args:
718 names: A sequence of artifact names.
719 is_artifact: Whether this is a named (True) or file (False) artifact.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800720
Gilad Arnold950569b2013-08-27 14:38:01 -0700721 Returns:
722 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800723
Gilad Arnold950569b2013-08-27 14:38:01 -0700724 Raises:
725 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700726 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800727 artifacts = []
Chris Sosa6b0c6172013-08-05 17:01:33 -0700728 for name in names:
Gilad Arnold950569b2013-08-27 14:38:01 -0700729 artifact_class, path, args, dargs = self._GetDescriptionComponents(
Chris Sosa6b0c6172013-08-05 17:01:33 -0700730 name, is_artifact)
joychen0a8e34e2013-06-24 17:58:36 -0700731 artifacts.append(artifact_class(self.download_dir, self.archive_url, path,
Gilad Arnold950569b2013-08-27 14:38:01 -0700732 self.build, *args, **dargs))
Chris Sosa76e44b92013-01-31 12:11:38 -0800733
734 return artifacts
735
736 def RequiredArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700737 """Returns BuildArtifacts for the factory's artifacts.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700738
Gilad Arnold950569b2013-08-27 14:38:01 -0700739 Returns:
740 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800741
Gilad Arnold950569b2013-08-27 14:38:01 -0700742 Raises:
743 KeyError: if artifact doesn't exist in ARTIFACT_IMPLEMENTATION_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700744 """
745 artifacts = []
746 if self.artifacts:
747 artifacts.extend(self._Artifacts(self.artifacts, True))
748 if self.files:
749 artifacts.extend(self._Artifacts(self.files, False))
750
751 return artifacts
Chris Sosa76e44b92013-01-31 12:11:38 -0800752
753 def OptionalArtifacts(self):
Gilad Arnold950569b2013-08-27 14:38:01 -0700754 """Returns BuildArtifacts that should be cached.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700755
Gilad Arnold950569b2013-08-27 14:38:01 -0700756 Returns:
757 An iterable of BuildArtifacts.
Yu-Ju Hong5d5bf0d2014-02-11 21:38:20 -0800758
Gilad Arnold950569b2013-08-27 14:38:01 -0700759 Raises:
760 KeyError: if an optional artifact doesn't exist in
761 ARTIFACT_IMPLEMENTATION_MAP yet defined in
762 artifact_info.REQUESTED_TO_OPTIONAL_MAP.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700763 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800764 optional_names = set()
765 for artifact_name, optional_list in (
766 artifact_info.REQUESTED_TO_OPTIONAL_MAP.iteritems()):
767 # We are already downloading it.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700768 if artifact_name in self.artifacts:
Chris Sosa76e44b92013-01-31 12:11:38 -0800769 optional_names = optional_names.union(optional_list)
770
Chris Sosa6b0c6172013-08-05 17:01:33 -0700771 return self._Artifacts(optional_names - set(self.artifacts), True)
Chris Sosa968a1062013-08-02 17:42:50 -0700772
773
774# A simple main to verify correctness of the artifact map when making simple
775# name changes.
776if __name__ == '__main__':
777 print 'ARTIFACT IMPLEMENTATION MAP (for debugging)'
778 print 'FORMAT: ARTIFACT -> IMPLEMENTATION (<class>_file)'
779 for key, value in sorted(ARTIFACT_IMPLEMENTATION_MAP.items()):
780 print '%s -> %s' % (key, value)