The make Command and Makefiles:-
Although, as you will see, the make command has a lot of built-in knowledge, it can’t know how to build your application all by itself. You must provide a file that tells make how your application is constructed.This file is called the makefile.
The makefile most often resides in the same directory as the other source files for the project. You can have many different makefiles on your machine at any one time. Indeed, if you have a large project, you may choose to manage it using separate makefiles for different parts of the project. The combination of the make command and a makefile provides a very powerful tool for managing projects. It’s often used not only to control the compilation of source code, but also to prepare manual pages and to install the application into a target directory.
The Syntax of Makefiles:-
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. Typically, the target is a single executable file.
The makefile is read by the make command, which determines the target file or files that are to be made and then compares the dates and times of the source files to decide which rules need to be invoked to construct the target. Often, other intermediate targets have to be created before the final target can be made. The make command uses the makefile to determine the order in which the targets have to be made and the correct sequence of rules to invoke.
Options and Parameters to make:-
The make program itself has several options. The three most commonly used are
1) -k, which tells make to keep going when an error is found, rather than stopping as soon as the first problem is detected. You can use this, for example, to find out in one go which source files fail to compile.
2) -n , which tells make to print out what it would have done without actually doing it.
3) -f
Dependencies and Target:-
The dependencies specify how each file in the final application relates to the source files.
Target is usually a executable file which get created after execution of make command.
For Eg. Suppose a program contains below source files. main.c,2.c,3.c,a.h,b.h and c.h
In make file write these rules by writing the target,a colon, space or tab, then a tab or space separated list of files that are required to create a target file.
//The dependency list of the above program is as follows.
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 a.h b.h c.h
This says that target myapp is depends on main.o,2.o and 3.o (dependencies for myapp target). target main.o is depends on main.c and a.h (dependencies for main.o target) target 2.o is depends on 2.c,a.h and b.h (dependencies for 2.o target) target 3.o is depends on 3.c,a.h,b.h,and c.h (dependencies for 3.o target)
This set of dependencies gives a hierarchy showing how the source files relate to one other. You can see quite easily that, if b.h changes, you need to revise both 2.o and 3.o, and because2.o and 3.o will have changed, you also need to rebuild myapp.
Rules:-
The second part of the makefile specifies the rules that describe how to create a target. In the example in the previous section, what command should be used after the make command has determined that 2.o needs rebuilding? It may be that simply using gcc -c 2.c is sufficient.
Syntax of makefiles:-
The difference between a space and a tab. All rules must be on lines that start with a tab; a space won’t do. Because several spaces and a tab look much the same and because almost everywhere else in Linux programming there’s little distinction between spaces and tabs, this can cause problems. Also, a space at the end of a line in the makefile may cause a make command to fail.
A simple makefile (name 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
You invoke the make command with the -f option because your makefile doesn’t have either of the usual default names of makefile or Makefile . If you invoke this code in a directory containing no source code, you get this message:
$make -f Makefile1
make: *** No rule to make target ‘main.c’, needed by ‘main.o’. Stop.
$
The make command has assumed that the first target in the makefile, myapp , is the file that you want to create. It has then looked at the other dependencies and, in particular, has determined that a file called main.c is needed. Because you haven’t created this file yet and the makefile does not say how it might be created, make has reported an error. So now create the source files and try again. Because you’re not interested in the result, these files can be very simple. The header files are actually empty, so you can create them with
touch:
$touch a.h
$touch b.h
$touch c.h
main.c contains main , which calls function_two and function_three . The other two files define function_two and function_three . The source files have #include lines for the appropriate headers, so they appear to be dependent on the contents of the included headers. It’s not much of an application, but here are the listings:
/* main.c */
#include <stdlib.h>
#include “a.h”
extern void function_two();
extern void function_three();
int main()
{
function_two();
function_three();
exit (EXIT_SUCCESS);
}
/* 2.c */
#include “a.h”
#include “b.h”
void function_two() {
}
/* 3.c */
#include “b.h”
#include “c.h”
void function_three() {
}
Now try make again:
$make -f Makefile1
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
$
This is a successful make.
How It Works:-
The make command has processed the dependencies section of the makefile and determined the files that need to be created and in which order. Even though you listed how to create myapp first, make has determined the correct order for creating the files. It has then invoked the appropriate commands you gave it in the rules section for creating those files. The make command displays the commands as it executes them. You can now test your makefile to see whether it handles changes to the file b.h correctly:
$touch b.h
$make -f Makefile1
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
$
The make command has read your makefile, determined the minimum number of commands required to rebuild myapp , and carried them out in the correct order.
Now see what happens if you delete an object file:
$rm 2.o
$make -f Makefile1
gcc -c 2.c
gcc -o myapp main.o 2.o 3.o
$
Again, make correctly determines the actions required.
Comments in a Makefile:-
A comment in a makefile starts with # and continues to the end of the line.
Macros in a Makefile:-
Even if this was all there was to make and makefiles, they would be powerful tools for managing multiple source file projects. However, they would also tend to be large and inflexible for projects consisting of a very large number of files. Makefiles therefore allow you to use macros so that you can write them in a more generalized form. You 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 . You can set the value of a macro to blank (which expands to nothing) by leaving the rest of the line after the = blank.
Another problem with Makefile1 is that it assumes the compiler is called gcc . On other UNIX systems, you might be using cc or c89 . If you ever wanted to take your makefile to a different version of UNIX, or even if you obtained a different compiler to use on your existing system, you would have to change several lines of your makefile to make it work. Macros are a good way of collecting all these system dependent parts, making it easy to change them. Macros are normally defined inside the makefile itself, but they can be specified by calling make with themacro definition. for example, make CC=c89. Command-line definitions like this override define s in the makefile. When used outside makefiles, macro definitions must be passed as a single argument, so either avoid spaces or use quotes like this: make “CC = c89 “.
//A makefile with macro:-
//Here’s a revised version of the makefile, Makefile2 , using some macros:
all: myapp
# Which compiler
CC = gcc
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall –ansi
# Options for release
# CFLAGS = -O -Wall –ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
If you delete your old installation and create a new one with this new makefile, you 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
$
Macro Definition
$? List of prerequisites (files the target depends on) changed more recently than the current targe
$@ Name of the current target
$< Name of the current prerequisite
$* Name of the current prerequisite, without any suffix
Multiple Targets:-
It’s often useful to make more than a single target file, or to collect several groups of commands into a single place. You can extend your makefile to do this. In the following example, you add a clean option that removes unwanted objects and an install option that moves the finished application to a different directory.
//Multiple targets
//Here’s the next version of the makefile,Makefile3:
all: myapp
# Which compiler
CC = gcc
# Where to install
INSTDIR = /usr/local/bin
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall –ansi
# Options for release
# CFLAGS = -O -Wall –ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
-rm main.o 2.o 3.o
install: myapp
@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”;\
fi
There are several things to notice in this makefile. First, the special target all still specifies only myapp as a target. Thus, when you execute make without specifying a target, the default behavior is to build the target myapp. The next important point concerns 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. Because make invokes a shell for executing rules and uses a new shell for each rule, you 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.
Managing Libraries with make:-
When you’re working on larger projects, it’s often convenient to manage several compilation products using a library . Libraries are files, conventionally with the extension .a (for archive ), that contain a collection of object files. The make command has a special syntax for dealing with libraries that makes them very easy to manage. The syntax is lib(file.o) , which means the object file file.o , as stored in the library lib.a . The make command has a built-in rule for managing libraries that is usually equivalent to something like this:
.c.a:
$(CC) -c $(CFLAGS) $<
$(AR) $(ARFLAGS) $@ $*.o
The macros $(AR) and $(ARFLAGS) normally default to the command ar and the options rv, respectively. The rather terse syntax tells make that to get from a.c file to an .a library it must apply two rules:
1) The first rule says that it must compile the source file and generate an object.
2) The second rule says to use the ar command to revise the library, adding the new object file.
So, if you have a library fud , containing the file bas.o , in the first rule $< is replaced by bas.c . In the second rule $@ is replaced by the library fud.a and $* is replaced by the name bas.
//Managing Libraries: makefile:-
all: myapp
# Which compiler
CC = gcc
# Where to install
INSTDIR = /usr/local/bin
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall –ansi
# Options for release
# CFLAGS = -O -Wall –ansi
# Local Libraries
MYLIB = mylib.a
myapp: main.o $(MYLIB)
$(CC) -o myapp main.o $(MYLIB)
$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
clean:
-rm main.o 2.o 3.o $(MYLIB)
install: myapp
@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”;\
fi
Notice how we allow the default rules to do most of the work. Now test your new version of the makefile
$rm -f myapp *.o mylib.a
$make -f Makefile5
gcc -g -Wall -ansi -c -o main.o main.c
gcc -g -Wall -ansi -c -o 2.o 2.c
ar rv mylib.a 2.o
a - 2.o
gcc -g -Wall -ansi -c -o 3.o 3.c
ar rv mylib.a 3.o
a - 3.o
gcc -o myapp main.o mylib.a
$touch c.h
$make -f Makefile5
gcc -g -Wall -ansi -c -o 3.o 3.c
ar rv mylib.a 3.o
r - 3.o
gcc -o myapp main.o mylib.a
$
How it works:-
You first delete all the objects and the library and allow make to build myapp , which it does by compiling and creating the library before linking main.o with the library to create myapp . You then test the dependency rule for 3.o , which tells make that, if c.h changes, then 3.c must be recompiled. It does this correctly, compiling 3.c and updating the library before re-linking to create a new myapp executable file.