Sunday, February 15, 2009

A short introduction to GNU make

A make rule looks like the following:

target: prerequisite
commands

Example:

.PHONY: all
all: program1 program2
program1: a.o b.o
gcc -o program1 a.o b.o
program2: c.o d.o
gcc -o program2 c.o d.o


This tells make not to search for a file named all and to assume that this target
is always obsolete.


Defining variables:

Variables in a make file are like in an ant build script but they
can only be set once unlike ant properties and dont change value.

FLAGS = first
all:
@echo FLAGS=$(FLAGS)

FLAGS = second
other:
@echo FLAGS=$(FLAGS)


The FLAGS variable is assigned the last value which is set in the build file
viz. second and so even if we run all target, the value echoed for FLAGS
will be second.
* So, a variable should be set just once in the make file. *

VAR = value
VAR := value


The := form allows variables to reference themselves without recursion.

VAR = value
# Wrong! Causes infinite recursion
VAR = $(VAR) more

# Okay, the := prevents recursion
VAR := $(VAR) more


# A GNU extension that does the same thing
VAR = value
VAR += more

make automatically removes leading and trailing white space from your
variable values when you use the traditional syntax.

TRAD= Hello World
all:
@echo "$(TRAD)" -- removes the spaces in the o/p


If we want to preserve the spaces:

TRAD=$() Hello World $()


we can use the built-in empty variables $() to mark the beginning and end.

GNU make comes with many implicit rules to do almost everything you need.


.c.o:
$(COMPILE.c) $(OUTPUT_OPTION) $<

where,
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
CC = gcc

This says, “If you see a target with an .o extension, and there is no explicit
rulefor it, look for a file with the same base and the .c extension. If you find
it, run these commands.”

We can checkout such implicit rules and built-in variables by:
make -p

CFLAGS and others are not defined but make allows referencing not defined
variables and will replace them with empty strings.

We can set CFLAGS= -g to enable debugging.

Common variables used in implicit rules:
CC = gcc - C Compiler
CXX = g++ - C++ compiler.
CFLAGS = none - flags passed to C compiler.
CXXFLAGS = none - flags passed to C++ compiler.
CPPFLAGS = none - flags passed to C Pre Processor. Typical flags include -I, -D and -U.



Default suffix rules to create object code:
.c = $(CC) -c $(CPPFLAGS) $(CFLAGS)
.cpp, .cc, .C = $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
.s = $(AS) $(ASFLAGS) -- for assembley code.


Automatic Variables:
Their values can change in the context they are used. Regular variables can
also contain automatic variables.

Example:
build_C_program=$(CC) -o $@ $^

program1: mod1.o mod2.o
$(build_C_program)
program2: mod3.o mod4.o
$(build_C_program)


Note: implicit rule will be used to create the pre-requisites modx.o:

.c.o:
$(COMPILE.c) $(OUTPUT_OPTION) $<


then modx.o will be fed to the build_C_program to generate the programx
executables.

Useful automatic variables in GNU make:
  1. $@ = target filename
  2. $^ = all pre-requisites with duplicate names removed.
  3. $+ = all pre-requisites with duplicate names.
  4. $< = name of first pre-requisite in the rule.
  5. $? = name of pre-requisite newer than target.
  6. $* = base name of target file (without extension).


Manipulating variables with functions:

CSRCS=foo.c
CXXSRCS=foo.cpp
OBJS=$(CSRCS:.c=.o) $(CXXSRCS:.cpp=.o)


or use:
OBJS:=$(addsuffix .o, $(basename $(SRCS)))


shell function:
CURRENT_TIME=$(shell date +%T)
something:
@echo started $(CURRENT_TIME)


Other functions provided by GNU make are:
  • $(subst from, to, text)
  • $(patsubst from-pattern, to-pattern, filenames)
  • $(strip string) -- removes the whitespace chars.
  • $(findstring match, string)
  • $(filter patterns, filenames) -- returns files that match one or more space
  • separated patterns
  • $(sort text) - lexical order sort
  • $(word n, text) - nth word in text
  • $(error message) - fails the build after printing the message.


User can define custom functions which can be called as:
myfunction = @echo $(1) $(2)
all:
$(call myfunction,hello,world)


Conditionals:
ifeq, ifneq
ifdef, ifndef

ifeq ($(shell uname -o),GNU/Linux)
CPPFLAGS += -DLINUX
endif


Note: The text that appears inside the conditional clause may be any valid make lines, but conditionals may not be used inside the command section of rules or inside variable/function definitions.

To ensure that A_VITAL_VARIABLE is defined or else the build has to fail:

ifndef (A_VITAL_VARIABLE)
$(error A_VITAL_VARIABLE is not set)
endif
Generally, projects will have a Makefile for each project source directory.

A sample makefile:
The below makefile resides in the bin/ directory and compiles C and C++ sources in src/ directory. The executables are created in bin/ directory.
.PHONY: all


VPATH=../src
CFLAGS += -Wall -Wformat-nonliteral -Wformat-y2k
build_C_program=$(CC) $(CFLAGS) -Wstrict-prototypes -o test $^
build_CXX_program=$(CXX) $(CXXFLAGS) -Weffc++ -o hello $^
CSRCS=test.c
CXXSRCS=hello.cpp
COBJS=$(CSRCS: .c=.o)
CPPOBJS=$(CXXSRCS: .cpp=.o)
CURRENT_TIME=$(shell date +%T)

ifdef (debug)
CFLAGS += -g
CXXFLAGS += -g
else
CFLAGS += -O2
CXXFLAGS += -O2
endif


all: empty.so ccode cppcode
@echo started $(CURRENT_TIME);

ccode: $(COBJS)
@echo Building C code; \
$(build_C_program)

cppcode: $(CPPOBJS)
@echo Building CPP code;\
$(build_CXX_program)

empty.so: empty.c
$(CC) -shared -fpic -o $@ $^

run: all
LD_LIBRARY_PATH=../src ./test

clean:
rm -f empty.so $(COBJS) $(CPPOBJS)

cleanall: clean
rm -f $(basename $(COBJS) $(CPPOBJS))

No comments: