diff options
| author | Charles Harris <charlesr.harris@gmail.com> | 2019-12-15 12:22:36 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-12-15 12:22:36 -0800 |
| commit | 740c7f5a089adceebd445b444f32d87ad09c7c14 (patch) | |
| tree | 7a9bf425ff4574e715180bafb726629046f6f276 | |
| parent | 2fc10a279edfd132ec27eeac9e72f5a02a8bae5e (diff) | |
| parent | 7142a6c979d6cb2fb514e6974bdb2639d387015a (diff) | |
| download | numpy-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.rst | 4 | ||||
| -rw-r--r-- | doc/source/f2py/advanced.rst | 52 | ||||
| -rw-r--r-- | numpy/f2py/capi_maps.py | 28 | ||||
| -rwxr-xr-x | numpy/f2py/f2py2e.py | 15 | ||||
| -rw-r--r-- | numpy/f2py/tests/test_assumed_shape.py | 22 | ||||
| -rw-r--r-- | numpy/f2py/tests/util.py | 23 |
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: |
