"""
Tools for building F2PY generated extension modules.
-----
Permission to use, modify, and distribute this software is given under the
terms of the NumPy License. See http://scipy.org.
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
Author: Pearu Peterson <pearu@cens.ioc.ee>
Created: Oct 2006
-----
"""
import os
import re
import sys
import tempfile
try:
from numpy import __version__
except ImportError:
numpy_version = 'N/A'
__all__ = ['main', 'compile']
__usage__ = """
F2PY G3 --- The third generation of Fortran to Python Interface Generator
=========================================================================
Description
-----------
f2py program generates a Python C/API file (<modulename>module.c) that
contains wrappers for given Fortran functions and data so that they
can be accessed from Python. With the -c option the corresponding
extension modules are built.
Options
-------
--g3-numpy Use numpy.f2py.lib tool, the 3rd generation of F2PY,
with NumPy support.
--2d-numpy Use numpy.f2py tool with NumPy support. [DEFAULT]
--2d-numeric Use f2py2e tool with Numeric support.
--2d-numarray Use f2py2e tool with Numarray support.
-m <modulename> Name of the module; f2py generates a Python/C API
file <modulename>module.c or extension module <modulename>.
For wrapping Fortran 90 modules, f2py will use Fortran
module names.
--parse Parse Fortran files and print result to stdout.
Options effective only with -h
------------------------------
-h <filename> Write signatures of the fortran routines to file <filename>
and exit. You can then edit <filename> and use it instead
of <fortran files> for generating extension module source.
If <filename> is stdout or stderr then the signatures are
printed to the corresponding stream.
--overwrite-signature Overwrite existing signature file.
Options effective only with -c
------------------------------
-c Compile fortran sources and build extension module.
--build-dir <dirname> All f2py generated files are created in <dirname>.
Default is tempfile.mktemp() and it will be removed after
f2py stops unless <dirname> is specified via --build-dir
option.
numpy.distutils options effective only with -c
----------------------------------------------
--fcompiler=<name> Specify Fortran compiler type by vendor
Extra options effective only with -c
------------------------------------
-L/path/to/lib/ -l<libname>
-D<name[=define]> -U<name>
-I/path/to/include/
<filename>.o <filename>.(so|dynlib|dll) <filename>.a
Using the following macros may be required with non-gcc Fortran
compilers:
-DPREPEND_FORTRAN -DNO_APPEND_FORTRAN -DUPPERCASE_FORTRAN
-DUNDERSCORE_G77
-DF2PY_DEBUG_PYOBJ_TOFROM --- pyobj_(to|from)_<ctype> functions will
print debugging messages to stderr.
"""
import re
import shutil
import parser.api
from parser.api import parse,PythonModule,EndStatement,Module,Subroutine,Function,\
get_reader
def get_values(sys_argv, prefix='', suffix='', strip_prefix=False, strip_suffix=False):
"""
Return a list of values with pattern
<prefix><value><suffix>.
The corresponding items will be removed from sys_argv.
"""
match = re.compile(prefix + r'.*' + suffix + '\Z').match
ret = [item for item in sys_argv if match(item)]
[sys_argv.remove(item) for item in ret]
if strip_prefix and prefix:
i = len(prefix)
ret = [item[i:] for item in ret]
if strip_suffix and suffix:
i = len(suffix)
ret = [item[:-i] for item in ret]
return ret
def get_option(sys_argv, option, default_return = None):
"""
Return True if sys_argv has <option>.
If <option> is not in sys_argv, return default_return.
<option> (when present) will be removed from sys_argv.
"""
try:
i = sys_argv.index(option)
except ValueError:
return default_return
del sys_argv[i]
return True
def get_option_value(sys_argv, option, default_value = None, default_return = None):
"""
Return <value> from
sys_argv = [...,<option>,<value>,...]
list.
If <option> is the last element, return default_value.
If <option> is not in sys_argv, return default_return.
Both <option> and <value> (when present) will be removed from sys_argv.
"""
try:
i = sys_argv.index(option)
except ValueError:
return default_return
if len(sys_argv)-1==i:
del sys_argv[i]
return default_value
value = sys_argv[i+1]
del sys_argv[i+1]
del sys_argv[i]
return value
def get_signature_output(sys_argv):
return get_option_value(sys_argv,'-h','stdout')
def parse_files(sys_argv):
flag = 'file'
file_names = []
only_names = []
skip_names = []
options = []
for word in sys_argv:
if word=='': pass
elif word=='only:': flag = 'only'
elif word=='skip:': flag = 'skip'
elif word==':': flag = 'file'
elif word.startswith('--'): options.append(word)
else:
{'file': file_names,'only': only_names, 'skip': skip_names}[flag].append(word)
if options:
sys.stderr.write('Unused options: %s\n' % (', '.join(options)))
for filename in file_names:
if not os.path.isfile(filename):
sys.stderr.write('No or not a file %r. Skipping.\n' % (filename))
continue
sys.stderr.write('Parsing %r..\n' % (filename))
reader = parser.api.get_reader(filename)
print parser.api.Fortran2003.Program(reader)
return
def dump_signature(sys_argv):
""" Read Fortran files and dump the signatures to file or stdout.
XXX: Not well tested.
"""
signature_output = get_signature_output(sys_argv)
# initialize output stream
if signature_output in ['stdout','stderr']:
output_stream = getattr(sys, signature_output)
modulename = get_option_value(sys_argv,'-m','untitled','unknown')
else:
name,ext = os.path.splitext(signature_output)
if ext != '.pyf':
signature_output += '.pyf'
if os.path.isfile(signature_output):
overwrite = get_option(sys_argv, '--overwrite-signature', False)
if not overwrite:
print >> sys.stderr, 'Signature file %r exists. '\
'Use --overwrite-signature to overwrite.' % (signature_output)
sys.exit()
modulename = get_option_value(sys_argv,'-m',os.path.basename(name),
os.path.basename(name))
output_stream = open(signature_output,'w')
flag = 'file'
file_names = []
only_names = []
skip_names = []
options = []
for word in sys_argv:
if word=='': pass
elif word=='only:': flag = 'only'
elif word=='skip:': flag = 'skip'
elif word==':': flag = 'file'
elif word.startswith('--'): options.append(word)
else:
{'file': file_names,'only': only_names,
'skip': skip_names}[flag].append(word)
if options:
sys.stderr.write('Unused options: %s\n' % (', '.join(options)))
output_stream.write('''! -*- f90 -*-
! Note: the context of this file is case sensitive.
''')
output_stream.write('PYTHON MODULE %s\n' % (modulename))
output_stream.write(' INTERFACE\n\n')
for filename in file_names:
if not os.path.isfile(filename):
sys.stderr.write('No or not a file %r. Skipping.\n' % (filename))
continue
sys.stderr.write('Parsing %r..\n' % (filename))
block = parse(filename)
if block is None:
sys.exit(1)
output_stream.write('! File: %s, source mode = %r\n' % (filename, block.reader.mode))
if block.content and isinstance(block.content[0],PythonModule):
for subblock in block.content[0].content[0].content:
if isinstance(subblock, EndStatement):
break
output_stream.write(subblock.topyf(' ')+'\n')
else:
output_stream.write(block.topyf(' ')+'\n')
output_stream.write(' END INTERFACE\n')
output_stream.write('END PYTHON MODULE %s\n' % (modulename))
if signature_output not in ['stdout','stderr']:
output_stream.close()
return
def construct_extension_sources(modulename, parse_files, include_dirs, build_dir):
"""
Construct wrapper sources.
"""
from py_wrap import PythonWrapperModule
f90_modules = []
external_subprograms = []
for filename in parse_files:
if not os.path.isfile(filename):
sys.stderr.write('No or not a file %r. Skipping.\n' % (filename))
continue
sys.stderr.write('Parsing %r..\n' % (filename))
for block in parse(filename, include_dirs=include_dirs).content:
if isinstance(block, Module):
f90_modules.append(block)
elif isinstance(block, (Subroutine, Function)):
external_subprograms.append(block)
else:
sys.stderr.write("Unhandled structure: %r\n" % (block.__class__))
module_infos = []
for block in f90_modules:
wrapper = PythonWrapperModule(block.name)
wrapper.add(block)
c_code = wrapper.c_code()
f_code = '! -*- f90 -*-\n' + wrapper.fortran_code()
c_fn = os.path.join(build_dir,'%smodule.c' % (block.name))
f_fn = os.path.join(build_dir,'%s_f_wrappers_f2py.f90' % (block.name))
f = open(c_fn,'w')
f.write(c_code)
f.close()
f = open(f_fn,'w')
f.write(f_code)
f.close()
#f_lib = '%s_f_wrappers_f2py' % (block.name)
module_info = {'name':block.name, 'c_sources':[c_fn],
'f_sources':[f_fn], 'language':'f90'}
module_infos.append(module_info)
if external_subprograms:
wrapper = PythonWrapperModule(modulename)
for block in external_subprograms:
wrapper.add(block)
c_code = wrapper.c_code()
f_code = wrapper.fortran_code()
c_fn = os.path.join(build_dir,'%smodule.c' % (modulename))
ext = '.f'
language = 'f77'
if wrapper.isf90:
f_code = '! -*- f90 -*-\n' + f_code
ext = '.f90'
language = 'f90'
f_fn = os.path.join(build_dir,'%s_f_wrappers_f2py%s' % (modulename, ext))
f = open(c_fn,'w')
f.write(c_code)
f.close()
f = open(f_fn,'w')
f.write(f_code)
f.close()
module_info = {'name':modulename, 'c_sources':[c_fn],
'f_sources':[f_fn], 'language':language}
module_infos.append(module_info)
return module_infos
def build_extension(sys_argv, sources_only = False):
"""
Build wrappers to Fortran 90 modules and external subprograms.
"""
modulename = get_option_value(sys_argv,'-m','untitled','unspecified')
if sources_only:
build_dir = get_option_value(sys_argv,'--build-dir','.','')
else:
build_dir = get_option_value(sys_argv,'--build-dir','.',None)
if build_dir is None:
build_dir = tempfile.mktemp()
clean_build_dir = True
else:
clean_build_dir = False
if build_dir and not os.path.exists(build_dir): os.makedirs(build_dir)
include_dirs = get_values(sys_argv,'-I',strip_prefix=True)
library_dirs = get_values(sys_argv,'-L',strip_prefix=True)
libraries = get_values(sys_argv,'-l',strip_prefix=True)
_define_macros = get_values(sys_argv,'-D',strip_prefix=True)
undef_macros = get_values(sys_argv,'-U',strip_prefix=True)
extra_objects = get_values(sys_argv,'','[.](o|a|so|dll|dylib|sl)')
define_macros = []
for item in _define_macros:
name_value = item.split('=',1)
if len(name_value)==1:
name_value.append(None)
if len(name_value)==2:
define_macros.append(tuple(name_value))
else:
print 'Invalid use of -D:',name_value
pyf_files = get_values(sys_argv,'','[.]pyf')
fortran_files = get_values(sys_argv,'','[.](f|f90|F90|F)')
c_files = get_values(sys_argv,'','[.](c|cpp|C|CPP|c[+][+])')
fc_flags = get_values(sys_argv,'--fcompiler=')
options = get_values(sys_argv,'-')
if options:
sys.stderr.write('Unused options: %s\n' % (', '.join(options)))
if pyf_files:
parse_files = pyf_files
else:
parse_files = fortran_files + c_files
module_infos = construct_extension_sources(modulename, parse_files, include_dirs, build_dir)
if sources_only:
return
def configuration(parent_package='', top_path=None or ''):
from numpy.distutils.misc_util import Configuration
config = Configuration('',parent_package,top_path)
flibname = modulename + '_fortran_f2py'
if fortran_files:
config.add_library(flibname,
sources = fortran_files)
libraries.insert(0,flibname)
for module_info in module_infos:
name = module_info['name']
c_sources = module_info['c_sources']
f_sources = module_info['f_sources']
language = module_info['language']
if f_sources:
f_lib = '%s_f_wrappers_f2py' % (name)
config.add_library(f_lib, sources = f_sources)
libs = [f_lib] + libraries
else:
libs = libraries
config.add_extension(name,
sources=c_sources + c_files,
libraries = libs,
define_macros = define_macros,
undef_macros = undef_macros,
include_dirs = include_dirs,
extra_objects = extra_objects,
language = language,
)
return config
old_sys_argv = sys.argv[:]
build_dir_ext_temp = os.path.join(build_dir,'ext_temp')
build_dir_clib_temp = os.path.join(build_dir,'clib_temp')
build_dir_clib_clib = os.path.join(build_dir,'clib_clib')
new_sys_argv = [sys.argv[0]] + ['build_ext',
'--build-temp',build_dir_ext_temp,
'--build-lib',build_dir,
'build_clib',
'--build-temp',build_dir_clib_temp,
'--build-clib',build_dir_clib_clib,
]
temp_dirs = [build_dir_ext_temp, build_dir_clib_temp, build_dir_clib_clib]
if fc_flags:
new_sys_argv += ['config_fc'] + fc_flags
sys.argv[:] = new_sys_argv
sys.stderr.write('setup arguments: %r\n' % (' '.join(sys.argv)))
from numpy.distutils.core import setup
setup(configuration=configuration)
sys.argv[:] = old_sys_argv
if 1 or clean_build_dir:
for d in temp_dirs:
if os.path.exists(d):
sys.stderr.write('Removing build directory %s\n'%(d))
shutil.rmtree(d)
return
def main(sys_argv = None):
""" Main function of f2py script.
"""
if sys_argv is None:
sys_argv = sys.argv[1:]
if '--help-link' in sys_argv:
sys_argv.remove('--help-link')
from numpy.distutils.system_info import show_all
show_all()
return
if '-c' in sys_argv:
sys_argv.remove('-c')
build_extension(sys_argv)
return
if '--parse' in sys_argv:
sys_argv.remove('--parse')
parse_files(sys_argv)
return
if '-h' in sys_argv:
dump_signature(sys_argv)
return
if not sys_argv or '--help' in sys_argv:
print >> sys.stdout, __usage__
build_extension(sys_argv, sources_only = True)
return
def compile(source,
jobname = 'untitled',
extra_args = [],
source_ext = None,
modulenames = None
):
"""
Build extension module from processing source with f2py.
jobname - the name of compile job. For non-module source
this will be also the name of extension module.
modulenames - the list of extension module names that
the given compilation job should create.
extra_args - a list of extra arguments for numpy style
setup.py command line.
source_ext - extension of the Fortran source file: .f90 or .f
Extension modules are saved to current working directory.
Returns a list of module objects according to modulenames
input.
"""
from nary import encode
tempdir = tempfile.gettempdir()
s = 'f2pyjob_%s_%s' % (jobname, encode(source))
tmpdir = os.path.join(tempdir, s)
if source_ext is None:
reader = get_reader(source)
source_ext = {'free90':'.f90','fix90':'.f90','fix77':'.f','pyf':'.pyf'}[reader.mode]
if modulenames is None:
modulenames = jobname,
if os.path.isdir(tmpdir):
sys.path.insert(0, tmpdir)
try:
modules = []
for modulename in modulenames:
exec('import %s as m' % (modulename))
modules.append(m)
sys.path.pop(0)
return modules
except ImportError:
pass
sys.path.pop(0)
else:
os.mkdir(tmpdir)
fname = os.path.join(tmpdir,'%s_src%s' % (jobname, source_ext))
f = open(fname,'w')
f.write(source)
f.close()
sys_argv = []
sys_argv.extend(['--build-dir',tmpdir])
#sys_argv.extend(['-DF2PY_DEBUG_PYOBJ_TOFROM'])
sys_argv.extend(['-m',jobname, fname])
build_extension(sys_argv + extra_args)
sys.path.insert(0, tmpdir)
modules = []
for modulename in modulenames:
exec('import %s as m' % (modulename))
modules.append(m)
sys.path.pop(0)
return modules
#EOF
|