Commits

Bob Ippolito  committed 002309a

setup.py overhaul

  • Participants
  • Parent commits 5fb4105
  • Branches pyobjc-ancient

Comments (0)

Files changed (9)

File setup-lib/build_html.py

 import os
 import subprocess
 from distutils.dep_util import newer
+from distutils.cmd import Command
 
 def rest2HTML(irrelevant, dirName, names):
     while '.svn' in names:
 else:
     TOOL = None
 
-def build_html():
-    if TOOL is None:
-        print "*** Can't generate HTML, docarticle.py is missing"
-        return
-    os.path.walk('Doc', rest2HTML, ['Doc/announcement.txt'])
-    rest2HTML(None, '.', ['Install.txt', 'ReadMe.txt', 'Examples/00ReadMe.txt', 'Installer Package/10.2/ReadMe.txt', 'Installer Package/10.3/ReadMe.txt', 'ProjectBuilder Extras/Project Templates/00README.txt', 'NEWS'])
-    if os.path.exists('ProjectBuilder Extras/Project Templates/00README.html'):
-            os.rename('ProjectBuilder Extras/Project Templates/00README.html', 'Doc/ProjectBuilder-Templates.html')
+class build_html(Command):
+    def initialize_options(self):
+        self.finalized = False
+
+    def finalize_options(self):
+        self.finalized = True
+
+    def run(self):
+        if TOOL is None:
+            print "*** Can't generate HTML, docarticle.py is missing"
+            return
+        os.path.walk('Doc', rest2HTML, ['Doc/announcement.txt'])
+        rest2HTML(None, '.', ['Install.txt', 'ReadMe.txt', 'Examples/00ReadMe.txt', 'Installer Package/10.2/ReadMe.txt', 'Installer Package/10.3/ReadMe.txt', 'ProjectBuilder Extras/Project Templates/00README.txt', 'NEWS'])
+        if os.path.exists('ProjectBuilder Extras/Project Templates/00README.html'):
+                os.rename('ProjectBuilder Extras/Project Templates/00README.html', 'Doc/ProjectBuilder-Templates.html')
+
+cmdclass = dict(build_html=build_html)

File setup-lib/build_libffi.py

+import os
+import sys
+import shutil
+from subprocess import Popen, PIPE
+from distutils.cmd import Command
+from distutils.errors import *
+
+def subprocess(taskName, *commands, **kw):
+    print "Performing task: %s" % (taskName,)
+    for cmd in commands:
+        print ' '.join(cmd)
+        process = Popen(cmd, stdout=PIPE, stderr=PIPE, **kw)
+        stdout, stderr = process.communicate()
+        res = process.wait()
+        if res:
+            for err in stderr:
+                sys.stderr.write(err)
+            raise DistutilsExecError("Task %r failed [%d]" % (taskName, res))
+
+class build_libffi(Command):
+    description = "build libffi"
+    user_options = [
+        ('libffi-sources=', None,
+        "location of libffi sources (defaults to \"libffi-src\")"),
+    ]
+
+    def initialize_options(self):
+        self.finalized = False
+        if sys.platform != 'darwin' and os.path.exists('/usr/include/ffi.h'):
+            self.libffi_sources = None
+        else:
+            self.libffi_sources = 'libffi-src'
+
+    def finalize_options(self):
+        if self.libffi_sources is not None:
+            self.libffi_sources = os.path.abspath(self.libffi_sources)
+            if not os.path.isdir(self.libffi_sources):
+                print >>sys.stderr, "LIBFFI_SOURCES is not a directory: %s" % (self.libffi_sources,)
+                print >>sys.stderr, "\tSee Install.txt or Install.html for more information."
+                raise DistutilsFileError('LIBFFI_SOURCES is not a directory')
+            build_base = os.path.abspath(self.reinitialize_command('build').build_base)
+            base = self.libffi_base = os.path.join(build_base, 'libffi')
+            self.cflags = ["-isystem", os.path.join(base, "include")]
+            extra = os.path.join(base, 'lib', 'gcc', 'include', 'libffi')
+            if os.path.exists(extra):
+                self.cflags.extend(['-isystem', extra])
+            self.ldflags = ['-L' + os.path.join(base, 'lib'), '-lffi']
+        else:
+            self.cflags = []
+            self.ldflags = ['-lffi']
+        self.finalized = True
+
+    def run(self):
+        self.finalize_options()
+        if self.libffi_sources is not None:
+            inst_dir = self.libffi_base
+            build_dir = os.path.join(inst_dir, 'BLD')
+            if not os.path.exists(build_dir):
+                os.makedirs(build_dir)
+
+            if not os.path.exists(os.path.join(inst_dir, 'lib', 'libffi.a')):
+                # No pre-build version available, build it now.
+                # Do not use a relative path for the build-tree, libtool on
+                # MacOS X doesn't like that.
+
+                subprocess('Building FFI',
+                    [os.path.join(self.libffi_sources, 'configure'),
+                        '--prefix=' + inst_dir, '--disable-shared', '--enable-static'],
+                    ['make', 'install'],
+                    cwd=build_dir)
+
+                # make sure cflags is set correctly
+                self.finalize_options()
+
+cmdclass = dict(build_libffi=build_libffi)

