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

Leave a comment

Your comment