modern Fortran dependencies

Using (GNU’s) make usually fails with modern Fortran code that uses modules.

Fortran is particularly suited for numerical and scientific computing (and has been for over 6 decades). Fortran comes in many flavors: FORTRAN, FORTRAN II, …, FORTRAN 66, FORTRAN 77, Fortran 90, and beyond. With the advent of Fortran 90, the idea of ‘modules’ were introduced. Modules, in part, brought Fortran into the modern era by introducing a simple way of encapsulating data and algorithms allowing for more modern programming techniques.

While Fortran is (mostly) backward compatible, the introduction of modules has caused frustration in compiling Fortran codes. Essentially any Fortran source file can only be compiled *after* all the modules it uses have already been compiled, otherwise the build will fail. Unlike C/C++ or pre-Fortran 90 where most compilers can automatically generate dependencies for a build, the same compilers will fail to generate dependencies for Fortran 90+ code unless the files are compiled in the correct order. This leads to a chicken and egg problem. For the compiler to generate dependencies automatically, it has to have already compiled the modules in the correct order. This is particularly annoying during development when dependencies often change.

cmake, SCons, etc. can handle these Fortran dependencies. Unfortunately, most Fortran builds depend on (GNU’s) make. The dependable makedepf90 can generate dependencies that make can understand. It can, usually, deal with preprocessor directives also.

For C/C++ files, generating dependencies for make is pretty easy. It can be done at the same time the files are compiled or separately. A typical pattern in a Makefile would look like

$(DEPDIR)/%.d: %.cpp
        @echo "Constructing dependencies for $<..."
        @$(CXX) -MT '$$(BLDDIR)/$(subst .cpp,.o,$<)' -MM $< > $@

For each *.cpp file, it generates the associated *.d file (rooted in $(DEPDIR) directory) which then can be included. For Fortran files, we would like a similar pattern, say:

$(DEPDIR)/%.d : %.f90
        @echo "Constructing dependencies for $<..."
        @./mkdependf90 $< '$$(BLDDIR)/' > $@

makedepf90 wants all of the Fortran source files and therefore isn’t an option here. If you can ignore preprocessing (i.e. preprocessing would not change the dependencies), the following simple bash script (mkdependf90) works well, albeit, not the quickest:

#!/usr/bin/env bash

#this generates dependencies for a fortran file to stdout
# $1 = source file
# $2 = root build directory (can be blank, a value or a make variable unresolved)
# $3 - if present only use modules (*.mod) files as dependencies, otherwise use both *.mod and its corresponding object (*.o) file as dependencies

#for the given source file, 
#  1) for each module "use"d, if source file (*.f90) that defines the module exists then
#       a) if $3 is not present, add the corresponding object file that defines the module to dependency list
#              this will force more recompiling (just basing off object or *.mod files is not enough for general fortran compilers)
#              if using gfortran >= 4.3.0 then *.mod timestamp is only changed if the module interface changed, 
#                therefore we can avoid including the *.o file(s) in dependencies and simply use the *.mod files.
#                in this case you should pass in anything for $3
#                note, this is NOT the behavior of intel's ifort
#       b) add the *.mod file to dependency list iff the module is not defined in source file
#  2) for each fortran 'include', add that file also to the dependencies list (if it exists) and recursively do the dependencies of that included file
#  3) for each module defined in the source file, add a simple rule for *.mod with dependency of *.o file

# NOTE: 
#  we don't deal with preprocessing or preprocessing includes (i.e. "#include ...')
#  all *.mod files are placed in $2 (= "$(BLDDIR)/", say) and all *.o are mirrored w.r.t. *.f90 (e.g. src/plot/poly.f90 and $(BLDDIR)/src/plot/poly.o) 

