Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Introduction to the "make" Utility

 
By Suminder Ahuja, August 2002  

Contents:

Overview


make is a UNIX® utility intended to automate and optimize the construction of programs. It can help you develop programs when they consist of many components or source files.

A descriptor file, namely makefile, describes the relationship among source files and provides commands for updating each file. make invokes makefile to automatically rebuild a program whenever one of the source files is modified. It only recompiles the files that were affected by changes, thus saving compiling time. This helps reduce the likelihood of human errors when making entries from the command line.

Apart from compilation, make is an effective tool for program installation and system configuration changes.

Introduction to makefile


A configuration file called makefile is constructed for the project. After every change made to the source files, a simple shell command,make, invokes the makefile and performs the necessary recompilations. make looks in the current working directory for one of the description files in the following order:

makefile
Makefile

By default, a simple make command builds the first target in the file. Other common variations to the command are as follows.

make prog1 Builds the target "prog1"
make -f mymakefile Uses the file "mymakefile" as the configuration file and builds first target in the file
make -f mymakefile prog1 Uses the file "mymakefile" as the configuration file and builds the target "prog1"

Components of makefile


makefile comprises five major features: comments, explicit rules, variable definitions, implicit rules, and directives.

Comments


'#' in a line of a makefile starts a comment and extends to the end of the line. A comment can be continued to multiple lines by a trailing backslash ('/') not escaped by another backslash. A comment can start anywhere in the line, and the text before '#' is not part of the comment. The default /bin/sh shell allows a comment on the command line, but not with the define directive.

Explicit Rules


A makefile consists of 'rules.' A rule explains how and when to remake certain files, which are the targets of the particular file. A rule consists of three parts: one or more targets, zero or more prerequisites, and zero or more commands.

target ... : prerequisites  
      	   command
           ...
           ...

A target is usually name of the field that make creates; examples are executable or object files. It can also be a name of an action to be carried out, for example, clean ( see section on Phony Targets). The target is made if any of the prerequisites have changed. A target is considered "up to date" if it exists and is newer than its prerequisites.

A prerequisite is one or more files used as input to create targets. The purpose of prerequisites is to define the dependency of some target on some source.

A command is in a rule with prerequisites. It is an action that make carries to create or update a target file when any of the prerequisites are changed. A rule can have more than one command. Each command to be executed in a rule is interpreted by a shell. By default, make uses the /bin/sh shell. The macro SHELL = bin/sh can override the default shell.

Execution of make

make follows the concept of "dependency rule." make reads the makefile in the current directory and begins by processing the first target. make looks at each of the target's dependencies (prerequisites) to see if they are also listed as targets.

make follows the chain of dependencies and walks down the recursion chain until it finds a target that has no prerequisites, or whose prerequisites have no rules. Once it reaches the end of the chain, it walks back the recursion chain executing the commands found in each target's rule. For every prerequisite encountered that has a rule, make follows the same pattern.

Once all of the prerequisite rules have been run, make eventually returns to the first target. If the target doesn't exist, or it is older than its prerequisites now are, make runs the commands to generate the target, as shown in the following makefile example (Example 1.0).

# Example 1.0
# Linking object files
prog1 : main.o file1.o \
        file2.o display.o
	   cc -o prog1 main.o file1.o \
                      file2.o display.o

# Compiling source files
main.o : main.c mydefs.h
    cc -c main.c
file1.o : file1.c mydefs.h
    cc -c file1.c
file2.o : file2.c command.h
    cc -c file2.c
display.o : display.c command.h
    cc -c display.c

# Removing the executable and object files 
.PHONY : clean
clean :
    	    rm prog1 *.o

# End of makefile 

Notes:

The shell command make or make prog1 invokes the following pattern of execution:

  1. make runs the rule for first target prog1 and figures its dependencies on main.o, file1.o, file2.o, and display.o.
  2. make next checks if any of the four object files are listed as targets. If yes, as in the example, it runs the rule for first prerequisite, that is, main.o, to find its dependencies.
  3. make checks whether the prerequisites of main.o have further dependencies. If no, as in the example, it checks if main.o is up to date. If not, it runs the command for main.o by compiling main.c to get the object file.
  4. make looks at the targets file1.o, file2.o and display.o and compiles these object files in a similar fashion.
  5. make returns to first target prog1. As it now has all up-to-date object files for the rule, it executes the command to build prog1.
  6. make removes (deletes) prog1 and all object files (see section on Phony Targets).
