2.  The Basics of PMake  

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

A makefile has four different types of lines in it:

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

2.1.  Dependency Lines  

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

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

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

:
If a colon is used, a target on the line is considered to be ``out-of-date'' (and in need of creation) if
Under this operation, steps will be taken to re-create the target only if it is found to be out-of-date by using these two rules.
!
If an exclamation point is used, the target will always be re-created, but this will not happen until all of its sources have been examined and re-created, if necessary.
::
If a double-colon is used, a target is out-of-date if:
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, now for an example. Take that C program I mentioned earlier. Say there are three C files (a.c, b.c and c.c) each of which includes the file defs.h. The dependencies between the files could then be expressed as follows: program : a.o b.o c.o a.o b.o c.o : defs.h a.o : a.c b.o : b.c c.o : c.c

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

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

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

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

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

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

2.2.  Shell Commands  

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

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

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

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

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

To expand on the earlier makefile, you might add commands as follows: program : a.o b.o c.o cc a.o b.o c.o -o program a.o b.o c.o : defs.h a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c

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

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

In most cases, shell commands are printed before they're actually executed. This is to keep you informed of what's going on. If an `@' appears, however, this echoing is suppressed. In the case of an echo command, say ``echo Linking index,'' it would be rather silly to see echo Linking index Linking index

so PMake allows you to place an `@' before the command (``@echo Linking index'') to prevent the command from being printed.

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

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

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

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

A target's shell script is fed to the shell on its (the shell's) input stream. This means that any commands, such as ci that need to get input from the terminal won't work right -- they'll get the shell's input, something they probably won't find to their liking. A simple way around this is to give a command like this: ci $(SRCS) < /dev/tty This would force the program's input to come from the terminal. If you can't do this for some reason, your only other alternative is to use PMake in its fullest compatibility mode. See Compatibility in chapter 4.

2.3.  Variables  

