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, or all.
  • 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!

Similar Posts