                                                             PMake—ATutorial
                                                                   Adam de Boor
                                                                BerkeleySoftworks
                                                            2150 Shattuck Ave,Penthouse
                                                                Berkeley, CA94704
                                                                 adam@bsw.uu.net
                                                                 ...!uunet!bsw!adam
                     1. Introduction
                     PMakeisaprogram for creating other programs, or anything else you can think of for it to do. The basic idea be-
                     hind PMakeisthat, for anygiv ensystem, be it a program or a document or whatever, there will be some ﬁles that
                     depend on the state of other ﬁles (on when theywere last modiﬁed). PMaketakes these dependencies, which you
                     must specify,and uses them to build whateveritisyou want it to build.
                     PMakeisalmost fully-compatible with Make, with which you may already be familiar.PMake’smost important fea-
                     ture is its ability to run several different jobs at once, making the creation of systems considerably faster.Italso has a
                     great deal more functionality than Make. Throughout the text, wheneversomething is mentioned that is an important
                     difference between PMakeand Make(i.e. something that will cause a makeﬁle to fail if you don’tdosomething
            NOTE     about it), or is simply important, it will be ﬂagged with a little sign in the left margin, likethis:
                     This tutorial is divided into three main sections corresponding to basic, intermediate and advanced PMakeusage. If
                     you already knowMakewell, you will only need to skim chapter 2 (there are some aspects of PMakethat I consider
                     basic to its use that didn’texist in Make). Things in chapter 3 makelife much easier,while those in chapter 4 are
                     strictly for those who knowwhat theyare doing. Chapter 5 has deﬁnitions for the jargon I use and chapter 6 contains
                     possible solutions to the problems presented throughout the tutorial.
                     2. The Basics of PMake
                     PMaketakes as input a ﬁle that tells a) which ﬁles depend on which other ﬁles to be complete and b) what to do
                     about ﬁles that are ‘‘out-of-date.’’ This ﬁle is known as a ‘‘makeﬁle’’and is usually kept in the top-most directory of
                     the system to be built. While you can call the makeﬁle anything you want, PMakewill look for Makefile and
                     makefile(in that order) in the current directory if you don’ttell it otherwise. To specify a different makeﬁle, use
                     the −f ﬂag (e.g. ‘‘pmake -f program.mk’’).
                     Amakeﬁle has four different types of lines in it:
                           •File dependencyspeciﬁcations
                           •Creation commands
                           •Variable assignments
                           •Comments, include statements and conditional directives
                     Anyline may be continued overmultiple lines by ending it with a backslash. The backslash, following newline and
                     anyinitial whitespace on the following line are compressed into a single space before the input line is examined by
                     PMake.
                     Permission to use, copy, modify,and distribute this software and its documentation for anypurpose and without fee is hereby granted,
                     provided that the above copyright notice appears in all copies. The University of California, BerkeleySoftworks, and Adam de Boor
                     makenorepresentations about the suitability of this software for anypurpose. It is provided "as is" without express or implied war-
                     ranty.
                PSD:12-2 PMake—ATutorial
                2.1. Dependency Lines
                As mentioned in the introduction, in anysystem, there are dependencies between the ﬁles that makeupthe system.
                Forinstance, in a program made up of several C source ﬁles and one header ﬁle, the C ﬁles will need to be re-com-
                piled should the header ﬁle be changed. For a document of several chapters and one macro ﬁle, the chapters will
                need to be reprocessed if anyofthe macros changes. These are dependencies and are speciﬁed by means of depen-
                dencylines in the makeﬁle.
                On a dependencyline, there are targets and sources, separated by a one- or two-character operator.The targets ‘‘de-
                pend’’onthe sources and are usually created from them. Anynumber of targets and sources may be speciﬁed on a
                dependencyline. All the targets in the line are made to depend on all the sources. Targets and sources need not be
                actual ﬁles, but every source must be either an actual ﬁle or another target in the makeﬁle. If you run out of room,
                use a backslash at the end of the line to continue onto the next one.
                Anyﬁle may be a target and anyﬁle may be a source, but the relationship between the two(or howevermany) is de-
                termined by the ‘‘operator’’that separates them. Three types of operators exist: one speciﬁes that the datedness of a
                target is determined by the state of its sources, while another speciﬁes other ﬁles (the sources) that need to be dealt
                with before the target can be re-created. The third operator is very similar to the ﬁrst, with the additional condition
                that the target is out-of-date if it has no sources. These operations are represented by the colon, the exclamation
                point and the double-colon, respectively,and are mutually exclusive.Their exact semantics are as follows:
                :Ifacolon is used, a target on the line is considered to be ‘‘out-of-date’’(and in need of creation) if
                    •any ofthe sources has been modiﬁed more recently than the target, or
                    •the target doesn’texist.
                    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 tworules.
                !Ifanexclamation point is used, the target will always be re-created, but this will not happen until all of its
                    sources have been examined and re-created, if necessary.
                :: Ifadouble-colon is used, a target is out-of-date if:
                    •any ofthe sources has been modiﬁed more recently than the target, or
                    •the target doesn’texist, or
                    •the target has no sources.
                    If the target is out-of-date according to these rules, it will be re-created. This operator also does something
                    else to the targets, but I’ll go into that in the next section (‘‘Shell Commands’’).
                Enough words, nowfor an example. Takethat C program I mentioned earlier.Say there are three C ﬁles (a.c, b.c
                and c.c)each of which includes the ﬁle defs.h.The dependencies between the ﬁles 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
                Youmay 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
                ﬁles don’t. The reason is quite simple: program cannot be made by linking together .c ﬁles — it must be made
                from .o ﬁles. Likewise, if you change defs.h,itisn’tthe .c ﬁles that need to be re-created, it’sthe .o ﬁles. If you
                think of dependencies in these terms — which ﬁles (targets) need to be created from which ﬁles (sources) — you
                should have noproblems.
                An important thing to notice about the above example, is that all the .o ﬁles 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 dependencylines. E.g.
                a.odepends on both defs.h and a.c.
                     PMake—ATutorial PSD:12-3
            NOTE     The order of the dependencylines in the makeﬁle is important: the ﬁrst target on the ﬁrst dependencyline in the
                     makeﬁle will be the one that gets made if you don’tsay otherwise. That’swhy program comes ﬁrst in the example
                     makeﬁle, 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 ﬁnal component (the ﬁle portion) of the target or source. The characters
                     mean the following things:
                     {}    These enclose a comma-separated list of options and cause the pattern to be expanded once for each element
                           of the list. Each expansion contains a different element. For example, src/{whiffle,beep,fish}.c
                           expands to the three words src/whiffle.c, src/beep.c,and src/fish.c.These braces may be
                           nested and, unlikethe other wildcard characters, the resulting words need not be actual ﬁles. All other wild-
                           card characters are expanded using the ﬁles that exist when PMakeisstarted.
                     *     This matches zero or more characters of anysort. src/*.c will expand to the same three words as above as
                           long as src contains those three ﬁles (and no other ﬁles that end in .c).
                     ?     Matches anysingle 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 anysingle character contained in
                           the list. E.g. [A-Za-z] will match all letters, while [0123456789] will match all numbers.
                     2.2. Shell Commands
                     ‘‘Isn’tthat nice,’’ you say to yourself, ‘‘but howare ﬁles actually ‘re-created,’ashelikes to spell it?’’The re-cre-
                     ation is accomplished by commands you place in the makeﬁle. These commands are passed to the Bourne shell
                     (better known as ‘‘/bin/sh’’) to be executed and are expected to do what’snecessary to update the target ﬁle (PMake
                     doesn’tactually check to see if the target was created. It just assumes it’sthere).
                     Shell commands in a makeﬁle look a lot likeshell commands you would type at a terminal, with one important ex-
                     ception: each command in a makeﬁle 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 followthe dependencyline for that target. While anygiv entarget may appear on
                     more than one dependencyline, only one of these dependencylines may be followed by a creation script, unless the
            NOTE     ‘::’ operator was used on the dependencyline.
                     If the double-colon was used, each dependencyline for the target may be followed by a shell script. That script will
                     only be executed if the target on the associated dependencyline is out-of-date with respect to the sources on that
                     line, according to the rules I gav e earlier.I’llgiv e you a good example of this later on.
                     To expand on the earlier makeﬁle, you might add commands as follows:
                             program : a.o b.o c.o
                                        cc a.o b.o c.o −o program
                             a.o b.o c.o           :defs.h
                             a.o :a.c
                                        cc −c a.c
                             b.o :b.c
                                        cc −c b.c
                             c.o :c.c
                                        cc −c c.c
                     Something you should remember when writing a makeﬁle is, the commands will be executed if the target on the de-
                     pendencyline is out-of-date, not the sources. In this example, the command ‘‘cc −c a.c’’ will be executed if
                     a.ois out-of-date. Because of the ‘:’ operator,this means that should a.c or defs.h have been modiﬁed more re-
                     cently than a.o,the command will be executed (a.o will be considered out-of-date).
                     Remember howIsaid the only difference between a makeﬁle shell command and a regular shell command was the
                     leading tab? I lied. There is another way in which makeﬁle commands differ from regular ones. The ﬁrst twochar-
                     acters after the initial whitespace are treated specially.Iftheyare anycombination of ‘@’ and ‘−’, theycause
                     PMaketododifferent things.
                    PSD:12-4 PMake—ATutorial
                    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 suppressed. In the case of an echo command, say ‘‘echo
                    Linking index,’’itwould be rather silly to see
                           echo Linking index
                           Linking index
                    so PMakeallows you to place an ‘@’ before the command (‘‘@echo Linking index’’)toprevent the com-
                    mand from being printed.
                    The other special character is the ‘−’. In case you didn’tknow, shell commands ﬁnish with a certain ‘‘exit status.’’
                    This status is made available by the operating system to whateverprogram invokedthe command. Normally this sta-
                    tus will be 0 if everything went ok and non-zero if something went wrong. For this reason, PMakewill consider an
                    error to have occurred if one of the shells it invokesreturns a non-zero status. When it detects an error,PMake’s
                    usual action is to abort whateverit’sdoing and exit with a non-zero status itself (anyother targets that were being
                    created will continue being made, but nothing newwill be started. PMakewill exit after the last job ﬁnishes). This
                    behavior can be altered, however, byplacing a ‘−’ at the front of a command (‘‘−mv index index.old’’), cer-
                    tain command-line arguments, or doing other things, to be detailed later.Insuch a case, the non-zero status is simply
           NOTE     ignored and PMakekeeps chugging along.
                    Because all the commands are giventoasingle shell to execute, such things as setting shell variables, changing di-
                    rectories, etc., last beyond the command in which theyare found. This also allows shell compound commands (like
                    for loops) to be entered in a natural manner.Since this could cause problems for some makeﬁles that depend on
                    each command being executed by a single shell, PMakehas a −B ﬂag (it stands for backwards-compatible) that
                    forces each command to be giventoaseparate shell. It also does several other things, all of which I discourage since
           NOTE     theyare nowold-fashioned. . . .
                    Atarget’sshell script is fed to the shell on its (the shell’s) input stream. This means that anycommands, such as ci
                    that need to get input from the terminal won’twork right — they’ll get the shell’sinput, something theyprobably
                    won’tﬁnd to their liking. A simple way around this is to give a command likethis:
                           ci $(SRCS) < /dev/tty
                    This would force the program’sinput to come from the terminal. If you can’tdothis for some reason, your only
                    other alternative istouse PMakeinits fullest compatibility mode. See Compatibility in chapter 4.
                    2.3. Variables
                    PMake, likeMakebefore it, has the ability to save textinvariables to be recalled later at your convenience. Vari-
                    ables in PMakeare used much likevariables 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’snothing to stop you from calling a variable @ˆ&$%$.Just tradi-
                    tion). Variables are assigned-to using lines of the form
                           VARIABLE = value
                    appended-to by
                           VARIABLE += value
                    conditionally assigned-to (if the variable isn’talready deﬁned) 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
                    Anywhitespace before value is stripped off. When appending, a space is placed between the old value and the stuff
                    being appended.
                    The ﬁnal way a variable may be assigned to is using
                     PMake—ATutorial PSD:12-5
                             VARIABLE != shell-command
                     In this case, shell-command has all its variables expanded (see below) and is passed offtoashell to execute. The
                     output of the shell is then placed in the variable. Anynewlines (other than the ﬁnal one) are replaced by spaces be-
                     fore the assignment is made. This is typically used to ﬁnd 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 im-
                     plementation 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 manyUNIX systems).
                     The value of a variable may be retrievedbyenclosing the variable name in parentheses or curly braces and preceding
                     the whole thing with a dollar sign.
                     Forexample, 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 makeﬁle and use the word $(CFLAGS) whereveryou would likethe string −I/sprite/src/lib/libc
           NOTE      −Oto appear.This is called variable expansion.
                     UnlikeMake, PMakewill not expand a variable unless it knows the variable exists. E.g. if you have a ${i} in a
                     shell command and you have not assigned a value to the variable i (the empty string is considered a value, by the
                     way), where Makewould have substituted the empty string, PMakewill leave the ${i} alone. Tokeep PMakefrom
                     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’sstyle of variable expansion will be used if you invoke PMakewith anyofthe compatibility
                     ﬂags (−V, −B or −M.The −V ﬂag alters just the variable expansion).
                     There are twodifferent times at which variable expansion occurs: When parsing a dependencyline, the expansion
                     occurs immediately upon reading the line. If anyvariable used on a dependencyline is undeﬁned, PMakewill print a
                     message and exit. Variables in shell commands are expanded when the command is executed. Variables used inside
                     another variable are expanded wheneverthe outer variable is expanded (the expansion of an inner variable has no ef-
                     fect on the outer variable. I.e. if the outer variable is used on a dependencyline and in a shell command, and the in-
                     ner variable changes value between when the dependencyline is read and the shell command is executed, twodiffer-
                     ent values will be substituted for the outer variable).
                     Variables come in four ﬂavors, though theyare all expanded the same and all look about the same. Theyare (in order
                     of expanding scope):
                          •Local variables.
                          •Command-line variables.
                          •Global variables.
                          •Environment variables.
                     The classiﬁcation of variables doesn’tmatter much, except that the classes are searched from the top (local) to the
                     bottom (environment) when looking up a variable. The ﬁrst one found wins.
                     2.3.1. Local Variables
                     Each target can have asmanyassev enlocal variables. These are variables that are only ‘‘visible’’within that target’s
                     shell script and contain such things as the target’sname, all of its sources (from all its dependencylines), those
                     sources that were out-of-date, etc. Four local variables are deﬁned for all targets. Theyare:
                          .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 guar-
                                anteed to be the same as the order in which the dependencies were given.
            PSD:12-6 PMake—ATutorial
               .ALLSRC
                  The list of all sources for this target in the order in which theywere given.
               .PREFIX
                  The target without its sufﬁx and without anyleading 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 theyare set and howtheyare used is described later.
            Four of these variables may be used in sources as well as in shell scripts. These are ‘‘.TARGET’’, ‘‘.PREFIX’’,
            ‘‘.ARCHIVE’’and ‘‘.MEMBER’’. The variables in the sources are expanded once for each target on the dependency
            line, providing what is known as a ‘‘dynamic source,’’ allowing you to specify several dependencylines at once. For
            example,
                $(OBJS) : $(.PREFIX).c
            will create a dependencybetween each object ﬁle and its corresponding C source ﬁle.
            2.3.2. Command-line Variables
            Command-line variables are set when PMakeisﬁrst invokedbygiving a variable assignment as one of the argu-
            ments. For example,
                pmake "CFLAGS = -I/sprite/src/lib/libc -O"
            would make CFLAGS be a command-line variable with the givenvalue. Anyassignments to CFLAGS in the make-
            ﬁle will have noeffect, because once it is set, there is (almost) nothing you can do to change a command-line vari-
            able (the search order,you see). Command-line variables may be set using anyofthe four assignment operators,
            though only = and ?= behave asyou would expect them to, mostly because assignments to command-line variables
            are performed before the makeﬁle is read, thus the values set in the makeﬁle 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 efﬁciencythat I won’tget into here). := and ?= will work if the only variables used are in the envi-
            ronment. != is sort of pointless to use from the command line, since the same effect can no doubt be accomplished
            using the shell’sown command substitution mechanisms (backquotes and all that).
            2.3.3. Global Variables
            Global variables are those set or appended-to in the makeﬁle. There are twoclasses of global variables: those you
            set and those PMakesets. As Isaid 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 PMakesets (almost) always begin with a period and
            always contain upper-case letters, only.The variables are as follows:
               .PMAKE
                  The name by which PMakewas invokedisstored in this variable. For compatibility,the name is also
                  stored in the MAKE variable.
               .MAKEFLAGS
                  All the relevant ﬂags with which PMakewas invoked. This does not include such things as −f or vari-
                  able assignments. Again for compatibility,this value is stored in the MFLAGS variable as well.
            Tw o other variables, ‘‘.INCLUDES’’and ‘‘.LIBS,’’ are covered in the section on special targets in chapter 3.
            Global variables may be deleted using lines of the form:
                #undef variable
            The ‘#’must be the ﬁrst character on the line. Note that this may only be done on global variables.
            2.3.4. Environment Variables
            Environment variables are passed by the shell that invokedPMakeand are givenbyPMaketoeach shell it invokes.
            Theyare expanded likeany other variable, but theycannot be altered in anyway.
                     PMake—ATutorial PSD:12-7
                     One special environment variable, PMAKE,isexamined by PMakefor command-line ﬂags, variable assignments,
                     etc., it should always use. This variable is examined before the actual arguments to PMakeare. In addition, all ﬂags
                     giventoPMake, either through the PMAKE variable or on the command line, are placed in this environment variable
                     and exported to each shell PMakeexecutes. Thus recursive inv ocations of PMakeautomatically receive the same
                     ﬂags as the top-most one.
                     Using all these variables, you can compress the sample makeﬁle evenmore:
                             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 makeﬁle start with a ‘#’ character and extend to the end of the line. Theymay 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 dependencyline, put a backslash in front of it. PMakewill compress the two
                     into a single ‘#’ (Note: this isn’ttrue if PMakeisoperating in full-compatibility mode).
            NOTE     2.5. Parallelism
                     PMakewas speciﬁcally designed to re-create several targets at once, when possible. You do not have todoanything
                     special to cause this to happen (unless PMakewas conﬁgured to not act in parallel, in which case you will have to
                     makeuse of the −L and −J ﬂags (see below)), but you do have tobecareful at times.
                     There are several problems you are likely to encounter.One is that some makeﬁles (and programs) are written in
                     such a way that it is impossible for twotargets to be made at once. The program xstr,for example, always modi-
                     ﬁes the ﬁles strings and x.c.There is no way to change it. Thus you cannot run twoofthem at once without
                     something being trashed. Similarly,ifyou have commands in the makeﬁle that always send output to the same ﬁle,
                     you will not be able to makemore than one target at once unless you change the ﬁle you use. You can, for instance,
                     add a $$$$ to the end of the ﬁle name to tack on the process ID of the shell executing the command (each $$ ex-
                     pands to a single $,thus giving you the shell variable $$). Since only one shell is used for all the commands, you’ll
                     get the same ﬁle name for each command in the script.
                     The other problem comes from improperly-speciﬁed dependencies that worked in Makebecause of its sequential,
                     depth-ﬁrst way of examining them. While I don’twant to go into depth on howPMakeworks (look in chapter 4 if
                     you’re interested), I will warn you that ﬁles in twodifferent ‘‘levels’’ofthe dependencytree may be examined in a
                     different order in PMakethan theywere in Make. For example, giventhe makeﬁle
                             a:bc
                             b:d
                     PMakewill examine the targets in the order c, d, b, a.Ifthe makeﬁle’sauthor expected PMaketoabort before
                     making c if an error occurred while making b,orif b needed to exist before c wasmade, s/he will be sorely disap-
                     pointed. The dependencies are incomplete, since in both these cases, c would depend on b.Sowatch out.
                     Another problem you may face is that, while PMakeisset up to handle the output from multiple jobs in a graceful
                     fashion, the same is not so for input. It has no way to regulate input to different jobs, so if you use the redirection
                     from /dev/ttyImentioned earlier,you must be careful not to run twoofthe jobs at once.
                     2.6. Writing and Debugging a Makeﬁle
                     Nowyou knowmost of what’sinamakeﬁle, what do you do next? There are twochoices: (1) use one of the uncom-
                     monly-available makeﬁle generators or (2) write your own makeﬁle (I leave out the third choice of ignoring PMake
            PSD:12-8 PMake—ATutorial
            and doing everything by hand as being beyond the bounds of common sense).
            When faced with the writing of a makeﬁle, it is usually best to start from ﬁrst principles: just what are you trying to
            do? What do you want the makeﬁle ﬁnally to produce?
            To begin with a somewhat traditional example, let’ssay you need to write a makeﬁle to create a program, expr,that
            takes standard inﬁx expressions and converts them to preﬁx form (for no readily apparent reason). You’ve got three
            source ﬁles, in C, that makeupthe program: main.c, parse.c,and output.c.Harking back to my pithyad-
            vice about dependencylines, you write the ﬁrst line of the ﬁle:
                expr :main.o parse.o output.o
            because you remember expr is made from .o ﬁles, not .c ﬁles. Similarly for the .o ﬁles 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 speciﬁed. What you need nowiscommands. These commands, remember,
            must produce the target on the dependencyline, usually by using the sources you’ve listed. You remember about lo-
            cal 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)
            Whyuse the variables? If your program grows to produce postﬁx expressions too (which, of course, requires a name
            change or two), it is one fewer place you have tochange the ﬁle. You cannot do this for the object ﬁles, however, be-
            cause theydepend on their corresponding source ﬁles and defs.h,thus if you said
                   cc -c $(.ALLSRC)
            you’dget (for main.o):
                   cc -c main.c defs.h
            which is wrong. So you round out the makeﬁle 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 makeﬁle is nowcomplete and will, in fact, create the program you want it to without unnecessary compilations
            or excessive typing on your part. There are twothings 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’’ isrepeated twice, necessitating twochanges when you add
               postﬁx (you were planning on that, weren’tyou?). This is in direct violation of de Boor’sFirst Rule of writing
               makeﬁles:
               Anything that needs to be written morethan once should be placed in a variable.
               Icannot emphasize this enough as being very important to the maintenance of a makeﬁle and its program.
            2) There is no way to alter the way compilations are performed short of editing the makeﬁle and making the
               change in all places. This is evil and violates de Boor’sSecond Rule, which follows directly from the ﬁrst:
               Any ﬂags or programs used inside a makeﬁle should be placed in a variable so theymay be changed,
               temporarily or permanently,with the greatest ease.
            The makeﬁle should more properly read:
             PMake—ATutorial PSD:12-9
                 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,ifyou likethe idea of dynamic sources mentioned in section 2.3.1, you could write it likethis:
                 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 tworules and examples lead to de Boor’sFirst Corollary:
                Variables areyour friends.
             Once you’ve written the makeﬁle comes the sometimes-difﬁcult task of making sure the darn thing works. Your
             most helpful tool to makesure the makeﬁle is at least syntactically correct is the −n ﬂag, which allows you to see if
             PMakewill chokeonthe makeﬁle. The second thing the −n ﬂag lets you do is see what PMakewould do without it
             actually doing it, thus you can makesure the right commands would be executed were you to give PMakeits head.
             When you ﬁnd your makeﬁle isn’tbehaving as you hoped, the ﬁrst question that comes to mind (after ‘‘What time is
             it, anyway?’’) is ‘‘Whynot?’’Inanswering this, twoﬂags will serveyou well: ‘‘-d m’’ and ‘‘-p 2.’’The ﬁrst
             causes PMaketotell you as it examines each target in the makeﬁle and indicate whyitisdeciding whateveritisde-
             ciding. You can then use the information printed for other targets to see where you went wrong. The ‘‘-p 2’’ ﬂag
             makes PMakeprint out its internal state when it is done, allowing you to see that you forgot to makethat one chapter
             depend on that ﬁle of macros you just got a newversion of. The output from ‘‘-p 2’’ isintended to resemble
             closely a real makeﬁle, but with additional information provided and with variables expanded in those commands
             PMakeactually printed or executed.
             Something to be especially careful about is circular dependencies. E.g.
                 a:b
                 b:cd
                 d:a
             In this case, because of howPMakeworks, c is the only thing PMakewill examine, because d and a will effectively
             fall offthe edge of the universe, making it impossible to examine b (or them, for that matter). PMakewill tell you
             (if run in its normal mode) all the targets involved in anycycle it looked at (i.e. if you have two cycles in the graph
             (naughty,naughty), but only try to makeatarget in one of them, PMakewill only tell you about that one. You’ll
             have totry to makethe other to ﬁnd the second cycle). When run as Make, it will only print the ﬁrst target in the cy-
             cle.
             2.7. Invoking PMake
             PMakecomes with a wide variety of ﬂags to choose from. Theymay appear in anyorder,interspersed with com-
             mand-line variable assignments and targets to create. The ﬂags are as follows:
             −dwhat
                This causes PMaketospewout debugging information that may prove useful to you. If you can’tﬁgure out
                whyPMakeisdoing what it’sdoing, you might try using this ﬂag. The what parameter is a string of single
                characters that tell PMakewhat aspects you are interested in. Most of what I describe will makelittle sense to
                you, unless you’ve dealt with Makebefore. Just remember where this table is and come back to it as you read
                on. The characters and the information theyproduce are as follows:
                 PSD:12-10 PMake—ATutorial
                      aArchive searching and caching.
                      cConditional evaluation.
                      dThe searching and caching of directories.
                      jVarious snippets of information related to the running of the multiple shells. Not particularly interesting.
                      mThe making of each target: what target is being examined; when it was last modiﬁed; whether it is out-
                          of-date; etc.
                      pMakeﬁle parsing.
                      rRemote execution.
                      sThe application of sufﬁx-transformation rules. (See chapter 3)
                      tThe maintenance of the list of targets.
                      vVariable assignment.
                      Of these all, the m and s letters will be most useful to you. If the −d is the ﬁnal argument or the argument
                      from which it would get these key letters (see belowfor a note about which argument would be used) begins
                      with a −,all of these debugging ﬂags will be set, resulting in massive amounts of output.
                 −f makeﬁle
                      Specify a makeﬁle to read different from the standard makeﬁles (Makefile or makefile). If makeﬁle is
                      ‘‘−’’, PMakeuses the standard input. This is useful for making quick and dirty makeﬁles. . .
                 −h   Prints out a summary of the various ﬂags PMakeaccepts. It can also be used to ﬁnd out what levelofconcur-
                      rencywas compiled into the version of PMakeyou are using (look at −J and −L)and various other informa-
                      tion on howPMakewas conﬁgured.
                 −i   If you give this ﬂag, PMakewill ignore non-zero status returned by anyofits shells. It’slikeplacing a ‘−’ be-
                      fore all the commands in the makeﬁle.
                 −k   This is similar to −i in that it allows PMaketocontinue when it sees an error,but unlike −i,where PMakecon-
                      tinues blithely as if nothing went wrong, −k causes it to recognize the error and only continue work on those
                      things that don’tdepend on the target, either directly or indirectly (through depending on something that de-
                      pends on it), whose creation returned the error.The ‘k’ is for ‘‘keep going’’. . .
                 −l   PMakehas the ability to lock a directory against other people executing it in the same directory (by means of a
                      ﬁle 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 ﬁnal product (too manycooks and all
                      that). Whether this locking is the default is up to your system administrator.Iflocking is on, −l will turn it off,
                      and vice versa. Note that this locking will not prevent you from invoking PMaketwice in the same place — if
                      you own the lock ﬁle, PMakewill warn you about it but continue to execute.
                 −mdirectory
                      Tells PMakeanother place to search for included makeﬁles via the <...> style. Several −m options can be
                      giventoform a search path. If this construct is used the default system makeﬁle search path is completely
                      overridden. Tobeexplained in chapter 3, section 3.2.
                 −n   This ﬂag tells PMakenot to execute the commands needed to update the out-of-date targets in the makeﬁle.
                      Rather,PMakewill simply print the commands it would have executed and exit. This is particularly useful for
                      checking the correctness of a makeﬁle. If PMakedoesn’tdowhat you expect it to, it’sagood chance the
                      makeﬁle is wrong.
                 −pnumber
                      This causes PMaketoprint its input in a reasonable form, though not necessarily one that would makeimme-
                      diate sense to anyone but me. The number is a bitwise-or of 1 and 2 where 1 means it should print the input
                      before doing anyprocessing 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 ﬁnd the difference between the two
                      interesting). This is mostly useful to me, but you may ﬁnd it informative insome bizarre circumstances.
                 −q   If you give PMakethis ﬂag, it will not try to re-create anything. It will just see if anything is out-of-date and
                      exit non-zero if so.
                   PMake—ATutorial PSD:12-11
                   −r   When PMakestarts up, it reads a default makeﬁle that tells it what sort of system it’sonand givesitsome idea
                        of what to do if you don’ttell it anything. I’ll tell you about it in chapter 3. If you give this ﬂag, PMakewon’t
                        read the default makeﬁle.
                   −s   This causes PMaketonot print commands before they’re executed. It is the equivalent of putting an ‘@’ be-
                        fore every command in the makeﬁle.
                   −t   Rather than try to re-create a target, PMakewill simply ‘‘touch’’itsoastomakeitappear up-to-date. If the
                        target didn’texist before, it will when PMakeﬁnishes, but if the target did exist, it will appear to have been
                        updated.
                   −v   This is a mixed-compatibility ﬂag intended to mimic the System V version of Make. It is the same as giving
                        −B,and −V as well as turning offdirectory locking. Targets can still be created in parallel, however. This is
                        the mode PMakewill enter if it is invokedeither as ‘‘smake’’ or‘‘vmake’’.
                   −x   This tells PMakeit’soktoexport jobs to other machines, if they’re available. It is used when running in Make
                        mode, as exporting in this mode tends to makethings run slower than if the commands were just executed lo-
                        cally.
                   −B   Forces PMaketobeasbackwards-compatible with Makeaspossible while still being itself. This includes:
                        •Executing one shell per shell command
                        •Expanding anything that looks evenvaguely likeavariable, with the empty string replacing anyvariable
                          PMakedoesn’tknow.
                        •Refusing to allowyou to escape a ‘#’ with a backslash.
                        •Permitting undeﬁned variables on dependencylines and conditionals (see below). Normally this causes
                          PMaketoabort.
                   −C This nulliﬁes anyand all compatibility mode ﬂags you may have giv enorimplied up to the time the −C is en-
                        countered. It is useful mostly in a makeﬁle that you wrote for PMaketoavoid bad things happening when
                        someone runs PMakeas‘‘make’’ orhas things set in the environment that tell it to be compatible. −C is not
                        placed in the PMAKE environment variable or the .MAKEFLAGS or MFLAGS global variables.
                   −Dvariable
                        Allows you to deﬁne a variable to have ‘‘1’’ asits value. The variable is a global variable, not a command-
                        line variable. This is useful mostly for people who are used to the C compiler arguments and those using con-
                        ditionals, which I’ll get into in section 4.3
                   −I directory
                        Tells PMakeanother place to search for included makeﬁles. Yet another thing to be explained in chapter 3
                        (section 3.2, to be precise).
                   −Jnumber
                        Givesthe absolute maximum number of targets to create at once on both local and remote machines.
                   −Lnumber
                        This speciﬁes 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 PMakemay hang until a remote machine becomes available, if one is not
                        available when it is started.
                   −M This is the ﬂag that provides absolute, complete, full compatibility with Make. It still allows you to use all but
                        afew ofthe features of PMake, but it is non-parallel. This is the mode PMakeenters 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 captured by PMakeinsome way in order to prevent the screen
                        from being ﬁlled with garbage evenmore indecipherable than you usually see. PMakehas twoways of doing
                        this, one of which provides for much cleaner output and a clear separation between the output of different
                        jobs, the other of which provides a more immediate response so one can tell what is really happening. The for-
                        mer is done by notifying you when the creation of a target starts, capturing the output and transferring it to the
                        screen all at once when the job ﬁnishes. The latter is done by catching the output of the shell (and its children)
                        and buffering it until an entire line is received, then printing that line preceded by an indication of which job
                        produced the output. Since I prefer this second method, it is the one used by default. The ﬁrst method will be
                        used if you give the −P ﬂag to PMake.
          PSD:12-12 PMake—ATutorial
          −V As mentioned before, the −V ﬂag tells PMaketouse Make’sstyle of expanding variables, substituting the
             empty string for anyvariable it doesn’tknow.
          −W There are several times when PMakewill print a message at you that is only a warning, i.e. it can continue to
             work in spite of your having done something silly (such as forgotten a leading tab for a shell command).
             Sometimes you are well aware of silly things you have done and would likePMaketostop bothering you.
             This ﬂag tells it to shut up about anything non-fatal.
          −X This ﬂag causes PMaketonot attempt to export anyjobs to another machine.
          Several ﬂags may followasingle ‘−’. Those ﬂags that require arguments takethem from successive parameters. E.g.
              pmake -fDnI server.mk DEBUG /chip2/X/server/include
          will cause PMaketoread server.mk as the input makeﬁle, deﬁne the variable DEBUG as a global variable and
          look for included makeﬁles in the directory /chip2/X/server/include.
          2.8. Summary
          Amakeﬁle is made of four types of lines:
             •Dependencylines
             •Creation commands
             •Variable assignments
             •Comments, include statements and conditional directives
          Adependencyline is a list of one or more targets, an operator (‘:’, ‘::’, or ‘!’), and a list of zero or more sources.
          Sources may contain wildcards and certain local variables.
          Acreation command is a regular shell command preceded by a tab.Inaddition, if the ﬁrst twocharacters after the
          tab (and other whitespace) are a combination of ‘@’or‘-’, PMakewill cause the command to not be printed (if the
          character is ‘@’) or errors from it to be ignored (if ‘-’). A blank line, dependencyline 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. Theymay be unconditionally assigned-to using the ‘=’operator,appended-to using
          the ‘+=’operator,conditionally (if the variable is undeﬁned) 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. Adollar sign may be escaped with another dollar sign. Variables are not expanded
          if PMakedoesn’tknowabout them. There are sevenlocal variables: .TARGET, .ALLSRC, .OODATE, .PREFIX,
          .IMPSRC,.ARCHIVE,and.MEMBER.Four of them (.TARGET, .PREFIX, .ARCHIVE,and .MEMBER)may be
          used to specify ‘‘dynamic sources.’’ Variables are good. Knowthem. Love them. Live them.
          Debugging of makeﬁles is best accomplished using the −n, −d m,and −p 2 ﬂags.
          2.9. Exercises          TBA
          3. Short-cuts and Other Nice Things
          Based on what I’ve told you so far,you may have gotten the impression that PMakeisjust a way of storing away
          commands and making sure you don’tforget to compile something. Good. That’sjust what it is. However, the ways
          I’ve described have been inelegant, at best, and painful, at worst. This chapter contains things that makethe writing
          of makeﬁles easier and the makeﬁles themselves shorter and easier to modify (and, occasionally,simpler). In this
          chapter,Iassume you are somewhat more familiar with Sprite (or UNIX, if that’swhat 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 ﬁle’sname consists of twoparts: a base name, which givessome hint as to the contents of the ﬁle,
          and a sufﬁx, which usually indicates the format of the ﬁle. Over the years, as has developed, naming conventions,
          with regard to sufﬁxes, have also developed that have become almost as incontrovertible as Law. E.g. a ﬁle ending in
                       PMake—ATutorial PSD:12-13
                       .c is assumed to contain C source code; one with a .o sufﬁx is assumed to be a compiled, relocatable object ﬁle
                       that may be linked into anyprogram; a ﬁle with a .ms sufﬁx is usually a text ﬁle to be processed by Troffwith the
                       −ms macro package, and so on. One of the best aspects of both Makeand PMakecomes from their understanding of
                       howthe sufﬁx of a ﬁle pertains to its contents and their ability to do things with a ﬁle based solely on its sufﬁx. This
                       ability comes from something known as a transformation rule. A transformation rule speciﬁes howtochange a ﬁle
                       with one sufﬁx into a ﬁle with another sufﬁx.
                       Atransformation rule looks much likeadependencyline, except the target is made of twoknown sufﬁxes stuck to-
                       gether.Sufﬁxes are made known to PMakebyplacing them as sources on a dependencyline whose target is the spe-
                       cial target .SUFFIXES.E.g.
                               .SUFFIXES : .o .c
                               .c.o :
                                           $(CC) $(CFLAGS) -c $(.IMPSRC)
                       The creation script attached to the target is used to transform a ﬁle with the ﬁrst sufﬁx (in this case, .c)into a ﬁle
                       with the second sufﬁx (here, .o). In addition, the target inherits whateverattributes have been applied to the trans-
                       formation rule. The simple rule givenabove says that to transform a C source ﬁle into an object ﬁle, you compile it
                       using cc with the −c ﬂag. This rule is taken straight from the system makeﬁle. Manytransformation rules (and suf-
                       ﬁxes) are deﬁned there, and I refer you to it for more examples (type ‘‘pmake -h’’ toﬁnd out where it is).
                       There are several things to note about the transformation rule givenabove:
                             1) The.IMPSRCvariable. This variable is set to the ‘‘implied source’’(the ﬁle from which the target is
                                   being created; the one with the ﬁrst sufﬁx), which, in this case, is the .c ﬁle.
                             2) The CFLAGS variable. Almost all of the transformation rules in the system makeﬁle are set up using
                                   variables that you can alter in your makeﬁle to tailor the rule to your needs. In this case, if you want all
                                   your C ﬁles to be compiled with the −g ﬂag, to provide information for dbx,you would set the
                                   CFLAGSvariable to contain -g (‘‘CFLAGS = -g’’)and PMakewould takecare of the rest.
                       To giv e you a quick example, the makeﬁle 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
                                                                                      1
                       The transformation rule I gav e above takes the place of the 6 lines
                               a.o :a.c
                                           cc -c a.c
                               b.o :b.c
                                           cc -c b.c
                               c.o :c.c
                                           cc -c c.c
                       Nowyou may be wondering about the dependencybetween the .o and .c ﬁles — it’snot mentioned anywhere in
                       the newmakeﬁle. This is because it isn’tneeded: one of the effects of applying a transformation rule is the target
                       comes to depend on the implied source. That’swhy it’scalled the implied source.
                       Foramore detailed example. Say you have a makeﬁle likethis:
                               a.out :a.o b.o
                                           $(CC) $(.ALLSRC)
                       and a directory set up likethis:
                         1 This is also somewhat cleaner,Ithink, than the dynamic source solution presented in 2.6
             PSD:12-14 PMake—ATutorial
                  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’smuch more informative totype ‘‘pmake -d s’’.This will
             showyou what PMakeisuptoasitprocesses the ﬁles. In this case, PMakeprints 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_FindDepsis the name of a function in PMakethat 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 deﬁned (a
             transformation may be deﬁned multiple times, by the way,but only the most recent one will be used). You will no-
             tice, however, that there is a deﬁnite order to the sufﬁxes that are tried. This order is set by the relative positions of
             the sufﬁxes on the .SUFFIXES line — the earlier a sufﬁx appears, the earlier it is checked as the source of a trans-
             formation. Once a sufﬁx has been deﬁned, the only way to change its position in the pecking order is to remove all
             the sufﬁxes (by having a .SUFFIXES dependencyline with no sources) and redeﬁne them in the order you want.
             (Previously-deﬁned transformation rules will be automatically redeﬁned as the sufﬁxes theyinv olveare re-entered.)
             Another way to affect the search order is to makethe dependencyexplicit. In the above example, a.out depends on
             a.oandb.o.Since a transformation exists from .o to .out,PMakeuses that, as indicated by the ‘‘using ex-
             isting source a.o’’ message.
             The search for a transformation starts from the sufﬁx of the target and continues through all the deﬁned transforma-
             tions, in the order dictated by the sufﬁx ranking, until an existing ﬁle with the same base (the target name minus the
             sufﬁx and anyleading directories) is found. At that point, one or more transformation rules will have been found to
             change the one existing ﬁle into the target.
             Forexample, ignoring what’sinthe system makeﬁle for now, say you have a makeﬁle likethis:
          PMake—ATutorial PSD:12-15
              .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 ﬁle jive.l.Ifyou 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: PMakestarts with the target jive.out,ﬁgures out its sufﬁx (.out)and looks for things it can
          transform to a .out ﬁle. In this case, it only ﬁnds .o,soitlooks for the ﬁle jive.o.Itfails to ﬁnd it, so it looks
          for transformations into a .o ﬁle. Again it has only one choice: .c.Soitlooks for jive.c and, as you know, fails
          to ﬁnd it. At this point it has twochoices: it can create the .c ﬁle from either a .y ﬁle or a .l ﬁle. Since .y came
          ﬁrst on the .SUFFIXES line, it checks for jive.y ﬁrst, but can’tﬁnd it, so it looks for jive.l and, lo and be-
          hold, there it is. At this point, it has deﬁned a transformation path as follows: .l → .c → .o → .out and applies
          the transformation rules accordingly.For completeness, and to give you a better idea of what PMakeactually did
          with this three-step transformation, this is what PMakeprinted for the rest of the process:
              Suff_FindDeps (jive.o)
                using existing source jive.c
                applying .c -> .o to "jive.c"
              Suff_FindDeps (jive.c)
                using existing source jive.l
                applying .l -> .c to "jive.l"
              Suff_FindDeps (jive.l)
              Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
              Examining jive.c...non-existent...out-of-date
              --- jive.c ---
              lex jive.l
              ... meaningless lex output deleted ...
              mv lex.yy.c jive.c
              Examining jive.o...non-existent...out-of-date
              --- jive.o ---
              cc -c jive.c
              Examining jive.out...non-existent...out-of-date
              --- jive.out ---
              cc -o jive.out jive.o
          One ﬁnal question remains: what does PMakedowith targets that have noknown sufﬁx? PMakesimply pretends it
          actually has a known sufﬁx and searches for transformations accordingly.The sufﬁx it chooses is the source for the
          .NULLtarget mentioned later.Inthe system makeﬁle, .out is chosen as the ‘‘null sufﬁx’’because most people use
          PMaketocreate programs. You are, however, free and welcome to change it to a sufﬁx of your own choosing. The
          PSD:12-16 PMake—ATutorial
          null sufﬁx is ignored, however, when PMakeisincompatibility mode (see chapter 4).
          3.2. Including Other Makeﬁles
          Just as for programs, it is often useful to extract certain parts of a makeﬁle into another ﬁle and just include it in
          other makeﬁles somehow. Manycompilers allowyou say something like
              #include "defs.h"
          to include the contents of defs.h in the source ﬁle. PMakeallows you to do the same thing for makeﬁles, with the
          added ability to use variables in the ﬁlenames. An include directive inamakeﬁle looks either likethis:
              #include <file>
          or this
              #include "file"
          The difference between the twoiswhere PMakesearches for the ﬁle: the ﬁrst way,PMakewill look for the ﬁle only
          in the system makeﬁle directory (or directories) (to ﬁnd out what that directory is, give PMakethe −h ﬂag). The
          system makeﬁle directory search path can be overridden via the −m option. For ﬁles in double-quotes, the search is
          more complex:
             1) Thedirectory of the makeﬁle that’sincluding the ﬁle.
             2) Thecurrent directory (the one in which you invokedPMake).
             3) Thedirectories givenbyyou using −I ﬂags, in the order in which you gav e them.
             4) Directories givenby .PATH dependencylines (see chapter 4).
             5) Thesystem makeﬁle directory.
          in that order.
          Youare free to use PMakevariables in the ﬁlename—PMakewill expand them before searching for the ﬁle. You
          must specify the searching method with either angle brackets or double-quotes outside of a variable expansion. I.e.
          the following
              SYSTEM = <command.mk>
              #include $(SYSTEM)
          won’twork.
          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.
          Forinstance: 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’tberun more than once in the same directory at the
          same time (each one creates a ﬁle called __.SYMDEF into which it stuffs information for the linker to use. Two of
          them running at once will overwrite each other’sﬁle and the result will be garbage for both parties). You might want
          away to save the ranlib commands til the end so theycan be run one after the other,thus keeping them from trashing
          each other’sﬁle. PMakeallows you to do this by inserting an ellipsis (‘‘. . .’’)asacommand between commands to
          be run at once and those to be run later.
          So for the ranlib case above,you might do this:
           PMake—ATutorial PSD:12-17
               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 theywould run one after the other (using the correct value for the .TARGET variable,
           of course).
           Commands savedinthis manner are only executed if PMakemanages to re-create everything without an error.
           3.4. Target Attributes
           PMakeallows you to give attributes to targets by means of special sources. Likeeverything else PMakeuses, 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ﬁnd uses for yourself. Think of it as ‘‘an exercise
           for the reader.’’Byplacing one (or more) of these as a source on a dependencyline, you are ‘‘marking the target(s)
           with that attribute.’’ That’sjust the way I phrase it, so you know.
           Anyattributes givenassources 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 PMakecan’tﬁgure out howtocreate it, it will ignore this
                 fact and assume the ﬁle isn’treally needed or actually exists and PMakejust can’tﬁnd it. This may
                 prove wrong, but the error will be noted later on, not when PMaketries to create the target so marked.
                 This attribute also prevents PMakefrom attempting to touch the target if it is giventhe −t ﬂag.
           .EXEC Thisattribute 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 ﬁles that need
                 to be compiled and loaded into a LISP process. Todothis, you echo LISP commands into a ﬁle and
                 execute a LISP with this ﬁle as its input when everything’sdone. Say also that you have toload other
                 ﬁles from another system before you can compile your ﬁles and further,that you don’twant to go
                 through the loading and dumping unless one of your ﬁles has changed. Your makeﬁle might look a
                 little bit likethis (remember,this is an educational example, and don’tworry about the COMPILE
                 rule, all will soon become clear,grasshopper):
             PSD:12-18 PMake—ATutorial
                          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’tappear in the local variables of targets that depend on them (nor are they
                     touched if PMakeisgiv enthe −t ﬂag). Note that all the rules, not just that for system,include
                     initas a source. This is because none of the other targets can be made until init has been made,
                     thus theydepend on it.
             .EXPORTThis is used to mark those targets whose creation should be sent to another machine if at all possible.
                     This may be used by some exportation schemes if the exportation is expensive.You should ask your
                     system administrator if it is necessary.
             .EXPORTSAME
                     Tells the export system that the job should be exported to a machine of the same architecture as the
                     current one. Certain operations (e.g. running text through nroff)can be performed the same on any
                     architecture (CPU and operating system type), while others (e.g. compiling a program with cc)must
                     be performed on a machine with the same architecture. Not all export systems will support this at-
                     tribute.
             .IGNORE Giving a target the .IGNORE attribute causes PMaketoignore errors from anyofthe target’scom-
                     mands, as if theyall had ‘−’ before them.
             .INVISIBLE This allows you to specify one target as a source for another without the one affecting the other’slo-
                     cal variables. Useful if, say,you have a makeﬁle that creates twoprograms, one of which is used to
                     create the other,soitmust 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 vari-
                     able containing the right things. Without the .INVISIBLE attribute for prog2,the MAKEINSTALL
                     rule couldn’tbeapplied. 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 Thisis another way to avoid performing some operations in parallel while permitting everything else
                     to be done so. Speciﬁcally it forces the target’sshell script to be executed only if one or more of the
                     sources was out-of-date. In addition, the target’sname, in both its .TARGET variable and all the local
                     variables of anytarget 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 men-
                     tioned earlier,only this time it’smore severe: you can’tjust put the ranlib offtothe end since the pro-
                     gram will need those libraries before it can be re-created. You can do something likethis:
             PMake—ATutorial PSD:12-19
                         program : $(OBJS) libraries
                               cc -o $(.TARGET) $(.ALLSRC)
                         libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
                               ranlib $(.OODATE)
                    In this case, PMakewill re-create the $(OBJS) as necessary,along with lib1.a, lib2.a,
                    lib3.aandlib4.a.Itwill then execute ranlib on anylibrary that was changed and set pro-
                    gram’s .ALLSRC variable to contain what’sin $(OBJS) followed by ‘‘lib1.a lib2.a
                    lib3.a lib4.a.’’Incase you’re wondering, it’scalled .JOIN because it joins together different
                    threads of the ‘‘input graph’’atthe target marked with the attribute. Another aspect of the .JOIN at-
                    tribute is it keeps the target from being created if the −t ﬂag was given.
             .MAKE The.MAKEattribute marks its target as being a recursive inv ocation of PMake. This forces PMaketo
                    execute the script associated with the target (if it’sout-of-date) evenifyou gav e the −n or −t ﬂag. By
                    doing this, you can start at the top of a system and type
                         pmake -n
                    and have itdescend the directory tree (if your makeﬁles are set up correctly), printing what it would
                    have executed if you hadn’tincluded the −n ﬂag.
             .NOEXPORT
                    If possible, PMakewill attempt to export the creation of all targets to another machine (this depends
                    on howPMakewas conﬁgured). Sometimes, the creation is so simple, it is pointless to send it to an-
                    other machine. If you give the target the .NOEXPORT attribute, it will be run locally,evenifyou’ve
                    givenPMakethe−L 0ﬂag.
             .NOTMAIN Normally,ifyou do not specify a target to makeinany other way,PMakewill takethe ﬁrst target on
                    the ﬁrst dependencyline of a makeﬁle as the target to create. That target is known as the ‘‘Main Tar-
                    get’’and is labeled as such if you print the dependencies out using the −p ﬂag. Giving a target this
                    attribute tells PMakethat the target is deﬁnitely not the Main Target. This allows you to place targets
                    in an included makeﬁle and have PMakecreate something else by default.
             .PRECIOUS When PMakeisinterrupted (you type control-C at the keyboard), it will attempt to clean up after it-
                    self by removing anyhalf-made targets. If a target has the .PRECIOUS attribute, however, PMake
                    will leave italone. An additional side effect of the ‘::’ operator is to mark the targets as .PRECIOUS.
             .SILENT Marking atarget with this attribute keeps its commands from being printed when they’re executed,
                    just as if theyhad an ‘@’ in front of them.
             .USE Bygiving a target this attribute, you turn it into PMake’sequivalent 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)ofthe source. If the target already has commands, the .USE target’scommands are
                    added to the end. If more than one .USE-marked source is giventoatarget, the rules are applied se-
                    quentially.
                    The typical .USE rule (as I call them) will use the sources of the target to which it is applied (as
                    stored in the .ALLSRC variable for the target) as its ‘‘arguments,’’ ifyou will. Forexample, you
                    probably noticed that the commands 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, likeso:
                         lib1.a :$(LIB1OBJS) MAKELIB
                         lib2.a :$(LIB2OBJS) MAKELIB
                         MAKELIB : .USE
                               rm -f $(.TARGET)
                               ar cr $(.TARGET) $(.ALLSRC)
                               ...
                               ranlib $(.TARGET)
            PSD:12-20 PMake—ATutorial
                    Several system makeﬁles (not to be confused with The System Makeﬁle) makeuse of these .USE
                    rules to makeyour life easier (they’re in the default, system makeﬁle directory...takealook). Note
                    that the .USE rule source itself (MAKELIB)does not appear in anyofthe targets’slocal variables.
                    There is no limit to the number of times I could use the MAKELIB rule. If there were more libraries, I
                    could continue with ‘‘lib3.a : $(LIB3OBJS) MAKELIB’’ and so on and so forth.
            3.5. Special Targets
            As there were in Make, so there are certain targets that have special meaning to PMake. When you use one on a de-
            pendencyline, it is the only target that may appear on the left-hand-side of the operator.Asfor the attributes and
            variables, all the special targets begin with a period and consist of upper-case letters only.Iwon’tdescribe them all
            in detail because some of them are rather complexand I’ll describe them in more detail than you’ll want in chapter
            4. The targets are as follows:
            .BEGIN Anycommands 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 anytarget (that was used only as a source) that PMakecan’tﬁgure out
                   anyother way to create. It’sonly ‘‘sort of’’ a .USE rule because only the shell script attached to the
                   .DEFAULT target is used. The .IMPSRC variable of a target that inherits .DEFAULT’s commands is
                   set to the target’sown name.
            .END Thisserves a function similar to .BEGIN,inthat commands attached to it are executed once everything
                   has been re-created (so long as no errors occurred). It also serves the extra function of being a place on
                   which PMakecan hang commands you put offtothe end. Thus the script for this target will be executed
                   before anyofthe commands you save with the ‘‘. . .’’.
            .EXPORTThe sources for this target are passed to the exportation system compiled into PMake. Some systems
                   will use these sources to conﬁgure themselves. You should ask your system administrator about this.
            .IGNORE Thistarget marks each of its sources with the .IGNORE attribute. If you don’tgiv e it anysources, then
                   it is likegiving the −i ﬂag when you invoke PMake—errors are ignored for all commands.
            .INCLUDES
                   The sources for this target are taken to be sufﬁxes that indicate a ﬁle that can be included in a program
                   source ﬁle. The sufﬁx must have already been declared with .SUFFIXES (see below). Anysufﬁx so
                   marked will have the directories on its search path (see .PATH,below) placed in the .INCLUDES vari-
                   able, each preceded by a −I ﬂag. This variable can then be used as an argument for the compiler in the
                   normal fashion. The .h sufﬁx is already marked in this way in the system makeﬁle. E.g. if you have
                       .SUFFIXES : .bitmap
                       .PATH.bitmap : /usr/local/X/lib/bitmaps
                       .INCLUDES : .bitmap
                   PMakewill place ‘‘-I/usr/local/X/lib/bitmaps’’ inthe .INCLUDES variable and you can
                   then say
                       cc $(.INCLUDES) -c xprogram.c
                   (Note: the .INCLUDES variable is not actually ﬁlled in until the entire makeﬁle has been read.)
            .INTERRUPT
                   When PMakeisinterrupted, it will execute the commands in the script for this target, if it exists.
            .LIBS Thisdoes for libraries what .INCLUDES does for include ﬁles, except the ﬂag used is −L,asrequired
                   by those linkers that allowyou to tell them where to ﬁnd libraries. The variable used is .LIBS.Be
                   forewarned that PMakemay not have been compiled to do this if the linker on your system doesn’tac-
                   cept the −L ﬂag, though the .LIBS variable will always be deﬁned once the makeﬁle has been read.
            .MAIN Ifyou didn’tgiv e atarget (or targets) to create when you invokedPMake, it will takethe sources of this
                   target as the targets to create.
          PMake—ATutorial PSD:12-21
          .MAKEFLAGS
               This target provides a way for you to always specify ﬂags for PMakewhen the makeﬁle is used. The
               ﬂags are just as theywould be typed to the shell (except you can’tuse shell variables unless they’re in
               the environment), though the −f and −r ﬂags have noeffect.
          .NULL Thisallows you to specify what sufﬁx PMakeshould pretend a ﬁle has if, in fact, it has no known sufﬁx.
               Only one sufﬁx may be so designated. The last source on the dependencyline is the sufﬁx that is used
               (you should, however, only give one sufﬁx. . .).
          .PATHIfyou give sources for this target, PMakewill takethem as directories in which to search for ﬁles it can-
               not ﬁnd in the current directory.Ifyou give nosources, it will clear out anydirectories added to the
               search path before. Since the effects of this all get very complex, I’ll leave ittil chapter four to give you
               acomplete explanation.
          .PATHsufﬁx
               This does a similar thing to .PATH,but it does it only for ﬁles with the givensufﬁx. The sufﬁx must
               have been deﬁned already.Look at Search Paths (section 4.1) for more information.
          .PRECIOUS
               Similar to .IGNORE,this givesthe .PRECIOUS attribute to each source on the dependencyline, un-
               less there are no sources, in which case the .PRECIOUS attribute is giventoevery target in the ﬁle.
          .RECURSIVE
               This target applies the .MAKE attribute to all its sources. It does nothing if you don’tgiv e it anysources.
          .SHELL PMakeisnot constrained to only using the Bourne shell to execute the commands you put in the make-
               ﬁle. You can tell it some other shell to use with this target. Check out AShell is a Shell is a Shell (sec-
               tion 4.4) for more information.
          .SILENT Whenyou use.SILENTas a target, it applies the .SILENT attribute to each of its sources. If there are
               no sources on the dependencyline, then it is as if you gav e PMakethe −s ﬂag and no commands will be
               echoed.
          .SUFFIXES
               This is used to give new ﬁle sufﬁxes for PMaketohandle. Each source is a sufﬁx PMakeshould recog-
               nize. If you give a .SUFFIXES dependencyline with no sources, PMakewill forget about all the suf-
               ﬁxes it knew(this also nukes the null sufﬁx). For those targets that need to have sufﬁxes deﬁned, this is
               howyou do it.
          In addition to these targets, a line of the form
              attribute : sources
          applies the attribute to all the targets listed as sources.
          3.6. Modifying Variable Expansion
          Variables need not always be expanded verbatim. PMakedeﬁnes several modiﬁers that may be applied to a vari-
          able’svalue before it is expanded. You apply a modiﬁer by placing it after the variable name with a colon between
          the two, likeso:
              ${VARIABLE:modiﬁer}
          Each modiﬁer is a single character followed by something speciﬁc to the modiﬁer itself. Youmay apply as many
          modiﬁers as you want — each one is applied to the result of the previous and is separated from the previous by an-
          other colon.
          There are sevenways to modify a variable’sexpansion, most of which come from the C shell variable modiﬁcation
          characters:
             Mpattern
               This is used to select only those words (a word is a series of characters that are neither spaces nor tabs)
               that match the given pattern.The pattern is a wildcard pattern likethat used by the shell, where *
               means 0 or more characters of anysort; ? is anysingle character; [abcd] matches anysingle charac-
               ter that is either ‘a’, ‘b’, ‘c’ or ‘d’ (there may be anynumber of characters between the brackets);
          PSD:12-22 PMake—ATutorial
               [0-9]matches anysingle character that is between ‘0’ and ‘9’ (i.e. anydigit. This form may be freely
               mixed with the other bracket form), and ‘\’ is used to escape anyofthe characters ‘*’, ‘?’, ‘[’ or ‘:’,
               leaving them as regular characters to match themselves in a word. For example, the system makeﬁle
               <makedepend.mk>uses ‘‘$(CFLAGS:M-[ID]*)’’ toextract all the −I and −D ﬂags that would be
               passed to the C compiler.This allows it to properly locate include ﬁles and generate the correct depen-
               dencies.
             Npattern
               This is identical to :M except it substitutes all words that don’tmatch the givenpattern.
             S/search-string/replacement-string/[g]
               Causes the ﬁrst occurrence of search-string in the variable to be replaced by replacement-string,unless
               the g ﬂag is givenatthe 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 twomay be combined to force an exact match). If a backslash precedes these twochar-
               acters, however, theylose their special meaning. Variable expansion also occurs in the normal fashion
               inside both the search-string and the replacement-string, except that a backslash is used to prevent the
               expansion of a $,not another dollar sign, as is usual. Note that search-string is just a string, not a pat-
               tern, so none of the usual regular-expression/wildcard characters have any special meaning save ˆ and
               $.Inthe replacement string, the & character is replaced by the search-string unless it is preceded by a
               backslash. You are allowed to use anycharacter 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 back-
               slash.
             TReplaces 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.’’
             HThis is similar to :T,except that every word is replaced by everything but the tail (the ‘‘head’’). Using
               the same deﬁnition of OBJS,the string ‘‘$(OBJS:H)’’ would expand to ‘‘../lib /usr/lib.’’
               Note that the ﬁnal slash on the heads is removedand anything without a head is replaced by the empty
               string.
             E :Ereplaces each word by its sufﬁx (‘‘extension’’). So ‘‘$(OBJS:E)’’ would give you ‘‘.o .a.’’
             RThis replaces each word by everything but the sufﬁx (the ‘‘root’’ofthe word). ‘‘$(OBJS:R)’’ expands
               to ‘‘ ../lib/a b /usr/lib/libm.’’
          In addition, the System V style of substitution is also supported. This looks like:
              $(VARIABLE:search-string=replacement)
          It must be the last modiﬁer in the chain. The search is anchored at the end of each word, so only sufﬁxes or whole
          words may be replaced.
          3.7. MoreonDebugging
          3.8. MoreExercises
          (3.1) You’ve got a set programs, each of which is created from its own assembly-language source ﬁle (sufﬁx
             .asm). Each program can be assembled into twoversions, one with error-checking code assembled in and
             one without. You could assemble them into ﬁles with different sufﬁxes (.eobj and .obj,for instance), but
             your linker only understands ﬁles that end in .obj.Totop it all off, the ﬁnal executables must have the sufﬁx
             .exe.How can you still use transformation rules to makeyour life easier (Hint: assume the error-checking
             versions have ec tacked onto their preﬁx)?
          (3.2) Assume, for a moment or two, you want to perform a sort of ‘‘indirection’’byplacing the name of a variable
             into another one, then you want to get the value of the ﬁrst by expanding the second somehow. Unfortunately,
             PMakedoesn’tallowconstructs like
           PMake—ATutorial PSD:12-23
                  $($(FOO))
              What do you do? Hint: no further variable expansion is performed after modiﬁers are applied, thus if you
              cause a $ to occur in the expansion, that’swhat will be in the result.
           4. PMakefor Gods
           This chapter is devoted to those facilities in PMakethat allowyou to do a great deal in a makeﬁle with very little
           work, as well as do some things you couldn’tdoinMakewithout a great deal of work (and perhaps the use of other
           programs). The problem with these features, is theymust be handled with care, or you will end up with a mess.
           Once more, I assume a greater familiarity with or Sprite than I did in the previous twochapters.
           4.1. Search Paths
           PMakesupports the dispersal of ﬁles into multiple directories by allowing you to specify places to look for sources
           with .PATH targets in the makeﬁle. The directories you give assources for these targets makeupa‘‘search path.’’
           Only those ﬁles used exclusively as sources are actually sought on a search path, the assumption being that anything
           listed as a target in the makeﬁle can be created by the makeﬁle and thus should be in the current directory.
           There are twotypes of search paths in PMake: one is used for all types of ﬁles (including included makeﬁles) and is
           speciﬁed with a plain .PATH target (e.g. ‘‘.PATH : RCS’’), while the other is speciﬁc to a certain type of ﬁle, as
           indicated by the ﬁle’ssufﬁx. A speciﬁc search path is indicated by immediately following the .PATH with the sufﬁx
           of the ﬁle. For instance
               .PATH.h : /sprite/lib/include /sprite/att/lib/include
           would tell PMaketolook in the directories /sprite/lib/include and /sprite/att/lib/include for
           anyﬁles whose sufﬁx is .h.
           The current directory is always consulted ﬁrst to see if a ﬁle exists. Only if it cannot be found there are the directo-
           ries in the speciﬁc search path, followed by those in the general search path, consulted.
           Asearch path is also used when expanding wildcard characters. If the pattern has a recognizable sufﬁx on it, the path
           for that sufﬁx will be used for the expansion. Otherwise the default search path is employed.
           When a ﬁle is found in some directory other than the current one, all local variables that would have contained the
           target’sname (.ALLSRC,and .IMPSRC)will instead contain the path to the ﬁle, as found by PMake. Thus if you
           have a ﬁle ../lib/mumble.cand a makeﬁle
               .PATH.c : ../lib
               mumble :mumble.c
                    $(CC) -o $(.TARGET) $(.ALLSRC)
           the command executed to create mumble would be ‘‘cc -o mumble ../lib/mumble.c.’’(As an aside, the
           command in this case isn’tstrictly necessary,since it will be found using transformation rules if it isn’tgiv en. This is
           because .out is the null sufﬁx by default and a transformation exists from .c to .out.Just thought I’dthrowthat
           in.)
           If a ﬁle exists in twodirectories on the same search path, the ﬁle in the ﬁrst directory on the path will be the one
           PMakeuses. So if you have a large system spread overmanydirectories, it would behoove you to followanaming
           convention that avoids such conﬂicts.
           Something you should knowabout the way search paths are implemented is that each directory is read, and its con-
           tents cached, exactly once — when it is ﬁrst encountered — so anychanges to the directories while PMakeisrun-
           ning will not be noted when searching for implicit sources, nor will theybefound when PMakeattempts to discover
           when the ﬁle was last modiﬁed, unless the ﬁle was created in the current directory.While people have suggested that
           PMakeshould read the directories each time, my experience suggests that the caching seldom causes problems. In
           addition, not caching the directories slows things down enormously because of PMake’sattempts to apply transfor-
           mation rules through non-existent ﬁles — the number of extra ﬁle-system searches is truly staggering, especially if
           manyﬁles without sufﬁxes are used and the null sufﬁx isn’tchanged from .out.
           PSD:12-24 PMake—ATutorial
           4.2. Archivesand Libraries
           and Sprite allowyou to merge ﬁles into an archive using the ar command. Further,ifthe ﬁles are relocatable object
           ﬁles, you can run ranlib on the archive and get yourself a library that you can link into anyprogram you want.
           The main problem with archivesistheydouble the space you need to store the archivedﬁles, since there’sone copy
           in the archive and one copyout by itself. The problem with libraries is you usually think of them as -lm rather than
           /usr/lib/libm.aand the linker thinks they’re out-of-date if you so much as look at them.
           PMakesolves the problem with archivesbyallowing you to tell it to examine the ﬁles in the archives(so you can re-
           move the individual ﬁles without having to regenerate them later). Tohandle the problem with libraries, PMakeadds
           an additional way of deciding if a library is out-of-date:
           •Ifthe table of contents is older than the library,orismissing, the library is out-of-date.
           Alibrary is anytarget that looks like‘‘−lname’’orthat ends in a sufﬁx that was marked as a library using the
           .LIBStarget. .a is so marked in the system makeﬁle.
           Members of an archive are speciﬁed as ‘‘archive(member[ member...])’’. Thus ‘‘’libdix.a(window.o)’’ speci-
           ﬁes the ﬁle window.o in the archive libdix.a.You may also use wildcards to specify the members of the
           archive.Just remember that most the wildcard characters will only ﬁnd existing ﬁles.
           Aﬁle that is a member of an archive istreated specially.Ifthe ﬁle doesn’texist, but it is in the archive,the modiﬁca-
           tion time recorded in the archive isused for the ﬁle when determining if the ﬁle is out-of-date. When ﬁguring out
           howtomakeanarchivedmember target (not the ﬁle itself, but the ﬁle in the archive — the archive(member)target),
           special care is taken with the transformation rules, as follows:
           • archive(member)ismade to depend on member.
           •The transformation from the member’s sufﬁx to the archive’s sufﬁx is applied to the archive(member)target.
           •Thearchive(member)’s .TARGET variable is set to the name of the member if member is actually a target, or the
            path to the member ﬁle if member is only a source.
           •The.ARCHIVEvariable for the archive(member)target is set to the name of the archive.
           •The.MEMBERvariable is set to the actual string inside the parentheses. In most cases, this will be the same as the
            .TARGETvariable.
           •The archive(member)’splace in the local variables of the targets that depend on it is taken by the value of its
            .TARGETvariable.
           Thus, a program library could be created with the following makeﬁle:
               .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 ﬁles to be compiled (if the corresponding source ﬁles were modiﬁed after the object
           ﬁle or,ifthat doesn’texist, the archivedobject ﬁle), the out-of-date ones archivedin libprog.a,atable of con-
           tents placed in the archive and the newly-archivedobject ﬁles to be removed.
           All this is used in the makelib.mk system makeﬁle to create a single library with ease. This makeﬁle looks like
           this:
                PMake—ATutorial PSD:12-25
                      #
                      #Rules for making libraries. The object files that make up the library
                      #are removed once they are archived.
                      #
                      #Tomake several libraries in parallel, you should define the variable
                      #"many_libraries". This will serialize the invocations of ranlib.
                      #
                      #Touse, 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...
                Likethe C compiler before it, PMakeallows you to conﬁgure the makeﬁle, based on the current environment, using
                conditional statements. A conditional looks likethis:
                      #if boolean expression
                      lines
                      #elif another boolean expression
                      morelines
                      #else
                      still morelines
                      #endif
                Theymay be nested to a maximum depth of 30 and may occur anywhere (except in a comment, of course). The ‘‘#’’
                must the very ﬁrst character on the line.
                PSD:12-26 PMake—ATutorial
                Each boolean expression is made up of terms that look likefunction calls, the standard C boolean operators &&, ||,
                and !,and the standard relational operators ==, !=, >, >=, <,and <=,with == and != being overloaded to allow
                string comparisons as well. && represents logical AND; || is logical OR and ! is logical NOT. The arithmetic and
                string operators takeprecedence overall three of these operators, while NOTtakes precedence overAND, which
                takes precedence overOR. This precedence may be overridden with parentheses, and an expression may be paren-
                thesized to your heart’scontent. Each term looks likeacall on one of four functions:
                makeThe syntax is make(target) where target is a target in the makeﬁle. This is true if the giventarget was
                        speciﬁed 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 givenonthe command line).
                deﬁned Thesyntax is defined(variable) and is true if variable is deﬁned. Certain variables are deﬁned in the
                        system makeﬁle that identify the system on which PMakeisbeing run.
                exists Thesyntax isexists(ﬁle)and is true if the ﬁle can be found on the global search path (i.e. that deﬁned
                        by .PATHtargets, not by .PATHsufﬁx targets).
                empty This syntax is much likethe others, except the string inside the parentheses is of the same form as you
                        would put between parentheses when expanding a variable, complete with modiﬁers and everything. The
                        function returns true if the resulting string is empty (NOTE: an undeﬁned variable in this context will
                        cause at the very least a warning message about a malformed conditional, and at the worst will cause the
                        process to stop once it has read the makeﬁle. If you want to check for a variable being deﬁned or empty,
                        use the expression ‘‘!defined(var)||empty(var)’’ asthe deﬁnition of || will prevent the
                        empty() from being evaluated and causing an error,ifthe variable is undeﬁned). This can be used to
                        see if a variable contains a givenword, for example:
                              #if !empty(var:Mword)
                The arithmetic and string operators may only be used to test the value of a variable. The lefthand side must contain
                the variable expansion, while the righthand side contains either a string, enclosed in double-quotes, or a number.The
                standard C numeric conventions (except for specifying an octal number) apply to both sides. E.g.
                      #if $(OS) == 4.3
                      #if $(MACHINE) == "sun3"
                      #if $(LOAD_ADDR) < 0xc000
                are all valid conditionals. In addition, the numeric value of a variable can be tested as a boolean as follows:
                      #if $(LOAD)
                would see if LOAD contains a non-zero value and
                      #if !$(LOAD)
                would test if LOAD contains a zero value.
                In addition to the bare ‘‘#if,’’there are other forms that apply one of the ﬁrst twofunctions to each term. Theyare
                as follows:
                          ifdef    deﬁned
                          ifndef   !deﬁned
                          ifmake   make
                          ifnmake !make
                There are also the ‘‘else if’’ forms: elif, elifdef, elifndef, elifmake,and elifnmake.
                Forinstance, if you wish to create twoversions of a program, one of which is optimized (the production version) and
                the other of which is for debugging (has symbols for dbx), you have two choices: you can create twomakeﬁles, one
                of which uses the −g ﬂag for the compilation, while the other uses the −O ﬂag, or you can use another target (call it
                debug)tocreate the debug version. The construct belowwill takecare of this for you. I have also made it so deﬁn-
                ing the variable DEBUG (say with pmake -D DEBUG)will also cause the debug version to be made.
           PMake—ATutorial PSD:12-27
               #if defined(DEBUG) || make(debug)
               CFLAGS += -g
               #else
               CFLAGS += -O
               #endif
           There are, of course, problems with this approach. The most glaring annoyance is that if you want to go from mak-
           ing a debug version to making a production version, you have toremove all the object ﬁles, or you will get some op-
           timized and some debug versions in the same program. Another annoyance is you have tobecareful not to maketwo
           targets that ‘‘conﬂict’’because of some conditionals in the makeﬁle. For instance
               #if make(print)
               FORMATTER = ditroff -Plaser_printer
               #endif
               #if make(draft)
               FORMATTER = nroff -Pdot_matrix_printer
               #endif
           would wreak havocifyou 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’’)isused to execute the commands to re-create targets.
           PMakealso allows you to specify a different shell for it to use when executing these commands. There are several
           things PMakemust knowabout the shell you wish to use. These things are speciﬁed as the sources for the .SHELL
           target by keyword, as follows:
           path=path
              PMakeneeds to knowwhere the shell actually resides, so it can execute it. If you specify this and nothing else,
              PMakewill use the last component of the path and look in its table of the shells it knows and use the speciﬁca-
              tion it ﬁnds, if any. Use this if you just want to use a different version of the Bourne or C Shell (yes, PMake
              knows howtouse 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ﬁed
              (other than path), it is the name by which PMakeattempts to ﬁnd a speciﬁcation for it (as mentioned above).
              Youcan use this if you would just rather use the C Shell than the Bourne Shell (‘‘.SHELL: name=csh’’
              will do it).
           quiet=echo-offcommand
              As mentioned before, PMakeactually controls whether commands are printed by introducing commands into
              the shell’sinput stream. This keyword, and the next two, control what those commands are. The quiet key-
              word is the command used to turn echoing off. Once it is turned off, echoing is expected to remain offuntil
              the echo-on command is given.
           echo=echo-on command
              The command PMakeshould give toturn echoing back on again.
           ﬁlter=printed echo-offcommand
              Manyshells will echo the echo-offcommand when it is given. This keyword tells PMakeinwhat format the
              shell actually prints the echo-offcommand. WhereverPMakesees this string in the shell’soutput, it will
              delete it and anyfollowing whitespace, up to and including the next newline. See the example at the end of
              this section for more details.
           echoFlag=ﬂagtoturn echoing on
              Unless a target has been marked .SILENT,PMakewants to start the shell running with echoing on. Todo
              this, it passes this ﬂag to the shell as one of its arguments. If either this or the next ﬂag begins with a ‘−’, the
              ﬂags will be passed to the shell as separate arguments. Otherwise, the twowill be concatenated (if theyare
              used at the same time, of course).
          PSD:12-28 PMake—ATutorial
          errFlag=ﬂagtoturn error checking on
             Likewise, unless a target is marked .IGNORE,PMakewishes error-checking to be on from the very start. To
             this end, it will pass this ﬂag 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 achievedbyinserting commands into the shell’sinput stream. This is
             the command to makethe shell check for errors. It also serves another purpose if the shell doesn’thav e error-
             control as commands, but I’ll get into that in a minute. Again, once error checking has been turned on, it is ex-
             pected to remain on until it is turned offagain.
          ignore=command to turn error checking off
             This is the command PMakeuses to turn error checking off. It has another use if the shell doesn’tdoerror-
             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 PMakeifthe shell can do error-control, but you’dbewrong. If hasErrCtl is
             yes,PMakeuses the check and ignore commands in a straight-forward manner.Ifthis is no,howev er, their
             use is rather different. In this case, the check command is used as a template, in which the string %s is re-
             placed by the command that’sabout to be executed, to produce a command for the shell that will echo the
             command to be executed. The ignore command is also used as a template, again with %s replaced by the
             command to be executed, to produce a command that will execute the command to be executed and ignore any
             error it returns. When these strings are used as templates, you must provide newline(s) (‘‘\n’’)inthe appro-
             priate place(s).
          The strings that followthese keywords may be enclosed in single or double quotes (the quotes will be stripped off)
          and may contain the usual C backslash-characters (\n is newline, \r is return, \b is backspace, \’ escapes a single-
          quote inside single-quotes, \" escapes a double-quote inside double-quotes). Nowfor an example.
          This is actually the contents of the <shx.mk> system makeﬁle, and causes PMaketouse the Bourne Shell in such a
          waythat each command is printed as it is executed. That is, if more than one command is givenonaline, 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 speciﬁcation runs likethis:
              #
              #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 PMakethe following:
          •The shell is located in the ﬁle /bin/sh.Itneed not tell PMakethat the name of the shell is sh as PMakecan
           ﬁgure that out for itself (it’sthe last component of the path).
          •The command to stop echoing is set -.
          •The command to start echoing is set -x.
          •When the echo offcommand is executed, the shell will print +set - (The ‘+’ comes from using the −x ﬂag
           (rather than the −v ﬂag PMakeusually uses)). PMakewill remove all occurrences of this string from the output,
           so you don’tnotice extra commands you didn’tput there.
          PMake—ATutorial PSD:12-29
          •The ﬂag the Bourne Shell will taketostart echoing in this way is the −x ﬂag. The Bourne Shell will only takeits
           ﬂag arguments concatenated as its ﬁrst argument, so neither this nor the errFlag speciﬁcation begins with a −.
          •The ﬂag to use to turn error-checking on from the start is −e.
          •The shell can turn error-checking on and off, and the commands to do so are set +e and set -e,respectively.
          Ishould note that this speciﬁcation is for Bourne Shells that are not part of Berkeleyasshells from Berkeleydon’t
          do error control. You can get a similar effect, however, bychanging the last three lines to be:
                hasErrCtl=no \
                check="echo \"+ %s\"\n" \
                ignore="sh -c ’%s || exit 0\n"
          This will cause PMaketoexecute the twocommands
              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 exe-
          cuted only if the ﬁrst command exited non-zero, and if the ﬁrst command exited zero, the shell will also exit zero,
          since that’sthe last command it executed).
          4.5. Compatibility
          There are three (well, 3 ½) levels of backwards-compatibility built into PMake. Most makeﬁles will need none at
          all. Some may need a little bit of work to operate correctly when run in parallel. Each levelencompasses the previ-
          ous levels (e.g. −B (one shell per command) implies −V)The three levels are described in the following three sec-
          tions.
          4.5.1. DEFCON3—Variable Expansion
          As noted before, PMakewill not expand a variable unless it knows of a value for it. This can cause problems for
          makeﬁles that expect to leave variables undeﬁned except in special circumstances (e.g. if more ﬂags 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 theyare enclosed in parentheses, how-
          ev er, the shell will declare a syntax error and the makewill come to a grinding halt.
          Youhav e twochoices: change the makeﬁle to deﬁne the variables (their values can be overridden on the command
          line, since that’swhere theywould have been set if you used Make, anyway) or always give the −V ﬂag (this can be
          done with the .MAKEFLAGS target, if you want).
          4.5.2. DEFCON2—The Number of the Beast
          Then there are the makeﬁles that expect certain commands, such as changing to a different directory,tonot affect
          other commands in a target’screation script. You can solvethis is either by going back to executing one shell per
          command (which is what the −B ﬂag forces PMaketodo), which slows the process down a good bit and requires
          you to use semicolons and escaped newlines for shell constructs, or by changing the makeﬁle to execute the offend-
          ing command(s) in a subshell (by placing the line inside parentheses), likeso:
              install :: .MAKE
                (cd src; $(.PMAKE) install)
                (cd lib; $(.PMAKE) install)
                (cd man; $(.PMAKE) install)
          This will always execute the three makes (evenifthe −n ﬂag was given) because of the combination of the ‘‘::’’op-
          erator 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.
                       PSD:12-30 PMake—ATutorial
                       4.5.3. DEFCON1—Imitation is the Not the Highest Form of Flattery
                       The ﬁnal category of makeﬁle is the one where every command requires input, the dependencies are incompletely
                       speciﬁed, or you simply cannot create more than one target at a time, as mentioned earlier.Inaddition, you may not
                       have the time or desire to upgrade the makeﬁle to run smoothly with PMake. If you are the conservative sort, this is
                       the compatibility mode for you. It is entered either by giving PMakethe −M ﬂag (for Make), or by executing PMake
                       as ‘‘make.’’Ineither case, PMakeperforms things exactly likeMake(while still supporting most of the nice new
                       features PMakeprovides). This includes:
                       •Noparallel execution.
                       •Targets are made in the exact order speciﬁed by the makeﬁle. The sources for each target are made in strict left-to-
                          right order,etc.
                       •Asingle Bourne shell is used to execute each command, thus the shell’s $$ variable is useless, changing directo-
                          ries doesn’twork across command lines, etc.
                       •Ifnospecial characters exist in a command line, PMakewill break the command into words itself and execute the
                          command directly,without executing a shell ﬁrst. The characters that cause PMaketoexecute a shell are: #, =, |,
                          ˆ, (, ), {, }, ;, &, <, >, *, ?, [, ], :, $, ‘,and \.You should notice that these are all the characters that are
                          givenspecial meaning by the shell (except ’ and  ,which PMakedeals with all by its lonesome).
                       •The use of the null sufﬁx is turned off.
                       4.6. The WayThings Work
                       When PMakereads the makeﬁle, it parses sources and targets into nodes in a graph. The graph is directed only in the
                       sense that PMakeknows which way is up. Each node contains not only links to all its parents and children (the
                       nodes that depend on it and those on which it depends, respectively), but also a count of the number of its children
                       that have already been processed.
                       The most important thing to knowabout howPMakeuses this graph is that the traversal is breadth-ﬁrst and occurs in
                       twopasses.
                       After PMakehas parsed the makeﬁle, it begins with the nodes the user has told it to make(either on the command
                       line, or via a .MAIN target, or by the target being the ﬁrst in the ﬁle not labeled with the .NOTMAIN attribute)
                       placed in a queue. It continues to takethe node offthe front of the queue, mark it as something that needs to be
                       made, pass the node to Suff_FindDeps (mentioned earlier) to ﬁnd anyimplicit sources for the node, and place
                       all the node’schildren that have yet to be marked at the end of the queue. If anyofthe children is a .USE rule, its at-
                       tributes are applied to the parent, then its commands are appended to the parent’slist of commands and its children
                       are linked to its parent. The parent’sunmade 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 sec-
                       ond pass. This process continues until the ﬁrst queue is empty.
                       At this point, all the leavesofthe graph are in the examination queue. PMakeremovesthe 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 asyn-
                       chronously.When the commands have completed, all the node’sparents have their unmade children counter decre-
                       mented and, if the counter is then 0, theyare placed on the examination queue. Likewise, if the node is up-to-date.
                       Only those parents that were marked on the downward pass are processed in this way.Thus PMaketraverses the
                       graph back up to the nodes the user instructed it to create. When the examination queue is empty and no shells are
                       running to create a target, PMakeisﬁnished.
                       Once all targets have been processed, PMakeexecutes 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 (PMakekeeps a running count of howmanytargets are left to be made), there is a cycle in the
                       graph. PMakedoes a depth-ﬁrst traversal of the graph to ﬁnd all the targets that weren’tmade 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 deﬁnition of a
                             sufﬁx, which PMakedoesn’tnecessarily share. You will have noticed that all the sufﬁxes used in this tutorial
            PMake—ATutorial PSD:12-31
               (and in UNIX in general) begin with a period (.ms, .c,etc.). Now, PMake’sidea of a sufﬁx is more likeEng-
               lish’s: it’sthe characters at the end of a word. With this in mind, one possible solution to this problem goes as
               follows:
                   .SUFFIXES : ec.exe .exe ec.obj .obj .asm
                   ec.objec.exe .obj.exe :
                         link -o $(.TARGET) $(.IMPSRC)
                   .asmec.obj :
                         asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
                   .asm.obj :
                         asm -o $(.TARGET) $(.IMPSRC)
            (3.2) The trick to this one lies in the ‘‘:=’’variable-assignment operator and the ‘‘:S’’variable-expansion modiﬁer.
               Basically what you want is to takethe 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 ‘‘$(’’ atthe front of the variable name and ‘‘)’’ atthe end, thus transforming ‘‘VAR,’’for exam-
               ple, into ‘‘$(VAR),’’which is just what we want. Unfortunately (as you knowifyou’ve tried it), since, as it
               says in the hint, PMakedoes no further substitution on the result of a modiﬁed expansion, that’s all you get.
               The solution is to makeuse of ‘‘:=’’toplace that string into yet another variable, then invoke the other vari-
               able directly:
                   *PTR :=$(PTR:S/ˆ/\$(/:S/$/)/)
               Youcan then use ‘‘$(*PTR)’’ toyour heart’scontent.
            6. Glossary of Jargon
            attribute: Aproperty giventoatarget that causes PMaketotreat it differently.
            command script: The lines immediately following a dependencyline that specify commands to execute to create
               each of the targets on the dependencyline. Each line in the command script must begin with a tab.
            command-line variable: Avariable deﬁned in an argument when PMakeisﬁrst executed. Overrides all assign-
               ments to the same variable name in the makeﬁle.
            conditional: Aconstruct much likethat used in C that allows a makeﬁle to be conﬁgured on the ﬂy based on the lo-
               cal 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 ﬂavors, as indicated by the opera-
               tor between the target and the source. ‘:’ givesastraight time-wise dependency(if the target is older than the
               source, the target is out-of-date), while ‘!’ provides simply an ordering and always considers the target out-of-
               date. ‘::’ is much like‘:’, save itcreates 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 dependencyline
               to specify a different source for each target on the line.
            global variable: Anyvariable deﬁned in a makeﬁle. Takes precedence overvariables deﬁned in the environment, but
               not overcommand-line or local variables.
            input graph: What PMakeconstructs from a makeﬁle. Consists of nodes made of the targets in the makeﬁle, and
               the links between them (the dependencies). The links are directed (from source to target) and there may not be
               anycycles (loops) in the graph.
            local variable: Avariable deﬁned by PMakevisible only in a target’sshell script. There are sevenlocal variables,
               not all of which are deﬁned for every target: .TARGET, .ALLSRC, .OODATE, .PREFIX, .IMPSRC,
               .ARCHIVE,and.MEMBER. .TARGET,.PREFIX,.ARCHIVE,and.MEMBERmay be used on dependency
               lines to create ‘‘dynamic sources.’’
          PSD:12-32 PMake—ATutorial
          makeﬁle: Aﬁle that describes howasystem is built. If you don’tknowwhat it is after reading this tutorial....
          modiﬁer: Aletter,following a colon, used to alter howavariable is expanded. It has no effect on the variable itself.
          operator: What separates a source from a target (on a dependencyline) and speciﬁes the relationship between the
             two. There are three: ‘:’, ‘::’, and ‘!’.
          search path: Alist of directories in which a ﬁle should be sought. PMake’sviewofthe contents of directories in a
             search path does not change once the makeﬁle has been read. A ﬁle is sought on a search path only if it is ex-
             clusively a source.
          shell: Aprogram to which commands are passed in order to create targets.
          source: Anything to the right of an operator on a dependencyline. Targets on the dependencyline are usually cre-
             ated from the sources.
          special target: Atarget that causes PMaketodospecial things when it’sencountered.
          sufﬁx: The tail end of a ﬁle name. Usually begins with a period, .c or .ms,e.g.
          target: Aword to the left of the operator on a dependencyline. More generally,any ﬁle that PMakemight create. A
             ﬁle may be (and often is) both a target and a source (what it is depends on howPMakeislooking at it at the
             time — sort of likethe wav e/particle duality of light, you know).
          transformation rule: Aspecial construct in a makeﬁle that speciﬁes howtocreate a ﬁle of one type from a ﬁle of
             another,asindicated by their sufﬁxes.
          variable expansion: The process of substituting the value of a variable for a reference to it. Expansion may be al-
             tered by means of modiﬁers.
          variable: Aplace in which to store text that may be retrievedlater.Also used to deﬁne the local environment. Con-
             ditionals exist that test whether a variable is deﬁned or not.
                     PMake—ATutorial PSD:12-33
                                                                 Table of Contents
                          1. Introduction .......................... 1
                          2. TheBasics of PMake....................... 1
                          2.1. DependencyLines . ....................... 2
                          2.2. ShellCommands . ........................ 3
                          2.3. Variables . .......................... 4
                          2.3.1. LocalVariables . ........................ 5
                          2.3.2. Command-lineVariables . ..................... 6
                          2.3.3. GlobalVariables .    ........................ 6
                          2.3.4. Environment Variables .    ...................... 6
                          2.4. Comments ........................... 7
                          2.5. Parallelism . ......................... 7
                          2.6. Writingand Debugging a Makeﬁle . .................. 7
                          2.7. Invoking PMake. . . . . . . . . . . . . . . . . . . . . . . . .                            9
                          2.8. Summary ........................... 12
                          2.9. Exercises . .......................... 12
                          3. Short-cutsand Other Nice Things        ................... 12
                          3.1. Transformation Rules ....................... 12
                          3.2. IncludingOther Makeﬁles . ..................... 16
                          3.3. Saving Commands ........................ 16
                          3.4. Target Attributes .    ........................ 17
                          3.5. SpecialTargets . ........................ 20
                          3.6. ModifyingVariable Expansion       .................... 21
                          3.7. Moreon Debugging . ....................... 22
                          3.8. MoreExercises . ........................ 22
                          4. PMakefor Gods ......................... 23
                          4.1. SearchPaths . ......................... 23
                          4.2. Archivesand Libraries ....................... 24
                          4.3. Onthe Condition... ........................ 25
                          4.4. AShell is a Shell is a Shell    ..................... 27
                          4.5. Compatibility .......................... 29
                          4.5.1. DEFCON3—Variable Expansion ................... 29
                          4.5.2. DEFCON2—The Number of the Beast ................. 29
                          4.5.3. DEFCON1—Imitation is the Not the Highest Form of Flattery      ......... 30
                          4.6. TheWayThings Work . ...................... 30
                          5. Answersto Exercises . ...................... 30
                          6. Glossaryof Jargon . ....................... 31
