"""
This is a script for generating a framework wrapping project.
usage: genwrapper.py [options]
options:
--version show program's version number and exit
-h, --help show this help message and exit
-f FRAMEWORK, --framework=FRAMEWORK
Generate wrapper for the named framework
-o DIR, --output=DIR Name of the output directory (default: '.')
-p NAME, --project-name=NAME
Name of the python project, defaults to 'pyobjc-
framework-FRAMEWORK'
"""
import subprocess
import optparse
import sys
import os
import textwrap
import plistlib
import objc
#
# Templates for the various files in a new framework wrapper:
#
SETUP_TMPL=textwrap.dedent("""\
'''
Wrappers for framework '%(framework)s'.
These wrappers don't include documentation, please check Apple's documention
for information on how to use this framework and PyObjC's documentation
for general tips and tricks regarding the translation between Python
and (Objective-)C frameworks
'''
import ez_setup
ez_setup.use_setuptools()
from setuptools import setup
try:
from PyObjCMetaData.commands import extra_cmdclass, extra_options
except ImportError:
extra_cmdclass = {}
extra_options = lambda name: {}
setup(
name=%(project)r,
version=%(pyobjc_version)r,
description = "Wrappers for the framework %(framework)s on Mac OS X",
long_description = __doc__,
author='Ronald Oussoren',
author_email='pyobjc-dev@lists.sourceforge.net',
url='http://pyobjc.sourceforge.net',
platforms = [ "MacOS X" ],
packages = [ "%(framework)s" ],
package_dir = {
'': 'Lib/'
},
setup_requires = [
'bdist_mpkg>=0.4.2',
],
install_requires = [
'pyobjc-core>=%(pyobjc_version)s',
%(dependencyList)s
],
dependency_links = [],
package_data = {
'': ['*.xml']
},
test_suite='%(framework)s.test',
cmdclass = extra_cmdclass,
options = extra_options('%(framework)s'),
# The package is actually zip-safe, but py2app isn't zip-aware yet.
zip_safe = False,
)
""")
README_TMPL=textwrap.dedent("""\
Wrappers for framework '%(framework)s'.
These wrappers don't include documentation, please check Apple's documention
for information on how to use this framework and PyObjC's documentation
for general tips and tricks regarding the translation between Python
and (Objective-)C frameworks
""")
LICENSE_TMPL=textwrap.dedent("""\
(This is the MIT license)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
""")
INIT_TMPL=textwrap.dedent("""\
'''
Python mapping for the %(framework)s framework.
This module does not contain docstrings for the wrapped code, check Apple's
documentation for details on how to use these functions and classes.
'''
import objc as _objc
%(frameworkDepends)s
__bundle__ = _objc.initFrameworkWrapper("%(framework)s",
frameworkIdentifier="%(bundleIdentifier)s",
frameworkPath=_objc.pathForFramework(
"%(frameworkPath)s"),
globals=globals())
""")
# Note: the test template doesn't define tests, but only contains a template
# for some tests. This is because it would be rather pointless to check if
# the generated wrapper is correct by using information generated by the
# metadata tool: if that were absolutely reliable we wouldn't need tests in
# the first place.
TEST_TMPL=textwrap.dedent("""\
'''
Some simple tests to check that the framework is properly wrapped.
'''
import objc
import unittest
import %(framework)s
class Test%(framework)s (unittest.TestCase):
def testClasses(self):
pass
# self.assert_( hasattr(%(framework)s, 'CLASSNAME') )
# self.assert_( isinstance(%(framework)s.CLASSNAME, objc.objc_class) )
# Tollfree CF-type:
# self.assert_( hasattr(%(framework)s, 'CLASSNAMERef') )
# self.assert_( %(framework)s.CLASSNAMERef is %(framework)s.CLASSNAME )
# Not-tollfree CF-type:
# self.assert_( hasattr(%(framework)s, 'CLASSNAMERef') )
# self.assert_( issubclass(%(framework)s.CLASSNAMERef, objc.lookUpClass('NSCFType')) )
# self.assert_( %(framework)s.CLASSNAMERef is not objc.lookUpClass('NSCFType') )
def testValues(self):
# Use this to test for a number of enum and #define values
pass
# Integer values:
# self.assert_( hasattr(%(framework)s, 'CONSTANT') )
# self.assert_( isinstance(%(framework)s.CONSTANT, (int, long)) )
# self.assertEquals(%(framework)s.CONSTANT, 7)
# String values:
# self.assert_( hasattr(%(framework)s, 'CONSTANT') )
# self.assert_( isinstance(%(framework)s.CONSTANT, (str, unicode)) )
# self.assertEquals(%(framework)s.CONSTANT, 'value')
def testVariables(self):
# Use this to test for global variables, (NSString*'s and the like)
pass
# self.assert_( hasattr(%(framework)s, 'CONSTANT') )
# self.assert_( isinstance(%(framework)s.CONSTANT, unicode) )
def testFunctions(self):
# Use this to test for functions
pass
# self.assert_( hasattr(%(framework)s, 'FUNCTION') )
def testOpaque(self):
# Use this to test for opaque pointers
pass
# self.assert_( hasattr(%(framework)s, 'OPAQUE') )
def testProtocols(self):
# Use this to test if informal protocols are present
pass
# self.assert_( hasattr(%(framework)s, 'protocols') )
# self.assert_( hasattr(%(framework)s.protocols, 'PROTOCOL') )
# self.assert_( isinstance(%(framework)s.protocols.PROTOCOL, objc.informal_protocol) )
def test_structs(self):
# Use this to test struct wrappers
pass
# self.assert_( hasattr(%(framework)s, 'STRUCT') )
# o = %(framework)s.STRUCT()
# self.assert_( hasattr(o, 'FIELD_NAME') )
if __name__ == "__main__":
unittest.main()
""")
def getBundleIdentifier(frameworkPath):
infoPlist = os.path.join(frameworkPath, "Resources", "Info.plist")
if not os.path.exists(infoPlist):
infoPlist = os.path.join(frameworkPath, "Resources", "Info-macos.plist")
if not os.path.exists(infoPlist):
raise RuntimeError, "Framework at %s has no Info.plist?"%(frameworkPath,)
try:
pl = plistlib.readPlist(infoPlist)
except:
# Some frameworks contain a binary plist file, which plistlib doesn't
# like at all.
r = subprocess.call(
['plutil', '-convert', 'xml1', '-o', 'Info.plist', infoPlist])
if r != 0:
raise RuntimeError, "plutil failed"
pl = plistlib.readPlist('Info.plist')
os.unlink('Info.plist')
return pl['CFBundleIdentifier']
def getFrameworkPath(framework):
# Normal frameworks:
for basedir in ('/Library/Frameworks', '/System/Library/Frameworks'):
path = os.path.join(basedir, framework + '.framework')
if os.path.exists(path):
return path
# Frameworks in an umbrella framework:
subpath = 'Frameworks/%s.framework'%(framework,)
for basedir in '/Library/Frameworks', '/System/Library/Frameworks':
for dn in os.listdir(basedir):
possibleMatch = os.path.join(basedir, dn, subpath)
if os.path.exists(possibleMatch):
return possibleMatch
raise RuntimeError, "Cannot find framework: %s"%(framework)
def main():
parser = optparse.OptionParser(version="%prog 0.5")
parser.add_option("-f", "--framework", dest="framework",
metavar="FRAMEWORK",
help="Generate wrapper for the named framework")
parser.add_option("-o", "--output", dest="output", metavar="DIR",
help="Name of the output directory (default: '.')", default=".")
parser.add_option("-p", "--project-name", dest="project", metavar="NAME",
help="Name of the python project, defaults to 'pyobjc-framework-FRAMEWORK'")
options, args = parser.parse_args()
if args:
parser.error("incorrect number of arguments")
if options.framework is None:
parser.error("you must specify a framework to wrap")
if options.project is None:
options.project = "pyobjc-framework-%s"%(options.framework,)
try:
frameworkPath = getFrameworkPath(options.framework)
except RuntimeError, msg:
parser.error(msg)
bundleIdentifier = getBundleIdentifier(frameworkPath)
if not os.path.exists(options.output):
os.makedirs(options.output)
if os.path.exists(os.path.join(options.output, options.project)):
parser.error("framework wrapper already exists")
framework = options.framework
project = options.project
pyobjc_version = objc.__version__
basedir = os.path.join(options.output, options.project)
os.mkdir(basedir)
os.mkdir(os.path.join(basedir, "Lib"))
os.mkdir(os.path.join(basedir, "Lib", options.framework))
os.mkdir(os.path.join(basedir, "Lib", options.framework, "test"))
os.mkdir(os.path.join(basedir, "Examples"))
os.mkdir(os.path.join(basedir, "Exceptions"))
fp = open(os.path.join(basedir, 'README.txt'), 'w')
fp.write(README_TMPL % locals())
fp.close()
fp = open(os.path.join(basedir, 'LICENSE.txt'), 'w')
fp.write(LICENSE_TMPL % locals())
fp.close()
import __main__
bindir = os.path.dirname(os.path.abspath(__main__.__file__))
proc = subprocess.Popen([
os.path.join(bindir, 'pyobjc-metadata-gen'),
'-f', framework,
'-F', 'final-md',
'-o', os.path.join(basedir, 'Lib', framework, 'PyObjC.bridgesupport'),
'-d', '-'
], stdout=subprocess.PIPE)
lines = proc.communicate()[0]
result = proc.returncode
if result != 0:
print >>sys.stderr, "Generating metadata failed"
sys.exit(1)
result = subprocess.call([
os.path.join(bindir, 'pyobjc-metadata-gen'),
'-f', framework,
'-F', 'excp-templ-md',
'-o', os.path.join(basedir, 'Exceptions', framework + '.bridgesupport'),
])
if result != 0:
print >>sys.stderr, "Generating exceptions template failed"
sys.exit(1)
importList = []
dependencyList = []
for ln in lines.splitlines():
importList.append('from %s import *'%(ln.strip(),))
dependencyList.append('\'pyobjc-framework-%s>=%s\','%(ln.strip(), pyobjc_version))
dependencyList = '\n '.join(dependencyList)
fp = open(os.path.join(basedir, 'setup.py'), 'w')
fp.write(SETUP_TMPL % locals())
fp.close()
frameworkDepends = '\n'.join(importList)
fp = open(
os.path.join(basedir, "Lib", framework, '__init__.py'), 'w')
fp.write(INIT_TMPL % locals())
fp.close()
fp = open(
os.path.join(basedir, "Lib", framework, 'test', '__init__.py'), 'w')
fp.close()
fp = open(
os.path.join(basedir, "Lib", framework, 'test', 'test_%s.py'%(framework.lower(),)), 'w')
fp.write(TEST_TMPL % locals())
fp.close()
result = subprocess.call([
'svn', 'export', 'svn://svn.eby-sarna.com/svnroot/ez_setup'],
cwd=basedir)
if result != 0:
print >>sys.stderr, "Couldn't extract the required 'ez_setup' files"
sys.exit(1)
print textwrap.dedent('''\
Generated framework wrappers for %(framework)s.
Please check and adjust setup.py, License.txt and ReadMe.txt to tast.
Also check and update the metadata exceptions (and if you make changes
there update the actual metadata) before releasing this package.
There is a skeleton for unittests in Lib/%(framework)s/test, please
add some real tests to that file.
You'd do well to add some examples as well :-)
Note: if you use subversion: remove ez_setup.py and add the following
svn:externals defintion::
ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
''') % locals()
sys.exit(0)
if __name__ == "__main__":
main()
|