Advantages of make

make is a "smart" tool in terms of how it interprets its dependency rules. Every file in the chain may not need to be compiled. make looks at the time stamp for each file in the chain and compiles from the point that is required to bring every file in the chain up to date. If any file is missing, it is updated if possible.

If you haven't edited any of your files since the last time you compiled your program, and you haven't deleted your copy of the program, then make assumes that the compiler will create exactly the same program you already have, and the utility doesn't bother doing anything. This can save a tremendous amount of compiling time.

Variable Definitions


A variable is a name defined in a makefile to represent a string of text. A line of this form:

NAME = value

defines NAME as a variable with the given value. The value can be extended to multiple lines with the '\' continuation character. The variable name consists of a sequence of characters. Special characters such as ':', '#', '=', or leading or trailing white space are not allowed. Variable names are case sensitive.

We simplify the preceding makefile example (Example 1.0) by using variables as shown in the following example (Example 1.1).

# Example 1.1
# Defining the object (objs) variable 
objs = main.o file1.o file2.o display.o

# Linking object files
prog1 : $(objs)
        cc -o prog1 $(objs) 
        echo prog1 : make complete 

# Compiling source files
main.o : main.c mydefs.h
     cc -c main.c
file1.o : file1.c mydefs.h
     cc -c file1.c
file2.o : file2.c command.h
     cc -c file2.c
display.o : display.c command.h
     cc -c display.c

# Removing the executable and object files 
.PHONY : clean
clean :
    	 rm prog1 $(objs)
         echo clean : make complete 

# End of makefile

Note: Refer to the section on Command Echoing.

In the preceding example, we have defined a value for variable objs.

objs    = main.o file1.o file2.o display.o

Once the variable is defined, its value can be extracted with the $(variable) or ${variable} operators. However, if the parentheses are left off, then just the first character of the variable name is used. Therefore:

  • $(objs) gets the value of variable objs.
  • $objs gets the value of variable o (if any).

make has the flexibility to use a variable that has not been defined; the value returned in this case is an empty string. Before setting a variable, make can also check whether it has been already set or not by using shorthand operator '?='. The following example checks whether foo has been already set or not:

foo ?= bar

If not, make will assign it a value.

Substitution Reference and Computed Variable Names

Two more advanced features of reference variables are substitution reference and computed variable names.

  • Substitution Reference

A substitution reference substitutes the value of a variable with alterations that you specify. It has the form '$(var:x=y)' and its meaning is to take the value of the variable var, replace every x at the end of a word with y in that value, and substitute the resulting string. The following example sets bar to a.c b.c c.c:

foo := a.o b.o c.o
bar := $(foo:.o=.c)
  • Computed Variable Names

A variable can be referenced inside the name of another variable; this is called a computed variable name.

Example:

a = b
b = c
x  :=  $($(a))

This example defines x as 'c'. The $(a) expands to 'b', so $($(a)) becomes $(b); and $(b) expands to 'c'.

Recursively Expanded and Simply Expanded Variables

There are two ways in which a variable can have a value, as recursively expanded variables and simply expanded variables.


Recursively Expanded Variables Simply Expanded Variables
Defined by lines using '=' or by "define" directive Defined by using lines ':='
Value specified is installed verbatim. Value is scanned once and for all when variable is defined.
Reference to other variables is expanded whenever variable is substituted. Do not not contain reference to other variables; contain their value as defined.
Example:

        foo = $(bar)
        bar = $(yep)
        yep = hello
command:
        all :;echo $(foo)

will echo 'hello'.  $(foo) will expand to $(bar) which expands to $(yep) and finally expands to 'hello'.

Example:
        a :=  foo           
        b :=  $(a) bar
        a :=  hello
is equivalent to:
          
        b :=  foo bar            
        a :=  hello
Perform what is intended but cause make to run slow, as reference in the definition will be executed every time a variable is expanded. Make it easier to predict complicated makefile programming.
Appending More Text to Variables

