GNU Make Fundamentals
Concepts and Principles of Program Building
PREREQUISITES — You should already…
- have some programming experience, preferably in C.
- have basic command line skills (Windows or POSIX).
Many development steps in the compilation of programs, can be broken down into a sequence of smaller steps. This is particularly true in C/C++ development, since these compilers can only compile one file at a time, even when programs consist of several source files; and only create object files, which in turn means that a linker must be used to create the final executable.
Practically, this means C/C++ developers use IDEs or compiler drivers to call the preprocessor, call the code generation program(s) of C/C++, and finally call the linker. A compiler driver, like
g++, does allow one to specify several source files, but this is not very efficient, if only one source file in a program has changed.
make utility can be given a
Makefile (naming convention), in which the program has specified the structure of the program, and the commands that must be run to produce intermediate files and final result. The Make utility will then only perform the necessary commands, and skip those that are not required.
Makefile has a certain syntax, and overall achitecture. The major, and most common, parts of this structure is explained here. You can read more in the GNU Make Manual.
Comments & Line Continuation
Makefile may contain comments, and follow POSIX scripting language convention. The hash (
#) character is used to start a single line comment, where all following text is ignored until the end of the line.
Long lines can be continued on the next, by ending the current line with a trailing backslash (
\). This can be repeated for several lines, if necessary.
Macros or Variables
Make can read environment variables, but one can create variables in the
Makefile, which it calls macros. They can be created or modified by assignment:
Spaces surrounding the equal sign are not significant. The expression on the right of the assignment may be blank. This will either create the macro name on the left, or clear its current contents.
You can append values to an existing macro with
+=. If the macro does not exist at that point, it will be equivalent to assignment:
Unlike POSIX shells, the contents of variables (macros) must be retrieved with parenthesis surrounding the names, prefixed with
›). Only macro names consisting of a single letter, do not require the surrounding parentheses.
Targets, Dependencies and Rules
The make file syntax allows one to specify the target file, which is dependend on one or more files (refered to as the target's dependencies). To ensure that the target is up to date with respect to its dependencies, i.e., the target has the newest date, any number of command lines, called recipes by GNU Make, can be executed.
One idiosyncrasy of GNU's Make, is that all the recipe lines must start with a hard tab character — just indenting the line with spaces, is an error. A blank line terminates the list of commands.
From a C program's perspective, an object file would be a target (e.g.
main.o), and its dependency would be
main.c has a newer date, the target is “out of date”, and to get it up to date, the object file must be re-generated.
Make creates special variables like
$*, amongst others, that expand to various filenames in rules. In GNU Make terminology, they are called automatic variables. They are useful in creating more easily reusable
Some documentation refers to rules as ‘recipes’, so keep that in mind. Generally, the compilation of every source file, will involved the same command line, apart from the names of the files. To make this simpler, one can specify a generic rule to use, in cases where an explicit rule is not present.
It does mean that one require a mechanism to replace certain parts of the command line(s) with either the target filename, or dependency filename(s), or both. This is where the special variables come into play.
Much like the C preprocessor, GNU Make can check if a macro exists, or not. This can be useful to conditionally create macros or rules. Unlike with recipes, hard tabs are not necessary in this example extract:
The indentation is a matter of style, not a syntactical requirement. Several other conditionals are available, allowing make syntax to be quite flexible and powerful.
It is possible to specify targets that are not real files. The are called pseudo targets (
.PHONY in Make terminology). Make must know they are not real files, so it will not check for a time stamp, and so that it will always execute the rules. Popular pseudo targets are
clean and sometimes
Given a C99 program that consists of, for example, three source files:
input.c, and has convenience headers:
input.h, the expanded steps required to create an executable called
myprogram.exe under Windows), the following commands are required in a Bash or compatible shell, to create a debug executable:
$> POSIX Shell Separate Compilation
CFLAGS=-Wall -Wextra -std=c99 gcc $CFLAGS -c -g main.c gcc $CFLAGS -c -g utility.c gcc $CFLAGS -c -g input.c gcc -g -o myprogram main.o utility.o input.o
The order in which the source files are compiled, or the order the object files are passed to the linker (
ld) is irrelevant. The last command only calls the linker, since none of the arguments are source files. We use
gcc to call the linker, since it must be passed extra object files like the startup code, and C99 standard library file(s). This is the easiest way to call the linker for C programs (and C++, but
g++ is used instead).
For completeness, if you are using the Windows Command Prompt, the above command lines will appear as follows (not properly highlighted, since Pandoc's syntax highlight component does not understand Command Prompt shell syntax):
$> Command Prompt Separate Compilation
set CFLAGS=-Wall -Wextra -std=c99 gcc %CFLAGS% -c -g main.c gcc %CFLAGS% -c -g utility.c gcc %CFLAGS% -c -g input.c gcc -g -o myprogram main.o utility.o input.o
If one of the source files should change, we only have to repeat the relevant
gcc $CFLAGS… command for that file, and then perform the “link” command
gcc -g -o…. For three files, this may not be such a big deal, but for 300, it is another story.
Makefile below has been set up so that it will only perform the compilation of modified source files (
*.c). The linker will then also be called to tie it all together into an executable. If you do not have
make, you may still have
mingw32-make, so use that instead.
Makefile assumes that you have a directory for each project, and that all
.c files in that directory, is part of the same project. It should be placed at the same directory level as the source files.
Makefile — Example Program Makefile
# GNU Make Makefile for My Example Program # # Call make with: `make all` for a “debug compile”, or # call make with: `make all RELEASE=1` for a “release compile”. # # Note: On Windows, you may have GNU's `make`, or `mingw32-make`, so you # should run the appropriate version with this `Makefile`. # operating system detection, to make the rest of the file more generic. # ifeq ($(OS),Windows_NT) THEOS := Windows else THEOS := $(shell uname -s) endif # per-project settings and modifications. do not add `.exe` on Windows. # PROG := myprogram SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) CC := gcc CFLAGS := -Wall -Wextra -pedantic -std=c99 LDFLAGS := # change compiler flags based on release/debug compiles. # ifdef RELEASE CFLAGS += -O2 -DNDEBUG else CFLAGS += -O0 -g2 endif # set operating system specifics, like adding `.exe` to the executable # name, and the commands for deleting files (Windows has no `rm`). # ifeq ($(THEOS),Windows) PROG := $(PROG).exe RMCMD := cmd /c del $(PROG) $(OBJS) 2>NUL: else RMCMD := rm $(PROG) $(OBJS) 2>/dev/null || true endif # generic rule for any `*.o` file, which is dependent on a `*.c` file # with the same name, just a different extension. `$@` will result in # the object file's name, and `$<` in the source file name. # %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< # this rule specifies that the program executable, is dependend on # the list of object files in `$(OBJS)`, and the commands to execute # when it is out of date, is to peforming the “link” command. # $(PROG): $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) .PHONY: all clean clean: -@$(RMCMD) all : $(PROG)
IMPORTANT — Hard Tab Characters
A reminder that GNU Make is archaic and cantankerous — it does not handle files or paths with spaces, and it insists on a hard tab character for the commands to execute after a dependency rule. You cannot let your editor expand a press of the ‹Tab› key into spaces.
Makefile can easily be used as a template for relatively uncomplicated multi-file C programs — simply copy to another project directory, and change the value for the
PROG variable. Then run
make all RELEASE=1 (release compile), or
make all (debug compile by default).
Makefile will also work for C++ programs compiled with GCC (
g++), but you have to, by convention, use
CXX for the C++ compiler executable macro, and
CXXFLAGS for C++-specific options in your command lines. Also remember to change reference to
In the above
Makefile example, since we test if we are running on Windows, by checking the existence of the
OS environment variable, which should be equal to
Windows_NT when running on any Windows OS. The
uname -s program and switch should be available on any POSIX system.
operating system detection in a makefile example
From this point on, the rest of the
Makefile code will test
THEOS; for example, we could have written the
clean: target as follows:
conditional directives based on operating system
uname -s will return
Darwin, and on Linux:
Linux, so you could use those values when you want to differentiate between the latter two operating systems.
TIP — Generated Dependencies with GCC
You can use:
g++ -MM *.cpp to create a list of targets and dependencies. Or
gcc -MM *.c for C programs. It will exclude system headers.
Conventional C++ specific macros are:
CXXFLAGS for C++ driver options, and
CXX for naming the specific C++ compiler driver executable. The rest of the
Makefile example is easily convertible for use in C++ projects.
2018-12-07: Corrected some comments in the Makefile. [brx]
2018-07-02: Add C++ notes & fixed
gcc -MM…for C depencies. [brx]
2017-11-19: Update to new admonitions. [brx]
2017-09-22: Created. Edited. [brx;jjc]