Like the C compiler before it, PMake allows you to configure the makefile, based on the current environment, using conditional statements. A conditional looks like this:
#if boolean expression lines #elif another boolean expression more lines #else still more lines #endif
They may be nested to a maximum depth of 30 and may occur anywhere (except in a comment, of course). The # must the very first character on the line.
Each boolean expression is made up of terms that look like function calls, the standard C boolean operators &&, ||, and !, and the standard relational operators ==, !=, >, >=, <, and <=, with == and != being overloaded to allow string comparisons as well. && represents logical AND; || is logical OR and ! is logical NOT. The arithmetic and string operators take precedence over all three of these operators, while NOT takes precedence over AND, which takes precedence over OR. This precedence may be overridden with parentheses, and an expression may be parenthesized to your heart's content. Each term looks like a call on one of four functions:
make | The syntax is make(target) where target is a target in the makefile. This is true if the given target was specified on the command line, or as the source for a .MAIN target (note that the sources for .MAIN are only used if no targets were given on the command line). |
defined | The syntax is defined(variable) and is true if variable is defined. Certain variables are defined in the system makefile that identify the system on which PMake is being run. |
exists | The syntax is exists(file) and is true if the file can be found on the global search path (i.e. that defined by .PATH targets, not by .PATHsuffix targets). |
empty | This syntax is much like the others, except the string inside the parentheses is of the same form as you would put between parentheses when expanding a variable, complete with modifiers and everything. The function returns true if the resulting string is empty. An undefined variable in this context will cause at the very least a warning message about a malformed conditional, and at the worst will cause the process to stop once it has read the makefile. If you want to check for a variable being defined or empty, use the expression: !defined(var) || empty(var) as the definition of || will prevent the empty() from being evaluated and causing an error, if the variable is undefined. This can be used to see if a variable contains a given word, for example: #if !empty(var:Mword) |
The arithmetic and string operators may only be used to test the value of a variable. The lefthand side must contain the variable expansion, while the righthand side contains either a string, enclosed in double-quotes, or a number. The standard C numeric conventions (except for specifying an octal number) apply to both sides. E.g.:
#if $(OS) == 4.3 #if $(MACHINE) == "sun3" #if $(LOAD_ADDR) > 0xc000
are all valid conditionals. In addition, the numeric value of a variable can be tested as a boolean as follows:
#if $(LOAD)
would see if LOAD contains a non-zero value and:
#if !$(LOAD)
would test if LOAD contains a zero value.
In addition to the bare #if, there are other forms that apply one of the first two functions to each term. They are as follows:
There are also the “else if” forms: elif, elifdef, elifndef, elifmake, and elifnmake.
For instance, if you wish to create two versions of a program, one of which is
optimized (the production version) and the other of which is for debugging (has symbols
for dbx), you have two choices: you can create two makefiles, one of which uses the -g
flag for the compilation, while the other uses the -O
flag, or you can use another target (call it debug) to create
the debug version. The construct below will take care of this for you. I have also made
it so defining the variable DEBUG (say with pmake -D DEBUG) will also cause the debug version to be made.
#if defined(DEBUG) || make(debug) CFLAGS += -g #else CFLAGS += -O #endif
There are, of course, problems with this approach. The most glaring annoyance is that if you want to go from making a debug version to making a production version, you have to remove all the object files, or you will get some optimized and some debug versions in the same program. Another annoyance is you have to be careful not to make two targets that “conflict” because of some conditionals in the makefile. For instance:
#if make(print) FORMATTER = ditroff -Plaser_printer #endif #if make(draft) FORMATTER = nroff -Pdot_matrix_printer #endif
would wreak havoc if you tried pmake draft print since you would use the same formatter for each target. As I said, this all gets somewhat complicated.