1#!/usr/bin/env python3 2 3""" 4PETSc: Portable, Extensible Toolkit for Scientific Computation 5============================================================== 6 7The Portable, Extensible Toolkit for Scientific Computation (PETSc), 8is a suite of data structures and routines for the scalable (parallel) 9solution of scientific applications modeled by partial differential 10equations. It employs the Message Passing Interface (MPI) standard for 11all message-passing communication. 12 13.. note:: 14 15 To install the ``PETSc`` and ``petsc4py`` packages 16 (``mpi4py`` is optional but highly recommended) use:: 17 18 $ python -m pip install numpy mpi4py 19 $ python -m pip install petsc petsc4py 20 21.. tip:: 22 23 You can also install the in-development versions with:: 24 25 $ python -m pip install Cython numpy mpi4py 26 $ python -m pip install --no-deps https://gitlab.com/petsc/petsc/-/archive/main/petsc-main.tar.gz 27 28 Provide any ``PETSc`` ./configure options using the environmental variable ``PETSC_CONFIGURE_OPTIONS``. 29 30 Do not use the ``PETSc`` ``./configure`` options ``--with-cc``, ``--with-cxx``, ``--with-fc``, or ``--with-mpi-dir``. 31 To set the MPI compilers use the environmental variables ``MPICC``, ``MPICXX``, ``MPIFORT``. 32 If ``mpi4py`` is installed the compilers will be obtained from that installation and ``MPICC``, ``MPICXX``, ``MPIFORT`` will be ignored. 33 34""" 35 36import re 37import os 38import sys 39import shlex 40import shutil 41from setuptools import setup 42from setuptools.command.install import install as _install 43try: 44 from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel 45except ImportError: 46 from wheel.bdist_wheel import bdist_wheel as _bdist_wheel 47from distutils import log 48 49init_py = """\ 50# Author: PETSc Team 51# Contact: petsc-maint@mcs.anl.gov 52 53def get_petsc_dir(): 54 import os 55 return os.path.dirname(__file__) 56 57 58def get_config(): 59 conf = {} 60 conf['PETSC_DIR'] = get_petsc_dir() 61 return conf 62""" 63 64main_py = """\ 65# Author: PETSc Team 66# Contact: petsc-maint@mcs.anl.gov 67 68if __name__ == "__main__": 69 import sys 70 if "--prefix" in sys.argv: 71 from . import get_petsc_dir 72 print(get_petsc_dir()) 73 del get_petsc_dir 74 del sys 75""" 76 77metadata = { 78 'provides' : ['petsc'], 79 'zip_safe' : False, 80} 81 82CONFIGURE_OPTIONS = [] 83 84 85def bootstrap(): 86 # Set PETSC_DIR and PETSC_ARCH 87 PETSC_DIR = os.path.abspath(os.getcwd()) 88 PETSC_ARCH = 'arch-python' 89 os.environ['PETSC_DIR'] = PETSC_DIR 90 os.environ['PETSC_ARCH'] = PETSC_ARCH 91 sys.path.insert(0, os.path.join(PETSC_DIR, 'config')) 92 sys.path.insert(0, os.path.join(PETSC_DIR, 'lib','petsc','conf')) 93 94 # Generate package __init__.py and __main__.py files 95 pkgdir = os.path.join('config', 'pypi') 96 os.makedirs(pkgdir, exist_ok=True) 97 for pyfile, contents in ( 98 ('__init__.py', init_py), 99 ('__main__.py', main_py), 100 ): 101 with open(os.path.join(pkgdir, pyfile), 'w') as fh: 102 fh.write(contents) 103 104 # Configure options 105 options = os.environ.get('PETSC_CONFIGURE_OPTIONS', '') 106 CONFIGURE_OPTIONS.extend(shlex.split(options)) 107 for i in CONFIGURE_OPTIONS: 108 if i.startswith('--with-mpi-dir='): 109 raise RuntimeError("Do not use --with-mpi-dir, use the environmental variables MPICC, MPICXX, MPIFORT") 110 if i.startswith('--with-cc='): 111 raise RuntimeError("Do not use --with-cc, use the environmental variable MPICC") 112 if i.startswith('--with-cxx=') and i != "--with-cxx=0": 113 raise RuntimeError("Do not use --with-cxx, use the environmental variable MPICXX") 114 if i.startswith('--with-fc=') and i != "--with-fc=0": 115 raise RuntimeError("Do not use --with-fc, use the environmental variable MPIFORT") 116 117 if '--with-mpi=0' not in CONFIGURE_OPTIONS: 118 # Simple-minded lookup for MPI and mpi4py 119 mpi4py = mpicc = None 120 try: 121 import mpi4py 122 conf = mpi4py.get_config() 123 mpicc = conf.get('mpicc') 124 except ImportError: # mpi4py is not installed 125 mpi4py = None 126 mpicc = (os.environ.get('MPICC') or 127 shutil.which('mpicc')) 128 except AttributeError: # mpi4py is too old 129 pass 130 if not mpi4py and mpicc: 131 metadata['install_requires'] = ['mpi4py>=1.2.2'] 132 133 134def config(prefix, dry_run=False): 135 log.info('PETSc: configure') 136 options = [ 137 '--prefix=' + prefix, 138 'PETSC_ARCH='+os.environ['PETSC_ARCH'], 139 '--with-shared-libraries=1', 140 '--with-debugging=0', 141 '--with-c2html=0', # not needed 142 ] 143 if '--with-fc=0' in CONFIGURE_OPTIONS: 144 options.append('--with-sowing=0') 145 if '--with-mpi=0' not in CONFIGURE_OPTIONS: 146 try: 147 import mpi4py 148 conf = mpi4py.get_config() 149 mpicc = conf.get('mpicc') 150 mpicxx = conf.get('mpicxx') 151 mpifort = conf.get('mpifort') or conf.get('mpif90') 152 except (ImportError, AttributeError): 153 mpicc = os.environ.get('MPICC') or shutil.which('mpicc') 154 mpicxx = os.environ.get('MPICXX') or shutil.which('mpicxx') 155 mpifort = os.environ.get('MPIFORT') or os.environ.get('MPIF90') 156 mpifort = mpifort or shutil.which('mpifort') 157 mpifort = mpifort or shutil.which('mpif90') 158 if mpicc: 159 options.append('--with-cc='+mpicc) 160 if '--with-cxx=0' not in CONFIGURE_OPTIONS: 161 if mpicxx: 162 options.append('--with-cxx='+mpicxx) 163 else: 164 options.append('--with-cxx=0') 165 if '--with-fc=0' not in CONFIGURE_OPTIONS: 166 if mpifort: 167 options.append('--with-fc='+mpifort) 168 else: 169 options.append('--with-fc=0') 170 options.append('--with-sowing=0') 171 else: 172 options.append('--with-mpi=0') 173 options.extend(CONFIGURE_OPTIONS) 174 # 175 log.info('configure options:') 176 for opt in options: 177 log.info(' '*4 + opt) 178 # Run PETSc configure 179 if dry_run: 180 return 181 use_config_py = False 182 if use_config_py: 183 import configure 184 configure.petsc_configure(options) 185 import logger 186 logger.Logger.defaultLog = None 187 else: 188 python = sys.executable 189 command = [python, './configure'] + options 190 status = os.system(" ".join(command)) 191 if status != 0: 192 raise RuntimeError(status) 193 # Fix PETSc configuration 194 using_build_backend = any( 195 os.environ.get(prefix + '_BUILD_BACKEND') 196 for prefix in ('_PYPROJECT_HOOKS', 'PEP517') 197 ) 198 if using_build_backend: 199 pdir = os.environ['PETSC_DIR'] 200 parch = os.environ['PETSC_ARCH'] 201 include = os.path.join(pdir, parch, 'include') 202 for filename in ( 203 'petscconf.h', 204 'petscconfiginfo.h', 205 'petscmachineinfo.h', 206 ): 207 filename = os.path.join(include, filename) 208 with open(filename, 'r') as old_fh: 209 contents = old_fh.read() 210 contents = contents.replace(prefix, '${PETSC_DIR}') 211 contents = re.sub( 212 r'^(#define PETSC_PYTHON_EXE) "(.*)"$', 213 r'\1 "python%d"' % sys.version_info[0], 214 contents, flags=re.MULTILINE, 215 ) 216 with open(filename, 'w') as new_fh: 217 new_fh.write(contents) 218 219 220def build(dry_run=False): 221 log.info('PETSc: build') 222 # Run PETSc build 223 if dry_run: 224 return 225 use_builder_py = False 226 if use_builder_py: 227 import builder 228 builder.PETScMaker().run() 229 import logger 230 logger.Logger.defaultLog = None 231 else: 232 make = shutil.which('make') 233 command = [make, 'all'] 234 status = os.system(" ".join(command)) 235 if status != 0: 236 raise RuntimeError(status) 237 238 239def install(dry_run=False): 240 log.info('PETSc: install') 241 # Run PETSc installer 242 if dry_run: 243 return 244 use_install_py = False 245 if use_install_py: 246 import install 247 install.Installer().run() 248 import logger 249 logger.Logger.defaultLog = None 250 else: 251 make = shutil.which('make') 252 command = [make, 'install'] 253 status = os.system(" ".join(command)) 254 if status != 0: 255 raise RuntimeError(status) 256 257 258class context(object): 259 def __init__(self): 260 self.sys_argv = sys.argv[:] 261 self.wdir = os.getcwd() 262 def enter(self): 263 del sys.argv[1:] 264 pdir = os.environ['PETSC_DIR'] 265 os.chdir(pdir) 266 return self 267 def exit(self): 268 sys.argv[:] = self.sys_argv 269 os.chdir(self.wdir) 270 271 272class cmd_install(_install): 273 274 def initialize_options(self): 275 _install.initialize_options(self) 276 277 def finalize_options(self): 278 _install.finalize_options(self) 279 self.install_lib = self.install_platlib 280 self.install_libbase = self.install_lib 281 self.old_and_unmanageable = True 282 283 def run(self): 284 root_dir = os.path.abspath(self.install_lib) 285 prefix = os.path.join(root_dir, 'petsc') 286 # 287 ctx = context().enter() 288 try: 289 config(prefix, self.dry_run) 290 build(self.dry_run) 291 install(self.dry_run) 292 finally: 293 ctx.exit() 294 # 295 self.outputs = [] 296 for dirpath, _, filenames in os.walk(prefix): 297 for fn in filenames: 298 self.outputs.append(os.path.join(dirpath, fn)) 299 # 300 _install.run(self) 301 302 def get_outputs(self): 303 outputs = getattr(self, 'outputs', []) 304 outputs += _install.get_outputs(self) 305 return outputs 306 307 308class cmd_bdist_wheel(_bdist_wheel): 309 310 def finalize_options(self): 311 super().finalize_options() 312 self.root_is_pure = False 313 self.build_number = None 314 315 def get_tag(self): 316 plat_tag = super().get_tag()[-1] 317 return (self.python_tag, "none", plat_tag) 318 319 320def version(): 321 version_re = { 322 'major' : re.compile(r"#define\s+PETSC_VERSION_MAJOR\s+(\d+)"), 323 'minor' : re.compile(r"#define\s+PETSC_VERSION_MINOR\s+(\d+)"), 324 'micro' : re.compile(r"#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)"), 325 'release': re.compile(r"#define\s+PETSC_VERSION_RELEASE\s+([-]*\d+)"), 326 } 327 petscversion_h = os.path.join('include','petscversion.h') 328 data = open(petscversion_h, 'r').read() 329 major = int(version_re['major'].search(data).groups()[0]) 330 minor = int(version_re['minor'].search(data).groups()[0]) 331 micro = int(version_re['micro'].search(data).groups()[0]) 332 release = int(version_re['release'].search(data).groups()[0]) 333 if release: 334 v = "%d.%d.%d" % (major, minor, micro) 335 else: 336 v = "%d.%d.0.dev%d" % (major, minor+1, 0) 337 return v 338 339 340def tarball(): 341 VERSION = version() 342 if '.dev' in VERSION: 343 return None 344 return ('https://web.cels.anl.gov/projects/petsc/download/release-snapshots/' 345 'petsc-%s.tar.gz#egg=petsc-%s' % (VERSION, VERSION)) 346 347 348description = __doc__.split('\n')[1:-1] 349del description[1:3] 350 351classifiers = """ 352Development Status :: 5 - Production/Stable 353Intended Audience :: Developers 354Intended Audience :: Science/Research 355Operating System :: POSIX 356Programming Language :: C 357Programming Language :: C++ 358Programming Language :: Fortran 359Programming Language :: Python 360Programming Language :: Python :: 3 361Topic :: Scientific/Engineering 362Topic :: Software Development :: Libraries 363""" 364 365bootstrap() 366setup( 367 name='petsc', 368 version=version(), 369 description=description.pop(0), 370 long_description='\n'.join(description), 371 long_description_content_type='text/x-rst', 372 classifiers=classifiers.split('\n')[1:-1], 373 keywords = ['PETSc', 'MPI'], 374 platforms=['POSIX'], 375 license='BSD-2-Clause', 376 377 url='https://petsc.org/', 378 download_url=tarball(), 379 380 author='PETSc Team', 381 author_email='petsc-maint@mcs.anl.gov', 382 maintainer='Lisandro Dalcin', 383 maintainer_email='dalcinl@gmail.com', 384 385 packages=['petsc'], 386 package_dir= {'petsc': 'config/pypi'}, 387 cmdclass={ 388 'install': cmd_install, 389 'bdist_wheel': cmd_bdist_wheel, 390 }, 391 **metadata 392) 393