diff --git a/f2py.py b/f2py.py index a105b351a774..5c2ce847f5de 100644 --- a/f2py.py +++ b/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[\w_]*?'\ '__user__[\w_]*)',re.I).match # End of copy +F2PY_INCLUDE_MATCH = re.compile(r"^\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') diff --git a/interface_gen.py b/interface_gen.py new file mode 100755 index 000000000000..5982d0cb00a4 --- /dev/null +++ b/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'') + 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'') + TYPE_EXP = re.compile(r'') + routine_name = re.compile(r'(subroutine|function)\s*(?P\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,'',sub) + m = TYPE_EXP.search(sub) + if m is not None: + sub = re.sub(TYPE_EXP,'',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('',char) + function_def = function_def.replace('',char.upper()) + function_def = function_def.replace('',type_in) + function_def = function_def.replace('', + generic_c_types[type_in]) + function_def = function_def.replace('', + generic_cc_types[type_in]) + function_def = function_def.replace('',rtype_in) + function_def = function_def.replace('', + generic_c_types[rtype_in]) + function_def = function_def.replace('',type_out) + function_def = function_def.replace('', + 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_files = include_exp.findall(interface_in) + for filename in include_files: + f = open(os.path.join(sdir,filename)) + interface_in = interface_in.replace(''%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() diff --git a/scipy/fftpack/bscript b/scipy/fftpack/bscript index f3272173238b..e87741c8b0ce 100644 --- a/scipy/fftpack/bscript +++ b/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)