C Programming

Class 4


Objective

c4.tar


Why do we need a makefile?

When we are writting small programs, we can simply rebuild the application after edits by recompiling all the files, just like what we did before. But with larger programs, the time for the edit-compile-test cycle will grow. Make command will save much time for us, especially when only a small section of the program is modified with the rest remains unchanged. Also, the make command will make the management of large programs or gruops programs much easier.
Suppose we have a situation shown below:

/* main.c */
#include "a.h"
...

/* 2.c */
#include "a.h"
#include "b.h"
...

/* 3.c */
#include "b.h"
#include "c.h"
...

If the programmer changes c.h, the files main.c and 2.c don't need to be recompiled, since they don't depend on this header file. The file 3.c does depend on c.h and should therefore be recompiled if c.h is changed. However, if b.h was changed and the programmer forgot to recompile 2.c, then the resulting program might no longer function correctly.
The make utility can solve both of these problems by ensuring that the files affected by changes are recompiled when necessary.

NOTE: The make command is not used only to compile programs.

How to write a makefile?

Before we use a makefile, we should provide a file that tells the make what to do.This file is makefile who usually has the name file "makefile" or "Makefile"(otherwise, "-f filename" should be added after make command to locate the makefile), and most often resides in the same directory as the other source files for the project. A makefile consists of a set of dependencies and rules. A dependency has a target (a file to be created) and a set of source files upon which it is dependent. The rules describe how to create the target from the dependent files. Commonly, the target is a single executable file.

Comments:A comment in a makefile starts with # and continues to the end of the line.
Dependencies: The dependencies specify how each file in the final application relates to the source files. example:
myapp: main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h

rules: rules are used to describe how to create a target. example:
gcc -c 2.c

NOTE: All rules must be on lines that start with a tab; a space or several spaces won't do, even though eight spaces look much the same as a tab. Also a space at the end of a line in the makefile may cause a make command fail.
A simple makefile--Makefile1:
myapp: main.o 2.o 3.o
     gcc -o myapp main.o 2.o 3.o
main.o: main.c a.h
     gcc -c main.c
2.o: 2.c a.h b.h
     gcc -c 2.c
3.o: 3.c b.h c.h
     gcc -c 3.c

if we try this makefile in a directory without source code
$ make -f Makefile1
make: *** No rule to make target 'main.c', needed by 'main.o'. Stop.

if we create all the three header files, even they are all actually empty.
$ touch a.h
$ touch b.h
$ touch c.h

try the make command again now to see the diffrence to the last make.
$ touch b.h
make again to see the diffrence to the last make.
$rm 2.o
make again to see the diffrence to the last make.

Makefiles allow us to use macros, so we can write them in a more genereal form.

We define a macro in a makefile by writing MACRONAME=value, then accessing the value of MACRONAME by writing either $(MACRONAME) or ${MACRONAME}. Some versions of make may also accept $MACRONAME. We can set a name to blank be leaving the rest of the line after the "=" blank.

Macors are normally defined inside the makefile itself, but they can be specified by calling make with the macro definition, for example, make CC=gcc. Command line definitions override difines in the makefile.

example of makefile with macros: Makefile2

if we delete our old installation and create a new one with this new makefile, we get:
$ rm *.o myapp
$ make -f Makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
$

The make command replaces the $(CC), $(CFLAGS) and $(INCLUDE) with the appropriate definition, rather like the C compiler does with #define. Now if we want to change the compile command, we only need to change a single line of the makefile.
the following is a list of several commonly used internal macros:

Macro discription
$? List of prerequisites changed more recently than the current target.
$@ Name of the current target.
$< Name of the current prerequisite.
$* Name of the current prerequisite, without any suffix.
There are two other useful special characters you may see in makefile, preceding the command.

- tells make to ignore any errors. For example if you wanted to make a directory, but wished to ignore any errors, perhaps because the directory might already exist, you just precede mkdir with a minus sign. We will see '-' in use shortly.

@ tells make not to print the command to standard output before executing it. This is handy if you perhaps what to use echo to display some instructions.

example: Makefile3
There are several things to notice in this makefile. Firstly, the special target all still only specifies myapp as a target. Thus when we execute make without specifying a target, the default behavior is to build the target myapp. If it was very important that subsequent commands only executed if the previous one had succeeded, then we could have written the commands joined by &&, like this:

@if [ -d $(INSTDIR) ]; \
     then \
    cp myapp $(INSTDIR) &&\
    chmod a+x $(INSTDIR)/myapp && \
    chmod og-w $(INSTDIR/myapp && \
    echo "Installed in $(INSTDIR)" ;\
else \
    echo "Sorry, $(INSTDIR) does not exist" ; false ; \
fi

The next important point is the two additional targets, clean and install. The clean target uses the rm command to remove the objects. The command starts with ? which tells make to ignore the result of the command, so make clean will succeed even if there are no objects and the rm command returns an error. The rules for making the target 'clean' don't specify clean as depending on anything; the rest of the line after clean: is blank. Thus the target is always considered out of date and its rule is always executed if clean is specified as a target.

The install target is dependent on myapp, so make knows that it must create myapp before carrying out other commands for making install. The rules for making install consist of some shell script commands. Since make invokes a shell for executing rules and uses a new shell for each rule, we must add backslashes so that all the script commands are on one logical line and are all passed together to a single invocation of the shell for execution. This command starts with an @ sign, which tells make not to print the command on standard output before executing the rule.

You may not have permission as an 'ordinary' user to install new commands in /usr/local/bin. You can either change permissions on this directory or change user (with the su command) to root before invoking make install.
So far, we've specified in the makefile exactly how to perform each step of the process. In fact, make has a large number of built?in rules which can significantly simplify makefiles, particularly if we have many source files. Let's create foo.c, a traditional 'Hello World' program: foo.c
run the make commmand:
$ make foo
and look what is printed in the screen.
As you can see, make knows how to invoke the compiler. Sometimes, these built-in rules are referred to as inference rules. The default rules use macros, so by specifying some new values for the macros we can change the default behavior.

$ rm foo/
$ make CC = gcc CFLAGS = "-Wall -g" foo
gcc -Wall -g foo.c -o foo
$

the following is a list of commonly used built-in rules:
Built-in Rule discription
CC Program for compiling C programs.
CXX Program for compiling C++ programs.
AR Archive-maintaining program.
CFLAGS Extra flags to give to the C compiler.
CXXFLAGS Extra flags to give to the C++ compiler.
SHELL The current shell you are using.
INCLUDE The directory where the include files kept.
INSTDIR The directory where to install a package.
You can ask make to print its built-in rules with the -p option:
$ make -p
We can now simplify our makefile to take account of these built-in rules by taking out the rules for making objects and just specifying the dependencies: Makefile4

More about Make in Manual of GNU Make



Note: These web pages were written with the reference book Beginning Linux Programming,2nd Edition by Neil Matthew and Richard Stones.