Using Tools: 13.4 pmake: Contents of a Makefile

Up: GEOS SDK TechDocs | Up | Prev: 13.3 Command Line Arguments | Next: 13.5 Advanced pmake Techniques

The pmake program takes as input a file that tells

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 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.

Dependency Lines

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 (to represent a colon in a target, you must precede it with a backslash: "\:"). 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 operator, 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, as described in Shell Commands).

Suppose 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.EXE 			: A.OBJ B.OBJ C.OBJ
A.OBJ B.OBJ C.OBJ 			: DEFS.H
A.OBJ 			: A.C
B.OBJ 			: B.C
C.OBJ 			: C.C

You may be wondering at this point, where A.OBJ, B.OBJ and C.OBJ came in and why they depend on defs.h and the C files don't. The reason is quite simple: PROGRAM.EXE cannot be made by linking together .c files--it must be made from .obj files. Likewise, if you change DEFS.H, it isn't the .c files that need to be re-created, it's the .obj 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 .obj 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. For example, A.OBJ depends on both DEFS.H and A.C.

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.EXE comes first in the example makefile, above.

Both targets and sources may contain the standard C-Shell wildcard characters ({, }, *, ?, [, and ]), but the square braces 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.

Shell Commands

At this point, you may be wondering how files are re-created. The re-creation is accomplished by commands you place in the makefile. These commands are passed to the shell to be executed and are expected to do what's necessary to update the target file. (The pmake program 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, 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.

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 given earlier.

To expand on the earlier makefile, you might add commands as follows:

PROGRAM.EXE : A.OBJ B.OBJ C.OBJ
	BCC A.OBJ B.OBJ C.OBJ -o PROGRAM.EXE
A.OBJ B.OBJ C.OBJ : DEFS.H
A.OBJ : A.C
	bcc -c A.C
B.OBJ : B.C
	bcc -c B.C
C.OBJ : C.C
	bcc -c C.C

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

There is another way in which makefile commands differ from regular shell commands. The first two characters after the initial whitespace are treated specially. If they are any combination of " @" and " -", they cause pmake to do things differently.

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, perhaps "echo Linking index" it would be rather messy to output

echo Linking index
Linking index

The other special character is the dash ("-"). 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 zero 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. This behavior can be altered, however, by placing a "-" at the front of a command (e.g. "-copy index index.old") . In such a case, the non-zero status is simply ignored and pmake keeps going.

