platform.py 7.25 KB
Newer Older
dugupeiwen's avatar
dugupeiwen committed
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
import setuptools
from setuptools.command.build_ext import build_ext
from setuptools.dist import Distribution
import numpy as np

import functools
import os
import subprocess
import sys
from tempfile import mkdtemp
from contextlib import contextmanager
from pathlib import Path

# Wire in distutils components from setuptools
CCompiler =  setuptools.distutils.ccompiler.CCompiler
new_compiler =  setuptools.distutils.ccompiler.new_compiler
customize_compiler = setuptools.distutils.sysconfig.customize_compiler
log = setuptools.distutils.log


_configs = {
    # DLL suffix, Python C extension suffix
    'win': ('.dll', '.pyd'),
    'default': ('.so', '.so'),
}


def get_configs(arg):
    return _configs.get(sys.platform[:3], _configs['default'])[arg]


find_shared_ending = functools.partial(get_configs, 0)
find_pyext_ending = functools.partial(get_configs, 1)

@contextmanager
def _gentmpfile(suffix):
    # windows locks the tempfile so use a tempdir + file, see
    # https://github.com/numba/numba/issues/3304
    try:
        tmpdir = mkdtemp()
        ntf = open(os.path.join(tmpdir, "temp%s" % suffix), 'wt')
        yield ntf
    finally:
        try:
            ntf.close()
            os.remove(ntf)
        except:
            pass
        else:
            os.rmdir(tmpdir)


@functools.lru_cache(maxsize=1)
def external_compiler_works():
    """
    Returns True if the "external compiler" bound in numpy.distutil is present
    and working, False otherwise.
    """
    compiler = new_compiler()
    customize_compiler(compiler)
    for suffix in ['.c', '.cxx']:
        try:
            with _gentmpfile(suffix) as ntf:
                simple_c = "int main(void) { return 0; }"
                ntf.write(simple_c)
                ntf.flush()
                ntf.close()
                # *output_dir* is set to avoid the compiler putting temp files
                # in the current directory.
                compiler.compile([ntf.name], output_dir=Path(ntf.name).anchor)
        except Exception: # likely CompileError or file system issue
            return False
    return True


class _DummyExtension(object):
    libraries = []


class Toolchain(object):

    def __init__(self):
        if not external_compiler_works():
            self._raise_external_compiler_error()

        self._verbose = False
        self._compiler = new_compiler()
        customize_compiler(self._compiler)
        self._build_ext = build_ext(Distribution())
        self._build_ext.finalize_options()
        self._py_lib_dirs = self._build_ext.library_dirs
        self._py_include_dirs = self._build_ext.include_dirs
        np_compile_args = {'include_dirs': [np.get_include(),],}
        if sys.platform == 'win32':
            np_compile_args['libraries'] = []
        else:
            np_compile_args['libraries'] = ['m',]
        self._math_info = np_compile_args

    @property
    def verbose(self):
        return self._verbose

    @verbose.setter
    def verbose(self, value):
        self._verbose = value
        # DEBUG will let Numpy spew many messages, so stick to INFO
        # to print commands executed by distutils
        log.set_threshold(log.INFO if value else log.WARN)

    def _raise_external_compiler_error(self):
        basemsg = ("Attempted to compile AOT function without the "
                   "compiler used by `numpy.distutils` present.")
        conda_msg = "If using conda try:\n\n#> conda install %s"
        plt = sys.platform
        if plt.startswith('linux'):
            if sys.maxsize <= 2 ** 32:
                compilers = ['gcc_linux-32', 'gxx_linux-32']
            else:
                compilers = ['gcc_linux-64', 'gxx_linux-64']
            msg = "%s %s" % (basemsg, conda_msg % ' '.join(compilers))
        elif plt.startswith('darwin'):
            compilers = ['clang_osx-64', 'clangxx_osx-64']
            msg = "%s %s" % (basemsg, conda_msg % ' '.join(compilers))
        elif plt.startswith('win32'):
            winmsg = "Cannot find suitable msvc."
            msg = "%s %s" % (basemsg, winmsg)
        else:
            msg = "Unknown platform %s" % plt
        raise RuntimeError(msg)

    def compile_objects(self, sources, output_dir,
                        include_dirs=(), depends=(), macros=(),
                        extra_cflags=None):
        """
        Compile the given source files into a separate object file each,
        all beneath the *output_dir*.  A list of paths to object files
        is returned.

        *macros* has the same format as in distutils: a list of 1- or 2-tuples.
        If a 1-tuple (name,), the given name is considered undefined by
        the C preprocessor.
        If a 2-tuple (name, value), the given name is expanded into the
        given value by the C preprocessor.
        """
        objects = self._compiler.compile(sources,
                                         output_dir=output_dir,
                                         include_dirs=include_dirs,
                                         depends=depends,
                                         macros=macros or [],
                                         extra_preargs=extra_cflags)
        return objects

    def link_shared(self, output, objects, libraries=(),
                    library_dirs=(), export_symbols=(),
                    extra_ldflags=None):
        """
        Create a shared library *output* linking the given *objects*
        and *libraries* (all strings).
        """
        output_dir, output_filename = os.path.split(output)
        self._compiler.link(CCompiler.SHARED_OBJECT, objects,
                            output_filename, output_dir,
                            libraries, library_dirs,
                            export_symbols=export_symbols,
                            extra_preargs=extra_ldflags)

    def get_python_libraries(self):
        """
        Get the library arguments necessary to link with Python.
        """
        libs = self._build_ext.get_libraries(_DummyExtension())
        if sys.platform == 'win32':
            # Under Windows, need to link explicitly against the CRT,
            # as the MSVC compiler would implicitly do.
            # (XXX msvcrtd in pydebug mode?)
            libs = libs + ['msvcrt']
        return libs + self._math_info['libraries']

    def get_python_library_dirs(self):
        """
        Get the library directories necessary to link with Python.
        """
        return list(self._py_lib_dirs)

    def get_python_include_dirs(self):
        """
        Get the include directories necessary to compile against the Python
        and Numpy C APIs.
        """
        return list(self._py_include_dirs) + self._math_info['include_dirs']

    def get_ext_filename(self, ext_name):
        """
        Given a C extension's module name, return its intended filename.
        """
        return self._build_ext.get_ext_filename(ext_name)


def _quote_arg(arg):
    """
    Quote the argument for safe use in a shell command line.
    """
    # If there is a quote in the string, assume relevants parts of the
    # string are already quoted (e.g. '-I"C:\\Program Files\\..."')
    if '"' not in arg and ' ' in arg:
        return '"%s"' % arg
    return arg


def _is_sequence(arg):
    if isinstance(arg, (str, bytes)):
        return False
    try:
        len(arg)
        return True
    except Exception:
        return False