Flutter Engine
The Flutter Engine
tweak_info_plist.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
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.
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
23import optparse
24import os
25from os import environ as env
26import plistlib
27import re
28import subprocess
29import sys
30import tempfile
31
32TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
33
34
35def _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)
41
42
43def _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(
49 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
50 except OSError:
51 return (None, 1)
52 (stdout, stderr) = proc.communicate()
53 return (stdout, proc.returncode)
54
55
56def _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
63
64
65def _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('Invalid version string specified: "%s"' % version,
72 file=sys.stderr)
73 return False
74
75 full_version = match.group(0)
76 bundle_version = match.group(1)
77
78 else:
79 # Pull in the Chrome version number.
80 VERSION_TOOL = os.path.join(TOP, 'build/util/version.py')
81 VERSION_FILE = os.path.join(TOP, 'chrome/VERSION')
82
83 (stdout, retval1) = _GetOutput([
84 VERSION_TOOL, '-f', VERSION_FILE, '-t',
85 '@MAJOR@.@MINOR@.@BUILD@.@PATCH@'
86 ])
87 full_version = stdout.rstrip()
88
89 (stdout, retval2) = _GetOutput(
90 [VERSION_TOOL, '-f', VERSION_FILE, '-t', '@BUILD@.@PATCH@'])
91 bundle_version = stdout.rstrip()
92
93 # If either of the two version commands finished with non-zero returncode,
94 # report the error up.
95 if retval1 or retval2:
96 return False
97
98 # Add public version info so "Get Info" works.
99 plist['CFBundleShortVersionString'] = full_version
100
101 # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1
102 # into 6, 2, 2 digits. The limitation was present in Tiger, but it could
103 # have been fixed in later OS release, but hasn't been tested (it's easy
104 # enough to find out with "lsregister -dump).
105 # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html
106 # BUILD will always be an increasing value, so BUILD_PATH gives us something
107 # unique that meetings what LS wants.
108 plist['CFBundleVersion'] = bundle_version
109
110 # Return with no error.
111 return True
112
113
114def _DoSCMKeys(plist, add_keys):
115 """Adds the SCM information, visible in about:version, to property list. If
116 |add_keys| is True, it will insert the keys, otherwise it will remove them."""
117 scm_revision = None
118 if add_keys:
119 # Pull in the Chrome revision number.
120 VERSION_TOOL = os.path.join(TOP, 'build/util/version.py')
121 LASTCHANGE_FILE = os.path.join(TOP, 'build/util/LASTCHANGE')
122 (stdout, retval) = _GetOutput(
123 [VERSION_TOOL, '-f', LASTCHANGE_FILE, '-t', '@LASTCHANGE@'])
124 if retval:
125 return False
126 scm_revision = stdout.rstrip()
127
128 # See if the operation failed.
129 _RemoveKeys(plist, 'SCMRevision')
130 if scm_revision != None:
131 plist['SCMRevision'] = scm_revision
132 elif add_keys:
133 print('Could not determine SCM revision. This may be OK.',
134 file=sys.stderr)
135
136 return True
137
138
139def _AddBreakpadKeys(plist, branding):
140 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and
141 also requires the |branding| argument."""
142 plist['BreakpadReportInterval'] = '3600' # Deliberately a string.
143 plist['BreakpadProduct'] = '%s_Mac' % branding
144 plist['BreakpadProductDisplay'] = branding
145 plist['BreakpadVersion'] = plist['CFBundleShortVersionString']
146 # These are both deliberately strings and not boolean.
147 plist['BreakpadSendAndExit'] = 'YES'
148 plist['BreakpadSkipConfirm'] = 'YES'
149
150
151def _RemoveBreakpadKeys(plist):
152 """Removes any set Breakpad keys."""
153 _RemoveKeys(plist, 'BreakpadURL', 'BreakpadReportInterval',
154 'BreakpadProduct', 'BreakpadProductDisplay', 'BreakpadVersion',
155 'BreakpadSendAndExit', 'BreakpadSkipConfirm')
156
157
158def _TagSuffixes():
159 # Keep this list sorted in the order that tag suffix components are to
160 # appear in a tag value. That is to say, it should be sorted per ASCII.
161 components = ('32bit', 'full')
162 assert tuple(sorted(components)) == components
163
164 components_len = len(components)
165 combinations = 1 << components_len
166 tag_suffixes = []
167 for combination in range(0, combinations):
168 tag_suffix = ''
169 for component_index in range(0, components_len):
170 if combination & (1 << component_index):
171 tag_suffix += '-' + components[component_index]
172 tag_suffixes.append(tag_suffix)
173 return tag_suffixes
174
175
176def _AddKeystoneKeys(plist, bundle_identifier):
177 """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
178 also requires the |bundle_identifier| argument (com.example.product)."""
179 plist['KSVersion'] = plist['CFBundleShortVersionString']
180 plist['KSProductID'] = bundle_identifier
181 plist['KSUpdateURL'] = 'https://tools.google.com/service/update2'
182
183 _RemoveKeys(plist, 'KSChannelID')
184 for tag_suffix in _TagSuffixes():
185 if tag_suffix:
186 plist['KSChannelID' + tag_suffix] = tag_suffix
187
188
189def _RemoveKeystoneKeys(plist):
190 """Removes any set Keystone keys."""
191 _RemoveKeys(plist, 'KSVersion', 'KSProductID', 'KSUpdateURL')
192
193 tag_keys = []
194 for tag_suffix in _TagSuffixes():
195 tag_keys.append('KSChannelID' + tag_suffix)
196 _RemoveKeys(plist, *tag_keys)
197
198
199def Main(argv):
200 parser = optparse.OptionParser('%prog [options]')
201 parser.add_option(
202 '--breakpad',
203 dest='use_breakpad',
204 action='store',
205 type='int',
206 default=False,
207 help='Enable Breakpad [1 or 0]')
208 parser.add_option(
209 '--breakpad_uploads',
210 dest='breakpad_uploads',
211 action='store',
212 type='int',
213 default=False,
214 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]')
215 parser.add_option(
216 '--keystone',
217 dest='use_keystone',
218 action='store',
219 type='int',
220 default=False,
221 help='Enable Keystone [1 or 0]')
222 parser.add_option(
223 '--scm',
224 dest='add_scm_info',
225 action='store',
226 type='int',
227 default=True,
228 help='Add SCM metadata [1 or 0]')
229 parser.add_option(
230 '--branding',
231 dest='branding',
232 action='store',
233 type='string',
234 default=None,
235 help='The branding of the binary')
236 parser.add_option(
237 '--bundle_id',
238 dest='bundle_identifier',
239 action='store',
240 type='string',
241 default=None,
242 help='The bundle id of the binary')
243 parser.add_option(
244 '--version',
245 dest='version',
246 action='store',
247 type='string',
248 default=None,
249 help='The version string [major.minor.build.patch]')
250 (options, args) = parser.parse_args(argv)
251
252 if len(args) > 0:
253 print(parser.get_usage(), file=sys.stderr)
254 return 1
255
256 # Read the plist into its parsed format.
257 DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'],
258 env['INFOPLIST_PATH'])
259 plist = plistlib.readPlist(DEST_INFO_PLIST)
260
261 # Insert the product version.
262 if not _AddVersionKeys(plist, version=options.version):
263 return 2
264
265 # Add Breakpad if configured to do so.
266 if options.use_breakpad:
267 if options.branding is None:
268 print('Use of Breakpad requires branding.', file=sys.stderr)
269 return 1
270 _AddBreakpadKeys(plist, options.branding)
271 if options.breakpad_uploads:
272 plist['BreakpadURL'] = 'https://clients2.google.com/cr/report'
273 else:
274 # This allows crash dumping to a file without uploading the
275 # dump, for testing purposes. Breakpad does not recognise
276 # "none" as a special value, but this does stop crash dump
277 # uploading from happening. We need to specify something
278 # because if "BreakpadURL" is not present, Breakpad will not
279 # register its crash handler and no crash dumping will occur.
280 plist['BreakpadURL'] = 'none'
281 else:
282 _RemoveBreakpadKeys(plist)
283
284 # Only add Keystone in Release builds.
285 if options.use_keystone and env['CONFIGURATION'] == 'Release':
286 if options.bundle_identifier is None:
287 print('Use of Keystone requires the bundle id.', file=sys.stderr)
288 return 1
289 _AddKeystoneKeys(plist, options.bundle_identifier)
290 else:
291 _RemoveKeystoneKeys(plist)
292
293 # Adds or removes any SCM keys.
294 if not _DoSCMKeys(plist, options.add_scm_info):
295 return 3
296
297 # Now that all keys have been mutated, rewrite the file.
298 temp_info_plist = tempfile.NamedTemporaryFile()
299 plistlib.writePlist(plist, temp_info_plist.name)
300
301 # Info.plist will work perfectly well in any plist format, but traditionally
302 # applications use xml1 for this, so convert it to ensure that it's valid.
303 proc = subprocess.Popen([
304 'plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST,
305 temp_info_plist.name
306 ])
307 proc.wait()
308 return proc.returncode
309
310
311if __name__ == '__main__':
312 sys.exit(Main(sys.argv[1:]))
def print(*args, **kwargs)
Definition: run_tests.py:49