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