1#!/usr/bin/env python3 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