File setup-lib/di_build_ext.py

+from distutils.command.build_ext import build_ext
+from subprocess import Popen
+import sys
+import os
+import shutil
+
+def subprocess(taskName, *commands, **kw):
+    print "Performing task: %s" % (taskName,)
+    for cmd in commands:
+        process = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, **kw)
+        res = process.wait()
+        if res:
+            raise OSError("Task %r failed [%d]" % (taskName, res))
+
+class pyobjc_build_ext (build_ext):
+    # Custom build_ext implementation. This differs in two ways from the
+    # standard one:
+    # 1. We first run the CodeGenerator script
+    # 2. We calculate a class-list after building the extensions, and if that
+    #    is different from what we had before (e.g. clean install or serious
+    #    OS upgrade) we rebuild the extensions.
+
+    def create_empty_class_list(self):
+        for fw in ('Fnd', 'App'):
+            fd = open('build/codegen/_%s_Classes.inc'%(fw,), 'w')
+            fd.write('static const char* gClassNames[] = {\n')
+            fd.write('\tNULL\n')
+            fd.write('};\n')
+            fd.close()
+
+    def create_cached_class_list(self):
+        sys.path.insert(0, self.build_lib)
+        import objc
+        retval = 0
+
+        for pfx, name in (('Fnd', 'Foundation'), ('App', 'AppKit')):
+            try:
+                m = __import__(name)
+            except ImportError:
+                continue
+            fd = open('build/codegen/_%s_Classes.inc~'%(pfx,), 'w')
+            fd.write('static const char* gClassNames[] = {\n')
+            for o in m.__dict__.values():
+                if not isinstance(o, objc.objc_class):
+                    continue
+                fd.write('\t"%s",\n'%(o.__name__))
+            fd.write('\tNULL\n')
+            fd.write('};\n')
+            fd.close()
+
+            d1 = open('build/codegen/_%s_Classes.inc~'%(pfx,), 'r').read()
+            d2 = open('build/codegen/_%s_Classes.inc'%(pfx,), 'r').read()
+
+            if d1 != d2:
+                os.rename(
+                        'build/codegen/_%s_Classes.inc~'%(pfx,),
+                        'build/codegen/_%s_Classes.inc'%(pfx,)
+                    )
+                retval = 1
+
+
+        return retval
+
+
+
+    def run(self):
+        # Save self.compiler, we need to reset it when we have to rebuild
+        # the extensions.
+        compiler_saved = self.compiler
+
+        subprocess("Generating wrappers & stubs",
+            [sys.executable, "Scripts/CodeGenerators/cocoa_generator.py"])
+
+        if not os.path.exists('build/codegen/_Fnd_Classes.inc'):
+            # Create a dummy classname list, to enable bootstrapping. Don't
+            # do this if there already is a list, everything is better than
+            # an empty list.
+            self.create_empty_class_list()
+
+        build_ext.run(self)
+
+        if self.create_cached_class_list():
+            # Note: dependencies don't work here, we depend on a file that
+            # probably didn't exist when the glob was done...
+            print "** Created a fresh class-cache, rebuilding the extensions"
+            if os.path.exists(
+                        os.path.join(self.build_temp, 'Modules', 'AppKit')):
+                shutil.rmtree(
+                        os.path.join(self.build_temp, 'Modules', 'AppKit'))
+                os.mkdir(
+                        os.path.join(self.build_temp, 'Modules', 'AppKit'))
+
+            if os.path.exists(
+                        os.path.join(self.build_temp, 'Modules', 'Foundation')):
+                shutil.rmtree(
+                        os.path.join(self.build_temp, 'Modules', 'Foundation'))
+                os.mkdir(
+                        os.path.join(self.build_temp, 'Modules', 'Foundation'))
+
+            try:
+                os.unlink(os.path.join(self.build_lib, 'AppKit', '_AppKit.so'))
+            except os.error:
+                pass
+
+            try:
+                os.unlink(os.path.join(self.build_lib, 'Foundation', '_Foundation.so'))
+            except os.error:
+                pass
+
+            self.compiler = compiler_saved
+
+            build_ext.run(self)
+
+    def build_extension(self, ext):
+        if getattr(ext, 'use_libffi', False):
+            libffi = self.reinitialize_command('build_libffi')
+            libffi.run()
+            ext.extra_compile_args[:0] = libffi.cflags
+            ext.extra_link_args[:0] = libffi.ldflags
+            ext.use_libffi = False
+        build_ext.build_extension(self, ext)
+
+
+cmdclass = dict(build_ext=pyobjc_build_ext)

