Makefiles and the make utility


Intro

Below are links to pages created by others, who did a much better (and more thorough) job than I plan to do, but I want to say a couple things before sending you off.

make is a utility that reads a formatted file (a makefile), and can be used to perform any actions that the shell understands. It performs some tedious tasks for you, such as checking dependencies. For example, if make is asked to create a program called hello it will first check to see if that program (called a target) already exists. If the makefile is done properly it will then check all of the source files that went into making the program. If any source files are newer than hello, then the program is out of date, and needs to be re-made, according to the corresponding rule.

If a target depends on other, intermediate targets, targets will be evaluated recursively, file timestamps will be compared, and only those targets that are out of date will be re-built.

Makefiles do not necessarily mean C or C++ programs! They can be used anytime it seems appropriate to perform an action based on the existence (and relative age) of a file. But they are well-suited to compiling a program that has potentially many source files, even compiled in different languages.


Makefiles and targets

You can call your makefile anything (as long as you identify it using the -f option), but w/out any options make will first search for a file called makefile, and if that fails, will then look for a file called Makefile.

A makefile is essentially a list of rules. Each rule contains a target, an optional dependency list, and a (possibly empty) list of commands. Looks something like this:

target : dependencies
        command
        ...

The target is a file to be built. A dependency is a file that the target depends on (needs); it might be source code, or it might be an intermediate file, and have its own target in the file, so that it can be made. If any dependency is newer than the target, the target is rebuilt.

A target may be a phony; that is, the actions don't actually produce a file w/the targetname. It is simply used to invoke some action. A typical example is the clean target, which might remove intermediate files, or all build files. Gnu make provides a special target, .PHONY, to explicitly label phony targets. This is handy if, e.g., you have a file in the directory called clean. Make will then report that "`clean' is up to date", and not execute any of the commands. .PHONY says "whatever, just execute the commands".

Each command, each single line, is executed by the shell (in a separate subshell). If you want a single, mutli-line command, you escape the newlines, and insert semicolons (so the shell can tell the components apart). E.g.:

for (( i=0 ; i<4; ++i )) ; do \
  echo $i ; \
done

You can tell make which target to make, simply by specifying it as an argument on the command line. If you don't explicity supply a target make will build the first target listed in the file (and any dependent targets).


Compiling C/C++ programs

For those of you who are addicted to the magic Run button on your IDE when creating C/C++ programs, a quick discussion of what happens under the hood:

Conceptually, at least, each source file is read by the compiler, which spits out a corresponding object file (.o or .obj). We have machine code at this point, but not an executable. The linker (an entirely different program than the compiler) comes along, identifies the entry point (the main function), and hooks the various object files into a single program.

It saves us much time to compile these object files separately. That way, if we change just one file, we simply re-compile that file, leave all others alone, and re-link.

gcc and g++ are really wrappers, compiling and linking. To compile a file down to object code without linking we use the -c option:

gcc -c hello.c
gcc -c there.c

You will see hello.o and there.o in your directory. To link these file into an executable we could invoke the linker directly, but the wrappers set up many options for us, so we'll just use them. If you give a compiler wrapper an object file it doesn't need to be compiled again, so will just be passed to the linker:

gcc -c there.o hello.o -ohelloThere

The above command results in an executable name helloThere .


References and tutorials