If the system call should be made through the DOS COMMAND.COM, precede the shell command with a backquote (`).

Variables

The pmake program 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. Variables are assigned using lines of the form

VARIABLE  = value

append using lines of the form

VARIABLE  += value

conditionally assigned (if the variable isn't already defined) by using lines of the form

VARIABLE  ?= value

and assigned 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 using lines of the form

VARIABLE  := value

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

The final way a variable may be assigned is using lines of the form

VARIABLE  != shell-command

or, if the shell command requires the use of the command.com interpreter,

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:

CURRENT_DIR != `cd

The value of a variable may be retrieved by enclosing the variable name in parentheses or curly braces and preceding the whole thing with a dollar sign. For example, to set the variable CFLAGS to the string "-I\NIHON\LIB\LIBC -O", you would place a line

CFLAGS = -I\NIHON\LIB\LIBC -O

in the makefile and use the expression

$(CFLAGS)

wherever you would like the string "-I\NIHON\LIB\LIBC -O" to appear. This is called 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. 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. That is, 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.

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. For example, for the target ..\..\LIB\FSREAD.C , this variable would contain FSREAD.

One other local variable, .IMPSRC, is set only for certain targets under special circumstances. It is discussed below.

Two of these variables may be used in sources as well as in shell scripts. These are .TARGET and .PREFIX. 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.

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\NIHON\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. 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. The := and ?= operators will work if the only variables used are in the environment.

Global Variables

Global variables are those set or appended in the makefile. There are two classes of global variables: those you set and those pmake sets. 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 contain only upper-case letters. 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 (See Special Targets ).

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 to global variables.

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. that 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.

Many other standard environment variables are defined and described in the Include\GEOS.MK included Makefile.

Using all these variables, you can compress the sample makefile even more:

OBJS = A.OBJ B.OBJ C.OBJ
PROGRAM.EXE : $(OBJS)
	BCC $(.ALLSRC) -o $(.TARGET)
$(OBJS) : DEFS.H
A.OBJ : A.C
	BCC -c A.C
B.OBJ : B.C
	BCC -c B.C
C.OBJ : C.C
	BCC -c C.C

In addition to variables which pmake will use, you can set environment variables which shell commands may use using the pmake_set directive.

.C.EBJ :
pmake_set CL = $(CCOMFLAGS) /Fo$(.TARGET)
$(CCOM) $(.IMPSRC)

You might use the above sequence to set up an argument list in the CL environment variable if your compiler (invoked with CCOM) needed its arguments in such a variable and was unable to take arguments in a file.

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 where they might be misinterpreted as a shell command.

Transformation Rules

As you know, a file's name consists of two parts: a base name, which gives some hint as to the contents of the file, and a suffix, which usually indicates the format of the file. Over the years, as DOS has developed, naming conventions, with regard to suffixes, have also developed that have become almost incontrovertible. For example, a file ending in .C is assumed to contain C source code; one with a .OBJ suffix is assumed to be a compiled, relocatable object file that may be linked into any program. One of the best aspects of pmake comes from its understanding of how the suffix of a file pertains to its contents and their ability to do things with a file based solely on its suffix. This ability comes from something known as a transformation rule. A transformation rule specifies how to change a file with one suffix into a file with another suffix.

A transformation rule looks much like a dependency line, except the target is made of two known suffixes stuck together. Suffixes are made known to pmake by placing them as sources on a dependency line whose target is the special target .SUFFIXES. For example:

.SUFFIXES 			: .obj .c
.c.obj 			:
	$(CCOM) $(CFLAGS) -c $(.IMPSRC)

The creation script attached to the target is used to transform a file with the first suffix (in this case, .c) into a file with the second suffix (here, .obj). In addition, the target inherits whatever attributes have been applied to the transformation rule. The simple rule above says that to transform a C source file into an object file, you compile it using your C compiler with the -c flag.

This rule is taken straight from the system makefile. Many transformation rules (and suffixes) are defined there; you should look there for more examples (type "pmake -h" to find out where it is).

There are some things to note about the transformation rule given above:

  1. The .IMPSRC variable. This variable is set to the "implied source" (the file from which the target is being created; the one with the first suffix), which, in this case, is the .c file.
  2. The CFLAGS variable. Almost all of the transformation rules in the system makefile are set up using variables that you can alter in your makefile to tailor the rule to your needs. In this case, if you want all your C files to be compiled with the -g flag, to provide information for Swat or CodeView, you would set the CFLAGS variable to contain -g ("CFLAGS = -g") and pmake would take care of the rest.

To give you a quick example, the makefile could be changed to this:

OBJS = A.OBJ B.OBJ C.OBJ
PROGRAM .EXE			: $(OBJS)
	$(CCOM) -o $(.TARGET) $(.ALLSRC)
$(OBJS) 			: DEFS.H

The transformation rule given above takes the place of the 6 lines.

A.OBJ : A.C
	BCC -c A.C
B.OBJ : B.C
	BCC -c B.C
C.OBJ : C.C
	BCC -c C.C

Now you may be wondering about the dependency between the .obj and .c files--it's not mentioned anywhere in the new makefile. This is because it isn't needed: one of the effects of applying a transformation rule is the target comes to depend on the implied source (hence the name).

For a more detailed example, Suppose you have a makefile like this:

A.EXE 			: A.OBJ B.OBJ
	$(CCOM) $(.ALLSRC)

and a directory set up like this:

total 4

MAKEFILE 		  34	09-07-89 		12:43a
A        C		 119	10-03-89 		 7:39p
A        OBJ 		201	09-07-89 		12:43a
B        C		  69	09-07-89 		12:43a

While just typing " pmake " will do the right thing, it's much more informative to type " pmake -ds " This will show you what pmake is up to as it processes the files. In this case, pmake prints the following:

Suff_FindDeps (A.EXE)
	using existing source A.OBJ
	applying .OBJ -> .EXE to "A.OBJ"
Suff_FindDeps (A.OBJ)
	trying A.C...got it
	applying .C -> .OBJ to "A.C"
Suff_FindDeps (B.OBJ)
	trying B.C...got it
	applying .C -> .OBJ 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.OBJ ---
bcc -c A.C
--- B.OBJ ---
bcc -c B.C
--- A.EXE ---
bcc A.OBJ B.OBJ

Suff_FindDeps is the name of a function in pmake that is called to check for implied sources for a target using transformation rules. The transformations it tries are, naturally enough, limited to the ones that have been defined (a transformation may be defined multiple times, by the way, but only the most recent one will be used). You will notice, however, that there is a definite order to the suffixes that are tried. This order is set by the relative positions of the suffixes on the .SUFFIXES line--the earlier a suffix appears, the earlier it is checked as the source of a transformation. Once a suffix has been defined, the only way to change its position is to remove all the suffixes (by having a .SUFFIXES dependency line with no sources) and redefine them in the order you want. (Previously-defined transformation rules will be automatically redefined as the suffixes they involve are re-entered.)

Another way to affect the search order is to make the dependency explicit. In the above example, a.exe depends on a.obj and b.obj. Since a transformation exists from .obj to .exe, pmake uses that, as indicated by the "using existing source a.obj" message.

The search for a transformation starts from the suffix of the target and continues through all the defined transformations, in the order dictated by the suffix ranking, until an existing file with the same base (the target name minus the suffix and any leading directories) is found. At that point, one or more transformation rules will have been found to change the one existing file into the target.

For example, ignoring what's in the system makefile for now, say you have a makefile like this:

.SUFFIXES : .EXE .OBJ .C .Y .L
.L.C :
	LEX $(.IMPSRC)
	MOVE LEX.YY.C $(.TARGET)
.Y.C :
	YACC $(.IMPSRC)
	MOVE Y.TAB.C $(.TARGET)
.C.OBJ :
	BCC -L $(.IMPSRC)
.OBJ.EXE :
	BCC -o $(.TARGET) $(.IMPSRC)

and the single file jive.l. If you were to type pmake -rd ms jive.exe , you would get the following output for jive.exe:

Suff_FindDeps (JIVE.EXE)
trying JIVE.OBJ...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 -> .OBJ to "JIVE.C"
applying .OBJ -> .EXE to "JIVE.OBJ"

The pmake tool starts with the target jive.exe, figures out its suffix (.exe) and looks for things it can transform to a .exe file. In this case, it only finds .obj, so it looks for the file JIVE.OBJ.

It fails to find it, so it looks for transformations into a .obj file. Again it has only one choice: .c. So it looks for JIVE.C and fails to find it. At this point it can create the .c file from either a .y file or a .l file. Since .y came first on the .SUFFIXES line, it checks for jive.y first, but can't find it, so it looks for jive.l. At this point, it has defined a transformation path as follows: .l->.c-> .obj-> .exe and applies the transformation rules accordingly. For completeness, and to give you a better idea of what pmake actually did with this three-step transformation, this is what pmake printed for the rest of the process:

Suff_FindDeps (JIVE.OBJ)
using existing source JIVE.C
applying .C -> .OBJ 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.OBJ...non-existent...out-of-date
--- JIVE.OBJ ---
bcc -c JIVE.C
Examining JIVE.EXE...non-existent...out-of-date
--- JIVE.EXE ---
bcc -o JIVE.EXE JIVE.OBJ

Including Other Makefiles

Just as for programs, it is often useful to extract certain parts of a makefile into another file and just include it in other makefiles somehow. Many compilers allow you to use something like

#include "defs.h"

to include the contents of defs.h in the source file. The pmake program allows you to do the same thing for makefiles, with the added ability to use variables in the filenames. An include directive in a makefile looks either like this

#include <file>

or like this

#include "file"

The difference between the two is where pmake searches for the file: the first way, pmake will look for the file only in the system makefile directory (to find out what that directory is, give pmake the -h flag).

For files in double-quotes, the search is more complex; pmake will look in the following places in the given order:

  1. The directory of the makefile that's including the file.
  2. The current directory (the one in which you invoked pmake ).
  3. The directories given by you using -I flags, in the order in which you gave them.
  4. Directories given by .PATH dependency lines.
  5. The system makefile directory.

You are free to use pmake variables in the filename-- pmake will expand them before searching for the file. You must specify the searching method with either angle brackets or double-quotes outside of a variable expansion. That is, the following

SYSTEM= <command.mk>
#include $(SYSTEM)

won't work; instead use the following:

SYSTEM= command.mk
#include <$(SYSTEM)>

Saving Commands

There may come a time when you will want to save certain commands to be executed when everything else is done, by inserting an ellipsis "..." in the Makefile. Commands saved in this manner are only executed if pmake manages to re-create everything without an error.

Target Attributes

The pmake tool allows you to give attributes to targets by means of special sources. Like everything else pmake uses, these sources begin with a period and are made up of all upper-case letters. By placing one (or more) of these as a source on a dependency line, you are "marking" the target(s) with that attribute.

Any attributes given as sources 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 pmake can't figure out how to create it, it will ignore this fact and assume the file isn't really needed or actually exists and pmake just can't find it. This may prove wrong, but the error will be noted later on, not when pmake tries to create the target so marked. This attribute also prevents pmake from attempting to touch the target if given the "-t" flag.
.EXEC
This attribute causes its shell script to be executed while having no effect on targets that depend on it. This makes the target into a sort of subroutine. EXEC sources don't appear in the local variables of targets that depend on them (nor are they touched if pmake is given the -t flag).
.IGNORE
Giving a target the .IGNORE attribute causes pmake to ignore errors from any of the target's commands, as if they all had "-" before them.
.MAKE
The .MAKE attribute marks its target as being a recursive invocation of pmake . This forces pmake to execute the script associated with the target (if it's out-of-date) even if you gave the -n or -t flag. By doing this, you can start at the top of a system and type
pmake -n

and have it descend the directory tree (if your makefiles are set up correctly), printing what it would have executed if you hadn't included the -n flag.

.NOTMAIN
Normally, if you do not specify a target to make in any other way, pmake will take the first target on the first dependency line of a makefile 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 flag. Giving a target, this attribute tells pmake that the target is definitely not the Main Target. This allows you to place targets in an included makefile and have pmake create something else by default.
.PRECIOUS
When pmake is interrupted (by someone typing control-C at the keyboard), it will attempt to clean up after itself by removing any half-made targets. If a target has the .PRECIOUS attribute, however, pmake will leave it alone. A side effect of the "::" operator is to mark the targets as .PRECIOUS.
.SILENT
Marking a target with this attribute keeps its commands from being printed when they're executed, just as if they had an "@" in front of them.
.USE
By giving a target this attribute, you turn it into pmake 's equivalent 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) of the source. If the target already has commands, the .USE target's commands are added to the end. If more than one .USE-marked source is given to a target, the rules are applied sequentially.

The typical .USE rule will use the sources of the target to which it is applied (as stored in the .ALLSRC variable for the target) as its "arguments." Several system makefiles (not to be confused with the system makefile) make use of these .USE rules to make developing easier (they're in the default, system makefile directory).

Special Targets

There are certain targets that have special meaning to pmake . When you use one on a dependency line, it is the only target that may appear on the left-hand-side of the operator. As for the attributes and variables, all the special targets begin with a period and consist of upper-case letters only. The targets are as follows:

.BEGIN
Any commands attached to this target are executed before anything else is done. You can use it for any initialization that needs doing.
.DEFAULT
This is sort of a .USE rule for any target (that was used only as a source) that pmake can't figure out any other way to create. It's only "sort of" a .USE rule because only the shell script attached to the .DEFAULT target is used. The .IMPSRC variable of a target that inherits .DEFAULT's commands is set to the target's own name.
.END
This serves a function similar to .BEGIN, in that commands attached to it are executed once everything has been re-created (so long as no errors occurred). It also serves the extra function of being a place on which pmake can hang commands you put off to the end. Thus the script for this target will be executed before any of the commands you save with the ellipsis marker.
.IGNORE
This target marks each of its sources with the .IGNORE attribute. If you don't give it any sources, then it is like giving the -i flag when you invoke pmake --errors are ignored for all commands.
.INCLUDES
The sources for this target are taken to be suffixes that indicate a file that can be included in a program source file. The suffix must already be declared with .SUFFIXES. Any suffix so marked will have the directories on its search path (see .PATH, below) placed in the .INCLUDES variable, each preceded by a "-I" flag. This variable can then be used as an argument for the compiler in the normal fashion. The ".h" suffix is already marked in this way in the system makefile. For example, if you have
.SUFFIXES 			: .PCX
.PATH.PCX 			: \CLIPART
.INCLUDES 			: .PCX

pmake places "-I\CLIPART" in the .INCLUDES variable and you can say

bcc $(.INCLUDES) -c xprogram.c

(Note: the . INCLUDES variable is not actually filled in until the entire makefile has been read.)

.INTERRUPT
When pmake is interrupted, it will execute the commands in the script for this target, if it exists.
.LIBS
This does for libraries what .INCLUDES does for include files, except the flag used is "-L", as required by those linkers that allow you to tell them where to find libraries. The variable used is .LIBS.
.MAIN
If you didn't give a target (or targets) to create when you invoked pmake , it will take the sources of this target as the targets to create.
.MAKEFLAGS
This target provides a way for you to always specify flags for pmake when the makefile is used. The flags are just as they would be typed to the shell (except you can't use shell variables unless they're in the environment), though the -f and -r flags have no effect.
.PATH
If you give sources for this target, pmake will take them as directories in which to search for files it cannot find in the current directory. If you give no sources, it will clear out any directories added to the search path before.
.PATHsuffix
This does a similar thing to .PATH, but it does it only for files with the given suffix. The suffix must have been defined already. Look at Search Paths for more information.
.PRECIOUS
Similar to .IGNORE, this gives the .PRECIOUS attribute to each source on the dependency line, unless there are no sources, in which case the .PRECIOUS attribute is given to every target in the file.
.RECURSIVE
This target applies the .MAKE attribute to all its sources. It does nothing if you don't give it any sources.
.SILENT
When you use .SILENT as a target, it applies the .SILENT attribute to each of its sources. If there are no sources on the dependency line, then it is as if you gave pmake the -s flag and no commands will be echoed.
.SUFFIXES
This is used to give new file suffixes for pmake to handle. Each source is a suffix pmake should recognize. If you give a .SUFFIXES dependency line with no sources, pmake will forget about all the suffixes it knew.

In addition to these targets, a line of the form

attribute : sources

applies the attribute to all the targets listed as sources .

Modifying Variable Expansion

Variables need not always be expanded verbatim. The pmake program defines several modifiers that may be applied to a variable's value before it is expanded. You apply a modifier by placing it after the variable name with a colon between the two, like so:

${VARIABLE:modifier}

Each modifier is a single character followed by something specific to the modifier itself. You may apply as many modifiers as you want--each one is applied to the result of the previous and is separated from the previous by another colon.

There are several ways to modify a variable's expansion:

:M pattern
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 like that used by the shell, where * means zero or more characters of any sort; ? is any single character; [abcd] matches any single character that is one of "a", "b", "c" or "d" (there may be any number of characters between the brackets); [0-9] matches any single character that is between "0" and "9" (i.e. any digit--this form may be freely mixed with the other bracket form), and `\' is used to escape any of the characters "*", "?", "[" or ":", leaving them as regular characters to match themselves in a word.
Remember that the pattern matcher requires you to prefix certain characters with a backslash, including backslash itself. this can lead to some impressive search strings, because pmake also requires that backslashes be preceded with backslashes:
	#if !empty(CURRENT_DIR:M*\\\\APPL\\\\*)

The above line checks to see if the current directory matches the form "*\APPL\*". (The pattern matcher is passed the string "*\\APPL\\*".)

:N pattern
This is identical to :M except it substitutes all words that don't match the given pattern.
:X pattern
This is like :M except that it returns only part of the matching string. You mark which part of the string you are interested in by enclosing it within backslashed square brackets ("\[" and "\]") (however, due to the backslash rules, you must actually use "\\[" and "\\]".)
	DEVEL_DIR 	:= \
	 $(CURRENT_DIR:X \\[*\\\\$(ROOT_DIR:T)\\\\*\\]\\\\*)

The above line returns part of the CURRENT_DIR string, specifically the directory just under the root directory. Free of backslashes, it's searching for [*\$(ROOT_DIR:T)\*]\*. If there is a subdirectory below the development directory, then this will strip off the lower layers.

:S/ search-string / replacement-string /[g]
This causes the first occurrence of search-string in the variable to be replaced by replacement-string, unless the "g" flag is given at the end, in which case all occurrences of the string are replaced. The substitution is performed on each word in the variable in turn. If search-string begins with a caret ("^"), the string must match starting at the beginning of the word. If search-string ends with a dollar sign ("$"), the string must match to the end of the word (these two may be combined to force an exact match). If a backslash precedes these two characters, however, they lose 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 a backslash. Thus, ":S/[A-D]/&&/" will find the string "[A-D]" and replace it with the string "[A-D][A-D]". You are allowed to use any character except colon or exclamation point to separate the two strings. This so-called delimiter character may be placed in either string by preceding it with a backslash.
:T
Replaces each word in the variable expansion by its last component (its "tail"). For example, given
		OBJS = ..\LIB\A.OBJ B \USR\LIB\LIBM.A
		TAILS = $(OBJS:T)

the variable TAILS would expand to "a.obj b libm.a" .

:H
This is similar to :T, except that every word is replaced by everything but the tail (the "head"). Using the same definition of OBJS, the string "$(OBJS:H)" would expand to "..\LIB \USR\LIB" Note that the final slash on the heads is removed and anything without a head is replaced by a single period.
:E
This replaces each word with its suffix ("extension"), so "$(OBJS:E)" would give you ".OBJ .A" .
:R
This replaces each word with everything but the suffix (thus returning the "root" of the word). "$(OBJS:R)" expands to "..\LIB\A B \USR\LIB\LIBM".

In addition, another style of substitution is also supported. This looks like:

$(VARIABLE:search-string=replacement)

It must be the last modifier in the chain. The search is anchored at the end of each word, so only suffixes or whole words may be replaced.


Up: GEOS SDK TechDocs | Up | Prev: 13.3 Command Line Arguments | Next: 13.5 Advanced pmake Techniques