                                                          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 behind 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 de-
                     pendencies, 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 im-
                     portant feature is its ability to run several different jobs at once, making the creation of systems consider-
                     ably 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 about it), or is simply important, it will be ﬂagged with a little
            NOTE     sign in the left margin, likethis:
                     This tutorial is divided into three main sections corresponding to basic, intermediate and advanced PMake
                     usage. 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 Califor-
                     nia, BerkeleySoftworks, and Adam de Boor makenorepresentations about the suitability of this software for
                     anypurpose. It is provided "as is" without express or implied warranty.
                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. For instance, in a program made up of several C source ﬁles and one header ﬁle, the C ﬁles will
                need to be re-compiled 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 dependencylines in the makeﬁle.
                On a dependencyline, there are targets and sources, separated by a one- or two-character operator.The tar-
                gets ‘‘depend’’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 however
                many) is determined 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 exclu-
                sive.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 de-
                pendencylines. E.g. a.o depends 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/{whif-
                           fle,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 wildcard 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 aslong 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 charac-
                           ter 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-creation 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 (PMakedoesn’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 impor-
                     tant exception: 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 cre-
                     ation script for a target should immediately followthe dependencyline for that target. While anygiv entar-
                     get may appear on more than one dependencyline, only one of these dependencylines may be followed by
            NOTE     acreation script, unless the ‘::’ 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 dependencyline is out-of-date, not the sources. In this example, the command ‘‘cc −c a.c’’ will
                     be executed if a.o is out-of-date. Because of the ‘:’ operator,this means that should a.c or defs.h have
                     been modiﬁed more recently than a.o,the command will be executed (a.o will be considered out-of-
                     date).
                     PSD:12-4 PMake—ATutorial
                     Remember howIsaid the only difference between a makeﬁle shell command and a regular shell command
                     wasthe leading tab? I lied. There is another way in which makeﬁle commands differ from regular ones.
                     The ﬁrst twocharacters after the initial whitespace are treated specially.Iftheyare anycombination of ‘@’
                     and ‘−’, theycause PMaketododifferent things.
                     In most cases, shell commands are printed before they’re actually executed. This is to keep you informed of
                     what’sgoing 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
                     command 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 status 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’susual action is to abort whateverit’sdoing and exit with a non-zero sta-
                     tus 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’’), certain command-line arguments, or doing
                     other things, to be detailed later.Insuch a case, the non-zero status is simply ignored and PMakekeeps
           NOTE      chugging along.
                     Because all the commands are giventoasingle shell to execute, such things as setting shell variables,
                     changing directories, etc., last beyond the command in which theyare found. This also allows shell com-
                     pound 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
           NOTE      several other things, all of which I discourage since 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, some-
                     thing 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.
                     Variables in PMakeare used much likevariables in the shell and, by tradition, consist of all upper-case let-
                     ters (you don’t have to use all upper-case letters. In fact there’snothing to stop you from calling a variable
                     @ˆ&$%$.Just tradition). Variables are assigned-to using lines of the form
                             VARIABLE = value
                     appended-to by
                             VARIABLE += value
                     conditionally assigned-to (if the variable isn’talready deﬁned) by
                             VARIABLE ?= value
                     and assigned-to with expansion (i.e. the value is expanded (see below) before being assigned to the vari-
                     able—useful for placing a value at the beginning of a variable, or other things) by
                     PMake—ATutorial PSD:12-5
                             VARIABLE := value
                     Anywhitespace before value is stripped off. When appending, a space is placed between the old value and
                     the stuffbeing appended.
                     The ﬁnal way a variable may be assigned to is using
                             VARIABLE != shell-command
                     In this case, shell-command has all its variables expanded (see below) and is passed offtoashell to exe-
                     cute. The output of the shell is then placed in the variable. Anynewlines (other than the ﬁnal one) are re-
                     placed by spaces before 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 implementation is less than intelligent and will likely freeze if you execute something that produces
                     thousands of bytes of output (8 Kb is the limit on 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
           NOTE      −I/sprite/src/lib/libc −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 ex-
                     pansion 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 ex-
                     ecuted. Variables used inside another variable are expanded wheneverthe outer variable is expanded (the
                     expansion of an inner variable has no effect on the outer variable. I.e. if the outer variable is used on a de-
                     pendencyline and in a shell command, and the inner variable changes value between when the dependency
                     line is read and the shell command is executed, twodifferent values will be substituted for the outer vari-
                     able).
                     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.
            PSD:12-6 PMake—ATutorial
            2.3.1. Local Variables
            Each target can have asmanyassev enlocal variables. These are variables that are only ‘‘visible’’within
            that target’sshell script and contain such things as the target’sname, all of its sources (from all its depen-
            dencylines), those sources that were out-of-date, etc. Four local variables are deﬁned for all targets. They
            are:
               .TARGET
                  The name of the target.
               .OODATE
                  The list of the sources for the target that were considered out-of-date. The order in the list is
                  not guaranteed to be the same as the order in which the dependencies were given.
               .ALLSRC
                  The list of all sources for this target in the order in which theywere given.
               .PREFIX
                  The target without its sufﬁx and without anyleading path. E.g. for the target
                  ../../lib/compat/fsRead.c,this variable would contain fsRead.
            Three other local variables are set only for certain targets under special circumstances. These are the
            ‘‘.IMPSRC,’’ ‘‘.ARCHIVE,’’ and ‘‘.MEMBER’’variables. When theyare set and howtheyare used is de-
            scribed later.
            Four of these variables may be used in sources as well as in shell scripts. These are ‘‘.TARGET’’, ‘‘.PRE-
            FIX’’, ‘‘.ARCHIVE’’and ‘‘.MEMBER’’. The variables in the sources are expanded once for each target on
            the dependencyline, providing what is known as a ‘‘dynamic source,’’ allowing you to specify several de-
            pendencylines 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
            arguments. For example,
                pmake "CFLAGS = -I/sprite/src/lib/libc -O"
            would make CFLAGSbe 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 com-
            mand-line variable (the search order,you see). Command-line variables may be set using anyofthe four as-
            signment operators, though only = and ?= behave asyou would expect them to, mostly because assign-
            ments 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 environment. != is sort of pointless to use from the
            command line, since the same effect can no doubt be accomplished using the shell’sown command substi-
            tution 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 theymay not contain a colon or an exclamation point. The variables PMakesets (almost) al-
            ways 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.
                     PMake—ATutorial PSD:12-7
                           .MAKEFLAGS
                                All the relevant ﬂags with which PMakewas invoked. This does not include such things as −f
                                or variable assignments. Again for compatibility,this value is stored in the MFLAGS variable
                                as well.
                     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.
                     One special environment variable, PMAKE,isexamined by PMakefor command-line ﬂags, variable assign-
                     ments, 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 PMake
                     automatically 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 any-
                     where 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 twointo a single ‘#’ (Note: this isn’ttrue if PMakeisoperating in full-compati-
                     bility mode).
            NOTE     2.5. Parallelism
                     PMakewas speciﬁcally designed to re-create several targets at once, when possible. You do not have todo
                     anything special to cause this to happen (unless PMakewas conﬁgured to not act in parallel, in which case
                     you will have tomakeuse 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 writ-
                     ten 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 twoof
                     them 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 $$ expands to a single $,thus giving you the shell variable $$).
                     Since only one shell is used for all the commands, you’ll get the same ﬁle name for each command in the
                     script.
            PSD:12-8 PMake—ATutorial
            The other problem comes from improperly-speciﬁed dependencies that worked in Makebecause of its se-
            quential, 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 depen-
            dencytree 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 disappointed. The dependencies are incomplete, since in both these cases, c would depend on b.
            So watch out.
            Another problem you may face is that, while 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/tty Imentioned 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 uncommonly-available makeﬁle generators or (2) write your own makeﬁle (I leave out the third choice
            of ignoring PMakeand 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 rea-
            son). You’ve got three source ﬁles, in C, that makeupthe program: main.c, parse.c,and output.c.
            Harking back to my pithyadvice 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, re-
            member,must produce the target on the dependencyline, usually by using the sources you’ve listed. You
            remember about local variables? Good, so it should come to you as no surprise when you write
                expr :main.o parse.o output.o
                      cc -o $(.TARGET) $(.ALLSRC)
            Whyuse the variables? If your program grows to produce postﬁx expressions too (which, of course, re-
            quires a name change or two), it is one fewer place you have tochange the ﬁle. You cannot do this for the
            object ﬁles, however, because 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:
             PMake—ATutorial PSD:12-9
                 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 com-
             pilations 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’s
                First 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 pro-
                gram.
             2) Thereis 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:
                 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 like
             this:
                 OBJS =main.o parse.o output.o
                 expr :$(OBJS)
                        $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
                 $(OBJS) : $(.PREFIX).c defs.h
                        $(CC) $(CFLAGS) -c $(.PREFIX).c
             These 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 PMake
             would 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 whyit
             is deciding whateveritisdeciding. You can then use the information printed for other targets to see where
                 PSD:12-10 PMake—ATutorial
                 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 cycle.
                 2.7. Invoking PMake
                 PMakecomes with a wide variety of ﬂags to choose from. Theymay appear in anyorder,interspersed with
                 command-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ﬁg-
                      ure 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:
                      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 argu-
                      ment 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 levelof
                      concurrencywas compiled into the version of PMakeyou are using (look at −J and −L)and various
                      other information on howPMakewas conﬁgured.
                 −i   If you give this ﬂag, PMakewill ignore non-zero status returned by anyofits shells. It’slikeplacing
                      a‘−’ before all the commands in the makeﬁle.
                 PMake—ATutorial PSD:12-11
                 −k   This is similar to −i in that it allows PMaketocontinue when it sees an error,but unlike −i,where
                      PMakecontinues blithely as if nothing went wrong, −k causes it to recognize the error and only con-
                      tinue work on those things that don’tdepend on the target, either directly or indirectly (through de-
                      pending on something that depends on it), whose creation returned the error.The ‘k’ is for ‘‘keep go-
                      ing’’. . .
                 −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 twopeople doing the same thing in the same place can be disastrous for the ﬁnal prod-
                      uct (too manycooks and all that). Whether this locking is the default is up to your system adminis-
                      trator.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
                      makeimmediate 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 twointeresting). This is mostly useful to me, but you may ﬁnd it in-
                      formative 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.
                 −r   When PMakestarts up, it reads a default makeﬁle that tells it what sort of system it’sonand givesit
                      some 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’tread the default makeﬁle.
                 −s   This causes PMaketonot print commands before they’re executed. It is the equivalent of putting an
                      ‘@’ before 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 Makemode, as exporting in this mode tends to makethings run slower than if the commands were
                      just executed locally.
                 −B   Forces PMaketobeasbackwards-compatible with Makeaspossible while still being itself. This in-
                      cludes:
                      •Executing one shell per shell command
                      •Expanding anything that looks evenvaguely likeavariable, with the empty string replacing any
                       variable PMakedoesn’tknow.
                      •Refusing to allowyou to escape a ‘#’ with a backslash.
                PSD:12-12 PMake—ATutorial
                     •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 encountered. 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
                     MFLAGSglobal variables.
                −Dvariable
                     Allows you to deﬁne a variable to have ‘‘1’’ asits value. The variable is a global variable, not a com-
                     mand-line variable. This is useful mostly for people who are used to the C compiler arguments and
                     those using conditionals, which I’ll get into in section 4.3
                −I directory
                     Tells 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 avail-
                     able, 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 a fewofthe 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
                     twocent’s-worth to the screen. This output must be captured by PMakeinsome way in order to pre-
                     vent the screen from being ﬁlled with garbage evenmore indecipherable than you usually see. PMake
                     has twoways of doing this, one of which provides for much cleaner output and a clear separation be-
                     tween the output of different jobs, the other of which provides a more immediate response so one can
                     tell what is really happening. The former is done by notifying you when the creation of a target starts,
                     capturing the output and transferring it to the screen all at once when the job ﬁnishes. The latter is
                     done by catching the output of the shell (and its children) and buffering it until an entire line is re-
                     ceived, 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.
                −V As mentioned before, the −V ﬂag tells PMaketouse Make’sstyle of expanding variables, substitut-
                     ing 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 likePMake
                     to stop 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 param-
                eters. 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 vari-
                able 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:
          PMake—ATutorial PSD:12-13
             •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 terminates 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,ap-
          pended-to using the ‘+=’operator,conditionally (if the variable is undeﬁned) assigned-to with the ‘?=’op-
          erator,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 en-
          closing 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 seven
          local variables: .TARGET, .ALLSRC, .OODATE, .PREFIX, .IMPSRC, .ARCHIVE,and .MEMBER.
          Four of them (.TARGET, .PREFIX, .ARCHIVE,and .MEMBER)may be used to specify ‘‘dynamic
          sources.’’ Variables are good. 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
          aw aycommands 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 UNIX®has devel-
          oped, naming conventions, with regard to sufﬁxes, have also developed that have become almost as incon-
          trovertible as Law. E.g. a ﬁle ending in .c is assumed to contain C source code; one with a .o sufﬁx is as-
          sumed 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 as-
          pects 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 some-
          thing 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 together.Sufﬁxes are made known to PMakebyplacing them as sources on a dependencyline whose
          target is the special target .SUFFIXES.E.g.
                       PSD:12-14 PMake—ATutorial
                               .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 transformation 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. Many
                       transformation 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) TheCFLAGSvariable. 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 CFLAGS variable to contain -g (‘‘CFLAGS = -g’’)and PMakewould take
                                  care 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 any-
                       where 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:
                               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:
                         1 This is also somewhat cleaner,Ithink, than the dynamic source solution presented in 2.6
          PMake—ATutorial PSD:12-15
              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 tar-
          get 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 notice, 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 transformation. 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 dependency
          line 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.o and b.o.Since a transformation exists from .o to .out,PMakeuses that, as indicated
          by the ‘‘using existing source a.o’’ message.
          The search for a transformation starts from the sufﬁx of the target and continues through all the deﬁned
          transformations, in the order dictated by the sufﬁx ranking, until an existing ﬁle with the same base (the tar-
          get 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:
          PSD:12-16 PMake—ATutorial
              .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 fol-
          lowing 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 behold, 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 PMake
          printed for the rest of the process:
              Suff_FindDeps (jive.o)
                using existing source jive.c
                applying .c -> .o to "jive.c"
              Suff_FindDeps (jive.c)
                using existing source jive.l
                applying .l -> .c to "jive.l"
              Suff_FindDeps (jive.l)
              Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
              Examining jive.c...non-existent...out-of-date
              --- jive.c ---
              lex jive.l
              ... meaningless lex output deleted ...
              mv lex.yy.c jive.c
              Examining jive.o...non-existent...out-of-date
              --- jive.o ---
              cc -c jive.c
              Examining jive.out...non-existent...out-of-date
              --- jive.out ---
              cc -o jive.out jive.o
          One ﬁ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 .NULL target mentioned later.Inthe system makeﬁle, .out is chosen as the ‘‘null
          PMake—ATutorial PSD:12-17
          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 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 like
          this:
              #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 dou-
          ble-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.
          Youmust specify the searching method with either angle brackets or double-quotes outside of a variable ex-
          pansion. 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. For instance: you’re making several different libraries at one time and you want to create the mem-
          bers 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 infor-
          mation for the linker to use. Two ofthem running at once will overwrite each other’sﬁle and the result will
          be garbage for both parties). You might want a way 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:
           PSD:12-18 PMake—ATutorial
               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 er-
           ror.
           3.4. Target Attributes
           PMakeallows you to give attributes to targets by means of special sources. Likeeverything else PMake
           uses, these sources begin with a period and are made up of all upper-case letters. There are various reasons
           for using them, and I will try to give examples for most of them. Others you’ll have toﬁnd uses for your-
           self. Think of it as ‘‘an exercise for the reader.’’Byplacing one (or more) of these as a source on a depen-
           dencyline, 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 ig-
                 nore 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 de-
                 pend 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’s
                 done. 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):
             PMake—ATutorial PSD:12-19
                          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
                     theytouched if PMakeisgiv enthe −t ﬂag). Note that all the rules, not just that for sys-
                     tem,include init as a source. This is because none of the other targets can be made until
                     inithas 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 anyarchitecture (CPU and operating system type), while others (e.g. compiling
                     aprogram with cc)must be performed on a machine with the same architecture. Not all ex-
                     port systems will support this attribute.
             .IGNORE Giving a target the .IGNORE attribute causes PMaketoignore errors from anyofthe tar-
                     get’scommands, 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’slocal 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 .ALL-
                     SRC variable containing the right things. Without the .INVISIBLE attribute for prog2,
                     the MAKEINSTALLrule couldn’tbeapplied. This is not as useful as it should be, and the se-
                     mantics 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 every-
                     thing 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 .TAR-
                     GET 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.
                     Youagain have the problem with ranlib that I mentioned earlier,only this time it’smore
                     severe: you can’tjust put the ranlib offtothe end since the program will need those libraries
                     before it can be re-created. You can do something likethis:
             PSD:12-20 PMake—ATutorial
                         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.a and lib4.a.Itwill then execute ranlib on anylibrary that was
                    changed and set program’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 attribute 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
                    PMaketoexecute 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 another machine. If you give the target the .NOEXPORT attribute, it will be run
                    locally,evenifyou’ve giv enPMakethe −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 Target’’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 Tar-
                    get. This allows you to place targets in an included makeﬁle and have PMakecreate some-
                    thing else by default.
             .PRECIOUS WhenPMakeisinterrupted (you type control-C at the keyboard), it will attempt to clean up
                    after itself by removing anyhalf-made targets. If a target has the .PRECIOUS attribute,
                    however, PMakewill leave italone. An additional side effect of the ‘::’ operator is to mark
                    the targets as .PRECIOUS.
             .SILENT Markingatarget with this attribute keeps its commands from being printed when they’re ex-
                    ecuted, 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 giventoa
                    target, the rules are applied sequentially.
                    The typical .USE rule (as I call them) will use the sources of the target to which it is applied
                    (as stored in the .ALLSRC variable for the target) as its ‘‘arguments,’’ ifyou will. Forex-
                    ample, 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:
            PMake—ATutorial PSD:12-21
                         lib1.a :$(LIB1OBJS) MAKELIB
                         lib2.a :$(LIB2OBJS) MAKELIB
                         MAKELIB : .USE
                               rm -f $(.TARGET)
                               ar cr $(.TARGET) $(.ALLSRC)
                               ...
                               ranlib $(.TARGET)
                    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...takea
                    look). Note that the .USE rule source itself (MAKELIB)does not appear in anyofthe tar-
                    gets’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 dependencyline, 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.I
            won’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 anyinitialization that needs doing.
            .DEFAULT
                   This is sort of a .USE rule for anytarget (that was used only as a source) that PMakecan’tﬁg-
                   ure out anyother way to create. It’sonly ‘‘sort of’’ a .USE rule because only the shell script at-
                   tached to the .DEFAULT target is used. The .IMPSRC variable of a target that inherits .DE-
                   FAULT’s commands is set to the target’sown name.
            .END Thisserves a function similar to .BEGIN,inthat commands attached to it are executed once
                   ev erything 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 adminis-
                   trator about this.
            .IGNORE This target marks each of its sources with the .IGNORE attribute. If you don’tgiv e it any
                   sources, 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 be-
                   low). Anysufﬁx so marked will have the directories on its search path (see .PATH,below)
                   placed in the .INCLUDES variable, 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
                   wayinthe 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
          PSD:12-22 PMake—ATutorial
                   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 ex-
               ists.
          .LIBS Thisdoes for libraries what .INCLUDES does for include ﬁles, except the ﬂag used is −L,as
               required by those linkers that allowyou to tell them where to ﬁnd libraries. The variable used
               is .LIBS.Beforewarned that PMakemay not have been compiled to do this if the linker on
               your system doesn’taccept the −L ﬂag, though the .LIBS variable will always be deﬁned
               once the makeﬁle has been read.
          .MAIN If you didn’tgiv e atarget (or targets) to create when you invokedPMake, it will takethe
               sources of this target as the targets to create.
          .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 This allows 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 cannot ﬁnd in the current directory.Ifyou give nosources, it will clear out anydirecto-
               ries added to the search path before. Since the effects of this all get very complex, I’ll leave it
               til chapter four to give you a complete 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 dependency
               line, unless there are no sources, in which case the .PRECIOUS attribute is giventoevery tar-
               get 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 (section 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 PMake
               should recognize. If you give a .SUFFIXES dependencyline with no sources, PMakewill for-
               get 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.
          PMake—ATutorial PSD:12-23
          3.6. Modifying Variable Expansion
          Variables need not always be expanded verbatim. PMakedeﬁnes several modiﬁers that may be applied to a
          variable’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
          manymodiﬁers as you want — each one is applied to the result of the previous and is separated from the
          previous by another colon.
          There are sevenways to modify a variable’sexpansion, most of which come from the C shell variable mod-
          iﬁ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 character that is either ‘a’, ‘b’, ‘c’ or ‘d’ (there may be anynumber of char-
               acters between the brackets); [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 dependencies.
            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 re-
               placed. The substitution is performed on each word in the variable in turn. If search-string be-
               gins 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 twocharacters, 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 pattern, so none of
               the usual regular-expression/wildcard characters have any special meaning save ˆ and $.In
               the replacement string, the & character is replaced by the search-string unless it is preceded by
               abackslash. You are allowed to use anycharacter except colon or exclamation point to sepa-
               rate the twostrings. This so-called delimiter character may be placed in either string by preced-
               ing it with a backslash.
            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 with-
               out a head is replaced by the empty string.
            E  :E replaces each word by its sufﬁx (‘‘extension’’). So ‘‘$(OBJS:E)’’ would give you ‘‘.o
               .a.’’
          PSD:12-24 PMake—ATutorial
            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 assem-
            bled 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 some-
            how. Unfortunately,PMakedoesn’tallowconstructs like
                $($(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 UNIX 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 as-
          sumption 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 fol-
          lowing 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/in-
          cludefor 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
          directories 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.
                PMake—ATutorial PSD:12-25
                When a ﬁle is found in some directory other than the current one, all local variables that would have con-
                tained the target’sname (.ALLSRC,and .IMPSRC)will instead contain the path to the ﬁle, as found by
                PMake. Thusif you have a ﬁle ../lib/mumble.c and 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 fol-
                lowanaming convention that avoids such conﬂicts.
                Something you should knowabout the way search paths are implemented is that each directory is read, and
                its contents cached, exactly once — when it is ﬁrst encountered — so anychanges to the directories while
                PMakeisrunning will not be noted when searching for implicit sources, nor will theybefound when
                PMakeattempts to discoverwhen the ﬁle was last modiﬁed, unless the ﬁle was created in the current direc-
                tory.While people have suggested that PMakeshould read the directories each time, my experience sug-
                gests that the caching seldom causes problems. In addition, not caching the directories slows things down
                enormously because of PMake’sattempts to apply transformation 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.
                4.2. Archivesand Libraries
                UNIX and Sprite allowyou to merge ﬁles into an archive using the ar command. Further,ifthe ﬁles are re-
                locatable 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 copyinthe archive and one copyout by itself. The problem with libraries is
                you usually think of them as -lm rather than /usr/lib/libm.a and the linker thinks they’re out-of-
                date if you so much as look at them.
                PMakesolves the problem with archivesbyallowing you to tell it to examine the ﬁles in the archives(so
                you can remove the individual ﬁles without having to regenerate them later). Tohandle the problem with li-
                braries, 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 .LIBS target. .a is so marked in the system makeﬁle.
                Members of an archive are speciﬁed as ‘‘archive(member[ member...])’’. Thus ‘‘’libdix.a(win-
                dow.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ﬁcation 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)tar-
                  get.
                •Thearchive(member)’s .TARGET variable is set to the name of the member if member is actually a tar-
                  get, 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.
           PSD:12-26 PMake—ATutorial
           •The .MEMBER variable is set to the actual string inside the parentheses. In most cases, this will be the
            same as the .TARGET variable.
           •Thearchive(member)’splace in the local variables of the targets that depend on it is taken by the value of
            its .TARGET variable.
           Thus, a program library could be created with the following 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 contents 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 likethis:
                PMake—ATutorial PSD:12-27
                      #
                      #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 environ-
                ment, 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).
                PSD:12-28 PMake—ATutorial
                The ‘‘#’’ must the very ﬁrst character on the line.
                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 over-
                loaded to allowstring 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 NOT
                takes precedence overAND, which takes precedence overOR. This precedence may be overridden with
                parentheses, and an expression may be parenthesized to your heart’scontent. Each term looks likeacall on
                one of four functions:
                makeThe syntax ismake(target)wheretargetis a target in the makeﬁle. This is true if the giventar-
                        get 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 The syntax 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 The syntax is exists(ﬁle) and is true if the ﬁle can be found on the global search path (i.e.
                        that deﬁned by .PATH targets, not by .PATHsufﬁx targets).
                empty Thissyntax 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
                        ev erything. The function returns true if the resulting string is empty (NOTE: an undeﬁned vari-
                        able in this context will cause at the very least a warning message about a malformed condi-
                        tional, 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.
           PMake—ATutorial PSD:12-29
           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 cre-
           ate 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ﬁning the variable DEBUG (say with pmake -D DEBUG)will also
           cause the debug version to be made.
               #if defined(DEBUG) || make(debug)
               CFLAGS += -g
               #else
               CFLAGS += -O
               #endif
           There are, of course, problems with this approach. The most glaring annoyance is that if you want to go
           from making a debug version to making a production version, you have toremove all the object ﬁles, or
           you will get some optimized and some debug versions in the same program. Another annoyance is you have
           to be careful not to maketwo targets that ‘‘conﬂict’’because of some conditionals in the makeﬁle. For in-
           stance
               #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ﬁcation it ﬁnds, if any. Use this if you just want to use a different version of
              the Bourne or C Shell (yes, PMakeknows 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). You can use this if you would just rather use the C Shell than the Bourne Shell
              (‘‘.SHELL: name=csh’’ will do it).
           quiet=echo-offcommand
              As mentioned before, PMakeactually controls whether commands are printed by introducing com-
              mands into the shell’sinput stream. This keyword, and the next two, control what those commands
              are. The quiet keyword is the command used to turn echoing off. Once it is turned off, echoing is ex-
              pected 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 for-
              mat the shell actually prints the echo-offcommand. WhereverPMakesees this string in the shell’s
              output, it will delete it and anyfollowing whitespace, up to and including the next newline. See the
          PSD:12-30 PMake—ATutorial
            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.
            To dothis, 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 con-
            catenated (if theyare used at the same time, of course).
          errFlag=ﬂagtoturn error checking on
            Likewise, unless a target is marked .IGNORE,PMakewishes error-checking to be on from the very
            start. Tothis end, it will pass this ﬂag to the shell as an argument. The same rules for an initial ‘−’ ap-
            ply 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 expected 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’tdo
            error-control, but I’ll tell you about that...now.
          hasErrCtl=yes or no
            This takes a value that is either yes or no.Now you might think that the existence of the check and
            ignore keywords would be enough to tell PMakeifthe shell can do error-control, but you’dbe
            wrong. If hasErrCtl is yes,PMakeuses the check and ignore commands in a straight-forward man-
            ner.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 replaced by the command that’sabout to be executed, to produce
            acommand 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 anyerror it returns. When these strings are
            used as templates, you must provide newline(s) (‘‘\n’’)inthe appropriate 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 way that each command is printed as it is executed. That is, if more than one command is givenon
          aline, 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"
          PMake—ATutorial PSD:12-31
          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.
          •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 be-
           gins 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,re-
           spectively.
          Ishould note that this speciﬁcation is for Bourne Shells that are not part of Berkeley UNIX,asshells from
          Berkeleydon’tdoerror 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 0to be executed 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 levelencom-
          passes the previous levels (e.g. −B (one shell per command) implies −V)The three levels are described in
          the following three sections.
          4.5.1. DEFCON3—Variable Expansion
          As noted before, PMakewill not expand a variable unless it knows of a value for it. This can cause prob-
          lems 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 they
          are enclosed in parentheses, however, 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
                        PSD:12-32 PMake—ATutorial
                        makeﬁle to execute the offending 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
                        ‘‘::’’operator and the .MAKE attribute. Each command will change to the proper directory to perform the
                        install, leaving the main shell in the directory in which it started.
                        4.5.3. 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 incom-
                        pletely speciﬁed, or you simply cannot create more than one target at a time, as mentioned earlier.Inaddi-
                        tion, 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 PMakeas‘‘make.’’Ineither case, PMakeperforms things exactly like
                        Make(while still supporting most of the nice newfeatures 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
                          directories 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
                          ashell 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 .NOT-
                        MAINattribute) placed in a queue. It continues to takethe node offthe front of the queue, mark it as some-
                        thing 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 attributes 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
                        .USEnode to have children that are .USE nodes and the rules will be applied in sequence. If the node has
                        no children, it is placed at the end of another queue to be examined in the second pass. This process contin-
                        ues 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 asynchronously.When the commands have completed, all the node’sparents have their un-
                        made children counter decremented and, if the counter is then 0, theyare placed on the examination queue.
            PMake—ATutorial PSD:12-33
            Likewise, if the node is up-to-date. Only those parents that were marked on the downward pass are pro-
            cessed in this way.Thus 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
            butthere 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ﬁni-
               tion of a sufﬁx, which PMakedoesn’tnecessarily share. You will have noticed that all the sufﬁxes
               used in this tutorial (and in UNIX in general) begin with a period (.ms, .c,etc.). Now, PMake’sidea
               of a sufﬁx is more likeEnglish’s: it’sthe characters at the end of a word. With this in mind, one pos-
               sible 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 example, 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 variable 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
               assignments 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 local environment, or on what is being made by that invocation of PMake.
            creation script: Commands used to create a target. See ‘‘command script.’’
            dependency: The relationship between a source and a target. This comes in three ﬂavors, as indicated by
               the operator 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.
          PSD:12-34 PMake—ATutorial
          dynamic source: This refers to a source that has a local variable invocation in it. It allows a single depen-
            dencyline 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 envi-
            ronment, 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 .MEMBER may be
            used on dependencylines to create ‘‘dynamic sources.’’
          makeﬁle: Aﬁle that describes howasystem is built. If you don’tknowwhat it is after reading this tuto-
            rial. . . .
          modiﬁer: Aletter,following a colon, used to alter howavariable is expanded. It has no effect on the vari-
            able itself.
          operator: What separates a source from a target (on a dependencyline) and speciﬁes the relationship be-
            tween the two. There are three: ‘:’, ‘::’, and ‘!’.
          search path: Alist of directories in which a ﬁle should be sought. PMake’sviewofthe contents of directo-
            ries 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 exclusively 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 usu-
            ally created 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 howPMakeis
            looking 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 altered by means of modiﬁers.
          variable: Aplace in which to store text that may be retrievedlater.Also used to deﬁne the local environ-
            ment. Conditionals exist that test whether a variable is deﬁned or not.
                   PMake—ATutorial PSD:12-35
                                                        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 . ..................... 6
                        2.3.2. Command-lineVariables . .................. 6
                        2.3.3. GlobalVariables .   ..................... 6
                        2.3.4. Environment Variables .  ................... 7
                        2.4. Comments ........................ 7
                        2.5. Parallelism . ....................... 7
                        2.6. Writingand Debugging a Makeﬁle . ............... 8
                        2.7. Invoking PMake. . . . . . . . . . . . . . . . . . . . . . 10
                        2.8. Summary ........................ 12
                        2.9. Exercises . ....................... 13
                        3. Short-cutsand Other Nice Things     ................ 13
                        3.1. Transformation Rules    .................... 13
                        3.2. IncludingOther Makeﬁles . .................. 17
                        3.3. Saving Commands ..................... 17
                        3.4. Target Attributes .   ..................... 18
                        3.5. SpecialTargets . ..................... 21
                        3.6. ModifyingVariable Expansion     ................. 23
                        3.7. Moreon Debugging . .................... 24
                        3.8. MoreExercises . ..................... 24
                        4. PMakefor Gods ...................... 24
                        4.1. SearchPaths . ...................... 24
                        4.2. Archivesand Libraries .................... 25
                        4.3. Onthe Condition... ..................... 27
                        4.4. AShell is a Shell is a Shell ................... 29
                        4.5. Compatibility ....................... 31
                        4.5.1. DEFCON3—Variable Expansion ................ 31
                        4.5.2. DEFCON2—The Number of the Beast .............. 31
                        4.5.3. DEFCON1—Imitation is the Not the Highest Form of Flattery ....... 32
                        4.6. TheWayThings Work . ................... 32
                        5. Answersto Exercises . ................... 33
                        6. Glossaryof Jargon . .................... 33
