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 if os.environ.get('PEP517_BUILD_BACKEND'): 192 pdir = os.environ['PETSC_DIR'] 193 parch = os.environ['PETSC_ARCH'] 194 include = os.path.join(pdir, parch, 'include') 195 for filename in ( 196 'petscconf.h', 197 'petscconfiginfo.h', 198 'petscmachineinfo.h', 199 ): 200 filename = os.path.join(include, filename) 201 with open(filename, 'r') as old_fh: 202 contents = old_fh.read() 203 contents = contents.replace(prefix, '${PETSC_DIR}') 204 contents = re.sub( 205 r'^(#define PETSC_PYTHON_EXE) "(.*)"$', 206 r'\1 "python%d"' % sys.version_info[0], 207 contents, flags=re.MULTILINE, 208 ) 209 with open(filename, 'w') as new_fh: 210 new_fh.write(contents) 211 212 213def build(dry_run=False): 214 log.info('PETSc: build') 215 # Run PETSc build 216 if dry_run: 217 return 218 use_builder_py = False 219 if use_builder_py: 220 import builder 221 builder.PETScMaker().run() 222 import logger 223 logger.Logger.defaultLog = None 224 else: 225 make = shutil.which('make') 226 command = [make, 'all'] 227 status = os.system(" ".join(command)) 228 if status != 0: 229 raise RuntimeError(status) 230 231 232def install(dry_run=False): 233 log.info('PETSc: install') 234 # Run PETSc installer 235 if dry_run: 236 return 237 use_install_py = False 238 if use_install_py: 239 import install 240 install.Installer().run() 241 import logger 242 logger.Logger.defaultLog = None 243 else: 244 make = shutil.which('make') 245 command = [make, 'install'] 246 status = os.system(" ".join(command)) 247 if status != 0: 248 raise RuntimeError(status) 249 250 251class context(object): 252 def __init__(self): 253 self.sys_argv = sys.argv[:] 254 self.wdir = os.getcwd() 255 def enter(self): 256 del sys.argv[1:] 257 pdir = os.environ['PETSC_DIR'] 258 os.chdir(pdir) 259 return self 260 def exit(self): 261 sys.argv[:] = self.sys_argv 262 os.chdir(self.wdir) 263 264 265class cmd_install(_install): 266 267 def initialize_options(self): 268 _install.initialize_options(self) 269 270 def finalize_options(self): 271 _install.finalize_options(self) 272 self.install_lib = self.install_platlib 273 self.install_libbase = self.install_lib 274 self.old_and_unmanageable = True 275 276 def run(self): 277 root_dir = os.path.abspath(self.install_lib) 278 prefix = os.path.join(root_dir, 'petsc') 279 # 280 ctx = context().enter() 281 try: 282 config(prefix, self.dry_run) 283 build(self.dry_run) 284 install(self.dry_run) 285 finally: 286 ctx.exit() 287 # 288 self.outputs = [] 289 for dirpath, _, filenames in os.walk(prefix): 290 for fn in filenames: 291 self.outputs.append(os.path.join(dirpath, fn)) 292 # 293 _install.run(self) 294 295 def get_outputs(self): 296 outputs = getattr(self, 'outputs', []) 297 outputs += _install.get_outputs(self) 298 return outputs 299 300 301class cmd_bdist_wheel(_bdist_wheel): 302 303 def finalize_options(self): 304 super().finalize_options() 305 self.root_is_pure = False 306 self.build_number = None 307 308 def get_tag(self): 309 plat_tag = super().get_tag()[-1] 310 return (self.python_tag, "none", plat_tag) 311 312 313def version(): 314 version_re = { 315 'major' : re.compile(r"#define\s+PETSC_VERSION_MAJOR\s+(\d+)"), 316 'minor' : re.compile(r"#define\s+PETSC_VERSION_MINOR\s+(\d+)"), 317 'micro' : re.compile(r"#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)"), 318 'release': re.compile(r"#define\s+PETSC_VERSION_RELEASE\s+([-]*\d+)"), 319 } 320 petscversion_h = os.path.join('include','petscversion.h') 321 data = open(petscversion_h, 'r').read() 322 major = int(version_re['major'].search(data).groups()[0]) 323 minor = int(version_re['minor'].search(data).groups()[0]) 324 micro = int(version_re['micro'].search(data).groups()[0]) 325 release = int(version_re['release'].search(data).groups()[0]) 326 if release: 327 v = "%d.%d.%d" % (major, minor, micro) 328 else: 329 v = "%d.%d.0.dev%d" % (major, minor+1, 0) 330 return v 331 332 333def tarball(): 334 VERSION = version() 335 if '.dev' in VERSION: 336 return None 337 return ('https://web.cels.anl.gov/projects/petsc/download/release-snapshots/' 338 'petsc-%s.tar.gz#egg=petsc-%s' % (VERSION, VERSION)) 339 340 341description = __doc__.split('\n')[1:-1] 342del description[1:3] 343 344classifiers = """ 345Development Status :: 5 - Production/Stable 346Intended Audience :: Developers 347Intended Audience :: Science/Research 348License :: OSI Approved :: BSD License 349Operating System :: POSIX 350Programming Language :: C 351Programming Language :: C++ 352Programming Language :: Fortran 353Programming Language :: Python 354Topic :: Scientific/Engineering 355Topic :: Software Development :: Libraries 356""" 357 358bootstrap() 359setup( 360 name='petsc', 361 version=version(), 362 description=description.pop(0), 363 long_description='\n'.join(description), 364 long_description_content_type='text/x-rst', 365 classifiers=classifiers.split('\n')[1:-1], 366 keywords = ['PETSc', 'MPI'], 367 platforms=['POSIX'], 368 license='BSD-2-Clause', 369 370 url='https://petsc.org/', 371 download_url=tarball(), 372 373 author='PETSc Team', 374 author_email='petsc-maint@mcs.anl.gov', 375 maintainer='Lisandro Dalcin', 376 maintainer_email='dalcinl@gmail.com', 377 378 packages=['petsc'], 379 package_dir= {'petsc': 'config/pypi'}, 380 cmdclass={ 381 'install': cmd_install, 382 'bdist_wheel': cmd_bdist_wheel, 383 }, 384 **metadata 385) 386