• Facebook
  • Twitter
  • Reddit
  • StumbleUpon
  • Digg
  • email

# Do this before ez_setup, since it plays with sys.path
import sys
script_dir = sys.path[0]
 
import ez_setup
ez_setup.use_setuptools()
 
from setuptools import setup, Command, Extension, Feature
from setuptools import Distribution as _Distribution
from distutils.errors import DistutilsOptionError
from distutils.command.build_ext import build_ext
 
import re
import os
 
class ITAPSCommand(Command):
    @staticmethod
    def _get_defs(itaps_defs, iface):
        from parse_makefile import parse_makefile
 
        makefile = parse_makefile(itaps_defs)
        prefix = iface.upper()
 
        include_dirs = []
        library_dirs = []
        libraries = []
 
        inc_match = re.compile(r'(?:(?<=\s)|^)-(I)\s*(\S*)')
        for m in inc_match.finditer( makefile[prefix+'_INCLUDES'] ):
            if m.group(2) not in include_dirs:
                include_dirs.append( m.group(2) )
 
        lib_match = re.compile(r'(?:(?<=\s)|^)-([lL])\s*(\S*)')
        for m in lib_match.finditer( makefile[prefix+'_LIBS'] ):
            if m.group(1) == 'L' and m.group(2) not in library_dirs:
                library_dirs.append( m.group(2) )
            elif m.group(1) == 'l' and m.group(2) not in libraries:
                libraries.append( m.group(2) )
 
        return (include_dirs, library_dirs, libraries)
 
    @staticmethod
    def _find_defs(iface, path=None):
        if path:
            if os.path.isfile(path):
                return os.path.normpath(path)
            name = os.path.join(path, iface+'-Defs.inc')
            if os.path.isfile(name):
                return os.path.normpath(name)
            name = os.path.join(path, 'lib', iface+'-Defs.inc')
            if os.path.isfile(name):
                return os.path.normpath(name)
            raise Exception("Unable to locate %s-Defs.inc" % iface)
        elif iface.upper()+'_DEFS' in os.environ:
            name = os.getenv(iface.upper()+"_DEFS")
            if os.path.isfile(name):
                return os.path.normpath(name)
        else:
            # try a bunch of places where iXxx-Defs.inc might be located
            search = [os.path.join(i, '..', 'lib') for i in
                      os.getenv('PATH',  "").split(':') +
                      os.getenv('CPATH', "").split(':')] + \
                     os.getenv('LD_LIBRARY_PATH', "").split(':') + \
                     ['/usr/local/lib', '/usr/lib']
            for i in search:
                name = os.path.join(i, iface+'-Defs.inc')
                if os.path.isfile(name):
                    return os.path.normpath(name)
 
    def initialize_options(self):
        self.defs = {}
 
    def finalize_options(self):
        for iface in ('iMesh', 'iGeom', 'iRel'):
            if not getattr(self.distribution, "with_"+iface):
                continue
 
            if hasattr(self.distribution, iface+"_path"):
                path = getattr(self.distribution, iface+"_path")
                self.defs[iface] = self._get_defs(self._find_defs(iface, path),
                                                  iface)
            else:
                defs_file = self._find_defs(iface)
                if defs_file:
                    self.defs[iface] = self._get_defs(defs_file, iface)
 
 
class BuildExtCommand(build_ext, ITAPSCommand):
    def _check_for_iface(self, iface, ext):
        from distutils.ccompiler import new_compiler, CompileError
        from shutil import rmtree
        import tempfile
 
        tmpdir = tempfile.mkdtemp()
        old = os.getcwd()
 
        os.chdir(tmpdir)
 
        # Try to include the relevant iXxx.h header, and disable the module
        # if it can't be found
        f = open('%s.c' % iface, 'w')
        f.write("#include <%s.h>\n" % iface)
        f.close()
        try:
            print "Checking for usability of %s..." % iface
            include_dirs = self.include_dirs + ext.include_dirs
            new_compiler().compile([f.name], include_dirs=include_dirs)
            success = True
        except CompileError:
            print >>sys.stderr, ("Warning: unable to find %s.h. " +
                                 "itaps.%s module disabled") % (iface, iface)
            success = False
 
        os.chdir(old)
        rmtree(tmpdir)
        return success
 
    def initialize_options(self):
        build_ext.initialize_options(self)
        ITAPSCommand.initialize_options(self)
 
    def finalize_options(self):
        build_ext.finalize_options(self)
        ITAPSCommand.finalize_options(self)
 
        # automatically include NumPy header dir if possible
        try:
            import numpy.core as nc
            self.include_dirs.append(os.path.join(nc.__path__[0], 'include'))
        except:
            pass
 
        # add in libs/dirs from iXxx-Defs.inc
        for m in self.extensions:
            name = m.name.split(".")[1]
            if name == "iBase": # TODO: hack
                for defs in self.defs.itervalues():
                    m.include_dirs.extend(defs[0])
            elif name == "iRel": # TODO: hack
                for defs in self.defs.itervalues():
                    m.include_dirs.extend(defs[0])
                    m.library_dirs.extend(defs[1])
                    m.libraries.extend(defs[2])
            else:
                defs = self.defs.get(name)
                if defs:
                    m.include_dirs.extend(defs[0])
                    m.library_dirs.extend(defs[1])
                    m.libraries.extend(defs[2])
 
    def build_extension(self, ext):
        name = ext.name.split('.')[1]
        if name == 'iBase' or self._check_for_iface(name, ext):
            build_ext.build_extension(self, ext)
 