File setup-lib/di_install.py

+import os
+import shutil
+from distutils.command.install import install
+
+CRUFT = """
+objc
+Foundation
+AppKit
+AddressBook
+autoGIL
+"""
+
+class pyobjc_install(install):
+    def run(self):
+        install.run(self)
+        # Hack to remove a previous version that may have been installed
+        install_dir = self.install_platlib
+        if install_dir is None:
+            return
+
+        # clean up cruft from pre 0.9
+        for name in CRUFT:
+            path = os.path.join(install_dir, name)
+            if os.path.isdir(path):
+                print "(removing old version: %s)" % (path,)
+                shutil.rmtree(path)
+            elif os.path.isfile(path):
+                print "(removing old version: %s)" % (path,)
+                os.remove(path)
+
+        install_dir = os.path.join(install_dir, 'PyObjC')
+        if not os.path.exists(install_dir):
+            return
+
+        # In version 1.2 some of the extension modules moved, 
+        # clean up the old location
+        for fn in os.listdir(install_dir):
+            fn = os.path.join(install_dir, fn)
+            if fn.endswith('.so'):
+                print "(removing old version: %s)" % (fn,)
+                os.unlink(fn)
+
+cmdclass = dict(install=pyobjc_install)

File setup-lib/di_sdist.py

 # should be removed.
 from distutils.command.sdist import sdist as sdist_base
 
-class cmd_sdist (sdist_base):
+class sdist (sdist_base):
+
+    def run(self):
+        self.run_command('build_html')
+        sdist_base.run(self)
 
     def prune_file_list(self):
         sdist_base.prune_file_list(self)
         self.filelist.exclude_pattern(r'/\.svn/.*$', is_regex=1)
         self.filelist.exclude_pattern(r'/build/.*$', is_regex=1)
         self.filelist.exclude_pattern(r'/.*~\.nib/.*$', is_regex=1)
+
+cmdclass = dict(sdist=sdist)

File setup-lib/di_test.py

         finally:
             if self.test_installed:
                 del sys.path[0]
+
+cmdclass = dict(test=cmd_test)

File setup-lib/pyobjc_commands.py

+import build_libffi
+import build_html
+import di_build_ext
+import di_sdist
+import di_test
+import di_install
+try:
+    import pyobjc_mpkg
+except ImportError:
+    pass
+
+extra_cmdclass = {}
+for v in globals().values():
+    extra_cmdclass.update(getattr(v, 'cmdclass', {}))

File setup-lib/pyobjc_mpkg.py

 import os
+if not os.path.exists('/usr/bin/sw_vers'):
+    # detect Mac OS X
+    raise ImportError('Not Mac OS X')
+
 import sys
 from distutils.version import LooseVersion
 from distutils import log
 #!/usr/bin/env python
 
-
 import sys
 import os
 import glob
 import site
 
+# We need at least Python 2.3
+MIN_PYTHON = (2, 3)
+
+if sys.version_info < MIN_PYTHON:
+    vstr = '.'.join(map(str, MIN_PYTHON))
+    raise SystemExit('PyObjC: Need at least Python ' + vstr)
+
 # Add our utility library to the path
-sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'setup-lib')))
 site.addsitedir(os.path.abspath('source-deps'))
+sys.path.insert(1,
+    os.path.abspath(os.path.join(os.path.dirname(__file__), 'setup-lib')))
 
-extra_cmdclass = {}
-if os.path.exists('/usr/bin/sw_vers'):
-    import pyobjc_mpkg
-    extra_cmdclass.update(pyobjc_mpkg.cmdclass)
+from pyobjc_commands import extra_cmdclass
 
 # Some PiPy stuff
 LONG_DESCRIPTION="""
 """
 
 from distutils.core import setup, Extension
-from distutils.command.build_ext import build_ext
 import os
 
