





   


                       PMake -- A Tutorial


                           Adam de Boor
                        Berkeley Softworks
                   2150 Shattuck Ave, Penthouse
                        Berkeley, CA 94704
                         adam@bsw.uu.net
                        ...!uunet!bsw!adam



   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 modi-
   fied). PMake takes these dependencies, which you must  spec-
   ify,  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.  Throughout  the
   text,  whenever  something is mentioned that is an important
   difference between PMake and Make (i.e. something that  will
   cause  a  makefile  to  fail if you don't do something about
   it), or is simply important, it will be flagged with a  lit-
   tle sign in the left margin, like this:
   This  tutorial  is  divided  into three main sections corre-
NOTsponding to basic, intermediate and advanced PMake usage. If
   you already know Make well, you will only need to skim chap-
   ter 2 (there are some aspects of PMake that I consider basic
   to  its use that didn't 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. Chapter 5 has defi-
   nitions for the jargon I use and chapter 6 contains possible
   solutions to the problems presented throughout the tutorial.

   -----------
   Permission  to  use,  copy, modify, and distribute
   this software and its documentation for  any  pur-
   pose  and  without fee is hereby granted, provided
   that the above copyright  notice  appears  in  all
   copies.   The  University  of California, Berkeley
   Softworks, and Adam de Boor  make  no  representa-
   tions  about  the suitability of this software for
   any purpose.   It  is  provided  "as  is"  without
   express or implied warranty.









   PSD:12-2                                 PMake -- A Tutorial


   2.  The Basics of PMake
   PMake takes as input a file that tells a) which files depend
   on  which other files to be complete and b) 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  don't  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:
        o File dependency specifications
        o Creation commands
        o Variable assignments
        o Comments, include statements and  conditional  direc-
          tives
   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  depen-
   dencies  and  are  specified by means of dependency lines in
   the makefile.
   On a dependency line, there are targets and  sources,  sepa-
   rated  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 deter-
   mined  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  fol-
   lows:









   PMake -- A Tutorial                                 PSD:12-3


   :    If  a colon is used, a target on the line is considered
        to be ``out-of-date'' (and in need of creation) if
        o any of the sources has been  modified  more  recently
          than the target, or
        o the target doesn't 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 nec-
        essary.
   ::   If a double-colon is used, a target is out-of-date if:
        o any of the sources has been  modified  more  recently
          than the target, or
        o the target doesn't exist, or
        o 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  some-
        thing else to the targets, but I'll go into that in the
        next section (``Shell Commands'').
   Enough words, now for an example. Take that C program I men-
   tioned  earlier.  Say  there are three C files (a.c, b.c and
   c.c) each of which includes the file defs.h.  The  dependen-
   cies 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 don't.
   The  reason is quite simple: program cannot be made by link-
   ing together .c files -- it must  be  made  from  .o  files.
   Likewise,  if  you change defs.h, it isn't the .c files that
   need to be re-created, it's 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. E.g.  a.o depends on both defs.h and a.c.
   The  order of the dependency lines in the makefile is impor-
