-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathplatform.py
218 lines (187 loc) · 7.25 KB
/
platform.py
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