-from di_test import cmd_test
-from di_sdist import cmd_sdist
-
-
-class pyobjc_build_ext (build_ext):
-    # Custom build_ext implementation. This differs in two ways from the
-    # standard one:
-    # 1. We first run the CodeGenerator script
-    # 2. We calculate a class-list after building the extensions, and if that
-    #    is different from what we had before (e.g. clean install or serious
-    #    OS upgrade) we rebuild the extensions.
-
-    def create_empty_class_list(self):
-        for fw in ('Fnd', 'App'):
-            fd = open('build/codegen/_%s_Classes.inc'%(fw,), 'w')
-            fd.write('static const char* gClassNames[] = {\n')
-            fd.write('\tNULL\n')
-            fd.write('};\n')
-            fd.close()
-
-    def create_cached_class_list(self):
-        sys.path.insert(0, self.build_lib)
-        import objc
-        retval = 0
-
-        for pfx, name in (('Fnd', 'Foundation'), ('App', 'AppKit')):
-            try:
-                m = __import__(name)
-            except ImportError:
-                continue
-            fd = open('build/codegen/_%s_Classes.inc~'%(pfx,), 'w')
-            fd.write('static const char* gClassNames[] = {\n')
-            for o in m.__dict__.values():
-                if not isinstance(o, objc.objc_class): continue
-                fd.write('\t"%s",\n'%(o.__name__))
-            fd.write('\tNULL\n')
-            fd.write('};\n')
-            fd.close()
-
-            d1 = open('build/codegen/_%s_Classes.inc~'%(pfx,), 'r').read()
-            d2 = open('build/codegen/_%s_Classes.inc'%(pfx,), 'r').read()
-
-            if d1 != d2:
-                os.rename(
-                        'build/codegen/_%s_Classes.inc~'%(pfx,),
-                        'build/codegen/_%s_Classes.inc'%(pfx,)
-                    )
-                retval = 1
-
-
-        return retval
-
-
-
-
-
-    def run(self):
-        task_build_libffi()
-
-        # Save self.compiler, we need to reset it when we have to rebuild
-        # the extensions.
-        compiler_saved = self.compiler
-
-        subprocess("Generating wrappers & stubs", "%s Scripts/CodeGenerators/cocoa_generator.py" % (sys.executable,), None)
-        if not os.path.exists('build/codegen/_Fnd_Classes.inc'):
-            # Create a dummy classname list, to enable bootstrapping. Don't
-            # do this if there already is a list, everything is better than
-            # an empty list.
-            self.create_empty_class_list()
-
-        build_ext.run(self)
-
-        if self.create_cached_class_list():
-            import shutil
-            # Note: dependencies don't work here, we depend on a file that
-            # probably didn't exist when the glob was done...
-            print "** Created a fresh class-cache, rebuilding the extensions"
-            if os.path.exists(
-                        os.path.join(self.build_temp, 'Modules', 'AppKit')):
-                shutil.rmtree(
-                        os.path.join(self.build_temp, 'Modules', 'AppKit'))
-                os.mkdir(
-                        os.path.join(self.build_temp, 'Modules', 'AppKit'))
-
-            if os.path.exists(
-                        os.path.join(self.build_temp, 'Modules', 'Foundation')):
-                shutil.rmtree(
-                        os.path.join(self.build_temp, 'Modules', 'Foundation'))
-                os.mkdir(
-                        os.path.join(self.build_temp, 'Modules', 'Foundation'))
-
-            try:
-                os.unlink(os.path.join(self.build_lib, 'AppKit', '_AppKit.so'))
-            except os.error:
-                pass
-
-            try:
-                os.unlink(os.path.join(self.build_lib, 'Foundation', '_Foundation.so'))
-            except os.error:
-                pass
-
-            self.compiler = compiler_saved
-
-            build_ext.run(self)
-
-LIBFFI_SOURCES='libffi-src'
-if sys.platform != 'darwin' and os.path.exists('/usr/include/ffi.h'):
-    # A system with a pre-existing libffi.
-    LIBFFI_SOURCES=None
-
-def subprocess(taskName, cmd, validRes=None):
-    print "Performing task: %s" % (taskName,)
-    fd = os.popen(cmd, 'r')
-    for ln in fd.xreadlines():
-        sys.stdout.write(ln)
-
-    res = fd.close()
-    if res is not validRes:
-        sys.stderr.write("Task '%s' failed [%d]\n"%(taskName, res))
-        sys.exit(1)
-
-# We need at least Python 2.3
-req_ver = (2, 3)
-
-if sys.version_info < req_ver:
-    sys.stderr.write('PyObjC: Need at least Python %s\n'%('.'.join(req_ver)))
-    sys.exit(1)
-
-if LIBFFI_SOURCES is not None:
-
-    def task_build_libffi():
-        if not os.path.isdir(LIBFFI_SOURCES):
-            sys.stderr.write(
-                'LIBFFI_SOURCES is not a directory: %s\n'%LIBFFI_SOURCES)
-            sys.stderr.write('\tSee Install.txt or Install.html for more information.\n')
-            sys.exit(1)
-
-        if not os.path.exists('build'):
-            os.mkdir('build')
-
-        if not os.path.exists('build/libffi'):
-            os.mkdir('build/libffi')
-
-        if not os.path.exists('build/libffi/BLD'):
-            os.mkdir('build/libffi/BLD')
-
-        if not os.path.exists('build/libffi/lib/libffi.a'):
-            # No pre-build version available, build it now.
-            # Do not use a relative path for the build-tree, libtool on
-            # MacOS X doesn't like that.
-            inst_dir = os.path.join(os.getcwd(), 'build/libffi')
-            src_path = os.path.abspath(LIBFFI_SOURCES)
-
-            if ' ' in src_path+inst_dir:
-                print >>sys.stderr, "LIBFFI can not build correctly in a path that contains spaces."
-                print >>sys.stderr, "This limitation includes the entire path (all parents, etc.)"
-                print >>sys.stderr, "Move the PyObjC and libffi source to a path without spaces and build again."
-                sys.exit(1)
-
-            inst_dir = inst_dir.replace("'", "'\"'\"'")
-            src_path = src_path.replace("'", "'\"'\"'")
-
-            subprocess('Building FFI', "cd build/libffi/BLD && '%s/configure' --prefix='%s' --disable-shared --enable-static && make install"%(src_path, inst_dir), None)
-
-    LIBFFI_BASE='build/libffi'
-    LIBFFI_CFLAGS=[
-        "-isystem", "%s/include"%LIBFFI_BASE,
-    ]
-    if os.path.exists('%s/lib/gcc/include/libffi'%LIBFFI_BASE):
-        LIBFFI_CFLAGS.extend([
-            "-isystem", "%s/lib/gcc/include/libffi"%LIBFFI_BASE,
-        ])
-    LIBFFI_LDFLAGS=[
-        '-L%s/lib'%LIBFFI_BASE, '-lffi',
-    ]
-
-else:
-    def task_build_libffi():
-        pass
-    LIBFFI_CFLAGS=[]
-    LIBFFI_LDFLAGS=['-lffi']
-
-
-sourceFiles = [
-        "Modules/objc/objc-runtime-apple.m",
-        "Modules/objc/objc-runtime-gnu.m",
-        "Modules/objc/objc_util.m",
-        "Modules/objc/objc_support.m",
-        "Modules/objc/class-builder.m",
-        "Modules/objc/class-list.m",
-        "Modules/objc/ObjCPointer.m",
-        "Modules/objc/objc-class.m",
-        "Modules/objc/unicode-object.m",
-        "Modules/objc/informal-protocol.m",
-        "Modules/objc/objc-object.m",
-        "Modules/objc/super-call.m",
-        "Modules/objc/selector.m",
-        "Modules/objc/method-accessor.m",
-        "Modules/objc/instance-var.m",
-        "Modules/objc/OC_PythonObject.m",
-        "Modules/objc/OC_PythonArray.m",
-        "Modules/objc/OC_PythonDictionary.m",
-        "Modules/objc/pyobjc-api.m",
-        "Modules/objc/alloc_hack.m",
-        "Modules/objc/toll-free-bridging.m",
-        "Modules/objc/method-signature.m",
-        "Modules/objc/module.m",
-        "Modules/objc/libffi_support.m",
-        "Modules/objc/pointer-support.m",
-        "Modules/objc/struct-wrapper.m",
-        "Modules/objc/method-imp.m",
-        "Modules/objc/bundle-variables.m",
-        "Modules/objc/function.m",
-]
+def frameworks(*args):
+    lst = []
+    for arg in args:
+        lst.extend(['-framework', arg])
+    return lst
 
 # On GNUstep we can read some configuration from the environment.
 gs_root = os.environ.get('GNUSTEP_SYSTEM_ROOT')
         print "I don't know how to build PyObjC on such a platform."
         print "Please read the ReadMe."
         print ""