NOTtant: the first target on the first dependency line  in  the
   makefile  will  be  the  one that gets made if you don't say
   otherwise.  That's 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









   PSD:12-4                                 PMake -- A Tutorial


   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 ele-
        ment. 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  con-
        tained  in the list. E.g.  [A-Za-z] will match all let-
        ters, while [0123456789] will match all numbers.

   2.2.  Shell Commands
   ``Isn't 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's necessary to update the target file
   (PMake doesn't actually check to see if the target was  cre-
   ated. It just assumes it's 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
NOTtarget  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:











   PMake -- A Tutorial                                 PSD:12-5


        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 depen-
   dency line is out-of-date, not the sources.  In  this  exam-
   ple,  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  dif-
   ferent things.
   In  most  cases,  shell  commands are printed before they're
   actually executed. This is to keep you  informed  of  what's
   going  on.  If an `@' appears, however, this echoing is sup-
   pressed. 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
   (``@echo Linking index'') to prevent the command from  being
   printed.
   The  other  special character is the `-'. In case you didn't
   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's 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  (``-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.









   PSD:12-6                                 PMake -- A Tutorial


   Because all the commands are given to a single shell to exe-
   cute, such  things  as  setting  shell  variables,  changing
NOTdirectories, 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  com-
   mand  being  executed by a single shell, PMake has a -B flag
   (it stands for backwards-compatible) that forces  each  com-
   mand  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
NOTshell's) input stream.  This means that any  commands,  such
   as  ci  that  need to get input from the terminal won't work
   right -- they'll get the shell's input, something they prob-
   ably won't 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 termi-
   nal.  If  you can't 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.  Vari-
   ables  in  PMake  are  used much like variables in the shell
   and, by tradition, consist of all  upper-case  letters  (you
   don't  have  to use all upper-case letters.  In fact there's
   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  isn't  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











   PMake -- A Tutorial                                 PSD:12-7


   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 new-
   lines  (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  imple-
   mentation 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 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.
   Unlike Make, PMake will not  expand  a  variable  unless  it
NOTknows  the  variable  exists.  E.g.  if you have a ${i} in a
   shell command and you have not assigned a value to the vari-
   able 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. I.e. if the outer variable is used on a dependency









   PSD:12-8                                 PMake -- A Tutorial


   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 substi-
   tuted 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):
        o Local variables.
        o Command-line variables.
        o Global variables.
        o Environment variables.
   The classification of variables doesn't 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/com-
             pat/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'', ``.AR-
   CHIVE'' and ``.MEMBER''. The variables in  the  sources  are
   expanded  once  for each target on the dependency line, pro-
   viding 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.











   PMake -- A Tutorial                                 PSD:12-9


   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  assign-
   ments  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 effi-
   ciency that I won't 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  make-
   file.   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  fol-
   lows:
        .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 cov-
   ered 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.











   PSD:12-10                                PMake -- A Tutorial


   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  com-
   mand  line,  are  placed  in  this  environment variable and
   exported to each shell PMake executes. Thus recursive  invo-
   cations of PMake automatically receive the same flags as the
   top-most one.
   Using all these variables, you can compress the sample make-
   file 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 back-
   slash in front of it.  PMake will compress the  two  into  a
   single  `#'  (Note: this isn't true if PMake is operating in
   full-compatibility mode).

   2.5.  Parallelism
   PMake was specifically designed to re-create several targets
NOTat  once, when possible. You do not have to do anything spe-
   cial 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









   PMake -- A Tutorial                                PSD:12-11


   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'll get the same file name for
   each command in the script.
   The  other problem comes from improperly-specified dependen-
   cies that worked in Make because of its  sequential,  depth-
   first  way  of examining them. While I don't want to go into
   depth on how PMake works (look in chapter 4 if you're inter-
   ested),  I  will warn you that files in two different ``lev-
   els'' 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  fash-
   ion,  the  same is not so for input.  It has no way to regu-
   late 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's in a makefile, what  do  you  do
   next?  There are two choices: (1) use one of the uncommonly-
   available makefile generators or (2) 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  try-
   ing 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've 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









   PSD:12-12                                PMake -- A Tutorial


   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've 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've 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'd 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'll 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, weren't 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.










   PMake -- A Tutorial                                PSD:12-13


   2)   There  is no way to alter the way compilations are per-
        formed 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  men-
   tioned 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 Corol-
   lary:
        Variables are your friends.
   Once you've written the makefile comes the  sometimes-diffi-
   cult  task  of  making  sure the darn thing works. Your most
   helpful tool to make sure the makefile is at least syntacti-
   cally  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 actu-
   ally 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 isn't 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 out-
   put from ``-p 2'' is intended to  resemble  closely  a  real









   PSD:12-14                                PMake -- A Tutorial


   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 depen-
   dencies.  E.g.

        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'll 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 can't figure out
        why PMake is doing what it's doing, you might try using
        this  flag.  The  what  parameter is a string of single
        characters that tell PMake what aspects you are  inter-
        ested  in.  Most  of  what  I describe will make little
        sense to you, unless you've  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









   PMake -- A Tutorial                                PSD:12-15


        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 infor-
        mation on how PMake was configured.
   -i   If you give this flag, PMake will ignore non-zero  sta-
        tus  returned by any of its shells. It's like placing a
        `-' before all the commands in the makefile.
   -k   This is similar to -i in that it allows PMake  to  con-
        tinue 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 don't 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  make-
        files  via  the <...> 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.  To be explained in chapter 3, section 3.2.
   -n   This  flag  tells  PMake  not  to  execute the commands
        needed to update the out-of-date targets in  the  make-
        file.  Rather,  PMake will simply print the commands it
        would have executed and exit. This is particularly use-
        ful  for  checking  the  correctness  of a makefile. If
        PMake doesn't do what you expect it  to,  it's  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  imme-
        diate  sense to anyone but me. The number is a bitwise-









   PSD:12-16                                PMake -- A Tutorial


        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--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-cre-
        ate  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's on and gives it some
        idea of what to do if you don't tell it anything.  I'll
        tell  you about it in chapter 3. If you give this flag,
        PMake won't read the default makefile.
   -s   This causes PMake to not print commands before  they're
        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  sim-
        ply ``touch'' it so as to make it appear up-to-date. If
        the target didn't exist before, it will when PMake fin-
        ishes,  but  if the target did exist, it will appear to
        have been updated.
   -v   This is a mixed-compatibility flag  intended  to  mimic
        the  System V version of Make. It is the same as giving
        -B, and -V as well as turning  off  directory  locking.
        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's  ok  to  export  jobs to other
        machines, if they're 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  exe-
        cuted locally.
   -B   Forces PMake to be as backwards-compatible with Make as
        possible while still being itself.  This includes:
        o Executing one shell per shell command
        o Expanding anything that looks  even  vaguely  like  a
          variable,  with  the empty string replacing any vari-
          able PMake doesn't know.
        o Refusing to allow you to escape a `#'  with  a  back-
          slash.
        o 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 .MAKE-
        FLAGS or MFLAGS global variables.










   PMake -- A Tutorial                                PSD:12-17


   -D variable
        Allows you to define a variable to have  ``1''  as  its
        value.   The  variable is a global variable, not a com-
        mand-line variable. This is useful  mostly  for  people
        who  are  used  to  the  C compiler arguments and those
        using conditionals, which I'll get into in section 4.3
   -I directory
        Tells PMake another place to search for included  make-
        files.  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  avail-
        able 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-par-
        allel. 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
        cent's-worth  to  the screen.  This output must be cap-
        tured by PMake in some way  in  order  to  prevent  the
        screen from being filled with garbage even more indeci-
        pherable than you usually see. PMake has  two  ways  of
        doing this, one of which provides for much cleaner out-
        put and a clear separation between the output  of  dif-
        ferent jobs, the other of which provides a more immedi-
        ate 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  transfer-
        ring  it  to  the  screen all at once when the job fin-
        ishes. 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  pre-
        ceded  by  an indication of which job produced the out-
        put. 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 doesn't 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). Some-
        times 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.









   PSD:12-18                                PMake -- A Tutorial


   -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. E.g.

        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:
        o Dependency lines
        o Creation commands
        o Variable assignments
        o Comments, include statements and  conditional  direc-
          tives
   A dependency line is a list of one or more targets, an oper-
   ator (`:', `::', 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 char-
   acter is `@') or errors from it to be ignored (if  `-').   A
   blank  line,  dependency  line or variable assignment termi-
   nates 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 uncondition-
   ally assigned-to using the `=' operator,  appended-to  using
   the  `+='  operator, conditionally (if the variable is unde-
   fined) 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  doesn't  know  about  them. There are seven local
   variables: .TARGET, .ALLSRC, .OODATE, .PREFIX, .IMPSRC, .AR-
   CHIVE,  and  .MEMBER.   Four of them (.TARGET, .PREFIX, .AR-
   CHIVE,  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.

   2.9.  Exercises
                               TBA












   PMake -- A Tutorial                                PSD:12-19


   3.  Short-cuts and Other Nice Things
   Based  on what I've told you so far, you may have gotten the
   impression that PMake is just a way of storing away commands
   and making sure you don't forget to compile something. Good.
   That's just what it is.  However, the  ways  I've  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's what you're using) than I did in chapter  2,  just
   so you're 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(R) has developed, naming con-
   ventions,  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  com-
   pile  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:










   PSD:12-20                                PMake -- A Tutorial


        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 transforma-
             tion 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 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 lines1

        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's not mentioned anywhere in the new make-
   file. This is because it isn't needed: one of the effects of
   applying a transformation rule is the target comes to depend
   on the implied source. That's why it's  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's
   -----------
     1 This  is  also somewhat cleaner, I think, than
   the dynamic source solution presented in 2.6









   PMake -- A Tutorial                                PSD:12-21


   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 trans-
   formation.  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.  (Previ-
   ously-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 depen-
   dency  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.










   PSD:12-22                                PMake -- A Tutorial


   The search for a transformation starts from  the  suffix  of
   the target and continues through all the defined transforma-
   tions, 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, fig-
   ures 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't find it, so it looks for jive.l and, lo and
   behold, there it is.  At this point, it has defined a trans-
   formation path as follows: .l  ->  .c  ->  .o  ->  .out  and
   applies  the transformation rules accordingly. For complete-
   ness, 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:












   PMake -- A Tutorial                                PSD:12-23


        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  accord-
   ingly.   The  suffix  it chooses is the source for the .NULL
   target mentioned later. In the system makefile, .out is cho-
   sen  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 some-
   thing 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









   PSD:12-24                                PMake -- A Tutorial


   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 fol-
   lowing

        SYSTEM    = <command.mk>

        #include $(SYSTEM)

   won't 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're  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't 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  run-
   ning 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:





















   PMake -- A Tutorial                                PSD:12-25


        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'll
   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's 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't figure out how to create it, it will
               ignore  this  fact  and  assume  the  file isn't
               really needed or actually exists and PMake  just
               can't  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's  done.
               Say  also that you have to load other files from









   PSD:12-26                                PMake -- A Tutorial


               another system before you can compile your files
               and  further,  that you don't 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 don't 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, don't appear in the  local  vari-
               ables  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 sys-
               tem, 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  cre-
               ation  should  be  sent to another machine if at
               all possible. This may be used by some  exporta-
               tion  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 sys-
               tem type), while others (e.g. compiling  a  pro-
               gram  with  cc)  must  be performed on a machine
               with the same architecture. Not all export  sys-
               tems 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









   PMake -- A Tutorial                                PSD:12-27


               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 con-
               taining the right things. Without the .INVISIBLE
               attribute  for  prog2,  the   MAKEINSTALL   rule
               couldn't 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 every-
               thing 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 tar-
               get 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  ear-
               lier, only this time it's more severe: you can't
               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 .ALL-
               SRC variable to contain what's in  $(OBJS)  fol-
               lowed  by  ``lib1.a  lib2.a lib3.a lib4.a.''  In
               case you're wondering, it's 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 cre-
               ated 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









   PSD:12-28                                PMake -- A Tutorial


               target (if it's 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  hadn't  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 tar-
               get the .NOEXPORT  attribute,  it  will  be  run
               locally,  even  if  you've  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 tar-
               get 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 some-
               thing 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 tar-
               gets as .PRECIOUS.
   .SILENT     Marking a target with this attribute  keeps  its
               commands  from  being  printed when they're exe-
               cuted, 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  tar-
               get's  commands  are  added  to the end. If more
               than one .USE-marked source is given to  a  tar-
               get, 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 tar-
               get) as its ``arguments,''  if  you  will.   For
               example,  you probably noticed that the commands









   PMake -- A Tutorial                                PSD:12-29


               for creating lib1.a and lib2.a in the example in
               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're in
               the default, system makefile directory...take  a
               look).   Note  that  the .USE rule source itself
               (MAKELIB) does not appear in  any  of  the  tar-
               gets'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 con-
               tinue 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  depen-
   dency  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 won't  describe  them
   all  in  detail  because some of them are rather complex and
   I'll describe them in more detail than you'll want in  chap-
   ter 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't figure
             out any other way to create. It's only ``sort of''
             a .USE rule because only the shell script attached
             to  the .DEFAULT target is used. The .IMPSRC vari-
             able 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  every-
             thing  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  tar-
             get  will  be  executed before any of the commands
             you save with the ``...''.









   PSD:12-30                                PMake -- A Tutorial


   .EXPORT   The sources for this  target  are  passed  to  the
             exportation  system compiled into PMake. Some sys-
             tems will use these  sources  to  configure  them-
             selves.  You  should ask your system administrator
             about this.
   .IGNORE   This target marks each of  its  sources  with  the
             .IGNORE  attribute.  If  you  don't  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  suf-
             fixes 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 make-
             file.  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
             doesn't accept the -L flag, though the .LIBS vari-
             able will always be defined once the makefile  has
             been read.
   .MAIN     If you didn't 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 spec-
             ify flags for PMake when the makefile is used. The
             flags are just as they would be typed to the shell









   PMake -- A Tutorial                                PSD:12-31


             (except  you  can't  use  shell  variables  unless
             they're  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  desig-
             nated.  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  direc-
             tories  added to the search path before. Since the
             effects of this all get very complex,  I'll  leave
             it  til chapter four to give you a complete expla-
             nation.
   .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 don't give it  any
             sources.
   .SHELL    PMake  is not constrained to only using the Bourne
             shell to execute the commands you put in the make-
             file. 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 suf-
             fix).  For those targets that need  to  have  suf-
             fixes 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.










   PSD:12-32                                PMake -- A Tutorial


   3.6.  Modifying Variable Expansion
   Variables  need  not  always  be  expanded  verbatim.  PMake
   defines  several  modifiers  that  may be applied to a vari-
   able'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 mod-
   ifiers 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 charac-
   ters:
        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 don't 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  expan-
             sion also occurs in the normal fashion inside both









   PMake -- A Tutorial                                PSD:12-33


             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  regular-
             expression/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  (``exten-
             sion'').  So  ``$(OBJS:E)''  would  give  you ``.o
             .a.''
        R    This replaces each word by everything but the suf-
             fix  (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 sup-
   ported.  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 on Debugging

   3.8.  More Exercises
   (3.1)
        You've  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 ver-
        sions, 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









   PSD:12-34                                PMake -- A Tutorial


        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 error-
        checking versions have ec tacked onto their prefix)?
   (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 doesn't 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's what will
        be in the result.

   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  couldn't  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 directo-
   ries  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 direc-
   tories in the specific search path, followed by those in the









   PMake -- A Tutorial                                PSD:12-35


   general search path, consulted.
   A search path is also used when expanding  wildcard  charac-
   ters.  If  the  pattern has a recognizable suffix on it, the
   path for that suffix will be used for the expansion.  Other-
   wise the default search path is employed.
   When  a  file is found in some directory other than the cur-
   rent 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

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

   the  command executed to create mumble would be ``cc -o mum-
   ble ../lib/mumble.c.''  (As an aside, the  command  in  this
   case  isn't strictly necessary, since it will be found using
   transformation rules if it isn't given. This is because .out
   is  the  null  suffix by default and a transformation exists
   from .c to .out.  Just thought I'd 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 conven-
   tion 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  cur-
   rent  directory.  While  people  have  suggested  that PMake
   should read the directories each time,  my  experience  sug-
   gests  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-sys-
   tem  searches  is truly staggering, especially if many files
   without suffixes are used and the null suffix isn't  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's
   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're
   out-of-date if you so much as look at them.









   PSD:12-36                                PMake -- A Tutorial


   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:
   o 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 remem-
   ber that most the wildcard characters will only find  exist-
   ing files.
   A  file that is a member of an archive is treated specially.
   If the file doesn't 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  fig-
   uring  out  how  to  make an archived member target (not the
   file itself, but the file in the archive -- the archive(mem-
   ber)  target), special care is taken with the transformation
   rules, as follows:
   o archive(member) is made to depend on member.
   o The transformation from the member's  suffix  to  the  ar-
     chive's suffix is applied to the archive(member) target.
   o 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.
   o The  .ARCHIVE  variable  for the archive(member) target is
     set to the name of the archive.
   o The .MEMBER variable is set to the  actual  string  inside
     the  parentheses.  In most cases, this will be the same as
     the .TARGET variable.
   o 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 doesn't exist, the archived object
   file), the out-of-date ones archived in libprog.a,  a  table









   PMake -- A Tutorial                                PSD:12-37


   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 condi-
   tional statements. A conditional looks like this:











   PSD:12-38                                PMake -- A Tutorial


        #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 com-
   parisons  as well.  && represents logical AND; || is logical
   OR and !  is logical NOT.  The arithmetic and string  opera-
   tors  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 parenthe-
   ses, and an expression may be parenthesized to your  heart's
   content.   Each  term looks like a call on one of four func-
   tions:
   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  com-
            mand 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  tar-
            gets).
   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 (NOTE: an undefined variable in this  con-
            text 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  unde-
            fined).  This can be used to see if a variable con-
            tains a given word, for example:










   PMake -- A Tutorial                                PSD:12-39


                 #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 stan-
   dard 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,  elifn-
   def, elifmake, and elifnmake.
   For  instance,  if you wish to create two versions of a pro-
   gram, 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









   PSD:12-40                                PMake -- A Tutorial


   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  annoy-
   ance  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  tar-
   gets. 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  speci-
        fied  (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.









   PMake -- A Tutorial                                PSD:12-41


   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  argu-
        ments. 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 doesn't have
        error-control as commands, but I'll 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=command to turn error checking off
        This  is  the command PMake uses to turn error checking
        off. It has another use if the shell doesn't do  error-
        control, but I'll 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'd 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 com-
        mand is used as a template, in which the string  %s  is
        replaced by the command that's about to be executed, to
        produce a command for the shell that will echo the com-
        mand 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 pro-
        vide 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)









   PSD:12-42                                PMake -- A Tutorial


   and may contain the usual C backslash-characters (\n is new-
   line,  \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 make-
   file, 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:
   o 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's the last component of the
     path).
   o The command to stop echoing is set -.
   o The command to start echoing is set -x.
   o 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 don't notice extra commands you didn't put there.
   o 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
     -.
   o The flag to use to turn error-checking on from  the  start
     is -e.
   o The shell can turn error-checking on and off, and the com-
     mands 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
   don't do error control. You can get a similar  effect,  how-
   ever, by changing the last three lines to be:











   PMake -- A Tutorial                                PSD:12-43


             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's the last command it executed).

   4.5.  Compatibility
   There  are  three (well, 3 1/2) levels of backwards-compati-
   bility 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.5.1.  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  make-
   files  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's 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.5.2.  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 com-
   mand (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:










   PSD:12-44                                PMake -- A Tutorial


        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.5.3.  DEFCON 1 -- Imitation is the Not the Highest Form of
   Flattery
   The  final  category of makefile is the one where every com-
   mand requires input, the dependencies are incompletely spec-
   ified, 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  fea-
   tures PMake provides). This includes:
   o No parallel execution.
   o Targets are made in the exact order specified by the make-
     file. The sources for each target are made in strict left-
     to-right order, etc.
   o A  single  Bourne  shell  is used to execute each command,
     thus the shell's $$ variable is useless, changing directo-
     ries doesn't work across command lines, etc.
   o 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).
   o The use of the null suffix is turned off.

   4.6.  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,  respec-
   tively), 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









   PMake -- A Tutorial                                PSD:12-45


   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 exami-
   nation  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 decre-
   mented 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 pro-
   cessed  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 cre-
   ate 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  weren't  made and prints them out one by
   one.

   5.  Answers to Exercises
   (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 doesn't  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's the characters at  the  end  of  a
        word.  With this in mind, one possible solution to this
        problem goes as follows:












   PSD:12-46                                PMake -- A Tutorial


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

   (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've tried it),  since,
        as it says in the hint, PMake does no further substitu-
        tion on the result of a modified expansion, that's  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.

   6.  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  assign-
        ments 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.  See
        ``command script.''
   dependency:  The relationship between a source and a target.
        This comes in three flavors, as indicated by the opera-
        tor  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









   PMake -- A Tutorial                                PSD:12-47


        target out-of-date. `::' is much like `:', save it cre-
        ates 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  depen-
        dency  line to specify a different source for each tar-
        get 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 vari-
        ables, 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  don't  know  what  it  is after reading this tuto-
        rial....
   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 depen-
        dency  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  cre-
        ated from the sources.
   special  target:  A  target  that causes PMake to do special
        things when it's encountered.
   suffix: The tail end of a file name. Usually begins  with  a
        period, .c or .ms, e.g.
   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.









   PSD:12-48                                PMake -- A Tutorial


   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 environ-
        ment. Conditionals exist that test whether  a  variable
        is defined or not.
























































   PMake -- A Tutorial                                PSD:12-49



                           Table of Contents


        1.    Introduction  . . . . . . . . . . . . . . . .   1
        2.    The Basics of PMake . . . . . . . . . . . . .   2
        2.1.  Dependency Lines  . . . . . . . . . . . . . .   2
        2.2.  Shell Commands  . . . . . . . . . . . . . . .   4
        2.3.  Variables . . . . . . . . . . . . . . . . . .   6
        2.3.1.Local Variables . . . . . . . . . . . . . . .   8
        2.3.2.Command-line Variables  . . . . . . . . . . .   9
        2.3.3.Global Variables  . . . . . . . . . . . . . .   9
        2.3.4.Environment Variables . . . . . . . . . . . .  10
        2.4.  Comments  . . . . . . . . . . . . . . . . . .  10
        2.5.  Parallelism . . . . . . . . . . . . . . . . .  10
        2.6.  Writing and Debugging a Makefile  . . . . . .  11
        2.7.  Invoking PMake  . . . . . . . . . . . . . . .  14
        2.8.  Summary . . . . . . . . . . . . . . . . . . .  18
        2.9.  Exercises . . . . . . . . . . . . . . . . . .  18
        3.    Short-cuts and Other Nice Things  . . . . . .  19
        3.1.  Transformation Rules  . . . . . . . . . . . .  19
        3.2.  Including Other Makefiles . . . . . . . . . .  23
        3.3.  Saving Commands . . . . . . . . . . . . . . .  24
        3.4.  Target Attributes . . . . . . . . . . . . . .  25
        3.5.  Special Targets . . . . . . . . . . . . . . .  29
        3.6.  Modifying Variable Expansion  . . . . . . . .  32
        3.7.  More on Debugging . . . . . . . . . . . . . .  33
        3.8.  More Exercises  . . . . . . . . . . . . . . .  33
        4.    PMake for Gods  . . . . . . . . . . . . . . .  34
        4.1.  Search Paths  . . . . . . . . . . . . . . . .  34
        4.2.  Archives and Libraries  . . . . . . . . . . .  35
        4.3.  On the Condition...   . . . . . . . . . . . .  37
        4.4.  A Shell is a Shell is a Shell . . . . . . . .  40
        4.5.  Compatibility . . . . . . . . . . . . . . . .  43
        4.5.1.DEFCON 3 -- Variable Expansion  . . . . . . .  43
        4.5.2.DEFCON 2 -- The Number of the Beast . . . . .  43
        4.5.3.DEFCON 1 -- Imitation is the Not the Highest
        Form of Flattery  . . . . . . . . . . . . . . . . .  44
        4.6.  The Way Things Work . . . . . . . . . . . . .  44
        5.    Answers to Exercises  . . . . . . . . . . . .  45
        6.    Glossary of Jargon  . . . . . . . . . . . . .  46



