class TestCommand(Command):
    description = 'run unit tests'
    user_options = [
        ('test-suites=','S',"Test suite to run (e.g. 'test.imesh')"),
        ]
 
    @staticmethod
    def meets_requirements(module):
        __import__(module)
        m = sys.modules[module]
        if not hasattr(m, '__requires__'):
            return True
        try:
            for i in m.__requires__:
                __import__(i)
            return True
        except:
            return False
 
    @staticmethod
    def import_recursive(module, callback=None):
        __import__(module)
        m = sys.modules[module]
        if callback: callback(m)
 
        if hasattr(m, '__all__'):
            for i in m.__all__:
                TestCommand.import_recursive("%s.%s" % (module, i), callback)
 
    def initialize_options(self):
        self.test_suites = None
 
    def finalize_options(self):
        if self.test_suites is None:
            # Yes, the sorting is important. For some reason it fails if we
            # test-import iRel before iMesh. This is probably a bad thing, but
            # who knows where the error lies?? (Probably iRel.)
            self.test_suites = [i for i in sorted(self.distribution.test_suites)
                                if self.meets_requirements(i)]
        else:
            self.test_suites = self.test_suites.split(",")
 
    def run(self):
        import unittest
        verbosity = 2 if self.verbose else 1
 
        runner = unittest.TextTestRunner(verbosity=verbosity)
        suite = unittest.TestSuite()
        def add_test(m):
            suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(m))
        for i in self.test_suites:
            self.import_recursive(i, add_test)
 
        res = runner.run(suite)
        if res.errors or res.failures:
            exit(1)
 
class DocCommand(Command):
    description = 'build documentation'
    user_options = [
        ('builder=', 'b', 'documentation builder'),
        ('target=',  't', 'target directory'),
        ]
 
    def initialize_options(self):
        self.builder = 'html'
        self.target = None
 
    def finalize_options(self):
        if self.target:
            self.target = os.path.abspath(self.target)
        else:
            self.target = '_build/' + self.builder
 
    def run(self):
        root = 'doc'
        old = os.getcwd()
 
        os.chdir(root)
        os.system('sphinx-build -b "%s" -d _build/doctrees . "%s"' %
                  (self.builder, self.target))
        os.chdir(old)        
 
class PerfCommand(Command):
    description = 'build/execute performance tests'
    user_options = [
        ('file=',  'F', 'file or directory containing test file(s)'),
        ('count=', 'c', 'number of times to test'),
        ]
 
    def initialize_options(self):
        self.file = None
        self.count = 20
 
    def finalize_options(self):
        if not self.file:
            raise DistutilsOptionError('"file" must be specified')
 
        try:
            self.count = int(self.count)
        except ValueError:
            raise DistutilsOptionError('"count" option must be an integer')
 
    def run(self):
        for cmd_name in self.get_sub_commands():
            self.run_command(cmd_name)
 
    sub_commands = [
        ('perf_build', None),
        ('perf_run',   None),
        ]
 
class PerfBuildCommand(ITAPSCommand):
    description = 'build performance tests'
 
    sep_by = " (separated by '%s')" % os.pathsep
    user_options = [
        ('include-dirs=', 'I',
         'list of directories to search for header files' + sep_by),
        ('libraries=', 'l',
         'external C libraries to link with'),
        ('library-dirs=', 'L',
         'directories to search for external C libraries' + sep_by),
        ]
 
    def initialize_options(self):
        ITAPSCommand.initialize_options(self)
 
    def finalize_options(self):
        ITAPSCommand.finalize_options(self)
 
    def run(self):
        root = 'perf'
        old = os.getcwd()
        os.chdir(root)
 
        from distutils.ccompiler import new_compiler
        self.compiler = new_compiler()
 
        objs = self.compiler.compile(
            ['perf.c'], include_dirs=self.defs['iMesh'][0])
        self.compiler.link_executable(
            objs, 'perf', library_dirs=self.defs['iMesh'][1],
            libraries=self.defs['iMesh'][2])
 
        os.chdir(old)
 
 
