3. Short-cuts and Other Nice Things
Based on what I've told you so far, you may have gotten the impression
that PMake is just a way of storing away commands and making sure you
don't forget to compile something. Good. That's just what it is.
However, the ways I've described have been inelegant, at best, and
painful, at worst.
This chapter contains things that make the
writing of makefiles easier and the makefiles themselves shorter and
easier to modify (and, occasionally, simpler). In this chapter, I
assume you are somewhat more
familiar with Sprite (or UNIX, if that's what you're using) than I did
in chapter 2, just so you're on your toes.
So without further ado...
3.1. 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
UNIXhas developed,
naming conventions, with regard to suffixes, have also developed that have
become almost as incontrovertible as Law. E.g. a file ending in
.c
is assumed to contain C source code; one with a
.o
suffix is assumed to be a compiled, relocatable object file that may
be linked into any program; a file with a
.ms
suffix is usually a text file to be processed by Troff with the -ms
macro package, and so on.
One of the best aspects of both Make and PMake comes from their
understanding of how the suffix of a file pertains to its contents and
their ability to do things with a file based soley 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.
E.g.
.SUFFIXES : .o .c
.c.o :
$(CC) $(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,
.o).
In addition, the target inherits whatever attributes have been applied
to the transformation rule.
The simple rule given above says that to transform a C source file
into an object file, you compile it using
cc
with the
-c
flag.
This rule is taken straight from the system makefile. Many
transformation rules (and suffixes) are defined there, and I refer you
to it for more examples (type
``pmake -h''
to find out where it is).
There are several 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
dbx,
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 in 2.3.4
could be changed to this:
OBJS = a.o b.o c.o
program : $(OBJS)
$(CC) -o $(.TARGET) $(.ALLSRC)
$(OBJS) : defs.h
The transformation rule I gave above takes the place of the 6 lines[note 1]
a.o : a.c
cc -c a.c
b.o : b.c
cc -c b.c
c.o : c.c
cc -c c.c
Now you may be wondering about the dependency between the
.o
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. That's why it's called the implied
source.
For a more detailed example. Say you have a makefile like this:
a.out : a.o b.o
$(CC) $(.ALLSRC)
and a directory set up like this:
total 4
-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile
-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c
-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o
-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c
While just typing
``pmake''
will do the right thing, it's much more informative to type
``pmake -d s''.
This will show you what PMake is up to as it processes the files. In
this case, PMake prints the following:
Suff_FindDeps (a.out)
using existing source a.o
applying .o -> .out to "a.o"
Suff_FindDeps (a.o)
trying a.c...got it
applying .c -> .o to "a.c"
Suff_FindDeps (b.o)
trying b.c...got it
applying .c -> .o 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.o ---
cc -c a.c
--- b.o ---
cc -c b.c
--- a.out ---
cc a.o b.o
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 in the pecking order 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.out
depends on
a.o
and
b.o.
Since a transformation exists from
.o
to
.out,
PMake uses that, as indicated by the
``using existing source a.o''
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 : .out .o .c .y .l
.l.c :
lex $(.IMPSRC)
mv lex.yy.c $(.TARGET)
.y.c :
yacc $(.IMPSRC)
mv y.tab.c $(.TARGET)
.c.o :
cc -c $(.IMPSRC)
.o.out :
cc -o $(.TARGET) $(.IMPSRC)
and the single file
jive.l.
If you were to type
``pmake -rd ms jive.out,''
you would get the following output for
jive.out:
Suff_FindDeps (jive.out)
trying jive.o...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 -> .o to "jive.c"
applying .o -> .out to "jive.o"
and this is why: PMake starts with the target
jive.out,
figures out its suffix
(.out)
and looks for things it can transform to a
.out
file. In this case, it only finds
.o,
so it looks for the file
jive.o.
It fails to find it, so it looks for transformations into a
.o
file. Again it has only one choice:
.c.
So it looks for
jive.c
and, as you know, fails to find it. At this point it has two choices:
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
and, lo and behold, there it is.
At this point, it has defined a transformation path as follows:
.l
->
.c
->
.o
->
.out
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.o)
using existing source jive.c
applying .c -> .o 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.o...non-existent...out-of-date
--- jive.o ---
cc -c jive.c
Examining jive.out...non-existent...out-of-date
--- jive.out ---
cc -o jive.out jive.o
One final question remains: what does PMake do with targets that have
no known suffix? PMake simply pretends it actually has a known suffix
and searches for transformations accordingly.
The suffix it chooses is the source for the
.NULL
target mentioned later. In the system makefile,
.out
is chosen as the ``null suffix''
because most people use PMake to create programs. You are, however,
free and welcome to change it to a suffix of your own choosing.
The null suffix is ignored, however, when PMake is in compatibility
mode (see chapter 4).
3.2. 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 say something like
#include "defs.h"
to include the contents of
defs.h
in the source file. PMake 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 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 (or directories)
(to find out what that directory is, give PMake the
-h
flag).
The system makefile directory search path can be overridden via the
-m
option.
For files in double-quotes, the search is more complex:
-
- 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 (see chapter 4).
- 5)
-
The system makefile directory.
in that order.
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. I.e. the following
SYSTEM = <command.mk>
#include $(SYSTEM)
won't work.
3.3. Saving Commands
There may come a time when you will want to save certain commands to
be executed when everything else is done. For instance: you're
making several different libraries at one time and you want to create the
members in parallel. Problem is,
ranlib
is another one of those programs that can't be run more than once in
the same directory at the same time (each one creates a file called
__.SYMDEF
into which it stuffs information for the linker to use. Two of them
running at once will overwrite each other's file and the result will
be garbage for both parties). You might want a way to save the ranlib
commands til the end so they can be run one after the other, thus
keeping them from trashing each other's file. PMake allows you to do
this by inserting an ellipsis (``...'') as a command between
commands to be run at once and those to be run later.
So for the
ranlib
case above, you might do this:
lib1.a : $(LIB1OBJS)
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
lib2.a : $(LIB2OBJS)
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
This would save both
ranlib $(.TARGET)
commands until the end, when they would run one after the other
(using the correct value for the
.TARGET
variable, of course).
Commands saved in this manner are only executed if PMake manages to
re-create everything without an error.
3.4. Target Attributes
PMake 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. There are various
reasons for using them, and I will try to give examples for most of
them. Others you'll have to find uses for yourself. Think of it as ``an
exercise for the reader.'' By placing one (or more) of these as a source on a
dependency line, you are ``marking the target(s) with that
attribute.'' That's just the way I phrase it, so you know.
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 it is 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. An example. Say you have some LISP files that need to
be compiled and loaded into a LISP process. To do this, you echo LISP
commands into a file and execute a LISP with this file as its input
when everything's done. Say also that you have to load other files
from another system before you can compile your files and further,
that you don't want to go through the loading and dumping unless one
of
your
files has changed. Your makefile might look a little bit
like this (remember, this is an educational example, and don't worry
about the
COMPILE
rule, all will soon become clear, grasshopper):
system : init a.fasl b.fasl c.fasl
for i in $(.ALLSRC);
do
echo -n '(load "' >> input
echo -n ${i} >> input
echo '")' >> input
done
echo '(dump "$(.TARGET)")' >> input
lisp < input
a.fasl : a.l init COMPILE
b.fasl : b.l init COMPILE
c.fasl : c.l init COMPILE
COMPILE : .USE
echo '(compile "$(.ALLSRC)")' >> input
init : .EXEC
echo '(load-system)' > input
-
.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).
Note that all the rules, not just that for
system,
include
init
as a source. This is because none of the other targets can be made
until
init
has been made, thus they depend on it.
- .EXPORT
-
This is used to mark those targets whose creation should be sent to
another machine if at all possible. This may be used by some
exportation schemes if the exportation is expensive. You should ask
your system administrator if it is necessary.
- .EXPORTSAME
-
Tells the export system that the job should be exported to a machine
of the same architecture as the current one. Certain operations (e.g.
running text through
nroff)
can be performed the same on any architecture (CPU and
operating system type), while others (e.g. compiling a program with
cc)
must be performed on a machine with the same architecture. Not all
export systems will support this attribute.
- .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.
- .INVISIBLE
-
This allows you to specify one target as a source for another without
the one affecting the other's local variables. Useful if, say, you
have a makefile that creates two programs, one of which is used to
create the other, so it must exist before the other is created. You
could say
prog1 : $(PROG1OBJS) prog2 MAKEINSTALL
prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
where
MAKEINSTALL
is some complex .USE rule (see below) that depends on the
.ALLSRC
variable containing the right things. Without the
.INVISIBLE
attribute for
prog2,
the
MAKEINSTALL
rule couldn't be applied. This is not as useful as it should be, and
the semantics may change (or the whole thing go away) in the
not-too-distant future.
- .JOIN
-
This is another way to avoid performing some operations in parallel
while permitting everything else to be done so. Specifically it
forces the target's shell script to be executed only if one or more of the
sources was out-of-date. In addition, the target's name,
in both its
.TARGET
variable and all the local variables of any target that depends on it,
is replaced by the value of its
.ALLSRC
variable.
As an example, suppose you have a program that has four libraries that
compile in the same directory along with, and at the same time as, the
program. You again have the problem with
ranlib
that I mentioned earlier, only this time it's more severe: you
can't just put the ranlib off to the end since the program
will need those libraries before it can be re-created. You can do
something like this:
program : $(OBJS) libraries
cc -o $(.TARGET) $(.ALLSRC)
libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
ranlib $(.OODATE)
In this case, PMake will re-create the
$(OBJS)
as necessary, along with
lib1.a,
lib2.a,
lib3.a
and
lib4.a.
It will then execute
ranlib
on any library that was changed and set
program's
.ALLSRC
variable to contain what's in
$(OBJS)
followed by
``lib1.a lib2.a lib3.a lib4.a.''
In case you're wondering, it's called
.JOIN
because it joins together different threads of the ``input graph'' at
the target marked with the attribute.
Another aspect of the .JOIN attribute is it keeps the target from
being created if the
-t
flag was given.
- .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.
- .NOEXPORT
-
If possible, PMake will attempt to export the creation of all targets to
another machine (this depends on how PMake was configured). Sometimes,
the creation is so simple, it is pointless to send it to another
machine. If you give the target the
.NOEXPORT
attribute, it will be run locally, even if you've given PMake the
-L 0
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 (you type 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. An additional 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 (as I call them) will use the sources of the
target to which it is applied (as stored in the
.ALLSRC
variable for the target) as its ``arguments,'' if you will.
For example, you probably noticed that the commands for creating
lib1.a
and
lib2.a
in the example in section 3.3
were exactly the same. You can use the
.USE
attribute to eliminate the repetition, like so:
lib1.a : $(LIB1OBJS) MAKELIB
lib2.a : $(LIB2OBJS) MAKELIB
MAKELIB : .USE
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
-
Several system makefiles (not to be confused with The System Makefile)
make use of these .USE rules to make your
life easier (they're in the default, system makefile directory...take a look).
Note that the .USE rule source itself
(MAKELIB)
does not appear in any of the targets's local variables.
There is no limit to the number of times I could use the
MAKELIB
rule. If there were more libraries, I could continue with
``lib3.a : $(LIB3OBJS) MAKELIB''
and so on and so forth.
3.5. Special Targets
As there were in Make, so 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.
I won't describe them all in detail because some of them are rather
complex and I'll describe them in more detail than you'll want in
chapter 4.
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 ``...''.
- .EXPORT
-
The sources for this target are passed to the exportation system compiled
into PMake. Some systems will use these sources to configure
themselves. You should ask your system administrator about this.
- .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 have already been declared with
.SUFFIXES
(see below).
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.
E.g. if you have
.SUFFIXES : .bitmap
.PATH.bitmap : /usr/local/X/lib/bitmaps
.INCLUDES : .bitmap
PMake will place
``-I/usr/local/X/lib/bitmaps''
in the
.INCLUDES
variable and you can then say
cc $(.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.
Be forewarned that PMake may not have been compiled to do this if the
linker on your system doesn't accept the
-L
flag, though the
.LIBS
variable will always be defined once the makefile has been read.
- .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.
- .NULL
-
This allows you to specify what suffix PMake should pretend a file has
if, in fact, it has no known suffix. Only one suffix may be so
designated. The last source on the dependency line is the suffix that
is used (you should, however, only give one suffix...).
- .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. Since the effects of this all get
very complex, I'll leave it til chapter four to give you a complete
explanation.
- .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
(section 4.1)
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.
- .SHELL
-
PMake is not constrained to only using the Bourne shell to execute
the commands you put in the makefile. You can tell it some other shell
to use with this target. Check out
A Shell is a Shell is a Shell
(section 4.4)
for more information.
- .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 (this also nukes the null suffix).
For those targets that need to have suffixes defined, this is how you do it.
In addition to these targets, a line of the form
attribute : sources
applies the
attribute
to all the targets listed as
sources.
3.6. Modifying Variable Expansion
Variables need not always be expanded verbatim. PMake 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 seven ways to modify a variable's expansion, most of which
come from the C shell variable modification characters:
-
- Mpattern
-
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 0 or more characters of any sort;
?
is any single character;
[abcd]
matches any single character that is either `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.
For example, the system makefile
<makedepend.mk>
uses
``$(CFLAGS:M-[ID]*)''
to extract all the
-I
and
-D
flags that would be passed to the C compiler. This allows it to
properly locate include files and generate the correct dependencies.
- Npattern
-
This is identical to
:M
except it substitutes all words that don't match the given pattern.
- S/search-string/replacement-string/[g]
-
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 occurences of the string
are replaced. The substitution is performed on each word in the
variable in turn. If
search-string
begins with a
^,
the string must match starting at the beginning of the word. If
search-string
ends with a
$,
the string must match to the end of the word (these two may be
combined to force an exact match). If a backslash preceeds 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.
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 preceeding it
with a backslash.
- T
-
Replaces each word in the variable expansion by its last
component (its ``tail''). For example, given
OBJS = ../lib/a.o b /usr/lib/libm.a
TAILS = $(OBJS:T)
the variable
TAILS
would expand to
``a.o 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 the empty string.
- E
-
:E
replaces each word by its suffix (``extension''). So
``$(OBJS:E)''
would give you
``.o .a.''
- R
-
This replaces each word by everything but the suffix (the ``root'' of
the word).
``$(OBJS:R)''
expands to ``
../lib/a b /usr/lib/libm.''
In addition, the System V 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.
3.7. More on Debugging
3.8. More Exercises
- (3.1)
-
You've got a set programs, each of which is created from its own
assembly-language source file (suffix
.asm).
Each program can be assembled into two versions, one with error-checking
code assembled in and one without. You could assemble them into files
with different suffixes
(.eobj
and
.obj,
for instance), but your linker only understands files that end in
.obj.
To top it all off, the final executables
must
have the suffix
.exe.
How can you still use transformation rules to make your life easier
(Hint: assume the error-checking versions have
ec
tacked onto their prefix)?
- (3.2)
-
Assume, for a moment or two, you want to perform a sort of
``indirection'' by placing the name of a variable into another one,
then you want to get the value of the first by expanding the second
somehow. Unfortunately, PMake doesn't allow constructs like
$($(FOO))
What do you do? Hint: no further variable expansion is performed after
modifiers are applied, thus if you cause a $ to occur in the
expansion, that's what will be in the result.