Archive for October, 2012

In at the Dep End

30/10/2012

The NAG Fortran Compiler has been updated to Release 5.3.1, which includes a few fixes to the module-dependency analyzer. Fortran modules require compilation before they can be Used so generating the correct dependencies for a (GNU) makefile is pretty vital (and tricky).

Here’s an example project having a non-trivial tree of module dependencies

$ cat main.f90
Program main
  Use module6
  Use module5
  Call msub6
  Call msub5
End Program
$ cat module6.f90
Module module6
Contains
  Subroutine msub6
    Use module7
    Call msub7
  End Subroutine
End Module
$ cat module5.f90
Module module5
Contains
  Subroutine msub5
    Use module4
    Call msub4
  End Subroutine
End Module
...
$ cat module9.f90
Module module9
Contains
  Subroutine msub9
    Print *, "OK9"
  End Subroutine
End Module
$ cat module0.f90
Module module0
Contains
  Subroutine msub0
    Print *, "OK0"
  End Subroutine
End Module

and here’s a dumb Python script to make those files

#!/usr/bin/env python

nfiles = 10

for i in range(nfiles):
    file_fo = open('module' + str(i) + '.f90',
                   'w')
    file_fo.writelines(['Module module' + str(i) + '\n',
                        'Contains\n',
                        '  Subroutine msub' + str(i) + '\n'])

    if (i in [0, nfiles - 1]):
        file_fo.write('    Print *, "OK' + str(i) + '"\n')
    else:

        if (i > nfiles / 2):
            m_no = i + 1
        else:
            m_no = i - 1

        file_fo.writelines(['    Use module' + str(m_no) + '\n',
                            '    Call msub' + str(m_no) + '\n'])

    file_fo.writelines(['  End Subroutine\n',
                        'End Module\n'])
    file_fo.close()

file_fo = open('main.f90',
               'w')
file_fo.writelines(['Program main\n',
                    '  Use module' + str(nfiles / 2 + 1) + '\n',
                    '  Use module' + str(nfiles / 2) + '\n',
                    '  Call msub' + str(nfiles / 2 + 1) + '\n',
                    '  Call msub' + str(nfiles / 2) + '\n',
                    'End Program\n'])
file_fo.close()

To create an accurate view of the project’s dependencies for a makefile you first need a dependency analyzer. With nagfor =depend this is quite easy:

$ nagfor =depend -otype=make *.f90
NAG Fortran Dependency Analyser Release 5.3.1(909)
NI_EQ==
NI_SC=\;
main.o:	main.f90
main.o:	module6.mod
main.o:	module5.mod
module0.o:	module0.f90
module0.mod:	module0.f90
module1.o:	module1.f90
module1.o:	module0.mod
module1.mod:	module1.f90
module2.o:	module2.f90
module2.o:	module1.mod
module2.mod:	module2.f90
module3.o:	module3.f90
module3.o:	module2.mod
module3.mod:	module3.f90
module4.o:	module4.f90
module4.o:	module3.mod
module4.mod:	module4.f90
module5.o:	module5.f90
module5.o:	module4.mod
module5.mod:	module5.f90
module6.o:	module6.f90
module6.o:	module7.mod
module6.mod:	module6.f90
module7.o:	module7.f90
module7.o:	module8.mod
module7.mod:	module7.f90
module8.o:	module8.f90
module8.o:	module9.mod
module8.mod:	module8.f90
module9.o:	module9.f90
module9.mod:	module9.f90

Note that no .mod files are required to pre-exist.

For the project above a simple makefile might look like

$ cat Makefile
all: main.r

main.r: main.exe
        ./$< > $@ 2>&1
        cat $@

SOURCES := $(sort $(wildcard module*.f90))
OBJECTS := $(SOURCES:.f90=.o)

main.exe: main.o $(OBJECTS)
        nagfor $^ -o $@

%.o %.mod: %.f90
        nagfor -c $<

clean:
        rm -f *.r *.exe *.o *.mod

but of course that doesn’t take the module dependencies into account yet, so that trying to make results in something like

$ make
...
Fatal Error: main.f90, line 2: Cannot find module MODULE6
...

There are several great discussions around of advanced auto-dependency generation including one (primarily for C source, although some of the ideas are transferrable to Fortran) by Paul D. Smith that inspired the approach below.

Essentially we use GNU make‘s include statement to build in a ‘pre-pass’ that generates a dependency file for all Fortran source in the project:

DEPS := $(SOURCES:.f90=.P) main.P

%.P: %.f90
        nagfor =depend -otype=make $< -o $@

include Depends

Depends: $(DEPS)
        cat $^ > $@

Thus the file Depends is automatically built first for a clean make and it’s updated and re-included into the makefile if any of the dependent Fortran source changes. It will work with -jN parallel make.

Then we see

$ make
...
 OK9
 OK0

The scheme is also reasonably portable, so that other compilers can be used for building the executable – as long as they output .mod files at all. Of course, you may need to postprocess the output from nagfor =depend for compilers that uppercase the names of modules when they create .mod files. Plus it goes without saying that if you’re using make you’ll probably want to follow the rule of one module per file.

Source code: https://github.com/matcross/blog/tree/master/in-at-the-dep-end