Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / build / mac / tweak_info_plist.py
1 #!/usr/bin/env python
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
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.dirname(os.path.dirname(os.path.dirname(__file__)))
33
34
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)
41
42
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)
54
55
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
63
64
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
73
74     full_version = match.group(0)
75     bundle_version = match.group(1)
76
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')
81
82     (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
83                                     '@MAJOR@.@MINOR@.@BUILD@.@PATCH@'])
84     full_version = stdout.rstrip()
85
86     (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
87                                     '@BUILD@.@PATCH@'])
88     bundle_version = stdout.rstrip()
89
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
94
95   # Add public version info so "Get Info" works.
96   plist['CFBundleShortVersionString'] = full_version
97
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
106
107   # Return with no error.
108   return True
109
110
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()
124
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.'
131
132   return True
133
134
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'
145
146
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')
157
158
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
164
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
175
176
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'
183
184   _RemoveKeys(plist, 'KSChannelID')
185   for tag_suffix in _TagSuffixes():
186     if tag_suffix:
187       plist['KSChannelID' + tag_suffix] = tag_suffix
188
189
190 def _RemoveKeystoneKeys(plist):
191   """Removes any set Keystone keys."""
192   _RemoveKeys(plist,
193       'KSVersion',
194       'KSProductID',
195       'KSUpdateURL')
196
197   tag_keys = []
198   for tag_suffix in _TagSuffixes():
199     tag_keys.append('KSChannelID' + tag_suffix)
200   _RemoveKeys(plist, *tag_keys)
201
202
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)
222
223   if len(args) > 0:
224     print >>sys.stderr, parser.get_usage()
225     return 1
226
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)
230
231   # Insert the product version.
232   if not _AddVersionKeys(plist, version=options.version):
233     return 2
234
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)
253
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)
262
263   # Adds or removes any SCM keys.
264   if not _DoSCMKeys(plist, options.add_scm_info):
265     return 3
266
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)
270
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
277
278
279 if __name__ == '__main__':
280   sys.exit(Main(sys.argv[1:]))