-        sys.exit(1)
+        raise SystemExit("ObjC runtime not found")
 
     CFLAGS=[
         "-DMACOSX",
         #"-fast", "-fPIC",
         ]
 
-    OBJC_LDFLAGS=[
-        '-framework', 'Foundation',
-        ]
-
-    CF_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'Foundation',
-        ]
-
-    FND_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'Foundation',
-        ]
-
-    APPKIT_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'AppKit',
-        ]
-
-    ADDRESSBOOK_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'AddressBook', '-framework', 'Foundation',
-        ]
-
-    SECURITY_INTERFACE_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'SecurityInterface', '-framework', 'Foundation',
-        ]
-
-    EXCEPTION_HANDLING_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'ExceptionHandling', '-framework', 'Foundation',
-        ]
-
-    PREFPANES_LDFLAGS=[
-        '-framework', 'CoreFoundation', '-framework', 'PreferencePanes', '-framework', 'Foundation',
-        ]
+    OBJC_LDFLAGS = frameworks('Foundation')
+    CF_LDFLAGS = frameworks('CoreFoundation', 'Foundation')
+    FND_LDFLAGS = frameworks('CoreFoundation', 'Foundation')
+    APPKIT_LDFLAGS = frameworks('CoreFoundation', 'AppKit')
+    ADDRESSBOOK_LDFLAGS = frameworks('CoreFoundation', 'AddressBook', 'Foundation')
+    SECURITY_INTERFACE_LDFLAGS = frameworks('CoreFoundation', 'SecurityInterface', 'Foundation')
+    EXCEPTION_HANDLING_LDFLAGS = frameworks('CoreFoundation', 'ExceptionHandling', 'Foundation')
+    PREFPANES_LDFLAGS = frameworks('CoreFoundation', 'PreferencePanes', 'Foundation')
 
 else:
     #
         '-lobjc'
         ]
 
