make is a tool to simplify building executables from many sources, make will only re-build things that need to be re-built

Contents of a makefile

  • variable definitions, text that can be substituted later
  • explicit rules, says when and how to remake files called the rule’s targets, it lists files that the target depends on called prerequisites and may also give a recipe to update the targets
  • implicit rules, says when and how to remake files based on the filename, it describes the dependencies of the target and gives a recipe to create/update such a target
  • directives, special instructions like
    • reading from another makefile include a.Makefile b.Makefile
    • decide based on some variables to use or not part of the makefile
    • defining multiline variables

Rules

targets : prerequisites
[tab] recipe

A rule tells make two things, when targets are out of date and how to update them when necessary

  • targets are filenames separated by spaces, usually there’s only one filename per rule
  • prerequisites determine when targets are out of date, targets are out of date if it doesn’t exist or is older than any of the prerequisites (by comparison of the last-modification time)
  • recipe determines how to update targets when they’re out of date, this is one or more lines to be executed by the shell

Example

foo.o : foo.c defs.h
  cc -c -g foo.c

The target is foo.o, the prerequisites are foo.c and defs.h, the command to update foo.o is cc -c -g foo.c, additionally it tells two things

  • how to decide whether foo.o is out of date, it’s out of date if foo.c or defs.h is more recent than it
  • how to update foo.o, it’s updated by compiling foo.c assuming that it includes defs.h

Wildcards

A single file can specify multiple files using wildcard character (the same as the ones in Bash e.g. *, ?, "")

clean:
  rm -f *.o     # `make clean` removes all the object files

To define a variable with a wildcard use

objects := $(wildcard *.o)

Phony targets

A phony target is one that is not the name of a file, it’s just the name of a recipe to be executed when you make an explicit request, the two reasons to use a phony target are

  • to avoid conflict with a file of the same name
  • to improve performance by avoiding the implicit rule search on this type of targets

When a rule has a recipe that won’t create the target file it will be executed every time the target comes up for remaking

clean:
  rm *.o program

If make clean is run the target clean will always be out of date (assuming such a file doesn’t exist) then the recipe will always be executed

If there’s a file clean the recipe will never be executed because since the target clean has no dependencies it’s considered to be always up to date, to avoid this problem we make the target a phony target, once this is done the recipe will be executed regardless of the existence of a file named clean

.PHONY: clean
clean:
  rm *.o program

Implicit rules

make is able to figure out which implicit rule to use based on the kind of source file that needs to be make/updated, for example the makefile

foo : foo.o bar.o
  cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

Doesn’t have rules on how to make foo.o or bar.o, make will automatically look for an implicit rule that tells how to make/update it from a catalogue of built in rules

Among the catalogue of built in rules for POSIX based OS the ones for C and C++ programs are

  • Compiling C, n.o is made from n.c automatically with a recipe of the form
$(CC) $(CPPFLAGS) $(CFLAGS) -c
  • Compiling C++, n.c is made from n.cc,n.cpp,n.C with a recipe of the form
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c
  • Linking C, n is made from n.o by running the linker ld via the C compiler with a recipe of the form
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)

Variables used by implicit rules

  • CC (default cc)
  • CXX (default g++)
  • CXXFLAGS, CPPFLAGS, CFLAGS (default empty)

Pattern rules

A pattern rule contains % exactly once in the target which matches any nonempty substring called the stem, then % in the prerequisites of a rule stands for the same stem that was matched in the target, for example a rule in the form

%.o: %.c
  rule

The recipe then needs a way to operate on the right source file name, such a name can’t be written on the recipe because the name is different each time the implicit rule is used, to refer to the correct name we use automatic variables which are variables computed afresh for each rule that is executed, they only have values within the recipe, the most used ones are

  • $@ - the filename of the target of the rule
  • $< - the name of the first prerequisite
  • $^ - the name of all the prerequisites
  • $? - the name of all the prerequisites that are newer than the target
  • $* - the stem

For example

%.o: %.c
  $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

Specifies how to make an object file n.o from a source file n.c provided that n.c exists or can be made, inside the recipe the automatic variables $@ and $< correspond to the target file and source file respectively

Variables

To substitute a variable’s value write $(var) or ${var}

objects = program.o foo.o utils.o
program : $(objects)
  cc -o program $(objects)

