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