Philosophy of Make


In this blog post I will try to explain to you the anatomy of a typical make file, as most of the new comers to Unix/Linux arena often find it difficult comprehend the philosophy behind the make & they often do get imitated by its weird looking syntax and structure. We’ll first describe why a make is needed and then I will highlight the basic structure and syntax of a make file and will then we will wrap up the post by presenting some examples to make your concepts clear.

The case for make

Make is a tool that is used to control the build process of your software.

Make automates what is built, how it gets built, when it gets built etc. A makefile is a place where you store all of your commands to build the software.

Similar concept exists in the Java world in shape of Ant and more recently Maven that even provide more functionality and flexibility to compile, test & even deploy your applications through simple xml files.

Suppose you have multiple source files that require long & complex compiler invocations, “make” simplifies this by storing these commands in a single makefile so that they can be managed from a single location. You can also specify which files & directories you want create and delete based upon certain circumstances. Like for example you want all your library files to be compiled into the lib folder of the installation directory. “Make” also makes it easier for other people who want to compile and execute your application; in place of creating huge documentation on how to build your software you can just tell them to run make & it would take care of the rest.

Finally as the final argument in favor of make I think it really speeds up the build process of your application. Make is intelligent enough to recognize which files have changed and thus only builds those files whose components have changed. Plus makefiles also constitute a database of dependency information allowing you to automatically verify that all the files for the build are present.

Anatomy of a MakeFile

A make file is a simple text file containing rules that tell make what to build and how to build it. A makefile consists of number of rules, at its basic the general structure of make looks likes this:-

# Comments use the hash symbol

Target : dependency [dependency] [ … ]

Command 1


Command n

Target is what we are trying to build a binary or an object file. Dependency is a list of one or more files required for the compilation, commands are the steps either compiler invocations or shell scripts required to create the target.

The spaces before commands are TABS & should be used as such, empty spaces would not suffice in their place & you would get the missing separator error. This is the most common problem people new to make get stucked with.

Example of a Make File

edit : main.o kbd.o command.o display.o \

insert.o search.o files.o utils.o

cc -o edit main.o kbd.o command.o display.o \

insert.o search.o files.o utils.o

main.o : main.c defs.h
              cc -c main.c
kbd.o : kbd.c defs.h command.h
              cc -c kbd.c
command.o : command.c defs.h command.h
              cc -c command.c
display.o : display.c defs.h buffer.h
              cc -c display.c
insert.o : insert.c defs.h buffer.h
              cc -c insert.c
search.o : search.c defs.h buffer.h
              cc -c search.c
files.o : files.c defs.h buffer.h command.h
              cc -c files.c
utils.o : utils.c defs.h
              cc -c utils.c
clean :
      rm edit main.o kbd.o command.o display.o \                               
      insert.o search.o files.o utils.o

Two long lines are split into two by using backslash-newline. This make file will create an executable “edit”.

To compile the program simply just type make on the command prompt.

This make file has 10 rules. The first target edit is the default target which is the binary we are trying to create. Edit has eight dependencies from main.o to utils.o, all of these files must exist to create the edit binary. The next line is the command that make executes to actually create the binary. Here make would actually build the executable from all these object files that are named .o.

When make encounters a dependency and it does not exists make executes the command to build it & if the dependency has further dependencies make tries to find them & if they does not exist make tries to build them using the command specified and so likewise make is able to traverse through all of the dependencies if present and build the software smoothly.

Clean target as you can see is the simple command that removes all the object files and as well as the final binary using the rm command.

Phony Targets

A phony target is one that is not really the name of a file. It is just a name for some commands to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.

Like for example there is a file named clean it would be disregarded by make. Phony targets are defined like this in a make file for example

.PHONY : clean zip

rm abc.o xyz

zip : clean

zip *.c *.h Makefile

There are a lot of command line arguments that make can take the following is a short list of useful command options.

-f file                         Read the file name file instead of standard names

-n                         Display the commands make would executes without actually executing them

-s                        Execute silently ie not printing the commands make executes

-w                        print directory names when make changes directory

-r                         disable all of makes built-in rules.

-d                         print lots of debugging information

-k                         keep executing even if one target fails to build.

-jN                        Run N commands in parallel, where n is an non-zero integer.

More exhaustive list can be found at


To simplify editing and maintenance make allows us to use variables. There are four kinds of variables

  1. User defined variables
  2. Environment variables
  3. Automatic variables and
  4. Pre-defined variables

