| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 | |
| 3 # Copyright (c) 2011 The Chromium 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 # | |
| 8 # Xcode supports build variable substitutions and CPP; sadly, that doesn't work | |
| 9 # because: | |
| 10 # | |
| 11 # 1. Xcode wants to do the Info.plist work before it runs any build phases, | |
| 12 # this means if we were to generate a .h file for INFOPLIST_PREFIX_HEADER | |
| 13 # we'd have to put it in another target so it runs in time. | |
| 14 # 2. Xcode also doesn't check to see if the header being used as a prefix for | |
| 15 # the Info.plist has changed. So even if we updated it, it's only looking | |
| 16 # at the modtime of the info.plist to see if that's changed. | |
| 17 # | |
| 18 # So, we work around all of this by making a script build phase that will run | |
| 19 # during the app build, and simply update the info.plist in place. This way | |
| 20 # by the time the app target is done, the info.plist is correct. | |
| 21 # | |
| 22 | |
| 23 import optparse | |
| 24 import os | |
| 25 from os import environ as env | |
| 26 import plistlib | |
| 27 import re | |
| 28 import subprocess | |
| 29 import sys | |
| 30 import tempfile | |
| 31 | |
| 32 TOP = os.path.join(env['SRCROOT'], '..') | |
| 33 | |
| 34 sys.path.insert(0, os.path.join(TOP, "build/util")) | |
| 35 import lastchange | |
| 36 | |
| 37 | |
| 38 def _GetOutput(args): | |
| 39 """Runs a subprocess and waits for termination. Returns (stdout, returncode) | |
| 40 of the process. stderr is attached to the parent.""" | |
| 41 proc = subprocess.Popen(args, stdout=subprocess.PIPE) | |
| 42 (stdout, stderr) = proc.communicate() | |
| 43 return (stdout, proc.returncode) | |
| 44 | |
| 45 | |
| 46 def _GetOutputNoError(args): | |
| 47 """Similar to _GetOutput() but ignores stderr. If there's an error launching | |
| 48 the child (like file not found), the exception will be caught and (None, 1) | |
| 49 will be returned to mimic quiet failure.""" | |
| 50 try: | |
| 51 proc = subprocess.Popen(args, stdout=subprocess.PIPE, | |
| 52 stderr=subprocess.PIPE) | |
| 53 except OSError: | |
| 54 return (None, 1) | |
| 55 (stdout, stderr) = proc.communicate() | |
| 56 return (stdout, proc.returncode) | |
| 57 | |
| 58 | |
| 59 def _RemoveKeys(plist, *keys): | |
| 60 """Removes a varargs of keys from the plist.""" | |
| 61 for key in keys: | |
| 62 try: | |
| 63 del plist[key] | |
| 64 except KeyError: | |
| 65 pass | |
| 66 | |
| 67 | |
| 68 def _AddVersionKeys(plist): | |
| 69 """Adds the product version number into the plist. Returns True on success and | |
| 70 False on error. The error will be printed to stderr.""" | |
| 71 # Pull in the Chrome version number. | |
| 72 VERSION_TOOL = os.path.join(TOP, 'chrome/tools/build/version.py') | |
| 73 VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') | |
| 74 | |
| 75 (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', | |
| 76 '@MAJOR@.@MINOR@.@BUILD@.@PATCH@']) | |
| 77 full_version = stdout.rstrip() | |
| 78 | |
| 79 (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', | |
| 80 '@BUILD@.@PATCH@']) | |
| 81 bundle_version = stdout.rstrip() | |
| 82 | |
| 83 # If either of the two version commands finished with non-zero returncode, | |
| 84 # report the error up. | |
| 85 if retval1 or retval2: | |
| 86 return False | |
| 87 | |
| 88 # Add public version info so "Get Info" works. | |
| 89 plist['CFBundleShortVersionString'] = full_version | |
| 90 | |
| 91 # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1 | |
| 92 # into 6, 2, 2 digits. The limitation was present in Tiger, but it could | |
| 93 # have been fixed in later OS release, but hasn't been tested (it's easy | |
| 94 # enough to find out with "lsregister -dump). | |
| 95 # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html | |
| 96 # BUILD will always be an increasing value, so BUILD_PATH gives us something | |
| 97 # unique that meetings what LS wants. | |
| 98 plist['CFBundleVersion'] = bundle_version | |
| 99 | |
| 100 # Return with no error. | |
| 101 return True | |
| 102 | |
| 103 | |
| 104 def _DoSVNKeys(plist, add_keys): | |
| 105 """Adds the SVN information, visible in about:version, to property list. If | |
| 106 |add_keys| is True, it will insert the keys, otherwise it will remove them.""" | |
| 107 scm_path, scm_revision = None, None | |
| 108 if add_keys: | |
| 109 version_info = lastchange.FetchVersionInfo( | |
| 110 default_lastchange=None, directory=TOP) | |
| 111 scm_path, scm_revision = version_info.url, version_info.revision | |
| 112 | |
| 113 # See if the operation failed. | |
| 114 _RemoveKeys(plist, 'SVNRevision') | |
| 115 if scm_revision != None: | |
| 116 plist['SVNRevision'] = scm_revision | |
| 117 elif add_keys: | |
| 118 print >>sys.stderr, 'Could not determine svn revision. This may be OK.' | |
| 119 | |
| 120 if scm_path != None: | |
| 121 plist['SVNPath'] = scm_path | |
| 122 else: | |
| 123 _RemoveKeys(plist, 'SVNPath') | |
| 124 | |
| 125 | |
| 126 def _DoPDFKeys(plist, add_keys): | |
| 127 """Adds PDF support to the document types list. If add_keys is True, it will | |
| 128 add the type information dictionary. If it is False, it will remove it if | |
| 129 present.""" | |
| 130 | |
| 131 PDF_FILE_EXTENSION = 'pdf' | |
| 132 | |
| 133 def __AddPDFKeys(sub_plist): | |
| 134 """Writes the keys into a sub-dictionary of the plist.""" | |
| 135 sub_plist['CFBundleTypeExtensions'] = [PDF_FILE_EXTENSION] | |
| 136 sub_plist['CFBundleTypeIconFile'] = 'document.icns' | |
| 137 sub_plist['CFBundleTypeMIMETypes'] = 'application/pdf' | |
| 138 sub_plist['CFBundleTypeName'] = 'PDF Document' | |
| 139 sub_plist['CFBundleTypeRole'] = 'Viewer' | |
| 140 | |
| 141 DOCUMENT_TYPES_KEY = 'CFBundleDocumentTypes' | |
| 142 | |
| 143 # First get the list of document types, creating it if necessary. | |
| 144 try: | |
| 145 extensions = plist[DOCUMENT_TYPES_KEY] | |
| 146 except KeyError: | |
| 147 # If this plist doesn't have a type dictionary, create one if set to add the | |
| 148 # keys. If not, bail. | |
| 149 if not add_keys: | |
| 150 return | |
| 151 extensions = plist[DOCUMENT_TYPES_KEY] = [] | |
| 152 | |
| 153 # Loop over each entry in the list, looking for one that handles PDF types. | |
| 154 for i, ext in enumerate(extensions): | |
| 155 # If an entry for .pdf files is found... | |
| 156 if 'CFBundleTypeExtensions' not in ext: | |
| 157 continue | |
| 158 if PDF_FILE_EXTENSION in ext['CFBundleTypeExtensions']: | |
| 159 if add_keys: | |
| 160 # Overwrite the existing keys with new ones. | |
| 161 __AddPDFKeys(ext) | |
| 162 else: | |
| 163 # Otherwise, delete the entry entirely. | |
| 164 del extensions[i] | |
| 165 return | |
| 166 | |
| 167 # No PDF entry exists. If one needs to be added, do so now. | |
| 168 if add_keys: | |
| 169 pdf_entry = {} | |
| 170 __AddPDFKeys(pdf_entry) | |
| 171 extensions.append(pdf_entry) | |
| 172 | |
| 173 | |
| 174 def _AddBreakpadKeys(plist, branding): | |
| 175 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and | |
| 176 also requires the |branding| argument.""" | |
| 177 plist['BreakpadReportInterval'] = '3600' # Deliberately a string. | |
| 178 plist['BreakpadProduct'] = '%s_Mac' % branding | |
| 179 plist['BreakpadProductDisplay'] = branding | |
| 180 plist['BreakpadVersion'] = plist['CFBundleShortVersionString'] | |
| 181 # These are both deliberately strings and not boolean. | |
| 182 plist['BreakpadSendAndExit'] = 'YES' | |
| 183 plist['BreakpadSkipConfirm'] = 'YES' | |
| 184 | |
| 185 | |
| 186 def _RemoveBreakpadKeys(plist): | |
| 187 """Removes any set Breakpad keys.""" | |
| 188 _RemoveKeys(plist, | |
| 189 'BreakpadURL', | |
| 190 'BreakpadReportInterval', | |
| 191 'BreakpadProduct', | |
| 192 'BreakpadProductDisplay', | |
| 193 'BreakpadVersion', | |
| 194 'BreakpadSendAndExit', | |
| 195 'BreakpadSkipConfirm') | |
| 196 | |
| 197 | |
| 198 def _AddKeystoneKeys(plist, bundle_identifier): | |
| 199 """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and | |
| 200 also requires the |bundle_identifier| argument (com.example.product).""" | |
| 201 plist['KSVersion'] = plist['CFBundleShortVersionString'] | |
| 202 plist['KSProductID'] = bundle_identifier | |
| 203 plist['KSUpdateURL'] = 'https://tools.google.com/service/update2' | |
| 204 | |
| 205 | |
| 206 def _RemoveKeystoneKeys(plist): | |
| 207 """Removes any set Keystone keys.""" | |
| 208 _RemoveKeys(plist, | |
| 209 'KSVersion', | |
| 210 'KSProductID', | |
| 211 'KSUpdateURL') | |
| 212 | |
| 213 | |
| 214 def Main(argv): | |
| 215 parser = optparse.OptionParser('%prog [options] branding bundle-id') | |
| 216 parser.add_option('--breakpad', dest='use_breakpad', action='store', | |
| 217 type='int', default=False, help='Enable Breakpad [1 or 0]') | |
| 218 parser.add_option('--breakpad_uploads', dest='breakpad_uploads', | |
| 219 action='store', type='int', default=False, | |
| 220 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]') | |
| 221 parser.add_option('-k', dest='use_keystone', action='store', type='int', | |
| 222 default=False, help='Enable Keystone [1 or 0]') | |
| 223 parser.add_option('-s', dest='add_svn_info', action='store', type='int', | |
| 224 default=True, help='Add SVN metadata [1 or 0]') | |
| 225 parser.add_option('-p', dest='add_pdf_support', action='store', type='int', | |
| 226 default=False, help='Add PDF file handler support [1 or 0]') | |
| 227 (options, args) = parser.parse_args(argv) | |
| 228 | |
| 229 if len(args) < 2: | |
| 230 print >>sys.stderr, parser.get_usage() | |
| 231 return 1 | |
| 232 | |
| 233 # Extract remaining arguments. | |
| 234 branding = args[0] | |
| 235 bundle_identifier = args[1] | |
| 236 | |
| 237 # Read the plist into its parsed format. | |
| 238 DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'], env['INFOPLIST_PATH']) | |
| 239 plist = plistlib.readPlist(DEST_INFO_PLIST) | |
| 240 | |
| 241 # Insert the product version. | |
| 242 if not _AddVersionKeys(plist): | |
| 243 return 2 | |
| 244 | |
| 245 # Add Breakpad if configured to do so. | |
| 246 if options.use_breakpad: | |
| 247 _AddBreakpadKeys(plist, branding) | |
| 248 if options.breakpad_uploads: | |
| 249 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' | |
| 250 else: | |
| 251 # This allows crash dumping to a file without uploading the | |
| 252 # dump, for testing purposes. Breakpad does not recognise | |
| 253 # "none" as a special value, but this does stop crash dump | |
| 254 # uploading from happening. We need to specify something | |
| 255 # because if "BreakpadURL" is not present, Breakpad will not | |
| 256 # register its crash handler and no crash dumping will occur. | |
| 257 plist['BreakpadURL'] = 'none' | |
| 258 else: | |
| 259 _RemoveBreakpadKeys(plist) | |
| 260 | |
| 261 # Only add Keystone in Release builds. | |
| 262 if options.use_keystone and env['CONFIGURATION'] == 'Release': | |
| 263 _AddKeystoneKeys(plist, bundle_identifier) | |
| 264 else: | |
| 265 _RemoveKeystoneKeys(plist) | |
| 266 | |
| 267 # Adds or removes any SVN keys. | |
| 268 _DoSVNKeys(plist, options.add_svn_info) | |
| 269 | |
| 270 # Adds or removes the PDF file handler entry. | |
| 271 _DoPDFKeys(plist, options.add_pdf_support) | |
| 272 | |
| 273 # Now that all keys have been mutated, rewrite the file. | |
| 274 temp_info_plist = tempfile.NamedTemporaryFile() | |
| 275 plistlib.writePlist(plist, temp_info_plist.name) | |
| 276 | |
| 277 # Info.plist will work perfectly well in any plist format, but traditionally | |
| 278 # applications use xml1 for this, so convert it to ensure that it's valid. | |
| 279 proc = subprocess.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST, | |
| 280 temp_info_plist.name]) | |
| 281 proc.wait() | |
| 282 return proc.returncode | |
| 283 | |
| 284 | |
| 285 if __name__ == '__main__': | |
| 286 sys.exit(Main(sys.argv[1:])) | |
| OLD | NEW |