Example source tree (physical organisation) is as follows:
seething ├── include │ ├── allheads.h │ ├── engine │ │ └── safe_sum.h │ ├── logger.h │ ├── one_loney_integer.h │ └── person.h ├── src │ ├── engine │ │ └── safe_sum.c │ ├── logger.c │ ├── main.c │ ├── person.c │ └── person_tests.c └── makefile
makefile (capital M) is parsed by GNU Make, which is responsible for generating the various GCC compiler commands, required to compile this source code. For example, I could issue individual compiler commands like this to create my binaries:
gcc -Wall -g -std=gnu11 -O0 -Iinclude -Iinclude/engine `pkg-config --cflags glib-2.0` -c -o main.o src/main.c
However, this is tedious, error prone, and slow. I don’t want to have to keep a list of source files I modified, that require rebuilding. Worse, things dependant on changes also need to be rebuilt, forcing me to constantly consider the dependency graph. What a chore. GNU make to the rescue.
To build the above source tree, here is one possible makefile:
This tiny makefile showcases many of the useful features of make, such as variables, implicit variables, recipes, path probing with vpath, automatic make variables (e.g.
$*), phony targets, and utility functions, which I will not delve into detail here. The offical documentation is very good.
The GNU make Manual is fantastic.
P=seething OBJECTS= main.o safe_sum.o the_day=$(shell date +%A)
make is a generic system for generating files, based on other files. It is in no way, specific to a C compiler, and can be used to drive any compiler. Make is a great platform for driving common tasks, such as producing documentation, or running a test suite. To ease working with common languages and compilers, make does have a general awareness, for example, of how to (agnostically) drive a C compiler. If make detects its working with a C, standard (or implicit) variables such as
CC (name of desired C compiler),
CFLAGS (compiler flags),
LDFLAGS (linker flags) etc kick in. See implicit variables for more.
Program for compiling C programs
CFLAGS= -Wall -g -std=gnu11 -O3 -Iinclude -Iinclude/engine
If you’re not familiar with GCC:
-Walladds compiler warnings
-gadds symbols for debugging
-std=gnu11compiler should allow code conforming to the C11 and POSIX standards
-O3indicates optimization level three, which tries every trick to build faster code
CFLAGS territory, its important to make note of
pkg-config - Return metainformation about installed libraries
Hard coding include paths to third party dependencies works, it is likely to become a maintenance point in the future, worse if there are plans to share this makefile with other developers, it is unlikely to work on their system (i.e. poor portability). For example, my project requires the GNOME core utility library GLib. I could append the hardcoded path to my
CFLAG like this:
However, a slightly better approach is to shell out to
pkg-config which will return the neatly formatted
-I, using paths based on my machines installation paths. For example:
CFLAGS+= `pkg-config --cflags glib-2.0`
Linker flags for everything non-library related (e.g.
-L/usr/local/lib) is where to search for libraries to resolve symbols
Libraries that the linker (
ld) needs to, link in.
LDLIBS= LDFLAGS+= -lglib-2.0 # hard-coded version
pkg-config can do linker flags, for more robust makefiles:
LDLIBS+= `pkg-config --libs glib-2.0 gobject-2.0 gio-2.0`
LDADD (ADDitional linker/ld) is useful for feeding any other additional terms to the linker, for example:
LDADD= -Llibpath -Wl,-Rlibpath
-Lflag tells the compiler where to search for libraries to resolve symbols
-Wlflag passes its flags through from gcc to the linker
-Rthe linker will embed these into the runtime search path for libraries to link to
VPATH specifies a list of directories that make should search:
OK, with the compiler and linker guff out of the way, its time to direct
make at our source code.
make generates files from other files, using recipes, the syntax is as follows. Please note, thanks to POSIX standardisation the recipe must be indented with a tab (not spaces):
<target>: <dependency> <recipe>
An example of this in action:
P=seething OBJECTS= main.o safe_sum.o all: $(P) $(P): $(OBJECTS) $(CC) $(CFLAGS) -o $(P) $(OBJECTS) $(LDLIBS)
The first target, in this case
all is the default target, and is dependent on my project
seething. In order to build the binary
seething, make moves on looking for the
seething target, which is dependent on objects
safe_sum.o. The recipe defined instructs make to invoke the
CC compiler, producing an output of
seething. Luckily we don’t have to explain to make how to build
safe_sum.o, because make is smart enough to infer a default build recipe, because we are dealing with C, will be:
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $*.c
safe_sum.o this will wind up being:
gcc -Wall -g -std=gnu11 -O0 -Iinclude -o safe_sum.o safe_sum.c
Automatic make variables
$<returns the name of the first prerequisite
$@the full target filename (eg the .o compiled from .c)
$*target with no suffix (e.g. prog.o, $* is just prog)
A phony target prevents make from confusing it with a real file. For example, with
.PHONY, even if a file called ‘clean’ is actually created,
make clean will still execute.
.PHONY: clean clean: rm -f $(P) rm -f *.o rm -f *.log
make provides a treasure trove of handy utility functions for common tasks such as transforming text. Below uses
filter-out to strip out a couple of CFLAG options:
the_day=$(shell date +%A) .PHONY: stringfun stringfun: $(info $(filter-out -Wall -g -O0,$(CFLAGS))) $(info Ben it's $(the_day)!)
[ben@think]$ make stringfun -std=gnu11 -Iinclude -Iinclude/engine `pkg-config --cflags glib-2.0` Ben it's Sunday! make: Nothing to be done for 'stringfun'.
make -p will dump out implicit rules and variables available