A Brief Introduction to ‘Makefiles’ in Open Source Software Development with GNU Make
GNU Make is a development utility which determines the parts of a particular code base that are to be recompiled and can issue commands to perform those operations on the code base. This particular make utility can be used with any programming language provided that their compilation can be done from the shell by issuing commands.
In order to use GNU Make, we need to have some set of rules which defines the relationship among different files in our program and commands for updating each file. These are written onto a special file called ‘makefile‘. The ‘make‘ command uses the ‘makefile‘ data base and the last modification times of the files to decide which all files are to recompiled again.
Contents of a Makefile
Generally ‘makefiles‘ contain 5 kinds of things namely: implicit rules, explicit rules, variable definitions, directives, and comments.
- An explicit rule specifies how to make/remake one or more files (called targets, will be explained later) and when to do the same.
- An implicit rule specifies how to make/remake one or more files based on their names. It describes how a target file name is related to one file with a name similar to the target.
- A variable definition is a line that specifies a string value for a variable to be substituted later.
- A directive is an instruction for make to do something special while reading the makefile.
- A ‘#’ symbol is used represent the start of a comment inside makefiles. A line starting with ‘#’ is simply ignored.
Structure of Makefiles
The information that tells make how to recompile a system comes from reading a data base called the makefile. A simple makefile will consists of rules of the following syntax:
target ... : prerequisites ... recipe ... ...
A target is defined to be the output file generated by the program. It can be also phony targets, which will be explained below. Examples of target files include executables, object files or phony targets like clean, install, uninstall etc.
A prerequisite is a file that is used as an input to create the target files.
A recipe is the action that make performs for creating the target file based on the prerequisites. It is necessary to put tab character before each recipe inside the makefiles unless we specify the ‘.RECIPEPREFIX‘ variable to define some other character as prefix to recipe.
A sample Makefile
final: main.o end.o inter.o start.o gcc -o final main.o end.o inter.o start.o main.o: main.c global.h gcc -c main.c end.o: end.c local.h global.h gcc -c end.c inter.o: inter.c global.h gcc -c inter.c start.o: start.c global.h gcc -c start.c clean: rm -f main.o end.o inter.o start.o
In the above example we used 4 C source files and two header files for creating the executable final. Here each ‘.o’ file is both a target and a prerequisite inside the makefile. Now take a look at the last target name clean. It is just a action rather than a target file.
Since we normally don’t need this during compilation, it is not written as a prerequisite in any other rules. Targets that do not refer to files but are just actions are called phony targets. They will not have any prerequisites as other target files.
How GNU Make process a Makefile
By default make starts off with the first target in the ‘makefile‘ and is called as ‘default goal’. Considering our example, we have final as our first target. Since its prerequisites include other object files those are to be updated before creating final. Each of these prerequisites are processed according to their own rules.
Recompilation occurs if there are modifications made to source files or header files or if the object file does not exists at all. After recompiling the necessary object files, make decides whether to relink final or not. This must be done if the file final does not exist, or if any of the object files are newer than it.
Thus if we change the file inter.c, then on running make it will recompile the source file to update the object file inter.o and then link final.
Using variables inside Makefiles
In our example, we had to list all the object files twice in the rule for final as shown below.
final: main.o end.o inter.o start.o gcc -o final main.o end.o inter.o start.o
In order to avoid such duplications, we can introduce variables to store the list of object files that are being used inside the makefile. By using the variable OBJ we can rewrite the sample makefile to a similar one shown below.
OBJ = main.o end.o inter.o start.o final: $(OBJ) gcc -o final $(OBJ) main.o: main.c global.h gcc -c main.c end.o: end.c local.h global.h gcc -c end.c inter.o: inter.c global.h gcc -c inter.c start.o: start.c global.h gcc -c start.c clean: rm -f $(OBJ)
Rules for cleaning the source directory
As we saw in the example makefile, we can define rules to clean up the source directory by removing the unwanted object files after the compilation. Suppose we happen to have a target file called clean. How can make differentiate the above two situations? Here comes the concept of phony targets.
A phony target is one that is not really the name of a file, rather it is just a name for a recipe to be executed whenever an explicit request is made from the makefile. One main reason to use phony target is to avoid a conflict with a file of the same name. Other reason is to improve performance.
To explain this thing, I will reveal one unexpected twist. The recipe for clean will not be executed by default on running make. Instead it is necessary to invoke the same by issuing the command make clean.
.PHONY: clean clean: rm -f $(OBJ)
Now try to create makefiles for your own code base. Feel free to comment here with your doubts.