In the this lecture, we will discuss the GNU make command for building software systems. In Lab4 you hand compiled multiple source files into an executable using the “mygcc” command. We will not do this anymore - no more issuing mygcc commands from the command line. The make command will do the heavy lifting of working out what gets compiled for a program. The GNU make command automatically works out which parts of a program with multiple source files need to be recompiled or not; then, it issues a set of commands to do the job for you. We will present a number of Makefile examples from simple to more sophisticated. You will need to write a simple Makefile for the crawler lab.
We plan to learn the following from today’s lecture:
Recall the compilation process we discussed in an earlier class. Source files such as crawler.c, list.c and html.c are compiled into object files such as crawler.o, list.o and html.o, respectively. Each object file contains a compiled system dependent representation of the source file. The source files include the header files as part of the preprocessor phase of the compilation. The linker links the object files and any needed libraries and produces an executable. The loader loads the program into memory ready to run.
Include figure here.
Up until now we have been typing out the gcc command lines to build and executable:
gcc -Wall -pedantic -std=c99 -o crawler crawler.c list.c html.c
or more specifically, with our alias mygccc:
mygcc -o crawler crawler.c list.c html.c
From now on in the course we will use a better method for building systems. We will use the GNU make software which automates the building of software systems. You should view the manual pages for the make utility. It acts as simple dependency tracking tool to maintain groups of programs. A Makefile (which is a plain text file that you write that the make utility interprets) follows a set of rules that capture the various actions that must be performed on individual source files in order to build a sub-system, such as, crawler. These rules are captured in the Makefile that is typically found in the source file directory. You can take a look at the Makefile we returned with lab4 for the prs program.
Before we describe the make tool and give some examples of Makefiles lets talk about the reasons for moving to a more formal tool for compiling and linking our source code.
First, it is tedious to keep typing out a list of files - maybe a large number of files. The make utility automates the compilation and linking process. With make you specify the files and how they fit together and make takes care of the rest. A nice part of the make utility is that it also takes care to determine if any files (*.c and *.h) have changes since the last build (where build here means the new executable created by make) and if so it takes care to recompile those files. An important aspect of the make utility is that it allows the programmer to specify any dependencies between files by writing rules in the Makefile; for example, if all the *.c files are dependent on crawler.h then all the files will be recompiled if the header file is changed. If, however, only one file is changed (e.g., list.c) then make will only recompile that file and then link it with the existing set object files (e.g., html.o). This is both efficient and can save a significant amount of time. The lecturer has worked on many projects were the build (running the make file) for larger project took 6 hours. I’d guess that you are recompiling crawler every few minutes right now because of fixes you are making. Clearly, a good goal of a large project is to only recompile the necessary files and not those files that have not changed or have no dependencies on files that have changed. That way you can also reduce the build time which makes good sense if you want to try out your new fix.
The Makefile below includes a number of variable definitions and dependency rules. Variables (where you see “ = ”) can be used to define a list of directories to search, set of files, the compiler to use, etc. Make rules can be very simple as in the case of the Makefile below. The make utility supports defined variables, conditionals and a number of very sophisticated features (which we will not cover in this course). The simple make file below is used for building the prs program for Lab3, as mentioned above.
The most important variables are:
CC - which, defines the name of the C compiler to be used and defaults to cc or gcc. In this case gcc is
CFLAGS - which, defines a set of options passed to the compiler for all source file compilation. In our case
we specify the same options we have used throughout the course “-Wall -pedantic -std=c99”. We can also
specify the include path to include standard directories (-I) or debugging (-g -ggdb) if we wish.
LDFLAGS - which, defines a set of options to pass to the linker. In the Makefile below we do not use the
LDFLAGS variable but this can be used to include application specific libraries (-l) and to set the library
search path (-L).
Other variables of note are:
EXEC - which, defines the name of the executable file.
SRCS - which, defines the list of source files.
OBJS - which, defines the list of object files.
Note, that if you cut and paste the Makefile from these lecture notes into an editor you will likely get format problems and make will not execute correctly. Better to save this file as one example and edit it. Again, the make command is very picky when it comes to formatting issues such as tabs, as discussed above.
“The syntax used by Make gives tab, a whitespace character, a different meaning from the space character. This is problematic, since there is usually no visual difference between a tab and a number of space characters. Thus, the syntax of make is often subject to criticism.”
In the example Makefile below commands start with a tab and not spaces. All other formatting uses single spaces.
The Makefile default action when simply typing make builds the prs executable. If the programmer types make debug then make will build the system with the “-g -ggdb” option which allows the GNU debugger (gdb) to be run on the executable.
The example Makefile above defines the set of rules and dependencies to follow in order to build the executable prs. To make the EXEC you need the OBJS. To get the OBJS you need the SRC. So the make follows the rules of starting with the compilation of the SRCS files:
tab$(CC) $(CFLAGS) -c $(SRCS)
Note that the tab (no spaces) is used in the Makefile. The command lines MUST be indented by a tab character. Using spaces will not work even if they look right in the editor or when you more or less the Makefile. Make sure that you have tabs and not spaces. If you go to the start of the line that has a tab you should be able to skip 8 characters by trying to move one. Beware of cuting and pasting sometimes spaces are substituted.
Also, not that the -c flag is added to the compilation line. This tells the compiler to only produce object files (file.o) and not to run the linker.
Similar rules are defined for the linking phase:
tab$(CC) $(CFLAGS) -o $(EXEC) $(OBJS)
Now the OBJS are created make can finish its work and produce the executable.
In the following example of the Makefile for the same program the dependencies of the files is explicitly states.
main.o: main.c game.h
tells the make utility that main.o is dependent on main.c and game.h. So if either file is changed (time stamps are different from the previous versions) then the compiler will create a new main.o
Try runing the Makefile and following the command sequence below. Because the Makefile now defines the dependencies the make utility is efficient and compiles only those files that have changed taking into account their dependencies.
The final example of the Makefile writes the rules in a more generic manner.
The $< represents an internal macro and stand for the ”implicit” source.