Main index

Introducing UNIX and Linux


Advanced shell programming

Overview
Sending and trapping signals
      Signal names
Functions
Aliases
The 'exec' mechanism
The 'eval' mechanism
Sending data across networks
      Sending printable characters
      Splitting files
Makefiles
Safe programming
Setting up a terminal
More on files
Miscellaneous utilities
Summary
Exercises

Makefiles

When developing software you will frequently create files that depend on the existence and/or state of other files. Consider the situation where you have a C program, and which you wish to call myprogram, which is stored in two files, prog1.c and prog2.c (Note the suffix .c for C files). In order to compile the program you would compile each of the two source code files, creating files prog1.o and prog2.o respectively which contain object code (binary code). Those two files would then be linked to create the final file myprogram. Program links are not related to file links discussed earlier on in the book.. That is, the binary code in prog1.o and prog2.o will be joined together to produce a single binary file - this is in fact not a trivial operation. We therefore have the dependencies:

  • prog1.o depends on prog1.c
  • prog2.o depends on prog2.c
  • myprogram depends on both prog1.o and prog2.o.

In order to compile this C program using the command cc, you would have three commands to perform:

cc -c prog1.c
cc -c prog2.c
cc -o myprogram prog1.o prog2.o

The first two lines, which translate the two source files to object code, can be performed in either order. The final command, which links two object code files together, must wait until the first two have been completed.

If you were to type in these commands each time, there would be a danger of making an error and accidentally forgetting to recompile one of the source files after editing it. Alternatively, you could place all three commands into a file and then execute that file as a shell script. This would have the disadvantage that if you edited (say) prog1.c you would also have to recompile prog2.c even though this was not necessary.

In this small example, this might seem a minor problem, but when performing serious system development it is not unusual to have large volumes of code that take a long time to compile. In such a case, it is sensible to minimise the amount of work that has to be done when small changes to the code are made.

A tool that makes use of file dependencies is known as make. This works in the following way: a file, called a makefile, is created (usually in the same directory containing the software being developed), and a program called make reads that file. The makefile contains information indicating

  • which file depends on which other file/s, and
  • what commands must be performed to bring a file 'up-to-date'.

The above example could have as the contents of its makefile:

myprogram: prog1.o prog2.o
        cc -o myprogram prog1.o prog2.o

prog1.o: prog1.c
        cc -c prog1.c

prog2.o: prog2.c
        cc -c prog2.c

You will notice two types of line in this file. There are lines that are not indented - they take the form of a word (known as the target) followed by a colon, and then followed by some other words, where the words would usually be names of files. These lines indicate that the word (file) on the left of the colon is dependent on the filenames on the right of the semicolon. Thus myprogram depends on both prog1.o and prog2.o, etc.

The indented lines (which must be indented with a single TAB, not with Spaces) indicate the action to be taken if the dependency (which would be shown on the previous line) is not up-to-date. So, if myprogram was older than either prog1.o or prog2.o, the command cc -o myprogram prog1.o prog2.o would be executed.

In order to use makefiles, data in the format discussed above should be stored in a file with name either Makefile or makefile. Then, to bring the software up-to-date, simply type make followed by the target which you need updated:

make myprogram

If you invoke make without any arguments, it will assume that the target is the first target mentioned in the makefile. If you run make with option -n it will display the commands that it would execute, but will not actually run them. If you are unsure of the correctness of your makefile, it is wise to run make with the -n option initially simply to ensure that the actions it performs are in fact the actions you expect. For instance, taking the above example would give:

make -n
cc -c prog1.c
cc -c prog2.c
cc -o myprogram prog1.o prog2.o

If, while running make, one of the commands fails (for example, you had not created prog1.c at all, or there was an error in that C program), then make would terminate at that point.

This utility has many other features, and can handle much more complex dependencies than the simple ones indicated here. If you are creating programs as part of a course in Pascal or C (say), then a simple makefile such as this one will be adequate. Only when you move on to more complex programming tasks will you need to examine make in greater detail.

Two other standard commands, which are principally used within makefiles, are worth mentioning briefly. Command ar is used to maintain 'archives' of files, and is used principally for maintaining libraries of object code, usually produced by a compiled language such as C. When an executable file consisting of binary code is produced, it contains information used by debugging utilities, but not required simply to execute it. This redundancy can be eliminated from such a file by means of the command strip.


Copyright © 2002 Mike Joy, Stephen Jarvis and Michael Luck