-    FND_LDFLAGS=OBJC_LDFLAGS
-    FNDMAP_LDFLAGS=OBJC_LDFLAGS
-    APPKIT_LDFLAGS=OBJC_LDFLAGS + ['-lgnustep-gui']
-    APPMAP_LDFLAGS=OBJC_LDFLAGS + ['-lgnustep-gui']
-    CF_LDFLAGS=[]
-    ADDRESSBOOK_LDFLAGS=OBJC_LDFLAGS + ['-lAddresses']
-    PREFPANES_LDFLAGS=[]
-    SECURITY_INTERFACE_LDFLAGS=[]
-    EXCEPTION_HANDLING_LDFLAGS=[]
+    FND_LDFLAGS = OBJC_LDFLAGS
+    FNDMAP_LDFLAGS = OBJC_LDFLAGS
+    APPKIT_LDFLAGS = OBJC_LDFLAGS + ['-lgnustep-gui']
+    APPMAP_LDFLAGS = OBJC_LDFLAGS + ['-lgnustep-gui']
+    CF_LDFLAGS = []
+    ADDRESSBOOK_LDFLAGS = OBJC_LDFLAGS + ['-lAddresses']
+    PREFPANES_LDFLAGS = []
+    SECURITY_INTERFACE_LDFLAGS = []
+    EXCEPTION_HANDLING_LDFLAGS = []
 
 CFLAGS.append('-Ibuild/codegen/')
 
+CorePackages = [ 'objc' ]
+objcExtension = Extension("objc._objc",
+    glob.glob(os.path.join('Modules', 'objc', '*.m')),
+    extra_compile_args=CFLAGS,
+    extra_link_args=OBJC_LDFLAGS,
+)
+objcExtension.use_libffi = True
 
+CoreExtensions =  [ objcExtension ]
 
+for test_source in glob.glob(os.path.join('Modules', 'objc', 'test', '*.m')):
+    name, ext = os.path.splitext(os.path.basename(test_source))
+    ext = Extension('objc.test.' + name,
+        [test_source],
+        extra_compile_args=['-IModules/objc'] + CFLAGS,
+        extra_link_args=OBJC_LDFLAGS)
+    if name == 'ctests':
+        ext.use_libffi = True
+    CoreExtensions.append(ext)
 
