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