Chris@87
|
1 """ Build swig, f2py, pyrex sources.
|
Chris@87
|
2 """
|
Chris@87
|
3 from __future__ import division, absolute_import, print_function
|
Chris@87
|
4
|
Chris@87
|
5 import os
|
Chris@87
|
6 import re
|
Chris@87
|
7 import sys
|
Chris@87
|
8 import shlex
|
Chris@87
|
9 import copy
|
Chris@87
|
10
|
Chris@87
|
11 from distutils.command import build_ext
|
Chris@87
|
12 from distutils.dep_util import newer_group, newer
|
Chris@87
|
13 from distutils.util import get_platform
|
Chris@87
|
14 from distutils.errors import DistutilsError, DistutilsSetupError
|
Chris@87
|
15
|
Chris@87
|
16 def have_pyrex():
|
Chris@87
|
17 try:
|
Chris@87
|
18 import Pyrex.Compiler.Main
|
Chris@87
|
19 return True
|
Chris@87
|
20 except ImportError:
|
Chris@87
|
21 return False
|
Chris@87
|
22
|
Chris@87
|
23 # this import can't be done here, as it uses numpy stuff only available
|
Chris@87
|
24 # after it's installed
|
Chris@87
|
25 #import numpy.f2py
|
Chris@87
|
26 from numpy.distutils import log
|
Chris@87
|
27 from numpy.distutils.misc_util import fortran_ext_match, \
|
Chris@87
|
28 appendpath, is_string, is_sequence, get_cmd
|
Chris@87
|
29 from numpy.distutils.from_template import process_file as process_f_file
|
Chris@87
|
30 from numpy.distutils.conv_template import process_file as process_c_file
|
Chris@87
|
31
|
Chris@87
|
32 def subst_vars(target, source, d):
|
Chris@87
|
33 """Substitute any occurence of @foo@ by d['foo'] from source file into
|
Chris@87
|
34 target."""
|
Chris@87
|
35 var = re.compile('@([a-zA-Z_]+)@')
|
Chris@87
|
36 fs = open(source, 'r')
|
Chris@87
|
37 try:
|
Chris@87
|
38 ft = open(target, 'w')
|
Chris@87
|
39 try:
|
Chris@87
|
40 for l in fs:
|
Chris@87
|
41 m = var.search(l)
|
Chris@87
|
42 if m:
|
Chris@87
|
43 ft.write(l.replace('@%s@' % m.group(1), d[m.group(1)]))
|
Chris@87
|
44 else:
|
Chris@87
|
45 ft.write(l)
|
Chris@87
|
46 finally:
|
Chris@87
|
47 ft.close()
|
Chris@87
|
48 finally:
|
Chris@87
|
49 fs.close()
|
Chris@87
|
50
|
Chris@87
|
51 class build_src(build_ext.build_ext):
|
Chris@87
|
52
|
Chris@87
|
53 description = "build sources from SWIG, F2PY files or a function"
|
Chris@87
|
54
|
Chris@87
|
55 user_options = [
|
Chris@87
|
56 ('build-src=', 'd', "directory to \"build\" sources to"),
|
Chris@87
|
57 ('f2py-opts=', None, "list of f2py command line options"),
|
Chris@87
|
58 ('swig=', None, "path to the SWIG executable"),
|
Chris@87
|
59 ('swig-opts=', None, "list of SWIG command line options"),
|
Chris@87
|
60 ('swig-cpp', None, "make SWIG create C++ files (default is autodetected from sources)"),
|
Chris@87
|
61 ('f2pyflags=', None, "additional flags to f2py (use --f2py-opts= instead)"), # obsolete
|
Chris@87
|
62 ('swigflags=', None, "additional flags to swig (use --swig-opts= instead)"), # obsolete
|
Chris@87
|
63 ('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
Chris@87
|
64 ('inplace', 'i',
|
Chris@87
|
65 "ignore build-lib and put compiled extensions into the source " +
|
Chris@87
|
66 "directory alongside your pure Python modules"),
|
Chris@87
|
67 ]
|
Chris@87
|
68
|
Chris@87
|
69 boolean_options = ['force', 'inplace']
|
Chris@87
|
70
|
Chris@87
|
71 help_options = []
|
Chris@87
|
72
|
Chris@87
|
73 def initialize_options(self):
|
Chris@87
|
74 self.extensions = None
|
Chris@87
|
75 self.package = None
|
Chris@87
|
76 self.py_modules = None
|
Chris@87
|
77 self.py_modules_dict = None
|
Chris@87
|
78 self.build_src = None
|
Chris@87
|
79 self.build_lib = None
|
Chris@87
|
80 self.build_base = None
|
Chris@87
|
81 self.force = None
|
Chris@87
|
82 self.inplace = None
|
Chris@87
|
83 self.package_dir = None
|
Chris@87
|
84 self.f2pyflags = None # obsolete
|
Chris@87
|
85 self.f2py_opts = None
|
Chris@87
|
86 self.swigflags = None # obsolete
|
Chris@87
|
87 self.swig_opts = None
|
Chris@87
|
88 self.swig_cpp = None
|
Chris@87
|
89 self.swig = None
|
Chris@87
|
90
|
Chris@87
|
91 def finalize_options(self):
|
Chris@87
|
92 self.set_undefined_options('build',
|
Chris@87
|
93 ('build_base', 'build_base'),
|
Chris@87
|
94 ('build_lib', 'build_lib'),
|
Chris@87
|
95 ('force', 'force'))
|
Chris@87
|
96 if self.package is None:
|
Chris@87
|
97 self.package = self.distribution.ext_package
|
Chris@87
|
98 self.extensions = self.distribution.ext_modules
|
Chris@87
|
99 self.libraries = self.distribution.libraries or []
|
Chris@87
|
100 self.py_modules = self.distribution.py_modules or []
|
Chris@87
|
101 self.data_files = self.distribution.data_files or []
|
Chris@87
|
102
|
Chris@87
|
103 if self.build_src is None:
|
Chris@87
|
104 plat_specifier = ".%s-%s" % (get_platform(), sys.version[0:3])
|
Chris@87
|
105 self.build_src = os.path.join(self.build_base, 'src'+plat_specifier)
|
Chris@87
|
106
|
Chris@87
|
107 # py_modules_dict is used in build_py.find_package_modules
|
Chris@87
|
108 self.py_modules_dict = {}
|
Chris@87
|
109
|
Chris@87
|
110 if self.f2pyflags:
|
Chris@87
|
111 if self.f2py_opts:
|
Chris@87
|
112 log.warn('ignoring --f2pyflags as --f2py-opts already used')
|
Chris@87
|
113 else:
|
Chris@87
|
114 self.f2py_opts = self.f2pyflags
|
Chris@87
|
115 self.f2pyflags = None
|
Chris@87
|
116 if self.f2py_opts is None:
|
Chris@87
|
117 self.f2py_opts = []
|
Chris@87
|
118 else:
|
Chris@87
|
119 self.f2py_opts = shlex.split(self.f2py_opts)
|
Chris@87
|
120
|
Chris@87
|
121 if self.swigflags:
|
Chris@87
|
122 if self.swig_opts:
|
Chris@87
|
123 log.warn('ignoring --swigflags as --swig-opts already used')
|
Chris@87
|
124 else:
|
Chris@87
|
125 self.swig_opts = self.swigflags
|
Chris@87
|
126 self.swigflags = None
|
Chris@87
|
127
|
Chris@87
|
128 if self.swig_opts is None:
|
Chris@87
|
129 self.swig_opts = []
|
Chris@87
|
130 else:
|
Chris@87
|
131 self.swig_opts = shlex.split(self.swig_opts)
|
Chris@87
|
132
|
Chris@87
|
133 # use options from build_ext command
|
Chris@87
|
134 build_ext = self.get_finalized_command('build_ext')
|
Chris@87
|
135 if self.inplace is None:
|
Chris@87
|
136 self.inplace = build_ext.inplace
|
Chris@87
|
137 if self.swig_cpp is None:
|
Chris@87
|
138 self.swig_cpp = build_ext.swig_cpp
|
Chris@87
|
139 for c in ['swig', 'swig_opt']:
|
Chris@87
|
140 o = '--'+c.replace('_', '-')
|
Chris@87
|
141 v = getattr(build_ext, c, None)
|
Chris@87
|
142 if v:
|
Chris@87
|
143 if getattr(self, c):
|
Chris@87
|
144 log.warn('both build_src and build_ext define %s option' % (o))
|
Chris@87
|
145 else:
|
Chris@87
|
146 log.info('using "%s=%s" option from build_ext command' % (o, v))
|
Chris@87
|
147 setattr(self, c, v)
|
Chris@87
|
148
|
Chris@87
|
149 def run(self):
|
Chris@87
|
150 log.info("build_src")
|
Chris@87
|
151 if not (self.extensions or self.libraries):
|
Chris@87
|
152 return
|
Chris@87
|
153 self.build_sources()
|
Chris@87
|
154
|
Chris@87
|
155 def build_sources(self):
|
Chris@87
|
156
|
Chris@87
|
157 if self.inplace:
|
Chris@87
|
158 self.get_package_dir = \
|
Chris@87
|
159 self.get_finalized_command('build_py').get_package_dir
|
Chris@87
|
160
|
Chris@87
|
161 self.build_py_modules_sources()
|
Chris@87
|
162
|
Chris@87
|
163 for libname_info in self.libraries:
|
Chris@87
|
164 self.build_library_sources(*libname_info)
|
Chris@87
|
165
|
Chris@87
|
166 if self.extensions:
|
Chris@87
|
167 self.check_extensions_list(self.extensions)
|
Chris@87
|
168
|
Chris@87
|
169 for ext in self.extensions:
|
Chris@87
|
170 self.build_extension_sources(ext)
|
Chris@87
|
171
|
Chris@87
|
172 self.build_data_files_sources()
|
Chris@87
|
173 self.build_npy_pkg_config()
|
Chris@87
|
174
|
Chris@87
|
175 def build_data_files_sources(self):
|
Chris@87
|
176 if not self.data_files:
|
Chris@87
|
177 return
|
Chris@87
|
178 log.info('building data_files sources')
|
Chris@87
|
179 from numpy.distutils.misc_util import get_data_files
|
Chris@87
|
180 new_data_files = []
|
Chris@87
|
181 for data in self.data_files:
|
Chris@87
|
182 if isinstance(data, str):
|
Chris@87
|
183 new_data_files.append(data)
|
Chris@87
|
184 elif isinstance(data, tuple):
|
Chris@87
|
185 d, files = data
|
Chris@87
|
186 if self.inplace:
|
Chris@87
|
187 build_dir = self.get_package_dir('.'.join(d.split(os.sep)))
|
Chris@87
|
188 else:
|
Chris@87
|
189 build_dir = os.path.join(self.build_src, d)
|
Chris@87
|
190 funcs = [f for f in files if hasattr(f, '__call__')]
|
Chris@87
|
191 files = [f for f in files if not hasattr(f, '__call__')]
|
Chris@87
|
192 for f in funcs:
|
Chris@87
|
193 if f.__code__.co_argcount==1:
|
Chris@87
|
194 s = f(build_dir)
|
Chris@87
|
195 else:
|
Chris@87
|
196 s = f()
|
Chris@87
|
197 if s is not None:
|
Chris@87
|
198 if isinstance(s, list):
|
Chris@87
|
199 files.extend(s)
|
Chris@87
|
200 elif isinstance(s, str):
|
Chris@87
|
201 files.append(s)
|
Chris@87
|
202 else:
|
Chris@87
|
203 raise TypeError(repr(s))
|
Chris@87
|
204 filenames = get_data_files((d, files))
|
Chris@87
|
205 new_data_files.append((d, filenames))
|
Chris@87
|
206 else:
|
Chris@87
|
207 raise TypeError(repr(data))
|
Chris@87
|
208 self.data_files[:] = new_data_files
|
Chris@87
|
209
|
Chris@87
|
210
|
Chris@87
|
211 def _build_npy_pkg_config(self, info, gd):
|
Chris@87
|
212 import shutil
|
Chris@87
|
213 template, install_dir, subst_dict = info
|
Chris@87
|
214 template_dir = os.path.dirname(template)
|
Chris@87
|
215 for k, v in gd.items():
|
Chris@87
|
216 subst_dict[k] = v
|
Chris@87
|
217
|
Chris@87
|
218 if self.inplace == 1:
|
Chris@87
|
219 generated_dir = os.path.join(template_dir, install_dir)
|
Chris@87
|
220 else:
|
Chris@87
|
221 generated_dir = os.path.join(self.build_src, template_dir,
|
Chris@87
|
222 install_dir)
|
Chris@87
|
223 generated = os.path.basename(os.path.splitext(template)[0])
|
Chris@87
|
224 generated_path = os.path.join(generated_dir, generated)
|
Chris@87
|
225 if not os.path.exists(generated_dir):
|
Chris@87
|
226 os.makedirs(generated_dir)
|
Chris@87
|
227
|
Chris@87
|
228 subst_vars(generated_path, template, subst_dict)
|
Chris@87
|
229
|
Chris@87
|
230 # Where to install relatively to install prefix
|
Chris@87
|
231 full_install_dir = os.path.join(template_dir, install_dir)
|
Chris@87
|
232 return full_install_dir, generated_path
|
Chris@87
|
233
|
Chris@87
|
234 def build_npy_pkg_config(self):
|
Chris@87
|
235 log.info('build_src: building npy-pkg config files')
|
Chris@87
|
236
|
Chris@87
|
237 # XXX: another ugly workaround to circumvent distutils brain damage. We
|
Chris@87
|
238 # need the install prefix here, but finalizing the options of the
|
Chris@87
|
239 # install command when only building sources cause error. Instead, we
|
Chris@87
|
240 # copy the install command instance, and finalize the copy so that it
|
Chris@87
|
241 # does not disrupt how distutils want to do things when with the
|
Chris@87
|
242 # original install command instance.
|
Chris@87
|
243 install_cmd = copy.copy(get_cmd('install'))
|
Chris@87
|
244 if not install_cmd.finalized == 1:
|
Chris@87
|
245 install_cmd.finalize_options()
|
Chris@87
|
246 build_npkg = False
|
Chris@87
|
247 gd = {}
|
Chris@87
|
248 if self.inplace == 1:
|
Chris@87
|
249 top_prefix = '.'
|
Chris@87
|
250 build_npkg = True
|
Chris@87
|
251 elif hasattr(install_cmd, 'install_libbase'):
|
Chris@87
|
252 top_prefix = install_cmd.install_libbase
|
Chris@87
|
253 build_npkg = True
|
Chris@87
|
254
|
Chris@87
|
255 if build_npkg:
|
Chris@87
|
256 for pkg, infos in self.distribution.installed_pkg_config.items():
|
Chris@87
|
257 pkg_path = self.distribution.package_dir[pkg]
|
Chris@87
|
258 prefix = os.path.join(os.path.abspath(top_prefix), pkg_path)
|
Chris@87
|
259 d = {'prefix': prefix}
|
Chris@87
|
260 for info in infos:
|
Chris@87
|
261 install_dir, generated = self._build_npy_pkg_config(info, d)
|
Chris@87
|
262 self.distribution.data_files.append((install_dir,
|
Chris@87
|
263 [generated]))
|
Chris@87
|
264
|
Chris@87
|
265 def build_py_modules_sources(self):
|
Chris@87
|
266 if not self.py_modules:
|
Chris@87
|
267 return
|
Chris@87
|
268 log.info('building py_modules sources')
|
Chris@87
|
269 new_py_modules = []
|
Chris@87
|
270 for source in self.py_modules:
|
Chris@87
|
271 if is_sequence(source) and len(source)==3:
|
Chris@87
|
272 package, module_base, source = source
|
Chris@87
|
273 if self.inplace:
|
Chris@87
|
274 build_dir = self.get_package_dir(package)
|
Chris@87
|
275 else:
|
Chris@87
|
276 build_dir = os.path.join(self.build_src,
|
Chris@87
|
277 os.path.join(*package.split('.')))
|
Chris@87
|
278 if hasattr(source, '__call__'):
|
Chris@87
|
279 target = os.path.join(build_dir, module_base + '.py')
|
Chris@87
|
280 source = source(target)
|
Chris@87
|
281 if source is None:
|
Chris@87
|
282 continue
|
Chris@87
|
283 modules = [(package, module_base, source)]
|
Chris@87
|
284 if package not in self.py_modules_dict:
|
Chris@87
|
285 self.py_modules_dict[package] = []
|
Chris@87
|
286 self.py_modules_dict[package] += modules
|
Chris@87
|
287 else:
|
Chris@87
|
288 new_py_modules.append(source)
|
Chris@87
|
289 self.py_modules[:] = new_py_modules
|
Chris@87
|
290
|
Chris@87
|
291 def build_library_sources(self, lib_name, build_info):
|
Chris@87
|
292 sources = list(build_info.get('sources', []))
|
Chris@87
|
293
|
Chris@87
|
294 if not sources:
|
Chris@87
|
295 return
|
Chris@87
|
296
|
Chris@87
|
297 log.info('building library "%s" sources' % (lib_name))
|
Chris@87
|
298
|
Chris@87
|
299 sources = self.generate_sources(sources, (lib_name, build_info))
|
Chris@87
|
300
|
Chris@87
|
301 sources = self.template_sources(sources, (lib_name, build_info))
|
Chris@87
|
302
|
Chris@87
|
303 sources, h_files = self.filter_h_files(sources)
|
Chris@87
|
304
|
Chris@87
|
305 if h_files:
|
Chris@87
|
306 log.info('%s - nothing done with h_files = %s',
|
Chris@87
|
307 self.package, h_files)
|
Chris@87
|
308
|
Chris@87
|
309 #for f in h_files:
|
Chris@87
|
310 # self.distribution.headers.append((lib_name,f))
|
Chris@87
|
311
|
Chris@87
|
312 build_info['sources'] = sources
|
Chris@87
|
313 return
|
Chris@87
|
314
|
Chris@87
|
315 def build_extension_sources(self, ext):
|
Chris@87
|
316
|
Chris@87
|
317 sources = list(ext.sources)
|
Chris@87
|
318
|
Chris@87
|
319 log.info('building extension "%s" sources' % (ext.name))
|
Chris@87
|
320
|
Chris@87
|
321 fullname = self.get_ext_fullname(ext.name)
|
Chris@87
|
322
|
Chris@87
|
323 modpath = fullname.split('.')
|
Chris@87
|
324 package = '.'.join(modpath[0:-1])
|
Chris@87
|
325
|
Chris@87
|
326 if self.inplace:
|
Chris@87
|
327 self.ext_target_dir = self.get_package_dir(package)
|
Chris@87
|
328
|
Chris@87
|
329 sources = self.generate_sources(sources, ext)
|
Chris@87
|
330
|
Chris@87
|
331 sources = self.template_sources(sources, ext)
|
Chris@87
|
332
|
Chris@87
|
333 sources = self.swig_sources(sources, ext)
|
Chris@87
|
334
|
Chris@87
|
335 sources = self.f2py_sources(sources, ext)
|
Chris@87
|
336
|
Chris@87
|
337 sources = self.pyrex_sources(sources, ext)
|
Chris@87
|
338
|
Chris@87
|
339 sources, py_files = self.filter_py_files(sources)
|
Chris@87
|
340
|
Chris@87
|
341 if package not in self.py_modules_dict:
|
Chris@87
|
342 self.py_modules_dict[package] = []
|
Chris@87
|
343 modules = []
|
Chris@87
|
344 for f in py_files:
|
Chris@87
|
345 module = os.path.splitext(os.path.basename(f))[0]
|
Chris@87
|
346 modules.append((package, module, f))
|
Chris@87
|
347 self.py_modules_dict[package] += modules
|
Chris@87
|
348
|
Chris@87
|
349 sources, h_files = self.filter_h_files(sources)
|
Chris@87
|
350
|
Chris@87
|
351 if h_files:
|
Chris@87
|
352 log.info('%s - nothing done with h_files = %s',
|
Chris@87
|
353 package, h_files)
|
Chris@87
|
354 #for f in h_files:
|
Chris@87
|
355 # self.distribution.headers.append((package,f))
|
Chris@87
|
356
|
Chris@87
|
357 ext.sources = sources
|
Chris@87
|
358
|
Chris@87
|
359 def generate_sources(self, sources, extension):
|
Chris@87
|
360 new_sources = []
|
Chris@87
|
361 func_sources = []
|
Chris@87
|
362 for source in sources:
|
Chris@87
|
363 if is_string(source):
|
Chris@87
|
364 new_sources.append(source)
|
Chris@87
|
365 else:
|
Chris@87
|
366 func_sources.append(source)
|
Chris@87
|
367 if not func_sources:
|
Chris@87
|
368 return new_sources
|
Chris@87
|
369 if self.inplace and not is_sequence(extension):
|
Chris@87
|
370 build_dir = self.ext_target_dir
|
Chris@87
|
371 else:
|
Chris@87
|
372 if is_sequence(extension):
|
Chris@87
|
373 name = extension[0]
|
Chris@87
|
374 # if 'include_dirs' not in extension[1]:
|
Chris@87
|
375 # extension[1]['include_dirs'] = []
|
Chris@87
|
376 # incl_dirs = extension[1]['include_dirs']
|
Chris@87
|
377 else:
|
Chris@87
|
378 name = extension.name
|
Chris@87
|
379 # incl_dirs = extension.include_dirs
|
Chris@87
|
380 #if self.build_src not in incl_dirs:
|
Chris@87
|
381 # incl_dirs.append(self.build_src)
|
Chris@87
|
382 build_dir = os.path.join(*([self.build_src]\
|
Chris@87
|
383 +name.split('.')[:-1]))
|
Chris@87
|
384 self.mkpath(build_dir)
|
Chris@87
|
385 for func in func_sources:
|
Chris@87
|
386 source = func(extension, build_dir)
|
Chris@87
|
387 if not source:
|
Chris@87
|
388 continue
|
Chris@87
|
389 if is_sequence(source):
|
Chris@87
|
390 [log.info(" adding '%s' to sources." % (s,)) for s in source]
|
Chris@87
|
391 new_sources.extend(source)
|
Chris@87
|
392 else:
|
Chris@87
|
393 log.info(" adding '%s' to sources." % (source,))
|
Chris@87
|
394 new_sources.append(source)
|
Chris@87
|
395
|
Chris@87
|
396 return new_sources
|
Chris@87
|
397
|
Chris@87
|
398 def filter_py_files(self, sources):
|
Chris@87
|
399 return self.filter_files(sources, ['.py'])
|
Chris@87
|
400
|
Chris@87
|
401 def filter_h_files(self, sources):
|
Chris@87
|
402 return self.filter_files(sources, ['.h', '.hpp', '.inc'])
|
Chris@87
|
403
|
Chris@87
|
404 def filter_files(self, sources, exts = []):
|
Chris@87
|
405 new_sources = []
|
Chris@87
|
406 files = []
|
Chris@87
|
407 for source in sources:
|
Chris@87
|
408 (base, ext) = os.path.splitext(source)
|
Chris@87
|
409 if ext in exts:
|
Chris@87
|
410 files.append(source)
|
Chris@87
|
411 else:
|
Chris@87
|
412 new_sources.append(source)
|
Chris@87
|
413 return new_sources, files
|
Chris@87
|
414
|
Chris@87
|
415 def template_sources(self, sources, extension):
|
Chris@87
|
416 new_sources = []
|
Chris@87
|
417 if is_sequence(extension):
|
Chris@87
|
418 depends = extension[1].get('depends')
|
Chris@87
|
419 include_dirs = extension[1].get('include_dirs')
|
Chris@87
|
420 else:
|
Chris@87
|
421 depends = extension.depends
|
Chris@87
|
422 include_dirs = extension.include_dirs
|
Chris@87
|
423 for source in sources:
|
Chris@87
|
424 (base, ext) = os.path.splitext(source)
|
Chris@87
|
425 if ext == '.src': # Template file
|
Chris@87
|
426 if self.inplace:
|
Chris@87
|
427 target_dir = os.path.dirname(base)
|
Chris@87
|
428 else:
|
Chris@87
|
429 target_dir = appendpath(self.build_src, os.path.dirname(base))
|
Chris@87
|
430 self.mkpath(target_dir)
|
Chris@87
|
431 target_file = os.path.join(target_dir, os.path.basename(base))
|
Chris@87
|
432 if (self.force or newer_group([source] + depends, target_file)):
|
Chris@87
|
433 if _f_pyf_ext_match(base):
|
Chris@87
|
434 log.info("from_template:> %s" % (target_file))
|
Chris@87
|
435 outstr = process_f_file(source)
|
Chris@87
|
436 else:
|
Chris@87
|
437 log.info("conv_template:> %s" % (target_file))
|
Chris@87
|
438 outstr = process_c_file(source)
|
Chris@87
|
439 fid = open(target_file, 'w')
|
Chris@87
|
440 fid.write(outstr)
|
Chris@87
|
441 fid.close()
|
Chris@87
|
442 if _header_ext_match(target_file):
|
Chris@87
|
443 d = os.path.dirname(target_file)
|
Chris@87
|
444 if d not in include_dirs:
|
Chris@87
|
445 log.info(" adding '%s' to include_dirs." % (d))
|
Chris@87
|
446 include_dirs.append(d)
|
Chris@87
|
447 new_sources.append(target_file)
|
Chris@87
|
448 else:
|
Chris@87
|
449 new_sources.append(source)
|
Chris@87
|
450 return new_sources
|
Chris@87
|
451
|
Chris@87
|
452 def pyrex_sources(self, sources, extension):
|
Chris@87
|
453 new_sources = []
|
Chris@87
|
454 ext_name = extension.name.split('.')[-1]
|
Chris@87
|
455 for source in sources:
|
Chris@87
|
456 (base, ext) = os.path.splitext(source)
|
Chris@87
|
457 if ext == '.pyx':
|
Chris@87
|
458 target_file = self.generate_a_pyrex_source(base, ext_name,
|
Chris@87
|
459 source,
|
Chris@87
|
460 extension)
|
Chris@87
|
461 new_sources.append(target_file)
|
Chris@87
|
462 else:
|
Chris@87
|
463 new_sources.append(source)
|
Chris@87
|
464 return new_sources
|
Chris@87
|
465
|
Chris@87
|
466 def generate_a_pyrex_source(self, base, ext_name, source, extension):
|
Chris@87
|
467 if self.inplace or not have_pyrex():
|
Chris@87
|
468 target_dir = os.path.dirname(base)
|
Chris@87
|
469 else:
|
Chris@87
|
470 target_dir = appendpath(self.build_src, os.path.dirname(base))
|
Chris@87
|
471 target_file = os.path.join(target_dir, ext_name + '.c')
|
Chris@87
|
472 depends = [source] + extension.depends
|
Chris@87
|
473 if self.force or newer_group(depends, target_file, 'newer'):
|
Chris@87
|
474 if have_pyrex():
|
Chris@87
|
475 import Pyrex.Compiler.Main
|
Chris@87
|
476 log.info("pyrexc:> %s" % (target_file))
|
Chris@87
|
477 self.mkpath(target_dir)
|
Chris@87
|
478 options = Pyrex.Compiler.Main.CompilationOptions(
|
Chris@87
|
479 defaults=Pyrex.Compiler.Main.default_options,
|
Chris@87
|
480 include_path=extension.include_dirs,
|
Chris@87
|
481 output_file=target_file)
|
Chris@87
|
482 pyrex_result = Pyrex.Compiler.Main.compile(source,
|
Chris@87
|
483 options=options)
|
Chris@87
|
484 if pyrex_result.num_errors != 0:
|
Chris@87
|
485 raise DistutilsError("%d errors while compiling %r with Pyrex" \
|
Chris@87
|
486 % (pyrex_result.num_errors, source))
|
Chris@87
|
487 elif os.path.isfile(target_file):
|
Chris@87
|
488 log.warn("Pyrex required for compiling %r but not available,"\
|
Chris@87
|
489 " using old target %r"\
|
Chris@87
|
490 % (source, target_file))
|
Chris@87
|
491 else:
|
Chris@87
|
492 raise DistutilsError("Pyrex required for compiling %r"\
|
Chris@87
|
493 " but notavailable" % (source,))
|
Chris@87
|
494 return target_file
|
Chris@87
|
495
|
Chris@87
|
496 def f2py_sources(self, sources, extension):
|
Chris@87
|
497 new_sources = []
|
Chris@87
|
498 f2py_sources = []
|
Chris@87
|
499 f_sources = []
|
Chris@87
|
500 f2py_targets = {}
|
Chris@87
|
501 target_dirs = []
|
Chris@87
|
502 ext_name = extension.name.split('.')[-1]
|
Chris@87
|
503 skip_f2py = 0
|
Chris@87
|
504
|
Chris@87
|
505 for source in sources:
|
Chris@87
|
506 (base, ext) = os.path.splitext(source)
|
Chris@87
|
507 if ext == '.pyf': # F2PY interface file
|
Chris@87
|
508 if self.inplace:
|
Chris@87
|
509 target_dir = os.path.dirname(base)
|
Chris@87
|
510 else:
|
Chris@87
|
511 target_dir = appendpath(self.build_src, os.path.dirname(base))
|
Chris@87
|
512 if os.path.isfile(source):
|
Chris@87
|
513 name = get_f2py_modulename(source)
|
Chris@87
|
514 if name != ext_name:
|
Chris@87
|
515 raise DistutilsSetupError('mismatch of extension names: %s '
|
Chris@87
|
516 'provides %r but expected %r' % (
|
Chris@87
|
517 source, name, ext_name))
|
Chris@87
|
518 target_file = os.path.join(target_dir, name+'module.c')
|
Chris@87
|
519 else:
|
Chris@87
|
520 log.debug(' source %s does not exist: skipping f2py\'ing.' \
|
Chris@87
|
521 % (source))
|
Chris@87
|
522 name = ext_name
|
Chris@87
|
523 skip_f2py = 1
|
Chris@87
|
524 target_file = os.path.join(target_dir, name+'module.c')
|
Chris@87
|
525 if not os.path.isfile(target_file):
|
Chris@87
|
526 log.warn(' target %s does not exist:\n '\
|
Chris@87
|
527 'Assuming %smodule.c was generated with '\
|
Chris@87
|
528 '"build_src --inplace" command.' \
|
Chris@87
|
529 % (target_file, name))
|
Chris@87
|
530 target_dir = os.path.dirname(base)
|
Chris@87
|
531 target_file = os.path.join(target_dir, name+'module.c')
|
Chris@87
|
532 if not os.path.isfile(target_file):
|
Chris@87
|
533 raise DistutilsSetupError("%r missing" % (target_file,))
|
Chris@87
|
534 log.info(' Yes! Using %r as up-to-date target.' \
|
Chris@87
|
535 % (target_file))
|
Chris@87
|
536 target_dirs.append(target_dir)
|
Chris@87
|
537 f2py_sources.append(source)
|
Chris@87
|
538 f2py_targets[source] = target_file
|
Chris@87
|
539 new_sources.append(target_file)
|
Chris@87
|
540 elif fortran_ext_match(ext):
|
Chris@87
|
541 f_sources.append(source)
|
Chris@87
|
542 else:
|
Chris@87
|
543 new_sources.append(source)
|
Chris@87
|
544
|
Chris@87
|
545 if not (f2py_sources or f_sources):
|
Chris@87
|
546 return new_sources
|
Chris@87
|
547
|
Chris@87
|
548 for d in target_dirs:
|
Chris@87
|
549 self.mkpath(d)
|
Chris@87
|
550
|
Chris@87
|
551 f2py_options = extension.f2py_options + self.f2py_opts
|
Chris@87
|
552
|
Chris@87
|
553 if self.distribution.libraries:
|
Chris@87
|
554 for name, build_info in self.distribution.libraries:
|
Chris@87
|
555 if name in extension.libraries:
|
Chris@87
|
556 f2py_options.extend(build_info.get('f2py_options', []))
|
Chris@87
|
557
|
Chris@87
|
558 log.info("f2py options: %s" % (f2py_options))
|
Chris@87
|
559
|
Chris@87
|
560 if f2py_sources:
|
Chris@87
|
561 if len(f2py_sources) != 1:
|
Chris@87
|
562 raise DistutilsSetupError(
|
Chris@87
|
563 'only one .pyf file is allowed per extension module but got'\
|
Chris@87
|
564 ' more: %r' % (f2py_sources,))
|
Chris@87
|
565 source = f2py_sources[0]
|
Chris@87
|
566 target_file = f2py_targets[source]
|
Chris@87
|
567 target_dir = os.path.dirname(target_file) or '.'
|
Chris@87
|
568 depends = [source] + extension.depends
|
Chris@87
|
569 if (self.force or newer_group(depends, target_file, 'newer')) \
|
Chris@87
|
570 and not skip_f2py:
|
Chris@87
|
571 log.info("f2py: %s" % (source))
|
Chris@87
|
572 import numpy.f2py
|
Chris@87
|
573 numpy.f2py.run_main(f2py_options
|
Chris@87
|
574 + ['--build-dir', target_dir, source])
|
Chris@87
|
575 else:
|
Chris@87
|
576 log.debug(" skipping '%s' f2py interface (up-to-date)" % (source))
|
Chris@87
|
577 else:
|
Chris@87
|
578 #XXX TODO: --inplace support for sdist command
|
Chris@87
|
579 if is_sequence(extension):
|
Chris@87
|
580 name = extension[0]
|
Chris@87
|
581 else: name = extension.name
|
Chris@87
|
582 target_dir = os.path.join(*([self.build_src]\
|
Chris@87
|
583 +name.split('.')[:-1]))
|
Chris@87
|
584 target_file = os.path.join(target_dir, ext_name + 'module.c')
|
Chris@87
|
585 new_sources.append(target_file)
|
Chris@87
|
586 depends = f_sources + extension.depends
|
Chris@87
|
587 if (self.force or newer_group(depends, target_file, 'newer')) \
|
Chris@87
|
588 and not skip_f2py:
|
Chris@87
|
589 log.info("f2py:> %s" % (target_file))
|
Chris@87
|
590 self.mkpath(target_dir)
|
Chris@87
|
591 import numpy.f2py
|
Chris@87
|
592 numpy.f2py.run_main(f2py_options + ['--lower',
|
Chris@87
|
593 '--build-dir', target_dir]+\
|
Chris@87
|
594 ['-m', ext_name]+f_sources)
|
Chris@87
|
595 else:
|
Chris@87
|
596 log.debug(" skipping f2py fortran files for '%s' (up-to-date)"\
|
Chris@87
|
597 % (target_file))
|
Chris@87
|
598
|
Chris@87
|
599 if not os.path.isfile(target_file):
|
Chris@87
|
600 raise DistutilsError("f2py target file %r not generated" % (target_file,))
|
Chris@87
|
601
|
Chris@87
|
602 target_c = os.path.join(self.build_src, 'fortranobject.c')
|
Chris@87
|
603 target_h = os.path.join(self.build_src, 'fortranobject.h')
|
Chris@87
|
604 log.info(" adding '%s' to sources." % (target_c))
|
Chris@87
|
605 new_sources.append(target_c)
|
Chris@87
|
606 if self.build_src not in extension.include_dirs:
|
Chris@87
|
607 log.info(" adding '%s' to include_dirs." \
|
Chris@87
|
608 % (self.build_src))
|
Chris@87
|
609 extension.include_dirs.append(self.build_src)
|
Chris@87
|
610
|
Chris@87
|
611 if not skip_f2py:
|
Chris@87
|
612 import numpy.f2py
|
Chris@87
|
613 d = os.path.dirname(numpy.f2py.__file__)
|
Chris@87
|
614 source_c = os.path.join(d, 'src', 'fortranobject.c')
|
Chris@87
|
615 source_h = os.path.join(d, 'src', 'fortranobject.h')
|
Chris@87
|
616 if newer(source_c, target_c) or newer(source_h, target_h):
|
Chris@87
|
617 self.mkpath(os.path.dirname(target_c))
|
Chris@87
|
618 self.copy_file(source_c, target_c)
|
Chris@87
|
619 self.copy_file(source_h, target_h)
|
Chris@87
|
620 else:
|
Chris@87
|
621 if not os.path.isfile(target_c):
|
Chris@87
|
622 raise DistutilsSetupError("f2py target_c file %r not found" % (target_c,))
|
Chris@87
|
623 if not os.path.isfile(target_h):
|
Chris@87
|
624 raise DistutilsSetupError("f2py target_h file %r not found" % (target_h,))
|
Chris@87
|
625
|
Chris@87
|
626 for name_ext in ['-f2pywrappers.f', '-f2pywrappers2.f90']:
|
Chris@87
|
627 filename = os.path.join(target_dir, ext_name + name_ext)
|
Chris@87
|
628 if os.path.isfile(filename):
|
Chris@87
|
629 log.info(" adding '%s' to sources." % (filename))
|
Chris@87
|
630 f_sources.append(filename)
|
Chris@87
|
631
|
Chris@87
|
632 return new_sources + f_sources
|
Chris@87
|
633
|
Chris@87
|
634 def swig_sources(self, sources, extension):
|
Chris@87
|
635 # Assuming SWIG 1.3.14 or later. See compatibility note in
|
Chris@87
|
636 # http://www.swig.org/Doc1.3/Python.html#Python_nn6
|
Chris@87
|
637
|
Chris@87
|
638 new_sources = []
|
Chris@87
|
639 swig_sources = []
|
Chris@87
|
640 swig_targets = {}
|
Chris@87
|
641 target_dirs = []
|
Chris@87
|
642 py_files = [] # swig generated .py files
|
Chris@87
|
643 target_ext = '.c'
|
Chris@87
|
644 if '-c++' in extension.swig_opts:
|
Chris@87
|
645 typ = 'c++'
|
Chris@87
|
646 is_cpp = True
|
Chris@87
|
647 extension.swig_opts.remove('-c++')
|
Chris@87
|
648 elif self.swig_cpp:
|
Chris@87
|
649 typ = 'c++'
|
Chris@87
|
650 is_cpp = True
|
Chris@87
|
651 else:
|
Chris@87
|
652 typ = None
|
Chris@87
|
653 is_cpp = False
|
Chris@87
|
654 skip_swig = 0
|
Chris@87
|
655 ext_name = extension.name.split('.')[-1]
|
Chris@87
|
656
|
Chris@87
|
657 for source in sources:
|
Chris@87
|
658 (base, ext) = os.path.splitext(source)
|
Chris@87
|
659 if ext == '.i': # SWIG interface file
|
Chris@87
|
660 # the code below assumes that the sources list
|
Chris@87
|
661 # contains not more than one .i SWIG interface file
|
Chris@87
|
662 if self.inplace:
|
Chris@87
|
663 target_dir = os.path.dirname(base)
|
Chris@87
|
664 py_target_dir = self.ext_target_dir
|
Chris@87
|
665 else:
|
Chris@87
|
666 target_dir = appendpath(self.build_src, os.path.dirname(base))
|
Chris@87
|
667 py_target_dir = target_dir
|
Chris@87
|
668 if os.path.isfile(source):
|
Chris@87
|
669 name = get_swig_modulename(source)
|
Chris@87
|
670 if name != ext_name[1:]:
|
Chris@87
|
671 raise DistutilsSetupError(
|
Chris@87
|
672 'mismatch of extension names: %s provides %r'
|
Chris@87
|
673 ' but expected %r' % (source, name, ext_name[1:]))
|
Chris@87
|
674 if typ is None:
|
Chris@87
|
675 typ = get_swig_target(source)
|
Chris@87
|
676 is_cpp = typ=='c++'
|
Chris@87
|
677 else:
|
Chris@87
|
678 typ2 = get_swig_target(source)
|
Chris@87
|
679 if typ2 is None:
|
Chris@87
|
680 log.warn('source %r does not define swig target, assuming %s swig target' \
|
Chris@87
|
681 % (source, typ))
|
Chris@87
|
682 elif typ!=typ2:
|
Chris@87
|
683 log.warn('expected %r but source %r defines %r swig target' \
|
Chris@87
|
684 % (typ, source, typ2))
|
Chris@87
|
685 if typ2=='c++':
|
Chris@87
|
686 log.warn('resetting swig target to c++ (some targets may have .c extension)')
|
Chris@87
|
687 is_cpp = True
|
Chris@87
|
688 else:
|
Chris@87
|
689 log.warn('assuming that %r has c++ swig target' % (source))
|
Chris@87
|
690 if is_cpp:
|
Chris@87
|
691 target_ext = '.cpp'
|
Chris@87
|
692 target_file = os.path.join(target_dir, '%s_wrap%s' \
|
Chris@87
|
693 % (name, target_ext))
|
Chris@87
|
694 else:
|
Chris@87
|
695 log.warn(' source %s does not exist: skipping swig\'ing.' \
|
Chris@87
|
696 % (source))
|
Chris@87
|
697 name = ext_name[1:]
|
Chris@87
|
698 skip_swig = 1
|
Chris@87
|
699 target_file = _find_swig_target(target_dir, name)
|
Chris@87
|
700 if not os.path.isfile(target_file):
|
Chris@87
|
701 log.warn(' target %s does not exist:\n '\
|
Chris@87
|
702 'Assuming %s_wrap.{c,cpp} was generated with '\
|
Chris@87
|
703 '"build_src --inplace" command.' \
|
Chris@87
|
704 % (target_file, name))
|
Chris@87
|
705 target_dir = os.path.dirname(base)
|
Chris@87
|
706 target_file = _find_swig_target(target_dir, name)
|
Chris@87
|
707 if not os.path.isfile(target_file):
|
Chris@87
|
708 raise DistutilsSetupError("%r missing" % (target_file,))
|
Chris@87
|
709 log.warn(' Yes! Using %r as up-to-date target.' \
|
Chris@87
|
710 % (target_file))
|
Chris@87
|
711 target_dirs.append(target_dir)
|
Chris@87
|
712 new_sources.append(target_file)
|
Chris@87
|
713 py_files.append(os.path.join(py_target_dir, name+'.py'))
|
Chris@87
|
714 swig_sources.append(source)
|
Chris@87
|
715 swig_targets[source] = new_sources[-1]
|
Chris@87
|
716 else:
|
Chris@87
|
717 new_sources.append(source)
|
Chris@87
|
718
|
Chris@87
|
719 if not swig_sources:
|
Chris@87
|
720 return new_sources
|
Chris@87
|
721
|
Chris@87
|
722 if skip_swig:
|
Chris@87
|
723 return new_sources + py_files
|
Chris@87
|
724
|
Chris@87
|
725 for d in target_dirs:
|
Chris@87
|
726 self.mkpath(d)
|
Chris@87
|
727
|
Chris@87
|
728 swig = self.swig or self.find_swig()
|
Chris@87
|
729 swig_cmd = [swig, "-python"] + extension.swig_opts
|
Chris@87
|
730 if is_cpp:
|
Chris@87
|
731 swig_cmd.append('-c++')
|
Chris@87
|
732 for d in extension.include_dirs:
|
Chris@87
|
733 swig_cmd.append('-I'+d)
|
Chris@87
|
734 for source in swig_sources:
|
Chris@87
|
735 target = swig_targets[source]
|
Chris@87
|
736 depends = [source] + extension.depends
|
Chris@87
|
737 if self.force or newer_group(depends, target, 'newer'):
|
Chris@87
|
738 log.info("%s: %s" % (os.path.basename(swig) \
|
Chris@87
|
739 + (is_cpp and '++' or ''), source))
|
Chris@87
|
740 self.spawn(swig_cmd + self.swig_opts \
|
Chris@87
|
741 + ["-o", target, '-outdir', py_target_dir, source])
|
Chris@87
|
742 else:
|
Chris@87
|
743 log.debug(" skipping '%s' swig interface (up-to-date)" \
|
Chris@87
|
744 % (source))
|
Chris@87
|
745
|
Chris@87
|
746 return new_sources + py_files
|
Chris@87
|
747
|
Chris@87
|
748 _f_pyf_ext_match = re.compile(r'.*[.](f90|f95|f77|for|ftn|f|pyf)\Z', re.I).match
|
Chris@87
|
749 _header_ext_match = re.compile(r'.*[.](inc|h|hpp)\Z', re.I).match
|
Chris@87
|
750
|
Chris@87
|
751 #### SWIG related auxiliary functions ####
|
Chris@87
|
752 _swig_module_name_match = re.compile(r'\s*%module\s*(.*\(\s*package\s*=\s*"(?P<package>[\w_]+)".*\)|)\s*(?P<name>[\w_]+)',
|
Chris@87
|
753 re.I).match
|
Chris@87
|
754 _has_c_header = re.compile(r'-[*]-\s*c\s*-[*]-', re.I).search
|
Chris@87
|
755 _has_cpp_header = re.compile(r'-[*]-\s*c[+][+]\s*-[*]-', re.I).search
|
Chris@87
|
756
|
Chris@87
|
757 def get_swig_target(source):
|
Chris@87
|
758 f = open(source, 'r')
|
Chris@87
|
759 result = None
|
Chris@87
|
760 line = f.readline()
|
Chris@87
|
761 if _has_cpp_header(line):
|
Chris@87
|
762 result = 'c++'
|
Chris@87
|
763 if _has_c_header(line):
|
Chris@87
|
764 result = 'c'
|
Chris@87
|
765 f.close()
|
Chris@87
|
766 return result
|
Chris@87
|
767
|
Chris@87
|
768 def get_swig_modulename(source):
|
Chris@87
|
769 f = open(source, 'r')
|
Chris@87
|
770 name = None
|
Chris@87
|
771 for line in f:
|
Chris@87
|
772 m = _swig_module_name_match(line)
|
Chris@87
|
773 if m:
|
Chris@87
|
774 name = m.group('name')
|
Chris@87
|
775 break
|
Chris@87
|
776 f.close()
|
Chris@87
|
777 return name
|
Chris@87
|
778
|
Chris@87
|
779 def _find_swig_target(target_dir, name):
|
Chris@87
|
780 for ext in ['.cpp', '.c']:
|
Chris@87
|
781 target = os.path.join(target_dir, '%s_wrap%s' % (name, ext))
|
Chris@87
|
782 if os.path.isfile(target):
|
Chris@87
|
783 break
|
Chris@87
|
784 return target
|
Chris@87
|
785
|
Chris@87
|
786 #### F2PY related auxiliary functions ####
|
Chris@87
|
787
|
Chris@87
|
788 _f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
|
Chris@87
|
789 re.I).match
|
Chris@87
|
790 _f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'\
|
Chris@87
|
791 '__user__[\w_]*)', re.I).match
|
Chris@87
|
792
|
Chris@87
|
793 def get_f2py_modulename(source):
|
Chris@87
|
794 name = None
|
Chris@87
|
795 f = open(source)
|
Chris@87
|
796 for line in f:
|
Chris@87
|
797 m = _f2py_module_name_match(line)
|
Chris@87
|
798 if m:
|
Chris@87
|
799 if _f2py_user_module_name_match(line): # skip *__user__* names
|
Chris@87
|
800 continue
|
Chris@87
|
801 name = m.group('name')
|
Chris@87
|
802 break
|
Chris@87
|
803 f.close()
|
Chris@87
|
804 return name
|
Chris@87
|
805
|
Chris@87
|
806 ##########################################
|