Monday, March 21, 2011

How to use GNU make

    When we are writing programs that has large number of source files, the commands that we have to use on the terminal will be very long since we have to put all the source files' names there. Therefore compiling again and again for testing and debugging purposes is difficult with the long list of source files's names. In addition to that there are certain other things that we have to consider when compiling source codes of a program. As an example if our program uses some libraries located in different directories in the system, we have to check wheather they exists. And also we may have to check whether the versions of different libraries and tools are compatible to compile our program.
    To ease the overhead of doing all those stuff we have GNU make which is a great utility available in Linux environment. In this article I note down the very basics of GNU make which can be used in our programming works. I use a simple examples to describe the most fundamental facilities we have in GNU make. Suppose we have a program with 4 source files given below.

1:  main.c file  
2:  ===========  
3:  #include "head1.h"  
4:  #include "head1.h"  
5:  #include "head1.h"  
6:  main() {  
7:    func_1();  
8:    func_2();  
9:    func_3();  
10:  }  
11:  head1.c  
12:  =======  
13:  #include "head1.h"  
14:  #include <stdio.h>  
15:  void func_1() {  
16:    printf("head-1\n");  
17:  }  
18:  head2.c  
19:  =======  
20:  #include "head2.h"  
21:  #include <stdio.h>  
22:  void func_2() {  
23:    printf("head-2\n");  
24:  }  
25:  head3.c  
26:  =======  
27:  #include "head3.h"  
28:  #include <stdio.h>  
29:  void func_3() {  
30:    printf("head-3\n");  
31:  }  

We need to compile these programs and create an executable program name "program". If we are not using GNU make, here what we will issue on the terminal to compile the program.
    gcc -o program main.c head1.c head2.c head3.c
Instead of it, we write a file with the contents below and save with the name "Makefile" in the same directory as the source files resides.

1:  Makefile  
2:  ========  
3:  program : main.o head1.o head2.o head3.o  
4:    gcc -o program main.o head1.o head2.o head3.o  
5:  main.o : main.c head1.h head2.h head3.h  
6:    gcc -c main.c  
7:  head1.o : head1.c head1.h  
8:    gcc -c head1.c  
9:  head2.o : head2.c head2.h  
10:    gcc -c head2.c  
11:  head3.o : head3.c head3.h  
12:    gcc -c head3.c  
13:  clean :  
14:    rm program main.o head1.o head2.o head3.o  

To compile the program what we have to do is just goto the directory from the Linux shell and issue the command 'make'. Then the 'Makefile' that we have created will get executed. Before closely analying the content in the make file it's better to look at the general syntax of a GNU make rule.

targets : prerequisites
             recipe
             ...

The targets is the file name that will be generated by executing this rule. Prerequisites are a list of file names separated by spaces that are required to execute this rule successfully. If the prerequisites are there, the recipe lines will be executed. In the recipe lines contains the instructions to do whatever to the prerequisites files and generate the target file. Lets take a look at the make file. There our first rule says that it should generate a file called program if the object files listed exists. If so, it execute the recipe and link the object files to create the target program. Likewise all the first five rules will specify that they are depend on and what they will produce.
    GNU make utility will consider all the dependencies of the source files to each other and do the compilation and linking. The last rule in the make file is something special. It doesn't talking about a target file called 'clean'. The clean target is used to specify what should be done if we enter the command in the shell as,
    make clean
In our make file we have specified to delete all the files that have generated by executing the make file. There's no prerequisites for the clean target. We can use variables in make files to reduce rewriting long lists of file names or other stuff. In our make file, let's use a variable to represent the object file names.

1:  Makefile  
2:  ========  
3:  objects = main.o head1.o head2.o head3.o  
4:  program : $(objects)  
5:    gcc -o program $(objects)  
6:  main.o : main.c head1.h head2.h head3.h  
7:    gcc -c main.c  
8:  head1.o : head1.c head1.h  
9:    gcc -c head1.c  
10:  head2.o : head2.c head2.h  
11:    gcc -c head2.c  
12:  head3.o : head3.c head3.h  
13:    gcc -c head3.c  
14:  clean :  
15:    rm program $(objects)  

We may have multiple make files in our program source directory. In such situations we may need to execute other make file by running a one make file. To do this we can inclue other make files' names in a make file by using the below way. 'Makefile.one' and 'Makefile.two' are two make files that we have in addition to the default make file.

    include Makefile.one
    include Makefile.two

Depending on the context we may have to follow different approaches to compile a program. Therefore conditionals can be used to specify different ways.
The syntax of a simple conditional with no else is as follows:

     conditional-directive
     text-if-true
     endif

The syntax of a complex conditional is as follows:

     conditional-directive
     text-if-true
     else
     text-if-false
     endif
or:

     conditional-directive
     text-if-one-is-true
     else conditional-directive
     text-if-true
     else
     text-if-false
     endif

Let's assume we have a program that should be linked to different libraries depending on the environment we are working on. Below code describes the part of the make file that do this.

     libs_for_gcc = -lgnu
     normal_libs =
    
     foo: $(objects)
     ifeq ($(CC),gcc)
             $(CC) -o foo $(objects) $(libs_for_gcc)
     else
             $(CC) -o foo $(objects) $(normal_libs)
     endif

The ifeq directive begins the conditional, and specifies the condition. It contains two arguments, separated by a comma and surrounded by parentheses. Variable substitution is performed on both arguments and then they are compared. The lines of the makefile following the ifeq are obeyed if the two arguments match; otherwise they are ignored.
    There's another conditional directive that is ifdef. It checks whether a variable has defined and perform tasks according to it. Below is an example for ifdef directive.

    bar = true
        foo = bar
        ifdef $(foo)
        frobozz = yes
    else
        frobozz = no
        endif
The information I have showed here are just a small part of what we can do with GNU make. Yet there are lot of things we have to explore in GNU make utility. It's up to you to learn it and make use of it in your day to day programming works.

No comments:

Post a Comment