                           PMake - A Tutorial

  Adam de Boor

   Redistribution and use in source (XML DocBook) and 'compiled'
   forms (XML, HTML, PDF, PostScript, RTF and so forth) with or
   without modification, are permitted provided that the following
   conditions are met:

    1. Redistributions of source code (XML DocBook) must retain the
       above copyright notice, this list of conditions and the
       following disclaimer as the first lines of this file
       unmodified.

    2. Redistributions in compiled form (transformed to other DTDs,
       converted to PDF, PostScript, RTF and other formats) must
       reproduce the above copyright notice, this list of conditions
       and the following disclaimer in the documentation and/or other
       materials provided with the distribution.

     Important: THIS DOCUMENTATION IS PROVIDED BY THE FREEBSD
     DOCUMENTATION PROJECT "AS IS" AND ANY EXPRESS OR IMPLIED
     WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD
     DOCUMENTATION PROJECT BE LIABLE FOR ANY DIRECT, INDIRECT,
     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
     DAMAGE.

     --------------------------------------------------------------

   Table of Contents

   1 Introduction

   2 The Basics of PMake

                2.1. Dependency Lines

                2.2. Shell Commands

                2.3. Variables

                             2.3.1. Local Variables

                             2.3.2. Command-line Variables

                             2.3.3. Global Variables

                             2.3.4. Environment Variables

                2.4. Comments

                2.5. Parallelism

                2.6. Writing and Debugging a Makefile

                2.7. Invoking PMake

                2.8. Summary

   3 Short-cuts and Other Nice Things

                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

   4 PMake for Gods

                4.1. Search Paths

                4.2. Archives and Libraries

                4.3. On the Condition...

                4.4. A Shell is a Shell is a Shell

                4.5. Compatibility

                4.6. DEFCON 3 - Variable Expansion

                4.7. DEFCON 2 - The Number of the Beast

                4.8. DEFCON 1 - Imitation is the Not the Highest Form
                of Flattery

                4.9. The Way Things Work

   5 Answers to Exercises

   Glossary of Jargon

     --------------------------------------------------------------

                         Chapter 1 Introduction

   PMake is a program for creating other programs, or anything else
   you can think of for it to do. The basic idea behind PMake is
   that, for any given system, be it a program or a document or
   whatever, there will be some files that depend on the state of
   other files (on when they were last modified). PMake takes these
   dependencies, which you must specify, and uses them to build
   whatever it is you want it to build.

   PMake is almost fully-compatible with Make, with which you may
   already be familiar. PMake's most important feature is its ability
   to run several different jobs at once, making the creation of
   systems considerably faster. It also has a great deal more
   functionality than Make.

   This tutorial is divided into three main sections corresponding to
   basic, intermediate and advanced PMake usage. If you already know
   Make well, you will only need to skim Chapter 2 (there are some
   aspects of PMake that I consider basic to its use that did not
   exist in Make). Things in Chapter 3 make life much easier, while
   those in Chapter 4 are strictly for those who know what they are
   doing. Glossary of Jargon has definitions for the jargon I use and
   Chapter 5 contains possible solutions to the problems presented
   throughout the tutorial.

     --------------------------------------------------------------

                     Chapter 2 The Basics of PMake

   PMake takes as input a file that tells which files depend on which
   other files to be complete and what to do about files that are
   "out-of-date". This file is known as a "makefile" and is usually
   kept in the top-most directory of the system to be built. While
   you can call the makefile anything you want, PMake will look for
   Makefile and makefile (in that order) in the current directory if
   you do not tell it otherwise. To specify a different makefile, use
   the -f flag, e.g.

 % pmake -f program.mk

   A makefile has four different types of lines in it:

     * File dependency specifications

     * Creation commands

     * Variable assignments

     * Comments, include statements and conditional directives

   Any line may be continued over multiple lines by ending it with a
   backslash. The backslash, following newline and any initial
   whitespace on the following line are compressed into a single
   space before the input line is examined by PMake.

     --------------------------------------------------------------