-CorePackages = [ 'objc' ]
-CoreExtensions =  [
-    Extension("objc._objc",
-              sourceFiles,
-              extra_compile_args=[
-              ] + LIBFFI_CFLAGS + CFLAGS,
-              extra_link_args=LIBFFI_LDFLAGS + OBJC_LDFLAGS),
-    Extension("objc.test.ctests",
-              [ 'Modules/objc/unittest.m' ],
-              extra_compile_args=[
-              ] + LIBFFI_CFLAGS + CFLAGS,
-              extra_link_args=LIBFFI_LDFLAGS + OBJC_LDFLAGS),
-    Extension("objc.test.testbndl",
-              ["Modules/objc/test/testbndl.m"],
-              extra_compile_args=["-IModules/objc" ] + CFLAGS,
-              extra_link_args=OBJC_LDFLAGS),
-    Extension("objc.test.testbndl2",
-              ["Modules/objc/test/testbndl2.m"],
-              extra_compile_args=["-IModules/objc" ] + CFLAGS,
-              extra_link_args=OBJC_LDFLAGS),
-    Extension("objc.test.testclassandinst",
-              ["Modules/objc/test/testclassandinst.m"],
-              extra_compile_args=["-IModules/objc" ] + CFLAGS,
-              extra_link_args=OBJC_LDFLAGS),
-    Extension("objc.test.testoutputinitializer",
-              ["Modules/objc/test/testoutputinitializer.m"],
-              extra_compile_args=["-IModules/objc" ] + CFLAGS,
-              extra_link_args=OBJC_LDFLAGS),
-    ]
+CoreFoundationDepends = dict()
 
-FoundationDepends = {
-    'depends': (glob.glob('build/codegen/_Fnd_*.inc')
-            + glob.glob('Modules/Foundation/*.m'))
-}
-AppKitDepends = {
-    'depends': (glob.glob('build/codegen/_App_*.inc')
-            + glob.glob('Modules/AppKit/*.m'))
-}
-AddressBookDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
-CoreFoundationDepends = {
-}
-SecurityInterfaceDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
-ExceptionHandlingDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
-PrefPanesDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
-InterfaceBuilderDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
-WebKitDepends = {
-    'depends': glob.glob('build/codegen/*.inc'),
-}
+FoundationDepends = dict(
+    depends=(
+          glob.glob('build/codegen/_Fnd_*.inc')
+        + glob.glob('Modules/Foundation/*.m')
+    ),
+)
+
+AppKitDepends = dict(
+    depends=(
+          glob.glob('build/codegen/_App_*.inc')
+        + glob.glob('Modules/AppKit/*.m')
+    ),
+)
+
+INCFILES = glob.glob('build/codegen/*.inc')
+
+AddressBookDepends = dict(depends=INCFILES)
+SecurityInterfaceDepends = dict(depends=INCFILES)
+ExceptionHandlingDepends = dict(depends=INCFILES)
+PrefPanesDepends = dict(depends=INCFILES)
+InterfaceBuilderDepends = dict(depends=INCFILES)
+WebKitDepends = dict(depends=INCFILES)
 
 FoundationPackages, FoundationExtensions = \
         IfFrameWork('Foundation.framework', [ 'Foundation' ], [
                       extra_compile_args=[
                         '-IModules/objc',
                       ] + CFLAGS,
-                      extra_link_args=[
-                        '-framework', 'InterfaceBuilder',
-                        '-framework', 'Foundation'
-                      ],
+                      extra_link_args=frameworks(
+                        'InterfaceBuilder',
+                        'Foundation'
+                      ),
                       **InterfaceBuilderDepends
                       ),
         ])
                       extra_compile_args=[
                         '-IModules/objc',
                       ] + CFLAGS,
-                      extra_link_args=[
-                        '-framework', 'WebKit',
-                        '-framework', 'Foundation'
-                      ],
+                      extra_link_args=frameworks(
+                        'WebKit',
+                        'Foundation'
+                      ),
                       **WebKitDepends
                       ),
         ], headername="WebKit.h")
 
 
 # skipping CoreFoundationPackages, it's fake!
-packages = CorePackages + AppKitPackages + FoundationPackages + AddressBookPackages + PrefPanesPackages + InterfaceBuilderPackages + ScreenSaverPackages + WebKitPackages + MessagePackages + SecurityInterfacePackages + ExceptionHandlingPackages + [ 'PyObjCTools' ]
+packages = (
+      CorePackages
+    + AppKitPackages
+    + FoundationPackages
+    + AddressBookPackages
+    + PrefPanesPackages
+    + InterfaceBuilderPackages
+    + ScreenSaverPackages
+    + WebKitPackages
+    + MessagePackages
+    + SecurityInterfacePackages
+    + ExceptionHandlingPackages
+    + [ 'PyObjCTools' ]
+)
 
 # The following line is needed to allow separate flat modules
 # to be installed from a different folder (needed for the
         package_dir[packageName] = testDir
         packages.append(packageName)
 
