| 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' % (branding, platform) |
| 139 plist['BreakpadProductDisplay'] = branding | 166 plist['BreakpadProductDisplay'] = branding |
| 140 plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] | 167 plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] |
| 141 # These are both deliberately strings and not boolean. | 168 # These are both deliberately strings and not boolean. |
| 142 plist['BreakpadSendAndExit'] = 'YES' | 169 plist['BreakpadSendAndExit'] = 'YES' |
| 143 plist['BreakpadSkipConfirm'] = 'YES' | 170 plist['BreakpadSkipConfirm'] = 'YES' |
| 144 | 171 |
| 145 | 172 |
| 146 def _RemoveBreakpadKeys(plist): | 173 def _RemoveBreakpadKeys(plist): |
| 147 """Removes any set Breakpad keys.""" | 174 """Removes any set Breakpad keys.""" |
| 148 _RemoveKeys(plist, | 175 _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]') | 240 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]') |
| 214 parser.add_option('--keystone', dest='use_keystone', action='store', | 241 parser.add_option('--keystone', dest='use_keystone', action='store', |
| 215 type='int', default=False, help='Enable Keystone [1 or 0]') | 242 type='int', default=False, help='Enable Keystone [1 or 0]') |
| 216 parser.add_option('--scm', dest='add_scm_info', action='store', type='int', | 243 parser.add_option('--scm', dest='add_scm_info', action='store', type='int', |
| 217 default=True, help='Add SCM metadata [1 or 0]') | 244 default=True, help='Add SCM metadata [1 or 0]') |
| 218 parser.add_option('--branding', dest='branding', action='store', | 245 parser.add_option('--branding', dest='branding', action='store', |
| 219 type='string', default=None, help='The branding of the binary') | 246 type='string', default=None, help='The branding of the binary') |
| 220 parser.add_option('--bundle_id', dest='bundle_identifier', | 247 parser.add_option('--bundle_id', dest='bundle_identifier', |
| 221 action='store', type='string', default=None, | 248 action='store', type='string', default=None, |
| 222 help='The bundle id of the binary') | 249 help='The bundle id of the binary') |
| 250 parser.add_option('--platform', choices=('ios', 'mac'), default='mac', |
| 251 help='The target platform of the bundle') |
| 252 parser.add_option('--version-overrides', action='append', |
| 253 help='Key-value pair to override specific component of version ' |
| 254 'like key=value (can be passed multiple time to configure ' |
| 255 'more than one override)') |
| 256 parser.add_option('--format', choices=('binary1', 'xml1', 'json'), |
| 257 default='xml1', help='Format to use when writing property list ' |
| 258 '(default: %(default)s)') |
| 223 parser.add_option('--version', dest='version', action='store', type='string', | 259 parser.add_option('--version', dest='version', action='store', type='string', |
| 224 default=None, help='The version string [major.minor.build.patch]') | 260 default=None, help='The version string [major.minor.build.patch]') |
| 225 (options, args) = parser.parse_args(argv) | 261 (options, args) = parser.parse_args(argv) |
| 226 | 262 |
| 227 if len(args) > 0: | 263 if len(args) > 0: |
| 228 print >>sys.stderr, parser.get_usage() | 264 print >>sys.stderr, parser.get_usage() |
| 229 return 1 | 265 return 1 |
| 230 | 266 |
| 231 if not options.plist_path: | 267 if not options.plist_path: |
| 232 print >>sys.stderr, 'No --plist specified.' | 268 print >>sys.stderr, 'No --plist specified.' |
| 233 return 1 | 269 return 1 |
| 234 | 270 |
| 235 # Read the plist into its parsed format. | 271 # Read the plist into its parsed format. Convert the file to 'xml1' as |
| 236 plist = plistlib.readPlist(options.plist_path) | 272 # plistlib only supports that format in Python 2.7. |
| 273 with tempfile.NamedTemporaryFile() as temp_info_plist: |
| 274 retcode = _ConvertPlist(options.plist_path, temp_info_plist.name, 'xml1') |
| 275 if retcode != 0: |
| 276 return retcode |
| 277 plist = plistlib.readPlist(temp_info_plist.name) |
| 278 |
| 279 # Convert overrides. |
| 280 overrides = {} |
| 281 if options.version_overrides: |
| 282 for pair in options.version_overrides: |
| 283 if not '=' in pair: |
| 284 print >>sys.stderr, 'Invalid value for --version-overrides:', pair |
| 285 return 1 |
| 286 key, value = pair.split('=', 1) |
| 287 overrides[key] = value |
| 288 if key not in ('MAJOR', 'MINOR', 'BUILD', 'PATCH'): |
| 289 print >>sys.stderr, 'Unsupported key for --version-overrides:', key |
| 290 return 1 |
| 237 | 291 |
| 238 # Insert the product version. | 292 # Insert the product version. |
| 239 if not _AddVersionKeys(plist, version=options.version): | 293 if not _AddVersionKeys(plist, version=options.version, overrides=overrides): |
| 240 return 2 | 294 return 2 |
| 241 | 295 |
| 242 # Add Breakpad if configured to do so. | 296 # Add Breakpad if configured to do so. |
| 243 if options.use_breakpad: | 297 if options.use_breakpad: |
| 244 if options.branding is None: | 298 if options.branding is None: |
| 245 print >>sys.stderr, 'Use of Breakpad requires branding.' | 299 print >>sys.stderr, 'Use of Breakpad requires branding.' |
| 246 return 1 | 300 return 1 |
| 247 _AddBreakpadKeys(plist, options.branding) | 301 # Map gyp "OS" / gn "target_os" passed via the --platform parameter to |
| 302 # the platform as known by breakpad. |
| 303 platform = {'mac': 'Mac', 'ios': 'iOS'}[options.platform] |
| 304 _AddBreakpadKeys(plist, options.branding, platform) |
| 248 if options.breakpad_uploads: | 305 if options.breakpad_uploads: |
| 249 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' | 306 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' |
| 250 else: | 307 else: |
| 251 # This allows crash dumping to a file without uploading the | 308 # This allows crash dumping to a file without uploading the |
| 252 # dump, for testing purposes. Breakpad does not recognise | 309 # dump, for testing purposes. Breakpad does not recognise |
| 253 # "none" as a special value, but this does stop crash dump | 310 # "none" as a special value, but this does stop crash dump |
| 254 # uploading from happening. We need to specify something | 311 # uploading from happening. We need to specify something |
| 255 # because if "BreakpadURL" is not present, Breakpad will not | 312 # because if "BreakpadURL" is not present, Breakpad will not |
| 256 # register its crash handler and no crash dumping will occur. | 313 # register its crash handler and no crash dumping will occur. |
| 257 plist['BreakpadURL'] = 'none' | 314 plist['BreakpadURL'] = 'none' |
| 258 else: | 315 else: |
| 259 _RemoveBreakpadKeys(plist) | 316 _RemoveBreakpadKeys(plist) |
| 260 | 317 |
| 261 # Add Keystone if configured to do so. | 318 # Add Keystone if configured to do so. |
| 262 if options.use_keystone: | 319 if options.use_keystone: |
| 263 if options.bundle_identifier is None: | 320 if options.bundle_identifier is None: |
| 264 print >>sys.stderr, 'Use of Keystone requires the bundle id.' | 321 print >>sys.stderr, 'Use of Keystone requires the bundle id.' |
| 265 return 1 | 322 return 1 |
| 266 _AddKeystoneKeys(plist, options.bundle_identifier) | 323 _AddKeystoneKeys(plist, options.bundle_identifier) |
| 267 else: | 324 else: |
| 268 _RemoveKeystoneKeys(plist) | 325 _RemoveKeystoneKeys(plist) |
| 269 | 326 |
| 270 # Adds or removes any SCM keys. | 327 # Adds or removes any SCM keys. |
| 271 if not _DoSCMKeys(plist, options.add_scm_info): | 328 if not _DoSCMKeys(plist, options.add_scm_info): |
| 272 return 3 | 329 return 3 |
| 273 | 330 |
| 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 | 331 output_path = options.plist_path |
| 281 if options.plist_output is not None: | 332 if options.plist_output is not None: |
| 282 output_path = options.plist_output | 333 output_path = options.plist_output |
| 283 proc = subprocess.Popen(['plutil', '-convert', 'xml1', | 334 |
| 284 '-o', output_path, | 335 # Now that all keys have been mutated, rewrite the file. |
| 285 temp_info_plist.name]) | 336 with tempfile.NamedTemporaryFile() as temp_info_plist: |
| 286 proc.wait() | 337 plistlib.writePlist(plist, temp_info_plist.name) |
| 287 return proc.returncode | 338 |
| 339 # Convert Info.plist to the format requested by the --format flag. Any |
| 340 # format would work on Mac but iOS requires specific format. |
| 341 return _ConvertPlist(temp_info_plist.name, output_path, options.format) |
| 288 | 342 |
| 289 | 343 |
| 290 if __name__ == '__main__': | 344 if __name__ == '__main__': |
| 291 sys.exit(Main(sys.argv[1:])) | 345 sys.exit(Main(sys.argv[1:])) |
| OLD | NEW |