$(objects) : defs.h

Setting variables

Variables defined with

  • =, define recursively expanded variable - if the value contains references to other variables these references are expanded whenever this variable is substituted
foo = $(bar)
bar = $(message)
message = hello

all:
  echo $(foo)   # prints hello
  • := or ::= simple expanded variable - the value of a variable is set as of the time it was defined
x := foo
y := $(x) bar
x := later

# at this point
#   - x is equal to `later`
#   - y is equal to `foo bar`
  • ?= sets the value of a variable if it’s not already set
foo = hello
foo ?= bar
# foo is equal to `hello`
  • != executes a program and sets a variable to its output (alternatively use $(shell commands))
foo != printf `hi`
# foo is equal to `hi`

Advanced features for reference to variables

  • substitution reference $(var:a=b) - substitutes every a at the end of a word with b
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# bar is equal to `a.c b.c c.c`
  • computed variable names $($(a)) - nested variable reference
x = y
y = z
a := $($(x))   # a is equal to `z`

Recipes

Each line must start with a tab, any line in the makefile that begins with tab and appears in a “rule context” will be considered part of the recipe for that rule, blank lines that appear in the middle of rules are ignored

Each time a recipe is executed make will invoke a new sub-shell for each line of the recipe, this implies that setting shell variables will not affect the following lines in the recipe

foo: bar/lose
  cd $bar   # dir is ./bar/
  cat file  # dir is ./

Normally make prints each line of the recipe in the shell before it’s executed to avoid this behavior prepend @

program.o: program.c
  @cc -c -g program.c
  # won't print the compilation line on the terminal

To ignore errors in a recipe line prepend the command with -

clean:
  -rm -f *.o

Running make

The simplest use is to recompile every file that is out of date, however it’s possible to update only some files, or find out which files are out of date without changing them

The exist status of make is always one of the following

  • 0 - make is successful
  • 1 - if -q is used and make determines that some target is not already up to date
  • 2 - if make encountered any errors

Goals

The goals are the targets that make should strive to update (other targets are updated if they appear as prerequisites of goals, or prerequisites of prerequisites of goals, etc)

By default the goal is the first target in the makefile (not counting targets that start with .)

A different goal can be specified with arguments to make by using their names, if many goals are specified make processes each of them in turn, any target in the makefile may be specified as a goal unless it starts with - or contains = (parsed as a switch or variable definition respectively)

For example given a project with multiple programs we can compile only a part of the program by specifying as a goal each file that we wish to remake

.PHONY: all
all: a b c

If we’re working on the program a we can execute make a so that only files of that program are recompiled

Specifying a goal has the following advantages

  • make files that are normally not made i.e. rules that are not prerequisites of the default goal e.g. a file for debugging output
  • run a recipe associated with a phony target

Flags

  • -f [filename] - use filename as the makefile (default to GNUMakefile,makefile,Makefile)
  • -n - prints all the recipes that are needed to update the targets without executing them
  • -q - check whether the targets are up to date, the exit code shows if updates are needed
  • -t - makes targets up to date without changing them (their modified times are updated)
  • -k - try to compile every file that can be tried instead of exiting on the first failure

Overriding variables

Given the following makefile

CFLAGS = -g

all: program.o

program.o: program.c
  cc -c $(CFLAGS) program.c

.PHONY: all

We can override the value of the variable CFLAGS when make is executed like this make CFLAGS="-g -O"

Convention for makefiles

Every makefile should include

# avoid trouble on systems where `SHELL` might be inherited from the environment
SHELL = /bin/sh

# specify all the suffixes which may be subject to implicit rules in this makefile
.SUFFIXES:            # clears the suffix list
.SUFFIXES: .c .o
  • Use $(srcdir)/ to refer to the location of the source files when the build directory is distinct from the source file directory
  • Use variables for specifying commands e.g. $(CXX) instead of g++
  • File management utilities such as ln,rm,mv don’t need to be referred through variables since users don’t need to replace them with other programs
  • Every makefile should define the var INSTALL which is the basic command for installing a file into the system

Standard targets

  • all - compiles the entire program, this should be the default target
  • install - compile the program and copy the executables, libraries to the desired place
  • clean - delete all the files that are created by building the program

Example

References