Introduction to the make utility



Overview

make is a standard Unix program, one you will find on any flavor of Unix. It helps you to save time by automating repetitive tasks involving files that are created from other files — for example, in building executable programs from source (C, C++, Fortran, etc.) files.

John Kerl
June, 1998

Compiling a C program without make

Suppose you had a C program to add two numbers. Also suppose that the function that did the addition was in one C source file, and the function that called the adder was in another C source file.

You might have:

/*--------------------------------------------------------------*/
/* sum.h */
int sum(int x, int y);

/*--------------------------------------------------------------*/
/* main.c */
#include <stdio.h>
#include "sum.h"

int main(void)
{
   int a = 3;
   int b = 4;
   int c;

   c = sum(a, b);

   printf("Sum of %d and %d is %d.\n", a, b, c);
}

/*--------------------------------------------------------------*/
/* sum.c */
#include <stdio.h>
#include "sum.h"

int sum(int x, int y)
{
   int result;

   result = x + y;

   return result;
}

To build this program, you might do:

cc sum.c main.c -o my_sum

Or, to compile and link separately:

cc -c sum.c
cc -c main.c
cc sum.o main.o -o my_sum

Each time you made a change to sum.h, main.c or sum.c, you would re-type these commands.

Maintaining text files without make

Suppose you had a half dozen HTML files, each with the same footer, e.g.:

Suppose those files were called part_1.html, part_2.html, ..., part_6.html. Each time you made a change to the footer, you'd have to edit all six source files — which could get tedious after a while.

Alternatively, you could include the footer in a single file — say, footer.txt — and have everything but the footer in files called part_1.txt, part_2.txt, ..., part_6.txt.

To change the footer, you'd just edit footer.txt, then do:

cat part_1.txt footer.txt > part_1.html
cat part_2.txt footer.txt > part_2.html
...
cat part_6.txt footer.txt > part_6.html

How make can simplify your work

In the previous two examples, I mentioned the command you might use to build a multiple-source-file C program, or to maintain a set of text files with some text in common.

Let's look at the C program. I mentioned that to rebuild the C program, you might issue the following commands:

cc -c sum.c
cc -c main.c
cc sum.o main.o -o my_sum

There are a few things one could improve on:

  1. It gets tiresome to type out all those long commands — particularly when you do so repeatedly.

  2. If someone else is compiling the program, they might not want to have to know the compile commands — they might prefer to just type a single command, and have the program know how to build itself. This would shield the user from the complexity of compiling a large program.

  3. If you've already built a program once, and then you only change one source file, there's no reason to recompile all the source files. Rather, you should only have to rebuild the ones that are affected by the changes you just made.

The make program addresses these needs.

You can put your source files (sum.h, main.c and sum.c), along with a special makefile (which we'll discuss in the next page) in a directory, then simply type make each time you make changes to any of your source files.

Anyone else who wants to build your program can just copy those four files, and type make — they don't have to know the (potentially gory) details of what it takes to build the program.

If you've already built your program; then you edit, say, sum.c; and then you type make, main.c won't be re-compiled — because it doesn't need to be. This is called build avoidance — rebuilding only what needs to be built. This becomes important for programs with hundreds or thousands of source files.

How to write a Makefile

A makefile has a simple syntax. It looks as follows:
target: dependencies
	rules
	rules
	rules

As I mentioned earlier, make is designed for files that are derived from other files — for example, object (.o) files are derived from source files (e.g. .c files). Any file (a target) that is derived from another one (a dependency) is said to be derived from it, or to depend on it. The steps you perform to create one file from another are called rules.

For example, to build our C program, we did the following:

cc -c sum.c
cc -c main.c
cc sum.o main.o -o my_sum

The first statement, cc -c sum.c, creates sum.o. So, sum.o is a target. It depends on sum.c. The rule to create sum.o is cc -c sum.c.

The key feature of the make program is dependency checking. What make does with a makefile's statements is: If any of dependencies is newer than target, or if the target doesn't exist yet, then execute the rules. On the other hand, if the target file is newer than its dependencies (by looking at the files' timestamps), then nothing is done. This is how make implements build avoidance — by using timestamps.

For each dependency, make also checks to see if that dependency is in turn a target. In this way you can have a tree of dependencies.

If a target has no dependencies, then its rules are executed unconditionally.

A target that is newer than its dependencies is said to be up to date; otherwise, it is said to be out of date.

Note that sum.c and main.c both include sum.h. If any changes were made to that header file, then we'd want both source files to be re-compiled. So sum.h should be an additional dependency for sum.o and main.o.

(sum.c and main.c also include stdio.h. We could list stdio.h as yet another dependency for sum.o and main.o; or, we could assume that, being a static system file, it won't change. Most people tend to take the latter approach.)

So, we could create a file called, say, makefile, with the following contents:

my_sum: sum.o main.o
	cc sum.o main.o -o my_sum

sum.o: sum.c sum.h
	cc -c sum.c

main.o: main.c sum.h
	cc -c main.c

(Note: Each rule must be preceded by a tab — not spaces. make depends on this.)

This says to the make program the following:

  • If sum.o or main.o is newer than my_sum, or if my_sum doesn't exist, or if sum.o or main.o are out-of-date targets, then execute the command cc sum.o main.o -o my_sum.
  • If sum.c or sum.h is newer than sum.o, or if sum.o doesn't exist, or if sum.c or sum.h are out-of-date targets, then execute the command cc -c sum.c.
  • If main.c or sum.h is newer than main.o, or if main.o doesn't exist, or if main.c or sum.h are out-of-date targets, then execute the command cc -c main.c.

Now, after editing any one of our source files, we can just type make, to re-build the my_sum program.

Remember that make doesn't rebuild what doesn't need rebuilding, based on timestamps. If we edit just main.c, then type make, only the first and third rules will be executed:

  • The make program will check to see if sum.o or main.o is newer than my_sum; neither one is.
  • However, main.o and sum.o are each themselves listed as targets in the makefile.
  • So, make checks to see if sum.c or sum.h is newer than sum.o — neither one is, and neither sum.c nor sum.h are listed as targets in the makefile. So, sum.o is up to date.
  • Then, make checks to see if main.c or sum.h is newer than main.o — main.c is newer, so the command cc -c main.c is executed. Now main.o is up to date.
  • Now that sum.o and main.o are up to date, main.o is newer than my_sum. So the cc sum.o main.o -o my_sum is executed. The my_sum is up to date with respect to its dependencies, so make is done.

If you've already run make, and the program compiled and linked successfully, and then you type make again without having edited any of the files, then makewill do nothing at all — because everything is up to date.


Here is what a makefile for our HTML files might look like:

part_1.html: part_1.txt footer.txt
	cat part_1.txt footer.txt > part_1.html
part_2.html: part_2.txt footer.txt
	cat part_2.txt footer.txt > part_2.html
...
part_6.html: part_6.txt footer.txt
	cat part_6.txt footer.txt > part_6.html

I include this example to show you that make can be used for many other things than compiling programs. You can use it any time one file is derived from another — in this case, .html files are derived from .txt files by using the Unix cat command.

Additional information

The previous page told you the basics you need to know to build makefiles that work. This page will tell you some more information you need to know to create and use useful makefiles.

Naming makefiles

On the last page, you may have wondered, "How did make know to use my file called makefile? I didn't pass any arguments to make".

Makefiles can have any name — makefile, my_program.mk, xyzpdq. The .mk extension is a convention, but is not mandatory. To tell make which makefile to use, use make's -f argument:

    make -f (name of makefile, e.g. my_program.mk)

If you don't use -f, then make will look for a file in the current directory called makefile. If that doesn't exist, it will look for a file in the current directory called Makefile. If it can't find any of these, it will print an error message.

So, it's often convenient to just name your makefile makefile. This saves typing.

Default target

A makefile typically contains several statements. How does make know which one(s) to look at? If you want make to execute a specific target or targets, then type make (one or more targets). If no target is specified on the command line, make look at the first target in the makefile.

For example, using the makefile from the previous page, we could rebuild sum.o and main.o by typing make sum.o main.o. This wouldn't rebuild my_sum, even if it was out of date.

If you use the -f option, too, it must come first: e.g. make -f my_program.mk foo.o.

Non-file targets

A target need not be a file: It may be simply a label, with dependencies.

Examples:

Since, as mentioned above, the first target in the makefile is the one looked at if none is specified on the command line, often people will have a first target called all in their makefile that looks like:

all: my_program my_other_program

my_program: my_program.o
	cc my_program.o -o my_program

my_other_program: my_other_program.o
	cc my_other_program.o -o my_other_program

...

It is a convention to name this first target all, to illustrate what the default behavior of the makefile is.

Another example of a non-file target is the clean target. Again, the name clean is just a convention. This is a target with no dependencies listed — so its rules are executed unconditionally. People often use this as a handy way to clean up all the derived files created by their makefile. For example, the makefile from the previous page might be extended to look like this:

my_sum: sum.o main.o
	cc sum.o main.o -o my_sum

sum.o: sum.c sum.h
	cc -c sum.c

main.o: main.c sum.h
	cc -c main.c

clean:
	rm -rf sum.o main.o my_sum
Then, if you type make, or make my_sum, then the my_sum target is looked at. However, if you specifically type make clean, then the command rm -rf sum.o main.o my_sum is executed.

In our text-file example, a complete makefile might look like:

all: part_1.html part_2.html part_3.html \
	part_4.html part_5.html part_6.html

part_1.html: part_1.txt footer.txt
	cat part_1.txt footer.txt > part_1.html

part_2.html: part_2.txt footer.txt
	cat part_2.txt footer.txt > part_2.html

part_3.html: part_3.txt footer.txt
	cat part_3.txt footer.txt > part_3.html

part_4.html: part_4.txt footer.txt
	cat part_4.txt footer.txt > part_4.html

part_5.html: part_5.txt footer.txt
	cat part_5.txt footer.txt > part_5.html

part_6.html: part_6.txt footer.txt
	cat part_6.txt footer.txt > part_6.html

clean:
	rm -f part_1.html part_2.html part_3.html \
		part_4.html part_5.html part_6.html

Variables

You can use variables in your makefiles. This example creates and uses a variable called CC:

CC=gcc -g -Wall

sum.o: sum.c sum.h
	${CC} -c sum.c

main.o: main.c sum.h
	${CC} -c main.c

my_sum: sum.o main.o
	${CC} sum.o main.o -o my_sum

This is convenient if, say, you want to use a different compiler, or change some compiler arguments, etc., without having to edit every compile line in the makefile.

The format is VARIABLE=value. It is a convention, but not required, that variable names are all in uppercase. It is important that you use the curly braces when referring to a variable after it's defined. This is different from the Unix shell, so omitting the curly braces is a common mistake.

Subshells

This is simply a gotcha: Every rule statement is executed as a separate Unix shell command. For example, the following makefile probably will not produce the desired results:

my_output_file: some_file.txt
	cd /tmp
	touch temporary_file.txt
	...

The first rule line will be a shell that cd's to /tmp and exits; the second one will be a brand-new shell which creates temporary_file.txt — in the current directory, not in /tmp. To create /tmp/temporary_file.txt, you might do the following:

my_output_file: some_file.txt
	cd /tmp; touch temporary_file.txt
	...

Comments

Comments are preceded by a # character, just as in shell scripts. For example:

# Here is how I build foo.o
foo.o: foo.c
	cc -c -g foo.c

# Here is how I build bar.o
bar.o: bar.c
	cc -c -g bar.c

...

Breaking up long lines

You can extend a line by putting a backslash as the last character. For example:

# Here is how I build my program from a dozen object files
my_program: file_1.o file_2.o file_3.o file_4.o \
		file_5.o file_6.o file_7.o file_8.o \
		file_9.o file_10.o file_11.o file_12.o
	cc file_1.o file_2.o file_3.o file_4.o \
		file_5.o file_6.o file_7.o file_8.o \
		file_9.o file_10.o file_11.o file_12.o \
		-o my_program

...

Miscellaneous tips

Here is some more information that you really don't need, except to save time. Makefiles will work fine without using any of the information on this page.

Suppressing echo of shell commands

Consider the following makefile:

my_program: my_program.c
	echo Starting to compile my_program ...
	cc my_program.c -o my_program
	echo done.

Running this makefile produces the following:

echo Starting to compile my_program ...
Starting to compile my_program ...
cc my_program.c -o my_program
echo done.
done.

The make program, by default, announces commands as it executes them. However, you can suppress this by prepending an @ character to a command. For example, if you change the above makefile to the following:

my_program: my_program.c
	@echo Starting to compile my_program ...
	cc my_program.c -o my_program
	@echo done.

Then you will get the following, more pleasant output:

Starting to compile my_program ...
cc my_program.c -o my_program
done.

Rewrite rules

In a large makefile, you may have something like the following:

file_1.o: file_1.c
	cc -g -c file_1.c

file_2.o: file_2.c
	cc -g -c file_2.c

file_3.o: file_3.c
	cc -g -c file_3.c

file_4.o: file_4.c
	cc -g -c file_4.c

...

You're doing the same thing for every file, so it seems tedious to have to write that down for each file. You might rather have a rule that says, for any file with a .c extension, to create a file with a .o extension, run the cc -c command with that file name as an argument. The way to do this is:

.c.o:
	cc -c -g $<