Makefiles
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.
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:
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:
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:
#include "stdio.h"
int main(void){
printf("I am a C program");
return 0;
}
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.
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.