xref: /petsc/setup.py (revision 98d129c30f3ee9fdddc40fdbc5a989b7be64f888)
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