Skip to content

Instantly share code, notes, and snippets.

@ananyo2012
Last active August 19, 2019 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ananyo2012/3e920e953e0d315cf1b656f22e604cb4 to your computer and use it in GitHub Desktop.
Save ananyo2012/3e920e953e0d315cf1b656f22e604cb4 to your computer and use it in GitHub Desktop.
Notes from GNU make documentation

An Introduction to Makefiles

What a Rule looks like

target … : prerequisites …
        recipe
        …
        …

Simple Makefile

edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o
        cc -o edit main.o kbd.o command.o display.o \
                   insert.o search.o files.o utils.o

main.o : main.c defs.h
        cc -c main.c
kbd.o : kbd.c defs.h command.h
        cc -c kbd.c
command.o : command.c defs.h command.h
        cc -c command.c
display.o : display.c defs.h buffer.h
        cc -c display.c
insert.o : insert.c defs.h buffer.h
        cc -c insert.c
search.o : search.c defs.h buffer.h
        cc -c search.c
files.o : files.c defs.h buffer.h command.h
        cc -c files.c
utils.o : utils.c defs.h
        cc -c utils.c
clean :
        rm edit main.o kbd.o command.o display.o \
           insert.o search.o files.o utils.o

We split each long line into two lines using backslash/newline; this is like using one long line, but is easier to read.

To use this makefile to create the executable file called edit, type:

make

To use this makefile to delete the executable file and all the object files from the directory, type:

make clean

How make Processes a Makefile

