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