Often we need to add more text to an existing variable. The shorthand operator '+=' provides this flexibility. In the following example, it takes the value of objs and adds the text file3.o to it:

objs  +=  file3.o

Thus we can set objs to main.o file1.o file2.o display.o file3.o:

objs =  main.o file1.o file2.o display.o
objs +=  file3.o
Automatic Variables

These variables have values computed afresh for each rule that is executed, based on the target and the prerequisites of the rule. In Example 1.2, which follows in the section on Implicit Rules, '$@' is used for the object file name and '$+' for the source file names.

Commonly used automatic variables include the following.

$@ The file name of the target of the rule
$% The target member name, when the target is an archive member
$< The name of the first prerequisite
$? The names of all the prerequisites that are newer than the target
$^ The names of all the prerequisites with spaces between them
$+ Similar to '$^', but prerequisites listed more than once are duplicated in the order they were listed in the makefile
Target-Specific and Pattern-Specific Variable Values

  • Target-Specific Variable Values

This feature defines a different value for the same variable, depending on the target make is building. The values are available locally within the context of the target's command script. It is of the form:

target ... : variable-assignment

The following statement sets CFLAGS to -g in the command script for prog1 and its prerequisites:

prog1 : CFLAGS = -g
prog1 : main.o file1.o file2.o display.o
  • Pattern-Specific Variable Values

This feature allows you to define a variable for any target that matches the specific pattern. A pattern is represented as '%'. The following example assigns CFLAGS the value of -o for all the targets matching the pattern %.o:

%.o : CFLAGS = -o

(For further information, see section on Additional References.)

Implicit Rules


Implicit rules tell make how to use customary techniques so that you do not have to specify them in detail when you want to use them. One of the implicit rules updates a '.o' file from a '.c' file using the cc -c command. Using implicit rules, the preceding example with makefile can be written as follows (Example 1.2).

# Example 1.2
#1
# Defining the compiler
CC=gcc

#2
# Defining the object (objs)  variable 
objs = main.o file1.o file2.o display.o

#3
# Linking object files
prog1 : $(objs)
        $(CC) -o $@ $+ 
        echo prog1 : make complete 

#4
# Tell make how to build .o from .c files 
%.o:%.c
	  $(CC) -c $+

#5
# Compiling source files. 
main.o : mydefs.h
file1.o : mydefs.h
file2.o : command.h
display.o : command.h

#6
# Removing the executable and object files
.PHONY : clean
clean :
         rm prog1 $(objs)
         echo clean : make complete 

# End of makefile

Notes:

  1. Using the variable for the compiler provides the flexibility to use the same makefile with different compilers.
  2. The variable objs is defined and equated with all the object files.
  3. '$@' automatic variable means target, and '$+' means all space-separated prerequisites.
  4. Pattern rules tell make how to convert *.c file to *.o file.
  5. make has a built-in pattern for converting *.h file to dependent *.o file.
  6. make deletes prog1 and all object files in the existing directory (see Phony Targets).

The commands build in implicit rules that use certain predefined variables, which fall in two classes: those that are names of programs (like CC) and those that contain arguments for the programs (like CFLAGS).

Some of the variables used as names of programs in built-in rules are as follows.

AR Archive maintaining programs, default ar
AS Program for doing assembly, default as
CC Program for compiling C programs, default cc
CXX Program for compiling C++ programs, default g++
RM Command to remove a file, default rm -f

Here are some of the variables whose values are additional arguments for the program.

ARFLAGS Flags to give the archive maintaining program; default rv
ASFLAGS Extra flags to give to assembler
CFLAGS Extra flags to give to C compiler
CXXFLAGS Extra flags to give to C++ compiler

Directives


The "Define" Directive

The define directive allows you to set the value of a variable. The define directive is followed on the same line only by the name of the variable. The value appears on the following lines. The endef in the last line marks the end of define. define works just like '=', for example:

define two-lines
echo foo
echo $(bar)
endef

The difference between ordinary variable assignment and the define directive is that the value in an ordinary assignment cannot contain a new line, but the new lines that separate the lines of the value in define become part of the variable's value.

The previous example is functionally equivalent to this:

two-lines = echo foo; echo $(bar)

Two commands separated by a semicolon behave much like two separate shell commands. However, note that using two separate lines means make will invoke the shell twice, running an independent subshell for each line.

