xref: /petsc/src/binding/petsc4py/setup.py (revision 661095bbfddda9a1493a32ea0d2305a96eb189ff)
1#!/usr/bin/env python
2# Author:  Lisandro Dalcin
3# Contact: dalcinl@gmail.com
4
5"""
6PETSc for Python
7"""
8
9import sys
10import os
11import re
12
13try:
14    import setuptools
15except ImportError:
16    setuptools = None
17
18pyver = sys.version_info[:2]
19if pyver < (2, 6) or (3, 0) <= pyver < (3, 2):
20    raise RuntimeError("Python version 2.6, 2.7 or >= 3.2 required")
21if pyver == (2, 6) or pyver == (3, 2):
22    sys.stderr.write(
23        "WARNING: Python %d.%d is not supported.\n" % pyver)
24
25# --------------------------------------------------------------------
26# Metadata
27# --------------------------------------------------------------------
28
29topdir = os.path.abspath(os.path.dirname(__file__))
30sys.path.insert(0, topdir)
31
32from conf.metadata import metadata
33
34def name():
35    return 'petsc4py'
36
37def version():
38    with open(os.path.join(topdir, 'src', '__init__.py')) as f:
39        m = re.search(r"__version__\s*=\s*'(.*)'", f.read())
40        return m.groups()[0]
41
42def description():
43    with open(os.path.join(topdir, 'DESCRIPTION.rst')) as f:
44        return f.read()
45
46name     = name()
47version  = version()
48
49url      = 'https://gitlab.com/petsc/petsc'
50pypiroot = 'https://pypi.io/packages/source/%s/%s/' % (name[0], name)
51download = pypiroot + '%(name)s-%(version)s.tar.gz' % vars()
52
53devstat  = ['Development Status :: 5 - Production/Stable']
54keywords = ['PETSc', 'MPI']
55
56metadata['name'] = name
57metadata['version'] = version
58metadata['description'] = __doc__.strip()
59metadata['long_description'] = description()
60metadata['keywords'] += keywords
61metadata['classifiers'] += devstat
62metadata['url'] = url
63metadata['download_url'] = download
64
65metadata['provides'] = ['petsc4py']
66metadata['requires'] = ['numpy']
67
68# --------------------------------------------------------------------
69# Extension modules
70# --------------------------------------------------------------------
71
72def get_ext_modules(Extension):
73    from os   import walk, path
74    from glob import glob
75    depends = []
76    for pth, dirs, files in walk('src'):
77        depends += glob(path.join(pth, '*.h'))
78        depends += glob(path.join(pth, '*.c'))
79    try:
80        import numpy
81        numpy_includes = [numpy.get_include()]
82    except ImportError:
83        numpy_includes = []
84    return [Extension('petsc4py.lib.PETSc',
85                      sources=['src/PETSc.c',
86                               'src/libpetsc4py.c',
87                               ],
88                      include_dirs=['src/include',
89                                    ] + numpy_includes,
90                      depends=depends)]
91
92# --------------------------------------------------------------------
93# Setup
94# --------------------------------------------------------------------
95
96from conf.petscconf import setup, Extension
97from conf.petscconf import config, build, build_src, build_ext, install
98from conf.petscconf import clean, test, sdist
99
100CYTHON = '0.24'
101
102def run_setup():
103    setup_args = metadata.copy()
104    if setuptools:
105        setup_args['zip_safe'] = False
106        setup_args['install_requires'] = ['numpy']
107        PETSC_DIR = os.environ.get('PETSC_DIR')
108        if not (PETSC_DIR and os.path.isdir(PETSC_DIR)):
109            vstr = setup_args['version'].split('.')[:2]
110            x, y = int(vstr[0]), int(vstr[1])
111            PETSC = ">=%s.%s,<%s.%s" % (x, y, x, y+1)
112            setup_args['install_requires'] += ['petsc'+PETSC]
113    if setuptools:
114        src = os.path.join('src', 'petsc4py.PETSc.c')
115        has_src = os.path.exists(os.path.join(topdir, src))
116        has_git = os.path.isdir(os.path.join(topdir, '.git'))
117        has_hg  = os.path.isdir(os.path.join(topdir, '.hg'))
118        suffix = os.path.join('src', 'binding', 'petsc4py')
119        in_petsc = topdir.endswith(os.path.sep + suffix)
120        if not has_src or has_git or has_hg or in_petsc:
121            setup_args['setup_requires'] = ['Cython>='+CYTHON]
122    #
123    setup(packages     = ['petsc4py',
124                          'petsc4py.lib',],
125          package_dir  = {'petsc4py'     : 'src',
126                          'petsc4py.lib' : 'src/lib'},
127          package_data = {'petsc4py'     : ['include/petsc4py/*.h',
128                                            'include/petsc4py/*.i',
129                                            'include/petsc4py/*.pxd',
130                                            'include/petsc4py/*.pxi',
131                                            'include/petsc4py/*.pyx',
132                                            'PETSc.pxd',],
133                          'petsc4py.lib' : ['petsc.cfg'],},
134          ext_modules  = get_ext_modules(Extension),
135          cmdclass     = {'config'     : config,
136                          'build'      : build,
137                          'build_src'  : build_src,
138                          'build_ext'  : build_ext,
139                          'install'    : install,
140                          'clean'      : clean,
141                          'test'       : test,
142                          'sdist'      : sdist,
143                          },
144          **setup_args)
145
146def chk_cython(VERSION):
147    from distutils import log
148    from distutils.version import LooseVersion
149    from distutils.version import StrictVersion
150    warn = lambda msg='': sys.stderr.write(msg+'\n')
151    #
152    try:
153        import Cython
154    except ImportError:
155        warn("*"*80)
156        warn()
157        warn(" You need to generate C source files with Cython!!")
158        warn(" Download and install Cython <http://www.cython.org>")
159        warn()
160        warn("*"*80)
161        return False
162    #
163    try:
164        CYTHON_VERSION = Cython.__version__
165    except AttributeError:
166        from Cython.Compiler.Version import version as CYTHON_VERSION
167    REQUIRED = VERSION
168    m = re.match(r"(\d+\.\d+(?:\.\d+)?).*", CYTHON_VERSION)
169    if m:
170        Version = StrictVersion
171        AVAILABLE = m.groups()[0]
172    else:
173        Version = LooseVersion
174        AVAILABLE = CYTHON_VERSION
175    if (REQUIRED is not None and
176        Version(AVAILABLE) < Version(REQUIRED)):
177        warn("*"*80)
178        warn()
179        warn(" You need to install Cython %s (you have version %s)"
180             % (REQUIRED, CYTHON_VERSION))
181        warn(" Download and install Cython <http://www.cython.org>")
182        warn()
183        warn("*"*80)
184        return False
185    #
186    return True
187
188def run_cython(source, target=None,
189               depends=(), includes=(),
190               destdir_c=None, destdir_h=None,
191               wdir=None, force=False, VERSION=None):
192    from glob import glob
193    from distutils import log
194    from distutils import dep_util
195    from distutils.errors import DistutilsError
196    if target is None:
197        target = os.path.splitext(source)[0]+'.c'
198    cwd = os.getcwd()
199    try:
200        if wdir: os.chdir(wdir)
201        alldeps = [source]
202        for dep in depends:
203            alldeps += glob(dep)
204        if not (force or dep_util.newer_group(alldeps, target)):
205            log.debug("skipping '%s' -> '%s' (up-to-date)",
206                      source, target)
207            return
208    finally:
209        os.chdir(cwd)
210    if not chk_cython(VERSION):
211        raise DistutilsError("requires Cython>=%s" % VERSION)
212    log.info("cythonizing '%s' -> '%s'", source, target)
213    from conf.cythonize import cythonize
214    err = cythonize(source, target,
215                    includes=includes,
216                    destdir_c=destdir_c,
217                    destdir_h=destdir_h,
218                    wdir=wdir)
219    if err:
220        raise DistutilsError(
221            "Cython failure: '%s' -> '%s'" % (source, target))
222
223def build_sources(cmd):
224    from os.path import exists, isdir, join
225
226    pdepends = []
227    if 'PETSC_DIR' in os.environ:
228      pd = os.environ['PETSC_DIR']
229      pdepends = [pd+'/include/*.h',pd+'/include/petsc/private/*.h']
230      if 'PETSC_ARCH' in os.environ:
231        pda = os.environ['PETSC_ARCH']
232        pdepends.append(pd+'/'+pda+'/include/petscconf.h')
233
234    # petsc4py.PETSc
235    source = 'petsc4py.PETSc.pyx'
236    target = 'petsc4py.PETSc.c'
237    depends = ['include/*/*.pxd',
238               'PETSc/*.pyx',
239               'PETSc/*.pxi']
240    depends.extend(pdepends)
241    includes = ['include']
242    destdir_h = os.path.join('include', 'petsc4py')
243    run_cython(source, target,
244               depends=depends, includes=includes,
245               destdir_c=None, destdir_h=destdir_h, wdir='src',
246               force=cmd.force, VERSION=CYTHON)
247    # libpetsc4py
248    source = os.path.join('libpetsc4py', 'libpetsc4py.pyx')
249    depends = ['include/petsc4py/*.pxd',
250               'libpetsc4py/*.pyx',
251               'libpetsc4py/*.pxi']
252    depends.extend(pdepends)
253    includes = ['include']
254    run_cython(source,
255               depends=depends, includes=includes,
256               destdir_c=None, destdir_h=None, wdir='src',
257               force=cmd.force, VERSION=CYTHON)
258
259build_src.run = build_sources
260
261def run_testsuite(cmd):
262    from distutils.errors import DistutilsError
263    sys.path.insert(0, 'test')
264    try:
265        from runtests import main
266    finally:
267        del sys.path[0]
268    if cmd.dry_run:
269        return
270    args = cmd.args[:] or []
271    if cmd.verbose < 1:
272        args.insert(0,'-q')
273    if cmd.verbose > 1:
274        args.insert(0,'-v')
275    err = main(args)
276    if err:
277        raise DistutilsError("test")
278
279test.run = run_testsuite
280
281# --------------------------------------------------------------------
282
283def main():
284    run_setup()
285
286if __name__ == '__main__':
287    main()
288
289# --------------------------------------------------------------------
290