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