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