xref: /petsc/setup.py (revision 2ff79c18c26c94ed8cb599682f680f231dca6444)
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, dry_run=False):
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    if dry_run:
161        return
162    use_config_py = False
163    if use_config_py:
164        import configure
165        configure.petsc_configure(options)
166        import logger
167        logger.Logger.defaultLog = None
168    else:
169        python = sys.executable
170        command = [python, './configure'] + options
171        status = os.system(" ".join(command))
172        if status != 0:
173            raise RuntimeError(status)
174
175    # Fix PETSc configuration
176    using_build_backend = any(
177        os.environ.get(prefix + '_BUILD_BACKEND')
178        for prefix in ('_PYPROJECT_HOOKS', 'PEP517')
179    )
180    if using_build_backend:
181        pdir = os.environ['PETSC_DIR']
182        parch = os.environ['PETSC_ARCH']
183        include = os.path.join(pdir, parch, 'include')
184        for filename in (
185            'petscconf.h',
186            'petscconfiginfo.h',
187            'petscmachineinfo.h',
188        ):
189            filename = os.path.join(include, filename)
190            with open(filename, 'r') as old_fh:
191                contents = old_fh.read()
192            contents = contents.replace(prefix, '${PETSC_DIR}')
193            contents = re.sub(
194                r'^(#define PETSC_PYTHON_EXE) "(.*)"$',
195                r'\1 "python%d"' % sys.version_info[0],
196                contents, flags=re.MULTILINE,
197            )
198            with open(filename, 'w') as new_fh:
199                new_fh.write(contents)
200
201
202def build(dry_run=False):
203    log.info('PETSc: build')
204    # Run PETSc build
205    if dry_run:
206        return
207    use_builder_py = False
208    if use_builder_py:
209        import builder
210        builder.PETScMaker().run()
211        import logger
212        logger.Logger.defaultLog = None
213    else:
214        make = shutil.which('make')
215        command = [make, 'all']
216        status = os.system(" ".join(command))
217        if status != 0:
218            raise RuntimeError(status)
219
220
221def install(dry_run=False):
222    log.info('PETSc: install')
223    # Run PETSc installer
224    if dry_run:
225        return
226    use_install_py = False
227    if use_install_py:
228        import install
229        install.Installer().run()
230        import logger
231        logger.Logger.defaultLog = None
232    else:
233        make = shutil.which('make')
234        command = [make, 'install']
235        status = os.system(" ".join(command))
236        if status != 0:
237            raise RuntimeError(status)
238
239
240class context(object):
241    def __init__(self):
242        self.sys_argv = sys.argv[:]
243        self.wdir = os.getcwd()
244    def enter(self):
245        del sys.argv[1:]
246        pdir = os.environ['PETSC_DIR']
247        os.chdir(pdir)
248        return self
249    def exit(self):
250        sys.argv[:] = self.sys_argv
251        os.chdir(self.wdir)
252
253
254class cmd_install(_install):
255
256    def initialize_options(self):
257        _install.initialize_options(self)
258
259    def finalize_options(self):
260        _install.finalize_options(self)
261        self.install_lib = self.install_platlib
262        self.install_libbase = self.install_lib
263        self.old_and_unmanageable = True
264
265    def run(self):
266        root_dir = os.path.abspath(self.install_lib)
267        prefix = os.path.join(root_dir, 'petsc')
268        #
269        ctx = context().enter()
270        try:
271            config(prefix, self.dry_run)
272            build(self.dry_run)
273            install(self.dry_run)
274        finally:
275            ctx.exit()
276        #
277        self.outputs = []
278        for dirpath, _, filenames in os.walk(prefix):
279            for fn in filenames:
280                self.outputs.append(os.path.join(dirpath, fn))
281        #
282        _install.run(self)
283
284    def get_outputs(self):
285        outputs = getattr(self, 'outputs', [])
286        outputs += _install.get_outputs(self)
287        return outputs
288
289
290class cmd_bdist_wheel(_bdist_wheel):
291
292    def finalize_options(self):
293        super().finalize_options()
294        self.root_is_pure = False
295        self.build_number = None
296        # self.keep_temp = True
297
298    def get_tag(self):
299        plat_tag = super().get_tag()[-1]
300        return (self.python_tag, "none", plat_tag)
301
302
303def version():
304    version_re = {
305        'major'  : re.compile(r"#define\s+PETSC_VERSION_MAJOR\s+(\d+)"),
306        'minor'  : re.compile(r"#define\s+PETSC_VERSION_MINOR\s+(\d+)"),
307        'micro'  : re.compile(r"#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)"),
308        'release': re.compile(r"#define\s+PETSC_VERSION_RELEASE\s+([-]*\d+)"),
309        }
310    petscversion_h = os.path.join('include','petscversion.h')
311    data = open(petscversion_h, 'r').read()
312    major = int(version_re['major'].search(data).groups()[0])
313    minor = int(version_re['minor'].search(data).groups()[0])
314    micro = int(version_re['micro'].search(data).groups()[0])
315    release = int(version_re['release'].search(data).groups()[0])
316    if release:
317        v = "%d.%d.%d" % (major, minor, micro)
318    else:
319        v = "%d.%d.0.dev%d" % (major, minor+1, 0)
320    return v
321
322
323def tarball():
324    VERSION = version()
325    if '.dev' in VERSION:
326        return None
327    return ('https://web.cels.anl.gov/projects/petsc/download/release-snapshots/'
328            'petsc-%s.tar.gz#egg=petsc-%s' % (VERSION, VERSION))
329
330
331description = __doc__.split('\n')[1:-1]
332del description[1:3]
333
334classifiers = """
335Development Status :: 5 - Production/Stable
336Intended Audience :: Developers
337Intended Audience :: Science/Research
338Operating System :: POSIX
339Programming Language :: C
340Programming Language :: C++
341Programming Language :: Fortran
342Programming Language :: Python
343Programming Language :: Python :: 3
344Topic :: Scientific/Engineering
345Topic :: Software Development :: Libraries
346"""
347
348bootstrap()
349setup(
350    name='petsc',
351    version=version(),
352    description=description.pop(0),
353    long_description='\n'.join(description),
354    long_description_content_type='text/x-rst',
355    classifiers=classifiers.split('\n')[1:-1],
356    keywords = ['PETSc', 'MPI'],
357    platforms=['POSIX'],
358    license='BSD-2-Clause',
359
360    url='https://petsc.org/',
361    download_url=tarball(),
362
363    author='PETSc Team',
364    author_email='petsc-maint@mcs.anl.gov',
365    maintainer='Lisandro Dalcin',
366    maintainer_email='dalcinl@gmail.com',
367
368    packages=['petsc'],
369    package_dir= {'petsc': 'config/pypi'},
370    cmdclass={
371        'install': cmd_install,
372        'bdist_wheel': cmd_bdist_wheel,
373    },
374    **metadata
375)
376