--move-methods causes wrong "use" statements
perrette opened this issue · comments
Coming back to f90wrap. Renewed congrats.
I really like the convenience --move-methods
options, but apparently it assumes that all methods with the type as first argument come from the same module as the type in question. Here a case where it will cause an error:
debug_move_methods.f90
:
module basemod
!! base module, where the type is defined
implicit none
type param_t
integer :: a
end type
contains
subroutine increment_a(p)
type(param_t), intent(inout) :: p
p%a = p%a + 1
end subroutine
end module
module extmod
!! extension module, adding non essential functionality
implicit none
use basemod, only: param_t
contains
subroutine print_a(p)
type(param_t), intent(in) :: p
print*, p%a
end subroutine
end module
The standard procedure works like a charm:
gfortran -c debug_move_methods.f90 -fPIC
f90wrap -m debug debug_move_methods.f90
f2py-f90wrap -c -m _debug debug_move_methods.o f90wrap_debug_move_methods.f90
But the --move-methods
variant fails:
f90wrap -m debug debug_move_methods.f90 --move-methods
f2py-f90wrap -c -m _debug debug_move_methods.o f90wrap_debug_move_methods.f90 # FAILS
because of unified (wrong) module use in f90wrap_debug_move_methods.f90
use basemod, only: param_t, print_a
Whereas print_a
is defined in the extension module extmod
, not in basemod
.
I am not saying my design is the best ever (maybe the use of new submodule feature might achieve something similar -- but is it supported by f90wrap?), but I assume this bug should easy to fix? Thanks in any case.
Here is a reasonable workaround for my use case: just create a dummy module with all imports and replace any "use ..." statements in f90wra_*.f90 files with "use dummy ...".
Here a python script to do this on the fly (e.g. to be called from a Makefile just after f90wrap):
"""module {dummy}
!
! Fix cross-dependency bug associated with --move-methods flag to f90wrap.
!
! The approach taken here is to create a dummy module which contains all needed module
! imports, and replace all "use {prefix}... " statements with "use {dummy}"
! It parses generated f90wrap_*.f90 code to create {dummy} sequentially.
!
! This module needs to be manually compiled and passed to f2py[-f90wrap] as object.
{statements}
contains
end module {dummy}
"""
from __future__ import print_function, absolute_import
import os, logging
logging.basicConfig(level=logging.INFO)
def parse_line(line):
"use mod , only : name1, name2"
if not ':' in line:
usemod, names = line, ""
else:
useonly, names = line.split(':')
usemod, _ = useonly.split(',')
_, mod = usemod.strip().split()
assert _.strip() == 'use'
return mod, [nm.strip() for nm in names.split(',')]
class DummyModule(object):
def __init__(self, name, prefix):
self.name = name
self.prefix = prefix
self.statements = []
def parse_line(self, line):
if line.strip().startswith('use {}'.format(self.prefix)):
mod, names = parse_line(line)
assert mod != self.name, 'already parsed + prefix == dummy'
# append use statement in dummy module
self.append(mod)
# replace original line
line = " use {}, only: {}".format(self.name, ", ".join(names))
elif line.strip().startswith('use {}'.format(self.name)):
raise ValueError('already parsed')
return line
def append(self, mod):
statement = " use "+mod
if statement not in self.statements:
logging.info("generate_dummy:: append module: "+mod)
self.statements.append(statement)
def format(self):
return __doc__.format(dummy=self.name, prefix=self.prefix, statements="\n".join(self.statements))
def main():
import argparse
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('files', nargs='*', default=[], help='f90wrap_*.f90 files')
parser.add_argument('--dummy', required=True,
help='name of dummy module to create, e.g. module_dummy_')
parser.add_argument('--prefix', required=True, help='prefix of modules to replace, e.g. module_')
parser.add_argument('--dry-run', action='store_true', help='only print dummy module, do not modify any file')
parser.add_argument('--add-modules', nargs='*', default=[], help='add additional modules (typically the extension modules erroneously removed by f90wrap with --move-method option activated)')
args = parser.parse_args()
dummy = DummyModule(args.dummy, args.prefix)
for fname in args.files:
logging.info("generate_dummy:: visit file: "+fname)
txt = open(fname).read()
lines = txt.splitlines()
for i, line in enumerate(lines):
lines[i] = dummy.parse_line(line)
newtxt = "\n".join(lines)
# replace file if modified
if txt != newtxt and not args.dry_run:
logging.info("generate_dummy:: update file: "+fname)
with open(fname, 'w') as f:
f.write(newtxt)
# append additional modules
for mod in args.add_modules:
dummy.append(mod)
print(dummy.format())
if __name__ == "__main__":
main()