xref: /petsc/setup.py (revision 1b37a2a7cc4a4fb30c3e967db1c694c0a1013f51)
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 ``PETSc`` and ``petsc4py`` (``mpi4py`` is optional
16   but highly recommended) use::
17
18     $ python -m pip install numpy mpi4py  (or pip install numpy mpi4py)
19     $ python -m pip install petsc petsc4py (or 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  To set the MPI compilers use the environmental variables ``MPICC``, ``MPICXX``, ``MPIF90``.
29
30  Provide any ``PETSc`` ./configure options using the environmental variable ``PETSC_CONFIGURE_OPTIONS``.
31
32  Do not use the ``PETSc`` ``./configure`` options ``--with-cc``, ``--with-cxx``, ``--with-fc``, or ``--with-mpi-dir``.
33
34  If ``mpi4py`` is installed the compilers will obtained from that installation and ``MPICC``, ``MPICXX``, ``MPIF90`` will be ignored.
35
36"""
37
38import sys, os
39from setuptools import setup
40from setuptools.command.install import install as _install
41from distutils.util import get_platform, split_quoted
42from distutils.spawn import find_executable
43from distutils import log
44
45init_py = """\
46# Author:  PETSc Team
47# Contact: petsc-maint@mcs.anl.gov
48
49def get_petsc_dir():
50    import os
51    return os.path.dirname(__file__)
52
53def get_config():
54    conf = {}
55    conf['PETSC_DIR'] = get_petsc_dir()
56    return conf
57"""
58
59metadata = {
60    'provides' : ['petsc'],
61    'zip_safe' : False,
62}
63
64CONFIGURE_OPTIONS = []
65
66def bootstrap():
67    # Set PETSC_DIR and PETSC_ARCH
68    PETSC_DIR  = os.path.abspath(os.getcwd())
69    PETSC_ARCH = 'arch-python-' + get_platform()
70    os.environ['PETSC_DIR']  = PETSC_DIR
71    os.environ['PETSC_ARCH'] = PETSC_ARCH
72    sys.path.insert(0, os.path.join(PETSC_DIR, 'config'))
73    sys.path.insert(0, os.path.join(PETSC_DIR, 'lib','petsc','conf'))
74    # Generate package __init__.py file
75    from distutils.dir_util import mkpath
76    pkgdir = os.path.join('config', 'pypi')
77    if not os.path.exists(pkgdir): mkpath(pkgdir)
78    pkgfile = os.path.join(pkgdir, '__init__.py')
79    fh = open(pkgfile, 'w')
80    fh.write(init_py)
81    fh.close()
82    # Configure options
83    options = os.environ.get('PETSC_CONFIGURE_OPTIONS', '')
84    CONFIGURE_OPTIONS.extend(split_quoted(options))
85    for i in CONFIGURE_OPTIONS:
86        if i.startswith('--with-mpi-dir='):
87            raise RuntimeError("Do not use --with-mpi-dir, use the environmental variables MPICC, MPICXX, MPIF90")
88        if i.startswith('--with-cc='):
89            raise RuntimeError("Do not use --with-cc, use the environmental variable MPICC")
90        if i.startswith('--with-cxx=') and i != "--with-cxx=0":
91            raise RuntimeError("Do not use --with-cxx, use the environmental variable MPICXX")
92        if i.startswith('--with-fc=') and i != "--with-fc=0":
93            raise RuntimeError("Do not use --with-fc, use the environmental variable MPIF90")
94
95    if '--with-mpi=0' not in CONFIGURE_OPTIONS:
96        # Simple-minded lookup for MPI and mpi4py
97        mpi4py = mpicc = None
98        try:
99            import mpi4py
100            conf = mpi4py.get_config()
101            mpicc = conf.get('mpicc')
102        except ImportError: # mpi4py is not installed
103            mpi4py = None
104            mpicc = (os.environ.get('MPICC') or
105                     find_executable('mpicc'))
106        except AttributeError: # mpi4py is too old
107            pass
108        if not mpi4py and mpicc:
109            metadata['install_requires'] = ['mpi4py>=1.2.2']
110
111def config(prefix, dry_run=False):
112    log.info('PETSc: configure')
113    options = [
114        '--prefix=' + prefix,
115        'PETSC_ARCH='+os.environ['PETSC_ARCH'],
116        '--with-shared-libraries=1',
117        '--with-debugging=0',
118        '--with-c2html=0', # not needed
119        ]
120    if '--with-fc=0' in CONFIGURE_OPTIONS:
121        options.append('--with-sowing=0')
122    if '--with-mpi=0' not in CONFIGURE_OPTIONS:
123        try:
124            import mpi4py
125            conf = mpi4py.get_config()
126            mpicc  = conf.get('mpicc')
127            mpicxx = conf.get('mpicxx')
128            mpif90 = conf.get('mpif90')
129        except (ImportError, AttributeError):
130            mpicc  = os.environ.get('MPICC')  or find_executable('mpicc')
131            mpicxx = os.environ.get('MPICXX') or find_executable('mpicxx')
132            mpif90 = os.environ.get('MPIF90') or find_executable('mpif90')
133        if mpicc:
134            options.append('--with-cc='+mpicc)
135            if '--with-cxx=0' not in CONFIGURE_OPTIONS:
136                if mpicxx:
137                    options.append('--with-cxx='+mpicxx)
138                else:
139                    options.append('--with-cxx=0')
140            if '--with-fc=0' not in CONFIGURE_OPTIONS:
141                if mpif90:
142                    options.append('--with-fc='+mpif90)
143                else:
144                    options.append('--with-fc=0')
145                    options.append('--with-sowing=0')
146        else:
147            options.append('--with-mpi=0')
148    options.extend(CONFIGURE_OPTIONS)
149    #
150    log.info('configure options:')
151    for opt in options:
152        log.info(' '*4 + opt)
153    # Run PETSc configure
154    if dry_run: return
155    use_config_py = False
156    if use_config_py:
157        import configure
158        configure.petsc_configure(options)
159        import logger
160        logger.Logger.defaultLog = None
161    else:
162        python = sys.executable
163        command = [python, './configure'] + options
164        status = os.system(" ".join(command))
165        if status != 0: raise RuntimeError(status)
166
167def build(dry_run=False):
168    log.info('PETSc: build')
169    # Run PETSc build
170    if dry_run: return
171    use_builder_py = False
172    if use_builder_py:
173        import builder
174        builder.PETScMaker().run()
175        import logger
176        logger.Logger.defaultLog = None
177    else:
178        make = find_executable('make')
179        command = [make, 'all']
180        status = os.system(" ".join(command))
181        if status != 0: raise RuntimeError(status)
182
183def install(dry_run=False):
184    log.info('PETSc: install')
185    # Run PETSc installer
186    if dry_run: return
187    use_install_py = False
188    if use_install_py:
189        import install
190        install.Installer().run()
191        import logger
192        logger.Logger.defaultLog = None
193    else:
194        make = find_executable('make')
195        command = [make, 'install']
196        status = os.system(" ".join(command))
197        if status != 0: raise RuntimeError(status)
198
199class context(object):
200    def __init__(self):
201        self.sys_argv = sys.argv[:]
202        self.wdir = os.getcwd()
203    def enter(self):
204        del sys.argv[1:]
205        pdir = os.environ['PETSC_DIR']
206        os.chdir(pdir)
207        return self
208    def exit(self):
209        sys.argv[:] = self.sys_argv
210        os.chdir(self.wdir)
211
212class cmd_install(_install):
213
214    def initialize_options(self):
215        _install.initialize_options(self)
216        self.optimize = 1
217
218    def finalize_options(self):
219        _install.finalize_options(self)
220        self.install_lib = self.install_platlib
221        self.install_libbase = self.install_lib
222        self.old_and_unmanageable = True
223
224    def run(self):
225        root_dir = os.path.abspath(self.install_lib)
226        prefix = os.path.join(root_dir, 'petsc')
227        #
228        ctx = context().enter()
229        try:
230            config(prefix, self.dry_run)
231            build(self.dry_run)
232            install(self.dry_run)
233        finally:
234            ctx.exit()
235        #
236        self.outputs = []
237        for dirpath, _, filenames in os.walk(prefix):
238            for fn in filenames:
239                self.outputs.append(os.path.join(dirpath, fn))
240        #
241        _install.run(self)
242
243    def get_outputs(self):
244        outputs = getattr(self, 'outputs', [])
245        outputs += _install.get_outputs(self)
246        return outputs
247
248def version():
249    import re
250    version_re = {
251        'major'  : re.compile(r"#define\s+PETSC_VERSION_MAJOR\s+(\d+)"),
252        'minor'  : re.compile(r"#define\s+PETSC_VERSION_MINOR\s+(\d+)"),
253        'micro'  : re.compile(r"#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)"),
254        'release': re.compile(r"#define\s+PETSC_VERSION_RELEASE\s+([-]*\d+)"),
255        }
256    petscversion_h = os.path.join('include','petscversion.h')
257    data = open(petscversion_h, 'r').read()
258    major = int(version_re['major'].search(data).groups()[0])
259    minor = int(version_re['minor'].search(data).groups()[0])
260    micro = int(version_re['micro'].search(data).groups()[0])
261    release = int(version_re['release'].search(data).groups()[0])
262    if release:
263        v = "%d.%d.%d" % (major, minor, micro)
264    else:
265        v = "%d.%d.0.dev%d" % (major, minor+1, 0)
266    return v
267
268def tarball():
269    VERSION = version()
270    if '.dev' in VERSION: return None
271    return ('https://web.cels.anl.gov/projects/petsc/download/release-snapshots/'
272            'petsc-%s.tar.gz#egg=petsc-%s' % (VERSION, VERSION))
273
274description = __doc__.split('\n')[1:-1]; del description[1:3]
275classifiers = """
276Development Status :: 5 - Production/Stable
277Intended Audience :: Developers
278Intended Audience :: Science/Research
279License :: OSI Approved :: BSD License
280Operating System :: POSIX
281Programming Language :: C
282Programming Language :: C++
283Programming Language :: Fortran
284Programming Language :: Python
285Topic :: Scientific/Engineering
286Topic :: Software Development :: Libraries
287"""
288
289if 'bdist_wheel' in sys.argv:
290    sys.stderr.write("petsc: this package cannot be built as a wheel\n")
291    sys.exit(1)
292
293bootstrap()
294setup(name='petsc',
295      version=version(),
296      description=description.pop(0),
297      long_description='\n'.join(description),
298      classifiers= classifiers.split('\n')[1:-1],
299      keywords = ['PETSc', 'MPI'],
300      platforms=['POSIX'],
301      license='BSD',
302
303      url='https://petsc.org/',
304      download_url=tarball(),
305
306      author='PETSc Team',
307      author_email='petsc-maint@mcs.anl.gov',
308      maintainer='Lisandro Dalcin',
309      maintainer_email='dalcinl@gmail.com',
310
311      packages = ['petsc'],
312      package_dir = {'petsc': 'config/pypi'},
313      cmdclass={'install': cmd_install},
314      **metadata)
315