Chris@87
|
1 """
|
Chris@87
|
2 Utility functions for
|
Chris@87
|
3
|
Chris@87
|
4 - building and importing modules on test time, using a temporary location
|
Chris@87
|
5 - detecting if compilers are present
|
Chris@87
|
6
|
Chris@87
|
7 """
|
Chris@87
|
8 from __future__ import division, absolute_import, print_function
|
Chris@87
|
9
|
Chris@87
|
10 import os
|
Chris@87
|
11 import sys
|
Chris@87
|
12 import subprocess
|
Chris@87
|
13 import tempfile
|
Chris@87
|
14 import shutil
|
Chris@87
|
15 import atexit
|
Chris@87
|
16 import textwrap
|
Chris@87
|
17 import re
|
Chris@87
|
18 import random
|
Chris@87
|
19
|
Chris@87
|
20 import nose
|
Chris@87
|
21
|
Chris@87
|
22 from numpy.compat import asbytes, asstr
|
Chris@87
|
23 import numpy.f2py
|
Chris@87
|
24
|
Chris@87
|
25 try:
|
Chris@87
|
26 from hashlib import md5
|
Chris@87
|
27 except ImportError:
|
Chris@87
|
28 from md5 import new as md5
|
Chris@87
|
29
|
Chris@87
|
30 #
|
Chris@87
|
31 # Maintaining a temporary module directory
|
Chris@87
|
32 #
|
Chris@87
|
33
|
Chris@87
|
34 _module_dir = None
|
Chris@87
|
35
|
Chris@87
|
36 def _cleanup():
|
Chris@87
|
37 global _module_dir
|
Chris@87
|
38 if _module_dir is not None:
|
Chris@87
|
39 try:
|
Chris@87
|
40 sys.path.remove(_module_dir)
|
Chris@87
|
41 except ValueError:
|
Chris@87
|
42 pass
|
Chris@87
|
43 try:
|
Chris@87
|
44 shutil.rmtree(_module_dir)
|
Chris@87
|
45 except (IOError, OSError):
|
Chris@87
|
46 pass
|
Chris@87
|
47 _module_dir = None
|
Chris@87
|
48
|
Chris@87
|
49 def get_module_dir():
|
Chris@87
|
50 global _module_dir
|
Chris@87
|
51 if _module_dir is None:
|
Chris@87
|
52 _module_dir = tempfile.mkdtemp()
|
Chris@87
|
53 atexit.register(_cleanup)
|
Chris@87
|
54 if _module_dir not in sys.path:
|
Chris@87
|
55 sys.path.insert(0, _module_dir)
|
Chris@87
|
56 return _module_dir
|
Chris@87
|
57
|
Chris@87
|
58 def get_temp_module_name():
|
Chris@87
|
59 # Assume single-threaded, and the module dir usable only by this thread
|
Chris@87
|
60 d = get_module_dir()
|
Chris@87
|
61 for j in range(5403, 9999999):
|
Chris@87
|
62 name = "_test_ext_module_%d" % j
|
Chris@87
|
63 fn = os.path.join(d, name)
|
Chris@87
|
64 if name not in sys.modules and not os.path.isfile(fn+'.py'):
|
Chris@87
|
65 return name
|
Chris@87
|
66 raise RuntimeError("Failed to create a temporary module name")
|
Chris@87
|
67
|
Chris@87
|
68 def _memoize(func):
|
Chris@87
|
69 memo = {}
|
Chris@87
|
70 def wrapper(*a, **kw):
|
Chris@87
|
71 key = repr((a, kw))
|
Chris@87
|
72 if key not in memo:
|
Chris@87
|
73 try:
|
Chris@87
|
74 memo[key] = func(*a, **kw)
|
Chris@87
|
75 except Exception as e:
|
Chris@87
|
76 memo[key] = e
|
Chris@87
|
77 raise
|
Chris@87
|
78 ret = memo[key]
|
Chris@87
|
79 if isinstance(ret, Exception):
|
Chris@87
|
80 raise ret
|
Chris@87
|
81 return ret
|
Chris@87
|
82 wrapper.__name__ = func.__name__
|
Chris@87
|
83 return wrapper
|
Chris@87
|
84
|
Chris@87
|
85 #
|
Chris@87
|
86 # Building modules
|
Chris@87
|
87 #
|
Chris@87
|
88
|
Chris@87
|
89 @_memoize
|
Chris@87
|
90 def build_module(source_files, options=[], skip=[], only=[], module_name=None):
|
Chris@87
|
91 """
|
Chris@87
|
92 Compile and import a f2py module, built from the given files.
|
Chris@87
|
93
|
Chris@87
|
94 """
|
Chris@87
|
95
|
Chris@87
|
96 code = ("import sys; sys.path = %s; import numpy.f2py as f2py2e; "
|
Chris@87
|
97 "f2py2e.main()" % repr(sys.path))
|
Chris@87
|
98
|
Chris@87
|
99 d = get_module_dir()
|
Chris@87
|
100
|
Chris@87
|
101 # Copy files
|
Chris@87
|
102 dst_sources = []
|
Chris@87
|
103 for fn in source_files:
|
Chris@87
|
104 if not os.path.isfile(fn):
|
Chris@87
|
105 raise RuntimeError("%s is not a file" % fn)
|
Chris@87
|
106 dst = os.path.join(d, os.path.basename(fn))
|
Chris@87
|
107 shutil.copyfile(fn, dst)
|
Chris@87
|
108 dst_sources.append(dst)
|
Chris@87
|
109
|
Chris@87
|
110 fn = os.path.join(os.path.dirname(fn), '.f2py_f2cmap')
|
Chris@87
|
111 if os.path.isfile(fn):
|
Chris@87
|
112 dst = os.path.join(d, os.path.basename(fn))
|
Chris@87
|
113 if not os.path.isfile(dst):
|
Chris@87
|
114 shutil.copyfile(fn, dst)
|
Chris@87
|
115
|
Chris@87
|
116 # Prepare options
|
Chris@87
|
117 if module_name is None:
|
Chris@87
|
118 module_name = get_temp_module_name()
|
Chris@87
|
119 f2py_opts = ['-c', '-m', module_name] + options + dst_sources
|
Chris@87
|
120 if skip:
|
Chris@87
|
121 f2py_opts += ['skip:'] + skip
|
Chris@87
|
122 if only:
|
Chris@87
|
123 f2py_opts += ['only:'] + only
|
Chris@87
|
124
|
Chris@87
|
125 # Build
|
Chris@87
|
126 cwd = os.getcwd()
|
Chris@87
|
127 try:
|
Chris@87
|
128 os.chdir(d)
|
Chris@87
|
129 cmd = [sys.executable, '-c', code] + f2py_opts
|
Chris@87
|
130 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
Chris@87
|
131 stderr=subprocess.STDOUT)
|
Chris@87
|
132 out, err = p.communicate()
|
Chris@87
|
133 if p.returncode != 0:
|
Chris@87
|
134 raise RuntimeError("Running f2py failed: %s\n%s"
|
Chris@87
|
135 % (cmd[4:], asstr(out)))
|
Chris@87
|
136 finally:
|
Chris@87
|
137 os.chdir(cwd)
|
Chris@87
|
138
|
Chris@87
|
139 # Partial cleanup
|
Chris@87
|
140 for fn in dst_sources:
|
Chris@87
|
141 os.unlink(fn)
|
Chris@87
|
142
|
Chris@87
|
143 # Import
|
Chris@87
|
144 __import__(module_name)
|
Chris@87
|
145 return sys.modules[module_name]
|
Chris@87
|
146
|
Chris@87
|
147 @_memoize
|
Chris@87
|
148 def build_code(source_code, options=[], skip=[], only=[], suffix=None,
|
Chris@87
|
149 module_name=None):
|
Chris@87
|
150 """
|
Chris@87
|
151 Compile and import Fortran code using f2py.
|
Chris@87
|
152
|
Chris@87
|
153 """
|
Chris@87
|
154 if suffix is None:
|
Chris@87
|
155 suffix = '.f'
|
Chris@87
|
156
|
Chris@87
|
157 fd, tmp_fn = tempfile.mkstemp(suffix=suffix)
|
Chris@87
|
158 os.write(fd, asbytes(source_code))
|
Chris@87
|
159 os.close(fd)
|
Chris@87
|
160
|
Chris@87
|
161 try:
|
Chris@87
|
162 return build_module([tmp_fn], options=options, skip=skip, only=only,
|
Chris@87
|
163 module_name=module_name)
|
Chris@87
|
164 finally:
|
Chris@87
|
165 os.unlink(tmp_fn)
|
Chris@87
|
166
|
Chris@87
|
167 #
|
Chris@87
|
168 # Check if compilers are available at all...
|
Chris@87
|
169 #
|
Chris@87
|
170
|
Chris@87
|
171 _compiler_status = None
|
Chris@87
|
172 def _get_compiler_status():
|
Chris@87
|
173 global _compiler_status
|
Chris@87
|
174 if _compiler_status is not None:
|
Chris@87
|
175 return _compiler_status
|
Chris@87
|
176
|
Chris@87
|
177 _compiler_status = (False, False, False)
|
Chris@87
|
178
|
Chris@87
|
179 # XXX: this is really ugly. But I don't know how to invoke Distutils
|
Chris@87
|
180 # in a safer way...
|
Chris@87
|
181 code = """
|
Chris@87
|
182 import os
|
Chris@87
|
183 import sys
|
Chris@87
|
184 sys.path = %(syspath)s
|
Chris@87
|
185
|
Chris@87
|
186 def configuration(parent_name='',top_path=None):
|
Chris@87
|
187 global config
|
Chris@87
|
188 from numpy.distutils.misc_util import Configuration
|
Chris@87
|
189 config = Configuration('', parent_name, top_path)
|
Chris@87
|
190 return config
|
Chris@87
|
191
|
Chris@87
|
192 from numpy.distutils.core import setup
|
Chris@87
|
193 setup(configuration=configuration)
|
Chris@87
|
194
|
Chris@87
|
195 config_cmd = config.get_config_cmd()
|
Chris@87
|
196 have_c = config_cmd.try_compile('void foo() {}')
|
Chris@87
|
197 print('COMPILERS:%%d,%%d,%%d' %% (have_c,
|
Chris@87
|
198 config.have_f77c(),
|
Chris@87
|
199 config.have_f90c()))
|
Chris@87
|
200 sys.exit(99)
|
Chris@87
|
201 """
|
Chris@87
|
202 code = code % dict(syspath=repr(sys.path))
|
Chris@87
|
203
|
Chris@87
|
204 fd, script = tempfile.mkstemp(suffix='.py')
|
Chris@87
|
205 os.write(fd, asbytes(code))
|
Chris@87
|
206 os.close(fd)
|
Chris@87
|
207
|
Chris@87
|
208 try:
|
Chris@87
|
209 cmd = [sys.executable, script, 'config']
|
Chris@87
|
210 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
Chris@87
|
211 stderr=subprocess.STDOUT)
|
Chris@87
|
212 out, err = p.communicate()
|
Chris@87
|
213 m = re.search(asbytes(r'COMPILERS:(\d+),(\d+),(\d+)'), out)
|
Chris@87
|
214 if m:
|
Chris@87
|
215 _compiler_status = (bool(int(m.group(1))), bool(int(m.group(2))),
|
Chris@87
|
216 bool(int(m.group(3))))
|
Chris@87
|
217 finally:
|
Chris@87
|
218 os.unlink(script)
|
Chris@87
|
219
|
Chris@87
|
220 # Finished
|
Chris@87
|
221 return _compiler_status
|
Chris@87
|
222
|
Chris@87
|
223 def has_c_compiler():
|
Chris@87
|
224 return _get_compiler_status()[0]
|
Chris@87
|
225
|
Chris@87
|
226 def has_f77_compiler():
|
Chris@87
|
227 return _get_compiler_status()[1]
|
Chris@87
|
228
|
Chris@87
|
229 def has_f90_compiler():
|
Chris@87
|
230 return _get_compiler_status()[2]
|
Chris@87
|
231
|
Chris@87
|
232 #
|
Chris@87
|
233 # Building with distutils
|
Chris@87
|
234 #
|
Chris@87
|
235
|
Chris@87
|
236 @_memoize
|
Chris@87
|
237 def build_module_distutils(source_files, config_code, module_name, **kw):
|
Chris@87
|
238 """
|
Chris@87
|
239 Build a module via distutils and import it.
|
Chris@87
|
240
|
Chris@87
|
241 """
|
Chris@87
|
242 from numpy.distutils.misc_util import Configuration
|
Chris@87
|
243 from numpy.distutils.core import setup
|
Chris@87
|
244
|
Chris@87
|
245 d = get_module_dir()
|
Chris@87
|
246
|
Chris@87
|
247 # Copy files
|
Chris@87
|
248 dst_sources = []
|
Chris@87
|
249 for fn in source_files:
|
Chris@87
|
250 if not os.path.isfile(fn):
|
Chris@87
|
251 raise RuntimeError("%s is not a file" % fn)
|
Chris@87
|
252 dst = os.path.join(d, os.path.basename(fn))
|
Chris@87
|
253 shutil.copyfile(fn, dst)
|
Chris@87
|
254 dst_sources.append(dst)
|
Chris@87
|
255
|
Chris@87
|
256 # Build script
|
Chris@87
|
257 config_code = textwrap.dedent(config_code).replace("\n", "\n ")
|
Chris@87
|
258
|
Chris@87
|
259 code = """\
|
Chris@87
|
260 import os
|
Chris@87
|
261 import sys
|
Chris@87
|
262 sys.path = %(syspath)s
|
Chris@87
|
263
|
Chris@87
|
264 def configuration(parent_name='',top_path=None):
|
Chris@87
|
265 from numpy.distutils.misc_util import Configuration
|
Chris@87
|
266 config = Configuration('', parent_name, top_path)
|
Chris@87
|
267 %(config_code)s
|
Chris@87
|
268 return config
|
Chris@87
|
269
|
Chris@87
|
270 if __name__ == "__main__":
|
Chris@87
|
271 from numpy.distutils.core import setup
|
Chris@87
|
272 setup(configuration=configuration)
|
Chris@87
|
273 """ % dict(config_code=config_code, syspath = repr(sys.path))
|
Chris@87
|
274
|
Chris@87
|
275 script = os.path.join(d, get_temp_module_name() + '.py')
|
Chris@87
|
276 dst_sources.append(script)
|
Chris@87
|
277 f = open(script, 'wb')
|
Chris@87
|
278 f.write(asbytes(code))
|
Chris@87
|
279 f.close()
|
Chris@87
|
280
|
Chris@87
|
281 # Build
|
Chris@87
|
282 cwd = os.getcwd()
|
Chris@87
|
283 try:
|
Chris@87
|
284 os.chdir(d)
|
Chris@87
|
285 cmd = [sys.executable, script, 'build_ext', '-i']
|
Chris@87
|
286 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
Chris@87
|
287 stderr=subprocess.STDOUT)
|
Chris@87
|
288 out, err = p.communicate()
|
Chris@87
|
289 if p.returncode != 0:
|
Chris@87
|
290 raise RuntimeError("Running distutils build failed: %s\n%s"
|
Chris@87
|
291 % (cmd[4:], asstr(out)))
|
Chris@87
|
292 finally:
|
Chris@87
|
293 os.chdir(cwd)
|
Chris@87
|
294
|
Chris@87
|
295 # Partial cleanup
|
Chris@87
|
296 for fn in dst_sources:
|
Chris@87
|
297 os.unlink(fn)
|
Chris@87
|
298
|
Chris@87
|
299 # Import
|
Chris@87
|
300 __import__(module_name)
|
Chris@87
|
301 return sys.modules[module_name]
|
Chris@87
|
302
|
Chris@87
|
303 #
|
Chris@87
|
304 # Unittest convenience
|
Chris@87
|
305 #
|
Chris@87
|
306
|
Chris@87
|
307 class F2PyTest(object):
|
Chris@87
|
308 code = None
|
Chris@87
|
309 sources = None
|
Chris@87
|
310 options = []
|
Chris@87
|
311 skip = []
|
Chris@87
|
312 only = []
|
Chris@87
|
313 suffix = '.f'
|
Chris@87
|
314 module = None
|
Chris@87
|
315 module_name = None
|
Chris@87
|
316
|
Chris@87
|
317 def setUp(self):
|
Chris@87
|
318 if self.module is not None:
|
Chris@87
|
319 return
|
Chris@87
|
320
|
Chris@87
|
321 # Check compiler availability first
|
Chris@87
|
322 if not has_c_compiler():
|
Chris@87
|
323 raise nose.SkipTest("No C compiler available")
|
Chris@87
|
324
|
Chris@87
|
325 codes = []
|
Chris@87
|
326 if self.sources:
|
Chris@87
|
327 codes.extend(self.sources)
|
Chris@87
|
328 if self.code is not None:
|
Chris@87
|
329 codes.append(self.suffix)
|
Chris@87
|
330
|
Chris@87
|
331 needs_f77 = False
|
Chris@87
|
332 needs_f90 = False
|
Chris@87
|
333 for fn in codes:
|
Chris@87
|
334 if fn.endswith('.f'):
|
Chris@87
|
335 needs_f77 = True
|
Chris@87
|
336 elif fn.endswith('.f90'):
|
Chris@87
|
337 needs_f90 = True
|
Chris@87
|
338 if needs_f77 and not has_f77_compiler():
|
Chris@87
|
339 raise nose.SkipTest("No Fortran 77 compiler available")
|
Chris@87
|
340 if needs_f90 and not has_f90_compiler():
|
Chris@87
|
341 raise nose.SkipTest("No Fortran 90 compiler available")
|
Chris@87
|
342
|
Chris@87
|
343 # Build the module
|
Chris@87
|
344 if self.code is not None:
|
Chris@87
|
345 self.module = build_code(self.code, options=self.options,
|
Chris@87
|
346 skip=self.skip, only=self.only,
|
Chris@87
|
347 suffix=self.suffix,
|
Chris@87
|
348 module_name=self.module_name)
|
Chris@87
|
349
|
Chris@87
|
350 if self.sources is not None:
|
Chris@87
|
351 self.module = build_module(self.sources, options=self.options,
|
Chris@87
|
352 skip=self.skip, only=self.only,
|
Chris@87
|
353 module_name=self.module_name)
|