Appendix B
Using GNU make to Build Programs

This discussion covers the fundamental concepts employed in a Makefile. My intent is to show you how to write Makefiles that help you debug your programs. The problem with many discussions of make is that they show how to use many of the “features” of the make program. Many problems students have when debugging their programs are actually caused by errors in the Makefile that cause make to use its default behavior.

For example, if you try to compile the program myProg with the command:

     make myProg

make will look for its instructions in a file in the current directory named Makefile (or makefile). Assuming Makefile exists, make searches the file for a target (defined later in this chapter) named myProg. If there is no Makefile, make searches for a file named myProg.s, myProg.c or myProg.cc. If either of the .s or .c source files exists, make issues the command

     cc myProg.c -o myProg

and if the .cc source file exits,

     g++ myProg.cc -o myProg

Notice that the compiler is invoked with only the -o option. For example, you cannot use gdb to debug the program because the -g option does not get used. This means that if one of the entries in your Makefile is incorrect, the default behavior may cause make to compile a source file without the debugging option. It is much easier to avoid these problems if you:

A Makefile consists of a series of entries. Each entry in a Makefile consists of:

  1. One dependency line. The format of a dependency line is:
          target: [prerequisite1] [prerequisite2]...  
      

    Prerequisites are names of files or other targets in this Makefile. Use spaces as separators between the prerequisites, not tabs.

  2. Zero or more Unix command lines. The format of a command line is:
    1. Each line must begin with a tab character (not a group of spaces). Be careful if you write a Makefile with an editor on another platform; some editors automatically replace tabs with spaces.
    2. The remainder of the line is a Unix command.

There can also be (should be!) comment lines that begin with a #.

If you invoke make with no argument:

     make

make starts with the first entry in the Makefile.

Starting with the prerequisites of the first target (the first entry or the argument used in the command), make follows the hierarchical tree of target / prerequisite entries until it gets to the lowest level — either a file or no prerequisites. It works its way back up through this tree. If any entry along the tree has a target that is not at least as recent as all of its prerequisites, the commands for that entry are executed.

A common organization is to have an entry for the program name, with each of the object files that make up the program as prerequisites. The command(s) in this entry link the object files together.

Then there is an entry for each object file, with its source file and any required local header files as prerequisites. The command(s) in each of these entries compile/assemble the source file and produce the corresponding object file.

It is also common to have various utility targets in a Makefile.

These concepts are illustrated in Listing B.1. Notice that there are no prerequisites for the clean target. Thus the command

     make clean

causes make to start with the clean entry. Since there are no prerequisites, the tree ends here, and the target is always “out of date.” Hence, the command for this entry will always be executed, but none of the other entries is executed.

IMPORTANT: When using the make program, it will echo each command as it is executed and provide diagnostic error messages on the screen. You must read this display very carefully in order to determine what has taken place.
1# makefile to create myProg 
2 
3# link object file to system libraries to create executable 
4myProg: myProg.o 
5      gcc -o myProg myProg.o 
6 
7# assemble source file to create object file 
8myProg.o: myProg.s 
9      as --gstabs myProg.s -o myProg.o 
10 
11# remove object files and backup files 
12clean: 
13      rm -i *.o *~
Listing B.1: An example of a Makefile for an assembly language program with one source file.

The functions in real programs are distributed amongst many files. When changes are made, it is clearly a waste of time to recompile all the functions. With a properly designed Makefile, make only recompiles files that have been changed. (This is a motivation for placing each function is its own file.) Listing B.2 illustrates a Makefile for a program where the main function and one subfunction are written in C and one subfunction is written in assembly language. Notice that header files have been created for both subfunctions to provide prototype statements for the main function, which is written in C. The assembly language source file does not #include its own header file because prototype statements do not apply to assembly language.

1# makefile to create biggerProg 
2 
3# link object files and system libraries to create executable 
4biggerProg: biggerProg.o sub1.o sub2.o 
5      gcc -o biggerProg biggerProg.o sub1.o sub2.o 
6 
7# compile/assemble source files to create object files 
8biggerProg.o: biggerProg.c sub1.h sub2.h 
9      gcc -g -c biggerProg.c 
10 
11sub1.o: sub1.c sub1.h 
12      gcc -g -c sub1.c 
13 
14sub2.o: sub2.s 
15      as --gstabs sub2.s -o sub2.o 
16 
17# remove object files and backup files 
18clean: 
19      rm -i *.o *~
Listing B.2: An example of a Makefile for a program with both C and assembly language source files.

As you can see in Listing B.2, there is quite a bit of repetition in a Makefile. Variables provide a good way to reduce the chance of typing errors. Listing B.3 illustrates the use of variables to simplify the Makefile from Listing B.2.

 
1# Makefile for biggerProg 
2# Bob Plantz - 19 June 1009 
3 
4# Specify the compiler and assembler options. 
5compflags = -g -c -O1 -Wall 
6asmflags = --gstabs 
7 
8# The object files are specific to this program. 
9objects = biggerProg.o sub1.o sub2.o 
10 
11biggerProg: $(objects) 
12        gcc -o biggerProg $(objects) 
13 
14biggerProg.o: biggerProg.c sub1.h sub2.h 
15        gcc $(compflags) -o biggerProg.o biggerProg.c 
16 
17sub1.o: sub1.c sub1.h 
18        gcc $(compflags) -o sub1.o sub1.c 
19 
20sub2.o: sub2.s 
21        as $(asmflags) -o sub2.o sub2.s 
22 
23clean: 
24        rm $(objects) biggerProg *~
Listing B.3: Makefile variables. Another version of Figure B.2

Executing make with the Makefile in Listing B.3 shows that the two C source files are compiled, and the assembly source file is assembled with the proper flags:

bob$ make
gcc -g -c -O1 -Wall -o biggerProg.o biggerProg.c
gcc -g -c -O1 -Wall -o sub1.o sub1.c
as --gstabs -o sub2.o sub2.s
gcc -o biggerProg biggerProg.o sub1.o sub2.o
bob$
make shows each command in your terminal window as it executes it. Reading them is the best way to ensure that your Makefile is written correctly. This display also shows how make starts with the commands at the leaves of the tree and works its way back up to the top of the tree.

We can use gdb to follow execution of the program:

bob$ gdb biggerProg
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/bob/biggerProg/biggerProg...done.
(gdb) li
2  * Demonstrate simple Makefile design
3  * Bob Plantz - 19 June 2009
4  */
5
6 #include <stdio.h>
7 #include "sub1.h"
8 #include "sub2.h"
9
10 int main()
11
(gdb)
12     printf("Starting in main, about to call sub1...\ n");
13     sub1();
14     printf("Back in main, about to call sub2...\ n");
15     sub2();
16     printf("Back in main.\ nProgram ending.\ n");
17
18     return 0;
19
(gdb) run
Starting program: /home/bob/biggerProg/biggerProg
Starting in main, about to call sub1...
In sub1
Back in main, about to call sub2...
In sub2
Back in main.
Program ending.
[Inferior 1 (process 4489) exited normally]
(gdb) q
bob$

However, if we do not write a complete Makefile (see Listing B.4)

 
1# Makefile for biggerProg 
2# Bob Plantz - 19 June 2009 
3# WARNING! THIS IS A BAD MAKEFILE!! 
4 
5# Specify the compiler and assembler options. 
6compflags = -g -c 
7asmflags = --gstabs 
8 
9# The object files are specific to this program. 
10objects = biggerProg.o sub1.o sub2.o 
11 
12biggerProg: $(objects) 
13        gcc -o biggerProg $(objects) 
14 
15clean: 
16        rm $(objects) biggerProg *~
Listing B.4: Incomplete Makefile. Several entries are missing, so make invokes its default behavior.

executing make shows that the two C source files are compiled, and the assembly source file is assembled using the make program’s own default behavior to give:

bob$ make
cc    -c -o biggerProg.o biggerProg.c
cc    -c -o sub1.o sub1.c
as   -o sub2.o sub2.s
gcc -o biggerProg biggerProg.o sub1.o sub2.o
bob$
Note that make does not give any error messages even though our Makefile is incomplete. It appears to have created the program correctly. However, when we try to use gdb we see:

bob$ gdb biggerProg
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/bob/biggerProg/biggerProg...(no debugging symbols found)...done.
(gdb) li
No symbol table is loaded.  Use the "file" command.
(gdb) run
Starting program: /home/bob/biggerProg/biggerProg
Starting in main, about to call sub1...
In sub1
Back in main, about to call sub2...
In sub2
Back in main.
Program ending.
[Inferior 1 (process 4541) exited normally]
(gdb) q
bob$

Reading the make messages on the screen shows that it created the program without using all our flags. The important lesson to note here is that an error-free execution of make is not sufficient to guarantee your program was built as you intended. You need to read the screen messages written on the screen when using make.

To learn more about using make see [30].