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
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.
25 from os
import environ
as env
32 TOP
= os
.path
.join(env
['SRCROOT'], '..')
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."""
48 proc
= subprocess
.Popen(args
, stdout
=subprocess
.PIPE
,
49 stderr
=subprocess
.PIPE
)
52 (stdout
, stderr
) = proc
.communicate()
53 return (stdout
, proc
.returncode
)
56 def _RemoveKeys(plist
, *keys
):
57 """Removes a varargs of keys from the plist."""
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."""
69 match
= re
.match('\d+\.\d+\.(\d+\.\d+)$', version
)
71 print >>sys
.stderr
, 'Invalid version string specified: "%s"' % version
74 full_version
= match
.group(0)
75 bundle_version
= match
.group(1)
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',
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
:
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.
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."""
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',
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
130 print >>sys
.stderr
, 'Could not determine SCM revision. This may be OK.'
135 def _DoPDFKeys(plist
, add_keys
):
136 """Adds PDF support to the document types list. If add_keys is True, it will
137 add the type information dictionary. If it is False, it will remove it if
140 PDF_FILE_EXTENSION
= 'pdf'
142 def __AddPDFKeys(sub_plist
):
143 """Writes the keys into a sub-dictionary of the plist."""
144 sub_plist
['CFBundleTypeExtensions'] = [PDF_FILE_EXTENSION
]
145 sub_plist
['CFBundleTypeIconFile'] = 'document.icns'
146 sub_plist
['CFBundleTypeMIMETypes'] = 'application/pdf'
147 sub_plist
['CFBundleTypeName'] = 'PDF Document'
148 sub_plist
['CFBundleTypeRole'] = 'Viewer'
150 DOCUMENT_TYPES_KEY
= 'CFBundleDocumentTypes'
152 # First get the list of document types, creating it if necessary.
154 extensions
= plist
[DOCUMENT_TYPES_KEY
]
156 # If this plist doesn't have a type dictionary, create one if set to add the
157 # keys. If not, bail.
160 extensions
= plist
[DOCUMENT_TYPES_KEY
] = []
162 # Loop over each entry in the list, looking for one that handles PDF types.
163 for i
, ext
in enumerate(extensions
):
164 # If an entry for .pdf files is found...
165 if 'CFBundleTypeExtensions' not in ext
:
167 if PDF_FILE_EXTENSION
in ext
['CFBundleTypeExtensions']:
169 # Overwrite the existing keys with new ones.
172 # Otherwise, delete the entry entirely.
176 # No PDF entry exists. If one needs to be added, do so now.
179 __AddPDFKeys(pdf_entry
)
180 extensions
.append(pdf_entry
)
183 def _AddBreakpadKeys(plist
, branding
):
184 """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and
185 also requires the |branding| argument."""
186 plist
['BreakpadReportInterval'] = '3600' # Deliberately a string.
187 plist
['BreakpadProduct'] = '%s_Mac' % branding
188 plist
['BreakpadProductDisplay'] = branding
189 plist
['BreakpadVersion'] = plist
['CFBundleShortVersionString']
190 # These are both deliberately strings and not boolean.
191 plist
['BreakpadSendAndExit'] = 'YES'
192 plist
['BreakpadSkipConfirm'] = 'YES'
195 def _RemoveBreakpadKeys(plist
):
196 """Removes any set Breakpad keys."""
199 'BreakpadReportInterval',
201 'BreakpadProductDisplay',
203 'BreakpadSendAndExit',
204 'BreakpadSkipConfirm')
208 # Keep this list sorted in the order that tag suffix components are to
209 # appear in a tag value. That is to say, it should be sorted per ASCII.
210 components
= ('32bit', 'full')
211 assert tuple(sorted(components
)) == components
213 components_len
= len(components
)
214 combinations
= 1 << components_len
216 for combination
in xrange(0, combinations
):
218 for component_index
in xrange(0, components_len
):
219 if combination
& (1 << component_index
):
220 tag_suffix
+= '-' + components
[component_index
]
221 tag_suffixes
.append(tag_suffix
)
225 def _AddKeystoneKeys(plist
, bundle_identifier
):
226 """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
227 also requires the |bundle_identifier| argument (com.example.product)."""
228 plist
['KSVersion'] = plist
['CFBundleShortVersionString']
229 plist
['KSProductID'] = bundle_identifier
230 plist
['KSUpdateURL'] = 'https://tools.google.com/service/update2'
232 _RemoveKeys(plist
, 'KSChannelID')
233 for tag_suffix
in _TagSuffixes():
235 plist
['KSChannelID' + tag_suffix
] = tag_suffix
238 def _RemoveKeystoneKeys(plist
):
239 """Removes any set Keystone keys."""
246 for tag_suffix
in _TagSuffixes():
247 tag_keys
.append('KSChannelID' + tag_suffix
)
248 _RemoveKeys(plist
, *tag_keys
)
252 parser
= optparse
.OptionParser('%prog [options]')
253 parser
.add_option('--breakpad', dest
='use_breakpad', action
='store',
254 type='int', default
=False, help='Enable Breakpad [1 or 0]')
255 parser
.add_option('--breakpad_uploads', dest
='breakpad_uploads',
256 action
='store', type='int', default
=False,
257 help='Enable Breakpad\'s uploading of crash dumps [1 or 0]')
258 parser
.add_option('--keystone', dest
='use_keystone', action
='store',
259 type='int', default
=False, help='Enable Keystone [1 or 0]')
260 parser
.add_option('--scm', dest
='add_scm_info', action
='store', type='int',
261 default
=True, help='Add SCM metadata [1 or 0]')
262 parser
.add_option('--pdf', dest
='add_pdf_support', action
='store', type='int',
263 default
=False, help='Add PDF file handler support [1 or 0]')
264 parser
.add_option('--branding', dest
='branding', action
='store',
265 type='string', default
=None, help='The branding of the binary')
266 parser
.add_option('--bundle_id', dest
='bundle_identifier',
267 action
='store', type='string', default
=None,
268 help='The bundle id of the binary')
269 parser
.add_option('--version', dest
='version', action
='store', type='string',
270 default
=None, help='The version string [major.minor.build.patch]')
271 (options
, args
) = parser
.parse_args(argv
)
274 print >>sys
.stderr
, parser
.get_usage()
277 # Read the plist into its parsed format.
278 DEST_INFO_PLIST
= os
.path
.join(env
['TARGET_BUILD_DIR'], env
['INFOPLIST_PATH'])
279 plist
= plistlib
.readPlist(DEST_INFO_PLIST
)
281 # Insert the product version.
282 if not _AddVersionKeys(plist
, version
=options
.version
):
285 # Add Breakpad if configured to do so.
286 if options
.use_breakpad
:
287 if options
.branding
is None:
288 print >>sys
.stderr
, 'Use of Breakpad requires branding.'
290 _AddBreakpadKeys(plist
, options
.branding
)
291 if options
.breakpad_uploads
:
292 plist
['BreakpadURL'] = 'https://clients2.google.com/cr/report'
294 # This allows crash dumping to a file without uploading the
295 # dump, for testing purposes. Breakpad does not recognise
296 # "none" as a special value, but this does stop crash dump
297 # uploading from happening. We need to specify something
298 # because if "BreakpadURL" is not present, Breakpad will not
299 # register its crash handler and no crash dumping will occur.
300 plist
['BreakpadURL'] = 'none'
302 _RemoveBreakpadKeys(plist
)
304 # Only add Keystone in Release builds.
305 if options
.use_keystone
and env
['CONFIGURATION'] == 'Release':
306 if options
.bundle_identifier
is None:
307 print >>sys
.stderr
, 'Use of Keystone requires the bundle id.'
309 _AddKeystoneKeys(plist
, options
.bundle_identifier
)
311 _RemoveKeystoneKeys(plist
)
313 # Adds or removes any SCM keys.
314 if not _DoSCMKeys(plist
, options
.add_scm_info
):
317 # Adds or removes the PDF file handler entry.
318 _DoPDFKeys(plist
, options
.add_pdf_support
)
320 # Now that all keys have been mutated, rewrite the file.
321 temp_info_plist
= tempfile
.NamedTemporaryFile()
322 plistlib
.writePlist(plist
, temp_info_plist
.name
)
324 # Info.plist will work perfectly well in any plist format, but traditionally
325 # applications use xml1 for this, so convert it to ensure that it's valid.
326 proc
= subprocess
.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST
,
327 temp_info_plist
.name
])
329 return proc
.returncode
332 if __name__
== '__main__':
333 sys
.exit(Main(sys
.argv
[1:]))