Makefiles are actually fairly generic
: they consist of rules specifying commands to be executed when a file (target
) is older than certain other files (its dependencies
). The commands are arbitrary, but they are expected to rebuild the target from the dependencies.
make(1) usually comes with a very large default Makefile that defines default behaviour for common situations.
The syntax of a Makefile rule is this:
target1 target2: dep1 dep2 dep3
command -flag argument | another > output
yet | another | command
This syntax was designed before it was common sense that whitespace should always be arbitrary. Command lines are defined as lines that start with a TAB character!
Countless Makefile writers have wrestled with this problem, and make doesn't exactly produce a clear error message on it, either.
Another syntactical quirk is in variables: in a Makefile,
$ABC means the value of the variable named
A followed by the string
BC, and you have to use
to refer to the variable named
But worse problems of Makefiles are in their semantics.
For any nontrivial project you want generic rules: rules that do not name names of individual files, but instead, determine how to make *any* *.o out of the corresponding *c, or *any* executable out of the *.o object files it depends on, or *any* PDF file from the corresponding LaTeX source. The original make allows wildcards, so you can write a rule header like this:
# (some rule to compile a *.o file)
# (some rule to compile myexecutable)
It also has special variables to refer to 'the target currently being made' and 'the dependencies newer than the current target'. But many variables are missing. What's worse, the wildcards are resolved on the filesystem, which causes chains to be broken: if all *.o files are missing,
the rule for myexecutable has no dependencies so it won't be triggered, even though there is a rule to make *.o files.
Suffix rule exist to solve this. They look like this:
# (some rule to compile a *.o file from a *.c file)
The semantics is different from normal rules, because it supports "chaining": I can have a sequence of rules like this
latex $? && bibtex $? latex $? # often good enough
dvips -o $@ $?
and even if foo.dvi and/or foo.ps do not exist, foo.pdf will be created from foo.tex.
But this syntax is restrictive: what if my files do not have a suffix at all?
And what to do if rules of both types match for the same target?
From here, things have grown really hairy. The existing Makefiles weren't powerful enough in many situations, but by the time something was done about it, Unix had already fork()ed in various directions. Consequently, we have BSD make, Sun make, pmake, nmake, GNU make, SysV make, and other variants that all provide a different set of extensions. If you see confusing error messages from 'make', you may have to try a different version of 'make'. Try GNU make first, especially if the software you're trying to compile was developed on Linux.
Solaris make, and GNU make, for instance, have pattern rules that look like this:
latex $? && bibtex $& latex $? # often good enough
dvips -o $@ $?
This looks more natural, and widens the set of possible patterns that can be used. However, the semantics aren't completely identical between the two makes: sometimes one of them will remake a target while the other won't, using the same Makefile.
Before you know it, your dependencies are too complex for these constructs. In particular, dependencies are often dynamic: they change during the course of a project, but a program or script can be written to determine them. For example, makedepend is a tool to determine the appropriate Makefile rules for compiling *.o files out of *.c and *.h files, by looking into the
*.c files and analysing the #include statements in them (gcc, which has swallowed all kinds of functionality that used to be in separate utilities, nowadays has a -M option for this purpose).
Some versions of make offer special support for dealing with this situation, but there is a more general method:
$(MAKE) -f $? $@
my-script-to-generate-the-makefile $? > $@
With this construction, the Makefile itself only has to list the files
to which a modification may affect the dependency tree; the generated Makefile
will contain the actual commands used to build the target, and will have an up to date list of dependencies for each target.
When a piece of software is insufficient for the task at hand, there are two remedies: extend it (which led to the many make versions) or wrap it. The make tool has in fact been extended with many features, but unless you standardize on a particular version of make, that doesn't help much, and even if you do, you will often meet situations that pose serious puzzles. The wrapping method consists of writing new software that extends functionality by applying the existing software in a particular way, without actually modifying it. In the case of make, this has led to software utilities that generate Makefiles from their own configuration files: an early one was imake, a very common one is GNU autoconf (which in turn has another tool that generated its configuration files, GNU automake); lately I've seen a lot of CMake which is an alternative that generates Makefiles which in turn call CMake. And lately I've patched GNU make to work around a limitation that I found too surprising to ignore. Oh well.