By default, make starts with the first target (not targets whose names start with ‘.’). This is called the default goal. (Goals are the targets that make strives ultimately to update. You can override this behavior using the command line or with the .DEFAULT_GOAL special variable.

In the simple example of the previous section, the default goal is to update the executable program edit; therefore, we put that rule first.

The other rules are processed because their targets appear as prerequisites of the goal. If some other rule is not depended on by the goal (or anything it depends on, etc.), that rule is not processed, unless you tell make to do so (with a command such as make clean).

After recompiling whichever object files need it, make decides whether to relink edit. This must be done if the file edit does not exist, or if any of the object files are newer than it. If an object file was just recompiled, it is now newer than edit, so edit is relinked.

Thus, if we change the file insert.c and run make, make will compile that file to update insert.o, and then link edit. If we change the file command.h and run make, make will recompile the object files kbd.o, command.o and files.o and then link the file edit.

Variables Make Makefiles Simpler

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

edit : $(objects)
        cc -o edit $(objects)
main.o : main.c defs.h
        cc -c main.c
kbd.o : kbd.c defs.h command.h
        cc -c kbd.c
command.o : command.c defs.h command.h
        cc -c command.c
display.o : display.c defs.h buffer.h
        cc -c display.c
insert.o : insert.c defs.h buffer.h
        cc -c insert.c
search.o : search.c defs.h buffer.h
        cc -c search.c
files.o : files.c defs.h buffer.h command.h
        cc -c files.c
utils.o : utils.c defs.h
        cc -c utils.c
clean :
        rm edit $(objects)

Letting make Deduce the Recipes

It is not necessary to spell out the recipes for compiling the individual C source files, because make can figure them out: it has an implicit rule for updating a ‘.o’ file from a correspondingly named ‘.c’ file using a ‘cc -c’ command. For example, it will use the recipe ‘cc -c main.c -o main.o’ to compile main.c into main.o. We can therefore omit the recipes from the rules for the object files.

When a ‘.c’ file is used automatically in this way, it is also automatically added to the list of prerequisites. We can therefore omit the ‘.c’ files from the prerequisites, provided we omit the recipe.

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

edit : $(objects)
        cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
        rm edit $(objects)

Another Style of Makefile

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

edit : $(objects)
        cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

Rules for Cleaning the Directory

Here is how we could write a make rule for cleaning our example editor:

clean:
        rm edit $(objects)

In practice, we might want to write the rule in a somewhat more complicated manner to handle unanticipated situations. We would do this:

.PHONY : clean
clean :
        -rm edit $(objects)

This prevents make from getting confused by an actual file called clean and causes it to continue in spite of errors from rm. (See Phony Targets, and Errors in Recipes.)

References:

https://www.gnu.org/software/make/manual/make.html

Writing Makefiles

What Makefiles Contain

  • An explicit rule says when and how to remake one or more files, called the rule’s targets.
  • An implicit rule says when and how to remake a class of files based on their names.
  • A variable definition is a line that specifies a text string value for a variable that can be substituted into the text later.
  • A directive is an instruction for make to do something special while reading the makefile.
    • Reading another makefile
    • Deciding (based on the values of variables) whether to use or ignore a part of the makefile
    • Defining a variable from a verbatim string containing multiple lines
  • ‘#’ in a line of a makefile starts a comment.

What Name to Give Your Makefile

By default, when make looks for the makefile, it tries the following names, in order: GNUmakefile, makefile and Makefile.

Normally you should call your makefile either makefile or Makefile. (We recommend Makefile because it appears prominently near the beginning of a directory listing, right near other important files such as README.)

If you want to use a nonstandard name for your makefile, you can specify the makefile name with the ‘-f’ or ‘--file’ option.

Including Other Makefiles

The include directive tells make to suspend reading the current makefile and read one or more other makefiles before continuing. The directive is a line in the makefile that looks like this:

include filenames…

One occasion for using include directives is when several programs, handled by individual makefiles in various directories, need to use a common set of variable definitions or pattern rules.

Another such occasion is when you want to generate prerequisites from source files automatically; the prerequisites can be put in a file that is included by the main makefile. This practice is generally cleaner than that of somehow appending the prerequisites to the end of the main makefile as has been traditionally done with other versions of make.

If the specified name does not start with a slash, and the file is not found in the current directory, several other directories are searched. First, any directories you have specified with the ‘-I’ or ‘--include-dir’ option are searched (see Summary of Options). Then the following directories (if they exist) are searched, in this order: prefix/include (normally /usr/local/include), /usr/gnu/include, /usr/local/include, /usr/include.

If you want make to simply ignore a makefile which does not exist or cannot be remade, with no error message, use the -include directive instead of include, like this:

-include filenames…

The Variable MAKEFILES

If the environment variable MAKEFILES is defined, make considers its value as a list of names (separated by whitespace) of additional makefiles to be read before the others. This works much like the include directive: various directories are searched for those files

How make Reads a Makefile

Variable Assignment

Variable definitions are parsed as follows:

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate
immediate != immediate

define immediate
  deferred
endef

define immediate =
  deferred
endef

define immediate ?=
  deferred
endef

define immediate :=
  immediate
endef

define immediate ::=
  immediate
endef

define immediate +=
  deferred or immediate
endef

define immediate !=
  immediate
endef

Conditional Directives

Conditional directives are parsed immediately. This means, for example, that automatic variables cannot be used in conditional directives, as automatic variables are not set until the recipe for that rule is invoked. If you need to use automatic variables in a conditional directive you must move the condition into the recipe and use shell conditional syntax instead.

Rule Definition

A rule is always expanded the same way, regardless of the form:

immediate : immediate ; deferred
        deferred

That is, the target and prerequisite sections are expanded immediately, and the recipe used to construct the target is always deferred. This general rule is true for explicit rules, pattern rules, suffix rules, static pattern rules, and simple prerequisite definitions.

Secondary Expansion

.SECONDEXPANSION:
AVAR = top
onefile: $(AVAR)
twofile: $$(AVAR)
AVAR = bottom

Here the prerequisite of onefile will be expanded immediately, and resolve to the value top, while the prerequisite of twofile will not be full expanded until the secondary expansion and yield a value of bottom.

This is marginally more exciting, but the true power of this feature only becomes apparent when you discover that secondary expansions always take place within the scope of the automatic variables for that target. This means that you can use variables such as $@, $*, etc. during the second expansion and they will have their expected values, just as in the recipe. All you have to do is defer the expansion by escaping the $. Also, secondary expansion occurs for both explicit and implicit (pattern) rules. Knowing this, the possible uses for this feature increase dramatically. For example:

.SECONDEXPANSION:
main_OBJS := main.o try.o test.o
lib_OBJS := lib.o api.o

main lib: $$($$@_OBJS)

Here, after the initial expansion the prerequisites of both the main and lib targets will be $($@_OBJS). During the secondary expansion, the $@ variable is set to the name of the target and so the expansion for the main target will yield $(main_OBJS), or main.o try.o test.o, while the secondary expansion for the lib target will yield $(lib_OBJS), or lib.o api.o.

References:

https://www.gnu.org/software/make/manual/make.html

Writing Rules

The order of rules is not significant, except for determining the default goal: the target for make to consider, if you do not otherwise specify one. The default goal is the target of the first rule in the first makefile. If the first rule has multiple targets, only the first target is taken as the default. There are two exceptions: a target starting with a period is not a default unless it contains one or more slashes, ‘/’, as well; and, a target that defines a pattern rule has no effect on the default goal.

Rule Example

foo.o : foo.c defs.h       # module for twiddling the frobs
        cc -c -g foo.c

Its target is foo.o and its prerequisites are foo.c and defs.h. It has one command in the recipe: ‘cc -c -g foo.c’. The recipe starts with a tab to identify it as a recipe.

Rule Syntax

targets : prerequisites
        recipe
        …

Types of Prerequisites

targets : normal-prerequisites | order-only-prerequisites

Consider an example where your targets are to be placed in a separate directory, and that directory might not exist before make is run. In this situation, you want the directory to be created before any targets are placed into it but, because the timestamps on directories change whenever a file is added, removed, or renamed, we certainly don’t want to rebuild all the targets whenever the directory’s timestamp changes. One way to manage this is with order-only prerequisites: make the directory an order-only prerequisite on all the targets:

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c
        $(COMPILE.c) $(OUTPUT_OPTION) $<

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
        mkdir $(OBJDIR)

Now the rule to create the objdir directory will be run, if needed, before any ‘.o’ is built, but no ‘.o’ will be built because the objdir directory timestamp changed.

Using Wildcard Characters in File Names

Wildcard Examples

Delete all object files

clean:
        rm -f *.o

print all the ‘.c’ files that have changed since the last time you printed them

print: *.c
        lpr -p $?
        touch print

Wildcard definition doesn't happen when you define a variable e.g.

objects = *.o

If you use wildcard expression in target or prerequisite wildcard expansion will take place there

objects := $(wildcard *.o)

The above expression uses the wildcard function

Searching directory for prerequisites

VPATH

The value of the make variable VPATH specifies a list of directories that make should search.In the VPATH variable, directory names are separated by colons or blanks. The order in which directories are listed is the order followed by make in its search. e.g.

VPATH: src:../headers

So a file is searched in the src and ../header directories as well

vpath directive

vpath %.h ../headers

tells make to look for any prerequisite whose name ends in .h in the directory ../headers if the file is not found in the current directory.

Writing recipes with directory search

VPATH = src:../headers
foo.o : foo.c defs.h hack.h
        cc -c $(CFLAGS) $< -o $@

$< is the first prerequisite, $^ all prerequisites, $@ is the target file.

Linked libraries

When a prerequisite’s name has the form ‘-lname’, make handles it specially by searching for the file libname.so, and, if it is not found, for the file libname.a in the current directory, in directories specified by matching vpath search paths and the VPATH search path, and then in the directories /lib, /usr/lib, and prefix/lib

Phony Targets

A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance. e.g.

.PHONY: clean
clean:
        rm *.o temp

Multiple Targets

This is useful in two cases.

  • You want just prerequisites, no recipe. For example:
kbd.o command.o files.o: command.h
  • The recipes do not need to be absolutely identical, since the automatic variable ‘$@’ can be used to substitute the particular target to be remade into the commands
bigoutput littleoutput : text.g
        generate text.g -$(subst output,,$@) > $@

is equivalent to

bigoutput : text.g
        generate text.g -big > bigoutput
littleoutput : text.g
        generate text.g -little > littleoutput

Multiple Rules for One Target

An extra rule with just prerequisites can be used to give a few extra prerequisites to many files at once. For example, makefiles often have a variable, such as objects, containing a list of all the compiler output files in the system being made. An easy way to say that all of them must be recompiled if config.h changes is to write the following:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

Another wrinkle is that the additional prerequisites could be specified with a variable that you set with a command line argument to make. e.g.

extradeps=
$(objects) : $(extradeps)

means that the command ‘make extradeps=foo.h’ will consider foo.h as a prerequisite of each object file, but plain ‘make’ will not.

Syntax of Static Pattern Rules

targets …: target-pattern: prereq-patterns …
        recipe
        …

Example:

objects = foo.o bar.o

all: $(objects)

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

Each target specified must match the target pattern; a warning is issued for each target that does not. If you have a list of files, only some of which will match the pattern, you can use the filter function to remove non-matching file names

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
        $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
        emacs -f batch-byte-compile $<

Generating Prerequisites Automatically

In the makefile for a program, many of the rules you need to write often say only that some object file depends on some header file. For example, if main.c uses defs.h via an #include, you would write:

main.o: defs.h

To avoid this hassle, most modern C compilers can write these rules for you, by looking at the #include lines in the source files. Usually this is done with the ‘-M’ option to the compiler. For example, the command:

cc -M main.c

generates the output

main.o : main.c defs.h

Writing Recipes in Rules

Recipe Syntax

  • A blank line that begins with a tab is not blank: it’s an empty recipe
  • A comment in a recipe is not a make comment; it will be passed to the shell as-is.
  • A variable definition in a “rule context” which is indented by a tab as the first character on the line, will be considered part of a recipe, not a make variable definition, and passed to the shell.
  • A conditional expression (ifdef, ifeq, etc. see Syntax of Conditionals) in a “rule context” which is indented by a tab as the first character on the line, will be considered part of a recipe and be passed to the shell.

Splitting Recipe Lines

As in normal makefile syntax, a single logical recipe line can be split into multiple physical lines in the makefile by placing a backslash before each newline.

all :
       @echo no\
space
       @echo no\
       space
       @echo one \
       space

Using Variables in Recipes

Variable and function references in recipes have identical syntax and semantics to references elsewhere in the makefile. They also have the same quoting rules: if you want a dollar sign to appear in your recipe, you must double it ('$$').

LIST = one two three
all:
        for i in $(LIST); do \
            echo $$i; \
        done

results in the following command being passed to the shell:

for i in one two three; do \
    echo $i; \
done

Recipe Echoing

Normally make prints each line of the recipe before it is executed. We call this echoing because it gives the appearance that you are typing the lines yourself.

When a line starts with ‘@’, the echoing of that line is suppressed. The ‘@’ is discarded before the line is passed to the shell. Typically you would use this for a command whose only effect is to print something, such as an echo command to indicate progress through the makefile:

@echo About to make distribution files

When make is given the flag ‘-n’ or ‘--just-print’ it only echoes most recipes, without executing them.

The ‘-s’ or ‘--silent’ flag to make prevents all echoing, as if all recipes started with ‘@’.

Recipe Execution

When it is time to execute recipes to update a target, they are executed by invoking a new sub-shell for each line of the recipe, unless the .ONESHELL special target is in effect (see Using One Shell)

This implies that setting shell variables and invoking shell commands such as cd that set a context local to each process will not affect the following lines in the recipe. If you want to use cd to affect the next statement, put both statements in a single recipe line. Then make will invoke one shell to run the entire line, and the shell will execute the statements in sequence. For example:

foo : bar/lose
        cd $(@D) && gobble $(@F) > ../$@

Using ONESHELL

If the .ONESHELL special target appears anywhere in the makefile then all recipe lines for each target will be provided to a single invocation of the shell. Newlines between recipe lines will be preserved.

.ONESHELL:
foo : bar/lose
        cd $(@D)
        gobble $(@F) > ../$@

If .ONESHELL is provided, then only the first line of the recipe will be checked for the special prefix characters (‘@’, ‘-’, and ‘+’). Subsequent lines will include the special characters in the recipe line when the SHELL is invoked. If you want your recipe to start with one of these special characters you’ll need to arrange for them to not be the first characters on the first line, perhaps by adding a comment or similar. For example, this would be a syntax error in Perl because the first ‘@’ is removed by make:


.ONESHELL:
SHELL = /usr/bin/perl
.SHELLFLAGS = -e
show :
        @f = qw(a b c);
        print "@f\n";

This will work correctly

.ONESHELL:
SHELL = /usr/bin/perl
.SHELLFLAGS = -e
show :
       # Make sure "@" is not the first character on the first line
       @f = qw(a b c);
       print "@f\n";

Choosing the Shell

The program used as the shell is taken from the variable SHELL. If this variable is not set in your makefile, the program /bin/sh is used as the shell. The argument(s) passed to the shell are taken from the variable .SHELLFLAGS. The default value of .SHELLFLAGS is -c normally, or -ec in POSIX-conforming mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment