make
A small orientation guide, to getting a gcc and make C development environment running. Assuming everything is installed on your system.
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
The 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:
make⌗
{% highlight make %}
P=seething
OBJECTS= main.o safe_sum.o
CC=gcc
CFLAGS= -Wall -g -std=gnu11 -O0 -Iinclude -Iinclude/engine
CFLAGS+= pkg-config --cflags glib-2.0
LDLIBS=
LDLIBS+= pkg-config --libs glib-2.0 gobject-2.0 gio-2.0
# -l
flags e.g: -lglib-2.0
VPATH=src:include:src/engine:include/engine
all: $(P)
$(P): $(OBJECTS) $(CC) $(CFLAGS) -o $(P) $(OBJECTS) $(LDLIBS)
.PHONY: clean clean: rm -f $(P) rm -f *.o rm -f *.log {% endhighlight %}
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.
make essentials⌗
The GNU make Manual is fantastic.
Custom variables
P=seething
OBJECTS= main.o safe_sum.o
the_day=$(shell date +%A)
Implicit variables
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
CC=gcc
Compiler flags:
CFLAGS= -Wall -g -std=gnu11 -O3 -Iinclude -Iinclude/engine
If you’re not familiar with GCC:
-Wall
adds compiler warnings-g
adds symbols for debugging-std=gnu11
compiler should allow code conforming to the C11 and POSIX standards-O3
indicates optimization level three, which tries every trick to build faster code-I
include paths
While in CFLAGS
territory, its important to make note of pkg-config
.
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:
CFLAGS+= -I/usr/include/glib-2.0
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
).
LDFLAGS=
-L
(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
Again, 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
-L
flag tells the compiler where to search for libraries to resolve symbols-Wl
flag passes its flags through from gcc to the linker-R
the linker will embed these into the runtime search path for libraries to link to
VPATH
specifies a list of directories that make should search:
VPATH=src:include:src/engine:include/engine
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 main.o
and 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 main.o
and 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
For 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)
Phony targets
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
Utility functions
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)!)
Running this:
[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'.
Other bits
make -p
will dump out implicit rules and variables available