Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import copy | 5 import copy |
| 6 import hashlib | 6 import hashlib |
| 7 import json | 7 import json |
| 8 import string | |
| 8 import sys | 9 import sys |
| 10 import urllib2 | |
| 9 | 11 |
| 10 MANIFEST_VERSION = 2 | 12 MANIFEST_VERSION = 2 |
| 11 | 13 |
| 12 # Some commonly-used key names. | 14 # Some commonly-used key names. |
| 13 ARCHIVES_KEY = 'archives' | 15 ARCHIVES_KEY = 'archives' |
| 14 BUNDLES_KEY = 'bundles' | 16 BUNDLES_KEY = 'bundles' |
| 15 NAME_KEY = 'name' | 17 NAME_KEY = 'name' |
| 16 REVISION_KEY = 'revision' | 18 REVISION_KEY = 'revision' |
| 17 VERSION_KEY = 'version' | 19 VERSION_KEY = 'version' |
| 18 | 20 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 50 def DictToJSON(pydict): | 52 def DictToJSON(pydict): |
| 51 """Convert a dict to a JSON-formatted string.""" | 53 """Convert a dict to a JSON-formatted string.""" |
| 52 pretty_string = json.dumps(pydict, sort_keys=False, indent=2) | 54 pretty_string = json.dumps(pydict, sort_keys=False, indent=2) |
| 53 # json.dumps sometimes returns trailing whitespace and does not put | 55 # json.dumps sometimes returns trailing whitespace and does not put |
| 54 # a newline at the end. This code fixes these problems. | 56 # a newline at the end. This code fixes these problems. |
| 55 pretty_lines = pretty_string.split('\n') | 57 pretty_lines = pretty_string.split('\n') |
| 56 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' | 58 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' |
| 57 | 59 |
| 58 | 60 |
| 59 def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): | 61 def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): |
| 60 ''' Download the archive data from from-stream and generate sha1 and | 62 '''Download the archive data from from-stream and generate sha1 and |
| 61 size info. | 63 size info. |
| 62 | 64 |
| 63 Args: | 65 Args: |
| 64 from_stream: An input stream that supports read. | 66 from_stream: An input stream that supports read. |
| 65 to_stream: [optional] the data is written to to_stream if it is | 67 to_stream: [optional] the data is written to to_stream if it is |
| 66 provided. | 68 provided. |
| 67 progress_func: [optional] A function used to report download progress. If | 69 progress_func: [optional] A function used to report download progress. If |
| 68 provided, progress_func is called with progress=0 at the | 70 provided, progress_func is called with progress=0 at the |
| 69 beginning of the download, periodically with progress=1 | 71 beginning of the download, periodically with progress=1 |
| 70 during the download, and progress=100 at the end. | 72 during the download, and progress=100 at the end. |
| 71 | 73 |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 139 raise Error('Archive "%s" has no checksum' % host_os) | 141 raise Error('Archive "%s" has no checksum' % host_os) |
| 140 elif not isinstance(checksum, dict): | 142 elif not isinstance(checksum, dict): |
| 141 raise Error('Archive "%s" has a checksum, but it is not a dict' % host_os) | 143 raise Error('Archive "%s" has a checksum, but it is not a dict' % host_os) |
| 142 elif not len(checksum): | 144 elif not len(checksum): |
| 143 raise Error('Archive "%s" has an empty checksum dict' % host_os) | 145 raise Error('Archive "%s" has an empty checksum dict' % host_os) |
| 144 # Verify that all key names are valid. | 146 # Verify that all key names are valid. |
| 145 for key in self: | 147 for key in self: |
| 146 if key not in VALID_ARCHIVE_KEYS: | 148 if key not in VALID_ARCHIVE_KEYS: |
| 147 raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) | 149 raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) |
| 148 | 150 |
| 151 def UpdateVitals(self, revision): | |
| 152 """Update the size and checksum information for this archive | |
| 153 based on the content currently at the URL. | |
| 154 | |
| 155 This allows the template mandifest to be maintained without | |
|
binji
2012/10/16 23:09:10
manifest
| |
| 156 the need to size and checksums to be present. | |
| 157 """ | |
| 158 template = string.Template(self['url']) | |
| 159 self['url'] = template.substitute({'revision': revision}) | |
| 160 from_stream = urllib2.urlopen(self['url']) | |
| 161 sha1_hash, size = DownloadAndComputeHash(from_stream) | |
| 162 self['size'] = size | |
| 163 self['checksum'] = { 'sha1': sha1_hash } | |
| 164 | |
| 149 def __getattr__(self, name): | 165 def __getattr__(self, name): |
| 150 """Retrieve values from this dict using attributes. | 166 """Retrieve values from this dict using attributes. |
| 151 | 167 |
| 152 This allows for foo.bar instead of foo['bar']. | 168 This allows for foo.bar instead of foo['bar']. |
| 153 | 169 |
| 154 Args: | 170 Args: |
| 155 name: the name of the key, 'bar' in the example above. | 171 name: the name of the key, 'bar' in the example above. |
| 156 Returns: | 172 Returns: |
| 157 The value associated with that key.""" | 173 The value associated with that key.""" |
| 158 if name not in VALID_ARCHIVE_KEYS: | 174 if name not in VALID_ARCHIVE_KEYS: |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 bundle: The other bundle. Must be a dict. | 224 bundle: The other bundle. Must be a dict. |
| 209 """ | 225 """ |
| 210 for k, v in bundle.iteritems(): | 226 for k, v in bundle.iteritems(): |
| 211 if k == ARCHIVES_KEY: | 227 if k == ARCHIVES_KEY: |
| 212 for archive in v: | 228 for archive in v: |
| 213 self.RemoveArchive(archive['host_os']) | 229 self.RemoveArchive(archive['host_os']) |
| 214 self.get(k, []).append(archive) | 230 self.get(k, []).append(archive) |
| 215 else: | 231 else: |
| 216 self[k] = v | 232 self[k] = v |
| 217 | 233 |
| 234 def __str__(self): | |
| 235 return self.GetDataAsString() | |
| 236 | |
| 218 def GetDataAsString(self): | 237 def GetDataAsString(self): |
| 219 """Returns the JSON bundle object, pretty-printed""" | 238 """Returns the JSON bundle object, pretty-printed""" |
| 220 return DictToJSON(self) | 239 return DictToJSON(self) |
| 221 | 240 |
| 222 def LoadDataFromString(self, json_string): | 241 def LoadDataFromString(self, json_string): |
| 223 """Load a JSON bundle string. Raises an exception if json_string | 242 """Load a JSON bundle string. Raises an exception if json_string |
| 224 is not well-formed JSON. | 243 is not well-formed JSON. |
| 225 | 244 |
| 226 Args: | 245 Args: |
| 227 json_string: a JSON-formatted string containing the bundle | 246 json_string: a JSON-formatted string containing the bundle |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 238 if key == ARCHIVES_KEY: | 257 if key == ARCHIVES_KEY: |
| 239 archives = [] | 258 archives = [] |
| 240 for a in value: | 259 for a in value: |
| 241 new_archive = Archive(a['host_os']) | 260 new_archive = Archive(a['host_os']) |
| 242 new_archive.CopyFrom(a) | 261 new_archive.CopyFrom(a) |
| 243 archives.append(new_archive) | 262 archives.append(new_archive) |
| 244 self[ARCHIVES_KEY] = archives | 263 self[ARCHIVES_KEY] = archives |
| 245 else: | 264 else: |
| 246 self[key] = value | 265 self[key] = value |
| 247 | 266 |
| 248 def Validate(self): | 267 def Validate(self, add_missing_info=False): |
| 249 """Validate the content of the bundle. Raise an Error if an invalid or | 268 """Validate the content of the bundle. Raise an Error if an invalid or |
| 250 missing field is found. """ | 269 missing field is found. """ |
| 251 # Check required fields. | 270 # Check required fields. |
| 252 if not self.get(NAME_KEY, None): | 271 if not self.get(NAME_KEY): |
| 253 raise Error('Bundle has no name') | 272 raise Error('Bundle has no name') |
| 254 if self.get(REVISION_KEY, None) == None: | 273 if self.get(REVISION_KEY) == None: |
| 255 raise Error('Bundle "%s" is missing a revision number' % self[NAME_KEY]) | 274 raise Error('Bundle "%s" is missing a revision number' % self[NAME_KEY]) |
| 256 if self.get(VERSION_KEY, None) == None: | 275 if self.get(VERSION_KEY) == None: |
| 257 raise Error('Bundle "%s" is missing a version number' % self[NAME_KEY]) | 276 raise Error('Bundle "%s" is missing a version number' % self[NAME_KEY]) |
| 258 if not self.get('description', None): | 277 if not self.get('description'): |
| 259 raise Error('Bundle "%s" is missing a description' % self[NAME_KEY]) | 278 raise Error('Bundle "%s" is missing a description' % self[NAME_KEY]) |
| 260 if not self.get('stability', None): | 279 if not self.get('stability'): |
| 261 raise Error('Bundle "%s" is missing stability info' % self[NAME_KEY]) | 280 raise Error('Bundle "%s" is missing stability info' % self[NAME_KEY]) |
| 262 if self.get('recommended', None) == None: | 281 if self.get('recommended') == None: |
| 263 raise Error('Bundle "%s" is missing the recommended field' % | 282 raise Error('Bundle "%s" is missing the recommended field' % |
| 264 self[NAME_KEY]) | 283 self[NAME_KEY]) |
| 265 # Check specific values | 284 # Check specific values |
| 266 if self['stability'] not in STABILITY_LITERALS: | 285 if self['stability'] not in STABILITY_LITERALS: |
| 267 raise Error('Bundle "%s" has invalid stability field: "%s"' % | 286 raise Error('Bundle "%s" has invalid stability field: "%s"' % |
| 268 (self[NAME_KEY], self['stability'])) | 287 (self[NAME_KEY], self['stability'])) |
| 269 if self['recommended'] not in YES_NO_LITERALS: | 288 if self['recommended'] not in YES_NO_LITERALS: |
| 270 raise Error( | 289 raise Error( |
| 271 'Bundle "%s" has invalid recommended field: "%s"' % | 290 'Bundle "%s" has invalid recommended field: "%s"' % |
| 272 (self[NAME_KEY], self['recommended'])) | 291 (self[NAME_KEY], self['recommended'])) |
| 273 # Verify that all key names are valid. | 292 # Verify that all key names are valid. |
| 274 for key in self: | 293 for key in self: |
| 275 if key not in VALID_BUNDLES_KEYS: | 294 if key not in VALID_BUNDLES_KEYS: |
| 276 raise Error('Bundle "%s" has invalid attribute "%s"' % | 295 raise Error('Bundle "%s" has invalid attribute "%s"' % |
| 277 (self[NAME_KEY], key)) | 296 (self[NAME_KEY], key)) |
| 278 # Validate the archives | 297 # Validate the archives |
| 279 for archive in self[ARCHIVES_KEY]: | 298 for archive in self[ARCHIVES_KEY]: |
| 299 if add_missing_info and 'size' not in archive: | |
| 300 archive.UpdateVitals(self[REVISION_KEY]) | |
| 280 archive.Validate() | 301 archive.Validate() |
| 281 | 302 |
| 303 | |
|
binji
2012/10/16 23:09:10
no newline necessary here
| |
| 282 def GetArchive(self, host_os_name): | 304 def GetArchive(self, host_os_name): |
| 283 """Retrieve the archive for the given host os. | 305 """Retrieve the archive for the given host os. |
| 284 | 306 |
| 285 Args: | 307 Args: |
| 286 host_os_name: name of host os whose archive must be retrieved. | 308 host_os_name: name of host os whose archive must be retrieved. |
| 287 Return: | 309 Return: |
| 288 An Archive instance or None if it doesn't exist.""" | 310 An Archive instance or None if it doesn't exist.""" |
| 289 for archive in self[ARCHIVES_KEY]: | 311 for archive in self[ARCHIVES_KEY]: |
| 290 if archive.host_os == host_os_name or archive.host_os == 'all': | 312 if archive.host_os == host_os_name or archive.host_os == 'all': |
| 291 return archive | 313 return archive |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 380 For ease of unit-testing, this class should not contain any file I/O. | 402 For ease of unit-testing, this class should not contain any file I/O. |
| 381 """ | 403 """ |
| 382 | 404 |
| 383 def __init__(self): | 405 def __init__(self): |
| 384 """Create a new SDKManifest object with default contents""" | 406 """Create a new SDKManifest object with default contents""" |
| 385 self._manifest_data = { | 407 self._manifest_data = { |
| 386 "manifest_version": MANIFEST_VERSION, | 408 "manifest_version": MANIFEST_VERSION, |
| 387 "bundles": [], | 409 "bundles": [], |
| 388 } | 410 } |
| 389 | 411 |
| 390 def Validate(self): | 412 def Validate(self, add_missing_info=False): |
| 391 """Validate the Manifest file and raises an exception for problems""" | 413 """Validate the Manifest file and raises an exception for problems""" |
| 392 # Validate the manifest top level | 414 # Validate the manifest top level |
| 393 if self._manifest_data["manifest_version"] > MANIFEST_VERSION: | 415 if self._manifest_data["manifest_version"] > MANIFEST_VERSION: |
| 394 raise Error("Manifest version too high: %s" % | 416 raise Error("Manifest version too high: %s" % |
| 395 self._manifest_data["manifest_version"]) | 417 self._manifest_data["manifest_version"]) |
| 396 # Verify that all key names are valid. | 418 # Verify that all key names are valid. |
| 397 for key in self._manifest_data: | 419 for key in self._manifest_data: |
| 398 if key not in VALID_MANIFEST_KEYS: | 420 if key not in VALID_MANIFEST_KEYS: |
| 399 raise Error('Manifest has invalid attribute "%s"' % key) | 421 raise Error('Manifest has invalid attribute "%s"' % key) |
| 400 # Validate each bundle | 422 # Validate each bundle |
| 401 for bundle in self._manifest_data[BUNDLES_KEY]: | 423 for bundle in self._manifest_data[BUNDLES_KEY]: |
| 402 bundle.Validate() | 424 bundle.Validate(add_missing_info) |
| 403 | 425 |
| 404 def GetBundle(self, name): | 426 def GetBundle(self, name): |
| 405 """Get a bundle from the array of bundles. | 427 """Get a bundle from the array of bundles. |
| 406 | 428 |
| 407 Args: | 429 Args: |
| 408 name: the name of the bundle to return. | 430 name: the name of the bundle to return. |
| 409 Return: | 431 Return: |
| 410 The first bundle with the given name, or None if it is not found.""" | 432 The first bundle with the given name, or None if it is not found.""" |
| 411 if not BUNDLES_KEY in self._manifest_data: | 433 if not BUNDLES_KEY in self._manifest_data: |
| 412 return None | 434 return None |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 450 Returns: | 472 Returns: |
| 451 True if Bundle needs to be updated. | 473 True if Bundle needs to be updated. |
| 452 """ | 474 """ |
| 453 if NAME_KEY not in bundle: | 475 if NAME_KEY not in bundle: |
| 454 raise KeyError("Bundle must have a 'name' key.") | 476 raise KeyError("Bundle must have a 'name' key.") |
| 455 local_bundle = self.GetBundle(bundle[NAME_KEY]) | 477 local_bundle = self.GetBundle(bundle[NAME_KEY]) |
| 456 return (local_bundle == None) or ( | 478 return (local_bundle == None) or ( |
| 457 (local_bundle[VERSION_KEY], local_bundle[REVISION_KEY]) < | 479 (local_bundle[VERSION_KEY], local_bundle[REVISION_KEY]) < |
| 458 (bundle[VERSION_KEY], bundle[REVISION_KEY])) | 480 (bundle[VERSION_KEY], bundle[REVISION_KEY])) |
| 459 | 481 |
| 460 def MergeBundle(self, bundle, allow_existing = True): | 482 def MergeBundle(self, bundle, allow_existing=True): |
| 461 """Merge a Bundle into this manifest. | 483 """Merge a Bundle into this manifest. |
| 462 | 484 |
| 463 The new bundle is added if not present, or merged into the existing bundle. | 485 The new bundle is added if not present, or merged into the existing bundle. |
| 464 | 486 |
| 465 Args: | 487 Args: |
| 466 bundle: The bundle to merge. | 488 bundle: The bundle to merge. |
| 467 """ | 489 """ |
| 468 if NAME_KEY not in bundle: | 490 if NAME_KEY not in bundle: |
| 469 raise KeyError("Bundle must have a 'name' key.") | 491 raise KeyError("Bundle must have a 'name' key.") |
| 470 local_bundle = self.GetBundle(bundle.name) | 492 local_bundle = self.GetBundle(bundle.name) |
| 471 if not local_bundle: | 493 if not local_bundle: |
| 472 self.SetBundle(bundle) | 494 self.SetBundle(bundle) |
| 473 else: | 495 else: |
| 474 if not allow_existing: | 496 if not allow_existing: |
| 475 raise Error('cannot merge manifest bundle \'%s\', it already exists' | 497 raise Error('cannot merge manifest bundle \'%s\', it already exists' |
| 476 % bundle.name) | 498 % bundle.name) |
| 477 local_bundle.MergeWithBundle(bundle) | 499 local_bundle.MergeWithBundle(bundle) |
| 478 | 500 |
| 479 def MergeManifest(self, manifest): | 501 def MergeManifest(self, manifest): |
| 480 '''Merge another manifest into this manifest, disallowing overiding. | 502 '''Merge another manifest into this manifest, disallowing overiding. |
| 481 | 503 |
| 482 Args | 504 Args |
| 483 manifest: The manifest to merge. | 505 manifest: The manifest to merge. |
| 484 ''' | 506 ''' |
| 485 for bundle in manifest.GetBundles(): | 507 for bundle in manifest.GetBundles(): |
| 486 self.MergeBundle(bundle, allow_existing = False) | 508 self.MergeBundle(bundle, allow_existing=False) |
| 487 | 509 |
| 488 def FilterBundles(self, predicate): | 510 def FilterBundles(self, predicate): |
| 489 """Filter the list of bundles by |predicate|. | 511 """Filter the list of bundles by |predicate|. |
| 490 | 512 |
| 491 For all bundles in this manifest, if predicate(bundle) is False, the bundle | 513 For all bundles in this manifest, if predicate(bundle) is False, the bundle |
| 492 is removed from the manifest. | 514 is removed from the manifest. |
| 493 | 515 |
| 494 Args: | 516 Args: |
| 495 predicate: a function that take a bundle and returns whether True to keep | 517 predicate: a function that take a bundle and returns whether True to keep |
| 496 it or False to remove it. | 518 it or False to remove it. |
| 497 """ | 519 """ |
| 498 self._manifest_data[BUNDLES_KEY] = filter(predicate, self.GetBundles()) | 520 self._manifest_data[BUNDLES_KEY] = filter(predicate, self.GetBundles()) |
| 499 | 521 |
| 500 def LoadDataFromString(self, json_string): | 522 def LoadDataFromString(self, json_string, add_missing_info=False): |
| 501 """Load a JSON manifest string. Raises an exception if json_string | 523 """Load a JSON manifest string. Raises an exception if json_string |
| 502 is not well-formed JSON. | 524 is not well-formed JSON. |
| 503 | 525 |
| 504 Args: | 526 Args: |
| 505 json_string: a JSON-formatted string containing the previous manifest | 527 json_string: a JSON-formatted string containing the previous manifest |
| 506 all_hosts: True indicates that we should load bundles for all hosts. | 528 all_hosts: True indicates that we should load bundles for all hosts. |
| 507 False (default) says to only load bundles for the current host""" | 529 False (default) says to only load bundles for the current host""" |
| 508 new_manifest = json.loads(json_string) | 530 new_manifest = json.loads(json_string) |
| 509 for key, value in new_manifest.items(): | 531 for key, value in new_manifest.items(): |
| 510 if key == BUNDLES_KEY: | 532 if key == BUNDLES_KEY: |
| 511 # Remap each bundle in |value| to a Bundle instance | 533 # Remap each bundle in |value| to a Bundle instance |
| 512 bundles = [] | 534 bundles = [] |
| 513 for b in value: | 535 for b in value: |
| 514 new_bundle = Bundle(b[NAME_KEY]) | 536 new_bundle = Bundle(b[NAME_KEY]) |
| 515 new_bundle.CopyFrom(b) | 537 new_bundle.CopyFrom(b) |
| 516 bundles.append(new_bundle) | 538 bundles.append(new_bundle) |
| 517 self._manifest_data[key] = bundles | 539 self._manifest_data[key] = bundles |
| 518 else: | 540 else: |
| 519 self._manifest_data[key] = value | 541 self._manifest_data[key] = value |
| 520 self.Validate() | 542 self.Validate(add_missing_info) |
| 543 | |
| 544 def __str__(self): | |
| 545 return self.GetDataAsString() | |
| 521 | 546 |
| 522 def GetDataAsString(self): | 547 def GetDataAsString(self): |
| 523 """Returns the current JSON manifest object, pretty-printed""" | 548 """Returns the current JSON manifest object, pretty-printed""" |
| 524 return DictToJSON(self._manifest_data) | 549 return DictToJSON(self._manifest_data) |
| OLD | NEW |