The "Include" Directive

The include directive tells make to suspend reading the current makefile and read from each listed file in turn. After finishing, make resumes reading the makefile in which the directive appears. In the form:

include filenames...

filenames can contain shell file name patterns.

For example, if you have three '.mk' files, 'x.mk', 'y.mk', and 'z.mk', and $(bar) expands to bish bash, then the following expression:

include foo *.mk $(bar)

is equivalent to:

include foo x.mk y.mk z.mk bish bash

One use of include directives is to assign a common set of variable definitions for programs handled by individual "makefile" files.

The "Override" Directive

The override directive can set the variable in the makefile, which is already set with a command argument. It is of the form:

override variable = value

To append more text to a variable defined on the command line, use:

override variable += more text

This directive was invented to alter and add to values that the user specifies with command arguments. For example, suppose you always want the -g switch when you run the C compiler, but you would like to allow the user to specify the other switches with a command argument as usual. You could use this override directive:

override CFLAGS += -g

Command Echoing


make prints each line before it is executed, called echoing. Echoing can be suppressed by '@' when starting. The echo can also be used to indicate progress through the makefile. For example:

echo prog1 : make complete

With the -n flag, make only echoes commands and does not execute them. Only in this case, even the commands starting with '@' are also printed. To prevent all echoing, the -s flag is used. Output acts as if all commands started with '@'.

Phony Targets


The phony target is not a name of a file but the action to be executed with an explicit request. Commands in phony targets do not create any target, but the command will be executed every time the target comes up for remaking. For example:

clean :
    	    rm prog1 *.o

deletes the prog1 and all the object files when the command is explicitly invoked with:

make clean

As the rm command does not create a file named clean, probably no such file will ever exist. But if anything ever does create a file named clean in this directory, the phony target will cease to work. The file clean would inevitably be considered up-to-date as it has no prerequisites, and its command would not be executed.

To avoid this problem, the target is declared as .PHONY. In the following example, make clean will run the commands regardless of whether or not there is a file named clean :

.PHONY  :  clean
clean  :
	   rm  prog1  *.o

Using Wildcard Characters in File Names


A single file name can specify many files using wildcard characters. make uses wildcard characters '*', '?', and '[...]' in target prerequisites, commands, and variables. Wildcards can be used in the commands of a rule, where they are expanded by the shell. For example, here is a rule to delete all the object files:

clean:
        rm *.o

Wildcard expansion does not occur when you define a variable. Thus, if you write this:

objects = *.o

then the value of the variable objects is the actual string '*.o'.

Errors in Commands


After a command is executed, make checks the exit status to verify successful completion, and sends the next command line to a new shell. If an error is encountered on return, make gives up on the current rule and perhaps on all rules. To ignore errors in a command line, write a '-' at the beginning of the line's text (after the initial tab). The '-' is discarded before the command is passed to the shell for execution. The following example causes rm to continue even if it is unable to remove a file:

clean:
        -rm  -f  *.o

A similar feature can be achieved by combining the -i flag with make; errors are ignored in all commands of all rules.

Parallel Execution


By default, make executes only one command at a time, waiting for it to finish before executing the next. Combined with '-j' option, make can execute many commands simultaneously. An integer suffix to '-j' option specifies the number of jobs that can be executed at once.

Updating Archive Files Using make


Archive files called member are used as subroutine libraries for linking. They are maintained with the program ar. In this form:

archive(member)

an individual member file can be used as a target or prerequisite in make. In the following example, the rule says to create a member foo.o in archive sublib by copying the file foo.o:

sublib(foo.o)  :  foo.o
	   ar  cr  sublib foo.o

Conclusion


This document is a starting kit for using makefile, and it covers all the necessary building blocks. make has many features that can be explored in the following references. Experiment with your own new variations on makefile.

Additional References


  1. GNU Make, a program for directing recompilations
  2. Sun official reference page on make

About the Author


Suminder Singh Ahuja is a software engineer with four years of experience in high-volume transaction technologies and databases such as TPF, IBM 370/390, VM/CMS, and UNIX. He has worked three years for Galileo, dealing in airline reservation solutions. Feel free to contact him at Suminder: suminder@rediffmail.com

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.