Fortran and GNU Make
Building binary file based on Fortran code that is organized in tree based source directories may be a struggle. Usually, you want to put all objects inside single directory while, at the same time, you would like to keep sources divided into some logical parts (based on source location and modules). Let’s say you have following source structure.
.
|-- Makefile
`-- src
|-- a
| |-- a.f90
| `-- aa.F90
|-- b
| |-- b.f90
| `-- bb.F90
`-- main.f90
We have to sub-directories (with some logical elements of the code). In addition to that, there is a Makefile that will handle compilation, linking and archiving sources inside libraries. Code from two sub-directories: “a” and “b”, will be packed into liba.a and libb.a respectively. We wanto to do that, as we want to be able to re-use parts of the code somewhere else. In this case, liba.a will contain two modules that can be used in some other project. As for b, that’s not that obvious as it depends on a. Anyway, it’s a good idea to encapsulate parts of the code into some logical elements (libraries). This approach enforces proper API design and makes code more portable.
Now, to make things more complicated, source file a.f90 will declare module called “a_module” and source file aa.F90 will declare module “aa_module”. These modules will be used inside source codes: b.f90 and bb.F90.
Let’s take a look at source codes themselves.
8< - CUT HERE --- CUT HERE -- src/a/a.f90 -- CUT HERE --- CUT HERE --
! Source code of file a.f90
module a_module
contains
subroutine a
write (*,*) 'Hello a'
end subroutine a
end module a_module
8< -- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --
8< - CUT HERE --- CUT HERE -- src/a/aa.F90 -- CUT HERE --- CUT HERE -
! Source code of file aa.F90
module aa_module
contains
subroutine aa
write (*,*) 'Hello aa'
end subroutine aa
end module aa_module
8< -- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --
8< - CUT HERE --- CUT HERE -- src/b/b.f90 -- CUT HERE --- CUT HERE -- ! Source code of file b.f90 subroutine b use a_module write (*,*) 'Hello b' call a end subroutine b 8< -- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --
8< - CUT HERE --- CUT HERE -- src/b/bb.F90 -- CUT HERE --- CUT HERE - ! Source code of file bb.F90 subroutine bb use aa_module write (*,*) 'Hello bb' call aa end subroutine bb 8< -- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --
8< - CUT HERE --- CUT HERE -- src/main.f90 -- CUT HERE --- CUT HERE - ! Source code of file main.f90 program main write (*,*) 'Hello main' call b call bb end program 8< -- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --- CUT HERE --
All these sources will be compiled using Makefile below. After compilation is done, you will get following structure:
.
|-- Makefile
|-- bin
| |-- main
| `-- main_lib
|-- include
| |-- a_module.mod
| `-- aa_module.mod
|-- lib
| |-- liba.a
| `-- libb.a
|-- obj
| |-- a.o
| |-- aa.o
| |-- b.o
| |-- bb.o
| `-- main.o
`-- src
|-- a
| |-- a.f90
| `-- aa.F90
|-- b
| |-- b.f90
| `-- bb.F90
`-- main.f90
To build everything, simply call
> make > ./main > make clean
And Makefile itself looks like this
8< --- CUT HERE --- CUT HERE -- Makefile -- CUT HERE --- CUT HERE ---
# Some helper variables that will make our life easier
# later on
F90 := gfortran
INCLUDE := -Iinclude # I am storing mod files inside "include"
MODULES_OUT := -Jinclude # directory, but you may preffer "mod"
LIBS := -Llib -la -lb
# Sources are distributted accros different directories
# and src itself has multiple sub-directories
SRC_A := $(wildcard src/a/*.[fF]90)
SRC_B := $(wildcard src/b/*.[fF]90)
SRC_MAIN := $(wildcard src/*.[fF]90)
# As we can have arbitrary source locations, I want to
# make rule for each source location our aim here is
# to put all object files inside "obj" directory and
# we want to flattern the structure
OBJ_A := $(patsubst src/a/%, obj/%,\
$(patsubst %.F90, %.o,\
$(patsubst %.f90, %.o, $(SRC_A))))
OBJ_B := $(patsubst src/b/%, obj/%,\
$(patsubst %.F90, %.o,\
$(patsubst %.f90, %.o, $(SRC_B))))
OBJ_MAIN := $(patsubst src/%, obj/%, \
$(patsubst %.f90, %.o, $(SRC_MAIN)))
# this is just a dummy target that creates all the
# directories, in case they are missing
dummy_build_folder := $(shell mkdir -p obj bin include lib)
# There are two ways of building main file. We can do it
# by linking all objects, or, we can link with libraries
# these two targets will build main slightly different way
all: bin/main bin/main_lib
# This target builds main using object files
bin/main: $(OBJ_MAIN) $(OBJ_A) $(OBJ_B)
@echo $^
$(F90) -o $@ $^
# This one, uses libraries built from sources a and b
bin/main_lib: $(OBJ_MAIN) lib/liba.a lib/libb.a
@echo $^
$(F90) -o $@ $^ $(LIBS)
# Library "a" contains only codes from sub-tree "a"
lib/liba.a: $(OBJ_A)
@echo $^
ar -rs $@ $^
# Library "b" contains only codes from sub-tree "b"
lib/libb.a: $(OBJ_B) lib/liba.a
@echo $^
ar -rs $@ $^
# We have to provide information how to build objects
# from the sources
obj/%.o: src/**/%.[fF]90
$(F90) $(MODULES_OUT) -o $@ -c $< $(INCLUDE)
# main is slightly different as it lays at different
# level
obj/%.o: src/%.[fF]90
$(F90) $(MODULES_OUT) -o $@ -c $< $(INCLUDE)
# We can do some cleaning aftewards. Clean should leave
# the directory in such a state that only sources and
# Makefile are present left there
clean:
- rm -rf obj
- rm -rf bin
- rm -rf include
- rm -rf lib
8< --- CUT HERE --- CUT HERE -- CUT HERE -- CUT HERE --- CUT HERE ---