1
""" Build swig, f2py, weave, sources.
8
from distutils.command import build_ext
9
from distutils.dep_util import newer_group, newer
10
from distutils.util import get_platform
12
from numpy.distutils import log
13
from numpy.distutils.misc_util import fortran_ext_match, \
14
appendpath, is_string, is_sequence
15
from numpy.distutils.from_template import process_file as process_f_file
16
from numpy.distutils.conv_template import process_file as process_c_file
18
class build_src(build_ext.build_ext):
20
description = "build sources from SWIG, F2PY files or a function"
23
('build-src=', 'd', "directory to \"build\" sources to"),
24
('f2pyflags=', None, "additonal flags to f2py"),
25
('swigflags=', None, "additional flags to swig"),
26
('force', 'f', "forcibly build everything (ignore file timestamps)"),
28
"ignore build-lib and put compiled extensions into the source " +
29
"directory alongside your pure Python modules"),
32
boolean_options = ['force','inplace']
36
def initialize_options(self):
37
self.extensions = None
39
self.py_modules = None
40
self.py_modules_dict = None
43
self.build_base = None
46
self.package_dir = None
51
def finalize_options(self):
52
self.set_undefined_options('build',
53
('build_base', 'build_base'),
54
('build_lib', 'build_lib'),
56
if self.package is None:
57
self.package = self.distribution.ext_package
58
self.extensions = self.distribution.ext_modules
59
self.libraries = self.distribution.libraries or []
60
self.py_modules = self.distribution.py_modules or []
61
self.data_files = self.distribution.data_files or []
63
if self.build_src is None:
64
plat_specifier = ".%s-%s" % (get_platform(), sys.version[0:3])
65
self.build_src = os.path.join(self.build_base, 'src'+plat_specifier)
66
if self.inplace is None:
67
build_ext = self.get_finalized_command('build_ext')
68
self.inplace = build_ext.inplace
70
# py_modules_dict is used in build_py.find_package_modules
71
self.py_modules_dict = {}
73
if self.f2pyflags is None:
76
self.f2pyflags = self.f2pyflags.split() # XXX spaces??
78
if self.swigflags is None:
81
self.swigflags = self.swigflags.split() # XXX spaces??
85
if not (self.extensions or self.libraries):
91
def build_sources(self):
94
self.get_package_dir = self.get_finalized_command('build_py')\
97
self.build_py_modules_sources()
99
for libname_info in self.libraries:
100
self.build_library_sources(*libname_info)
103
self.check_extensions_list(self.extensions)
105
for ext in self.extensions:
106
self.build_extension_sources(ext)
108
self.build_data_files_sources()
112
def build_data_files_sources(self):
113
if not self.data_files:
115
log.info('building data_files sources')
116
from numpy.distutils.misc_util import get_data_files
118
for data in self.data_files:
119
if isinstance(data,str):
120
new_data_files.append(data)
121
elif isinstance(data,tuple):
124
build_dir = self.get_package_dir('.'.join(d.split(os.sep)))
126
build_dir = os.path.join(self.build_src,d)
127
funcs = filter(callable,files)
128
files = filter(lambda f:not callable(f), files)
130
if f.func_code.co_argcount==1:
135
if isinstance(s,list):
137
elif isinstance(s,str):
140
raise TypeError(repr(s))
141
filenames = get_data_files((d,files))
142
new_data_files.append((d, filenames))
145
self.data_files[:] = new_data_files
148
def build_py_modules_sources(self):
149
if not self.py_modules:
151
log.info('building py_modules sources')
153
for source in self.py_modules:
154
if is_sequence(source) and len(source)==3:
155
package, module_base, source = source
157
build_dir = self.get_package_dir(package)
159
build_dir = os.path.join(self.build_src,
160
os.path.join(*package.split('.')))
162
target = os.path.join(build_dir, module_base + '.py')
163
source = source(target)
166
modules = [(package, module_base, source)]
167
if not self.py_modules_dict.has_key(package):
168
self.py_modules_dict[package] = []
169
self.py_modules_dict[package] += modules
171
new_py_modules.append(source)
172
self.py_modules[:] = new_py_modules
175
def build_library_sources(self, lib_name, build_info):
176
sources = list(build_info.get('sources',[]))
181
log.info('building library "%s" sources' % (lib_name))
183
sources = self.generate_sources(sources, (lib_name, build_info))
185
sources = self.template_sources(sources, (lib_name, build_info))
187
sources, h_files = self.filter_h_files(sources)
190
print self.package,'- nothing done with h_files=',h_files
193
# self.distribution.headers.append((lib_name,f))
195
build_info['sources'] = sources
198
def build_extension_sources(self, ext):
200
sources = list(ext.sources)
202
log.info('building extension "%s" sources' % (ext.name))
204
fullname = self.get_ext_fullname(ext.name)
206
modpath = fullname.split('.')
207
package = '.'.join(modpath[0:-1])
210
self.ext_target_dir = self.get_package_dir(package)
212
sources = self.generate_sources(sources, ext)
214
sources = self.template_sources(sources, ext)
216
sources = self.swig_sources(sources, ext)
218
sources = self.f2py_sources(sources, ext)
220
sources = self.pyrex_sources(sources, ext)
222
sources, py_files = self.filter_py_files(sources)
224
if not self.py_modules_dict.has_key(package):
225
self.py_modules_dict[package] = []
228
module = os.path.splitext(os.path.basename(f))[0]
229
modules.append((package, module, f))
230
self.py_modules_dict[package] += modules
232
sources, h_files = self.filter_h_files(sources)
235
print package,'- nothing done with h_files=',h_files
237
# self.distribution.headers.append((package,f))
239
ext.sources = sources
243
def generate_sources(self, sources, extension):
246
for source in sources:
247
if is_string(source):
248
new_sources.append(source)
250
func_sources.append(source)
253
if self.inplace and not is_sequence(extension):
254
build_dir = self.ext_target_dir
256
if is_sequence(extension):
258
# if not extension[1].has_key('include_dirs'):
259
# extension[1]['include_dirs'] = []
260
# incl_dirs = extension[1]['include_dirs']
262
name = extension.name
263
# incl_dirs = extension.include_dirs
264
#if self.build_src not in incl_dirs:
265
# incl_dirs.append(self.build_src)
266
build_dir = os.path.join(*([self.build_src]\
267
+name.split('.')[:-1]))
268
self.mkpath(build_dir)
269
for func in func_sources:
270
source = func(extension, build_dir)
273
if is_sequence(source):
274
[log.info(" adding '%s' to sources." % (s,)) for s in source]
275
new_sources.extend(source)
277
log.info(" adding '%s' to sources." % (source,))
278
new_sources.append(source)
282
def filter_py_files(self, sources):
283
return self.filter_files(sources,['.py'])
285
def filter_h_files(self, sources):
286
return self.filter_files(sources,['.h','.hpp','.inc'])
288
def filter_files(self, sources, exts = []):
291
for source in sources:
292
(base, ext) = os.path.splitext(source)
296
new_sources.append(source)
297
return new_sources, files
299
def template_sources(self, sources, extension):
301
if is_sequence(extension):
302
depends = extension[1].get('depends')
303
include_dirs = extension[1].get('include_dirs')
305
depends = extension.depends
306
include_dirs = extension.include_dirs
307
for source in sources:
308
(base, ext) = os.path.splitext(source)
309
if ext == '.src': # Template file
311
target_dir = os.path.dirname(base)
313
target_dir = appendpath(self.build_src, os.path.dirname(base))
314
self.mkpath(target_dir)
315
target_file = os.path.join(target_dir,os.path.basename(base))
316
if (self.force or newer_group([source] + depends, target_file)):
317
if _f_pyf_ext_match(base):
318
log.info("from_template:> %s" % (target_file))
319
outstr = process_f_file(source)
321
log.info("conv_template:> %s" % (target_file))
322
outstr = process_c_file(source)
323
fid = open(target_file,'w')
326
if _header_ext_match(target_file):
327
d = os.path.dirname(target_file)
328
if d not in include_dirs:
329
log.info(" adding '%s' to include_dirs." % (d))
330
include_dirs.append(d)
331
new_sources.append(target_file)
333
new_sources.append(source)
336
def pyrex_sources(self, sources, extension):
344
ext_name = extension.name.split('.')[-1]
345
for source in sources:
346
(base, ext) = os.path.splitext(source)
348
if self.inplace or not have_pyrex:
349
target_dir = os.path.dirname(base)
351
target_dir = appendpath(self.build_src, os.path.dirname(base))
352
target_file = os.path.join(target_dir, ext_name + '.c')
353
depends = [source] + extension.depends
354
if (self.force or newer_group(depends, target_file, 'newer')):
356
log.info("pyrexc:> %s" % (target_file))
357
self.mkpath(target_dir)
358
from Pyrex.Compiler import Main
359
options = Main.CompilationOptions(
360
defaults=Main.default_options,
361
output_file=target_file)
362
pyrex_result = Main.compile(source, options=options)
363
if pyrex_result.num_errors != 0:
364
raise RuntimeError("%d errors in Pyrex compile" %
365
pyrex_result.num_errors)
367
log.warn("Pyrex needed to compile %s but not available."\
368
" Using old target %s"\
369
% (source, target_file))
370
new_sources.append(target_file)
372
new_sources.append(source)
375
def f2py_sources(self, sources, extension):
381
ext_name = extension.name.split('.')[-1]
384
for source in sources:
385
(base, ext) = os.path.splitext(source)
386
if ext == '.pyf': # F2PY interface file
388
target_dir = os.path.dirname(base)
390
target_dir = appendpath(self.build_src, os.path.dirname(base))
391
if os.path.isfile(source):
392
name = get_f2py_modulename(source)
394
raise ValueError('mismatch of extension names: %s '
395
'provides %r but expected %r' % (
396
source, name, ext_name))
397
target_file = os.path.join(target_dir,name+'module.c')
399
log.debug(' source %s does not exist: skipping f2py\'ing.' \
403
target_file = os.path.join(target_dir,name+'module.c')
404
if not os.path.isfile(target_file):
405
log.debug(' target %s does not exist:\n '\
406
'Assuming %smodule.c was generated with '\
407
'"build_src --inplace" command.' \
408
% (target_file, name))
409
target_dir = os.path.dirname(base)
410
target_file = os.path.join(target_dir,name+'module.c')
411
if not os.path.isfile(target_file):
412
raise ValueError("%r missing" % (target_file,))
413
log.debug(' Yes! Using %s as up-to-date target.' \
415
target_dirs.append(target_dir)
416
f2py_sources.append(source)
417
f2py_targets[source] = target_file
418
new_sources.append(target_file)
419
elif fortran_ext_match(ext):
420
f_sources.append(source)
422
new_sources.append(source)
424
if not (f2py_sources or f_sources):
427
map(self.mkpath, target_dirs)
429
f2py_options = extension.f2py_options + self.f2pyflags
431
if self.distribution.libraries:
432
for name,build_info in self.distribution.libraries:
433
if name in extension.libraries:
434
f2py_options.extend(build_info.get('f2py_options',[]))
436
log.info("f2py options: %s" % (f2py_options))
439
if len(f2py_sources) != 1:
441
'only one .pyf file is allowed per extension module but got'\
442
' more: %r' % (f2py_sources,))
443
source = f2py_sources[0]
444
target_file = f2py_targets[source]
445
target_dir = os.path.dirname(target_file) or '.'
446
depends = [source] + extension.depends
447
if (self.force or newer_group(depends, target_file,'newer')) \
449
log.info("f2py: %s" % (source))
450
import numpy.f2py as f2py2e
451
f2py2e.run_main(f2py_options + ['--build-dir',target_dir,source])
453
log.debug(" skipping '%s' f2py interface (up-to-date)" % (source))
455
#XXX TODO: --inplace support for sdist command
456
if is_sequence(extension):
458
else: name = extension.name
459
target_dir = os.path.join(*([self.build_src]\
460
+name.split('.')[:-1]))
461
target_file = os.path.join(target_dir,ext_name + 'module.c')
462
new_sources.append(target_file)
463
depends = f_sources + extension.depends
464
if (self.force or newer_group(depends, target_file, 'newer')) \
466
import numpy.f2py as f2py2e
467
log.info("f2py:> %s" % (target_file))
468
self.mkpath(target_dir)
469
f2py2e.run_main(f2py_options + ['--lower',
470
'--build-dir',target_dir]+\
471
['-m',ext_name]+f_sources)
473
log.debug(" skipping f2py fortran files for '%s' (up-to-date)"\
476
if not os.path.isfile(target_file):
477
raise ValueError("%r missing" % (target_file,))
479
target_c = os.path.join(self.build_src,'fortranobject.c')
480
target_h = os.path.join(self.build_src,'fortranobject.h')
481
log.info(" adding '%s' to sources." % (target_c))
482
new_sources.append(target_c)
483
if self.build_src not in extension.include_dirs:
484
log.info(" adding '%s' to include_dirs." \
486
extension.include_dirs.append(self.build_src)
489
import numpy.f2py as f2py2e
490
d = os.path.dirname(f2py2e.__file__)
491
source_c = os.path.join(d,'src','fortranobject.c')
492
source_h = os.path.join(d,'src','fortranobject.h')
493
if newer(source_c,target_c) or newer(source_h,target_h):
494
self.mkpath(os.path.dirname(target_c))
495
self.copy_file(source_c,target_c)
496
self.copy_file(source_h,target_h)
498
if not os.path.isfile(target_c):
499
raise ValueError("%r missing" % (target_c,))
500
if not os.path.isfile(target_h):
501
raise ValueError("%r missing" % (target_h,))
503
for name_ext in ['-f2pywrappers.f','-f2pywrappers2.f90']:
504
filename = os.path.join(target_dir,ext_name + name_ext)
505
if os.path.isfile(filename):
506
log.info(" adding '%s' to sources." % (filename))
507
f_sources.append(filename)
509
return new_sources + f_sources
511
def swig_sources(self, sources, extension):
512
# Assuming SWIG 1.3.14 or later. See compatibility note in
513
# http://www.swig.org/Doc1.3/Python.html#Python_nn6
519
py_files = [] # swig generated .py files
524
ext_name = extension.name.split('.')[-1]
526
for source in sources:
527
(base, ext) = os.path.splitext(source)
528
if ext == '.i': # SWIG interface file
530
target_dir = os.path.dirname(base)
531
py_target_dir = self.ext_target_dir
533
target_dir = appendpath(self.build_src, os.path.dirname(base))
534
py_target_dir = target_dir
535
if os.path.isfile(source):
536
name = get_swig_modulename(source)
537
if name != ext_name[1:]:
539
'mismatch of extension names: %s provides %r'
540
' but expected %r' % (source, name, ext_name[1:]))
542
typ = get_swig_target(source)
547
assert typ == get_swig_target(source), repr(typ)
548
target_file = os.path.join(target_dir,'%s_wrap%s' \
549
% (name, target_ext))
551
log.debug(' source %s does not exist: skipping swig\'ing.' \
555
target_file = _find_swig_target(target_dir, name)
556
if not os.path.isfile(target_file):
557
log.debug(' target %s does not exist:\n '\
558
'Assuming %s_wrap.{c,cpp} was generated with '\
559
'"build_src --inplace" command.' \
560
% (target_file, name))
561
target_dir = os.path.dirname(base)
562
target_file = _find_swig_target(target_dir, name)
563
if not os.path.isfile(target_file):
564
raise ValueError("%r missing" % (target_file,))
565
log.debug(' Yes! Using %s as up-to-date target.' \
567
target_dirs.append(target_dir)
568
new_sources.append(target_file)
569
py_files.append(os.path.join(py_target_dir, name+'.py'))
570
swig_sources.append(source)
571
swig_targets[source] = new_sources[-1]
573
new_sources.append(source)
579
return new_sources + py_files
581
map(self.mkpath, target_dirs)
582
swig = self.find_swig()
583
swig_cmd = [swig, "-python"]
585
swig_cmd.append('-c++')
586
for d in extension.include_dirs:
587
swig_cmd.append('-I'+d)
588
for source in swig_sources:
589
target = swig_targets[source]
590
depends = [source] + extension.depends
591
if self.force or newer_group(depends, target, 'newer'):
592
log.info("%s: %s" % (os.path.basename(swig) \
593
+ (is_cpp and '++' or ''), source))
594
self.spawn(swig_cmd + self.swigflags \
595
+ ["-o", target, '-outdir', py_target_dir, source])
597
log.debug(" skipping '%s' swig interface (up-to-date)" \
600
return new_sources + py_files
602
_f_pyf_ext_match = re.compile(r'.*[.](f90|f95|f77|for|ftn|f|pyf)\Z',re.I).match
603
_header_ext_match = re.compile(r'.*[.](inc|h|hpp)\Z',re.I).match
605
#### SWIG related auxiliary functions ####
606
_swig_module_name_match = re.compile(r'\s*%module\s*(.*\(\s*package\s*=\s*"(?P<package>[\w_]+)".*\)|)\s*(?P<name>[\w_]+)',
608
_has_c_header = re.compile(r'-[*]-\s*c\s*-[*]-',re.I).search
609
_has_cpp_header = re.compile(r'-[*]-\s*c[+][+]\s*-[*]-',re.I).search
611
def get_swig_target(source):
615
if _has_cpp_header(line):
617
if _has_c_header(line):
622
def get_swig_modulename(source):
624
f_readlines = getattr(f,'xreadlines',f.readlines)
626
for line in f_readlines():
627
m = _swig_module_name_match(line)
629
name = m.group('name')
634
def _find_swig_target(target_dir,name):
635
for ext in ['.cpp','.c']:
636
target = os.path.join(target_dir,'%s_wrap%s' % (name, ext))
637
if os.path.isfile(target):
641
#### F2PY related auxiliary functions ####
643
_f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
645
_f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'\
646
'__user__[\w_]*)',re.I).match
648
def get_f2py_modulename(source):
651
f_readlines = getattr(f,'xreadlines',f.readlines)
652
for line in f_readlines():
653
m = _f2py_module_name_match(line)
655
if _f2py_user_module_name_match(line): # skip *__user__* names
657
name = m.group('name')
662
##########################################