OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 | 2 |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 # | 7 # |
8 # Xcode supports build variable substitutions and CPP; sadly, that doesn't work | 8 # Xcode supports build variable substitutions and CPP; sadly, that doesn't work |
9 # because: | 9 # because: |
10 # | 10 # |
(...skipping 13 matching lines...) Expand all Loading... | |
24 import os | 24 import os |
25 import plistlib | 25 import plistlib |
26 import re | 26 import re |
27 import subprocess | 27 import subprocess |
28 import sys | 28 import sys |
29 import tempfile | 29 import tempfile |
30 | 30 |
31 TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) | 31 TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) |
32 | 32 |
33 | 33 |
34 def _ConvertPlist(source_plist, output_plist, fmt): | |
35 """Convert |source_plist| to |fmt| and save as |output_plist|.""" | |
36 return subprocess.call( | |
37 ['plutil', '-convert', fmt, '-o', output_plist, source_plist]) | |
38 | |
39 | |
34 def _GetOutput(args): | 40 def _GetOutput(args): |
35 """Runs a subprocess and waits for termination. Returns (stdout, returncode) | 41 """Runs a subprocess and waits for termination. Returns (stdout, returncode) |
36 of the process. stderr is attached to the parent.""" | 42 of the process. stderr is attached to the parent.""" |
37 proc = subprocess.Popen(args, stdout=subprocess.PIPE) | 43 proc = subprocess.Popen(args, stdout=subprocess.PIPE) |
38 (stdout, stderr) = proc.communicate() | 44 (stdout, stderr) = proc.communicate() |
39 return (stdout, proc.returncode) | 45 return (stdout, proc.returncode) |
40 | 46 |
41 | 47 |
42 def _GetOutputNoError(args): | 48 def _GetOutputNoError(args): |
43 """Similar to _GetOutput() but ignores stderr. If there's an error launching | 49 """Similar to _GetOutput() but ignores stderr. If there's an error launching |
(...skipping 10 matching lines...) Expand all Loading... | |
54 | 60 |
55 def _RemoveKeys(plist, *keys): | 61 def _RemoveKeys(plist, *keys): |
56 """Removes a varargs of keys from the plist.""" | 62 """Removes a varargs of keys from the plist.""" |
57 for key in keys: | 63 for key in keys: |
58 try: | 64 try: |
59 del plist[key] | 65 del plist[key] |
60 except KeyError: | 66 except KeyError: |
61 pass | 67 pass |
62 | 68 |
63 | 69 |
64 def _AddVersionKeys(plist, version=None): | 70 def _ApplyVersionOverrides(version, keys, overrides, separator='.'): |
71 """Applies version overrides. | |
72 | |
73 Given a |version| string as "a.b.c.d" (assuming a default separator) with | |
74 version components named by |keys| then overrides any value that is present | |
75 in |overrides|. | |
76 | |
77 >>> _ApplyVersionOverrides('a.b', ['major', 'minor'], {'minor': 'd'}) | |
78 'a.d' | |
79 """ | |
80 if not overrides: | |
81 return version | |
82 version_values = version.split(separator) | |
83 for i, (key, value) in enumerate(zip(keys, version_values)): | |
84 if key in overrides: | |
85 version_values[i] = overrides[key] | |
86 return separator.join(version_values) | |
87 | |
88 | |
89 def _AddVersionKeys(plist, version=None, overrides=None): | |
65 """Adds the product version number into the plist. Returns True on success and | 90 """Adds the product version number into the plist. Returns True on success and |
66 False on error. The error will be printed to stderr.""" | 91 False on error. The error will be printed to stderr.""" |
67 if version: | 92 if version: |
68 match = re.match('\d+\.\d+\.(\d+\.\d+)$', version) | 93 match = re.match('\d+\.\d+\.(\d+\.\d+)$', version) |
69 if not match: | 94 if not match: |
70 print >>sys.stderr, 'Invalid version string specified: "%s"' % version | 95 print >>sys.stderr, 'Invalid version string specified: "%s"' % version |
71 return False | 96 return False |
72 | 97 |
73 full_version = match.group(0) | 98 full_version = match.group(0) |
74 bundle_version = match.group(1) | 99 bundle_version = match.group(1) |
75 | 100 |
76 else: | 101 else: |
77 # Pull in the Chrome version number. | 102 # Pull in the Chrome version number. |
78 VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') | 103 VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') |
79 VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') | 104 VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') |
80 | 105 |
81 (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', | 106 (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', |
82 '@MAJOR@.@MINOR@.@BUILD@.@PATCH@']) | 107 '@MAJOR@.@MINOR@.@BUILD@.@PATCH@']) |
83 full_version = stdout.rstrip() | 108 full_version = _ApplyVersionOverrides( |
109 stdout.rstrip(), ('MAJOR', 'MINOR', 'BUILD', 'PATCH'), overrides) | |
84 | 110 |
85 (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', | 111 (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', |
86 '@BUILD@.@PATCH@']) | 112 '@BUILD@.@PATCH@']) |
87 bundle_version = stdout.rstrip() | 113 bundle_version = _ApplyVersionOverrides( |
114 stdout.rstrip(), ('BUILD', 'PATCH'), overrides) | |
88 | 115 |
89 # If either of the two version commands finished with non-zero returncode, | 116 # If either of the two version commands finished with non-zero returncode, |
90 # report the error up. | 117 # report the error up. |
91 if retval1 or retval2: | 118 if retval1 or retval2: |
92 return False | 119 return False |
93 | 120 |
94 # Add public version info so "Get Info" works. | 121 # Add public version info so "Get Info" works. |
95 plist['CFBundleShortVersionString'] = full_version | 122 plist['CFBundleShortVersionString'] = full_version |
96 | 123 |
97 # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1 | 124 # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1 |
(...skipping 26 matching lines...) Expand all Loading... | |
124 # See if the operation failed. | 151 # See if the operation failed. |
125 _RemoveKeys(plist, 'SCMRevision') | 152 _RemoveKeys(plist, 'SCMRevision') |
126 if scm_revision != None: | 153 if scm_revision != None: |
127 plist['SCMRevision'] = scm_revision | 154 plist['SCMRevision'] = scm_revision |
128 elif add_keys: | 155 elif add_keys: |
129 print >>sys.stderr, 'Could not determine SCM revision. This may be OK.' | 156 print >>sys.stderr, 'Could not determine SCM revision. This may be OK.' |
130 | 157 |
131 return True | 158 return True |
132 | 159 |
133 | 160 |
134 def _AddBreakpadKeys(plist, branding): | 161 def _AddBreakpadKeys(plist, branding, platform): |
135 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and | 162 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and |
136 also requires the |branding| argument.""" | 163 also requires the |branding| argument.""" |
137 plist['BreakpadReportInterval'] = '3600' # Deliberately a string. | 164 plist['BreakpadReportInterval'] = '3600' # Deliberately a string. |
138 plist['BreakpadProduct'] = '%s_Mac' % branding | 165 plist['BreakpadProduct'] = '%s_%s' % ( |
166 branding, {'mac': 'Mac', 'ios': 'iOS'}[platform]) | |
139 plist['BreakpadProductDisplay'] = branding | 167 plist['BreakpadProductDisplay'] = branding |
140 plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] | 168 plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] |
141 # These are both deliberately strings and not boolean. | 169 # These are both deliberately strings and not boolean. |
142 plist['BreakpadSendAndExit'] = 'YES' | 170 plist['BreakpadSendAndExit'] = 'YES' |
143 plist['BreakpadSkipConfirm'] = 'YES' | 171 plist['BreakpadSkipConfirm'] = 'YES' |
144 | 172 |
145 | 173 |
146 def _RemoveBreakpadKeys(plist): | 174 def _RemoveBreakpadKeys(plist): |
147 """Removes any set Breakpad keys.""" | 175 """Removes any set Breakpad keys.""" |
148 _RemoveKeys(plist, | 176 _RemoveKeys(plist, |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
213 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]') | 241 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]') |
214 parser.add_option('--keystone', dest='use_keystone', action='store', | 242 parser.add_option('--keystone', dest='use_keystone', action='store', |
215 type='int', default=False, help='Enable Keystone [1 or 0]') | 243 type='int', default=False, help='Enable Keystone [1 or 0]') |
216 parser.add_option('--scm', dest='add_scm_info', action='store', type='int', | 244 parser.add_option('--scm', dest='add_scm_info', action='store', type='int', |
217 default=True, help='Add SCM metadata [1 or 0]') | 245 default=True, help='Add SCM metadata [1 or 0]') |
218 parser.add_option('--branding', dest='branding', action='store', | 246 parser.add_option('--branding', dest='branding', action='store', |
219 type='string', default=None, help='The branding of the binary') | 247 type='string', default=None, help='The branding of the binary') |
220 parser.add_option('--bundle_id', dest='bundle_identifier', | 248 parser.add_option('--bundle_id', dest='bundle_identifier', |
221 action='store', type='string', default=None, | 249 action='store', type='string', default=None, |
222 help='The bundle id of the binary') | 250 help='The bundle id of the binary') |
251 parser.add_option('--platform', choices=('ios', 'mac'), default='mac', | |
TVL
2016/06/03 15:49:11
Why not "iOS" and "Mac" so you don't have to do a
sdefresne
2016/06/03 16:11:45
This value will be passed from BUILD.gn or *.gyp f
TVL
2016/06/03 16:24:27
That's fair, minor nit then: you might want to do
sdefresne
2016/06/03 16:29:04
Done.
| |
252 help='The target platform of the bundle') | |
253 parser.add_option('--version-overrides', action='append', | |
254 help='Key-value pair to override specific component of version') | |
TVL
2016/06/03 15:49:11
Like --version does, you probably want to give an
sdefresne
2016/06/03 16:11:45
Done.
| |
255 parser.add_option('--format', choices=('binary1', 'xml1', 'json'), | |
256 default='xml1', help='Format to use when writing property list') | |
223 parser.add_option('--version', dest='version', action='store', type='string', | 257 parser.add_option('--version', dest='version', action='store', type='string', |
224 default=None, help='The version string [major.minor.build.patch]') | 258 default=None, help='The version string [major.minor.build.patch]') |
225 (options, args) = parser.parse_args(argv) | 259 (options, args) = parser.parse_args(argv) |
226 | 260 |
227 if len(args) > 0: | 261 if len(args) > 0: |
228 print >>sys.stderr, parser.get_usage() | 262 print >>sys.stderr, parser.get_usage() |
229 return 1 | 263 return 1 |
230 | 264 |
231 if not options.plist_path: | 265 if not options.plist_path: |
232 print >>sys.stderr, 'No --plist specified.' | 266 print >>sys.stderr, 'No --plist specified.' |
233 return 1 | 267 return 1 |
234 | 268 |
235 # Read the plist into its parsed format. | 269 # Read the plist into its parsed format. Convert the file to 'xml1' as |
236 plist = plistlib.readPlist(options.plist_path) | 270 # plistlib only supports that format in Python 2.7. |
271 with tempfile.NamedTemporaryFile() as temp_info_plist: | |
272 retcode = _ConvertPlist(options.plist_path, temp_info_plist.name, 'xml1') | |
273 if retcode != 0: | |
274 return retcode | |
275 plist = plistlib.readPlist(temp_info_plist.name) | |
276 | |
277 # Convert overrides. | |
278 overrides = {} | |
279 if options.version_overrides: | |
280 for pair in options.version_overrides: | |
281 if not '=' in pair: | |
282 print >>sys.stderr, 'Invalid value for --version-overrides:', pair | |
283 return 1 | |
284 key, value = pair.split('=', 1) | |
285 overrides[key] = value | |
TVL
2016/06/03 15:49:11
Do you want to validate the key to ensure it is on
sdefresne
2016/06/03 16:11:45
Done.
| |
237 | 286 |
238 # Insert the product version. | 287 # Insert the product version. |
239 if not _AddVersionKeys(plist, version=options.version): | 288 if not _AddVersionKeys(plist, version=options.version, overrides=overrides): |
240 return 2 | 289 return 2 |
241 | 290 |
242 # Add Breakpad if configured to do so. | 291 # Add Breakpad if configured to do so. |
243 if options.use_breakpad: | 292 if options.use_breakpad: |
244 if options.branding is None: | 293 if options.branding is None: |
245 print >>sys.stderr, 'Use of Breakpad requires branding.' | 294 print >>sys.stderr, 'Use of Breakpad requires branding.' |
246 return 1 | 295 return 1 |
247 _AddBreakpadKeys(plist, options.branding) | 296 _AddBreakpadKeys(plist, options.branding, options.platform) |
248 if options.breakpad_uploads: | 297 if options.breakpad_uploads: |
249 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' | 298 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' |
250 else: | 299 else: |
251 # This allows crash dumping to a file without uploading the | 300 # This allows crash dumping to a file without uploading the |
252 # dump, for testing purposes. Breakpad does not recognise | 301 # dump, for testing purposes. Breakpad does not recognise |
253 # "none" as a special value, but this does stop crash dump | 302 # "none" as a special value, but this does stop crash dump |
254 # uploading from happening. We need to specify something | 303 # uploading from happening. We need to specify something |
255 # because if "BreakpadURL" is not present, Breakpad will not | 304 # because if "BreakpadURL" is not present, Breakpad will not |
256 # register its crash handler and no crash dumping will occur. | 305 # register its crash handler and no crash dumping will occur. |
257 plist['BreakpadURL'] = 'none' | 306 plist['BreakpadURL'] = 'none' |
258 else: | 307 else: |
259 _RemoveBreakpadKeys(plist) | 308 _RemoveBreakpadKeys(plist) |
260 | 309 |
261 # Add Keystone if configured to do so. | 310 # Add Keystone if configured to do so. |
262 if options.use_keystone: | 311 if options.use_keystone: |
263 if options.bundle_identifier is None: | 312 if options.bundle_identifier is None: |
264 print >>sys.stderr, 'Use of Keystone requires the bundle id.' | 313 print >>sys.stderr, 'Use of Keystone requires the bundle id.' |
265 return 1 | 314 return 1 |
266 _AddKeystoneKeys(plist, options.bundle_identifier) | 315 _AddKeystoneKeys(plist, options.bundle_identifier) |
267 else: | 316 else: |
268 _RemoveKeystoneKeys(plist) | 317 _RemoveKeystoneKeys(plist) |
269 | 318 |
270 # Adds or removes any SCM keys. | 319 # Adds or removes any SCM keys. |
271 if not _DoSCMKeys(plist, options.add_scm_info): | 320 if not _DoSCMKeys(plist, options.add_scm_info): |
272 return 3 | 321 return 3 |
273 | 322 |
274 # Now that all keys have been mutated, rewrite the file. | |
275 temp_info_plist = tempfile.NamedTemporaryFile() | |
276 plistlib.writePlist(plist, temp_info_plist.name) | |
277 | |
278 # Info.plist will work perfectly well in any plist format, but traditionally | |
279 # applications use xml1 for this, so convert it to ensure that it's valid. | |
280 output_path = options.plist_path | 323 output_path = options.plist_path |
281 if options.plist_output is not None: | 324 if options.plist_output is not None: |
282 output_path = options.plist_output | 325 output_path = options.plist_output |
283 proc = subprocess.Popen(['plutil', '-convert', 'xml1', | 326 |
284 '-o', output_path, | 327 # Now that all keys have been mutated, rewrite the file. |
285 temp_info_plist.name]) | 328 with tempfile.NamedTemporaryFile() as temp_info_plist: |
286 proc.wait() | 329 plistlib.writePlist(plist, temp_info_plist.name) |
287 return proc.returncode | 330 |
331 # Info.plist will work perfectly well in any plist format, but traditionally | |
332 # applications use xml1 for this, so convert it to ensure that it's valid. | |
TVL
2016/06/03 15:49:11
Comment isn't completely right, you are honoring t
sdefresne
2016/06/03 16:11:45
Done.
| |
333 return _ConvertPlist(temp_info_plist.name, output_path, options.format) | |
288 | 334 |
289 | 335 |
290 if __name__ == '__main__': | 336 if __name__ == '__main__': |
291 sys.exit(Main(sys.argv[1:])) | 337 sys.exit(Main(sys.argv[1:])) |
OLD | NEW |