-dist = setup(name = "pyobjc",
-             version = package_version(),
-             description = "Python<->ObjC Interoperability Module",
-             long_description = LONG_DESCRIPTION,
-             author = "bbum, RonaldO, SteveM, LeleG, many others stretching back through the reaches of time...",
-             author_email = "pyobjc-dev@lists.sourceforge.net",
-             url = "http://pyobjc.sourceforge.net/",
-             platforms = [ 'MacOS X' ],
-             ext_modules = (
-                             CoreExtensions
-                           + AppKitExtensions
-                           + FoundationExtensions
-                           + AddressBookExtensions
-                           + PrefPanesExtensions
-                           + InterfaceBuilderExtensions
-                           + ScreenSaverExtensions
-                           + MessageExtensions
-                           + SecurityInterfaceExtensions
-                           + ExceptionHandlingExtensions
-                           + CoreFoundationExtensions
-                           + WebKitExtensions
-                           ),
-             packages = packages,
-             package_dir = package_dir,
-             scripts = [ 'Scripts/nibclassbuilder', ],
-             extra_path = "PyObjC",
-             cmdclass = dict(
-                build_ext=pyobjc_build_ext,
-                test=cmd_test,
-                sdist=cmd_sdist,
-                **extra_cmdclass
-             ),
-             classifiers = [
-                'Development Status :: 5 - Production/Stable',
-                'Environment :: Console',
-                'Environment :: MacOS X :: Cocoa',
-                'Intended Audience :: Developers',
-                'License :: OSI Approved :: MIT License',
-                'Natural Language :: English',
-                'Operating System :: MacOS :: MacOS X',
-                'Programming Language :: Python',
-                'Programming Language :: Objective C',
-                'Topic :: Software Development :: Libraries :: Python Modules',
-                'Topic :: Software Development :: User Interfaces',
-             ],
-             license = 'MIT License',
-             download_url = 'http://pyobjc.sourceforge.net/software/index.php',
+CLASSIFIERS = filter(None,
+"""
+Development Status :: 5 - Production/Stable
+Environment :: Console
+Environment :: MacOS X :: Cocoa
+Intended Audience :: Developers
+License :: OSI Approved :: MIT License
+Natural Language :: English
+Operating System :: MacOS :: MacOS X
+Programming Language :: Python
+Programming Language :: Objective C
+Topic :: Software Development :: Libraries :: Python Modules
+Topic :: Software Development :: User Interfaces
+""".splitlines())
+
+dist = setup(
+    name = "pyobjc",
+    version = package_version(),
+    description = "Python<->ObjC Interoperability Module",
+    long_description = LONG_DESCRIPTION,
+    author = "bbum, RonaldO, SteveM, LeleG, many others stretching back through the reaches of time...",
+    author_email = "pyobjc-dev@lists.sourceforge.net",
+    url = "http://pyobjc.sourceforge.net/",
+    platforms = [ 'MacOS X' ],
+    ext_modules = (
+         CoreExtensions
+       + AppKitExtensions
+       + FoundationExtensions
+       + AddressBookExtensions
+       + PrefPanesExtensions
+       + InterfaceBuilderExtensions
+       + ScreenSaverExtensions
+       + MessageExtensions
+       + SecurityInterfaceExtensions
+       + ExceptionHandlingExtensions
+       + CoreFoundationExtensions
+       + WebKitExtensions
+    ),
+    packages = packages,
+    package_dir = package_dir,
+    scripts = [ 'Scripts/nibclassbuilder', ],
+    extra_path = "PyObjC",
+    cmdclass = extra_cmdclass,
+    classifiers = CLASSIFIERS,
+    license = 'MIT License',
+    download_url = 'http://pyobjc.sourceforge.net/software/index.php',
 )
-
-if "install" in sys.argv:
-    # Hack to remove a previous version that may have been installed
-    import shutil
-    inst = dist.get_command_obj("install")
-    install_dir = inst.install_platlib
-    if install_dir is not None:
-        # clean up cruft from pre 0.9
-        for name in ("objc", "Foundation", "AppKit", "AddressBook", "autoGIL"):
-            path = os.path.join(install_dir, name)
-            if os.path.isdir(path):
-                print "(removing old version: %s)" % path
-                shutil.rmtree(path)
-            elif os.path.isfile(path):
-                print "(removing old version: %s)" % path
-                os.remove(path)
-
-        # In version 1.2 some of the extension modules moved, clean up the old
-        # location
-        install_dir = os.path.join(install_dir, 'PyObjC')
-        for fn in os.listdir(install_dir):
-            fn = os.path.join(install_dir, fn)
-            if fn.endswith('.so'):
-                os.unlink(fn)