PMake, like Make before it, has the ability to save text in variables to be recalled later at your convenience. Variables in PMake are used much like variables in the shell and, by tradition, consist of all upper-case letters (you don't have to use all upper-case letters. In fact there's nothing to stop you from calling a variable @^&$%$. Just tradition). Variables are assigned-to using lines of the form VARIABLE = value appended-to by VARIABLE += value conditionally assigned-to (if the variable isn't already defined) by VARIABLE ?= value and assigned-to with expansion (i.e. the value is expanded (see below) before being assigned to the variable--useful for placing a value at the beginning of a variable, or other things) by VARIABLE := value

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

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

Note: this is intended to be used to execute commands that produce small amounts of output (e.g. ``pwd''). The implementation is less than intelligent and will likely freeze if you execute something that produces thousands of bytes of output (8 Kb is the limit on many UNIX systems).

The value of a variable may be retrieved by enclosing the variable name in parentheses or curly braces and preceeding the whole thing with a dollar sign.

For example, to set the variable CFLAGS to the string ``-I/sprite/src/lib/libc -O,'' you would place a line CFLAGS = -I/sprite/src/lib/libc -O in the makefile and use the word $(CFLAGS) wherever you would like the string -I/sprite/src/lib/libc -O to appear. This is called variable expansion.

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

Unlike Make, PMake will not expand a variable unless it knows the variable exists. E.g. if you have a ${i} in a shell command and you have not assigned a value to the variable i (the empty string is considered a value, by the way), where Make would have substituted the empty string, PMake will leave the ${i} alone. To keep PMake from substituting for a variable it knows, precede the dollar sign with another dollar sign. (e.g. to pass ${HOME} to the shell, use $${HOME}). This causes PMake, in effect, to expand the $ macro, which expands to a single $. For compatibility, Make's style of variable expansion will be used if you invoke PMake with any of the compatibility flags (-V, -B or -M. The -V flag alters just the variable expansion).

There are two different times at which variable expansion occurs: When parsing a dependency line, the expansion occurs immediately upon reading the line. If any variable used on a dependency line is undefined, PMake will print a message and exit. Variables in shell commands are expanded when the command is executed. Variables used inside another variable are expanded whenever the outer variable is expanded (the expansion of an inner variable has no effect on the outer variable. I.e. if the outer variable is used on a dependency line and in a shell command, and the inner variable changes value between when the dependency line is read and the shell command is executed, two different values will be substituted for the outer variable).

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

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

2.3.1.  Local Variables  

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

.TARGET
The name of the target.
.OODATE
The list of the sources for the target that were considered out-of-date. The order in the list is not guaranteed to be the same as the order in which the dependencies were given.
.ALLSRC
The list of all sources for this target in the order in which they were given.
.PREFIX
The target without its suffix and without any leading path. E.g. for the target ../../lib/compat/fsRead.c, this variable would contain fsRead.

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

Four of these variables may be used in sources as well as in shell scripts. These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The variables in the sources are expanded once for each target on the dependency line, providing what is known as a ``dynamic source,'' allowing you to specify several dependency lines at once. For example, $(OBJS) : $(.PREFIX).c will create a dependency between each object file and its corresponding C source file.

2.3.2.  Command-line Variables  

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

2.3.3.  Global Variables  

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

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

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

Global variables may be deleted using lines of the form: #undef variable The `#' must be the first character on the line. Note that this may only be done on global variables.

2.3.4.  Environment Variables  

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

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

Using all these variables, you can compress the sample makefile even more: OBJS = a.o b.o c.o program : $(OBJS) cc $(.ALLSRC) -o $(.TARGET) $(OBJS) : defs.h a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c

2.4.  Comments  

Comments in a makefile start with a `#' character and extend to the end of the line. They may appear anywhere you want them, except in a shell command (though the shell will treat it as a comment, too). If, for some reason, you need to use the `#' in a variable or on a dependency line, put a backslash in front of it. PMake will compress the two into a single `#' (Note: this isn't true if PMake is operating in full-compatibility mode).

2.5.  Parallelism  

D's -1u'D't 5u' D'l 71u 0u'D'l 50u 50u'D'l 0u 71u'D'l -50u 50u'D'l -71u 0u'D'l -50u -50u'D'l 0u -71u'D'l 50u -50u' D't 3u'
D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'

NOTE

D't 3u'D's -1u'

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

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

The other problem comes from improperly-specified dependencies that worked in Make because of its sequential, depth-first way of examining them. While I don't want to go into depth on how PMake works (look in chapter 4 if you're interested), I will warn you that files in two different ``levels'' of the dependency tree may be examined in a different order in PMake than they were in Make. For example, given the makefile a : b c b : d PMake will examine the targets in the order c, d, b, a. If the makefile's author expected PMake to abort before making c if an error occurred while making b, or if b needed to exist before c was made, s/he will be sorely disappointed. The dependencies are incomplete, since in both these cases, c would depend on b. So watch out.

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

2.6.  Writing and Debugging a Makefile  

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

When faced with the writing of a makefile, it is usually best to start from first principles: just what are you trying to do? What do you want the makefile finally to produce?

To begin with a somewhat traditional example, let's say you need to write a makefile to create a program, expr, that takes standard infix expressions and converts them to prefix form (for no readily apparent reason). You've got three source files, in C, that make up the program: main.c, parse.c, and output.c. Harking back to my pithy advice about dependency lines, you write the first line of the file: expr : main.o parse.o output.o because you remember expr is made from .o files, not .c files. Similarly for the .o files you produce the lines: main.o : main.c parse.o : parse.c output.o : output.c main.o parse.o output.o : defs.h

Great. You've now got the dependencies specified. What you need now is commands. These commands, remember, must produce the target on the dependency line, usually by using the sources you've listed. You remember about local variables? Good, so it should come to you as no surprise when you write expr : main.o parse.o output.o cc -o $(.TARGET) $(.ALLSRC) Why use the variables? If your program grows to produce postfix expressions too (which, of course, requires a name change or two), it is one fewer place you have to change the file. You cannot do this for the object files, however, because they depend on their corresponding source files and defs.h, thus if you said cc -c $(.ALLSRC) you'd get (for main.o): cc -c main.c defs.h which is wrong. So you round out the makefile with these lines: main.o : main.c cc -c main.c parse.o : parse.c cc -c parse.c output.o : output.c cc -c output.c

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

