world leader in high performance signal processing
Trace: » make

Using Make


The make command is actually a powerful utility. It can be used to generate a series of commands to be executed by the shell. It is intended for use in maintaining files and, most commonly, for generating the final executable for a complex collection of programs. The GNU Make documentation is cited in the References section, but many Unix programming books will also have a chapter or section on make. There is an excellent O'Reilly book on the System V Release 4 version of make, but here we stay with GNU make. Since make works with a potentially large set of interdependent files, we need to specify those interdependencies along with what we want done. This information is provided in a makefile. If make is invoked without specifying a makefile it works through the default names in order until it finds one. These names are

  • GNUmakefile
  • makefile
  • Makefile

However, you can specify your own choice e.g.

make -f my_makefile

We cannot do any more here than hint at the power of make. Nevertheless, we'll find it useful to work through a simple example. We assume that the files reside in the same directory and that is the directory from which we will invoke

  • main.c
  • parser.c
  • lexer.c
  • makefile

Creating a New Makefile

Consider a project with the three C files, main.c, parser.c, and lexer.c. These are to be combined into a single executable. So that you can work through this in the exercises, we'll pick these to be essentially stubs of some final large mythical programs.


#include proj.h
extern int parser();
int main() 


#include "proj.h"
#include "parser.h"
extern int lexer();
int parser() 


#include "proj.h"
extern int lexer();
int lexer() {

To combine these into a single executable, app, we would do the following sequence of steps:

To build above application by hand

rgetz@test:~/test> gcc -c lexer.c
rgetz@test:~/test> gcc -c parser.c
rgetz@test:~/test> gcc -c main.c
rgetz@test:~/test> gcc -o app main.o parser.o lexer.o

On the host

As the project moved along we would work on one part or another and periodically recompile the appropriate parts, keeping careful track. The make utility automates this and the up front work of writing the makefile is not a great burden. In fact, it helps organize your approach to the problem. For our example, we'll start with the makefile below.


#define CC
CC = gcc 
#declare a target (app) and list its dependants
# specify the rule to build the target 
# NOTE start each command line with a <TAB> 
app: main.o lexer.o parser.o
      $(CC) $(CFLAGS) -o app main.o parser.o lexer.o
main.o: main.c proj.h
      $(CC) $(CFLAGS) -c main.c
parser.o: parser.c proj.h parser.h
      $(CC) $(CFLAGS) -c parser.c
lexer.o: lexer.c proj.h lexer.c
      $(CC) $(CFLAGS) -c lexer.c

Makefiles must use tabs, and end with no white space at the end of a line, copying and pasting will not work, unless you correct this

Comparing this to the manual steps indicates the flavor of what is happening, but let's go through it and then try some variations. first line

CC = gcc

Here CC and CFLAGS are a user defined variable, which has been given a value of 'gcc', clearly intended to identify the compiler and '-O2' to set any flags for the compiler. When it is later used, it will be enclosed to look like this


If we wanted the capability to compile for subsequent debugging, but wanted to be able to turn that feature off, we could write

CC = gcc
# CC = gcc

The '#' designates a comment, so to turn on the debugging support, we would just remove the '#' remainder of the makefile The rest of the file consists of rules. Make will parse each rule and find out:

  • what it should build - the target
  • what the target depends on - the dependencies
  • how to build the target - the command(s)

The format is : target: dependency list
list of commands

Important note: For historical reasons, too deeply entrenched to change, a command must begin with a tab character. Some editors can be configured to substitute the appropriate number of spaces for the tab character. This will not work for make. As an example, consider this rule taken from our makefile:

app: main.o lexer.o parser.o
$(CC) -o app main.o parser.o lexer.o

Here, app is the target. It depends on the files main.o, parser.o, and lexer.o (the dependencies). The second line tells make how to build app. Now make is smart enough to parse the whole file and figure out that the dependencies form a hierarchy and then knows which commands to do first. Further, make will not do unnecessary work. It will not bother to rebuild files whose constituent components have not changed.

A More Terse Makefile

Here is a makefile that works just like the earlier one. App:

main.o lexer.o parser. main.o: main.c proj.h parser.o: \
parser.c proj.h parser.h lexer.o: lexer.c proj.h lexer.c 

We've taken out all the commands. This will work because make can deduce the needed commands.

In this section we've introduced only the tip of the iceberg. We urge you to not only start using make (if you don't already) and get the GNU make documentation to learn its many other capabilities. As is typical with other UNIX derived tools, if you wish it had some particular feature, chances are very good that it already does.

Activities / Exercises

With an editor create the files

  • main.c
  • parser.c
  • lexer.c
  • makefile

where all reside in the same directory. You'll also need the corresponding header files, which can be empty. Create this with touch i.e.

touch proj.h

touch parser.h

touch lexer.h

Carry out these activities:

  • Now invoke make and note what is reported to the screen and what new files are created.
  • Try removing some *.o files, one at a time, and do another make.

Does what make reports to the screen make sense?

  • Try removing one of the header files.

What happens?