1#!/usr/bin/env python 2# Author: Lisandro Dalcin 3# Contact: dalcinl@gmail.com 4 5""" 6PETSc for Python 7""" 8 9import re 10import os 11import sys 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# python-3.11+ requires cython 0.29.32+ 26if pyver >= (3, 11): 27 CYTHON = '0.29.32' 28else: 29 CYTHON = '0.24' 30 31# -------------------------------------------------------------------- 32# Metadata 33# -------------------------------------------------------------------- 34 35topdir = os.path.abspath(os.path.dirname(__file__)) 36sys.path.insert(0, topdir) 37 38def name(): 39 return 'petsc4py' 40 41def version(): 42 with open(os.path.join(topdir, 'src', '__init__.py')) as f: 43 m = re.search(r"__version__\s*=\s*'(.*)'", f.read()) 44 return m.groups()[0] 45 46def description(): 47 return __doc__.strip() 48 49def long_description(): 50 with open(os.path.join(topdir, 'DESCRIPTION.rst')) as f: 51 return f.read() 52 53name = name() 54version = version() 55 56url = 'https://gitlab.com/petsc/petsc' 57pypiroot = 'https://pypi.io/packages/source/%s/%s/' % (name[0], name) 58download = pypiroot + '%(name)s-%(version)s.tar.gz' % vars() 59 60classifiers = """ 61License :: OSI Approved :: BSD License 62Operating System :: POSIX 63Intended Audience :: Developers 64Intended Audience :: Science/Research 65Programming Language :: C 66Programming Language :: C++ 67Programming Language :: Cython 68Programming Language :: Python 69Programming Language :: Python :: 2 70Programming Language :: Python :: 3 71Programming Language :: Python :: Implementation :: CPython 72Topic :: Scientific/Engineering 73Topic :: Software Development :: Libraries :: Python Modules 74Development Status :: 5 - Production/Stable 75""" 76 77keywords = """ 78scientific computing 79parallel computing 80PETSc 81MPI 82""" 83 84platforms = """ 85POSIX 86Linux 87macOS 88FreeBSD 89""" 90 91metadata = { 92 'name' : name, 93 'version' : version, 94 'description' : description(), 95 'long_description' : long_description(), 96 'url' : url, 97 'download_url' : download, 98 'classifiers' : classifiers.strip().split('\n'), 99 'keywords' : keywords.strip().split('\n'), 100 'license' : 'BSD-2-Clause', 101 'platforms' : platforms.split('\n'), 102 'author' : 'Lisandro Dalcin', 103 'author_email' : 'dalcinl@gmail.com', 104 'maintainer' : 'PETSc Team', 105 'maintainer_email' : 'petsc-maint@mcs.anl.gov', 106} 107metadata.update({ 108 'requires': ['numpy'], 109}) 110 111metadata_extra = { 112 'long_description_content_type': 'text/rst', 113} 114 115# -------------------------------------------------------------------- 116# Extension modules 117# -------------------------------------------------------------------- 118 119def extensions(): 120 from os import walk 121 from glob import glob 122 from os.path import join 123 glob_join = lambda *args: glob(join(*args)) 124 depends = [] 125 for pth, dirs, files in walk('src'): 126 depends += glob_join(pth, '*.h') 127 depends += glob_join(pth, '*.c') 128 if 'PETSC_DIR' in os.environ: 129 pd = os.environ['PETSC_DIR'] 130 pa = os.environ.get('PETSC_ARCH', '') 131 depends += glob_join(pd, 'include', '*.h') 132 depends += glob_join(pd, 'include', 'petsc', 'private', '*.h') 133 depends += glob_join(pd, pa, 'include', 'petscconf.h') 134 numpy_include = os.environ.get('NUMPY_INCLUDE') 135 if numpy_include is not None: 136 numpy_includes = [numpy_include] 137 else: 138 try: 139 import numpy 140 numpy_includes = [numpy.get_include()] 141 except ImportError: 142 numpy_includes = [] 143 PETSc = dict( 144 name='petsc4py.lib.PETSc', 145 sources=['src/PETSc.c'], 146 depends=depends, 147 include_dirs=[ 148 'src/include', 149 ] + numpy_includes, 150 define_macros=[ 151 ('MPICH_SKIP_MPICXX', 1), 152 ('OMPI_SKIP_MPICXX', 1), 153 ], 154 ) 155 return [PETSc] 156 157# -------------------------------------------------------------------- 158# Setup 159# -------------------------------------------------------------------- 160 161from conf.petscconf import setup, Extension 162from conf.petscconf import config, build, build_src, build_ext, install 163from conf.petscconf import clean, sdist 164 165def get_release(): 166 release = 1 167 if topdir.endswith(os.path.join(os.path.sep, 'src', 'binding', name)): 168 topname = name.replace('4py', '') 169 rootdir = os.path.abspath(os.path.join(topdir, *[os.path.pardir]*3)) 170 version_h = os.path.join(rootdir, 'include', '%sversion.h' % topname) 171 release_macro = '%s_VERSION_RELEASE' % topname.upper() 172 version_re = re.compile(r"#define\s+%s\s+([-]*\d+)" % release_macro) 173 if os.path.exists(version_h) and os.path.isfile(version_h): 174 with open(version_h, 'r') as f: 175 release = int(version_re.search(f.read()).groups()[0]) 176 return bool(release) 177 178def requires(pkgname, major, minor, release=True): 179 minor = minor + int(not release) 180 devel = '' if release else '.dev0' 181 vmin = "%s.%s%s" % (major, minor, devel) 182 vmax = "%s.%s" % (major, minor + 1) 183 return "%s>=%s,<%s" % (pkgname, vmin, vmax) 184 185def run_setup(): 186 setup_args = metadata.copy() 187 vstr = setup_args['version'].split('.')[:2] 188 x, y = tuple(map(int, vstr)) 189 release = get_release() 190 if not release: 191 setup_args['version'] = "%d.%d.0.dev0" %(x, y+1) 192 if setuptools: 193 setup_args['zip_safe'] = False 194 setup_args['install_requires'] = ['numpy'] 195 PETSC_DIR = os.environ.get('PETSC_DIR') 196 if not (PETSC_DIR and os.path.isdir(PETSC_DIR)): 197 petsc = requires('petsc', x, y, release) 198 setup_args['install_requires'] += [petsc] 199 setup_args.update(metadata_extra) 200 if setuptools: 201 src = os.path.join('src', 'petsc4py.PETSc.c') 202 has_src = os.path.exists(os.path.join(topdir, src)) 203 has_git = os.path.isdir(os.path.join(topdir, '.git')) 204 has_hg = os.path.isdir(os.path.join(topdir, '.hg')) 205 suffix = os.path.join('src', 'binding', 'petsc4py') 206 in_petsc = topdir.endswith(os.path.sep + suffix) 207 if not has_src or has_git or has_hg or in_petsc: 208 setup_args['setup_requires'] = ['Cython>='+CYTHON] 209 # 210 setup( 211 packages=[ 212 'petsc4py', 213 'petsc4py.lib', 214 ], 215 package_dir={ 216 'petsc4py' : 'src', 217 'petsc4py.lib' : 'src/lib', 218 }, 219 package_data={ 220 'petsc4py': [ 221 'include/petsc4py/*.h', 222 'include/petsc4py/*.i', 223 'include/petsc4py/*.pxd', 224 'include/petsc4py/*.pxi', 225 'include/petsc4py/*.pyx', 226 'PETSc.pxd', 227 ], 228 'petsc4py.lib': [ 229 'petsc.cfg', 230 ], 231 }, 232 ext_modules=[Extension(**ext) for ext in extensions()], 233 cmdclass={ 234 'config': config, 235 'build': build, 236 'build_src': build_src, 237 'build_ext': build_ext, 238 'install': install, 239 'clean': clean, 240 'sdist': sdist, 241 }, 242 **setup_args, 243 ) 244 245# -------------------------------------------------------------------- 246 247def cython_req(): 248 return CYTHON 249 250def cython_chk(VERSION, verbose=True): 251 from conf.baseconf import log 252 from conf.baseconf import Version 253 from conf.baseconf import LegacyVersion 254 if verbose: 255 warn = lambda msg='': sys.stderr.write(msg+'\n') 256 else: 257 warn = lambda msg='': None 258 # 259 try: 260 import Cython 261 except ImportError: 262 warn("*"*80) 263 warn() 264 warn(" You need Cython to generate C source files.\n") 265 warn(" $ python -m pip install cython") 266 warn() 267 warn("*"*80) 268 return False 269 # 270 REQUIRED = VERSION 271 CYTHON_VERSION = Cython.__version__ 272 if VERSION is not None: 273 m = re.match(r"(\d+\.\d+(?:\.\d+)?).*", CYTHON_VERSION) 274 if m: 275 REQUIRED = Version(VERSION) 276 AVAILABLE = Version(m.groups()[0]) 277 else: 278 REQUIRED = LegacyVersion(VERSION) 279 AVAILABLE = LegacyVersion(CYTHON_VERSION) 280 if AVAILABLE < REQUIRED: 281 warn("*"*80) 282 warn() 283 warn(" You need Cython >= {0} (you have version {1}).\n" 284 .format(REQUIRED, CYTHON_VERSION)) 285 warn(" $ python -m pip install --upgrade cython") 286 warn() 287 warn("*"*80) 288 return False 289 # 290 if verbose: 291 log.info("using Cython version %s" % CYTHON_VERSION) 292 return True 293 294def cython_run( 295 source, target=None, 296 depends=(), includes=(), 297 destdir_c=None, destdir_h=None, 298 workdir=None, force=False, 299 VERSION=None, 300): 301 from glob import glob 302 from conf.baseconf import log 303 from conf.baseconf import dep_util 304 from conf.baseconf import DistutilsError 305 if target is None: 306 target = os.path.splitext(source)[0]+'.c' 307 cwd = os.getcwd() 308 try: 309 if workdir: 310 os.chdir(workdir) 311 alldeps = [source] 312 for dep in depends: 313 alldeps += glob(dep) 314 if not (force or dep_util.newer_group(alldeps, target)): 315 log.debug("skipping '%s' -> '%s' (up-to-date)", 316 source, target) 317 return 318 finally: 319 os.chdir(cwd) 320 # 321 require = 'Cython' 322 if VERSION is not None: 323 require += '>=%s' % VERSION 324 if not cython_chk(VERSION, verbose=False): 325 pkgname = re.compile(r'cython(\.|$)', re.IGNORECASE) 326 for modname in list(sys.modules.keys()): 327 if pkgname.match(modname): 328 del sys.modules[modname] 329 try: 330 import warnings 331 import setuptools 332 install_setup_requires = setuptools._install_setup_requires 333 with warnings.catch_warnings(): 334 category = setuptools.SetuptoolsDeprecationWarning 335 warnings.simplefilter('ignore', category) 336 log.info("fetching build requirement %s" % require) 337 install_setup_requires(dict(setup_requires=[require])) 338 except Exception: 339 log.info("failed to fetch build requirement %s" % require) 340 if not cython_chk(VERSION): 341 raise DistutilsError("requires Cython>=%s" % VERSION) 342 # 343 log.info("cythonizing '%s' -> '%s'", source, target) 344 from conf.cythonize import cythonize 345 err = cythonize( 346 source, target, 347 includes=includes, 348 destdir_c=destdir_c, 349 destdir_h=destdir_h, 350 workdir=workdir, 351 ) 352 if err: 353 raise DistutilsError( 354 "Cython failure: '%s' -> '%s'" % (source, target)) 355 356def build_sources(cmd): 357 from os.path import exists, isdir, join 358 # petsc4py.PETSc 359 source = 'petsc4py.PETSc.pyx' 360 target = 'petsc4py.PETSc.c' 361 depends = [ 362 'include/*/*.pxd', 363 'PETSc/*.pyx', 364 'PETSc/*.pxi', 365 ] 366 includes = ['include'] 367 destdir_h = os.path.join('include', 'petsc4py') 368 cython_run( 369 source, target, 370 depends=depends, 371 includes=includes, 372 destdir_c=None, 373 destdir_h=destdir_h, 374 workdir='src', 375 force=cmd.force, 376 VERSION=cython_req(), 377 ) 378 379build_src.run = build_sources 380 381# -------------------------------------------------------------------- 382 383def main(): 384 run_setup() 385 386if __name__ == '__main__': 387 main() 388 389# -------------------------------------------------------------------- 390