My Avatar

Shilong ZHAO

Some GNU Make Notes

2016-02-20 00:00:00 +0100

In case you have any questions or suggestions, you can leave comments HERE . Thanks!

Syntax

It is confusing sometime seeing shell syntax instructions inside Makefile. That is because there are two distinct syntaxes in Makefile: make syntax and recipe syntax.

make syntax

You already know this, right? ;)

recipes syntax

Recipes are written in shell syntax and will be executed in the shell after a few interpretation by make .

Each line inside the recipe must be started with a tab. If a shell variable is needed, then it will be preceded with two dollar signs (‘$$’), where the first one escapes the second.

For the following example

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

will be interpreted to the shell as

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

Environment

There are three different environments: the Makefile itself, the Makefile shell, the parent shell starting Makefile

Two Flavors of Variables

recursion expansion

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all: ;echo $(foo)

It will cause an infinite loop in variable expansion.

Simply expanded variables

x := foo
y := $(foo) bar
x := later

is equivalent to

y := foo bar
x := later

Automatic Variables

$@ target name

$% target member name, when target is an archive file

$< first prerequisite name

$^ name of all prerequisites, separated with spaces

The eval Function

The eval function allows you to define new makefile constructs that are not constant. The result of evaluating other variables and functions. The argument to the eval function is expanded, then the results of that expansion are parsed as makefile syntax. The expanded results can define new make variables, targets, implicit or explicit rules, etc.

If in a project, there are multiple targets, say server and client. They are built with the same instruction structure with only differences in variables.

CC=cc
LD=cc
CFLAGS=$(shell pkg-config openssl --libs)
TRAGETS=client server

client_OBJS=client.o
server_OBJS=server.o
common_OBJS=common.o

all: $(TARGETS)

# we will replace the following two rules
client: $(client_OBJS) $(common_OBJS)
    $(LD) -o $@ $^ $(CFLAGS)
server: $(server_OBJS) $(common_OBJS)
    $(LD) -o $@ $^ $(CFLAGS)

%.o: %.c
   $(CC) $^ -c -o $@ 

The only differences in the rule server and client are: target name (server vs client)and first prerequisite name(server_OBJS vs client_OBJS).

It implies us that we can construct the corresponding rules with eval function. And here is the Makefile example for a simple project in book Network Security with OpenSSL Chapter 5.

CC=cc
LD=cc
CFLAGS=$(shell pkg-config openssl --libs)
TARGETS=client server

client_OBJS=client.o
server_OBJS=server.o
common_OBJS=common.o

all: $(TARGETS)

define TARGET_TEMPLATE

$(1): $$($(1)_OBJS) $(common_OBJS) 
   $(LD) -o $$@ $$^ $$(CFLAGS)

endef

$(foreach target, $(TARGETS), $(eval $(call TARGET_TEMPLATE,$(target))))

%.o: %.c
   $(CC) $^ -c -o $@ 

clean: 
   rm -rf *.o $(TARGETS)