1)
The string ``main.o parse.o output.o'' is repeated twice, necessitating two changes when you add postfix (you were planning on that, weren't you?). This is in direct violation of de Boor's First Rule of writing makefiles:
Anything that needs to be written more than once should be placed in a variable.
I cannot emphasize this enough as being very important to the maintenance of a makefile and its program.
2)
There is no way to alter the way compilations are performed short of editing the makefile and making the change in all places. This is evil and violates de Boor's Second Rule, which follows directly from the first:
Any flags or programs used inside a makefile should be placed in a variable so they may be changed, temporarily or permanently, with the greatest ease.

The makefile should more properly read: OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) main.o : main.c $(CC) $(CFLAGS) -c main.c parse.o : parse.c $(CC) $(CFLAGS) -c parse.c output.o : output.c $(CC) $(CFLAGS) -c output.c $(OBJS) : defs.h Alternatively, if you like the idea of dynamic sources mentioned in section 2.3.1, you could write it like this: OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) $(OBJS) : $(.PREFIX).c defs.h $(CC) $(CFLAGS) -c $(.PREFIX).c These two rules and examples lead to de Boor's First Corollary:

Variables are your friends.

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

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

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

2.7.  Invoking PMake  

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

-d what
This causes PMake to spew out debugging information that may prove useful to you. If you can't figure out why PMake is doing what it's doing, you might try using this flag. The what parameter is a string of single characters that tell PMake what aspects you are interested in. Most of what I describe will make little sense to you, unless you've dealt with Make before. Just remember where this table is and come back to it as you read on. The characters and the information they produce are as follows:
a
Archive searching and caching.
c
Conditional evaluation.
d
The searching and caching of directories.
j
Various snippets of information related to the running of the multiple shells. Not particularly interesting.
m
The making of each target: what target is being examined; when it was last modified; whether it is out-of-date; etc.
p
Makefile parsing.
r
Remote execution.
s
The application of suffix-transformation rules. (See chapter 3)
t
The maintenance of the list of targets.
v
Variable assignment.
Of these all, the m and s letters will be most useful to you. If the -d is the final argument or the argument from which it would get these key letters (see below for a note about which argument would be used) begins with a -, all of these debugging flags will be set, resulting in massive amounts of output.
-f makefile
Specify a makefile to read different from the standard makefiles (Makefile or makefile). If makefile is ``-'', PMake uses the standard input. This is useful for making quick and dirty makefiles...
-h
Prints out a summary of the various flags PMake accepts. It can also be used to find out what level of concurrency was compiled into the version of PMake you are using (look at -J and -L) and various other information on how PMake was configured.
-i
If you give this flag, PMake will ignore non-zero status returned by any of its shells. It's like placing a `-' before all the commands in the makefile.
-k
This is similar to -i in that it allows PMake to continue when it sees an error, but unlike -i, where PMake continues blithely as if nothing went wrong, -k causes it to recognize the error and only continue work on those things that don't depend on the target, either directly or indirectly (through depending on something that depends on it), whose creation returned the error. The `k' is for ``keep going''...
-l
PMake has the ability to lock a directory against other people executing it in the same directory (by means of a file called ``LOCK.make'' that it creates and checks for in the directory). This is a Good Thing because two people doing the same thing in the same place can be disastrous for the final product (too many cooks and all that). Whether this locking is the default is up to your system administrator. If locking is on, -l will turn it off, and vice versa. Note that this locking will not prevent you from invoking PMake twice in the same place -- if you own the lock file, PMake will warn you about it but continue to execute.
-m directory
Tells PMake another place to search for included makefiles via the <...> style. Several -m options can be given to form a search path. If this construct is used the default system makefile search path is completely overridden. To be explained in chapter 3, section 3.2.
-n
This flag tells PMake not to execute the commands needed to update the out-of-date targets in the makefile. Rather, PMake will simply print the commands it would have executed and exit. This is particularly useful for checking the correctness of a makefile. If PMake doesn't do what you expect it to, it's a good chance the makefile is wrong.
-p number
This causes PMake to print its input in a reasonable form, though not necessarily one that would make immediate sense to anyone but me. The number is a bitwise-or of 1 and 2 where 1 means it should print the input before doing any processing and 2 says it should print it after everything has been re-created. Thus -p 3 would print it twice--once before processing and once after (you might find the difference between the two interesting). This is mostly useful to me, but you may find it informative in some bizarre circumstances.
-q
If you give PMake this flag, it will not try to re-create anything. It will just see if anything is out-of-date and exit non-zero if so.
-r
When PMake starts up, it reads a default makefile that tells it what sort of system it's on and gives it some idea of what to do if you don't tell it anything. I'll tell you about it in chapter 3. If you give this flag, PMake won't read the default makefile.
-s
This causes PMake to not print commands before they're executed. It is the equivalent of putting an `@' before every command in the makefile.
-t
Rather than try to re-create a target, PMake will simply ``touch'' it so as to make it appear up-to-date. If the target didn't exist before, it will when PMake finishes, but if the target did exist, it will appear to have been updated.
-v
This is a mixed-compatibility flag intended to mimic the System V version of Make. It is the same as giving -B, and -V as well as turning off directory locking. Targets can still be created in parallel, however. This is the mode PMake will enter if it is invoked either as ``smake'' or ``vmake''.
-x
This tells PMake it's ok to export jobs to other machines, if they're available. It is used when running in Make mode, as exporting in this mode tends to make things run slower than if the commands were just executed locally.
-B
Forces PMake to be as backwards-compatible with Make as possible while still being itself. This includes:
-C
This nullifies any and all compatibility mode flags you may have given or implied up to the time the -C is encountered. It is useful mostly in a makefile that you wrote for PMake to avoid bad things happening when someone runs PMake as ``make'' or has things set in the environment that tell it to be compatible. -C is not placed in the PMAKE environment variable or the .MAKEFLAGS or MFLAGS global variables.
-D variable
Allows you to define a variable to have ``1'' as its value. The variable is a global variable, not a command-line variable. This is useful mostly for people who are used to the C compiler arguments and those using conditionals, which I'll get into in section 4.3
-I directory
Tells PMake another place to search for included makefiles. Yet another thing to be explained in chapter 3 (section 3.2, to be precise).
-J number
Gives the absolute maximum number of targets to create at once on both local and remote machines.
-L number
This specifies the maximum number of targets to create on the local machine at once. This may be 0, though you should be wary of doing this, as PMake may hang until a remote machine becomes available, if one is not available when it is started.
-M
This is the flag that provides absolute, complete, full compatibility with Make. It still allows you to use all but a few of the features of PMake, but it is non-parallel. This is the mode PMake enters if you call it ``make.''
-P
When creating targets in parallel, several shells are executing at once, each wanting to write its own two cent's-worth to the screen. This output must be captured by PMake in some way in order to prevent the screen from being filled with garbage even more indecipherable than you usually see. PMake has two ways of doing this, one of which provides for much cleaner output and a clear separation between the output of different jobs, the other of which provides a more immediate response so one can tell what is really happpening. The former is done by notifying you when the creation of a target starts, capturing the output and transferring it to the screen all at once when the job finishes. The latter is done by catching the output of the shell (and its children) and buffering it until an entire line is received, then printing that line preceded by an indication of which job produced the output. Since I prefer this second method, it is the one used by default. The first method will be used if you give the -P flag to PMake.
-V
As mentioned before, the -V flag tells PMake to use Make's style of expanding variables, substituting the empty string for any variable it doesn't know.
-W
There are several times when PMake will print a message at you that is only a warning, i.e. it can continue to work in spite of your having done something silly (such as forgotten a leading tab for a shell command). Sometimes you are well aware of silly things you have done and would like PMake to stop bothering you. This flag tells it to shut up about anything non-fatal.
-X
This flag causes PMake to not attempt to export any jobs to another machine.

Several flags may follow a single `-'. Those flags that require arguments take them from successive parameters. E.g. pmake -fDnI server.mk DEBUG /chip2/X/server/include will cause PMake to read server.mk as the input makefile, define the variable DEBUG as a global variable and look for included makefiles in the directory /chip2/X/server/include.

2.8.  Summary  

A makefile is made of four types of lines:

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

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

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

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

2.9.  Exercises   TBA