Inexpensive, effective mixed-platform Network Security using Linux-based solutions. 

 
Horizon Network Security™
phone: +1 770-662-8321, email: support@VerySecureLinux.com

 
Our Publications

PROBLEM SOLVER

Making it

by Bob Toxen

Compiling a simple C program under UNIX is a fairly straight-forward matter. It can be as easy as entering a command like:

  % cc -O -o simp simp.c

Some programs need some libraries and must be compiled like so:


  % cc -O -o foo foo.c -lcurses -ltermcap -lgl -lm

Other programs may have separate source files and need to know what version of UNIX they will be used on (for conditional compilation). Such a program may need to have the binary stripped out of the symbol table. To compile the program, commands like the following would be necessary:


  % cc -O -DSYS5 -c egads1.c egads2.c
  % cc -s -o egads egads1.o egads2.o -ltermcap -lPW -lm

Now suppose you have to worry about compiling these programs, as well as 300 others, which include large libraries, kernel applications, formatting documents, and the like. Typing all those cc commands would clearly be a large task. Remembering how to compile each program would be nearly impossible considering all the compile and load flags, libraries, and other matters. A shell script can be used to make this task easier but this doesn't solve the problem of determining which modules have to be recompiled if you change source modules or include files.

Fortunately, a program named make has been developed to handle all these tasks simply and elegantly. To use make, one edits a file called makefile or Makefile in the current directory. The makefile delineates the sources that binaries depend on and tells how to compile the sources into a specific binary.

ON THE MAKE

Two different types of lines can be found in makefiles. The first (which describes who depends on whom) is called a dependency line. It consists of the name of the file to be made followed by a colon (:), optionally followed by spaces (or tabs), and followed by a blank-separated list of files the makefile depends on. In our first example, the dependency line would be:

  simp:   simp.c

The second type of line (which describes how to do the actual compilation) is called an action line and consists of an initial tab followed by a legal shell command. There may be several action lines following each dependency line. On standard System III & V, the $SHELL environment variable determines which shell one gets, with the default being sh. On other systems, sh is always used. Some advanced makefiles use if, for, and other shell statements. In our example, the format would be:


  cc -O -o simp simp.c
The make command provides certain macros to make things easier. A $@ variable can be used as a placeholder for the thing to be made and a $? can hold the place of the source files that need re-compilation. Thus our action line could read:

  cc -O -o $@ $?
and the makefile would look like:

  simp:   simp.c
          cc -O -o $@ $?
To use it, simply enter:

  % make

The same makefile can be used to describe how to make many different things. Figure 1 contains a makefile capable of compiling three different programs.

Note that egads depends on both egads1.o and egads2.o and that these in turn depend on egads1.c and egads2.c, respectively. The make program is quite smart and will figure out that when egads depends on egads1.o and egads1.o depends on egads1.c, it must first make egads1.o from egads1.c and them make egads from egads1.o.

If egads1.o already exists, make will determine when egads1.c was last changed (edited) and compare this time to the time that egads1.o was last changed (compiled). If egads1.c is newer, make will assume you just edited it and that it needs to be re-compiled.

On the other hand, if egads1.o is newer, make will assume that egads1.c was compiled into egads1.o more recently than it was edited and that it therefore does not need to be re-compiled.

Thus if you have edited egads1.c since the last invocation of make but have not edited egads2.c , make will include the following:


  cc -O -DSYS5 -c egads1.c
  cc -s -o egads egads1.o egads2.o -ltermcap -lPW -lm
but will not do:

  cc -O -DSYS5 -c egads2.c

To make simp and egads (but not foo), issue the command:


  % make simp egads
To make all three, issue the command:

  % make simp foo egads



  all:    simp foo egads

  simp:   simp.c
          cc -O -o $@ $?

  foo:    foo.c
          cc -O -o foo foo.c -lcurses -ltermcap -lgl -lm

  egads1.o:egads1.c
          cc -O -DSYS5 -c egads1.c

  egads2.o:egads2.c
          cc -O -DSYS5 -c egads2.c

  egads:  egads1.o egads2.o
          cc -s -o egads egads1.o egads2.o \
            -lcurses -ltermcap -lPW -lm

