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