Ensure classes only referenced by .xib files end up in the executable

Review URL: http://codereview.chromium.org/27277

git-svn-id: http://src.chromium.org/svn/trunk/src/build@10619 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
mark@chromium.org 2009-02-27 19:28:52 +00:00
Родитель 3da8b6ab18
Коммит 776aaf065b
1 изменённых файлов: 146 добавлений и 0 удалений

146
mac/make_ib_classes.py Executable file
Просмотреть файл

@ -0,0 +1,146 @@
#!/usr/bin/python
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Usage: make_ib_classes.py output.mm input.xib [...]
#
# Generates an Objective-C++ file at output.mm referencing each class described
# in input.xib.
#
# This script is useful when building an application containing .nib or .xib
# files that reference Objective-C classes that may not be referenced by other
# code in the application. The intended use case is when the .nib and .xib
# files refer to classes that are built into a static library that gets linked
# into the main executable. If nothing in the main executable references those
# classes, the linker will not include them in its output (without -all_load or
# -ObjC). Using this script, references to such classes are created, such that
# if output.mm is compiled into the application itself, it will provide the
# class references and cause the linker to bring the required code into the
# executable.
#
# If your application is structured in the above way, and you're plagued with
# messages like:
# app[12345:101] Unknown class `MyApp' in nib file, using `NSObject' instead.
# then this script may be right for you.
import errno
import os
import os.path
import re
import subprocess
import sys
# Patterns used by ListIBClasses
# A pattern that matches the line preceding a class name.
_class_re = re.compile('<key>class</key>$')
# A pattern that matches the line with a class name; match group 1 should be
# the class name.
_class_name_re = re.compile('<string>(.*)</string>$')
# A pattern that matches class names to exclude from the output. This includes
# various Cocoa classes.
_forbidden_class_re = re.compile('^(NS|IB|FirstResponder$|WebView$)')
def ListIBClasses(ib_path, class_names=None):
"""Returns a list of class names referenced by ib_path.
ib_path is a path to an Interface Builder document. It may be a .nib or a
.xib.
This function calls "ibtool --classes" to get the list of class names.
ibtool's output is in XML plist format. Rather than doing proper structured
plist scanning, this function relies on the fact that plists are serialized
to XML in a consistent way, and simply takes the string value names of any
dictionary key named "class" as class names.
class_names may be specified as an existing list to use. This is helpful
when this function will be called several times for multiple nib/xib files.
"""
if class_names == None:
class_names = []
# When running within an Xcode build, use the tools from that Xcode
# installation.
developer_tools_dir = os.getenv('DEVELOPER_BIN_DIR', '/usr/bin')
ibtool_path = os.path.join(developer_tools_dir, 'ibtool')
ibtool_command = [ibtool_path, '--classes', ib_path]
ibtool = subprocess.Popen(ibtool_command, stdout=subprocess.PIPE)
ibtool_output = ibtool.communicate()[0]
ibtool_rv = ibtool.returncode
assert ibtool_rv == 0
# Loop through the output, looking for "class" keys. The string value on
# any line following a "class" key is taken as a class name.
is_class_name = False
for line in ibtool_output.splitlines():
if is_class_name:
class_name = _class_name_re.search(line).group(1)
is_class_name = False
if not class_name in class_names and \
not _forbidden_class_re.search(class_name):
class_names.append(class_name)
elif _class_re.search(line):
is_class_name = True
return class_names
def main(args):
assert len(args) > 2
(script_path, output_path) = args[0:2]
assert output_path.endswith('.mm')
input_paths = args[2:]
try:
os.unlink(output_path)
except OSError, e:
if e.errno != errno.ENOENT:
raise
class_names = []
# Get the class names for all desired files.
for input_path in input_paths:
ListIBClasses(input_path, class_names)
class_names.sort()
# Write the requested output file. Each class is referenced simply by
# calling its +class function. In order to do this, each class needs a
# bogus @interface to tell the compiler that it's an NSObject subclass.
# #import NSObject.h to get the definition of NSObject without bringing in
# other headers that might provide real declarations.
output_file = open(output_path, 'w')
print >>output_file, \
"""// This file was generated by %s. Do not edit.
#import <Foundation/NSObject.h>
""" % os.path.basename(script_path)
for class_name in class_names:
print >>output_file, '@interface %s : NSObject\n@end' % class_name
print >>output_file, '\nnamespace {\n\nvoid IBClasses() {'
for class_name in class_names:
print >>output_file, ' [%s class];' % class_name
print >>output_file, '}\n\n} // namespace'
output_file.close()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))