Reputation: 1215
I am working on a big Fortran 90 code, with a lot of modules. What bothers me is that when I modify the inner code of a function inside a module (without changing its mask), my Makefile (whose dependencies are based on "use") recompile every file that "use" that modified module, and recursively.
But when modifying the inner code of a function without touching its input/output, recompiling other files than the modified one is useless, no?
So I would like to separate the function declaration from their definition, like with the .h files in C or C++. What is the clean way to do this? Do I have to use Fortran include/preprocessor #include
, or is there a "module/use" way of doing this?
I have tried something like this, but it seems to be quite nonsense...
main.f90
program prog
use foomod_header
integer :: i
bar=0
i=42
call foosub(i)
end program prog
foomod_header.f90
module foomod_header
integer :: bar
interface
subroutine foosub(i)
integer :: i
end subroutine
end interface
end module foomod_header
foomod.f90
module foomod
use foomod_header
contains
subroutine foosub(i)
integer ::i
print *,i+bar
end subroutine foosub
end module foomod
Upvotes: 2
Views: 600
Reputation: 4426
Maybe the cleanest solution is to change the build system.
The real dependency introduced by a USE
statement is not the source-code file, but the generated .mod
file, which acts as a sort of "binary header file". I.e. where makefiles typically contain something like
MyProgram.o: MyModule.f90
what they really should contain is
MyProgram.o: MyModule.mod
MyModule.mod: MyModule.f90
with the creation of the .mod
file being done in a way, that ensures an unchanged file-system timestamp, if the interface hasn't actually changed.
Sadly, compiler-support is awkward. Most compilers will overwrite the .mod
file anyway, so the build process must at the same time detect, that the .mod
file hasn't changed, e.g. by restoring the old modification time if the contents are unchanged, but at the same time needs to avoid recompiling the source file unnecessarily, which requires updating the modification time of the .mod
file.
Additionally, some compilers (Intel, *cough*) add a binary time-stamp to the contents of the .mod
files, that needs to be manually excluded from the comparison and has changed binary position across releases. This adds effort when supporting multiple compilers.
Upvotes: 1
Reputation: 21441
If submodules aren't an option (and they are ideal for this), then what you can do is make the procedure an external procedure and provide an interface for that procedure in a module. For example:
! Program.f90
PROGRAM p
USE Interfaces
IMPLICIT NONE
...
CALL SomeProcedure(xyz)
END PROGRAM p
! Interfaces.f90
MODULE Interfaces
IMPLICIT NONE
INTERFACE
SUBROUTINE SomeProcedure(some_arg)
USE SomeOtherModule
IMPLICIT NONE
TYPE(SomeType) :: some_arg
END SUBROUTINE SomeProcedure
END INTERFACE
END MODULE Interfaces
! SomeProcedure.f90
SUBROUTINE SomeProcedure(some_arg)
USE SomeOtherModule
IMPLICIT NONE
TYPE(SomeType) :: some_arg
...
END SUBROUTINE SomeProcedure
Some important notes:
There must only ever be one interface definition for a procedure accessible in a scope. Inside a subprogram the interface for the procedure defined by the subprogram is also considered defined - hence inside the subprogram you must not permit an interface block for procedures defined by the subprogram to be accessible. In terms of the example, this means that you must not have a USE Interfaces
statement without an only clause inside the SomeProcedure
external procedure.
If you do change the arguments or similar of the procedure inside SomeProcedure.f90 you had better make sure that you change the corresponding interface block inside the module!
If you can use F2003, the IMPORT statement can make life easier. Otherwise you might have to have additional modules (such as SomeOtherModule
in the example) to share type definitions and the like between the Interfaces module and the external procedure.
If you have private entities or components relevant to the procedure then Fortran's rules entity and component accessibility may prevent you using this approach.
Typically some sort of whole program analysis is done at high levels of optimization. That analysis is typically much slower than the actual parsing of the code - splitting out procedures in this manner may not actually shorten build times significantly under these conditions.
Upvotes: 2