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
	      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.
All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/
Questions that are not answered by the
    documentation may be
    sent to <freebsd-questions@FreeBSD.org>.
    Send questions about this document to <freebsd-doc@FreeBSD.org>.