How to create a simple Makefile in linux

Utpal Kumar   3 minute read      

We learn how to write a Makefile to automate the compilation of our source code. We will use one example from Fortran.

Why we need a Makefile?

  • If you have multiple source files in C, Fortran, C++ or any other compilable languages, we have to do them separately. That’s where makefile becomes important. By simply running the makefile, one can compile the whole source code with multiple scripts.

  • Imagine that we have a module that contains a subroutine. After compilation of the module, the compiled subroutine will be available to all subprograms that uses that module. Now, consider what will happen if we make a small change in the module. Even after we recompile this particular module, all other files that uses this module needs to be recompiled (this is not always the case though if the changes only reflects the internals of the subroutine). If your project is large, then recompiling all the scripts can be a tedious process. Note that this issue can be avoided if we properly partition the modules.

  • Some programs has circular dependencies, where there are multiple modules that call each other. In this case, compiling only one file will fail.

What is a Makefile?

Make is like a mini programming language whose purpose is to compile a sequence of files to make a single executable file and to help us in the process.

Create a basic Makefile:

Below is an example of a basic Makefile that runs a “hello …” shell script. There are two targets here - hello, and fun.

VAR=earthinversion
VAR2=utpal

hello:
	@echo "hello ${VAR}"

fun: hello
	@echo "hello ${VAR2}"

Here, in the part fun: hello, fun is the target and hello is the dependency.

The first target is always the default. When we run make, then the first target is executed. The second target fun has dependencies. When we run make fun then it fun executes the target hello and then it executes the target fun.

$ make fun
hello earthinversion
hello utpal

We can also define VAR and VAR2 while executing make to override variable setting in the script.

$ make VAR2=ron fun
hello earthinversion
hello ron

Special variables

VAR=earthinversion
VAR2=utpal

awesome:
	@echo "hello ${VAR}"

fun: hello
	@echo ${VAR2} is $@ and $< 
$ make fun
hello earthinversion
utpal is fun and hello

In the above example, we used two variables $@ and $<. You might have already noticed. $@ is the target at the left of the colon and $< is for the one at the right of the colon.

Example Fortran code (.f90)

I borrow the example script from an excellent tutorial on Fortran by Daniel Price (see ref).

area.f90

! A module containing functions to compute the area of a circle
! Utpal Kumar
module geometry
  implicit none
  real, parameter :: pi = 4.*atan(1.)
  public :: area, pi
  private

contains
!
! A function to calculate the area of a circle
!
real function area(r)
  real, intent(in) :: r
  area = pi*r**2
 end function area
end module geometry

maths.f90

program maths
  use geometry, only:area,pi
  implicit none
  real :: r

  r = 2.0
  print*,'pi is ',pi  
  print*,'the area of the circle of radius ',r,' is ', area(r)
 
end program maths

The script maths.f90 uses the module area.f90. So, we compile the two independently and then compile the one executable. The workflow would be:

gfortran -o area.o -c area.f90
gfortran -o maths.o -c maths.f90
gfortran -o maths area.o maths.o 

Notice that I compiled the module, area.f90 before maths.f90 because of the inherent dependencies.

Use Makefile to compile codes

Now, we will use Makefile to organize our codes

FC=gfortran #fortran compiler
FFLAGS=-O3 -Wall -Wextra -std=f2008 #optimization (for level 3) flags, compiler warnings and the strictest adherence to the latest standards
SRC=area.f90 maths.f90
OBJ=${SRC:.f90=.o} #substitute .f90 with .o


%.o: %.f90 #wildcard rule, creation of *.o depends on *.f90
	$(FC) $(FFLAGS) -o $@ -c $<

maths: $(OBJ)
	$(FC) $(FFLAGS) -o $@ $(OBJ) 

clean: #cleans all the old compilation files
	@rm -f *.mod *.o maths

In this example, the first (or default) target is a ‘wildcard’ rule that tells the Make how to compile file ending in .o from the corresponding .f90. Just like above, the creation of *.o file is dependent on *.f90 files.

$ make
gfortran -O3 -Wall -Wextra -std=f2008 -o area.o -c area.f90
gfortran -O3 -Wall -Wextra -std=f2008 -o maths.o -c maths.f90
gfortran -O3 -Wall -Wextra -std=f2008 -o maths area.o maths.o 

If you don’t change anything in the script and try compiling the codes again, then Make can “smartly” figure that out.

$ make
make: `maths' is up to date.

You can use the above Makefile as your template for most of the applications.

References

  1. dprice-lab-Fortran.pdf

Disclaimer of liability

The information provided by the Earth Inversion is made available for educational purposes only.

Whilst we endeavor to keep the information up-to-date and correct. Earth Inversion makes no representations or warranties of any kind, express or implied about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services or related graphics content on the website for any purpose.

UNDER NO CIRCUMSTANCE SHALL WE HAVE ANY LIABILITY TO YOU FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF THE SITE OR RELIANCE ON ANY INFORMATION PROVIDED ON THE SITE. ANY RELIANCE YOU PLACED ON SUCH MATERIAL IS THEREFORE STRICTLY AT YOUR OWN RISK.


Leave a comment