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 ---