find_mod_inc() {
	deps=""

	#loop over each module 'use'd in this source file: $j will be the name (lower case) of the module
	for j in $(grep -i "^ *use "  $1 | awk '{print tolower($0)}' | sed "s/,/ /g; s/::/ /; s/ non_intrinsic / /; s/ intrinsic / /" | awk '{print $2}' | sort -u | sed "s/\!/ /" | awk '{print $1}'|sort -u); do

        	#for each module found, find the source file that defines it- may not exist (or may be in the current source file): $i might be such source file
		      moddef=""
       	 for i in $(find . -name "*.f90" | grep -v $1 | sed "s/^\.\///" | xargs grep -i "^ *module *${j}" | awk -F: '{print $1}'|sort -u); do
			          (( $(grep -i "^ *module *${j}" $i | awk '{print tolower($2)}' | sort -u | sed "s/\!/ /" | awk -v m=$j '{if ($1 == m) print m}' | wc -l) > 0 )) && moddef="1" && [ ! -n "${onlymod}" ] && deps+=" ${blddir}${i/.f90/.o}"  #add correspond source object to dependencies
        	done

        	#add *.mod file to dependency if module is not defined in the source file
		      [ -n "$moddef" ] && (( $(grep -i "^ *module *${j}" $1 | awk '{print tolower($2)}' | sort -u | sed "s/\!/ /" | awk -v m=$j '{if ($1 == m) print m}' |wc -l) < 1 )) && deps+=" ${blddir}${j}.mod"
	done

	#look for any 'include' statements... and add them onto the dependencies if they are in any subdirectories
	for i in $(grep -i "^ *include " $1 | sed "s/\"/ /g; s/\'/ /g"| awk '{print $2}');do
	  	    k=$(basename $i) && (( $(find . -name $k | wc -l) > 0 )) && deps+=" $(dirname $1)/${i}$(find_mod_inc $(dirname $1)/${i})" #recursion!
	done
	echo "${deps}"
}

[ -n "$3" ] && onlymod="1"
blddir="" && [ -n "$2" ] && blddir="$2"
sfile="$1" && mkline="${blddir}${sfile/.f90/.o} : ${sfile}$(find_mod_inc ${sfile})" #object file : source file
echo "$mkline"  #ok, final dependency list for the object file

#for each module defined in the source file, add a simple rule for *.mod w/ dependency of the object file
for i in $(grep -i "^ *module " $sfile | awk '{if (tolower($1) == "module" && tolower($2) != "procedure") print tolower($2)}' | sort -u | sed "s/\!/ /" | awk '{print $1}');do 
	    echo && echo "${blddir}${i}.mod : ${blddir}${sfile/.f90/.o}"
done

For an example, placing mkdependf90 at the top most Makefile, for a particular problem:

> ./mkdependf90 src/data.f90 '$(BLDDIR)/' 
$(BLDDIR)/src/data.o : src/data.f90 $(BLDDIR)/src/fftw.o $(BLDDIR)/fftw_m.mod $(BLDDIR)/src/FP/fp.o $(BLDDIR)/fp_m.mod $(BLDDIR)/src/io.o $(BLDDIR)/io_m.mod

$(BLDDIR)/data_m.mod : $(BLDDIR)/src/data.o

$(BLDDIR)/window_m.mod : $(BLDDIR)/src/data.o

Here, src/data.f90 generates $(BLDDIR)/src/data.o and two modules: data_m.mod and window_m.mod. However, it depends on the modules fftw_m, fp_m and io_m which come from src/fftw.f90, src/FP/fp.f90 and src/io.f90, respectively.

If you are using gfortran v4.3.0 or above, then the *.mod file’s timestamp only changes if the interface changes. In this case, the dependencies need only to include the *.mod files and not the associated *.o files. This can significantly reduce the recompiling effort during development. In this case, add some non-empty third token to mkdependf90:

> ./mkdependf90 src/data.f90 '$(BLDDIR)/' noobj
$(BLDDIR)/src/data.o : src/data.f90 $(BLDDIR)/fftw_m.mod $(BLDDIR)/fp_m.mod $(BLDDIR)/io_m.mod

$(BLDDIR)/data_m.mod : $(BLDDIR)/src/data.o

$(BLDDIR)/window_m.mod : $(BLDDIR)/src/data.o

In the majority of Fortran codes I’ve developed or encountered, this mkdependf90 works quite well. And it’s lightweight enough (~30 lines) so that modifying it for any particular case is straight forward.

Finally, make can now figure out how to compile Fortran correctly.

Add a Comment

Your email address will not be published. Required fields are marked *