Skip to content

Commit

Permalink
ENH: make f2py feature explicit + add support for f2py wo .pyf mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
cournape committed Jun 3, 2011
1 parent 219742b commit a93924c
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 28 deletions.
170 changes: 143 additions & 27 deletions f2py.py
Expand Up @@ -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'
Expand All @@ -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.
Expand All @@ -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')
Expand Down
162 changes: 162 additions & 0 deletions 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()
3 changes: 2 additions & 1 deletion scipy/fftpack/bscript
Expand Up @@ -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)

0 comments on commit a93924c

Please sign in to comment.