Roll src/third_party/WebKit 605a979:06cb9e9 (svn 202556:202558)
[chromium-blink-merge.git] / build / mac / tweak_info_plist.py
blob2057bac83865d1747ae7e41917e85f28d4d1a028
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.dirname(os.path.dirname(os.path.dirname(__file__)))
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, 'build/util/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, 'build/util/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 return True
135 def _AddBreakpadKeys(plist, branding):
136 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and
137 also requires the |branding| argument."""
138 plist['BreakpadReportInterval'] = '3600' # Deliberately a string.
139 plist['BreakpadProduct'] = '%s_Mac' % branding
140 plist['BreakpadProductDisplay'] = branding
141 plist['BreakpadVersion'] = plist['CFBundleShortVersionString']
142 # These are both deliberately strings and not boolean.
143 plist['BreakpadSendAndExit'] = 'YES'
144 plist['BreakpadSkipConfirm'] = 'YES'
147 def _RemoveBreakpadKeys(plist):
148 """Removes any set Breakpad keys."""
149 _RemoveKeys(plist,
150 'BreakpadURL',
151 'BreakpadReportInterval',
152 'BreakpadProduct',
153 'BreakpadProductDisplay',
154 'BreakpadVersion',
155 'BreakpadSendAndExit',
156 'BreakpadSkipConfirm')
159 def _TagSuffixes():
160 # Keep this list sorted in the order that tag suffix components are to
161 # appear in a tag value. That is to say, it should be sorted per ASCII.
162 components = ('32bit', 'full')
163 assert tuple(sorted(components)) == components
165 components_len = len(components)
166 combinations = 1 << components_len
167 tag_suffixes = []
168 for combination in xrange(0, combinations):
169 tag_suffix = ''
170 for component_index in xrange(0, components_len):
171 if combination & (1 << component_index):
172 tag_suffix += '-' + components[component_index]
173 tag_suffixes.append(tag_suffix)
174 return tag_suffixes
177 def _AddKeystoneKeys(plist, bundle_identifier):
178 """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
179 also requires the |bundle_identifier| argument (com.example.product)."""
180 plist['KSVersion'] = plist['CFBundleShortVersionString']
181 plist['KSProductID'] = bundle_identifier
182 plist['KSUpdateURL'] = 'https://tools.google.com/service/update2'
184 _RemoveKeys(plist, 'KSChannelID')
185 for tag_suffix in _TagSuffixes():
186 if tag_suffix:
187 plist['KSChannelID' + tag_suffix] = tag_suffix
190 def _RemoveKeystoneKeys(plist):
191 """Removes any set Keystone keys."""
192 _RemoveKeys(plist,
193 'KSVersion',
194 'KSProductID',
195 'KSUpdateURL')
197 tag_keys = []
198 for tag_suffix in _TagSuffixes():
199 tag_keys.append('KSChannelID' + tag_suffix)
200 _RemoveKeys(plist, *tag_keys)
203 def Main(argv):
204 parser = optparse.OptionParser('%prog [options]')
205 parser.add_option('--breakpad', dest='use_breakpad', action='store',
206 type='int', default=False, help='Enable Breakpad [1 or 0]')
207 parser.add_option('--breakpad_uploads', dest='breakpad_uploads',
208 action='store', type='int', default=False,
209 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]')
210 parser.add_option('--keystone', dest='use_keystone', action='store',
211 type='int', default=False, help='Enable Keystone [1 or 0]')
212 parser.add_option('--scm', dest='add_scm_info', action='store', type='int',
213 default=True, help='Add SCM metadata [1 or 0]')
214 parser.add_option('--branding', dest='branding', action='store',
215 type='string', default=None, help='The branding of the binary')
216 parser.add_option('--bundle_id', dest='bundle_identifier',
217 action='store', type='string', default=None,
218 help='The bundle id of the binary')
219 parser.add_option('--version', dest='version', action='store', type='string',
220 default=None, help='The version string [major.minor.build.patch]')
221 (options, args) = parser.parse_args(argv)
223 if len(args) > 0:
224 print >>sys.stderr, parser.get_usage()
225 return 1
227 # Read the plist into its parsed format.
228 DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'], env['INFOPLIST_PATH'])
229 plist = plistlib.readPlist(DEST_INFO_PLIST)
231 # Insert the product version.
232 if not _AddVersionKeys(plist, version=options.version):
233 return 2
235 # Add Breakpad if configured to do so.
236 if options.use_breakpad:
237 if options.branding is None:
238 print >>sys.stderr, 'Use of Breakpad requires branding.'
239 return 1
240 _AddBreakpadKeys(plist, options.branding)
241 if options.breakpad_uploads:
242 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report'
243 else:
244 # This allows crash dumping to a file without uploading the
245 # dump, for testing purposes. Breakpad does not recognise
246 # "none" as a special value, but this does stop crash dump
247 # uploading from happening. We need to specify something
248 # because if "BreakpadURL" is not present, Breakpad will not
249 # register its crash handler and no crash dumping will occur.
250 plist['BreakpadURL'] = 'none'
251 else:
252 _RemoveBreakpadKeys(plist)
254 # Only add Keystone in Release builds.
255 if options.use_keystone and env['CONFIGURATION'] == 'Release':
256 if options.bundle_identifier is None:
257 print >>sys.stderr, 'Use of Keystone requires the bundle id.'
258 return 1
259 _AddKeystoneKeys(plist, options.bundle_identifier)
260 else:
261 _RemoveKeystoneKeys(plist)
263 # Adds or removes any SCM keys.
264 if not _DoSCMKeys(plist, options.add_scm_info):
265 return 3
267 # Now that all keys have been mutated, rewrite the file.
268 temp_info_plist = tempfile.NamedTemporaryFile()
269 plistlib.writePlist(plist, temp_info_plist.name)
271 # Info.plist will work perfectly well in any plist format, but traditionally
272 # applications use xml1 for this, so convert it to ensure that it's valid.
273 proc = subprocess.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST,
274 temp_info_plist.name])
275 proc.wait()
276 return proc.returncode
279 if __name__ == '__main__':
280 sys.exit(Main(sys.argv[1:]))