User-Defined Variables

We define variables using the general form:

VARNAME = value [ … ]

It is a convention to use upper case letters for variables names, to obtain a variables value we just parenthesize the variable and add a dollar sign as a prefix.

There are two kinds of variables in “make” simply expanded variables and recursively expanded variables. Recursively expanded variables as the name suggest expands variables within variables until there are no further variables. For example

HOME_DIR = /home/asad

DEV_DIR = $(HOME-DIR)/development

PROJ_DIR = $(HOME-DIR)$(DEV-DIR)/project


If you use SRC_DIR variable somewhere all of the variables would get expanded.

Simply expanded variables are scanned once where they are defined and all embedded variable references are immediately resolved. Syntax is slightly different

CC := gcc

CC += -g

This would result in gcc –g when resolved.

If you consider using recursively expanded variables for this for example

CC = gcc

CC = $(CC) – g

We would get stuck in an infinite loop as $(CC) would keep on getting resolved to the same expression $(CC) & make would throw an error.

Environment Variables

Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment. $(HOME) variable for example can be used in make to refer to the users home directory.

Automatic Variables

Automatic variables are those variables whose value is evaluated each time a rule is executed, based on target and dependencies of that rule. Automatic variables are used to create pattern rules, patterns are generic instructions on how to compile an arbitrary .c file to its corresponding .o file.

Here is a partial list of automatic variables

$@                        The file name of the target of the rule.

$<                        The name of the first prerequisite/dependency.

$?                        The names of all the prerequisites that are newer than the target, with spaces between them.

$*                         The basename (or stem) of a filename.

$^                        Expands to a space-delimited list of all of a rules depedencies.

$(@D)                        The directory part of targets path name, if named target is in the sub-directory.

$(@F)                        The file part of targets path name, if named target is in the sub-directory.

For an exhaustive list please visit


CFLAGS := -g

CC := gcc

.c .o:

$(CC) $CFLAGS –c $* .c

lock: lock.c

$(CC) $(CFLAGS) $< -o $@

for each filename ending in .c make would create an object file ending with .o, while executing $< would be replace with lock.c similarly target name $@ will be replaced by lock.

Pre-Defined Variables

These are the variables that as the name suggests are predefined by make. A few of them are listed below.

AR                        Archive-maintaining program; default `ar’.

AS                        Program for compiling assembly files; default `as’.

CC                        Program for compiling C programs; default `cc’.

CO                        Program for checking out files from RCS; default `co’.

CXX                        Program for compiling C++ programs; default `g++’.

CO                         Program for extracting a file from RCS; default `co’.

CPP                        Program for running the C preprocessor, with results to standard output; default `$(CC) -E’.

RM                        Command to remove a file; default `rm -f’.

ARFLAGS            Flags to give the archive-maintaining program; default `rv’.

ASFLAGS            Extra flags to give to the assembler (when explicitly invoked on a `.s’ or `.S’ file).

CFLAGS            Extra flags to give to the C compiler.

For an exhaustive list please see

Conditional Statements

Conditional expressions can be applied to a make file so that parts of make file execute or not based on certain conditions. Generic syntax is as follows






The following example of a conditional tells make to use one set of libraries if the CC variable is `gcc’, and a different set of libraries otherwise. It works by controlling which of two command lines will be used as the command for a rule. The result is that `CC=gcc’ as an argument to make changes not only which compiler is used but also which libraries are linked.

libs_for_gcc = -lgnu

normal_libs =

foo: $(objects)

ifeq ($(CC),gcc)

$(CC) -o foo $(objects) $(libs_for_gcc)


$(CC) -o foo $(objects) $(normal_libs)


This conditional uses three directives: one ifeq, one else and one endif.

Last Example

In the last example I would like to show you how to create install, dist and uninstall targets that you can most probably use while distributing your applications.

Hello: hello.c

install : hello

install $< $(HOME)

.PHONY : dist unistall

dist :

$(RM) hello *.o core

tar czvf hello.tar.gz hello.c Makefile


$(RM) $(HOME)/hello

and you can use make install, make dist & make uninstall commands for respective functions.

With this I think we should wrap up our tutorial on make. This is not a fully exhaustive tutorial but it would place you on the right grounds & will get you started with make hopefully. If you have any questions please don’t hesitate to ask and If you want to know more about specific details & I don’t impress you much you can always access the online documentation at

3 thoughts on “Philosophy of Make

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s