
GNU Make is a development utility that determines which parts of a particular code base need to be recompiled and can issue Linux commands to perform those operations.
This build automation tool can be used with any programming language whose compilation can be done from the shell by issuing commands, making it invaluable for C, C++, and many other compiled languages.
Makefiles in Linux
To use GNU Make, we need a set of rules that define the relationship among different files in our program and commands for updating each file. These are written into a special file called ‘Makefile‘ or ‘makefile‘.
The ‘make‘ command uses the makefile database and the last modification times of the files to decide which files need to be recompiled.
Contents of a Makefile
Generally, makefiles contain five kinds of elements: explicit rules, implicit rules, variable definitions, directives, and comments.
- Explicit rules specify how to make or remake one or more files (called
targets
) and when to do so. - Implicit rules specify how to make or remake files based on their names, describing how a target file relates to another file with a similar name.
- Variable definitions are lines that specify string values for variables to be substituted later in the makefile.
- Directives are instructions for make to do something special while reading the makefile.
- Comments start with a
'#'
symbol. Any line starting with'#'
is ignored by make.
Structure of Makefiles
The information that tells make how to recompile a system comes from reading the makefile.
A simple makefile consists of rules with the following syntax:
target ... : prerequisites ... recipe ... ...
- A target is the output file generated by the program, which can also be a phony target (explained below). Examples include executables, object files, or phony targets like
clean
,install
,test
, orall
. - A prerequisite (also called a dependency) is a file used as input to create the target files.
- A recipe is the action that make performs to create the target file based on the prerequisites. It’s necessary to put a tab character before each recipe line unless you specify the
.RECIPEPREFIX
variable to define a different character as the prefix.
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 final
In this example we use four C source files and two header files to create the executable final. Each ‘.o'
file is both a target and a prerequisite within the makefile. Notice the last target named clean
– it’s an action rather than an actual file.
Since we normally don’t need to clean during compilation, it’s not written as a prerequisite in any other rules. Targets that don’t refer to files but are just actions are called phony targets. They typically don’t have prerequisites like regular target files do.
How GNU Make Processes a Makefile
By default, make starts with the first target in the makefile, called the default goal. In our example, final
is the first target. Since its prerequisites include object files, those must be updated before creating final
. Each prerequisite is processed according to its own rule.
Recompilation occurs if modifications were made to source or header files, or if the object file doesn’t exist at all. After recompiling the necessary object files, make decides whether to relink final
. This happens if final doesn’t exist or if any of the object files are newer than it.
For example, if we change inter.c
and run make, it will recompile that source file to update inter.o
and then link final
.
Using Variables in Makefiles
In our example, we had to list all the object files twice in the rule for final
:
final: main.o end.o inter.o start.o gcc -o final main.o end.o inter.o start.o
To avoid such duplication, we can introduce variables to store lists of files. Using variables also makes the makefile easier to maintain.
Here’s an improved version:
CC = gcc CFLAGS = -Wall -Wextra -O2 OBJ = main.o end.o inter.o start.o TARGET = final $(TARGET): $(OBJ) $(CC) -o $(TARGET) $(OBJ) main.o: main.c global.h $(CC) $(CFLAGS) -c main.c end.o: end.c local.h global.h $(CC) $(CFLAGS) -c end.c inter.o: inter.c global.h $(CC) $(CFLAGS) -c inter.c start.o: start.c global.h $(CC) $(CFLAGS) -c start.c clean: rm -f $(OBJ) $(TARGET)
Notice how we’ve also defined CC for the compiler and CFLAGS for compilation flags, which makes it easy to change compiler options or even switch compilers for the entire project.
Rules for Cleaning the Source Directory
As seen in the example, we can define rules to clean up the source directory by removing unwanted files after compilation. But suppose we have an actual file called clean
– how can make differentiate between the file and the target? This is where phony targets come in.
A phony target is not actually the name of a file; it’s just a name for a recipe to be executed when explicitly requested. The main reasons to use phony targets are to avoid conflicts with files of the same name and to improve performance.
Here’s an important detail: the recipe for clean
won’t be executed by default when running make. Instead, you must explicitly invoke it with make clean
.
To properly declare a phony target, use the .PHONY
directive:
.PHONY: clean all install clean: rm -f $(OBJ) $(TARGET) all: $(TARGET)
Modern Makefile Best Practices
Here are some contemporary practices to consider:
Use pattern rules to reduce repetition:
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
Add a default ‘all‘ target:
.PHONY: all all: $(TARGET)
Include dependency generation for automatic header tracking:
DEPS = $(OBJ:.o=.d) -include $(DEPS)
Add more phony targets for common operations:
.PHONY: install test run
Use automatic variables like $@
(target), $>
(first prerequisite), and $^
(all prerequisites) to make rules more generic.
Conclusion
Now try creating makefiles for your own code base. GNU Make remains a powerful and widely-used build tool, especially for C and C++ projects.
Understanding makefiles will help you work with countless open-source projects and give you fine-grained control over your build process. Feel free to comment with your questions or experiences!