class PerfRunCommand(Command):
    description = 'execute performance tests'
    user_options = [
        ('file=',  'F', 'file or directory containing test file(s)'),
        ('count=', 'c', 'number of times to test'),
        ]
 
    def initialize_options(self):
        self.file = None
        self.count = 20
 
    def finalize_options(self):
        self.set_undefined_options('perf',
                                   ('file', 'file'),
                                   ('count', 'count') )
 
        if not self.file:
            raise DistutilsOptionError('"file" must be specified')
 
        try:
            self.count = int(self.count)
        except ValueError:
            raise DistutilsOptionError('"count" option must be an integer')
 
    def run(self):
        root = 'perf'
        old = os.getcwd()
        os.chdir(root)
        os.system('python perf.py -c%d "%s"' % (self.count, self.file))
        os.chdir(old)
 
 
iBase = Extension('itaps.iBase',
                  depends = ['common.h', 'iBase_Python.h',
                             'iBase_handleTempl.def','iBase_array.inl'],
                  sources = ['iBase.c'],
                  )
 
iMesh = Feature('iMesh interface',
                standard = True,
                ext_modules = [Extension(
                            'itaps.iMesh',
                            depends = ['common.h', 'errors.h', 'helpers.h', 
                                       'iMesh_Python.h', 'iBase_Python.h',
                                       'iMesh_entSet.inl', 'iMesh_iter.inl',
                                       'iMesh_tag.inl', 'iMesh_doc.h',
                                       'numpy_extensions.h'],
                            sources = ['iMesh.c']
                            )],
                test_suites=['test.imesh'],
                )
 
iGeom = Feature('iGeom interface',
                standard = True,
                ext_modules = [Extension(
                            'itaps.iGeom',
                            depends = ['common.h', 'errors.h', 'helpers.h',
                                       'iGeom_Python.h', 'iBase_Python.h',
                                       'iGeom_entSet.inl', 'iGeom_iter.inl',
                                       'iGeom_tag.inl', 'iGeom_doc.h',
                                       'numpy_extensions.h'],
                            sources = ['iGeom.c']
                          )],
                test_suites=['test.igeom'],
                )
 
iRel  = Feature('iRel interface',
                standard = True,
                require_features = ['iMesh', 'iGeom'],
                ext_modules = [Extension(
                            'itaps.iRel',
                            depends = ['common.h', 'errors.h', 'helpers.h',
                                       'iRel_Python.h', 'iBase_Python.h',
                                       'iRel_relation.inl',
                                       'numpy_extensions.h'],
                            sources = ['iRel.c']
                          )],
                test_suites=['test.irel'],
                )
 
class Distribution(_Distribution):
    global_options = _Distribution.global_options + [
        ('iMesh-path=', None, 'root directory for iMesh interface'),
        ('iGeom-path=', None, 'root directory for iGeom interface'),
        ('iRel-path=',  None, 'root directory for iRel interface'),
        ]
    test_suites = []
 
setup(name = 'PyTAPS',
      version = '1.0rc2',
      description = 'Python bindings for ITAPS interfaces',
      author = 'Jim Porter',
      author_email = 'jvporter@wisc.edu',
      url = 'http://packages.python.org/PyTAPS/',
 
      long_description = open(os.path.join(script_dir, "README")).read(),
 
      classifiers = [ 'Development Status :: 4 - Beta',
                      'Environment :: Console',
                      'Intended Audience :: Developers',
                      'Intended Audience :: Science/Research',
                      'License :: OSI Approved :: GNU Library or Lesser ' +
                          'General Public License (LGPL)',
                      'Topic :: Scientific/Engineering',
                      ],
 
      requires = ['numpy(>=1.3.0)'],
      setup_requires = ['numpy>=1.3.0'],
      install_requires = ['numpy>=1.3.0'],
      provides = ['itaps'],
 
      package_dir = {'itaps': 'pkg'},
      packages = ['itaps'],
 
      ext_modules = [iBase],
      py_modules = ['itaps.helpers'],
      features = { 'iMesh' : iMesh,
                   'iGeom' : iGeom,
                   'iRel'  : iRel,
                   },
      headers = ['iBase_Python.h', 'iMesh_Python.h', 'iGeom_Python.h',
                 'iRel_Python.h', 'helpers.h'],
      scripts = ['tools/tagviewer'],
 
      distclass = Distribution,
      cmdclass = { 'build_ext'  : BuildExtCommand,
                   'test'       : TestCommand,
                   'doc'        : DocCommand,
                   'perf'       : PerfCommand,
                   'perf_build' : PerfBuildCommand,
                   'perf_run'   : PerfRunCommand,
                   },
      test_suites = ['test.ibase', 'test.helpers'],
      zip_safe = False,
      )