Introduction to Make

Make originated as a system for building compiled code. It is now used as a system for making changes across many files and directories. It is useful whenever a change in one file requires changes or actions elsewhere.

Make is useful for system administrators as well as developers. This article primarily discusses Make as a compilation tool, but it can be effective for program installation or system configuration changes.

Running Make

Make requires a configuration file. Once this file is constructed for your project, you usually type make to build the changed files.

The usual name for this file is Makefile; the capitalization lists the makefile with README and other special files.

When run with no arguments, GNU Make looks for the configuration files named GNUmakefile, Makefile, and makefile in the current working directory. If using other names, call Make with make -f filename.

The makefile in this example displays make complete and does nothing else. By default, Make lists the commands it runs. To have it run quietly, call Make with make -s.

Simple Makefiles

The examples in this article are written for C, and produce the sample target file. Make can produce any target file, use any shell commands, and work from any source files. Make works best with languages where the compiler does not itself try to resolve dependencies.

Experienced users of Make will see redundant lines in the example makefile. Make already knows how to compile some types of files, and the rules for those files can be left out. (These are called implicit rules.) For clarity, the examples show these rules.

A target is considered "up to date" if it exists and is newer than its prerequisites.

Make works backwards, starting with the target of the first rule in the file. In our example, that's sample. Make checks the prerequisites for sample -- main.o and example.o -- to see if they have rules. If they do, it recursively checks their rules.

Make walks down the recursion chain until it finds a target that has no prerequisites, or whose prerequisites have no rules. Once it hits one of those, it walks back up its recursion chain and runs commands as necessary. It creates a recursion chain for every prerequisite it encounters that has a rule.

Once all of the prerequisite rules have been run, it eventually returns to sample's rule. If the file doesn't exist, or is older than its prerequisites now are (after their rules have been recursively tested), it runs the commands to generate sample.

In the example makefile, Make:

Runs the first rule it sees -- sample.

Checks to see whether sample's prerequisites have rules. They do.

Runs the rule for the first prerequisite -- main.o.

Checks to see whether main.o's prerequisites have rules. They don't.

Checks whether main.o is up to date. If not, it runs the commands for main.o.

Runs the rule for the second prerequisite -- example.o.

Checks to see whether example.o's prerequisites have rules. They don't.

Checks whether or not example.o is up to date. If not, it runs the commands for example.o.

Returns to sample's rule

Checks whether or not sample is up to date. If not, it runs the commands to update it.

Make can run the prerequisites in any order. The important part of this sequence is that it runs recursively backwards from the first target (or the target named in the command parameters), and tests only the rules that it encounters in the prerequisites chain.

Make aborts compilation if it receives an error. This is usually useful behavior -- it lets you correct compiler-detected problems during a compile-and-test cycle. The option -i tells Make to ignore errors.

Phony Targets

In software development, it's very convenient to create a script to remove old compiled code so that the next build recompiles everything. It's also convenient to have a script for installing the code. Make allows scripts like this to be included in the makefile, as phony targets. Phony targets may have prerequisites, and may themselves be prerequisites.

The special rule .PHONY tells Make which targets are not files. This avoids conflict with files of the same name, and improves performance.

If a phony target is included as a prerequisite for another target, it will be run every time that other target is required. Phony targets are never up-to-date.

To run a phony target from the command line, call Make with the name of the phony target, e.g.: make clean.

Makefile Variables

As a project gets larger, more files are usually added. If you repeat a list of files, you can accidentally leave files out of the list. It's simpler to make use of a variable that expands into the list.

The syntax for declaring and setting a makefile variable is varname = variable contents. To call the variable, use $(varname).

Use a variable for the compiler, in case you want to use the same makefile with a different compiler.

When called without a rule parameter, Make runs the first rule it encounters. It is more human-readable to explicitly state your first rule. all is a common name for a first rule.

The automatic variable $@ means "the name of the target." The automatic variable $+ means "all prerequisites, space-separated." Automatic variables are pre-defined in Make.

A pattern rule tells make how to convert the prerequisite to the target. The % in the pattern means "one or more characters," and refers to the same string in the prerequisite and the target. This particular pattern tells make how to convert a *.c file to a *.o file of the same name.

The automatic variable $+ means "all prerequisites, space-separated."

These rules are relying on "implicit rules." Make has built-in patterns for converting a *.h file to the dependent *.o. These rules are included to define the prerequisites for the relevant *.o files.

Caveats and Gotchas

This article is written about GNU Make. Other Unix systems use different versions of Make, which may have minor variations.

Make rules require a tab at the beginning of each command. A series of spaces doesn't work.

Make works backwards from the target to the prerequisites.

Final Words

Make has many features I haven't mentioned, and is useful in any situation where a file is dependent on files that may change. Experiment, and develop your own interesting variations on makefiles.