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