===== Make and Makefiles =====
//make// is a specialized scripting language used to build software. Unlike most scripting languages, commands are not executed in linear (start-to-finish) sequence; instead, command sequences are defined in terms of what input they accept and what output they produce, and //make// automatically sequences the commands to produce the required output.
Running the ''make'' command by itself will execute the makefile script named ''Makefile'' or ''makefile'' in the current directory.
===== Targets and Dependencies =====
Picture a very simple build, where the file ''test.c'' is compiled by //gcc// into the executable binary named ''test''.
test.c -> compiled by the command 'gcc test.c -o test' -> test
The binary executable product file, ''test'', is considered the //target// -- the object to be built. The file ''test.c'' is a dependency - a file that is required in order to produce the target. //gcc// is the command that builds the target from the dependency.
In a make script (typically called a //Makefile//), the syntax looks like this:
target: dependencies
commands
(Note that the //commands// must be indented by tabs and not spaces in most versions of //make//).
The example above could be written:
test: test.c
gcc test.c -o test
Here is the result when this //Makefile// is executed:
$ ls -l
total 8
-rw-rw-r--. 1 chris chris 35 Jan 10 19:21 Makefile
-rw-rw-r--. 1 chris chris 40 Jan 10 19:15 test.c
$ cat Makefile
test: test.c
gcc -o test test.c
$ make
gcc -o test test.c
When executed a second time, //make// does nothing:
$ make
make: `test' is up to date.
This is because the timestamp on the target (''test'') is later than the timestamp on the dependency (''test.c''). If the dependency has been changed since the target was built, though, then //make// will rebuild the target.
==== Complex Dependencies ====
A more complicated build will involve a number of targets and dependencies. C programs, for example, can be compiled into intermediate files, called object files (.o extension), which can then be combined to produce executables.
Picture this scenario:
* There are three object files:
* double.c, number.h, and sauce.h compile to make: double.o
* half.c, number.h, and sauce.h compile to make: half.o
* sauce.c compiles to make: sauce.o
* There are two binary targets:
* double.o and sauce.o can be linked to produce: double
* half.o and sauce.o can be linked to produce: half
The //Makefile// for these relationships may be written like this:
CC=cc
CFLAGS=-O3
all: half double
half: half.o sauce.o
${CC} ${CFLAGS} -o half half.o sauce.o
double: double.o sauce.o
${CC} ${CFLAGS} -o double double.o sauce.o
half.o: half.c number.h
${CC} ${CFLAGS} -c half.c
double.o: double.c number.h
${CC} ${CFLAGS} -c double.c
sauce.o: sauce.c
${CC} ${CFLAGS} -c sauce.c
There are several things worth noting about this //Makefile//:
- Variables are used for the name of the compiler and the compiler flags. This makes it very easy to change these values -- to use the //gcc// compiler, for example, the CC variable could simply be changed to //gcc//. If variables were not used, you would have to change every line that invoked the compiler.
- //all// is a dummy target. Since it appears as the first target in the file, it is executed by default if no target is specified as an argument to the ''make'' command. It depends on the //half// and //double// files, which will be built in order to satisfy the dependency. However, the //all// target does not specify any commands, and the file ''all'' will never be built.
When //make// is executed the first time, five compilations are performed:
$ make
cc -O3 -c half.c
cc -O3 -c sauce.c
cc -O3 -o half half.o sauce.o
cc -O3 -c double.c
cc -O3 -o double double.o sauce.o
Note that the commands are not being executed in the order in which they appear in the file -- instead, they are ordered according to dependencies.
When executed a second time, no compilations are performed:
$ make
make: Nothing to be done for 'all'.
If the file ''half.c'' was edited or the last-modified timestamp (mtime) was updated, running //make// would execute two compilations:
$ touch half.c
$ make
cc -O3 -c half.c
cc -O3 -o half half.o sauce.o
This reveals the power of //make// -- it does the absolute minimum in order to build the specified target.
On a large programming project, a binary may be comprised of hundreds or even thousands of source files, and compiling all of those files may take hours. If a software developer edits just one file, it's a waste of time to rebuild everything, so //make// can save a lot of time -- especially when the software is rebuilt many thousand times.
==== Fake Targets ====
It is not uncommon to include "fake" targets in a Makefile -- targets which never get built, but which perform a useful operation. For example, a target of "all" never produces an actual file named "all". Typical fake targets include:
* all: build all binaries
* docs: builts all documentation (e.g., generates PDFs, HTML, manpages, etc)
* install: install all files, building binaries, documentation, etc if required
* clean: erases all built intermediate and binary files
* dist-clean (or distclean): erases all files not included in the original distribution of the source
* check (or test): tests the software
The make command will exit as soon as any command pipeline fails, so fake targets which may non-fatally fail are usually forced to return a success code; for example, to delete files which may or may not exist as part of a "clean" target, code such as this may be used:
rm *.o || true
===== Examples =====
* A tarball of simple Makefile examples: https://matrix.senecacollege.ca/~chris.tyler/spo600/makefile-examples.tgz