xref: /petsc/src/binding/petsc4py/setup.py (revision 697336901c45ac77e1fd620fe1fca906cf3f95c8)
1#!/usr/bin/env python
2# Author:  Lisandro Dalcin
3# Contact: dalcinl@gmail.com
4
5import re
6import os
7import sys
8
9try:
10    import setuptools
11except ImportError:
12    setuptools = None
13
14topdir = os.path.abspath(os.path.dirname(__file__))
15sys.path.insert(0, os.path.join(topdir, 'conf'))
16
17pyver = sys.version_info[:2]
18if pyver < (2, 6) or (3, 0) <= pyver < (3, 2):
19    raise RuntimeError("Python version 2.6, 2.7 or >= 3.2 required")
20if pyver == (2, 6) or pyver == (3, 2):
21    sys.stderr.write(
22        "WARNING: Python %d.%d is not supported.\n" % pyver)
23
24PNAME = 'PETSc'
25EMAIL = 'petsc-maint@mcs.anl.gov'
26PLIST = [PNAME]
27
28# --------------------------------------------------------------------
29# Metadata
30# --------------------------------------------------------------------
31
32def F(string):
33    return string.format(
34        Name=PNAME,
35        name=PNAME.lower(),
36        pyname=PNAME.lower()+'4py',
37    )
38
39def get_name():
40    return F('{pyname}')
41
42def get_version():
43    try:
44        return get_version.result
45    except AttributeError:
46        pass
47    pkg_init_py = os.path.join(F('{pyname}'), '__init__.py')
48    with open(os.path.join(topdir, 'src', pkg_init_py)) as f:
49        m = re.search(r"__version__\s*=\s*'(.*)'", f.read())
50    version = m.groups()[0]
51    get_version.result = version
52    return version
53
54def description():
55    return F('{Name} for Python')
56
57def long_description():
58    with open(os.path.join(topdir, 'DESCRIPTION.rst')) as f:
59        return f.read()
60
61url      = F('https://gitlab.com/{name}/{name}')
62pypiroot = F('https://pypi.io/packages/source')
63pypislug = F('{pyname}')[0] + F('/{pyname}')
64tarball  = F('{pyname}-%s.tar.gz' % get_version())
65download = '/'.join([pypiroot, pypislug, tarball])
66
67classifiers = """
68License :: OSI Approved :: BSD License
69Operating System :: POSIX
70Intended Audience :: Developers
71Intended Audience :: Science/Research
72Programming Language :: C
73Programming Language :: C++
74Programming Language :: Cython
75Programming Language :: Python
76Programming Language :: Python :: 2
77Programming Language :: Python :: 3
78Programming Language :: Python :: Implementation :: CPython
79Topic :: Scientific/Engineering
80Topic :: Software Development :: Libraries :: Python Modules
81Development Status :: 5 - Production/Stable
82""".strip().split('\n')
83
84keywords = """
85scientific computing
86parallel computing
87MPI
88""".strip().split('\n')
89
90platforms = """
91POSIX
92Linux
93macOS
94FreeBSD
95""".strip().split('\n')
96
97metadata = {
98    'name'             : get_name(),
99    'version'          : get_version(),
100    'description'      : description(),
101    'long_description' : long_description(),
102    'url'              : url,
103    'download_url'     : download,
104    'classifiers'      : classifiers,
105    'keywords'         : keywords + PLIST,
106    'license'          : 'BSD-2-Clause',
107    'platforms'        : platforms,
108    'author'           : 'Lisandro Dalcin',
109    'author_email'     : 'dalcinl@gmail.com',
110    'maintainer'       : F('{Name} Team'),
111    'maintainer_email' : EMAIL,
112}
113metadata.update({
114    'requires': ['numpy'],
115})
116
117metadata_extra = {
118    'long_description_content_type': 'text/x-rst',
119}
120
121# --------------------------------------------------------------------
122# Extension modules
123# --------------------------------------------------------------------
124
125def sources():
126    src = dict(
127        source=F('{pyname}/{Name}.pyx'),
128        depends=[
129            F('{pyname}/*.pyx'),
130            F('{pyname}/*.pxd'),
131            F('{pyname}/{Name}/*.pyx'),
132            F('{pyname}/{Name}/*.pxd'),
133            F('{pyname}/{Name}/*.pxi'),
134        ],
135        workdir='src',
136    )
137    return [src]
138
139def extensions():
140    from os import walk
141    from glob import glob
142    from os.path import join
143    #
144    depends = []
145    glob_join = lambda *args: glob(join(*args))
146    for pth, dirs, files in walk('src'):
147        depends += glob_join(pth, '*.h')
148        depends += glob_join(pth, '*.c')
149    for pkg in map(str.lower, reversed(PLIST)):
150        if (pkg.upper()+'_DIR') in os.environ:
151            pd = os.environ[pkg.upper()+'_DIR']
152            pa = os.environ.get('PETSC_ARCH', '')
153            depends += glob_join(pd, 'include', '*.h')
154            depends += glob_join(pd, 'include', pkg, 'private', '*.h')
155            depends += glob_join(pd, pa, 'include', '%sconf.h' % pkg)
156    #
157    include_dirs = []
158    numpy_include = os.environ.get('NUMPY_INCLUDE')
159    if numpy_include is not None:
160        numpy_includes = [numpy_include]
161    else:
162        try:
163            import numpy
164            numpy_includes = [numpy.get_include()]
165        except ImportError:
166            numpy_includes = []
167    include_dirs.extend(numpy_includes)
168    if F('{pyname}') != 'petsc4py':
169        try:
170            import petsc4py
171            petsc4py_includes = [petsc4py.get_include()]
172        except ImportError:
173            petsc4py_includes = []
174        include_dirs.extend(petsc4py_includes)
175    #
176    ext = dict(
177        name=F('{pyname}.lib.{Name}'),
178        sources=[F('src/{pyname}/{Name}.c')],
179        depends=depends,
180        include_dirs=[
181            'src',
182            F('src/{pyname}/include'),
183        ] + include_dirs,
184        define_macros=[
185            ('MPICH_SKIP_MPICXX', 1),
186            ('OMPI_SKIP_MPICXX', 1),
187            ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'),
188        ],
189    )
190    return [ext]
191
192# --------------------------------------------------------------------
193# Setup
194# --------------------------------------------------------------------
195
196def get_release():
197    suffix = os.path.join('src', 'binding', F('{pyname}'))
198    if not topdir.endswith(os.path.join(os.path.sep, suffix)):
199        return True
200    release = 1
201    rootdir = os.path.abspath(os.path.join(topdir, *[os.path.pardir]*3))
202    version_h = os.path.join(rootdir, 'include', F('{name}version.h'))
203    release_macro = '%s_VERSION_RELEASE' % F('{name}').upper()
204    version_re = re.compile(r"#define\s+%s\s+([-]*\d+)" % release_macro)
205    if os.path.exists(version_h) and os.path.isfile(version_h):
206        with open(version_h, 'r') as f:
207            release = int(version_re.search(f.read()).groups()[0])
208    return bool(release)
209
210def requires(pkgname, major, minor, release=True):
211    minor = minor + int(not release)
212    devel = '' if release else '.dev0'
213    vmin = "%s.%s%s" % (major, minor, devel)
214    vmax = "%s.%s" % (major, minor + 1)
215    return "%s>=%s,<%s" % (pkgname, vmin, vmax)
216
217def run_setup():
218    setup_args = metadata.copy()
219    vstr = setup_args['version'].split('.')[:2]
220    x, y = tuple(map(int, vstr))
221    release = get_release()
222    if not release:
223        setup_args['version'] = "%d.%d.0.dev0" %(x, y+1)
224    if setuptools:
225        setup_args['zip_safe'] = False
226        setup_args['install_requires'] = ['numpy']
227        for pkg in map(str.lower, PLIST):
228            PKG_DIR = os.environ.get(pkg.upper() + '_DIR')
229            if not (PKG_DIR and os.path.isdir(PKG_DIR)):
230                package = requires(pkg, x, y, release)
231                setup_args['install_requires'] += [package]
232        if F('{pyname}') != 'petsc4py':
233            package = requires('petsc4py', x, y, release)
234            setup_args['install_requires'] += [package]
235        setup_args.update(metadata_extra)
236    #
237    conf = __import__(F('conf{name}'))
238    conf.setup(
239        packages=[
240            F('{pyname}'),
241            F('{pyname}.lib'),
242        ],
243        package_dir={'' : 'src'},
244        package_data={
245            F('{pyname}'): [
246                F('{Name}.pxd'),
247                F('{Name}*.h'),
248                F('include/{pyname}/*.h'),
249                F('include/{pyname}/*.i'),
250                'py.typed',
251                '*.pyi',
252                '*/*.pyi',
253            ],
254            F('{pyname}.lib'): [
255                F('{name}.cfg'),
256            ],
257        },
258        cython_sources=[
259            src for src in sources()
260        ],
261        ext_modules=[
262            conf.Extension(**ext) for ext in extensions()
263        ],
264        **setup_args
265    )
266
267# --------------------------------------------------------------------
268
269def main():
270    run_setup()
271
272if __name__ == '__main__':
273    main()
274
275# --------------------------------------------------------------------
276