1#!/usr/bin/env python
2# Copyright (C) 2003-2020  CAMP
3# Please see the accompanying LICENSE file for further information.
4
5import os
6import re
7import sys
8from pathlib import Path
9from subprocess import PIPE, run
10from sysconfig import get_platform
11
12from setuptools import Extension, find_packages, setup
13from setuptools.command.build_ext import build_ext as _build_ext
14from setuptools.command.develop import develop as _develop
15from setuptools.command.install import install as _install
16
17from config import build_interpreter, check_dependencies, write_configuration
18
19assert sys.version_info >= (3, 6)
20
21# Get the current version number:
22txt = Path('gpaw/__init__.py').read_text()
23version = re.search("__version__ = '(.*)'", txt)[1]
24ase_version_required = re.search("__ase_version_required__ = '(.*)'", txt)[1]
25
26description = 'GPAW: DFT and beyond within the projector-augmented wave method'
27long_description = Path('README.rst').read_text()
28
29remove_default_flags = False
30if '--remove-default-flags' in sys.argv:
31    remove_default_flags = True
32    sys.argv.remove('--remove-default-flags')
33
34for i, arg in enumerate(sys.argv):
35    if arg.startswith('--customize='):
36        custom = arg.split('=')[1]
37        raise DeprecationWarning(
38            f'Please set GPAW_CONFIG={custom} or place {custom} in ' +
39            '~/.gpaw/siteconfig.py')
40
41libraries = ['xc']
42library_dirs = []
43include_dirs = []
44extra_link_args = []
45extra_compile_args = ['-Wall', '-Wno-unknown-pragmas', '-std=c99']
46runtime_library_dirs = []
47extra_objects = []
48define_macros = [('NPY_NO_DEPRECATED_API', '7'),
49                 ('GPAW_NO_UNDERSCORE_CBLACS', '1'),
50                 ('GPAW_NO_UNDERSCORE_CSCALAPACK', '1')]
51undef_macros = ['NDEBUG']
52
53mpi_libraries = []
54mpi_library_dirs = []
55mpi_include_dirs = []
56mpi_runtime_library_dirs = []
57mpi_define_macros = []
58
59parallel_python_interpreter = False
60compiler = None
61noblas = False
62nolibxc = False
63fftw = False
64scalapack = False
65libvdwxc = False
66elpa = False
67
68if os.name != 'nt' and run(['which', 'mpicc'], stdout=PIPE).returncode == 0:
69    mpicompiler = 'mpicc'
70else:
71    mpicompiler = None
72
73mpilinker = mpicompiler
74
75# Search and store current git hash if possible
76try:
77    from ase.utils import search_current_git_hash
78    githash = search_current_git_hash('gpaw')
79    if githash is not None:
80        define_macros += [('GPAW_GITHASH', githash)]
81    else:
82        print('.git directory not found. GPAW git hash not written.')
83except ImportError:
84    print('ASE not found. GPAW git hash not written.')
85
86# User provided customizations:
87gpaw_config = os.environ.get('GPAW_CONFIG')
88if gpaw_config and not Path(gpaw_config).is_file():
89    raise FileNotFoundError(gpaw_config)
90for siteconfig in [gpaw_config,
91                   'siteconfig.py',
92                   '~/.gpaw/siteconfig.py']:
93    if siteconfig is not None:
94        path = Path(siteconfig).expanduser()
95        if path.is_file():
96            print('Reading configuration from', path)
97            exec(path.read_text())
98            break
99else:  # no break
100    if not noblas:
101        libraries.append('blas')
102
103if not parallel_python_interpreter and mpicompiler:
104    # Build MPI-interface into _gpaw.so:
105    compiler = mpicompiler
106    define_macros += [('PARALLEL', '1')]
107
108platform_id = os.getenv('CPU_ARCH')
109if platform_id:
110    os.environ['_PYTHON_HOST_PLATFORM'] = get_platform() + '-' + platform_id
111
112if compiler is not None:
113    # A hack to change the used compiler and linker:
114    try:
115        # distutils is deprecated and will be removed in 3.12
116        from distutils.sysconfig import get_config_vars
117    except ImportError:
118        from sysconfig import get_config_vars
119    vars = get_config_vars()
120    if remove_default_flags:
121        for key in ['BASECFLAGS', 'CFLAGS', 'OPT', 'PY_CFLAGS',
122                    'CCSHARED', 'CFLAGSFORSHARED', 'LINKFORSHARED',
123                    'LIBS', 'SHLIBS']:
124            if key in vars:
125                value = vars[key].split()
126                # remove all gcc flags (causing problems with other compilers)
127                for v in list(value):
128                    value.remove(v)
129                vars[key] = ' '.join(value)
130    for key in ['CC', 'LDSHARED']:
131        if key in vars:
132            value = vars[key].split()
133            # first argument is the compiler/linker.  Replace with mpicompiler:
134            value[0] = compiler
135            vars[key] = ' '.join(value)
136
137for flag, name in [(noblas, 'GPAW_WITHOUT_BLAS'),
138                   (nolibxc, 'GPAW_WITHOUT_LIBXC'),
139                   (fftw, 'GPAW_WITH_FFTW'),
140                   (scalapack, 'GPAW_WITH_SL'),
141                   (libvdwxc, 'GPAW_WITH_LIBVDWXC'),
142                   (elpa, 'GPAW_WITH_ELPA')]:
143    if flag:
144        define_macros.append((name, '1'))
145
146sources = [Path('c/bmgs/bmgs.c')]
147sources += Path('c').glob('*.c')
148sources += Path('c/xc').glob('*.c')
149if nolibxc:
150    for name in ['libxc.c', 'm06l.c',
151                 'tpss.c', 'revtpss.c', 'revtpss_c_pbe.c',
152                 'xc_mgga.c']:
153        sources.remove(Path(f'c/xc/{name}'))
154# Make build process deterministic (for "reproducible build")
155sources = [str(source) for source in sources]
156sources.sort()
157
158check_dependencies(sources)
159
160# Convert Path objects to str:
161library_dirs = [str(dir) for dir in library_dirs]
162include_dirs = [str(dir) for dir in include_dirs]
163
164extensions = [Extension('_gpaw',
165                        sources,
166                        libraries=libraries,
167                        library_dirs=library_dirs,
168                        include_dirs=include_dirs,
169                        define_macros=define_macros,
170                        undef_macros=undef_macros,
171                        extra_link_args=extra_link_args,
172                        extra_compile_args=extra_compile_args,
173                        runtime_library_dirs=runtime_library_dirs,
174                        extra_objects=extra_objects)]
175
176write_configuration(define_macros, include_dirs, libraries, library_dirs,
177                    extra_link_args, extra_compile_args,
178                    runtime_library_dirs, extra_objects, mpicompiler,
179                    mpi_libraries, mpi_library_dirs, mpi_include_dirs,
180                    mpi_runtime_library_dirs, mpi_define_macros)
181
182
183class build_ext(_build_ext):
184    def run(self):
185        import numpy as np
186        self.include_dirs.append(np.get_include())
187
188        _build_ext.run(self)
189
190        if parallel_python_interpreter:
191            include_dirs.append(np.get_include())
192            # Also build gpaw-python:
193            error = build_interpreter(
194                define_macros, include_dirs, libraries,
195                library_dirs, extra_link_args, extra_compile_args,
196                runtime_library_dirs, extra_objects,
197                mpicompiler, mpilinker, mpi_libraries,
198                mpi_library_dirs,
199                mpi_include_dirs,
200                mpi_runtime_library_dirs, mpi_define_macros)
201            assert error == 0
202
203
204def copy_gpaw_python(cmd, dir: str) -> None:
205    major, minor = sys.version_info[:2]
206    plat = get_platform() + f'-{major}.{minor}'
207    source = f'build/bin.{plat}/gpaw-python'
208    target = os.path.join(dir, 'gpaw-python')
209    cmd.copy_file(source, target)
210
211
212class install(_install):
213    def run(self):
214        _install.run(self)
215        copy_gpaw_python(self, self.install_scripts)
216
217
218class develop(_develop):
219    def run(self):
220        _develop.run(self)
221        copy_gpaw_python(self, self.script_dir)
222
223
224cmdclass = {'build_ext': build_ext}
225if parallel_python_interpreter:
226    cmdclass['install'] = install
227    cmdclass['develop'] = develop
228
229files = ['gpaw-analyse-basis', 'gpaw-basis',
230         'gpaw-plot-parallel-timings', 'gpaw-runscript',
231         'gpaw-setup', 'gpaw-upfplot']
232scripts = [str(Path('tools') / script) for script in files]
233
234
235setup(name='gpaw',
236      version=version,
237      description=description,
238      long_description=long_description,
239      maintainer='GPAW-community',
240      maintainer_email='gpaw-users@listserv.fysik.dtu.dk',
241      url='https://wiki.fysik.dtu.dk/gpaw',
242      license='GPLv3+',
243      platforms=['unix'],
244      packages=find_packages(),
245      entry_points={'console_scripts': ['gpaw = gpaw.cli.main:main']},
246      setup_requires=['numpy'],
247      install_requires=[f'ase>={ase_version_required}',
248                        'scipy>=1.2.0'],
249      ext_modules=extensions,
250      scripts=scripts,
251      cmdclass=cmdclass,
252      classifiers=[
253          'Development Status :: 6 - Mature',
254          'License :: OSI Approved :: '
255          'GNU General Public License v3 or later (GPLv3+)',
256          'Operating System :: OS Independent',
257          'Programming Language :: Python :: 3',
258          'Programming Language :: Python :: 3.6',
259          'Programming Language :: Python :: 3.7',
260          'Programming Language :: Python :: 3.8',
261          'Programming Language :: Python :: 3.9',
262          'Topic :: Scientific/Engineering :: Physics'])
263