File: setup.py

package info (click to toggle)
pykdtree 1.4.3%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 248 kB
  • sloc: python: 500; makefile: 18
file content (227 lines) | stat: -rw-r--r-- 7,850 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#pykdtree, Fast kd-tree implementation with OpenMP-enabled queries
#
#Copyright (C) 2013 - present  Esben S. Nielsen
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import re

import numpy as np
from Cython.Build import build_ext
from Cython.Distutils import Extension
from setuptools import setup
from setuptools.command.build_ext import build_ext


OMP_SETTING_TABLE = {
    '1': 'probe',
    '0': None,
    'gcc': 'gomp',
    'gomp': 'gomp',
    'clang': 'omp',
    'omp': 'omp',
    'msvc': 'msvc',
    'probe': 'probe'
}

OMP_COMPILE_ARGS = {
    'gomp': ['-fopenmp'],
    'omp': ['-Xpreprocessor', '-fopenmp'],
    'msvc': ['/openmp']
}

OMP_LINK_ARGS = {
    'gomp': ['-lgomp'],
    'omp': ['-lomp'],
    'msvc': []
}


class build_ext_subclass(build_ext):
    """Custom extension building to have platform and compiler specific flags."""

    def build_extensions(self):
        comp = self.compiler.compiler_type
        omp_comp, omp_link = _omp_compile_link_args(comp)
        if comp in ('unix', 'cygwin', 'mingw32'):
            extra_compile_args = ['-std=c17', '-O3'] + omp_comp
            extra_link_args = omp_link
        elif comp == 'msvc':
            extra_compile_args = ['/Ox'] + omp_comp
            extra_link_args = omp_link
        else:
            # Add support for more compilers here
            raise ValueError('Compiler flags undefined for %s. Please modify setup.py and add compiler flags'
                             % comp)
        self.extensions[0].extra_compile_args = extra_compile_args
        self.extensions[0].extra_link_args = extra_link_args
        build_ext.build_extensions(self)


def _omp_compile_link_args(compiler):
    # Get OpenMP setting from environment
    use_omp = OMP_SETTING_TABLE[os.environ.get('USE_OMP', 'probe')]

    compile_args = []
    link_args = []
    if use_omp == "probe":
        use_omp, compile_args, link_args = _probe_omp_for_compiler_and_platform(compiler)

    print(f"Will use {use_omp} for OpenMP." if use_omp else "OpenMP support not available.")
    compile_args += OMP_COMPILE_ARGS.get(use_omp, [])
    link_args += OMP_LINK_ARGS.get(use_omp, [])
    print(f"Compiler: {compiler} / OpenMP: {use_omp} / OpenMP compile args: {compile_args} / OpenMP link args: {link_args}")
    return compile_args, link_args


def _probe_omp_for_compiler_and_platform(compiler):
    compile_args = []
    link_args = []
    if compiler == "msvc":
        use_omp = "msvc"
    elif _is_conda_interpreter():
        # Conda provides its own compiler which does support openmp
        use_omp = "gomp"
    elif _is_macOS():
        # OpenMP is not supported with system clang but homebrew and macports have libomp packages
        compile_args, link_args = _macOS_omp_options_from_probe()
        if not (compile_args or link_args):
            print("Probe for libomp failed, skipping use of OpenMP with clang.")
            print("It may be possible to build with OpenMP using USE_OMP=clang with CFLAGS and LDFLAGS explicit settings to use libomp.")
            use_omp = None
        else:
            use_omp = "omp"
    else:
        use_omp = "gomp"
    return use_omp, compile_args, link_args


def _is_conda_interpreter():
    """Is the running interpreter from Anaconda or miniconda?

    See https://stackoverflow.com/a/21318941/433202

    Examples::

        2.7.6 |Anaconda 1.8.0 (x86_64)| (default, Jan 10 2014, 11:23:15)
        2.7.6 |Continuum Analytics, Inc.| (default, Jan 10 2014, 11:23:15)
        3.6.6 | packaged by conda-forge | (default, Jul 26 2018, 09:55:02)

    """
    return 'conda' in sys.version or 'Continuum' in sys.version


