Chapter 3. Short-cuts and Other Nice Things

Table of Contents
3.1. Transformation Rules
3.2. Including Other Makefiles
3.3. Saving Commands
3.4. Target Attributes
3.5. Special Targets
3.6. Modifying Variable Expansion
3.7. More Exercises

Based on what I have told you so far, you may have gotten the impression that PMake is just a way of storing away commands and making sure you do not forget to compile something. Good. That is just what it is. However, the ways I have described have been inelegant, at best, and painful, at worst. This chapter contains things that make the writing of makefiles easier and the makefiles themselves shorter and easier to modify (and, occasionally, simpler). In this chapter, I assume you are somewhat more familiar with Sprite (or UNIX®, if that is what you are using) than I did in Chapter 2, The Basics of PMake, just so you are on your toes. So without further ado…

3.1. Transformation Rules

As you know, a file's name consists of two parts: a base name, which gives some hint as to the contents of the file, and a suffix, which usually indicates the format of the file. Over the years, as UNIX® has developed, naming conventions, with regard to suffixes, have also developed that have become almost as incontrovertible as Law. E.g. a file ending in .c is assumed to contain C source code; one with a .o suffix is assumed to be a compiled, relocatable object file that may be linked into any program; a file with a .ms suffix is usually a text file to be processed by Troff with the -ms macro package, and so on. One of the best aspects of both Make and PMake comes from their understanding of how the suffix of a file pertains to its contents and their ability to do things with a file based solely on its suffix. This ability comes from something known as a transformation rule. A transformation rule specifies how to change a file with one suffix into a file with another suffix.

A transformation rule looks much like a dependency line, except the target is made of two known suffixes stuck together. Suffixes are made known to PMake by placing them as sources on a dependency line whose target is the special target .SUFFIXES. E.g.:

.SUFFIXES   : .o .c
.c.o        :
	$(CC) $(CFLAGS) -c $(.IMPSRC)

The creation script attached to the target is used to trans form a file with the first suffix (in this case, .c) into a file with the second suffix (here, .o). In addition, the target inherits whatever attributes have been applied to the transformation rule. The simple rule given above says that to transform a C source file into an object file, you compile it using cc with the -c flag. This rule is taken straight from the system makefile. Many transformation rules (and suffixes) are defined there, and I refer you to it for more examples (type pmake -h to find out where it is).

There are several things to note about the transformation rule given above:

  1. The .IMPSRC variable. This variable is set to the implied source (the file from which the target is being created; the one with the first suffix), which, in this case, is the .c file.

  2. The CFLAGS variable. Almost all of the transformation rules in the system makefile are set up using variables that you can alter in your makefile to tailor the rule to your needs. In this case, if you want all your C files to be compiled with the -g flag, to provide information for dbx, you would set the CFLAGS variable to contain -g (CFLAGS = -g) and PMake would take care of the rest.

To give you a quick example, the makefile in Section 2.3.4, “Environment Variables” could be changed to this:

OBJS            = a.o b.o c.o
program         : $(OBJS)
	 $(CC) -o $(.TARGET) $(.ALLSRC)
$(OBJS)         : defs.h

The transformation rule I gave above takes the place of the 6 lines [1]:

a.o             : a.c
	cc -c a.c
b.o             : b.c
	cc -c b.c
c.o             : c.c
	cc -c c.c

Now you may be wondering about the dependency between the .o and .c files – it is not mentioned anywhere in the new makefile. This is because it is not needed: one of the effects of applying a transformation rule is the target comes to depend on the implied source. That's why it is called the implied source.

For a more detailed example. Say you have a makefile like this:

a.out           : a.o b.o
	$(CC) $(.ALLSRC)

and a directory set up like this:

total 4
-rw-rw-r--  1 deboor        34 Sep  7 00:43 Makefile
-rw-rw-r--  1 deboor       119 Oct  3 19:39 a.c
-rw-rw-r--  1 deboor       201 Sep  7 00:43 a.o
-rw-rw-r--  1 deboor        69 Sep  7 00:43 b.c

While just typing pmake will do the right thing, it is much more informative to type pmake -d s. This will show you what PMake is up to as it processes the files. In this case, PMake prints the following:

Suff_FindDeps (a.out)
     using existing source a.o
     applying .o -> .out to "a.o"
Suff_FindDeps (a.o)
     trying a.c...got it
     applying .c -> .o to "a.c"
Suff_FindDeps (b.o)
     trying b.c...got it
     applying .c -> .o to "b.c"
