Using the make utility to build software (Advanced)

  • Post author:
  • Post last modified:December 29, 2023
  • Reading time:7 mins read

make is a utility for building programs based on the contents of a specially formatted text file named Makefile or makefile under Linux and Unix environments. A makefile has explicit rules, implicit rules, variable definitions, directives and comments. In the primer make tutorial, we looked at some example makefiles containing explicit rules. In this tutorial we will look at the variable definitions, directives and implicit rules.

VARIABLES

There are two kinds of variables in make. The first one is called recursively expanded variables. These are defined using the = operator. The variables are expanded in a lazy fashion, just at the time required. For example,

x = $(y)  
y = $(z)  
z = a  

all:  
        @echo $(x)  

The substitutions happen when the value of x is to be printed in the echo command. The value printed is a.

The second kind of variables are called Simply expanded variables and are defined using the := operator. For example,

x := $(y)   
y := $(z)   
z := a   

all:     
        @echo $(x)   

Since y is not initialized (it does not exist) at the time x is assigned, x does not have a value and the execution of command echo prints nothing.

PRE-DEFINED VARIABLES

Some of the pre-defined used by make for implicit rules are:

  • CC, program for compiling C programs; default: cc
  • CXX, program for compiling C++ programs; default: g++
  • CFLAGS, extra flags for the C compiler
  • CPPFLAGS, extra flags for the C preprocessor
  • CXXFLAGS, extra flags for the C++ compiler

AUTOMATIC VARIABLES

There are automatic variables in make. Automatic variables are computed afresh for each execution of a rule based on target and prerequisites. The automatic variables are valid only in the commands for a rule and are not defined for the target part or the prerequisites part of a rule. Some of the important automatic variables are,

  • $@, the filename of the target of the rule,
  • $<, the name of the first prerequisite,
  • $?, the name of all prerequisites newer than the target, separated by spaces,
  • $^, the name of all prerequisites with spaces in between them,
  • $|, the names of all order-only prerequisites, with spaces in between them
  • $*, the stem with which an implicit rule matches

Directives

A directive is a special command for make to read another makefile, decide whether to use or ignore a part of makefile based on values of some variables and defining a variable verbatim from a string containing multiple lines. The following part of a makefile gives an example of a directive defining a multi-line variable.


OBJECTS = scheduler_app.o scheduler.o linklist.o 

define complete_message 
@echo $(base_msg) 
@echo $(additional_info) 
endef 

base_msg = target 
additional_info = made 

scheduler-app: $(OBJECTS) 
         gcc $(OBJECTS) -o scheduler_app 
         $(complete_message)  

After scheduler_app has been made, it displays target on one line and made on the next. The variables defined using define directive are recursively expanded variables.

make PROCESSING

make works in two phases. First it reads the entire makefile. It captures all the variables and rules in its internal variables and structures. It, then, decides which targets need to be built and executes the command for doing so.

BUILT-IN IMPLICIT RULES

There are built-in implicit rules in make to build various targets from corresponding pre-requisites. Built-in rules use the customary command and pre-defined variables to build targets. You can change the default value of pre-defined variables to fine-tune built-in rules to your requirements. For example, the built-in implicit rule of making a .o file from .c file is by the command cc -c file.c.

# 
# Makefile 
# 
OBJECTS = scheduler_app.o scheduler.o linklist.o  

scheduler_app: $(OBJECTS) 
        gcc $(OBJECTS) -o scheduler_app  


scheduler_app.o: scheduler.h 

scheduler.o: scheduler.h 

linklist.o: linklist.h 


.PHONY: clean 
clean: 
        rm *.o scheduler_app 

In most cases, especially in Linux environments, cc defaults to gcc, cc, being a symbolic link to gcc. The object files scheduler_app.o, scheduler.o and linklist.o are all compiled by the default command,

$(CC) -c $(CFLAGS) $(CPPFLAGS) file.c

Similarly, the default command for compiling C++ programs is

$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) file.cc

The preferred extension for C++ programs is .cc.

PATTERN RULES

We can define implicit rules by writing pattern rules. In a pattern rule, the target contains a % character. A % matches a non-empty string in the target. For example %.o matches any .o file. The sub-string matched by % is called the stem. For example, %.o matches scheduler.o and scheduler is the stem. In a rule, % refers to the same stem. So, if cc and gcc do not mean the same command on your system, you can ensure that gcc is used for compilation by writing the following pattern rule,

%.o: %.c 
      gcc -c $< -o $@

$< and $@ are the automatic variables described earlier in this tutorial. $< refers to the first prerequisite while $@ refers to name of the target of the rule. This is the rule for building any .o file from .c file. And the complete makefile incorporating the pattern rule is,

# 
# makefile
#

OBJECTS = scheduler_app.o scheduler.o linklist.o
%.o: %.c
        gcc -c $< -o $@

scheduler-app: $(OBJECTS)
         gcc $(OBJECTS) -o scheduler_app

scheduler.o: scheduler.h
linklist.o: linklist.h
scheduler_app.o: scheduler.h

.PHONY: clean
clean:
        rm *.o scheduler_app

REFERENCE

SEE ALSO

Using the make utility to build software (Primer)

Share

Karunesh Johri

Software developer, working with C and Linux.
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments