A Quick Look at Makefiles

You can imagine how tedious the build process could be if you had a large number of source code files for a particular project. Manually entering individual compiler and linker commands on the command line becomes tiresome very quickly. In order to avoid this, a makefile can be used. A makefile is a script that tells the make utility how to build a particular program. (The make utility is typically installed with the other GNU tools.) The make utility follows the rules in the makefile in order to automatically generate output files from a set of input source files.

Makefiles might be a bit of a pain to set up, but they can be a great timesaver and a very powerful tool when building project files over and over (and over) again. Having a sample available can reduce the pain of setting up a makefile.

The basic layout for a makefile build rule is:

target:   prerequisite
          command

The target is what is going to be built, the prerequisite is a file that must exist before the target can be created, and the command is a shell command used to create the target. There can be multiple prerequisites on the target line (separated by white space) and/or multiple command lines. But be sure to put a tab, not spaces, at the beginning of every line containing a command.

Here’s a makefile for building our Blinking LED program:

XCC     = arm-elf-gcc
LD      = arm-elf-ld
CFLAGS  = -g -c -Wall \\
          -I../include
LDFLAGS = -Map blink.map -T viperlite.ld -N

all: blink.exe

led.o: led.c led.h
    $(XCC) $(CFLAGS) led.c

blink.o: blink.c led.h
    $(XCC) $(CFLAGS) blink.c

blink.exe: blink.o led.o viperlite.ld
    $(LD) $(LDFLAGS) -o $@ led.o blink.o

clean:
    -rm -f blink.exe *.o blink.map

The first four statements in this makefile contain variables for use in the makefile. The variable names are on the left side of the equal sign. In this makefile, the respective variables do the following:

XCC

Defines the compiler executable program

LD

Defines the linker executable program

CFLAGS

Defines the flags for the compiler

LDFLAGS

Defines the flags for the linker

Variables in a makefile are used to eliminate some of the duplication of text as well as to ease portability. In order to use a variable in the code, the syntax $() is used with the variable name enclosed in the parentheses.

Note that if a line in a makefile gets too long, you can continue it on the following line by using the backslash (\\), as shown with the CFLAGS variable.

Now for the build rules. The build targets in this file are all, led.o, blink.o, and blink.exe. Unless you specify a target when invoking the make utility, it searches for the first target (in this case, the first target is all) and tries to build it; this, in turn, can lead to it finding and building other targets. The make utility creates (or re-creates, as the case may be) the target file if it does not exist or if the prerequisite files are more recent than the target file.

At this point, it might help to look at the makefile from the bottom up. In order for blink.exe to be created, blink.o and led.o need to be built as shown in the prerequisites. However, since these files don’t exist, the make utility will need to create them first. It will search for ways to create these two files and will find them listed as targets in the makefile. The make utility can create these files because the prerequisites (the source files) for these two targets exist.

Because the targets led.o and blink.o are handled similarly, let’s focus on just one of them. The prerequisites for the target led.o are led.c and led.h. As stated above, the command tells the make utility how to create the target. The first part of the command for led.o is a reference to the variable XCC, as indicated by the syntax $(XCC), and the next part of the command is a reference to the variable CFLAGS, as indicated by the syntax $(CFLAGS). The make utility simply replaces variable references with the text assigned to them in the makefile. The final part of the command is the source file led.c. Strung together, these elements construct the command that the make utility executes. This generates a command on the shell command line as follows:

arm-elf-gcc -g -c -Wall -I../include led.c

This is the same command we entered by hand in order to compile the led.c file earlier in this chapter, in the section “Building the Blinking LED Program.” The make utility compiles blink.c in the same way.

At this point, the make utility has all of the prerequisites needed to generate the target blink.exe default target. The command that the make utility executes (the same command we entered by hand to link and locate the Blinking LED program) to build blink.exe is:

arm-elf-ld -Map blink.map -T viperlite.ld -N -o blink.exe led.o blink.o

You may notice that in this makefile the linker is invoked directly. Instead, gcc could have been used to invoke the linker indirectly with the following line:

arm-elf-gcc -Wl,-Map,blink.map -T viperlite.ld -N -o blink.exe led.o blink.o

When invoking the linker indirectly, the special option –Wl is used so that gcc passes the request to generate a linker map file to the linker rather than trying to parse the argument itself. While this simple Blinking LED program does not need to link using gcc, you should remember that more complex C programs may need special runtime library support from gcc and will need to be linked in this way.

The last part of the makefile is the target clean. However, because it was not needed for the default target, the command was not executed.

To execute the makefile’s build instructions, simply change to the directory that contains the makefile and enter the command:

# make

The make utility will search the current directory for a file named makefile. If your makefile has a different name, you can specify that on the command line following the -f option.

With the previous command, the make utility will make the first target it finds. You can also specify targets on the command line for the make utility. For example, because all is the default target in the preceding makefile, you can just as easily use the following command:

# make all

A target called clean is typically included in a makefile, with commands for removing old object files and executables, in order to allow you to create a fresh build. The command line for executing the clean target is:

# make clean

Keep in mind that we’ve presented a very basic example of the make utility and makefiles for a very basic project. The make utility contains very powerful tools within its advanced features that can benefit you when executing large and more complex projects.

Tip

It is important to keep the makefile updated as your project changes. Remember to incorporate new source files and keep your prerequisites up to date. If prerequisites are not set up properly, you might change a particular source file, but that source file will not get incorporated into the build. This situation can leave you scratching your head.

Additional information about the GNU make utility can be found online at http://www.gnu.org as well as in the book Managing Projects with GNU make, by Robert Mecklenburg (O’Reilly). These resources will give you a deeper understanding of both the make utility and makefiles and allow you to use their more powerful features.

Get Programming Embedded Systems, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.