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