Figure 1 -- A sample makefile

As you can see, we are again faced with having to remember what we have to type in order to compile everything in a directory. All the same, it is still less to type than before. This can be cut even further by having a name such as all (which is commonly used for this purpose) depend on everything you usually want to make in that directory. The example in Figure 1 shows how this can be done. Thus, if we issue the command:

  % make all
or even:

  % make
all three programs will be re-compiled since all is the first target (or thing to be made) in the file. It does not matter that there is not really a program called all or that there is not an action line associated with it. We could, in fact, have an action line such as:

  @echo All Done
The make command would otherwise display each command line on the terminal just before executing it. The @ sign inhibits this.

MAKING THE MOST OF MAKE

We can use other pseudo targets similar to all to do other operations. One that's commonly used is called install, as in install the compiled binary where it will be used with the correct permissions. Assuming our sample programs are to be usable by everyone, we might want to install them in the /usr/bin directory with the owner and group defined as bin and the mode set to 755. To do this, we could add the lines displayed in Figure 2 to our makefile. Note that install depends on all so that if we do:


  % make install
the all target will first be "made", ensuring that the compiled programs are up to date.

  install:all
          cp simp foo egads /usr/bin
          cd /usr/bin ; chmod 755 simp foo egads
          cd /usr/bin ; chgrp bin simp foo egads ; \
            chown bin simp foo egads

Figure 2 -- Making programs available systemwide.

Two other commonly included targets are clean and clobber used as follows:


  % make clean
This is commonly used to remove temporary files such as *.o. The command:

  % make clobber
is used to remove temporary files and binaries. Thus it removes everything except the source files (and the makefile). These entries for our sample makefile would look like:

  clean:
          rm -f *.o

  clobber:clean
          rm -f simp foo egads

There are many other uses for makefiles. One common use is for document preparation. Suppose we are working on a document that is made up of several chapters. The text of each chapter is in a separate file whose name is docx.mm, where x is the chapter number. Let's say we also use tables and equations in the document. The makefile might look like the one shown in Figure 3. One can use the command:


  % make print
to print the entire document.

  SHELL   = /bin/sh

  print:
          tbl doc1.mm doc2.mm doc3.mm | neqn | nroff -mm

  spell:
          for x in doc1.mm doc2.mm doc3.mm ; \
            do spell $$x | pr -h $$x | lpr ; done

  doc1 doc2 doc3:
          tbl $@.mm | neqn | nroff -mm

Figure 3 -- A sample makefile for document production.

One can also enter:


  % make spell
to run a spelling check on each text file separately. The pr -h $$x action specified within spell will print the name of the file at the top of each page of spelling errors. The file will then be sent to the printer via lpr. Since $ is used to reference make macros, $$ is used to send a $ to the shell. We use a shell for statement to iterate through each document file. The backslash indicates a continuation line. That is, the line it occurs on and the following line will be joined together before being given to the shell.

The $@, as discussed previously, gets expanded to the thing we want to make. Since we have listed several items to the left of the colon, the action line will be used to make any of them. Thus we could issue the command:


  % make doc2
to format and print just doc2.mm. Lastly, the line:

  SHELL   = /bin/sh
is used on standard System III & V to have make use sh to process the action lines even when csh is being used for an interactive shell. This is important for complex makefiles where shell syntaxes differ. Also, sh's usually faster speed is more important than csh's greater power when making files.

SUMMARY

This should not be taken as anything more than an introduction to make and makefiles. There are many more things that can be done and many advanced features that can be utilized to make life much easier. These features must be learned before intermediate or advanced makefiles are attempted.
Bob Toxen is a member of the technical staff at Silicon Graphics, Inc. He has gained a reputation as a leading expert on uucp communications, file system repair, and UNIX utilities. He has also done ports of System III and System V to systems based on the Zilog 8000 and Motorola 68010.
Copyright © 1985, 2007, 2014, 2020 Robert M. Toxen. All rights reserved.
Back