Suff_FindDeps (a.c)
     trying a.y...not there
     trying a.l...not there
     trying a.c,v...not there
     trying a.y,v...not there
     trying a.l,v...not there
Suff_FindDeps (b.c)
     trying b.y...not there
     trying b.l...not there
     trying b.c,v...not there
     trying b.y,v...not there
     trying b.l,v...not there
--- a.o ---
cc  -c a.c
--- b.o ---
cc  -c b.c
--- a.out ---
cc a.o b.o

Suff_FindDeps is the name of a function in PMake that is called to check for implied sources for a target using transformation rules. The transformations it tries are, naturally enough, limited to the ones that have been defined (a transformation may be defined multiple times, by the way, but only the most recent one will be used). You will notice, however, that there is a definite order to the suffixes that are tried. This order is set by the relative positions of the suffixes on the .SUFFIXES line – the earlier a suffix appears, the earlier it is checked as the source of a transformation. Once a suffix has been defined, the only way to change its position in the pecking order is to remove all the suffixes (by having a .SUFFIXES dependency line with no sources) and redefine them in the order you want. (Previously-defined transformation rules will be automatically redefined as the suffixes they involve are re-entered.) Another way to affect the search order is to make the dependency explicit. In the above example, a.out depends on a.o and b.o. Since a transformation exists from .o to .out, PMake uses that, as indicated by the using existing source a.o message.

The search for a transformation starts from the suffix of the target and continues through all the defined transformations, in the order dictated by the suffix ranking, until an existing file with the same base (the target name minus the suffix and any leading directories) is found. At that point, one or more transformation rules will have been found to change the one existing file into the target.

For example, ignoring what's in the system makefile for now, say you have a makefile like this:

.SUFFIXES       : .out .o .c .y .l
.l.c            :
	lex $(.IMPSRC)
	mv lex.yy.c $(.TARGET)
.y.c            :
	yacc $(.IMPSRC)
	mv y.tab.c $(.TARGET)
.c.o            :
	cc -c $(.IMPSRC)
.o.out          :
	cc -o $(.TARGET) $(.IMPSRC)

and the single file jive.l. If you were to type pmake -rd ms jive.out, you would get the following output for jive.out:

Suff_FindDeps (jive.out)
     trying jive.o...not there
     trying jive.c...not there
     trying jive.y...not there
     trying jive.l...got it
     applying .l -> .c to "jive.l"
     applying .c -> .o to "jive.c"
     applying .o -> .out to "jive.o"

and this is why: PMake starts with the target jive.out, figures out its suffix (.out) and looks for things it can transform to a .out file. In this case, it only finds .o, so it looks for the file jive.o. It fails to find it, so it looks for transformations into a .o file. Again it has only one choice: .c. So it looks for jive.c and, as you know, fails to find it. At this point it has two choices: it can create the .c file from either a .y file or a .l file. Since .y came first on the .SUFFIXES line, it checks for jive.y first, but can not find it, so it looks for jive.l and, lo and behold, there it is. At this point, it has defined a transformation path as follows:

.l  ->  .c  ->  .o  -> .out

and applies the transformation rules accordingly. For completeness, and to give you a better idea of what PMake actually did with this three-step transformation, this is what PMake printed for the rest of the process:

Suff_FindDeps (jive.o)
     using existing source jive.c
     applying .c -> .o to "jive.c"
Suff_FindDeps (jive.c)
     using existing source jive.l
     applying .l -> .c to "jive.l"
Suff_FindDeps (jive.l)
Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
Examining jive.c...non-existent...out-of-date
--- jive.c ---
lex jive.l
... meaningless lex output deleted ...
mv lex.yy.c jive.c
Examining jive.o...non-existent...out-of-date
--- jive.o ---
cc -c jive.c
Examining jive.out...non-existent...out-of-date
--- jive.out ---
cc -o jive.out jive.o

One final question remains: what does PMake do with targets that have no known suffix? PMake simply pretends it actually has a known suffix and searches for transformations accordingly. The suffix it chooses is the source for the .NULL target mentioned later. In the system makefile, .out is chosen as the null suffix because most people use PMake to create programs. You are, however, free and welcome to change it to a suffix of your own choosing. The null suffix is ignored, however, when PMake is in compatibility mode (see Chapter 4, PMake for Gods).



[1] This is also somewhat cleaner, I think, than the dynamic source solution presented in Section 2.6, “Writing and Debugging a Makefile”.

All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/

Questions that are not answered by the documentation may be sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.