def _is_macOS():
    return 'darwin' in sys.platform


def _macOS_omp_options_from_probe():
    """Get common include and library paths for libomp installation on macOS.

    For example ``(['-I/opt/local/include/libomp'], ['-L/opt/local/lib/libomp'])``.

    """
    for cmd in ["brew ls --verbose libomp", "port contents libomp"]:
        inc, lib = _compile_link_paths_from_manifest(cmd)
        if inc and lib:
            return [f"-I{inc}"], [f"-L{lib}"]
    return [], []    


def _compile_link_paths_from_manifest(cmd):
    """Parse include and library paths from OSX package managers.

    Example executions::

        # Homebrew
        $ brew ls --verbose libomp
        /opt/homebrew/Cellar/libomp/15.0.7/INSTALL_RECEIPT.json
        /opt/homebrew/Cellar/libomp/15.0.7/.brew/libomp.rb
        /opt/homebrew/Cellar/libomp/15.0.7/include/ompt.h
        /opt/homebrew/Cellar/libomp/15.0.7/include/omp.h
        /opt/homebrew/Cellar/libomp/15.0.7/include/omp-tools.h
        /opt/homebrew/Cellar/libomp/15.0.7/lib/libomp.dylib
        /opt/homebrew/Cellar/libomp/15.0.7/lib/libomp.a

        # MacPorts
        $ port contents libomp
        Port libomp contains:
          /opt/local/include/libomp/omp-tools.h
          /opt/local/include/libomp/omp.h
          /opt/local/include/libomp/ompt.h
          /opt/local/lib/libomp/libgomp.dylib
          /opt/local/lib/libomp/libiomp5.dylib
          /opt/local/lib/libomp/libomp.dylib
          /opt/local/share/doc/libomp/LICENSE.TXT
          /opt/local/share/doc/libomp/README.txt

    """
    from subprocess import run
    query = run(cmd, shell=True, check=False, capture_output=True)
    if query.returncode != 0:
        return None, None
    manifest = query.stdout.decode("UTF-8")
    # find all the unique directories mentioned in the manifest
    dirs = set(os.path.split(filename)[0] for filename in re.findall(r'^\s*(/.*?)\s*$', manifest, re.MULTILINE))
    # find a unique libdir and incdir
    inc = tuple(d for d in dirs if re.search(r'/include(\W|$)', d))
    lib = tuple(d for d in dirs if re.search(r'/lib(\W|$)', d))
    # only return success if there's no ambiguity
    return (inc + lib) if len(inc) == 1 and len(lib) == 1 else (None, None)


with open('README.rst', 'r') as readme_file:
    readme = readme_file.read()

extensions = [
    Extension('pykdtree.kdtree', sources=['pykdtree/kdtree.pyx', 'pykdtree/_kdtree_core.c'],
              include_dirs=[np.get_include()],
              define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_25_API_VERSION")],
              cython_directives={"language_level": "3"},
              ),
]


setup(
    name='pykdtree',
    version='1.4.3',
    url="https://github.com/storpipfugl/pykdtree",
    description='Fast kd-tree implementation with OpenMP-enabled queries',
    long_description=readme,
    long_description_content_type="text/x-rst",
    author='Esben S. Nielsen',
    author_email='storpipfugl@gmail.com',
    packages=['pykdtree'],
    python_requires='>=3.9',
    install_requires=['numpy'],
    extras_require={
        'test': ['pytest', 'mypy'],
    },
    zip_safe=False,
    ext_modules=extensions,
    cmdclass={'build_ext': build_ext_subclass},
    license="LGPL-3.0-or-later",
    license_files=["LICENSE.txt"],
    classifiers=[
      'Development Status :: 5 - Production/Stable',
      'Programming Language :: Python',
      'Operating System :: OS Independent',
      'Intended Audience :: Science/Research',
      'Topic :: Scientific/Engineering',
      'Programming Language :: Python :: Free Threading :: 3 - Stable',
      ]
    )