PMake -- A Tutorial
Prev | Chapter 4 PMake for Gods | Next

# 4.3. On the Condition...

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 `.PATH`suffix`` 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:

`ifdef` | `defined` |
`ifndef` | `!defined` |
`ifmake` | `make` |
`ifnmake` | `!make` |

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.

Prev | Home | Next
Archives and Libraries | Up | A Shell is a Shell is a Shell

This, and other documents, can be downloaded from ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

For questions about FreeBSD, read the documentation before contacting <questions@FreeBSD.org>.
For questions about this documentation, e-mail <doc@FreeBSD.org>.