Digital Garden
Computer Science
Make
Makefiles

Makefiles

Info

For further reading I strongly recommend this website by Theicfire (opens in a new tab).

Simply put Makefiles are used to help decide which parts of a large program need to be recompiled. Makefiles are most commonly used with big C/C++ programs as other programming languages have their own build tools that are very similar to Makefiles (Java has Gradle or Maven). Makefiles don't necessarily need to be used for compiling programs as they just define a series of instructions to run depending on what files have changed. The focus of this page will however be on how Makefiles are used to compile C/C++ programs.

Running a Makefile

To use Makefiles you need to have make installed. To check whether you have installed it use the command make --help.

The series of instructions for a Makefile or written in a file called Makefile (no file ending) hence the name Makefiles. On Unix based systems this is easily created with the touch Makefile command.

Just like all programming languages, there is a hello world example for Makefiles as well.

Makefile
hello:
    echo "Hello, World"

To run the example you just need to use the make command in the same folder as the Makefile.

foo@bar:~$ make
echo "Hello, World"
Hello, World

Syntax

Now that we have seen a simple example let's go through how Makefiles are structured and what they exactly do.

A Makefile contains a set of rules. And a rule looks like this:

Makefile
targets: dependencies
    command
    command
    command
  • The targets are filenames, typically there is only one per rule but there can be multiple separated by spaces.
  • The commands are a series of steps typically used to make the target(s). These need to start with a tab character, not spaces (be careful how your editor handles tabs).
  • The prerequisites or also more commonly called dependencies are also filenames that are separated by spaces. These files need to exist before the commands for the target are run.

Now that we know a bit more let's go back to the hello world example:

Makefile
hello:
    echo "Hello, World"

We have the target hello, that in turn has no dependencies. This means that in theory, the goal of this rule would be to create a file called hello and to do this the echo "Hello, World". Because it is the first and only rule in the Makefile we can just run make, but we can also specify which target we want to build by passing it to the make command make hello would in this case be equivalent. Because this Makefile never actually creates the target we can run the command over and over again.

As mentioned before Makefiles are typically used for C/C++ programs so let's look at such an example:

main.c
#include "stdio.h"
 
int main(void){
    printf("I am a C program");
    return 0;
}
Makefile
main:
    gcc main.c -o main

Now after executing the make command we are actually creating the target as intended. But if we try and run the make command again it will say that the target is already up to date. This is because the main file has already been created.

foo@bar:~$ make
gcc main.c -o main
foo@bar:~$ make
make: `main' is up to date.

Some people that are spoilt from their IDEs might now expect that if we change main.c and run the Makefile again that it will get recompiled, this however is not the case. This is what the dependencies are for.

Makefile
main: main.c
    gcc main.c -o main

If you add main.c as a dependency and execute the make command again, it will check if any of the dependencies have changed since last building the target. More technically explained it will check all the modification timestamps of the dependency files and compare them to the timestamp of the target. If the target is older than one of the modification timestamps it will rebuild the target.