OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
| 6 import hashlib |
6 import json | 7 import json |
| 8 import sys |
7 | 9 |
8 MANIFEST_VERSION = 2 | 10 MANIFEST_VERSION = 2 |
9 | 11 |
10 # Some commonly-used key names. | 12 # Some commonly-used key names. |
11 ARCHIVES_KEY = 'archives' | 13 ARCHIVES_KEY = 'archives' |
12 BUNDLES_KEY = 'bundles' | 14 BUNDLES_KEY = 'bundles' |
13 NAME_KEY = 'name' | 15 NAME_KEY = 'name' |
14 REVISION_KEY = 'revision' | 16 REVISION_KEY = 'revision' |
15 VERSION_KEY = 'version' | 17 VERSION_KEY = 'version' |
16 | 18 |
(...skipping 10 matching lines...) Expand all Loading... |
27 # Valid values for bundle-recommended field. | 29 # Valid values for bundle-recommended field. |
28 YES_NO_LITERALS = ['yes', 'no'] | 30 YES_NO_LITERALS = ['yes', 'no'] |
29 VALID_BUNDLES_KEYS = frozenset([ | 31 VALID_BUNDLES_KEYS = frozenset([ |
30 ARCHIVES_KEY, NAME_KEY, VERSION_KEY, REVISION_KEY, | 32 ARCHIVES_KEY, NAME_KEY, VERSION_KEY, REVISION_KEY, |
31 'description', 'desc_url', 'stability', 'recommended', 'repath', | 33 'description', 'desc_url', 'stability', 'recommended', 'repath', |
32 ]) | 34 ]) |
33 | 35 |
34 VALID_MANIFEST_KEYS = frozenset(['manifest_version', BUNDLES_KEY]) | 36 VALID_MANIFEST_KEYS = frozenset(['manifest_version', BUNDLES_KEY]) |
35 | 37 |
36 | 38 |
| 39 def GetHostOS(): |
| 40 '''Returns the host_os value that corresponds to the current host OS''' |
| 41 return { |
| 42 'linux2': 'linux', |
| 43 'darwin': 'mac', |
| 44 'cygwin': 'win', |
| 45 'win32': 'win' |
| 46 }[sys.platform] |
| 47 |
| 48 |
| 49 def DictToJSON(dict): |
| 50 """Convert a dict to a JSON-formatted string.""" |
| 51 pretty_string = json.dumps(dict, sort_keys=False, indent=2) |
| 52 # json.dumps sometimes returns trailing whitespace and does not put |
| 53 # a newline at the end. This code fixes these problems. |
| 54 pretty_lines = pretty_string.split('\n') |
| 55 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' |
| 56 |
| 57 |
| 58 def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): |
| 59 ''' Download the archive data from from-stream and generate sha1 and |
| 60 size info. |
| 61 |
| 62 Args: |
| 63 from_stream: An input stream that supports read. |
| 64 to_stream: [optional] the data is written to to_stream if it is |
| 65 provided. |
| 66 progress_func: [optional] A function used to report download progress. If |
| 67 provided, progress_func is called with progress=0 at the |
| 68 beginning of the download, periodically with progress=1 |
| 69 during the download, and progress=100 at the end. |
| 70 |
| 71 Return |
| 72 A tuple (sha1, size) where sha1 is a sha1-hash for the archive data and |
| 73 size is the size of the archive data in bytes.''' |
| 74 # Use a no-op progress function if none is specified. |
| 75 def progress_no_op(progress): |
| 76 pass |
| 77 if not progress_func: |
| 78 progress_func = progress_no_op |
| 79 |
| 80 sha1_hash = hashlib.sha1() |
| 81 size = 0 |
| 82 progress_func(progress=0) |
| 83 while(1): |
| 84 data = from_stream.read(32768) |
| 85 if not data: |
| 86 break |
| 87 sha1_hash.update(data) |
| 88 size += len(data) |
| 89 if to_stream: |
| 90 to_stream.write(data) |
| 91 progress_func(size) |
| 92 |
| 93 progress_func(progress=100) |
| 94 return sha1_hash.hexdigest(), size |
| 95 |
| 96 |
37 class Error(Exception): | 97 class Error(Exception): |
38 """Generic error/exception for manifest_util module""" | 98 """Generic error/exception for manifest_util module""" |
39 pass | 99 pass |
40 | 100 |
41 | 101 |
42 class Archive(dict): | 102 class Archive(dict): |
43 """A placeholder for sdk archive information. We derive Archive from | 103 """A placeholder for sdk archive information. We derive Archive from |
44 dict so that it is easily serializable. """ | 104 dict so that it is easily serializable. """ |
45 | 105 |
46 def __init__(self, host_os_name): | 106 def __init__(self, host_os_name): |
(...skipping 16 matching lines...) Expand all Loading... |
63 Returns: True if self is a valid bundle. | 123 Returns: True if self is a valid bundle. |
64 """ | 124 """ |
65 host_os = self.get('host_os', None) | 125 host_os = self.get('host_os', None) |
66 if host_os and host_os not in HOST_OS_LITERALS: | 126 if host_os and host_os not in HOST_OS_LITERALS: |
67 raise Error('Invalid host-os name in archive') | 127 raise Error('Invalid host-os name in archive') |
68 # Ensure host_os has a valid string. We'll use it for pretty printing. | 128 # Ensure host_os has a valid string. We'll use it for pretty printing. |
69 if not host_os: | 129 if not host_os: |
70 host_os = 'all (default)' | 130 host_os = 'all (default)' |
71 if not self.get('url', None): | 131 if not self.get('url', None): |
72 raise Error('Archive "%s" has no URL' % host_os) | 132 raise Error('Archive "%s" has no URL' % host_os) |
| 133 if not self.get('size', None): |
| 134 raise Error('Archive "%s" has no size' % host_os) |
| 135 checksum = self.get('checksum', None) |
| 136 if not checksum: |
| 137 raise Error('Archive "%s" has no checksum' % host_os) |
| 138 elif not isinstance(checksum, dict): |
| 139 raise Error('Archive "%s" has a checksum, but it is not a dict' % host_os) |
| 140 elif not len(checksum): |
| 141 raise Error('Archive "%s" has an empty checksum dict' % host_os) |
73 # Verify that all key names are valid. | 142 # Verify that all key names are valid. |
74 for key, val in self.iteritems(): | 143 for key, val in self.iteritems(): |
75 if key not in VALID_ARCHIVE_KEYS: | 144 if key not in VALID_ARCHIVE_KEYS: |
76 raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) | 145 raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) |
77 | 146 |
78 @property | 147 @property |
79 def url(self): | 148 def url(self): |
80 """Returns the URL of this archive""" | 149 """Returns the URL of this archive""" |
81 return self['url'] | 150 return self['url'] |
82 | 151 |
| 152 @url.setter |
| 153 def url(self, url): |
| 154 """Set the URL of this archive""" |
| 155 self['url'] = url |
| 156 |
83 @property | 157 @property |
84 def size(self): | 158 def size(self): |
85 """Returns the size of this archive, in bytes""" | 159 """Returns the size of this archive, in bytes""" |
86 return self['size'] | 160 return self['size'] |
87 | 161 |
88 @property | 162 @property |
89 def host_os(self): | 163 def host_os(self): |
90 """Returns the host OS of this archive""" | 164 """Returns the host OS of this archive""" |
91 return self['host_os'] | 165 return self['host_os'] |
92 | 166 |
(...skipping 24 matching lines...) Expand all Loading... |
117 either of the files or dicts sets are removed from the latter, meaning that | 191 either of the files or dicts sets are removed from the latter, meaning that |
118 symlinks or links which overlap file or directory entries take precedence. | 192 symlinks or links which overlap file or directory entries take precedence. |
119 | 193 |
120 Args: | 194 Args: |
121 bundle: The other bundle. Must be a dict. | 195 bundle: The other bundle. Must be a dict. |
122 Returns: | 196 Returns: |
123 A dict which is the result of merging the two Bundles. | 197 A dict which is the result of merging the two Bundles. |
124 """ | 198 """ |
125 return Bundle(self.items() + bundle.items()) | 199 return Bundle(self.items() + bundle.items()) |
126 | 200 |
| 201 def ToJSON(self): |
| 202 """Convert this bundle to a JSON-formatted string.""" |
| 203 return DictToJSON(self) |
| 204 |
| 205 def FromJSON(self, json_string): |
| 206 """Parse and load bundle data from a JSON-formatted string.""" |
| 207 self.CopyFrom(json.loads(json_string)) |
| 208 |
127 def CopyFrom(self, dict): | 209 def CopyFrom(self, dict): |
128 """Update the content of the bundle by copying values from the given | 210 """Update the content of the bundle by copying values from the given |
129 dictionary. | 211 dictionary. |
130 | 212 |
131 Args: | 213 Args: |
132 dict: The dictionary whose values must be copied to the bundle.""" | 214 dict: The dictionary whose values must be copied to the bundle.""" |
133 for key, value in dict.items(): | 215 for key, value in dict.items(): |
134 if key == ARCHIVES_KEY: | 216 if key == ARCHIVES_KEY: |
135 archives = [] | 217 archives = [] |
136 for a in value: | 218 for a in value: |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
180 | 262 |
181 Args: | 263 Args: |
182 host_os_name: name of host os whose archive must be retrieved. | 264 host_os_name: name of host os whose archive must be retrieved. |
183 Return: | 265 Return: |
184 An Archive instance or None if it doesn't exist.""" | 266 An Archive instance or None if it doesn't exist.""" |
185 for archive in self[ARCHIVES_KEY]: | 267 for archive in self[ARCHIVES_KEY]: |
186 if archive.host_os == host_os_name: | 268 if archive.host_os == host_os_name: |
187 return archive | 269 return archive |
188 return None | 270 return None |
189 | 271 |
| 272 def GetHostOSArchive(self): |
| 273 """Retrieve the archive for the current host os.""" |
| 274 return self.GetArchive(GetHostOS()) |
| 275 |
190 def GetArchives(self): | 276 def GetArchives(self): |
191 """Returns all the archives in this bundle""" | 277 """Returns all the archives in this bundle""" |
192 return self[ARCHIVES_KEY] | 278 return self[ARCHIVES_KEY] |
193 | 279 |
194 @property | 280 @property |
195 def name(self): | 281 def name(self): |
196 """Returns the name of this bundle""" | 282 """Returns the name of this bundle""" |
197 return self[NAME_KEY] | 283 return self[NAME_KEY] |
198 | 284 |
199 @property | 285 @property |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 new_bundle = Bundle(b[NAME_KEY]) | 425 new_bundle = Bundle(b[NAME_KEY]) |
340 new_bundle.CopyFrom(b) | 426 new_bundle.CopyFrom(b) |
341 bundles.append(new_bundle) | 427 bundles.append(new_bundle) |
342 self._manifest_data[key] = bundles | 428 self._manifest_data[key] = bundles |
343 else: | 429 else: |
344 self._manifest_data[key] = value | 430 self._manifest_data[key] = value |
345 self.Validate() | 431 self.Validate() |
346 | 432 |
347 def GetManifestString(self): | 433 def GetManifestString(self): |
348 """Returns the current JSON manifest object, pretty-printed""" | 434 """Returns the current JSON manifest object, pretty-printed""" |
349 pretty_string = json.dumps(self._manifest_data, sort_keys=False, indent=2) | 435 return DictToJSON(self._manifest_data) |
350 # json.dumps sometimes returns trailing whitespace and does not put | |
351 # a newline at the end. This code fixes these problems. | |
352 pretty_lines = pretty_string.split('\n') | |
353 return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' | |
OLD | NEW |