1#!/usr/bin/env python 2# Author: Lisandro Dalcin 3# Contact: dalcinl@gmail.com 4 5""" 6PETSc for Python 7""" 8 9import sys 10import os 11import re 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# -------------------------------------------------------------------- 26# Metadata 27# -------------------------------------------------------------------- 28 29topdir = os.path.abspath(os.path.dirname(__file__)) 30sys.path.insert(0, topdir) 31 32from conf.metadata import metadata 33 34def name(): 35 return 'petsc4py' 36 37def version(): 38 with open(os.path.join(topdir, 'src', '__init__.py')) as f: 39 m = re.search(r"__version__\s*=\s*'(.*)'", f.read()) 40 return m.groups()[0] 41 42def description(): 43 with open(os.path.join(topdir, 'DESCRIPTION.rst')) as f: 44 return f.read() 45 46name = name() 47version = version() 48 49url = 'https://gitlab.com/petsc/petsc' 50pypiroot = 'https://pypi.io/packages/source/%s/%s/' % (name[0], name) 51download = pypiroot + '%(name)s-%(version)s.tar.gz' % vars() 52 53devstat = ['Development Status :: 5 - Production/Stable'] 54keywords = ['PETSc', 'MPI'] 55 56metadata['name'] = name 57metadata['version'] = version 58metadata['description'] = __doc__.strip() 59metadata['long_description'] = description() 60metadata['keywords'] += keywords 61metadata['classifiers'] += devstat 62metadata['url'] = url 63metadata['download_url'] = download 64 65metadata['provides'] = ['petsc4py'] 66metadata['requires'] = ['numpy'] 67 68# -------------------------------------------------------------------- 69# Extension modules 70# -------------------------------------------------------------------- 71 72def get_ext_modules(Extension): 73 from os import walk 74 from glob import glob 75 from os.path import join 76 glob_join = lambda *args: glob(join(*args)) 77 depends = [] 78 for pth, dirs, files in walk('src'): 79 depends += glob_join(pth, '*.h') 80 depends += glob_join(pth, '*.c') 81 if 'PETSC_DIR' in os.environ: 82 pd = os.environ['PETSC_DIR'] 83 pa = os.environ.get('PETSC_ARCH', '') 84 depends += glob_join(pd, 'include', '*.h') 85 depends += glob_join(pd, 'include', 'petsc', 'private', '*.h') 86 depends += glob_join(pd, pa, 'include', 'petscconf.h') 87 numpy_include = os.environ.get('NUMPY_INCLUDE') 88 if numpy_include is not None: 89 numpy_includes = [numpy_include] 90 else: 91 try: 92 import numpy 93 numpy_includes = [numpy.get_include()] 94 except ImportError: 95 numpy_includes = [] 96 return [Extension('petsc4py.lib.PETSc', 97 sources=['src/PETSc.c', 98 'src/libpetsc4py.c', 99 ], 100 include_dirs=['src/include', 101 ] + numpy_includes, 102 depends=depends)] 103 104# -------------------------------------------------------------------- 105# Setup 106# -------------------------------------------------------------------- 107 108from conf.petscconf import setup, Extension 109from conf.petscconf import config, build, build_src, build_ext, install 110from conf.petscconf import clean, test, sdist 111 112# python-3.11+ requires cython 0.29.32+ 113if pyver >= (3, 11): 114 CYTHON = '0.29.32' 115else: 116 CYTHON = '0.24' 117 118def get_release(): 119 release = 1 120 if topdir.endswith(os.path.join(os.path.sep, 'src', 'binding', name)): 121 topname = name.replace('4py', '') 122 rootdir = os.path.abspath(os.path.join(topdir, *[os.path.pardir]*3)) 123 version_h = os.path.join(rootdir, 'include', '%sversion.h' % topname) 124 release_macro = '%s_VERSION_RELEASE' % topname.upper() 125 version_re = re.compile(r"#define\s+%s\s+([-]*\d+)" % release_macro) 126 if os.path.exists(version_h) and os.path.isfile(version_h): 127 with open(version_h, 'r') as f: 128 release = int(version_re.search(f.read()).groups()[0]) 129 return bool(release) 130 131def requires(pkgname, major, minor, release=True): 132 minor = minor + int(not release) 133 devel = '' if release else '.dev0' 134 vmin = "%s.%s%s" % (major, minor, devel) 135 vmax = "%s.%s" % (major, minor + 1) 136 return "%s>=%s,<%s" % (pkgname, vmin, vmax) 137 138def run_setup(): 139 setup_args = metadata.copy() 140 vstr = setup_args['version'].split('.')[:2] 141 x, y = tuple(map(int, vstr)) 142 release = get_release() 143 if not release: 144 setup_args['version'] = "%d.%d.0.dev0" %(x, y+1) 145 if setuptools: 146 setup_args['zip_safe'] = False 147 setup_args['install_requires'] = ['numpy'] 148 PETSC_DIR = os.environ.get('PETSC_DIR') 149 if not (PETSC_DIR and os.path.isdir(PETSC_DIR)): 150 petsc = requires('petsc', x, y, release) 151 setup_args['install_requires'] += [petsc] 152 if setuptools: 153 src = os.path.join('src', 'petsc4py.PETSc.c') 154 has_src = os.path.exists(os.path.join(topdir, src)) 155 has_git = os.path.isdir(os.path.join(topdir, '.git')) 156 has_hg = os.path.isdir(os.path.join(topdir, '.hg')) 157 suffix = os.path.join('src', 'binding', 'petsc4py') 158 in_petsc = topdir.endswith(os.path.sep + suffix) 159 if not has_src or has_git or has_hg or in_petsc: 160 setup_args['setup_requires'] = ['Cython>='+CYTHON] 161 # 162 setup(packages = ['petsc4py', 163 'petsc4py.lib',], 164 package_dir = {'petsc4py' : 'src', 165 'petsc4py.lib' : 'src/lib'}, 166 package_data = {'petsc4py' : ['include/petsc4py/*.h', 167 'include/petsc4py/*.i', 168 'include/petsc4py/*.pxd', 169 'include/petsc4py/*.pxi', 170 'include/petsc4py/*.pyx', 171 'PETSc.pxd',], 172 'petsc4py.lib' : ['petsc.cfg'],}, 173 ext_modules = get_ext_modules(Extension), 174 cmdclass = {'config' : config, 175 'build' : build, 176 'build_src' : build_src, 177 'build_ext' : build_ext, 178 'install' : install, 179 'clean' : clean, 180 'test' : test, 181 'sdist' : sdist, 182 }, 183 **setup_args) 184 185def chk_cython(VERSION): 186 from distutils import log 187 from distutils.version import LooseVersion 188 from distutils.version import StrictVersion 189 warn = lambda msg='': sys.stderr.write(msg+'\n') 190 # 191 try: 192 import Cython 193 except ImportError: 194 warn("*"*80) 195 warn() 196 warn(" You need to generate C source files with Cython!!") 197 warn(" Download and install Cython <http://www.cython.org>") 198 warn() 199 warn("*"*80) 200 return False 201 # 202 try: 203 CYTHON_VERSION = Cython.__version__ 204 except AttributeError: 205 from Cython.Compiler.Version import version as CYTHON_VERSION 206 REQUIRED = VERSION 207 m = re.match(r"(\d+\.\d+(?:\.\d+)?).*", CYTHON_VERSION) 208 if m: 209 Version = StrictVersion 210 AVAILABLE = m.groups()[0] 211 else: 212 Version = LooseVersion 213 AVAILABLE = CYTHON_VERSION 214 if (REQUIRED is not None and 215 Version(AVAILABLE) < Version(REQUIRED)): 216 warn("*"*80) 217 warn() 218 warn(" You need to install Cython %s (you have version %s)" 219 % (REQUIRED, CYTHON_VERSION)) 220 warn(" Download and install Cython <http://www.cython.org>") 221 warn() 222 warn("*"*80) 223 return False 224 # 225 return True 226 227def run_cython(source, target=None, 228 depends=(), includes=(), 229 destdir_c=None, destdir_h=None, 230 wdir=None, force=False, VERSION=None): 231 from glob import glob 232 from distutils import log 233 from distutils import dep_util 234 from distutils.errors import DistutilsError 235 if target is None: 236 target = os.path.splitext(source)[0]+'.c' 237 cwd = os.getcwd() 238 try: 239 if wdir: os.chdir(wdir) 240 alldeps = [source] 241 for dep in depends: 242 alldeps += glob(dep) 243 if not (force or dep_util.newer_group(alldeps, target)): 244 log.debug("skipping '%s' -> '%s' (up-to-date)", 245 source, target) 246 return 247 finally: 248 os.chdir(cwd) 249 if not chk_cython(VERSION): 250 raise DistutilsError("requires Cython>=%s" % VERSION) 251 log.info("cythonizing '%s' -> '%s'", source, target) 252 from conf.cythonize import cythonize 253 err = cythonize(source, target, 254 includes=includes, 255 destdir_c=destdir_c, 256 destdir_h=destdir_h, 257 wdir=wdir) 258 if err: 259 raise DistutilsError( 260 "Cython failure: '%s' -> '%s'" % (source, target)) 261 262def build_sources(cmd): 263 from os.path import exists, isdir, join 264 265 # petsc4py.PETSc 266 source = 'petsc4py.PETSc.pyx' 267 target = 'petsc4py.PETSc.c' 268 depends = ['include/*/*.pxd', 269 'PETSc/*.pyx', 270 'PETSc/*.pxi'] 271 includes = ['include'] 272 destdir_h = os.path.join('include', 'petsc4py') 273 run_cython(source, target, 274 depends=depends, includes=includes, 275 destdir_c=None, destdir_h=destdir_h, wdir='src', 276 force=cmd.force, VERSION=CYTHON) 277 # libpetsc4py 278 source = os.path.join('libpetsc4py', 'libpetsc4py.pyx') 279 depends = ['include/petsc4py/*.pxd', 280 'libpetsc4py/*.pyx', 281 'libpetsc4py/*.pxi'] 282 includes = ['include'] 283 run_cython(source, 284 depends=depends, includes=includes, 285 destdir_c=None, destdir_h=None, wdir='src', 286 force=cmd.force, VERSION=CYTHON) 287 288build_src.run = build_sources 289 290def run_testsuite(cmd): 291 from distutils.errors import DistutilsError 292 sys.path.insert(0, 'test') 293 try: 294 from runtests import main 295 finally: 296 del sys.path[0] 297 if cmd.dry_run: 298 return 299 args = cmd.args[:] or [] 300 if cmd.verbose < 1: 301 args.insert(0,'-q') 302 if cmd.verbose > 1: 303 args.insert(0,'-v') 304 err = main(args) 305 if err: 306 raise DistutilsError("test") 307 308test.run = run_testsuite 309 310# -------------------------------------------------------------------- 311 312def main(): 313 run_setup() 314 315if __name__ == '__main__': 316 main() 317 318# -------------------------------------------------------------------- 319