Tools and Development Environment (make)

From Wiki**3

The following examples describe how to use the make tool. This very short guide is not intended as a replacement for the corresponding manual.

Make

Make is a utility for building and maintaining programs. In a more general definition, it can be described as a dependency management tool (in the sense that is controls how targets are updated when dependencies change).

Make is especially useful for managing large programs, in which multiple, often not obvious, dependencies occur.

In general, make reads its rules from a "makefile", but it may be built with implicit rules for frequently used tasks. A makefile is a sequence of definitions (of variables, rules, and directives). The first explicit rule is the default (i.e., the one whose target is to be built if no target is specified).

Below, the most usual features are covered (other features, such as conditionals and cycles are not, since they are not of special interest in most cases).

Variables

Make variables may be initialized with literals or with the result of commands. The following definitions have the same result (assuming that the directory contains source files named file1.src through fileN.src.

 SRCFILES = file1.src file2.src ... fileN.src   <----- literal initialization
 SRCFILES = $(wildcard *.src)                   <----- wildcard initialization
 SRCFILES = $(shell ls *.src)                   <----- use output of shell command

Access to a variable is done by using the $ sign followed by a single character (if the variable name has more than one character, then it must appear in parenthesis). In addition to user-defined variables, make also defines automatic (pseudo-)variables (the list is not exhaustive).

 $(SRCFILES)    <----- access to variable SRCFILES
 $<             <----- first dependency (pseudo-variable)
 $^             <----- all dependencies (pseudo-variable)
 $*             <----- pattern match (pseudo-variable)
 $@             <----- current target name (pseudo-variable)

Some variables are pre-defined by most make distributions. These are normal variables and may be redefined by the user. The following list shows some of the most common (the list is not exhaustive).

  • CXX, CXXFLAGS - the C++ compiler and corresponding compilation flags
  • CC, CFLAGS - the C compiler and corresponding compilation flags

A variable may be defined based on another: for instance, considering the above SRCFILES variable, we could define a new list by editing the previous one, replacing the .src with another (in our example, with .der, for "derived").

 DERFILES = $(SRCFILES:%.src=%.der)

Rules

The general syntax of a rule is as follows (target is supposed to be "built" by the commands 1 though N):

 target: dependency1 dependency2 ... dependencyK
         command1
         command2
         ...
         commandN

There are two types of rules: implicit, which make uses for derived building targets from derived (calculated) dependencies; and explicit, in which both targets and dependencies are specified.

The following is an example of an implicit rule for obtaining a compiled (.o) file from a C++ (.cpp) source, by simple compilation. Variable CXXFLAGS is used to specify compilation flags and CXX is the compiler's name (g++ by default).

 CXXFLAGS=-ggdb -O3

 %.o: %.cpp
         $(CXX) $(CXXFLAGS) -c $*.c -o $*.o

In this rule, % represents a pattern (the same pattern appears in the commands section represented by the $* pseudo-variable).

The same rule could be written thus (here $< represents the first dependency, and $@ the target to be built by the rule):

 %.o: %.cpp
         $(CXX) $(CXXFLAGS) -c $< -o $@

 a.o: a.cpp x.h  y.h
 b.o: b.cpp x.h
         $(CXX) -DSPECIAL $(CXXFLAGS) -c $< -o $@

Additional dependencies cannot be specified in the above rule, but, since make accumulates the dependencies for a given target, additional dependencies may be specified by compatible rules. These additional rules do not need a section with commands (the commands in the implicit rule will be used). Nevertheless, if there are commands in both an implicit and an explicit rule, the explicit's are used. In the first example, a.cpp would depend on x.h and y.h, while b.cpp would depend on just x.h and use a special compilation command.

Comparable explicit rules would be:

 a.o: a.cpp x.h y.h
         $(CXX) $(CXXFLAGS) -c $< -o $@

 b.o: b.cpp x.h
         $(CXX) -DSPECIAL $(CXXFLAGS) -c $< -o $@

Note that in the case of implicit rules (the most frequent), file dependencies can be computed automatically, for instance with GCC. The following examples assume that all files are in the current directory. If they are not, then the second set of examples could be used (CXXFILES is a variable containing the list of C++ source files:

 g++ -M  *.cpp              <--- computes all dependencies (system and local)
 g++ -MM *.cpp              <--- computes all dependencies (only local dependencies)
 g++ -M  $(CXXFILES)        <--- computes all dependencies (system and local)
 g++ -MM $(CXXFILES)        <--- computes all dependencies (only local dependencies)

Of course, it is possible to build a rule for updating the dependencies and many projects do it:

 depend:
         $(CXX) -MM $(CXXFLAGS) $(CXXFILES) > .makedeps

 -include .makedeps

The minus (-) sign before the include instruction indicates that processing should not stop if .makedeps does not exist (normally, and if nothing is told otherwise, make stops when an error occurs).

Examples

  • See the makefiles in the CDK, RTS, and example compilers: those makefiles perform various tasks and use many of the described features.
  • See also the toy examples below.