summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2019-12-15 12:22:36 -0800
committerGitHub <noreply@github.com>2019-12-15 12:22:36 -0800
commit740c7f5a089adceebd445b444f32d87ad09c7c14 (patch)
tree7a9bf425ff4574e715180bafb726629046f6f276
parent2fc10a279edfd132ec27eeac9e72f5a02a8bae5e (diff)
parent7142a6c979d6cb2fb514e6974bdb2639d387015a (diff)
downloadnumpy-740c7f5a089adceebd445b444f32d87ad09c7c14.tar.gz
Merge pull request #15106 from pv/f2py-f2cmap
ENH: f2py: add --f2cmap option for specifying the name of .f2py_f2cmap
-rw-r--r--doc/release/upcoming_changes/15106.new_feature.rst4
-rw-r--r--doc/source/f2py/advanced.rst52
-rw-r--r--numpy/f2py/capi_maps.py28
-rwxr-xr-xnumpy/f2py/f2py2e.py15
-rw-r--r--numpy/f2py/tests/test_assumed_shape.py22
-rw-r--r--numpy/f2py/tests/util.py23
6 files changed, 125 insertions, 19 deletions
diff --git a/doc/release/upcoming_changes/15106.new_feature.rst b/doc/release/upcoming_changes/15106.new_feature.rst
new file mode 100644
index 000000000..9f1d0b247
--- /dev/null
+++ b/doc/release/upcoming_changes/15106.new_feature.rst
@@ -0,0 +1,4 @@
+Add ``--f2cmap`` option to F2PY
+-------------------------------
+Allow specifying a file to load Fortran-to-C type map
+customizations from.
diff --git a/doc/source/f2py/advanced.rst b/doc/source/f2py/advanced.rst
index c9f3862e6..375922033 100644
--- a/doc/source/f2py/advanced.rst
+++ b/doc/source/f2py/advanced.rst
@@ -43,3 +43,55 @@ In Python:
.. include:: var_session.dat
:literal:
+
+
+Dealing with KIND specifiers
+============================
+
+Currently, F2PY can handle only ``<type spec>(kind=<kindselector>)``
+declarations where ``<kindselector>`` is a numeric integer (e.g. 1, 2,
+4,...), but not a function call ``KIND(..)`` or any other
+expression. F2PY needs to know what would be the corresponding C type
+and a general solution for that would be too complicated to implement.
+
+However, F2PY provides a hook to overcome this difficulty, namely,
+users can define their own <Fortran type> to <C type> maps. For
+example, if Fortran 90 code contains::
+
+ REAL(kind=KIND(0.0D0)) ...
+
+then create a mapping file containing a Python dictionary::
+
+ {'real': {'KIND(0.0D0)': 'double'}}
+
+for instance.
+
+Use the ``--f2cmap`` command-line option to pass the file name to F2PY.
+By default, F2PY assumes file name is ``.f2py_f2cmap`` in the current
+working directory.
+
+Or more generally, the f2cmap file must contain a dictionary
+with items::
+
+ <Fortran typespec> : {<selector_expr>:<C type>}
+
+that defines mapping between Fortran type::
+
+ <Fortran typespec>([kind=]<selector_expr>)
+
+and the corresponding <C type>. <C type> can be one of the following::
+
+ char
+ signed_char
+ short
+ int
+ long_long
+ float
+ double
+ long_double
+ complex_float
+ complex_double
+ complex_long_double
+ string
+
+For more information, see F2Py source code ``numpy/f2py/capi_maps.py``.
diff --git a/numpy/f2py/capi_maps.py b/numpy/f2py/capi_maps.py
index c41dd77c6..ce79f680f 100644
--- a/numpy/f2py/capi_maps.py
+++ b/numpy/f2py/capi_maps.py
@@ -179,17 +179,29 @@ f2cmap_all = {'real': {'': 'float', '4': 'float', '8': 'double',
'character': {'': 'string'}
}
-if os.path.isfile('.f2py_f2cmap'):
+f2cmap_default = copy.deepcopy(f2cmap_all)
+
+
+def load_f2cmap_file(f2cmap_file):
+ global f2cmap_all
+
+ f2cmap_all = copy.deepcopy(f2cmap_default)
+
+ if f2cmap_file is None:
+ # Default value
+ f2cmap_file = '.f2py_f2cmap'
+ if not os.path.isfile(f2cmap_file):
+ return
+
# User defined additions to f2cmap_all.
- # .f2py_f2cmap must contain a dictionary of dictionaries, only. For
+ # f2cmap_file must contain a dictionary of dictionaries, only. For
# example, {'real':{'low':'float'}} means that Fortran 'real(low)' is
# interpreted as C 'float'. This feature is useful for F90/95 users if
# they use PARAMETERSs in type specifications.
try:
- outmess('Reading .f2py_f2cmap ...\n')
- f = open('.f2py_f2cmap', 'r')
- d = eval(f.read(), {}, {})
- f.close()
+ outmess('Reading f2cmap from {!r} ...\n'.format(f2cmap_file))
+ with open(f2cmap_file, 'r') as f:
+ d = eval(f.read(), {}, {})
for k, d1 in list(d.items()):
for k1 in list(d1.keys()):
d1[k1.lower()] = d1[k1]
@@ -208,10 +220,10 @@ if os.path.isfile('.f2py_f2cmap'):
else:
errmess("\tIgnoring map {'%s':{'%s':'%s'}}: '%s' must be in %s\n" % (
k, k1, d[k][k1], d[k][k1], list(c2py_map.keys())))
- outmess('Successfully applied user defined changes from .f2py_f2cmap\n')
+ outmess('Successfully applied user defined f2cmap changes\n')
except Exception as msg:
errmess(
- 'Failed to apply user defined changes from .f2py_f2cmap: %s. Skipping.\n' % (msg))
+ 'Failed to apply user defined f2cmap changes: %s. Skipping.\n' % (msg))
cformat_map = {'double': '%g',
'float': '%g',
diff --git a/numpy/f2py/f2py2e.py b/numpy/f2py/f2py2e.py
index 110337f92..d03eff9e3 100755
--- a/numpy/f2py/f2py2e.py
+++ b/numpy/f2py/f2py2e.py
@@ -28,6 +28,7 @@ from . import auxfuncs
from . import cfuncs
from . import f90mod_rules
from . import __version__
+from . import capi_maps
f2py_version = __version__.version
errmess = sys.stderr.write
@@ -118,6 +119,9 @@ Options:
--link-<resource> switch below. [..] is optional list
of resources names. E.g. try 'f2py --help-link lapack_opt'.
+ --f2cmap <filename> Load Fortran-to-Python KIND specification from the given
+ file. Default: .f2py_f2cmap in current directory.
+
--quiet Run quietly.
--verbose Run with extra verbosity.
-v Print f2py version ID and exit.
@@ -175,7 +179,7 @@ http://cens.ioc.ee/projects/f2py2e/""" % (f2py_version, numpy_version)
def scaninputline(inputline):
files, skipfuncs, onlyfuncs, debug = [], [], [], []
- f, f2, f3, f5, f6, f7, f8, f9 = 1, 0, 0, 0, 0, 0, 0, 0
+ f, f2, f3, f5, f6, f7, f8, f9, f10 = 1, 0, 0, 0, 0, 0, 0, 0, 0
verbose = 1
dolc = -1
dolatexdoc = 0
@@ -226,6 +230,8 @@ def scaninputline(inputline):
f8 = 1
elif l == '--f2py-wrapper-output':
f9 = 1
+ elif l == '--f2cmap':
+ f10 = 1
elif l == '--overwrite-signature':
options['h-overwrite'] = 1
elif l == '-h':
@@ -267,6 +273,9 @@ def scaninputline(inputline):
elif f9:
f9 = 0
options["f2py_wrapper_output"] = l
+ elif f10:
+ f10 = 0
+ options["f2cmap_file"] = l
elif f == 1:
try:
with open(l):
@@ -312,6 +321,7 @@ def scaninputline(inputline):
options['wrapfuncs'] = wrapfuncs
options['buildpath'] = buildpath
options['include_paths'] = include_paths
+ options.setdefault('f2cmap_file', None)
return files, options
@@ -422,6 +432,7 @@ def run_main(comline_list):
fobjcsrc = os.path.join(f2pydir, 'src', 'fortranobject.c')
files, options = scaninputline(comline_list)
auxfuncs.options = options
+ capi_maps.load_f2cmap_file(options['f2cmap_file'])
postlist = callcrackfortran(files, options)
isusedby = {}
for i in range(len(postlist)):
@@ -574,7 +585,7 @@ def run_compile():
modulename = 'untitled'
sources = sys.argv[1:]
- for optname in ['--include_paths', '--include-paths']:
+ for optname in ['--include_paths', '--include-paths', '--f2cmap']:
if optname in sys.argv:
i = sys.argv.index(optname)
f2py_flags.extend(sys.argv[i:i + 2])
diff --git a/numpy/f2py/tests/test_assumed_shape.py b/numpy/f2py/tests/test_assumed_shape.py
index 460afd68d..e5695a61c 100644
--- a/numpy/f2py/tests/test_assumed_shape.py
+++ b/numpy/f2py/tests/test_assumed_shape.py
@@ -2,6 +2,7 @@ from __future__ import division, absolute_import, print_function
import os
import pytest
+import tempfile
from numpy.testing import assert_
from . import util
@@ -16,6 +17,7 @@ class TestAssumedShapeSumExample(util.F2PyTest):
_path('src', 'assumed_shape', 'foo_use.f90'),
_path('src', 'assumed_shape', 'precision.f90'),
_path('src', 'assumed_shape', 'foo_mod.f90'),
+ _path('src', 'assumed_shape', '.f2py_f2cmap'),
]
@pytest.mark.slow
@@ -31,3 +33,23 @@ class TestAssumedShapeSumExample(util.F2PyTest):
assert_(r == 3, repr(r))
r = self.module.mod.fsum([1, 2])
assert_(r == 3, repr(r))
+
+
+class TestF2cmapOption(TestAssumedShapeSumExample):
+ def setup(self):
+ # Use a custom file name for .f2py_f2cmap
+ self.sources = list(self.sources)
+ f2cmap_src = self.sources.pop(-1)
+
+ self.f2cmap_file = tempfile.NamedTemporaryFile(delete=False)
+ with open(f2cmap_src, 'rb') as f:
+ self.f2cmap_file.write(f.read())
+ self.f2cmap_file.close()
+
+ self.sources.append(self.f2cmap_file.name)
+ self.options = ["--f2cmap", self.f2cmap_file.name]
+
+ super(TestF2cmapOption, self).setup()
+
+ def teardown(self):
+ os.unlink(self.f2cmap_file.name)
diff --git a/numpy/f2py/tests/util.py b/numpy/f2py/tests/util.py
index 77cb612d0..bf005df88 100644
--- a/numpy/f2py/tests/util.py
+++ b/numpy/f2py/tests/util.py
@@ -107,6 +107,7 @@ def build_module(source_files, options=[], skip=[], only=[], module_name=None):
# Copy files
dst_sources = []
+ f2py_sources = []
for fn in source_files:
if not os.path.isfile(fn):
raise RuntimeError("%s is not a file" % fn)
@@ -114,16 +115,14 @@ def build_module(source_files, options=[], skip=[], only=[], module_name=None):
shutil.copyfile(fn, dst)
dst_sources.append(dst)
- fn = os.path.join(os.path.dirname(fn), '.f2py_f2cmap')
- if os.path.isfile(fn):
- dst = os.path.join(d, os.path.basename(fn))
- if not os.path.isfile(dst):
- shutil.copyfile(fn, dst)
+ base, ext = os.path.splitext(dst)
+ if ext in ('.f90', '.f', '.c', '.pyf'):
+ f2py_sources.append(dst)
# Prepare options
if module_name is None:
module_name = get_temp_module_name()
- f2py_opts = ['-c', '-m', module_name] + options + dst_sources
+ f2py_opts = ['-c', '-m', module_name] + options + f2py_sources
if skip:
f2py_opts += ['skip:'] + skip
if only:
@@ -205,14 +204,20 @@ def _get_compiler_status():
""")
code = code % dict(syspath=repr(sys.path))
- with temppath(suffix='.py') as script:
+ tmpdir = tempfile.mkdtemp()
+ try:
+ script = os.path.join(tmpdir, 'setup.py')
+
with open(script, 'w') as f:
f.write(code)
- cmd = [sys.executable, script, 'config']
+ cmd = [sys.executable, 'setup.py', 'config']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ stderr=subprocess.STDOUT,
+ cwd=tmpdir)
out, err = p.communicate()
+ finally:
+ shutil.rmtree(tmpdir)
m = re.search(br'COMPILERS:(\d+),(\d+),(\d+)', out)
if m: