make (1/4)

Introduction

This tutorial explains how to use make. There are several variants of make, in this tutorial we are focusing on GNU Make.

Basic Concepts

A first look at a makefile

We're going to give an overview of what a makefile consists of. We are going to glance over a few details for the sake of clarity so be aware that the description here is not entirely accurate.

A makefile consists of a set of rules that describe how to to build the thing that must be built. Each rule has the following format:

makefile rules syntax
target ... : prerequisites
             recipe
             ...

For instance here is an example of a makefile:

makefile example
hello: main.c
	gcc main.c -o hello

hello is the target, main.c is a prerequisite, and gcc main.c -o hello is the recipe. Note that this is by no means the recommended way of writing a makefile, but we're keeping it simple for now.

Typically makefiles need to be executed from the directory in which they are. So to execute the above makefile we would cd to the directory in which it is located and run make -f makefile hello. This will tell make to execute the file called makefile and try to build the target called hello. Actually you could omit all the arguments and just run make due to the defaults used by make, more on this later in this tutorial.

Let's have a look at the different parts of a rule:

  1. The target is the file the rule must build. In our example a file called hello. During execution make will check if the file exists and is up to date. If not it will execute the commands listed in the recipe. As you can see make doesn't need to know the details of what is in these files. In our case it's an executable but as far as make is concerned it could be anything. All make does is checking if the target file is up to date and executing the recipe if it isn't. It doesn't need to understand what the recipe does either. This makes it a very flexible tool.
  2. The prerequisites is the list of things the target depends on. As we said make will check if the target is up to date. It will do so by looking if the target file exists and if it is more recent than the prerequisites.
  3. The recipe is the list of commands that must be run if the target is not up to date.

The goals are the ultimate targets the makefile is meant to build. When invoking make you specify which target amongst all the rules in the makefile is to be the goal or let make use the defaut. So in the example above when we ran the command make -f makefile hello, hello was our goal. The first target in the makefile is called the default goal and is the one that will be built if make is invoked without arguments. Therefore you need to pay attention to what the first rule in your makefile is, except for that the order of the rules is irrelevant. Note that there are a few exceptions to the rule that says the first target will be the default goal, we will discuss them later.

In the next sections we'll look at how this works with a few examples.

A simple "Hello World!" makefile

Let's start with a very simple example to familiarize ourselves with make. We want to produce an executable called "hello" from a single source file called main.c the contents of which are reproduced below.

File: main.c
#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    return 0;
}

To achieve this we write the very simple makefile below. We assume our makefile is a file called "makefile".

File: makefile
hello: main.c
	gcc main.c -o hello

From what we have learnt in the previous section we know that the default goal will be the hello target, that the file main.c is the only prerequisite needed to build the hello file and that gcc main.c -o hello is the command to run to create hello if it doesn't exist or is not up to date.

Let's look at a few executions of this makefile to understand how it works. Our example is in a directory called HelloWorld1 and contains 2 files. As you can see we are executing this in a terminal on Ubuntu.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ ls
main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ make -f makefile hello
gcc main.c -o hello
xleclercq@ubuntu:~/HelloWorld1$ ls
hello  main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ ./hello
Hello World!
xleclercq@ubuntu:~/HelloWorld1$

As you can see we ran make passing in the name of our makefile and specifying hello as the goal. make then looked at the hello rule and checked whether the prerequisites main.c was present and up to date. Since it was it went on to check whether the file hello was present. Since it wasn't it invoked the recipe gcc main.c -o hello, which you can see outputted on the terminal. We verify successful execution by running our newly created executable.

Let's now look at another way to execute make which is actually the more usual way. We start by deleting the hello file to reset our environment.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ rm hello
xleclercq@ubuntu:~/HelloWorld1$ make
gcc main.c -o hello
xleclercq@ubuntu:~/HelloWorld1$ ls
hello  main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ ./hello
Hello World!
xleclercq@ubuntu:~/HelloWorld1$

There is actually no difference except we relied on make's defaults. Since hello is the first target it is the default goal so we don't need to specify it on the command line. Also by default make will look for a file called "Makefile" or "makefile" (GNU Make also looks for a file called GNUMakefile) if a makefile isn't specified on the command line.

Let's now look at what happens if we run make again, this time without deleting the hello file.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ make
make: `hello' is up to date.
xleclercq@ubuntu:~/HelloWorld1$

This time make found the hello file and found it was more recent than its prerequisite main.c so it didn't execute the recipe.

But look what happens when we touch the main.c file.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ touch main.c
xleclercq@ubuntu:~/HelloWorld1$ make
gcc main.c -o hello
xleclercq@ubuntu:~/HelloWorld1$

Although the hello file is present make finds it is less recent than main.c and so it executes the recipe.

As a last test we will know see what happens if we delete the prerequisite main.c.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ rm main.c
xleclercq@ubuntu:~/HelloWorld1$ make
make: *** No rule to make target `main.c', needed by `hello'.  Stop.
xleclercq@ubuntu:~/HelloWorld1$ 

As you can see even though the file hello is present make complains that the prerequisite main.c is not present and returns an error. However the error message doesn't exactly say that. When it couldn't find main.c, make looked for a rule to create it but it fails because there is no such rule and therefore it can't create the missing prerequisite. This is what the error messages says.

Now as another test let us consider the following makefile. It is identical to the one we have used so far except the prerequisite part of the rule is empty. Note that logically the target still depends on main.c but the makefile is not aware of that fact.

File: makefile
hello:
	gcc main.c -o hello

Let's now test what happens when we run make on that file.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ make
gcc main.c -o hello
xleclercq@ubuntu:~/HelloWorld1$ ls
hello  main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ touch main.c
xleclercq@ubuntu:~/HelloWorld1$ make
make: `hello' is up to date.
xleclercq@ubuntu:~/HelloWorld1$

Even though we touched main.c, make still thinks the target is up to date because there are no prerequisites listed. So if there are no prerequisites the recipe will never be executed unless the target file doesn't exist at all.

Phony targets

So far we have seen how a target was the file that needed to be built by the recipe. However it is possible to write a rule in such a way that the target becomes just an action that must be performed rather than a file to produce. First let us consider the way we can abuse the rule evaluation mechanism to get this behaviour. Afterwards we'll show the better way to do it.

Because make doesn't actually know the details of what the recipe does, there is no guarantee the recipe actully produces the target file. So if we want a recipe to be executed every time the makefile is executed we can just write a rule like this:

File: makefile
echoaction:
	echo "Hello from the makefile"

And if we run this we will see the following:

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ make
echo "Hello from the makefile"
Hello from the makefile
xleclercq@ubuntu:~/HelloWorld1$ make
echo "Hello from the makefile"
Hello from the makefile
xleclercq@ubuntu:~/HelloWorld1$ 

During each invocation, make fails to find the taget and so excutes the recipe every time. So the target really becomes an action to perform rather than a file to produce.

The above solution is obviously misusing the way a rule is supposed to work and if for some reason the echoaction file was found the makefile would break as shown in the next execution example.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ touch echoaction
xleclercq@ubuntu:~/HelloWorld1$ ls
echoaction  makefile
xleclercq@ubuntu:~/HelloWorld1$ make
make: `echoaction' is up to date.
xleclercq@ubuntu:~/HelloWorld1$

Since there are no prerequisites and the echoaction file now exists the rule will never be executed. Of course echoaction is a fairly obscure name but you can imagine some makefiles have rules with more common names. And anyway in general abusing mechanisms produces obscure bugs when something doesn't go as expected. Fortunately make provides a dedicated mechanism for actions we want to be executed all the time regardless of the presence or freshness of a file. This mechanism is called phony targets. Let us rewrite the makefile to make use of the phony target functionality.

File: makefile
.PHONY: echoaction

echoaction:
	echo "Hello from the makefile"

For any of the targets listed in the .PHONY list, make will not check whether the target exists on the filesystem or its freshness. Not only does this make the makefile a bit more robust it also helps with performance since make won't be looking for files that should never exist anyway.

Here is what happens now when we execute this makefile.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ ls
echoaction  makefile
xleclercq@ubuntu:~/HelloWorld1$ make
echo "Hello from the makefile"
Hello from the makefile
xleclercq@ubuntu:~/HelloWorld1$

It now works as desired even though there is a file called echoaction.

Targets as prerequisites

So far the prerequisites we have seen were a file present on the filesystem. Very often though the prerequisite will itself be produced by another rule. For instance look at a slightly modified version of our "Hello World!" example.

File: makefile
hello: main2.c
	gcc main2.c -o hello

main2.c: main.c
    cp main.c main2.c

We'll present a more useful example later but for now we'll use this example where for no good reason we want to use main2.c as the input for gcc rather than main.c. main2.c is just produced from main.c using cp and this is represented by the main2.c rule we added to the makefile.

As we briefly hinted at when we discussed the error message produced when make couldn't find the prerequisite earlier on this page, make actually evaluates rules recursively. When looking for a prerequisite it actually doesn't look for the file on the filesystem immediately, it first will try to find a rule that has the prerequisite as a target. If it doesn't find one it will simply look for the file on the filesystem. But if it does find a rule, it will process that rule as any other rule: it will check if the file exists and if it is up to date, if it isn't it will execute the recipe for that rule. Obviously this is if the target is not a phony target. If it finds a phony target then it will always execute the recipe as this is how phony targets work normally. Let us look at a few examples of execution of this makefile.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ ls
main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ make
cp main.c main2.c
gcc main2.c -o hello
xleclercq@ubuntu:~/HelloWorld1$ ls
hello  main2.c  main.c  makefile
xleclercq@ubuntu:~/HelloWorld1$ ./hello
Hello World!
xleclercq@ubuntu:~/HelloWorld1$

You can see that make first ran the recipe to create the main2.c target and then ran the recipe to create the hello file.

Let's see what happens when we update the timestamp of main2.c which is now present.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ touch main2.c
xleclercq@ubuntu:~/HelloWorld1$ make
gcc main2.c -o hello
xleclercq@ubuntu:~/HelloWorld1$

As main2.c is more recent than main.c the recipe for the main2.c target is not executed. However make detects that hello is out of date and rebuilds it.

Let's see what happens if we update the timestamp on main.c.

Executing the makefile
xleclercq@ubuntu:~/HelloWorld1$ touch main.c
xleclercq@ubuntu:~/HelloWorld1$ make
cp main.c main2.c
gcc main2.c -o hello
xleclercq@ubuntu:~/HelloWorld1$

As you could have guessed, make now rebuilds both main2.c and hello.


blog comments powered by Disqus

Copyright(c) 2006-2017 Xavier Leclercq | Privacy policy

Home
Contact Us
Search