Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENH: make f2py feature explicit + add support for f2py wo .pyf mode.

  • Loading branch information...
commit a93924cac957636d4d1dad9405c15a4383030f7c 1 parent 219742b
@cournape cournape authored
Showing with 307 additions and 28 deletions.
  1. +143 −27 f2py.py
  2. +162 −0 interface_gen.py
  3. +2 −1  scipy/fftpack/bscript
View
170 f2py.py
@@ -4,8 +4,12 @@
import numpy.f2py
import numpy.distutils.misc_util
-from waflib import Task
-from waflib.TaskGen import extension
+from waflib \
+ import \
+ Task
+from waflib.TaskGen \
+ import \
+ extension, feature, before_method, after_method
CGEN_TEMPLATE = '%smodule'
FOBJECT_FILE = 'fortranobject.c'
@@ -21,9 +25,19 @@
F2PY_UMODNAME_MATCH = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'\
'__user__[\w_]*)',re.I).match
# End of copy
+F2PY_INCLUDE_MATCH = re.compile(r"^\s+\<include_file=(\S+)\>")
+
def is_pyf(node):
return node.name.endswith(".pyf")
+def include_pyf(node):
+ includes = []
+ for line in node.read().splitlines():
+ m = F2PY_INCLUDE_MATCH.match(line)
+ if m:
+ includes.append(m.group(1))
+ return includes
+
def f2py_modulename(node):
"""This returns the name of the module from the pyf source file.
@@ -39,48 +53,150 @@ def f2py_modulename(node):
break
return name
-@extension('.pyf')
-def add_f2py_files(task_gen, node):
- ext = '.c'
-
+def generate_fake_interface(name, node):
+ """Generate a (fake) .pyf file from another pyf file (!)."""
+ content = """\
+python module %(name)s
+ usercode void empty_module(void) {}
+ interface
+ subroutine empty_module()
+ intent(c) empty_module
+ end subroutine empty_module
+ end interface
+end python module%(name)s
+"""
+ node.write(content % {"name": name})
+
+@feature('f2py')
+@before_method('apply_incpaths')
+def apply_f2py_includes(task_gen):
includes = task_gen.env["INCLUDES"]
d = op.dirname(numpy.f2py.__file__)
includes.append(op.join(d, 'src'))
includes.extend(numpy.distutils.misc_util.get_numpy_include_dirs())
task_gen.env["INCLUDES"] = includes
- if is_pyf(node):
- module_name = f2py_modulename(node)
- module_node = node.parent.find_or_declare("%smodule.c" % module_name)
- parent_node = module_node.parent
+@feature('f2py')
+@before_method('apply_link')
+def apply_f2py_fortran_sources(task_gen):
+ for s in task_gen.source:
+ if is_pyf(s):
+ return
+
+ if not hasattr(task_gen, "name"):
+ module_name = f2py_modulename(task_gen.source[0])
+ else:
+ module_name = task_gen.name
+
+ module_node = task_gen.source[0].parent.find_or_declare("%smodule.c" % module_name)
+ task_gen.create_compiled_task("c", module_node)
+
+ build_dir = module_node.parent.bldpath()
+
+ source = task_gen.source[:]
+ tsk = task_gen.create_task('f2py_fortran', source, module_node)
+ # FIXME: ask waf ML how flags sharing and co is supposed to work
+ tsk.env.F2PYFLAGS = task_gen.env.F2PYFLAGS[:]
+ tsk.env.F2PYFLAGS.extend(["--build-dir", build_dir])
+ tsk.env.F2PYFLAGS.extend(["--lower", "-m", module_name])
+ #add_f2py_extra(task_gen, module_node)
+ task_gen.source += tsk.outputs
+
+ parent_node = module_node.parent
- # Make sure module_node is a generated file to avoid overwriting user
- # content (I really don't like find_or_declare).
- assert module_node.is_bld()
- build_dir = parent_node.bldpath()
+ # Make sure module_node is a generated file to avoid overwriting user
+ # content (I really don't like find_or_declare).
+ assert module_node.is_bld()
- fortranobject_node = task_gen.bld.bldnode.find_node(op.join(F2PY_TEMP_DIR, FOBJECT_FILE))
- assert fortranobject_node is not None
- fwrapper_node = parent_node.find_or_declare(FWRAP_TEMPLATE % module_name)
- fwrapper_node.write("")
+ fortranobject_source_node = task_gen.bld.bldnode.find_node(op.join(F2PY_TEMP_DIR, FOBJECT_FILE))
+ assert fortranobject_source_node is not None
+ fortranobject_node = parent_node.make_node("fortranobject%s.c" % module_name)
+ fortranobject_node.write(fortranobject_source_node.read())
- # XXX: evil hack to make get_bld_sig work here. Find out how to do this properly
- fortranobject_node.is_bld = lambda : False
- fwrapper_node.is_bld = lambda : False
- task_gen.source.append(fortranobject_node)
- task_gen.source.append(fwrapper_node)
+ # FIXME: race condition
+ fwrapper_node = parent_node.make_node(FWRAP_TEMPLATE % module_name)
+ fwrapper_node.write("")
- task_gen.env.F2PYFLAGS.extend(["--build-dir", build_dir])
+ # XXX: evil hack to make get_bld_sig work here. Find out how to do this properly
+ fwrapper_node.is_bld = lambda : False
+ fortranobject_node.is_bld = lambda : False
- tsk = task_gen.create_task('f2py', node, module_node)
- task_gen.source += tsk.outputs
+ task_gen.create_compiled_task("c", fortranobject_node)
+ task_gen.create_compiled_task("fc", fwrapper_node)
+
+def add_f2py_extra(task_gen, module_node, module_name):
+ parent_node = module_node.parent
+
+ # Make sure module_node is a generated file to avoid overwriting user
+ # content (I really don't like find_or_declare).
+ assert module_node.is_bld()
+
+ fortranobject_source_node = task_gen.bld.bldnode.find_node(op.join(F2PY_TEMP_DIR, FOBJECT_FILE))
+ assert fortranobject_source_node is not None
+ fortranobject_node = parent_node.make_node("%s-fortranobject.c" % module_name)
+ fortranobject_node.write(fortranobject_source_node.read())
+
+ # FIXME: race condition
+ fwrapper_node = parent_node.make_node(FWRAP_TEMPLATE % module_name)
+ fwrapper_node.write("")
+ # XXX: evil hack to make get_bld_sig work here. Find out how to do this properly
+ fwrapper_node.is_bld = lambda : False
+ fortranobject_node.is_bld = lambda : False
+
+ task_gen.source.append(fortranobject_node)
+ task_gen.source.append(fwrapper_node)
+
+@extension('.pyf')
+def add_f2py_files(task_gen, node):
+ ext = '.c'
+
+ if not is_pyf(node):
+ raise ValueError("Gne ?")
+
+ if not hasattr(task_gen, "name"):
+ module_name = f2py_modulename(node)
else:
- raise NotImplementedError("non .pyf input not supported yet.")
+ module_name = task_gen.name
+ module_node = node.parent.find_or_declare("%smodule.c" % module_name)
+
+ includes = include_pyf(node)
+ use_interface = ("f2py_interface_gen" in task_gen.features) \
+ or ("f2py_fake_interface_gen" in task_gen.features) \
+ or len(includes) > 0
+ if use_interface:
+ from interface_gen import generate_interface
+
+ real_pyf = node.parent.find_or_declare("%s.pyf" % module_name)
+ # Guard against overwriting existing source code by accident. Did I
+ # say I hate find_or_declare ?
+ assert real_pyf.is_bld()
+ if "f2py_fake_interface_gen" in task_gen.features:
+ generate_fake_interface(module_name, real_pyf)
+ else:
+ generate_interface(module_name, node.abspath(), real_pyf.abspath())
+ node = real_pyf
+
+ # XXX: evil hack to make get_bld_sig work here. Find out how to do this properly
+ node.is_bld = lambda : False
+
+ add_f2py_extra(task_gen, module_node, module_name)
+
+ tsk = task_gen.create_task('f2py', node, module_node)
+ build_dir = module_node.parent.bldpath()
+ # FIXME: ask waf ML how flags sharing and co is supposed to work
+ tsk.env.F2PYFLAGS = task_gen.env.F2PYFLAGS[:]
+ tsk.env.F2PYFLAGS.extend(["--build-dir", build_dir])
+ task_gen.source += tsk.outputs
class f2py(Task.Task):
run_str = '${F2PY} ${F2PYFLAGS} ${SRC}'
color = 'CYAN'
+class f2py_fortran(Task.Task):
+ run_str = '${F2PY} ${F2PYFLAGS} ${SRC}'
+ color = 'CYAN'
+ ext_out = [".h"]
+
def configure(conf):
if not conf.env.CC and not conf.env.CXX:
conf.fatal('Load a C/C++ compiler first')
View
162 interface_gen.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+
+import os
+import re
+from distutils.dir_util import mkpath
+
+def all_subroutines(interface_in):
+ # remove comments
+ comment_block_exp = re.compile(r'/\*(?:\s|.)*?\*/')
+ subroutine_exp = re.compile(r'subroutine (?:\s|.)*?end subroutine.*')
+ function_exp = re.compile(r'function (?:\s|.)*?end function.*')
+
+ interface = comment_block_exp.sub('',interface_in)
+ subroutine_list = subroutine_exp.findall(interface)
+ function_list = function_exp.findall(interface)
+ subroutine_list = subroutine_list + function_list
+ subroutine_list = map(lambda x: x.strip(),subroutine_list)
+ return subroutine_list
+
+def real_convert(val_string):
+ return val_string
+
+def complex_convert(val_string):
+ return '(' + val_string + ',0.)'
+
+def convert_types(interface_in,converter):
+ regexp = re.compile(r'<type_convert=(.*?)>')
+ interface = interface_in[:]
+ while 1:
+ sub = regexp.search(interface)
+ if sub is None: break
+ converted = converter(sub.group(1))
+ interface = interface.replace(sub.group(),converted)
+ return interface
+
+def generic_expand(generic_interface,skip_names=[]):
+ generic_types ={'s' :('real', 'real', real_convert,
+ 'real'),
+ 'd' :('double precision','double precision',real_convert,
+ 'double precision'),
+ 'c' :('complex', 'complex',complex_convert,
+ 'real'),
+ 'z' :('double complex', 'double complex',complex_convert,
+ 'double precision'),
+ 'cs':('complex', 'real',complex_convert,
+ 'real'),
+ 'zd':('double complex', 'double precision',complex_convert,
+ 'double precision'),
+ 'sc':('real', 'complex',real_convert,
+ 'real'),
+ 'dz':('double precision','double complex', real_convert,
+ 'double precision')}
+ generic_c_types = {'real':'float',
+ 'double precision':'double',
+ 'complex':'complex_float',
+ 'double complex':'complex_double'}
+ # cc_types is specific in ATLAS C BLAS, in particular, for complex arguments
+ generic_cc_types = {'real':'float',
+ 'double precision':'double',
+ 'complex':'void',
+ 'double complex':'void'}
+ #2. get all subroutines
+ subs = all_subroutines(generic_interface)
+ #print len(subs)
+ #loop through the subs
+ type_exp = re.compile(r'<tchar=(.*?)>')
+ TYPE_EXP = re.compile(r'<TCHAR=(.*?)>')
+ routine_name = re.compile(r'(subroutine|function)\s*(?P<name>\w+)\s*\(')
+ interface = ''
+ for sub in subs:
+ #3. Find the typecodes to use:
+ m = type_exp.search(sub)
+ if m is None:
+ interface = interface + '\n\n' + sub
+ continue
+ type_chars = m.group(1)
+ # get rid of spaces
+ type_chars = type_chars.replace(' ','')
+ # get a list of the characters (or character pairs)
+ type_chars = type_chars.split(',')
+ # Now get rid of the special tag that contained the types
+ sub = re.sub(type_exp,'<tchar>',sub)
+ m = TYPE_EXP.search(sub)
+ if m is not None:
+ sub = re.sub(TYPE_EXP,'<TCHAR>',sub)
+ sub_generic = sub.strip()
+ for char in type_chars:
+ type_in,type_out,converter, rtype_in = generic_types[char]
+ sub = convert_types(sub_generic,converter)
+ function_def = sub.replace('<tchar>',char)
+ function_def = function_def.replace('<TCHAR>',char.upper())
+ function_def = function_def.replace('<type_in>',type_in)
+ function_def = function_def.replace('<type_in_c>',
+ generic_c_types[type_in])
+ function_def = function_def.replace('<type_in_cc>',
+ generic_cc_types[type_in])
+ function_def = function_def.replace('<rtype_in>',rtype_in)
+ function_def = function_def.replace('<rtype_in_c>',
+ generic_c_types[rtype_in])
+ function_def = function_def.replace('<type_out>',type_out)
+ function_def = function_def.replace('<type_out_c>',
+ generic_c_types[type_out])
+ m = routine_name.match(function_def)
+ if m:
+ if m.group('name') in skip_names:
+ print 'Skipping',m.group('name')
+ continue
+ else:
+ print 'Possible bug: Failed to determine routines name'
+ interface = interface + '\n\n' + function_def
+
+ return interface
+
+#def interface_to_module(interface_in,module_name,include_list,sdir='.'):
+def interface_to_module(interface_in,module_name):
+ pre_prefix = "!%f90 -*- f90 -*-\n"
+ # heading and tail of the module definition.
+ file_prefix = "\npython module " + module_name +" ! in\n" \
+ "!usercode '''#include \"cblas.h\"\n"\
+ "!'''\n"\
+ " interface \n"
+ file_suffix = "\n end interface\n" \
+ "end module %s" % module_name
+ return pre_prefix + file_prefix + interface_in + file_suffix
+
+def process_includes(interface_in,sdir='.'):
+ include_exp = re.compile(r'\n\s*[^!]\s*<include_file=(.*?)>')
+ include_files = include_exp.findall(interface_in)
+ for filename in include_files:
+ f = open(os.path.join(sdir,filename))
+ interface_in = interface_in.replace('<include_file=%s>'%filename,
+ f.read())
+ f.close()
+ return interface_in
+
+def generate_interface(module_name,src_file,target_file,skip_names=[]):
+ #print "generating",module_name,"interface"
+ f = open(src_file)
+ generic_interface = f.read()
+ f.close()
+ sdir = os.path.dirname(src_file)
+ generic_interface = process_includes(generic_interface,sdir)
+ generic_interface = generic_expand(generic_interface,skip_names)
+ module_def = interface_to_module(generic_interface,module_name)
+ mkpath(os.path.dirname(target_file))
+ f = open(target_file,'w')
+ user_routines = os.path.join(sdir,module_name+"_user_routines.pyf")
+ if os.path.exists(user_routines):
+ f2 = open(user_routines)
+ f.write(f2.read())
+ f2.close()
+ f.write(module_def)
+ f.close()
+
+def process_all():
+ # process the standard files.
+ for name in ['fblas','cblas','clapack','flapack']:
+ generate_interface(name,'generic_%s.pyf'%(name),name+'.pyf')
+
+
+if __name__ == "__main__":
+ process_all()
View
3  scipy/fftpack/bscript
@@ -5,9 +5,10 @@ def pbuild(context):
bld = context.waf_context
def builder(extension):
- bld(features="c pyext cshlib bento",
+ bld(features="c pyext cshlib bento f2py",
source=extension.sources,
target=extension.name,
includes="src",
use="dfftpack fftpack")
context.register_builder("_fftpack", builder)
+ context.register_builder("convolve", builder)
Please sign in to comment.
Something went wrong with that request. Please try again.