2.1. Dependency Lines

   As mentioned in the introduction, in any system, there are
   dependencies between the files that make up the system. For
   instance, in a program made up of several C source files and one
   header file, the C files will need to be re-compiled should the
   header file be changed. For a document of several chapters and one
   macro file, the chapters will need to be reprocessed if any of the
   macros changes. These are dependencies and are specified by means
   of dependency lines in the makefile.

   On a dependency line, there are targets and sources, separated by
   a one- or two-character operator. The targets "depend" on the
   sources and are usually created from them. Any number of targets
   and sources may be specified on a dependency line. All the targets
   in the line are made to depend on all the sources. Targets and
   sources need not be actual files, but every source must be either
   an actual file or another target in the makefile. If you run out
   of room, use a backslash at the end of the line to continue onto
   the next one.

   Any file may be a target and any file may be a source, but the
   relationship between the two (or however many) is determined by
   the "operator" that separates them. Three types of operators
   exist: one specifies that the datedness of a target is determined
   by the state of its sources, while another specifies other files
   (the sources) that need to be dealt with before the target can be
   re-created. The third operator is very similar to the first, with
   the additional condition that the target is out-of-date if it has
   no sources. These operations are represented by the colon, the
   exclamation point and the double-colon, respectively, and are
   mutually exclusive. Their exact semantics are as follows:

   :  If a colon is used, a target on the line is considered to be    
      "out-of-date" (and in need of creation) if any of the sources   
      has been modified more recently than the target, or the target  
      does not exist. Under this operation, steps will be taken to    
      re-create the target only if it is found to be out-of-date by   
      using these two rules.                                          
   !  If an exclamation point is used, the target will always be      
      re-created, but this will not happen until all of its sources   
      have been examined and re-created, if necessary.                
   :: If a double-colon is used, a target is "out-of-date" if any of  
      the sources has been modified more recently than the target, or 
      the target does not exist, or the target has no sources. If the 
      target is out-of-date according to these rules, it will be      
      re-created. This operator also does something else to the       
      targets, but I will go into that in the next section (see Shell 
      Commands).                                                      

   Enough words, now for an example. Take that C program I mentioned
   earlier. Say there are three C files (a.c, b.c and c.c) each of
   which includes the file defs.h. The dependencies between the files
   could then be expressed as follows:

 program         : a.o b.o c.o

 a.o b.o c.o     : defs.h

 a.o             : a.c

 b.o             : b.c

 c.o             : c.c

   You may be wondering at this point, where a.o, b.o and c.o came in
   and why they depend on defs.h and the C files do not. The reason
   is quite simple: program cannot be made by linking together .c
   files-it must be made from .o files. Likewise, if you change
   defs.h, it is not the .c files that need to be re-created, it is
   the .o files. If you think of dependencies in these terms-which
   files (targets) need to be created from which files (sources)-you
   should have no problems.

   An important thing to notice about the above example, is that all
   the .o files appear as targets on more than one line. This is
   perfectly all right: the target is made to depend on all the
   sources mentioned on all the dependency lines. For example, a.o
   depends on both defs.h and a.c.

   The order of the dependency lines in the makefile is important:
   the first target on the first dependency line in the makefile will
   be the one that gets made if you do not say otherwise. That is why
   program comes first in the example makefile, above.

   Both targets and sources may contain the standard C-Shell wildcard
   characters ({, }, *, ?, [, and ]), but the non-curly-brace ones
   may only appear in the final component (the file portion) of the
   target or source. The characters mean the following things:

   {} These enclose a comma-separated list of options and cause the   
      pattern to be expanded once for each element of the list. Each  
      expansion contains a different element. For example,            
      src/{whiffle,beep,fish}.c expands to the three words            
      src/whiffle.c, src/beep.c, and src/fish.c. These braces may be  
      nested and, unlike the other wildcard characters, the resulting 
      words need not be actual files. All other wildcard characters   
      are expanded using the files that exist when PMake is started.  
   *  This matches zero or more characters of any sort. src/*.c will  
      expand to the same three words as above as long as src contains 
      those three files (and no other files that end in .c).>         
   ?  Matches any single character.                                   
   [] This is known as a character class and contains either a list   
      of single characters, or a series of character ranges (a-z, for 
      example means all characters between a and z), or both. It      
      matches any single character contained in the list. For         
      example, [A-Za-z] will match all letters, while [0123456789]    
      will match all numbers.                                         

     --------------------------------------------------------------

2.2. Shell Commands

   "Is not that nice," you say to yourself, "but how are files
   actually ``re-created'', as he likes to spell it?" The re-creation
   is accomplished by commands you place in the makefile. These
   commands are passed to the Bourne shell (better known as /bin/sh)
   to be executed and are expected to do what is necessary to update
   the target file (PMake does not actually check to see if the
   target was created. It just assumes it is there).

   Shell commands in a makefile look a lot like shell commands you
   would type at a terminal, with one important exception: each
   command in a makefile must be preceded by at least one tab.

   Each target has associated with it a shell script made up of one
   or more of these shell commands. The creation script for a target
   should immediately follow the dependency line for that target.
   While any given target may appear on more than one dependency
   line, only one of these dependency lines may be followed by a
   creation script, unless the :: operator was used on the dependency
   line.

   If the double-colon was used, each dependency line for the target
   may be followed by a shell script. That script will only be
   executed if the target on the associated dependency line is
   out-of-date with respect to the sources on that line, according to
   the rules I gave earlier. I'll give you a good example of this
   later on.

   To expand on the earlier makefile, you might add commands as
   follows:

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

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

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

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

   Something you should remember when writing a makefile is, the
   commands will be executed if the target on the dependency line is
   out-of-date, not the sources. In this example, the command cc -c
   a.c will be executed if a.o is out-of-date. Because of the :
   operator, this means that should a.c or defs.h have been modified
   more recently than a.o, the command will be executed (a.o will be
   considered out-of-date).

   Remember how I said the only difference between a makefile shell
   command and a regular shell command was the leading tab? I lied.
   There is another way in which makefile commands differ from
   regular ones. The first two characters after the initial
   whitespace are treated specially. If they are any combination of @
   and -, they cause PMake to do different things.

   In most cases, shell commands are printed before they are actually
   executed. This is to keep you informed of what is going on. If an
   @ appears, however, this echoing is suppressed. In the case of an
   echo command, say

 echo Linking index

   it would be rather silly to see

 echo Linking index
 Linking index

   so PMake allows you to place an @ before the command to prevent
   the command from being printed:

 @echo Linking index

   The other special character is the -. In case you did not know,
   shell commands finish with a certain "exit status". This status is
   made available by the operating system to whatever program invoked
   the command. Normally this status will be 0 if everything went ok
   and non-zero if something went wrong. For this reason, PMake will
   consider an error to have occurred if one of the shells it invokes
   returns a non-zero status. When it detects an error, PMake's usual
   action is to abort whatever it is doing and exit with a non-zero
   status itself (any other targets that were being created will
   continue being made, but nothing new will be started. PMake will
   exit after the last job finishes). This behavior can be altered,
   however, by placing a - at the front of a command (e.g. -mv index
   index.old), certain command-line arguments, or doing other things,
   to be detailed later. In such a case, the non-zero status is
   simply ignored and PMake keeps chugging along.

   Because all the commands are given to a single shell to execute,
   such things as setting shell variables, changing directories,
   etc., last beyond the command in which they are found. This also
   allows shell compound commands (like for loops) to be entered in a
   natural manner. Since this could cause problems for some makefiles
   that depend on each command being executed by a single shell,
   PMake has a -B flag (it stands for backwards-compatible) that
   forces each command to be given to a separate shell. It also does
   several other things, all of which I discourage since they are now
   old-fashioned.

   A target's shell script is fed to the shell on its (the shell's)
   input stream. This means that any commands, such as ci that need
   to get input from the terminal will not work right - they will get
   the shell's input, something they probably will not find to their
   liking. A simple way around this is to give a command like this:

 ci $(SRCS) < /dev/tty

   This would force the program's input to come from the terminal. If
   you cannot do this for some reason, your only other alternative is
   to use PMake in its fullest compatibility mode. See
   "Compatibility" in Chapter 4.

     --------------------------------------------------------------

2.3. Variables

   PMake, like Make before it, has the ability to save text in
   variables to be recalled later at your convenience. Variables in
   PMake are used much like variables in the shell and, by tradition,
   consist of all upper-case letters (you do not have to use all
   upper-case letters. In fact there is nothing to stop you from
   calling a variable @^&$%$. Just tradition). Variables are
   assigned-to using lines of the form:

 VARIABLE = value

   appended-to by:

 VARIABLE += value

   conditionally assigned-to (if the variable is not already defined)
   by:

 VARIABLE ?= value

   and assigned-to with expansion (i.e. the value is expanded (see
   below) before being assigned to the variable-useful for placing a
   value at the beginning of a variable, or other things) by:

 VARIABLE := value

   Any whitespace before value is stripped off. When appending, a
   space is placed between the old value and the stuff being
   appended.

   The final way a variable may be assigned to is using:

 VARIABLE != shell-command

   In this case, shell-command has all its variables expanded (see
   below) and is passed off to a shell to execute. The output of the
   shell is then placed in the variable. Any newlines (other than the
   final one) are replaced by spaces before the assignment is made.
   This is typically used to find the current directory via a line
   like:

 CWD             != pwd

     Note: This is intended to be used to execute commands that
     produce small amounts of output (e.g. pwd). The implementation
     is less than intelligent and will likely freeze if you execute
     something that produces thousands of bytes of output (8 Kb is
     the limit on many UNIX(R) systems). The value of a variable may
     be retrieved by enclosing the variable name in parentheses or
     curly braces and preceding the whole thing with a dollar sign.

   For example, to set the variable CFLAGS to the string
   -I/sprite/src/lib/libc -O, you would place a line:

 CFLAGS = -I/sprite/src/lib/libc -O

   in the makefile and use the word $(CFLAGS) wherever you would like
   the string -I/sprite/src/lib/libc -O to appear. This is called
   variable expansion.

     Note: Unlike Make, PMake will not expand a variable unless it
     knows the variable exists. E.g. if you have a ${i} in a shell
     command and you have not assigned a value to the variable i (the
     empty string is considered a value, by the way), where Make
     would have substituted the empty string, PMake will leave the
     ${i} alone. To keep PMake from substituting for a variable it
     knows, precede the dollar sign with another dollar sign (e.g. to
     pass ${HOME} to the shell, use $${HOME}). This causes PMake, in
     effect, to expand the $ macro, which expands to a single $.

   For compatibility, Make's style of variable expansion will be used
   if you invoke PMake with any of the compatibility flags (-V, -B or
   -M. The -V flag alters just the variable expansion). There are two
   different times at which variable expansion occurs: when parsing a
   dependency line, the expansion occurs immediately upon reading the
   line. If any variable used on a dependency line is undefined,
   PMake will print a message and exit. Variables in shell commands
   are expanded when the command is executed. Variables used inside
   another variable are expanded whenever the outer variable is
   expanded (the expansion of an inner variable has no effect on the
   outer variable. For example, if the outer variable is used on a
   dependency line and in a shell command, and the inner variable
   changes value between when the dependency line is read and the
   shell command is executed, two different values will be
   substituted for the outer variable).

   Variables come in four flavors, though they are all expanded the
   same and all look about the same. They are (in order of expanding
   scope):

     * Local variables.

     * Command-line variables.

     * Global variables.

     * Environment variables.

   The classification of variables does not matter much, except that
   the classes are searched from the top (local) to the bottom
   (environment) when looking up a variable. The first one found
   wins.

     --------------------------------------------------------------

  2.3.1. Local Variables

   Each target can have as many as seven local variables. These are
   variables that are only "visible" within that target's shell
   script and contain such things as the target's name, all of its
   sources (from all its dependency lines), those sources that were
   out-of-date, etc. Four local variables are defined for all
   targets. They are:

   .TARGET

           The name of the target.

   .OODATE

           The list of the sources for the target that were
           considered out-of-date. The order in the list is not
           guaranteed to be the same as the order in which the
           dependencies were given.

   .ALLSRC

           The list of all sources for this target in the order in
           which they were given.

   .PREFIX

           The target without its suffix and without any leading
           path. E.g. for the target ../../lib/compat/fsRead.c, this
           variable would contain fsRead.

   Three other local variables are set only for certain targets under
   special circumstances. These are the .IMPSRC, .ARCHIVE, and
   .MEMBER variables. When they are set and how they are used is
   described later.

   Four of these variables may be used in sources as well as in shell
   scripts. These are .TARGET, .PREFIX, .ARCHIVE and .MEMBER. The
   variables in the sources are expanded once for each target on the
   dependency line, providing what is known as a "dynamic source,"
   allowing you to specify several dependency lines at once. For
   example:

 $(OBJS)         : $(.PREFIX).c

   will create a dependency between each object file and its
   corresponding C source file.

     --------------------------------------------------------------

  2.3.2. Command-line Variables

   Command-line variables are set when PMake is first invoked by
   giving a variable assignment as one of the arguments. For example:

 pmake "CFLAGS = -I/sprite/src/lib/libc -O"

   would make CFLAGS be a command-line variable with the given value.
   Any assignments to CFLAGS in the makefile will have no effect,
   because once it is set, there is (almost) nothing you can do to
   change a command-line variable (the search order, you see).
   Command-line variables may be set using any of the four assignment
   operators, though only = and ?= behave as you would expect them
   to, mostly because assignments to command-line variables are
   performed before the makefile is read, thus the values set in the
   makefile are unavailable at the time. += is the same as =, because
   the old value of the variable is sought only in the scope in which
   the assignment is taking place (for reasons of efficiency that I
   will not get into here). := and ?= will work if the only variables
   used are in the environment. != is sort of pointless to use from
   the command line, since the same effect can no doubt be
   accomplished using the shell's own command substitution mechanisms
   (backquotes and all that).

     --------------------------------------------------------------

  2.3.3. Global Variables

   Global variables are those set or appended-to in the makefile.
   There are two classes of global variables: those you set and those
   PMake sets. As I said before, the ones you set can have any name
   you want them to have, except they may not contain a colon or an
   exclamation point. The variables PMake sets (almost) always begin
   with a period and always contain upper-case letters, only. The
   variables are as follows:

   .PMAKE

           The name by which PMake was invoked is stored in this
           variable. For compatibility, the name is also stored in
           the MAKE variable.

   .MAKEFLAGS

           All the relevant flags with which PMake was invoked. This
           does not include such things as -f or variable
           assignments. Again for compatibility, this value is stored
           in the MFLAGS variable as well.

   Two other variables, .INCLUDES and .LIBS, are covered in the
   section on special targets in Chapter 3.

   Global variables may be deleted using lines of the form:

 #undef variable

   The # must be the first character on the line. Note that this may
   only be done on global variables.

     --------------------------------------------------------------

  2.3.4. Environment Variables

   Environment variables are passed by the shell that invoked PMake
   and are given by PMake to each shell it invokes. They are expanded
   like any other variable, but they cannot be altered in any way.

   One special environment variable, PMAKE, is examined by PMake for
   command-line flags, variable assignments, etc., it should always
   use. This variable is examined before the actual arguments to
   PMake are. In addition, all flags given to PMake, either through
   the PMAKE variable or on the command line, are placed in this
   environment variable and exported to each shell PMake executes.
   Thus recursive invocations of PMake automatically receive the same
   flags as the top-most one.

   Using all these variables, you can compress the sample makefile
   even more:

 OBJS            = a.o b.o c.o

 program         : $(OBJS)
         cc $(.ALLSRC) -o $(.TARGET)

 $(OBJS)         : defs.h

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

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

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

     --------------------------------------------------------------

2.4. Comments

   Comments in a makefile start with a # character and extend to the
   end of the line. They may appear anywhere you want them, except in
   a shell command (though the shell will treat it as a comment,
   too). If, for some reason, you need to use the # in a variable or
   on a dependency line, put a backslash in front of it. PMake will
   compress the two into a single #.

     Note: This is not true if PMake is operating in
     full-compatibility mode).

     --------------------------------------------------------------

2.5. Parallelism

   PMake was specifically designed to re-create several targets at
   once, when possible. You do not have to do anything special to
   cause this to happen (unless PMake was configured to not act in
   parallel, in which case you will have to make use of the -L and -J
   flags (see below)), but you do have to be careful at times.

   There are several problems you are likely to encounter. One is
   that some makefiles (and programs) are written in such a way that
   it is impossible for two targets to be made at once. The program
   xstr, for example, always modifies the files strings and x.c.
   There is no way to change it. Thus you cannot run two of them at
   once without something being trashed. Similarly, if you have
   commands in the makefile that always send output to the same file,
   you will not be able to make more than one target at once unless
   you change the file you use. You can, for instance, add a $$$$ to
   the end of the file name to tack on the process ID of the shell
   executing the command (each $$ expands to a single $, thus giving
   you the shell variable $$). Since only one shell is used for all
   the commands, you will get the same file name for each command in
   the script.

   The other problem comes from improperly-specified dependencies
   that worked in Make because of its sequential, depth-first way of
   examining them. While I do not want to go into depth on how PMake
   works (look in Chapter 4 if you are interested), I will warn you
   that files in two different levels of the dependency tree may be
   examined in a different order in PMake than they were in Make. For
   example, given the makefile:

 a               :

 b c b           : d

   PMake will examine the targets in the order c, d, b, a. If the
   makefile's author expected PMake to abort before making c if an
   error occurred while making b, or if b needed to exist before c
   was made, (s)he will be sorely disappointed. The dependencies are
   incomplete, since in both these cases, c would depend on b. So
   watch out.

   Another problem you may face is that, while PMake is set up to
   handle the output from multiple jobs in a graceful fashion, the
   same is not so for input. It has no way to regulate input to
   different jobs, so if you use the redirection from /dev/tty I
   mentioned earlier, you must be careful not to run two of the jobs
   at once.

     --------------------------------------------------------------

2.6. Writing and Debugging a Makefile

   Now you know most of what is in a Makefile, what do you do next?
   There are two choices: use one of the uncommonly-available
   makefile generators or write your own makefile (I leave out the
   third choice of ignoring PMake and doing everything by hand as
   being beyond the bounds of common sense).

   When faced with the writing of a makefile, it is usually best to
   start from first principles: just what are you trying to do? What
   do you want the makefile finally to produce? To begin with a
   somewhat traditional example, let's say you need to write a
   makefile to create a program, expr, that takes standard infix
   expressions and converts them to prefix form (for no readily
   apparent reason). You have got three source files, in C, that make
   up the program: main.c, parse.c, and output.c. Harking back to my
   pithy advice about dependency lines, you write the first line of
   the file:

 expr            : main.o parse.o output.o

   because you remember expr is made from .o files, not .c files.
   Similarly for the .o files you produce the lines:

 main.o          : main.c

 parse.o         : parse.c

 output.o        : output.c

 main.o parse.o output.o : defs.h

   Great. You have now got the dependencies specified. What you need
   now is commands. These commands, remember, must produce the target
   on the dependency line, usually by using the sources you have
   listed. You remember about local variables? Good, so it should
   come to you as no surprise when you write:

 expr            : main.o parse.o output.o
         cc -o $(.TARGET) $(.ALLSRC)

   Why use the variables? If your program grows to produce postfix
   expressions too (which, of course, requires a name change or two),
   it is one fewer place you have to change the file. You cannot do
   this for the object files, however, because they depend on their
   corresponding source files and defs.h, thus if you said:

 cc -c $(.ALLSRC)

   you will get (for main.o):

 cc -c main.c defs.h

   which is wrong. So you round out the makefile with these lines:

 main.o          : main.c
         cc -c main.c

 parse.o         : parse.c
         cc -c parse.c

 output.o        : output.c
         cc -c output.c

   The makefile is now complete and will, in fact, create the program
   you want it to without unnecessary compilations or excessive
   typing on your part. There are two things wrong with it, however
   (aside from it being altogether too long, something I will address
   in Chapter 3):

    1. The string main.o parse.o output.o is repeated twice,
       necessitating two changes when you add postfix (you were
       planning on that, were not you?). This is in direct violation
       of de Boor's First Rule of writing makefiles:

       Anything that needs to be written more than once should be
       placed in a variable. I cannot emphasize this enough as being
       very important to the maintenance of a makefile and its
       program.

    2. There is no way to alter the way compilations are performed
       short of editing the makefile and making the change in all
       places. This is evil and violates de Boor's Second Rule, which
       follows directly from the first:

       Any flags or programs used inside a makefile should be placed
       in a variable so they may be changed, temporarily or
       permanently, with the greatest ease.

   The makefile should more properly read:

 OBJS            = main.o parse.o output.o

 expr            : $(OBJS)
         $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)

 main.o          : main.c
         $(CC) $(CFLAGS) -c main.c

 parse.o         : parse.c
         $(CC) $(CFLAGS) -c parse.c

 output.o        : output.c
         $(CC) $(CFLAGS) -c output.c

 $(OBJS)         : defs.h

   Alternatively, if you like the idea of dynamic sources mentioned
   in Section 2.3.1, you could write it like this:

 OBJS            = main.o parse.o output.o

 expr            : $(OBJS)
         $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)

 $(OBJS)         : $(.PREFIX).c defs.h
         $(CC) $(CFLAGS) -c $(.PREFIX).c

   These two rules and examples lead to de Boor's First Corollary:
   Variables are your friends.

   Once you have written the makefile comes the sometimes-difficult
   task of making sure the darn thing works. Your most helpful tool
   to make sure the makefile is at least syntactically correct is the
   -n flag, which allows you to see if PMake will choke on the
   makefile. The second thing the -n flag lets you do is see what
   PMake would do without it actually doing it, thus you can make
   sure the right commands would be executed were you to give PMake
   its head.

   When you find your makefile is not behaving as you hoped, the
   first question that comes to mind (after "What time is it,
   anyway?") is "Why not?" In answering this, two flags will serve
   you well: -d m and "-p 2". The first causes PMake to tell you as
   it examines each target in the makefile and indicate why it is
   deciding whatever it is deciding. You can then use the information
   printed for other targets to see where you went wrong. The "-p 2"
   flag makes PMake print out its internal state when it is done,
   allowing you to see that you forgot to make that one chapter
   depend on that file of macros you just got a new version of. The
   output from "-p 2" is intended to resemble closely a real
   makefile, but with additional information provided and with
   variables expanded in those commands PMake actually printed or
   executed.

   Something to be especially careful about is circular dependencies.
   For example:

 a         : b

 b         : c d

 d         : a

   In this case, because of how PMake works, c is the only thing
   PMake will examine, because d and a will effectively fall off the
   edge of the universe, making it impossible to examine b (or them,
   for that matter). PMake will tell you (if run in its normal mode)
   all the targets involved in any cycle it looked at (i.e. if you
   have two cycles in the graph (naughty, naughty), but only try to
   make a target in one of them, PMake will only tell you about that
   one. You will have to try to make the other to find the second
   cycle). When run as Make, it will only print the first target in
   the cycle.

     --------------------------------------------------------------

2.7. Invoking PMake

   PMake comes with a wide variety of flags to choose from. They may
   appear in any order, interspersed with command-line variable
   assignments and targets to create. The flags are as follows:

   -d what

           This causes PMake to spew out debugging information that
           may prove useful to you. If you cannot figure out why
           PMake is doing what it is doing, you might try using this
           flag. The what parameter is a string of single characters
           that tell PMake what aspects you are interested in. Most
           of what I describe will make little sense to you, unless
           you have dealt with Make before. Just remember where this
           table is and come back to it as you read on. The
           characters and the information they produce are as
           follows:

           a Archive searching and caching.                           
           c Conditional evaluation.                                  
           d The searching and caching of directories.                
           j Various snippets of information related to the running   
             of the multiple shells. Not particularly interesting.    
           m The making of each target: what target is being          
             examined; when it was last modified; whether it is       
             out-of-date; etc.                                        
           p Makefile parsing.                                        
           r Remote execution.                                        
           s The application of suffix-transformation rules. (See     
             Chapter 3.)                                              
           t The maintenance of the list of targets.                  
           v Variable assignment.                                     

           Of these all, the m and s letters will be most useful to
           you. If the -d is the final argument or the argument from
           which it would get these key letters (see below for a note
           about which argument would be used) begins with a -, all
           of these debugging flags will be set, resulting in massive
           amounts of output.

   -f makefile

           Specify a makefile to read different from the standard
           makefiles (Makefile or makefile). If makefile is -, PMake
           uses the standard input. This is useful for making quick
           and dirty makefiles.

   -h

           Prints out a summary of the various flags PMake accepts.
           It can also be used to find out what level of concurrency
           was compiled into the version of PMake you are using (look
           at -J and -L) and various other information on how PMake
           was configured.

   -i

           If you give this flag, PMake will ignore non-zero status
           returned by any of its shells. It is like placing a -
           before all the commands in the makefile.

   -k

           This is similar to -i in that it allows PMake to continue
           when it sees an error, but unlike -i, where PMake
           continues blithely as if nothing went wrong, -k causes it
           to recognize the error and only continue work on those
           things that do not depend on the target, either directly
           or indirectly (through depending on something that depends
           on it), whose creation returned the error. The k is for
           "keep going".

   -l

           PMake has the ability to lock a directory against other
           people executing it in the same directory (by means of a
           file called LOCK.make that it creates and checks for in
           the directory). This is a Good Thing because two people
           doing the same thing in the same place can be disastrous
           for the final product (too many cooks and all that).
           Whether this locking is the default is up to your system
           administrator. If locking is on, -l will turn it off, and
           vice versa. Note that this locking will not prevent you
           from invoking PMake twice in the same place-if you own the
           lock file, PMake will warn you about it but continue to
           execute.

   -m directory

           Tells PMake another place to search for included makefiles
           via the <filename> style. Several -m options can be given
           to form a search path. If this construct is used the
           default system makefile search path is completely
           overridden.

   -n

           This flag tells PMake not to execute the commands needed
           to update the out-of-date targets in the makefile. Rather,
           PMake will simply print the commands it would have
           executed and exit. This is particularly useful for
           checking the correctness of a makefile. If PMake does not
           do what you expect it to, it is a good chance the makefile
           is wrong.

   -p number

           This causes PMake to print its input in a reasonable form,
           though not necessarily one that would make immediate sense
           to anyone but me. The number is a bitwise OR of 1 and 2,
           where 1 means it should print the input before doing any
           processing and 2 says it should print it after everything
           has been re-created. Thus -p 3 would print it twice-a-once
           before processing and once after (you might find the
           difference between the two interesting). This is mostly
           useful to me, but you may find it informative in some
           bizarre circumstances.

   -q

           If you give PMake this flag, it will not try to re-create
           anything. It will just see if anything is out-of-date and
           exit non-zero if so.

   -r

           When PMake starts up, it reads a default makefile that
           tells it what sort of system it is on and gives it some
           idea of what to do if you do not tell it anything. I will
           tell you about it in Chapter 3. If you give this flag,
           PMake will not read the default makefile.

   -s

           This causes PMake to not print commands before they are
           executed. It is the equivalent of putting an "@" before
           every command in the makefile.

   -t

           Rather than try to re-create a target, PMake will simply
           "touch" it so as to make it appear up-to-date. If the
           target did not exist before, it will when PMake finishes,
           but if the target did exist, it will appear to have been
           updated.

   -v

           Targets can still be created in parallel, however. This is
           the mode PMake will enter if it is invoked either as smake
           or vmake.

   -x

           This tells PMake it is OK to export jobs to other
           machines, if they are available. It is used when running
           in Make mode, as exporting in this mode tends to make
           things run slower than if the commands were just executed
           locally.

   -B

           Forces PMake to be as backwards-compatible with Make as
           possible while still being itself. This includes:

              * Executing one shell per shell command

              * Expanding anything that looks even vaguely like a
                variable, with the empty string replacing any
                variable PMake does not know.

              * Refusing to allow you to escape a # with a backslash.

              * Permitting undefined variables on dependency lines
                and conditionals (see below). Normally this causes
                PMake to abort.

   -C

           This nullifies any and all compatibility mode flags you
           may have given or implied up to the time the -C is
           encountered. It is useful mostly in a makefile that you
           wrote for PMake to avoid bad things happening when someone
           runs PMake as make or has things set in the environment
           that tell it to be compatible. -C is not placed in the
           PMAKE environment variable or the .MAKEFLAGS or MFLAGS
           global variables.

   -D variable

           Allows you to define a variable to have "1" as its value.
           The variable is a global variable, not a command-line
           variable. This is useful mostly for people who are used to
           the C compiler arguments and those using conditionals,
           which I will get into in Section 4.3.

   -I directory

           Tells PMake another place to search for included
           makefiles. Yet another thing to be explained in Chapter 3
           (Section 3.2, to be precise).

   -J number

           Gives the absolute maximum number of targets to create at
           once on both local and remote machines.

   -L number

           This specifies the maximum number of targets to create on
           the local machine at once. This may be 0, though you
           should be wary of doing this, as PMake may hang until a
           remote machine becomes available, if one is not available
           when it is started.

   -M

           This is the flag that provides absolute, complete, full
           compatibility with Make. It still allows you to use all
           but a few of the features of PMake, but it is
           non-parallel. This is the mode PMake enters if you call it
           make.

   -P

           When creating targets in parallel, several shells are
           executing at once, each wanting to write its own two
           cents'-worth to the screen. This output must be captured
           by PMake in some way in order to prevent the screen from
           being filled with garbage even more indecipherable than
           you usually see. PMake has two ways of doing this, one of
           which provides for much cleaner output and a clear
           separation between the output of different jobs, the other
           of which provides a more immediate response so one can
           tell what is really happening. The former is done by
           notifying you when the creation of a target starts,
           capturing the output and transferring it to the screen all
           at once when the job finishes. The latter is done by
           catching the output of the shell (and its children) and
           buffering it until an entire line is received, then
           printing that line preceded by an indication of which job
           produced the output. Since I prefer this second method, it
           is the one used by default. The first method will be used
           if you give the -P flag to PMake.

   -V

           As mentioned before, the -V flag tells PMake to use Make's
           style of expanding variables, substituting the empty
           string for any variable it does not know.

   -W

           There are several times when PMake will print a message at
           you that is only a warning, i.e. it can continue to work
           in spite of your having done something silly (such as
           forgotten a leading tab for a shell command). Sometimes
           you are well aware of silly things you have done and would
           like PMake to stop bothering you. This flag tells it to
           shut up about anything non-fatal.

   -X

           This flag causes PMake to not attempt to export any jobs
           to another machine.

   Several flags may follow a single -. Those flags that require
   arguments take them from successive parameters. For example:

 pmake -fDnI server.mk DEBUG /chip2/X/server/include

   will cause PMake to read server.mk as the input makefile, define
   the variable DEBUG as a global variable and look for included
   makefiles in the directory /chip2/X/server/include.

     --------------------------------------------------------------

2.8. Summary

   A makefile is made of four types of lines:

     * Dependency lines

     * Creation commands

     * Variable assignments

     * Comments, include statements and conditional directives

   A dependency line is a list of one or more targets, an operator
   (:, ::, or !), and a list of zero or more sources. Sources may
   contain wildcards and certain local variables.

   A creation command is a regular shell command preceded by a tab.
   In addition, if the first two characters after the tab (and other
   whitespace) are a combination of @ or -, PMake will cause the
   command to not be printed (if the character is @) or errors from
   it to be ignored (if -). A blank line, dependency line or variable
   assignment terminates a creation script. There may be only one
   creation script for each target with a : or ! operator.

   Variables are places to store text. They may be unconditionally
   assigned-to using the = operator, appended-to using the +=
   operator, conditionally (if the variable is undefined) assigned-to
   with the ?= operator, and assigned-to with variable expansion with
   the := operator. The output of a shell command may be assigned to
   a variable using the != operator. Variables may be expanded (their
   value inserted) by enclosing their name in parentheses or curly
   braces, preceded by a dollar sign. A dollar sign may be escaped
   with another dollar sign. Variables are not expanded if PMake does
   not know about them. There are seven local variables: .TARGET,
   .ALLSRC, .OODATE, .PREFIX, .IMPSRC, .ARCHIVE, and .MEMBER. Four of
   them (.TARGET, .PREFIX, .ARCHIVE, and .MEMBER) may be used to
   specify "dynamic sources". Variables are good. Know them. Love
   them. Live them.

   Debugging of makefiles is best accomplished using the -n, -d m,
   and -p 2 flags.

     --------------------------------------------------------------

               Chapter 3 Short-cuts and Other Nice Things

   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, 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 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).

     --------------------------------------------------------------

3.2. Including Other Makefiles

   Just as for programs, it is often useful to extract certain parts
   of a makefile into another file and just include it in other
   makefiles somehow. Many compilers allow you say something like:

 #include "defs.h"

   to include the contents of defs.h in the source file. PMake allows
   you to do the same thing for makefiles, with the added ability to
   use variables in the filenames. An include directive in a makefile
   looks either like this:

 #include <file>

   or this:

 #include "file"

   The difference between the two is where PMake searches for the
   file: the first way, PMake will look for the file only in the
   system makefile directory (or directories) (to find out what that
   directory is, give PMake the -h flag). The system makefile
   directory search path can be overridden via the -m option. For
   files in double-quotes, the search is more complex:

    1. The directory of the makefile that's including the file.

    2. The current directory (the one in which you invoked PMake).

    3. The directories given by you using -I flags, in the order in
       which you gave them.

    4. Directories given by .PATH dependency lines (see Chapter 4).

    5. The system makefile directory.

   in that order.

   You are free to use PMake variables in the filename - PMake will
   expand them before searching for the file. You must specify the
   searching method with either angle brackets or double-quotes
   outside of a variable expansion. I.e. the following:

 SYSTEM    = <command.mk>

 #include $(SYSTEM)

   will not work.

     --------------------------------------------------------------

3.3. Saving Commands

   There may come a time when you will want to save certain commands
   to be executed when everything else is done. For instance: you are
   making several different libraries at one time and you want to
   create the members in parallel. Problem is, ranlib is another one
   of those programs that can not be run more than once in the same
   directory at the same time (each one creates a file called
   __.SYMDEF into which it stuffs information for the linker to use.
   Two of them running at once will overwrite each other's file and
   the result will be garbage for both parties). You might want a way
   to save the ranlib commands til the end so they can be run one
   after the other, thus keeping them from trashing each other's
   file. PMake allows you to do this by inserting an ellipsis ("...")
   as a command between commands to be run at once and those to be
   run later.

   So for the ranlib case above, you might do this:

 lib1.a          : $(LIB1OBJS)
         rm -f $(.TARGET)
         ar cr $(.TARGET) $(.ALLSRC)
         ...
         ranlib $(.TARGET)

 lib2.a          : $(LIB2OBJS)
         rm -f $(.TARGET)
         ar cr $(.TARGET) $(.ALLSRC)
         ...
         ranlib $(.TARGET)

   This would save both

 ranlib $(.TARGET)

   commands until the end, when they would run one after the other
   (using the correct value for the .TARGET variable, of course).

   Commands saved in this manner are only executed if PMake manages
   to re-create everything without an error.

     --------------------------------------------------------------

3.4. Target Attributes

   PMake allows you to give attributes to targets by means of special
   sources. Like everything else PMake uses, these sources begin with
   a period and are made up of all upper-case letters. There are
   various reasons for using them, and I will try to give examples
   for most of them. Others you will have to find uses for yourself.
   Think of it as "an exercise for the reader". By placing one (or
   more) of these as a source on a dependency line, you are "marking
   the target(s) with that attribute". That is just the way I phrase
   it, so you know.

   Any attributes given as sources for a transformation rule are
   applied to the target of the transformation rule when the rule is
   applied.

   .DONTCARE   If a target is marked with this attribute and PMake    
               can not figure out how to create it, it will ignore    
               this fact and assume the file is not really needed or  
               actually exists and PMake just can not find it. This   
               may prove wrong, but the error will be noted later on, 
               not when PMake tries to create the target so marked.   
               This attribute also prevents PMake from attempting to  
               touch the target if it is given the -t flag.           
   .EXEC       This attribute causes its shell script to be executed  
               while having no effect on targets that depend on it.   
               This makes the target into a sort of subroutine. An    
               example. Say you have some LISP files that need to be  
               compiled and loaded into a LISP process. To do this,   
               you echo LISP commands into a file and execute a LISP  
               with this file as its input when everything is done.   
               Say also that you have to load other files from        
               another system before you can compile your files and   
               further, that you do not want to go through the        
               loading and dumping unless one of your files has       
               changed. Your makefile might look a little bit like    
               this (remember, this is an educational example, and do 
               not worry about the COMPILE rule, all will soon become 
               clear, grasshopper):                                   
                                                                      
               system          : init a.fasl b.fasl c.fasl            
                       for i in $(.ALLSRC);                           
                       do                                             
                               echo -n '(load "' >> input             
                               echo -n ${i} >> input                  
                               echo '")' >> input                     
                       done                                           
                       echo '(dump "$(.TARGET)")' >> input            
                       lisp < input                                   
                                                                      
               a.fasl          : a.l init COMPILE                     
               b.fasl          : b.l init COMPILE                     
               c.fasl          : c.l init COMPILE                     
               COMPILE         : .USE                                 
                       echo '(compile "$(.ALLSRC)")' >> input         
               init            : .EXEC                                
                       echo '(load-system)' > input                   
                                                                      
               .EXEC sources, do not appear in the local variables of 
               targets that depend on them (nor are they touched if   
               PMake is given the -t flag). Note that all the rules,  
               not just that for system, include init as a source.    
               This is because none of the other targets can be made  
               until init has been made, thus they depend on it.      
   .EXPORT     This is used to mark those targets whose creation      
               should be sent to another machine if at all possible.  
               This may be used by some exportation schemes if the    
               exportation is expensive. You should ask your system   
               administrator if it is necessary.                      
   .EXPORTSAME Tells the export system that the job should be         
               exported to a machine of the same architecture as the  
               current one. Certain operations (e.g. running text     
               through nroff) can be performed the same on any        
               architecture (CPU and operating system type), while    
               others (e.g. compiling a program with cc) must be      
               performed on a machine with the same architecture. Not 
               all export systems will support this attribute.        
   .IGNORE     Giving a target the .IGNORE attribute causes PMake to  
               ignore errors from any of the target's commands, as if 
               they all had - before them.                            
   .INVISIBLE  This allows you to specify one target as a source for  
               another without the one affecting the other's local    
               variables. Useful if, say, you have a makefile that    
               creates two programs, one of which is used to create   
               the other, so it must exist before the other is        
               created. You could say                                 
                                                                      
               prog1           : $(PROG1OBJS) prog2 MAKEINSTALL       
               prog2           : $(PROG2OBJS) .INVISIBLE MAKEINSTALL  
                                                                      
               where MAKEINSTALL is some complex .USE rule (see       
               below) that depends on the .ALLSRC variable containing 
               the right things. Without the .INVISIBLE attribute for 
               prog2, the MAKEINSTALL rule could not be applied. This 
               is not as useful as it should be, and the semantics    
               may change (or the whole thing go away) in the         
               not-too-distant future.                                
   .JOIN       This is another way to avoid performing some           
               operations in parallel while permitting everything     
               else to be done so. Specifically it forces the         
               target's shell script to be executed only if one or    
               more of the sources was out-of-date. In addition, the  
               target's name, in both its .TARGET variable and all    
               the local variables of any target that depends on it,  
               is replaced by the value of its .ALLSRC variable. As   
               an example, suppose you have a program that has four   
               libraries that compile in the same directory along     
               with, and at the same time as, the program. You again  
               have the problem with ranlib that I mentioned earlier, 
               only this time it is more severe: you can not just put 
               the ranlib off to the end since the program will need  
               those libraries before it can be re-created. You can   
               do something like this:                                
                                                                      
               program         : $(OBJS) libraries                    
                       cc -o $(.TARGET) $(.ALLSRC)                    
                                                                      
               libraries       : lib1.a lib2.a lib3.a lib4.a .JOIN    
                       ranlib $(.OODATE)                              
                                                                      
               In this case, PMake will re-create the $(OBJS) as      
               necessary, along with lib1.a, lib2.a, lib3.a and       
               lib4.a. It will then execute ranlib on any library     
               that was changed and set program's .ALLSRC variable to 
               contain what's in $(OBJS) followed by "lib1.a lib2.a   
               lib3.a lib4.a." In case you are wondering, it is       
               called .JOIN because it joins together different       
               threads of the "input graph" at the target marked with 
               the attribute. Another aspect of the .JOIN attribute   
               is it keeps the target from being created if the -t    
               flag was given.                                        
   .MAKE       The .MAKE attribute marks its target as being a        
               recursive invocation of PMake. This forces PMake to    
               execute the script associated with the target (if it   
               is out-of-date) even if you gave the -n or -t flag. By 
               doing this, you can start at the top of a system and   
               type                                                   
                                                                      
               pmake -n                                               
                                                                      
               and have it descend the directory tree (if your        
               makefiles are set up correctly), printing what it      
               would have executed if you had not included the -n     
               flag.                                                  
   .NOEXPORT   If possible, PMake will attempt to export the creation 
               of all targets to another machine (this depends on how 
               PMake was configured). Sometimes, the creation is so   
               simple, it is pointless to send it to another machine. 
               If you give the target the .NOEXPORT attribute, it     
               will be run loally, even if you have given PMake the   
               -L 0 flag.                                             
   .NOTMAIN    Normally, if you do not specify a target to make in    
               any other way, PMake will take the first target on the 
               first dependency line of a makefile as the target to   
               create. That target is known as the "Main Target" and  
               is labeled as such if you print the dependencies out   
               using the -p flag. Giving a target this attribute      
               tells PMake that the target is definitely not the Main 
               Target. This allows you to place targets in an         
               included makefile and have PMake create something else 
               by default.                                            
   .PRECIOUS   When PMake is interrupted (you type control-C at the   
               keyboard), it will attempt to clean up after itself by 
               removing any half-made targets. If a target has the    
               .PRECIOUS attribute, however, PMake will leave it      
               alone. An additional side effect of the :: operator is 
               to mark the targets as .PRECIOUS.                      
   .SILENT     Marking a target with this attribute keeps its         
               commands from being printed when they are executed,    
               just as if they had an @ in front of them.             
   .USE        By giving a target this attribute, you turn it into    
               PMake's equivalent of a macro. When the target is used 
               as a source for another target, the other target       
               acquires the commands, sources and attributes (except  
               .USE) of the source. If the target already has         
               commands, the .USE target's commands are added to the  
               end. If more than one .USE-marked source is given to a 
               target, the rules are applied sequentially. The        
               typical .USE rule (as I call them) will use the        
               sources of the target to which it is applied (as       
               stored in the .ALLSRC variable for the target) as its  
               "arguments," if you will. For example, you probably    
               noticed that the commands for creating lib1.a and      
               lib2.a in the example in section Section 3.3 were      
               exactly the same. You can use the .USE attribute to    
               eliminate the repetition, like so:                     
                                                                      
               lib1.a          : $(LIB1OBJS) MAKELIB                  
               lib2.a          : $(LIB2OBJS) MAKELIB                  
                                                                      
               MAKELIB         : .USE                                 
                       rm -f $(.TARGET)                               
                       ar cr $(.TARGET) $(.ALLSRC)                    
                       ...                                            
                       ranlib $(.TARGET)                              
                                                                      
               Several system makefiles (not to be confused with The  
               System Makefile) make use of these .USE rules to make  
               your life easier (they are in the default, system      
               makefile directory...take a look). Note that the .USE  
               rule source itself (MAKELIB) does not appear in any of 
               the targets's local variables. There is no limit to    
               the number of times I could use the MAKELIB rule. If   
               there were more libraries, I could continue with       
               lib3.a : $(LIB3OBJS) MAKELIB and so on and so forth.   

     --------------------------------------------------------------

3.5. Special Targets

   As there were in Make, so there are certain targets that have
   special meaning to PMake. When you use one on a dependency line,
   it is the only target that may appear on the left-hand-side of the
   operator. As for the attributes and variables, all the special
   targets begin with a period and consist of upper-case letters
   only. I will not describe them all in detail because some of them
   are rather complex and I will describe them in more detail than
   you will want in Chapter 4. The targets are as follows:

   .BEGIN      Any commands attached to this target are executed      
               before anything else is done. You can use it for any   
               initialization that needs doing.                       
   .DEFAULT    This is sort of a .USE rule for any target (that was   
               used only as a source) that PMake can not figure out   
               any other way to create. It is only "sort of" a .USE   
               rule because only the shell script attached to the     
               .DEFAULT target is used. The .IMPSRC variable of a     
               target that inherits .DEFAULT's commands is set to the 
               target's own name.                                     
   .END        This serves a function similar to .BEGIN, in that      
               commands attached to it are executed once everything   
               has been re-created (so long as no errors occurred).   
               It also serves the extra function of being a place on  
               which PMake can hang commands you put off to the end.  
               Thus the script for this target will be executed       
               before any of the commands you save with the "...".    
   .EXPORT     The sources for this target are passed to the          
               exportation system compiled into PMake. Some systems   
               will use these sources to configure themselves. You    
               should ask your system administrator about this.       
   .IGNORE     This target marks each of its sources with the .IGNORE 
               attribute. If you do not give it any sources, then it  
               is like giving the -i flag when you invoke PMake -     
               errors are ignored for all commands.                   
   .INCLUDES   The sources for this target are taken to be suffixes   
               that indicate a file that can be included in a program 
               source file. The suffix must have already been         
               declared with .SUFFIXES (see below). Any suffix so     
               marked will have the directories on its search path    
               (see .PATH, below) placed in the .INCLUDES variable,   
               each preceded by a -I flag. This variable can then be  
               used as an argument for the compiler in the normal     
               fashion. The .h suffix is already marked in this way   
               in the system makefile. E.g. if you have               
                                                                      
               .SUFFIXES       : .bitmap                              
               .PATH.bitmap    : /usr/local/X/lib/bitmaps             
               .INCLUDES       : .bitmap                              
                                                                      
               PMake will place -I/usr/local/X/lib/bitmaps in the     
               .INCLUDES variable and you can then say                
                                                                      
               cc $(.INCLUDES) -c xprogram.c                          
                                                                      
               (Note: the .INCLUDES variable is not actually filled   
               in until the entire makefile has been read.)           
   .INTERRUPT  When PMake is interrupted, it will execute the         
               commands in the script for this target, if it exists.  
   .LIBS       This does for libraries what .INCLUDES does for        
               include files, except the flag used is -L, as required 
               by those linkers that allow you to tell them where to  
               find libraries. The variable used is .LIBS. Be         
               forewarned that PMake may not have been compiled to do 
               this if the linker on your system does not accept the  
               -L flag, though the .LIBS variable will always be      
               defined once the makefile has been read.               
   .MAIN       If you did not give a target (or targets) to create    
               when you invoked PMake, it will take the sources of    
               this target as the targets to create.                  
   .MAKEFLAGS  This target provides a way for you to always specify   
               flags for PMake when the makefile is used. The flags   
               are just as they would be typed to the shell (except   
               you can not use shell variables unless they are in the 
               environment), though the -f and -r flags have no       
               effect.                                                
   .NULL       This allows you to specify what suffix PMake should    
               pretend a file has if, in fact, it has no known        
               suffix. Only one suffix may be so designated. The last 
               source on the dependency line is the suffix that is    
               used (you should, however, only give one suffix...).   
   .PATH       If you give sources for this target, PMake will take   
               them as directories in which to search for files it    
               cannot find in the current directory. If you give no   
               sources, it will clear out any directories added to    
               the search path before. Since the effects of this all  
               get very complex, we will leave it till Chapter 4 to   
               give you a complete explanation.                       
   .PATHsuffix This does a similar thing to .PATH, but it does it     
               only for files with the given suffix. The suffix must  
               have been defined already. Look at Search Paths        
               (Section 4.1) for more information.                    
   .PRECIOUS   Similar to .IGNORE, this gives the .PRECIOUS attribute 
               to each source on the dependency line, unless there    
               are no sources, in which case the .PRECIOUS attribute  
               is given to every target in the file.                  
   .RECURSIVE  This target applies the .MAKE attribute to all its     
               sources. It does nothing if you do not give it any     
               sources.                                               
   .SHELL      PMake is not constrained to only using the Bourne      
               shell to execute the commands you put in the makefile. 
               You can tell it some other shell to use with this      
               target. Check out "A Shell is a Shell is a Shell"      
               (Section 4.4) for more information.                    
   .SILENT     When you use .SILENT as a target, it applies the       
               .SILENT attribute to each of its sources. If there are 
               no sources on the dependency line, then it is as if    
               you gave PMake the -s flag and no commands will be     
               echoed.                                                
   .SUFFIXES   This is used to give new file suffixes for PMake to    
               handle. Each source is a suffix PMake should           
               recognize. If you give a .SUFFIXES dependency line     
               with no sources, PMake will forget about all the       
               suffixes it knew (this also nukes the null suffix).    
               For those targets that need to have suffixes defined,  
               this is how you do it.                                 

   In addition to these targets, a line of the form:

 attribute : sources

   applies the attribute to all the targets listed as sources.

     --------------------------------------------------------------

3.6. Modifying Variable Expansion

   Variables need not always be expanded verbatim. PMake defines
   several modifiers that may be applied to a variable's value before
   it is expanded. You apply a modifier by placing it after the
   variable name with a colon between the two, like so:

 ${VARIABLE:modifier}

   Each modifier is a single character followed by something specific
   to the modifier itself. You may apply as many modifiers as you
   want - each one is applied to the result of the previous and is
   separated from the previous by another colon.

   There are seven ways to modify a variable's expansion, most of
   which come from the C shell variable modification characters:

   Mpattern

           This is used to select only those words (a word is a
           series of characters that are neither spaces nor tabs)
           that match the given pattern. The pattern is a wildcard
           pattern like that used by the shell, where * means 0 or
           more characters of any sort; ? is any single character;
           [abcd] matches any single character that is either a, b, c
           or d (there may be any number of characters between the
           brackets); [0-9] matches any single character that is
           between 0 and 9 (i.e. any digit. This form may be freely
           mixed with the other bracket form), and \ is used to
           escape any of the characters *, ?, [ or :, leaving them as
           regular characters to match themselves in a word. For
           example, the system makefile <makedepend.mk> uses
           $(CFLAGS:M-[ID]*) to extract all the -I and -D flags that
           would be passed to the C compiler. This allows it to
           properly locate include files and generate the correct
           dependencies.

   Npattern

           This is identical to :M except it substitutes all words
           that do not match the given pattern.

   S/search-string/replacement-string/[g]

           Causes the first occurrence of search-string in the
           variable to be replaced by replacement-string, unless the
           g flag is given at the end, in which case all occurrences
           of the string are replaced. The substitution is performed
           on each word in the variable in turn. If search-string
           begins with a ^, the string must match starting at the
           beginning of the word. If search-string ends with a $, the
           string must match to the end of the word (these two may be
           combined to force an exact match). If a backslash precedes
           these two characters, however, they lose their special
           meaning. Variable expansion also occurs in the normal
           fashion inside both the search-string and the
           replacement-string, except that a backslash is used to
           prevent the expansion of a $, not another dollar sign, as
           is usual. Note that search-string is just a string, not a
           pattern, so none of the usual regularexpression/wildcard
           characters have any special meaning save ^ and $. In the
           replacement string, the & character is replaced by the
           search-string unless it is preceded by a backslash. You
           are allowed to use any character except colon or
           exclamation point to separate the two strings. This
           so-called delimiter character may be placed in either
           string by preceding it with a backslash.

   T

           Replaces each word in the variable expansion by its last
           component (its "tail"). For example, given:

 OBJS = ../lib/a.o b /usr/lib/libm.a
 TAILS = $(OBJS:T)

           the variable TAILS would expand to a.o b libm.a.

   H

           This is similar to :T, except that every word is replaced
           by everything but the tail (the "head"). Using the same
           definition of OBJS, the string $(OBJS:H) would expand to
           ../lib /usr/lib. Note that the final slash on the heads is
           removed and anything without a head is replaced by the
           empty string.

   E

           :E replaces each word by its suffix ("extension"). So
           $(OBJS:E) would give you .o .a.

   R

           This replaces each word by everything but the suffix (the
           "root" of the word). $(OBJS:R) expands to ../lib/a b
           /usr/lib/libm.

   In addition, the System V style of substitution is also supported.
   This looks like:

 $(VARIABLE:search-string=replacement)

   It must be the last modifier in the chain. The search is anchored
   at the end of each word, so only suffixes or whole words may be
   replaced.

     --------------------------------------------------------------

3.7. More Exercises

                              Exercise 3.1

   You have got a set programs, each of which is created from its own
   assembly-language source file (suffix .asm). Each program can be
   assembled into two versions, one with error-checking code
   assembled in and one without. You could assemble them into files
   with different suffixes (.eobj and .obj, for instance), but your
   linker only understands files that end in .obj. To top it all off,
   the final executables must have the suffix .exe. How can you still
   use transformation rules to make your life easier (Hint: assume
   the errorchecking versions have ec tacked onto their prefix)?

                              Exercise 3.2

   Assume, for a moment or two, you want to perform a sort of
   "indirection" by placing the name of a variable into another one,
   then you want to get the value of the first by expanding the
   second somehow. Unfortunately, PMake does not allow constructs
   like:

 $($(FOO))

   What do you do? Hint: no further variable expansion is performed
   after modifiers are applied, thus if you cause a $ to occur in the
   expansion, that is what will be in the result.

     --------------------------------------------------------------

                        Chapter 4 PMake for Gods

   This chapter is devoted to those facilities in PMake that allow
   you to do a great deal in a makefile with very little work, as
   well as do some things you could not do in Make without a great
   deal of work (and perhaps the use of other programs). The problem
   with these features, is they must be handled with care, or you
   will end up with a mess.

   Once more, I assume a greater familiarity with UNIX or Sprite than
   I did in the previous two chapters.

     --------------------------------------------------------------

4.1. Search Paths

   PMake supports the dispersal of files into multiple directories by
   allowing you to specify places to look for sources with .PATH
   targets in the makefile. The directories you give as sources for
   these targets make up a "search path". Only those files used
   exclusively as sources are actually sought on a search path, the
   assumption being that anything listed as a target in the makefile
   can be created by the makefile and thus should be in the current
   directory.

   There are two types of search paths in PMake: one is used for all
   types of files (including included makefiles) and is specified
   with a plain .PATH target (e.g. .PATH : RCS), while the other is
   specific to a certain type of file, as indicated by the file's
   suffix. A specific search path is indicated by immediately
   following the .PATH with the suffix of the file. For instance:

 .PATH.h         : /sprite/lib/include /sprite/att/lib/include

   would tell PMake to look in the directories /sprite/lib/include
   and /sprite/att/lib/include for any files whose suffix is .h.

   The current directory is always consulted first to see if a file
   exists. Only if it cannot be found there are the directories in
   the specific search path, followed by those in the general search
   path, consulted.

   A search path is also used when expanding wildcard characters. If
   the pattern has a recognizable suffix on it, the path for that
   suffix will be used for the expansion. Otherwise the default
   search path is employed.

   When a file is found in some directory other than the current one,
   all local variables that would have contained the target's name
   (.ALLSRC, and .IMPSRC) will instead contain the path to the file,
   as found by PMake. Thus if you have a file ../lib/mumble.c and a
   makefile like this:

 .PATH.c         : ../lib
 mumble          : mumble.c
         $(CC) -o $(.TARGET) $(.ALLSRC)

   the command executed to create mumble would be cc -o mumble
   ../lib/mumble.c. (as an aside, the command in this case is not
   strictly necessary, since it will be found using transformation
   rules if it is not given. This is because .out is the null suffix
   by default and a transformation exists from .c to .out. Just
   thought I would throw that in). If a file exists in two
   directories on the same search path, the file in the first
   directory on the path will be the one PMake uses. So if you have a
   large system spread over many directories, it would behoove you to
   follow a naming convention that avoids such conflicts.

   Something you should know about the way search paths are
   implemented is that each directory is read, and its contents
   cached, exactly once - when it is first encountered - so any
   changes to the directories while PMake is running will not be
   noted when searching for implicit sources, nor will they be found
   when PMake attempts to discover when the file was last modified,
   unless the file was created in the current directory. While people
   have suggested that PMake should read the directories each time,
   my experience suggests that the caching seldom causes problems. In
   addition, not caching the directories slows things down enormously
   because of PMake's attempts to apply transformation rules through
   non-existent files - the number of extra file-system searches is
   truly staggering, especially if many files without suffixes are
   used and the null suffix is not changed from .out.

     --------------------------------------------------------------

4.2. Archives and Libraries

   UNIX and Sprite allow you to merge files into an archive using the
   ar command. Further, if the files are relocatable object files,
   you can run ranlib on the archive and get yourself a library that
   you can link into any program you want. The main problem with
   archives is they double the space you need to store the archived
   files, since there is one copy in the archive and one copy out by
   itself. The problem with libraries is you usually think of them as
   -lm rather than /usr/lib/libm.a and the linker thinks they are
   out-of-date if you so much as look at them.

   PMake solves the problem with archives by allowing you to tell it
   to examine the files in the archives (so you can remove the
   individual files without having to regenerate them later). To
   handle the problem with libraries, PMake adds an additional way of
   deciding if a library is out-of-date: if the table of contents is
   older than the library, or is missing, the library is out-of-date.

   A library is any target that looks like -lname or that ends in a
   suffix that was marked as a library using the .LIBS target. .a is
   so marked in the system makefile. Members of an archive are
   specified as archive(member[member...]). Thus libdix.a(window.o)
   specifies the file window.o in the archive libdix.a. You may also
   use wildcards to specify the members of the archive. Just remember
   that most the wildcard characters will only find existing files. A
   file that is a member of an archive is treated specially. If the
   file does not exist, but it is in the archive, the modification
   time recorded in the archive is used for the file when determining
   if the file is out-of-date. When figuring out how to make an
   archived member target (not the file itself, but the file in the
   archive - the archive(member) target), special care is taken with
   the transformation rules, as follows:

     * archive(member) is made to depend on member.

     * The transformation from the member's suffix to the archive's
       suffix is applied to the archive(member) target.

     * The archive(member)'s .TARGET variable is set to the name of
       the member if member is actually a target, or the path to the
       member file if member is only a source.

     * The .ARCHIVE variable for the archive(member) target is set to
       the name of the archive.

     * The .MEMBER variable is set to the actual string inside the
       parentheses. In most cases, this will be the same as the
       .TARGET variable.

     * The archive(member)'s place in the local variables of the
       targets that depend on it is taken by the value of its .TARGET
       variable.

   Thus, a program library could be created with the following
   makefile:

 .o.a            :
         ...
         rm -f $(.TARGET:T)
 OBJS            = obj1.o obj2.o obj3.o
 libprog.a       : libprog.a($(OBJS))
         ar cru $(.TARGET) $(.OODATE)
         ranlib $(.TARGET)

   This will cause the three object files to be compiled (if the
   corresponding source files were modified after the object file or,
   if that does not exist, the archived object file), the out-of-date
   ones archived in libprog.a, a table of contents placed in the
   archive and the newly-archived object files to be removed.

   All this is used in the makelib.mk system makefile to create a
   single library with ease. This makefile looks like this:

 #
 # Rules for making libraries. The object files that make up the library
 # are removed once they are archived.
 #
 # To make several libraries in parallel, you should define the variable
 # "many_libraries". This will serialize the invocations of ranlib.
 #
 # To use, do something like this:
 #
 # OBJECTS = <files in the library>
 #
 # fish.a: fish.a($(OBJECTS)) MAKELIB
 #
 #

 #ifndef _MAKELIB_MK
 _MAKELIB_MK    =

 #include  <po.mk>

 .po.a .o.a     :
         ...
         rm -f $(.MEMBER)

 ARFLAGS        ?= crl

 #
 # Re-archive the out-of-date members and recreate the library's table of
 # contents using ranlib. If many_libraries is defined, put the ranlib
 # off til the end so many libraries can be made at once.
 #
 MAKELIB        : .USE .PRECIOUS
         ar $(ARFLAGS) $(.TARGET) $(.OODATE)
 #ifndef no_ranlib
 # ifdef many_libraries
         ...
 # endif many_libraries
         ranlib $(.TARGET)
 #endif no_ranlib

 #endif _MAKELIB_MK

     --------------------------------------------------------------

4.3. On the Condition...

   Like the C compiler before it, PMake allows you to configure the
   makefile, based on the current environment, using conditional
   statements. A conditional looks like this:

 #if boolean expression
 lines
 #elif another boolean expression
 more lines
 #else
 still more lines
 #endif

   They may be nested to a maximum depth of 30 and may occur anywhere
   (except in a comment, of course). The # must the very first
   character on the line.

   Each boolean expression is made up of terms that look like
   function calls, the standard C boolean operators &&, ||, and !,
   and the standard relational operators ==, !=, >, >=, <, and <=,
   with == and != being overloaded to allow string comparisons as
   well. && represents logical AND; || is logical OR and ! is logical
   NOT. The arithmetic and string operators take precedence over all
   three of these operators, while NOT takes precedence over AND,
   which takes precedence over OR. This precedence may be overridden
   with parentheses, and an expression may be parenthesized to your
   heart's content. Each term looks like a call on one of four
   functions:

   make    The syntax is make(target) where target is a target in the 
           makefile. This is true if the given target was specified   
           on the command line, or as the source for a .MAIN target   
           (note that the sources for .MAIN are only used if no       
           targets were given on the command line).                   
   defined The syntax is defined(variable) and is true if variable is 
           defined. Certain variables are defined in the system       
           makefile that identify the system on which PMake is being  
           run.                                                       
   exists  The syntax is exists(file) and is true if the file can be  
           found on the global search path (i.e. that defined by      
           .PATH targets, not by .PATHsuffix targets).                
   empty   This syntax is much like the others, except the string     
           inside the parentheses is of the same form as you would    
           put between parentheses when expanding a variable,         
           complete with modifiers and everything. The function       
           returns true if the resulting string is empty. An          
           undefined variable in this context will cause at the very  
           least a warning message about a malformed conditional, and 
           at the worst will cause the process to stop once it has    
           read the makefile. If you want to check for a variable     
           being defined or empty, use the expression: !defined(var)  
           || empty(var) as the definition of || will prevent the     
           empty() from being evaluated and causing an error, if the  
           variable is undefined. This can be used to see if a        
           variable contains a given word, for example: #if           
           !empty(var:Mword)                                          

   The arithmetic and string operators may only be used to test the
   value of a variable. The lefthand side must contain the variable
   expansion, while the righthand side contains either a string,
   enclosed in double-quotes, or a number. The standard C numeric
   conventions (except for specifying an octal number) apply to both
   sides. E.g.:

 #if $(OS) == 4.3

 #if $(MACHINE) == "sun3"

 #if $(LOAD_ADDR) > 0xc000

   are all valid conditionals. In addition, the numeric value of a
   variable can be tested as a boolean as follows:

 #if $(LOAD)

   would see if LOAD contains a non-zero value and:

 #if !$(LOAD)

   would test if LOAD contains a zero value.

   In addition to the bare #if, there are other forms that apply one
   of the first two functions to each term. They are as follows:

   ifdef   defined    
   ifndef  !defined   
   ifmake  make       
   ifnmake !make      

   There are also the "else if" forms: elif, elifdef, elifndef,
   elifmake, and elifnmake.

   For instance, if you wish to create two versions of a program, one
   of which is optimized (the production version) and the other of
   which is for debugging (has symbols for dbx), you have two
   choices: you can create two makefiles, one of which uses the -g
   flag for the compilation, while the other uses the -O flag, or you
   can use another target (call it debug) to create the debug
   version. The construct below will take care of this for you. I
   have also made it so defining the variable DEBUG (say with pmake
   -D DEBUG) will also cause the debug version to be made.

 #if defined(DEBUG) || make(debug)
 CFLAGS         += -g
 #else
 CFLAGS         += -O
 #endif

   There are, of course, problems with this approach. The most
   glaring annoyance is that if you want to go from making a debug
   version to making a production version, you have to remove all the
   object files, or you will get some optimized and some debug
   versions in the same program. Another annoyance is you have to be
   careful not to make two targets that "conflict" because of some
   conditionals in the makefile. For instance:

 #if make(print)
 FORMATTER = ditroff -Plaser_printer
 #endif
 #if make(draft)
 FORMATTER = nroff -Pdot_matrix_printer
 #endif

   would wreak havoc if you tried pmake draft print since you would
   use the same formatter for each target. As I said, this all gets
   somewhat complicated.

     --------------------------------------------------------------

4.4. A Shell is a Shell is a Shell

   In normal operation, the Bourne Shell (better known as sh) is used
   to execute the commands to re-create targets. PMake also allows
   you to specify a different shell for it to use when executing
   these commands. There are several things PMake must know about the
   shell you wish to use. These things are specified as the sources
   for the .SHELL target by keyword, as follows:

   path=path

           PMake needs to know where the shell actually resides, so
           it can execute it. If you specify this and nothing else,
           PMake will use the last component of the path and look in
           its table of the shells it knows and use the specification
           it finds, if any. Use this if you just want to use a
           different version of the Bourne or C Shell (yes, PMake
           knows how to use the C Shell too).

   name=name

           This is the name by which the shell is to be known. It is
           a single word and, if no other keywords are specified
           (other than path), it is the name by which PMake attempts
           to find a specification for it (as mentioned above). You
           can use this if you would just rather use the C Shell than
           the Bourne Shell (.SHELL: name=csh will do it).

   quiet=echo-off command

           As mentioned before, PMake actually controls whether
           commands are printed by introducing commands into the
           shell's input stream. This keyword, and the next two,
           control what those commands are. The quiet keyword is the
           command used to turn echoing off. Once it is turned off,
           echoing is expected to remain off until the echo-on
           command is given.

   echo=echo-on command

           The command PMake should give to turn echoing back on
           again.

   filter=printed echo-off command

           Many shells will echo the echo-off command when it is
           given. This keyword tells PMake in what format the shell
           actually prints the echo-off command. Wherever PMake sees
           this string in the shell's output, it will delete it and
           any following whitespace, up to and including the next
           newline. See the example at the end of this section for
           more details.

   echoFlag=flag to turn echoing on

           Unless a target has been marked .SILENT, PMake wants to
           start the shell running with echoing on. To do this, it
           passes this flag to the shell as one of its arguments. If
           either this or the next flag begins with a -, the flags
           will be passed to the shell as separate arguments.
           Otherwise, the two will be concatenated (if they are used
           at the same time, of course).

   errFlag=flag to turn error checking on

           Likewise, unless a target is marked .IGNORE, PMake wishes
           error-checking to be on from the very start. To this end,
           it will pass this flag to the shell as an argument. The
           same rules for an initial - apply as for the echoFlag.

   check=command to turn error checking on

           Just as for echo-control, error-control is achieved by
           inserting commands into the shell's input stream. This is
           the command to make the shell check for errors. It also
           serves another purpose if the shell does not have
           error-control as commands, but I will get into that in a
           minute. Again, once error checking has been turned on, it
           is expected to remain on until it is turned off again.

   ignore=commandto turn error checking off

           This is the command PMake uses to turn error checking off.
           It has another use if the shell does not do errorcontrol,
           but I will tell you about that...now.

   hasErrCtl=yes or no

           This takes a value that is either yes or no. Now you might
           think that the existence of the check and ignore keywords
           would be enough to tell PMake if the shell can do
           error-control, but you would be wrong. If hasErrCtl is
           yes, PMake uses the check and ignore commands in a
           straight-forward manner. If this is no, however, their use
           is rather different. In this case, the check command is
           used as a template, in which the string %s is replaced by
           the command that is about to be executed, to produce a
           command for the shell that will echo the command to be
           executed. The ignore command is also used as a template,
           again with %s replaced by the command to be executed, to
           produce a command that will execute the command to be
           executed and ignore any error it returns. When these
           strings are used as templates, you must provide newline(s)
           (\n) in the appropriate place(s).

   The strings that follow these keywords may be enclosed in single
   or double quotes (the quotes will be stripped off) and may contain
   the usual C backslash-characters (\n is newline, \r is return, \b
   is backspace, \' escapes a single-quote inside single-quotes, \"
   escapes a double-quote inside double-quotes). Now for an example.

   This is actually the contents of the <shx.mk> system makefile, and
   causes PMake to use the Bourne Shell in such a way that each
   command is printed as it is executed. That is, if more than one
   command is given on a line, each will be printed separately.
   Similarly, each time the body of a loop is executed, the commands
   within that loop will be printed, etc. The specification runs like
   this:

 #
 # This is a shell specification to have the Bourne shell echo
 # the commands just before executing them, rather than when it reads
 # them. Useful if you want to see how variables are being expanded, etc.
 #
 .SHELL    : path=/bin/sh \
      quiet="set -" \
      echo="set -x" \
      filter="+ set - " \
      echoFlag=x \
      errFlag=e \
      hasErrCtl=yes \
      check="set -e" \
      ignore="set +e"

   It tells PMake the following:

     * The shell is located in the file /bin/sh. It need not tell
       PMake that the name of the shell is sh as PMake can figure
       that out for itself (it is the last component of the path).

     * The command to stop echoing is set -.

     * The command to start echoing is set -x.

     * When the echo off command is executed, the shell will print +
       set - (The + comes from using the -x flag (rather than the -v
       flag PMake usually uses)). PMake will remove all occurrences
       of this string from the output, so you do not notice extra
       commands you did not put there.

     * The flag the Bourne Shell will take to start echoing in this
       way is the -x flag. The Bourne Shell will only take its flag
       arguments concatenated as its first argument, so neither this
       nor the errFlag specification begins with a -.

     * The flag to use to turn error-checking on from the start is
       -e.

     * The shell can turn error-checking on and off, and the commands
       to do so are set +e and set -e, respectively.

   I should note that this specification is for Bourne Shells that
   are not part of Berkeley UNIX, as shells from Berkeley do not do
   error control. You can get a similar effect, however, by changing
   the last three lines to be:

 hasErrCtl=no \
 check="echo \"+ %s\"\n" \
 ignore="sh -c '%s || exit 0\n"

   This will cause PMake to execute the two commands:

 echo "+ cmd"
 sh -c 'cmd || true'

   for each command for which errors are to be ignored. (In case you
   are wondering, the thing for ignore tells the shell to execute
   another shell without error checking on and always exit 0, since
   the || causes the exit 0 to be executed only if the first command
   exited non-zero, and if the first command exited zero, the shell
   will also exit zero, since that is the last command it executed).

     --------------------------------------------------------------

4.5. Compatibility

   There are three (well, 3 1/2) levels of backwards-compatibility
   built into PMake. Most makefiles will need none at all. Some may
   need a little bit of work to operate correctly when run in
   parallel. Each level encompasses the previous levels (e.g. -B (one
   shell per command) implies -V). The three levels are described in
   the following three sections.

     --------------------------------------------------------------

4.6. DEFCON 3 - Variable Expansion

   As noted before, PMake will not expand a variable unless it knows
   of a value for it. This can cause problems for makefiles that
   expect to leave variables undefined except in special
   circumstances (e.g. if more flags need to be passed to the C
   compiler or the output from a text processor should be sent to a
   different printer). If the variables are enclosed in curly braces
   (${PRINTER}), the shell will let them pass. If they are enclosed
   in parentheses, however, the shell will declare a syntax error and
   the make will come to a grinding halt.

   You have two choices: change the makefile to define the variables
   (their values can be overridden on the command line, since that is
   where they would have been set if you used Make, anyway) or always
   give the -V flag (this can be done with the .MAKEFLAGS target, if
   you want).

     --------------------------------------------------------------

4.7. DEFCON 2 - The Number of the Beast

   Then there are the makefiles that expect certain commands, such as
   changing to a different directory, to not affect other commands in
   a target's creation script. You can solve this is either by going
   back to executing one shell per command (which is what the -B flag
   forces PMake to do), which slows the process down a good bit and
   requires you to use semicolons and escaped newlines for shell
   constructs, or by changing the makefile to execute the offending
   command(s) in a subshell (by placing the line inside parentheses),
   like so:

 install :: .MAKE
       (cd src; $(.PMAKE) install)
       (cd lib; $(.PMAKE) install)
       (cd man; $(.PMAKE) install)

   This will always execute the three makes (even if the -n flag was
   given) because of the combination of the :: operator and the .MAKE
   attribute. Each command will change to the proper directory to
   perform the install, leaving the main shell in the directory in
   which it started.

     --------------------------------------------------------------

4.8. DEFCON 1 - Imitation is the Not the Highest Form of Flattery

   The final category of makefile is the one where every command
   requires input, the dependencies are incompletely specified, or
   you simply cannot create more than one target at a time, as
   mentioned earlier. In addition, you may not have the time or
   desire to upgrade the makefile to run smoothly with PMake. If you
   are the conservative sort, this is the compatibility mode for you.
   It is entered either by giving PMake the -M flag (for Make), or by
   executing PMake as make. In either case, PMake performs things
   exactly like Make (while still supporting most of the nice new
   features PMake provides). This includes:

     * No parallel execution.

     * Targets are made in the exact order specified by the makefile.
       The sources for each target are made in strict left-to-right
       order, etc.

     * A single Bourne shell is used to execute each command, thus
       the shell's $$ variable is useless, changing directories does
       not work across command lines, etc.

     * If no special characters exist in a command line, PMake will
       break the command into words itself and execute the command
       directly, without executing a shell first. The characters that
       cause PMake to execute a shell are: #, =, |, ^, (, ), {, }, ;,
       &, >, <, *, ?, [, ], :, $, `, and \. You should notice that
       these are all the characters that are given special meaning by
       the shell (except ' and , which PMake deals with all by its
       lonesome).

     * The use of the null suffix is turned off.

     --------------------------------------------------------------

4.9. The Way Things Work

   When PMake reads the makefile, it parses sources and targets into
   nodes in a graph. The graph is directed only in the sense that
   PMake knows which way is up. Each node contains not only links to
   all its parents and children (the nodes that depend on it and
   those on which it depends, respectively), but also a count of the
   number of its children that have already been processed.

   The most important thing to know about how PMake uses this graph
   is that the traversal is breadth-first and occurs in two passes.

   After PMake has parsed the makefile, it begins with the nodes the
   user has told it to make (either on the command line, or via a
   .MAIN target, or by the target being the first in the file not
   labeled with the .NOTMAIN attribute) placed in a queue. It
   continues to take the node off the front of the queue, mark it as
   something that needs to be made, pass the node to Suff_FindDeps
   (mentioned earlier) to find any implicit sources for the node, and
   place all the node's children that have yet to be marked at the
   end of the queue. If any of the children is a .USE rule, its
   attributes are applied to the parent, then its commands are
   appended to the parent's list of commands and its children are
   linked to its parent. The parent's unmade children counter is then
   decremented (since the .USE node has been processed). You will
   note that this allows a .USE node to have children that are .USE
   nodes and the rules will be applied in sequence. If the node has
   no children, it is placed at the end of another queue to be
   examined in the second pass. This process continues until the
   first queue is empty.

   At this point, all the leaves of the graph are in the examination
   queue. PMake removes the node at the head of the queue and sees if
   it is out-of-date. If it is, it is passed to a function that will
   execute the commands for the node asynchronously. When the
   commands have completed, all the node's parents have their unmade
   children counter decremented and, if the counter is then 0, they
   are placed on the examination queue. Likewise, if the node is
   up-to-date. Only those parents that were marked on the downward
   pass are processed in this way. Thus PMake traverses the graph
   back up to the nodes the user instructed it to create. When the
   examination queue is empty and no shells are running to create a
   target, PMake is finished.

   Once all targets have been processed, PMake executes the commands
   attached to the .END target, either explicitly or through the use
   of an ellipsis in a shell script. If there were no errors during
   the entire process but there are still some targets unmade (PMake
   keeps a running count of how many targets are left to be made),
   there is a cycle in the graph. PMake does a depth-first traversal
   of the graph to find all the targets that were not made and prints
   them out one by one.

     --------------------------------------------------------------

                     Chapter 5 Answers to Exercises

                              Exercise 3.1

   This is something of a trick question, for which I apologize. The
   trick comes from the UNIX definition of a suffix, which PMake does
   not necessarily share. You will have noticed that all the suffixes
   used in this tutorial (and in UNIX in general) begin with a period
   (.ms, .c, etc.). Now, PMake's idea of a suffix is more like
   English's: it is the characters at the end of a word. With this in
   mind, one possible solution to this problem goes as follows:

 .SUFFIXES       : ec.exe .exe ec.obj .obj .asm
 ec.objec.exe .obj.exe :
         link -o $(.TARGET) $(.IMPSRC)
 .asmec.obj      :
         asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
 .asm.obj        :
         asm -o $(.TARGET) $(.IMPSRC)

                             Excercise 3.2

   The trick to this one lies in the := variable-assignment operator
   and the :S variable-expansion modifier. Basically what you want is
   to take the pointer variable, so to speak, and transform it into
   an invocation of the variable at which it points. You might try
   something like:

 $(PTR:S/^/\$(/:S/$/))

   which places $( at the front of the variable name and ) at the
   end, thus transforming VAR, for example, into $(VAR), which is
   just what we want. Unfortunately (as you know if you have tried
   it), since, as it says in the hint, PMake does no further
   substitution on the result of a modified expansion, that is all
   you get. The solution is to make use of := to place that string
   into yet another variable, then invoke the other variable
   directly:

 *PTR            := $(PTR:S/^/\$(/:S/$/)/)

   You can then use $(*PTR) to your heart's content.

                           Glossary of Jargon

   attribute

           A property given to a target that causes PMake to treat it
           differently.

   command script

           The lines immediately following a dependency line that
           specify commands to execute to create each of the targets
           on the dependency line. Each line in the command script
           must begin with a tab.

   command-line variable

           A variable defined in an argument when PMake is first
           executed. Overrides all assignments to the same variable
           name in the makefile.

   conditional

           A construct much like that used in C that allows a
           makefile to be configured on the fly based on the local
           environment, or on what is being made by that invocation
           of PMake.

   creation script

           Commands used to create a target.

   dependency

           The relationship between a source and a target. This comes
           in three flavors, as indicated by the operator between the
           target and the source. : gives a straight time-wise
           dependency (if the target is older than the source, the
           target is out-of-date), while ! provides simply an
           ordering and always considers the target out-of-date. ::
           is much like :, save it creates multiple instances of a
           target each of which depends on its own list of sources.

   dynamic source

           This refers to a source that has a local variable
           invocation in it. It allows a single dependency line to
           specify a different source for each target on the line.

   global variable

           Any variable defined in a makefile. Takes precedence over
           variables defined in the environment, but not over
           command-line or local variables.

   input graph

           What PMake constructs from a makefile. Consists of nodes
           made of the targets in the makefile, and the links between
           them (the dependencies). The links are directed (from
           source to target) and there may not be any cycles (loops)
           in the graph.

   local variable

           A variable defined by PMake visible only in a target's
           shell script. There are seven local variables, not all of
           which are defined for every target: .TARGET, .ALLSRC,
           .OODATE, .PREFIX, .IMPSRC, .ARCHIVE, and .MEMBER. .TARGET,
           .PREFIX, .ARCHIVE, and .MEMBER may be used on dependency
           lines to create "dynamic sources".

   makefile

           A file that describes how a system is built. If you do not
           know what it is after reading this tutorial...

   modifier

           A letter, following a colon, used to alter how a variable
           is expanded. It has no effect on the variable itself.

   operator

           What separates a source from a target (on a dependency
           line) and specifies the relationship between the two.
           There are three: :, ::, and !.

   search path

           A list of directories in which a file should be sought.
           PMake's view of the contents of directories in a search
           path does not change once the makefile has been read. A
           file is sought on a search path only if it is exclusively
           a source.

   shell

           A program to which commands are passed in order to create
           targets.

   source

           Anything to the right of an operator on a dependency line.
           Targets on the dependency line are usually created from
           the sources.

   special target

           A target that causes PMake to do special things when it is
           encountered.

   suffix

           The tail end of a file name. Usually begins with a period,
           like .c or .ms.

   target

           A word to the left of the operator on a dependency line.
           More generally, any file that PMake might create. A file
           may be (and often is) both a target and a source (what it
           is depends on how PMake is looking at it at the time -
           sort of like the wave/particle duality of light, you
           know).

   transformation rule

           A special construct in a makefile that specifies how to
           create a file of one type from a file of another, as
           indicated by their suffixes.

   variable expansion

           The process of substituting the value of a variable for a
           reference to it. Expansion may be altered by means of
           modifiers.

   variable

           A place in which to store text that may be retrieved
           later. Also used to define the local environment.
           Conditionals exist that test whether a variable is defined
           or not.

  Notes

   [1] This is also somewhat cleaner, I think, than the dynamic       
       source solution presented in Section 2.6.                      

     --------------------------------------------------------------
