Add gyp rules to make adb_reboot and remove rebootondisconnect gyp stuff.
[chromium-blink-merge.git] / build / mac / tweak_info_plist.py
blob761352ef735f02a0915ae78faff61a49e458e827
1 #!/usr/bin/env python
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
5 # found in the LICENSE file.
8 # Xcode supports build variable substitutions and CPP; sadly, that doesn't work
9 # because:
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.
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.
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
32 TOP = os.path.join(env['SRCROOT'], '..')
35 def _GetOutput(args):
36 """Runs a subprocess and waits for termination. Returns (stdout, returncode)
37 of the process. stderr is attached to the parent."""
38 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
39 (stdout, stderr) = proc.communicate()
40 return (stdout, proc.returncode)
43 def _GetOutputNoError(args):
44 """Similar to _GetOutput() but ignores stderr. If there's an error launching
45 the child (like file not found), the exception will be caught and (None, 1)
46 will be returned to mimic quiet failure."""
47 try:
48 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
49 stderr=subprocess.PIPE)
50 except OSError:
51 return (None, 1)
52 (stdout, stderr) = proc.communicate()
53 return (stdout, proc.returncode)
56 def _RemoveKeys(plist, *keys):
57 """Removes a varargs of keys from the plist."""
58 for key in keys:
59 try:
60 del plist[key]
61 except KeyError:
62 pass
65 def _AddVersionKeys(plist, version=None):
66 """Adds the product version number into the plist. Returns True on success and
67 False on error. The error will be printed to stderr."""
68 if version:
69 match = re.match('\d+\.\d+\.(\d+\.\d+)$', version)
70 if not match:
71 print >>sys.stderr, 'Invalid version string specified: "%s"' % version
72 return False
74 full_version = match.group(0)
75 bundle_version = match.group(1)
77 else:
78 # Pull in the Chrome version number.
79 VERSION_TOOL = os.path.join(TOP, 'chrome/tools/build/version.py')
80 VERSION_FILE = os.path.join(TOP, 'chrome/VERSION')
82 (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
83 '@MAJOR@.@MINOR@.@BUILD@.@PATCH@'])
84 full_version = stdout.rstrip()
86 (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
87 '@BUILD@.@PATCH@'])
88 bundle_version = stdout.rstrip()
90 # If either of the two version commands finished with non-zero returncode,
91 # report the error up.
92 if retval1 or retval2:
93 return False
95 # Add public version info so "Get Info" works.
96 plist['CFBundleShortVersionString'] = full_version
98 # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1
99 # into 6, 2, 2 digits. The limitation was present in Tiger, but it could
100 # have been fixed in later OS release, but hasn't been tested (it's easy
101 # enough to find out with "lsregister -dump).
102 # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html
103 # BUILD will always be an increasing value, so BUILD_PATH gives us something
104 # unique that meetings what LS wants.
105 plist['CFBundleVersion'] = bundle_version
107 # Return with no error.
108 return True
111 def _DoSCMKeys(plist, add_keys):
112 """Adds the SCM information, visible in about:version, to property list. If
113 |add_keys| is True, it will insert the keys, otherwise it will remove them."""
114 scm_revision = None
115 if add_keys:
116 # Pull in the Chrome revision number.
117 VERSION_TOOL = os.path.join(TOP, 'chrome/tools/build/version.py')
118 LASTCHANGE_FILE = os.path.join(TOP, 'build/util/LASTCHANGE')
119 (stdout, retval) = _GetOutput([VERSION_TOOL, '-f', LASTCHANGE_FILE, '-t',
120 '@LASTCHANGE@'])
121 if retval:
122 return False
123 scm_revision = stdout.rstrip()
125 # See if the operation failed.
126 _RemoveKeys(plist, 'SCMRevision')
127 if scm_revision != None:
128 plist['SCMRevision'] = scm_revision
129 elif add_keys:
130 print >>sys.stderr, 'Could not determine SCM revision. This may be OK.'
132 # TODO(thakis): Remove this once m25 has reached stable.
133 _RemoveKeys(plist, 'SCMPath')
134 return True
137 def _DoPDFKeys(plist, add_keys):
138 """Adds PDF support to the document types list. If add_keys is True, it will
139 add the type information dictionary. If it is False, it will remove it if
140 present."""
142 PDF_FILE_EXTENSION = 'pdf'
144 def __AddPDFKeys(sub_plist):
145 """Writes the keys into a sub-dictionary of the plist."""
146 sub_plist['CFBundleTypeExtensions'] = [PDF_FILE_EXTENSION]
147 sub_plist['CFBundleTypeIconFile'] = 'document.icns'
148 sub_plist['CFBundleTypeMIMETypes'] = 'application/pdf'
149 sub_plist['CFBundleTypeName'] = 'PDF Document'
150 sub_plist['CFBundleTypeRole'] = 'Viewer'
152 DOCUMENT_TYPES_KEY = 'CFBundleDocumentTypes'
154 # First get the list of document types, creating it if necessary.
155 try:
156 extensions = plist[DOCUMENT_TYPES_KEY]
157 except KeyError:
158 # If this plist doesn't have a type dictionary, create one if set to add the
159 # keys. If not, bail.
160 if not add_keys:
161 return
162 extensions = plist[DOCUMENT_TYPES_KEY] = []
164 # Loop over each entry in the list, looking for one that handles PDF types.
165 for i, ext in enumerate(extensions):
166 # If an entry for .pdf files is found...
167 if 'CFBundleTypeExtensions' not in ext:
168 continue
169 if PDF_FILE_EXTENSION in ext['CFBundleTypeExtensions']:
170 if add_keys:
171 # Overwrite the existing keys with new ones.
172 __AddPDFKeys(ext)
173 else:
174 # Otherwise, delete the entry entirely.
175 del extensions[i]
176 return
178 # No PDF entry exists. If one needs to be added, do so now.
179 if add_keys:
180 pdf_entry = {}
181 __AddPDFKeys(pdf_entry)
182 extensions.append(pdf_entry)
185 def _AddBreakpadKeys(plist, branding):
186 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and
187 also requires the |branding| argument."""
188 plist['BreakpadReportInterval'] = '3600' # Deliberately a string.
189 plist['BreakpadProduct'] = '%s_Mac' % branding
190 plist['BreakpadProductDisplay'] = branding
191 plist['BreakpadVersion'] = plist['CFBundleShortVersionString']
192 # These are both deliberately strings and not boolean.
193 plist['BreakpadSendAndExit'] = 'YES'
194 plist['BreakpadSkipConfirm'] = 'YES'
197 def _RemoveBreakpadKeys(plist):
198 """Removes any set Breakpad keys."""
199 _RemoveKeys(plist,
200 'BreakpadURL',
201 'BreakpadReportInterval',
202 'BreakpadProduct',
203 'BreakpadProductDisplay',
204 'BreakpadVersion',
205 'BreakpadSendAndExit',
206 'BreakpadSkipConfirm')
209 def _AddKeystoneKeys(plist, bundle_identifier):
210 """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
211 also requires the |bundle_identifier| argument (com.example.product)."""
212 plist['KSVersion'] = plist['CFBundleShortVersionString']
213 plist['KSProductID'] = bundle_identifier
214 plist['KSUpdateURL'] = 'https://tools.google.com/service/update2'
217 def _RemoveKeystoneKeys(plist):
218 """Removes any set Keystone keys."""
219 _RemoveKeys(plist,
220 'KSVersion',
221 'KSProductID',
222 'KSUpdateURL')
225 def Main(argv):
226 parser = optparse.OptionParser('%prog [options]')
227 parser.add_option('--breakpad', dest='use_breakpad', action='store',
228 type='int', default=False, help='Enable Breakpad [1 or 0]')
229 parser.add_option('--breakpad_uploads', dest='breakpad_uploads',
230 action='store', type='int', default=False,
231 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]')
232 parser.add_option('--keystone', dest='use_keystone', action='store',
233 type='int', default=False, help='Enable Keystone [1 or 0]')
234 parser.add_option('--scm', dest='add_scm_info', action='store', type='int',
235 default=True, help='Add SCM metadata [1 or 0]')
236 parser.add_option('--pdf', dest='add_pdf_support', action='store', type='int',
237 default=False, help='Add PDF file handler support [1 or 0]')
238 parser.add_option('--branding', dest='branding', action='store',
239 type='string', default=None, help='The branding of the binary')
240 parser.add_option('--bundle_id', dest='bundle_identifier',
241 action='store', type='string', default=None,
242 help='The bundle id of the binary')
243 parser.add_option('--version', dest='version', action='store', type='string',
244 default=None, help='The version string [major.minor.build.patch]')
245 (options, args) = parser.parse_args(argv)
247 if len(args) > 0:
248 print >>sys.stderr, parser.get_usage()
249 return 1
251 # Read the plist into its parsed format.
252 DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'], env['INFOPLIST_PATH'])
253 plist = plistlib.readPlist(DEST_INFO_PLIST)
255 # Insert the product version.
256 if not _AddVersionKeys(plist, version=options.version):
257 return 2
259 # Add Breakpad if configured to do so.
260 if options.use_breakpad:
261 if options.branding is None:
262 print >>sys.stderr, 'Use of Breakpad requires branding.'
263 return 1
264 _AddBreakpadKeys(plist, options.branding)
265 if options.breakpad_uploads:
266 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report'
267 else:
268 # This allows crash dumping to a file without uploading the
269 # dump, for testing purposes. Breakpad does not recognise
270 # "none" as a special value, but this does stop crash dump
271 # uploading from happening. We need to specify something
272 # because if "BreakpadURL" is not present, Breakpad will not
273 # register its crash handler and no crash dumping will occur.
274 plist['BreakpadURL'] = 'none'
275 else:
276 _RemoveBreakpadKeys(plist)
278 # Only add Keystone in Release builds.
279 if options.use_keystone and env['CONFIGURATION'] == 'Release':
280 if options.bundle_identifier is None:
281 print >>sys.stderr, 'Use of Keystone requires the bundle id.'
282 return 1
283 _AddKeystoneKeys(plist, options.bundle_identifier)
284 else:
285 _RemoveKeystoneKeys(plist)
287 # Adds or removes any SCM keys.
288 if not _DoSCMKeys(plist, options.add_scm_info):
289 return 3
291 # Adds or removes the PDF file handler entry.
292 _DoPDFKeys(plist, options.add_pdf_support)
294 # Now that all keys have been mutated, rewrite the file.
295 temp_info_plist = tempfile.NamedTemporaryFile()
296 plistlib.writePlist(plist, temp_info_plist.name)
298 # Info.plist will work perfectly well in any plist format, but traditionally
299 # applications use xml1 for this, so convert it to ensure that it's valid.
300 proc = subprocess.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST,
301 temp_info_plist.name])
302 proc.wait()
303 return proc.returncode
306 if __name__ == '__main__':
307 sys.exit(Main(sys.argv[1:]))