MirBSD manpage: 12.make(PSD)


                     Make -- A Tutorial

                        Adam de Boor

                     Berkeley Softworks
                2150 Shattuck Ave, Penthouse
                     Berkeley, CA 94704
                      adam@bsw.uu.net
                     ...!uunet!bsw!adam

1. Introduction
This is a historic document describing PMake, the antecessor
of  BSD  and  MirOS  make, NetBSD(TM) nmake and some others.
Please keep this in mind while reading the  following  docu-
mentation.  In case of doubt please consider the manual page
for your make executable.
Make is a program for creating other programs,  or  anything
else  you  can think of for it to do.  The basic idea behind
Make is that, for any given system, be it  a  program  or  a
document  or  whatever, there will be some files that depend
on the state of other files (on when they  were  last  modi-
fied).   Make  takes  these  dependencies,  which  you  must
specify, and uses them to build whatever it is you  want  it
to build.
OpenBSD's Make is based upon PMake, a parallel  make  origi-
nally  developed for the distributed operating system called
Sprite. PMake departs from usual Make practices  in  several
ways.  A  large number of those quirks are not relevant in a
modern POSIX world, and hence development of OpenBSD's  make
has  aimed at removing unwanted differences. Useful features
of OpenBSD's Make which are  not  POSIX  compliant  will  be
flagged  with  a  little sign in the left margin, like this:
Also note that this  tutorial  was  originally  written  for
PMake, and hence may not be totally accurate.
This  tutorial  is  divided   into   three   main   sections
corresponding  to  basic,  intermediate  and  advanced  Make
usage. If you already know Make well, you will only need  to
_________________________
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies.
The University of California, Berkeley Softworks, and Adam de Boor make no
representations about the suitability of this software for any
purpose.  It is provided "as is" without express or implied warranty.

                     December 24, 2022

PSD:12-2                                  Make -- A Tutorial

skim chapter 2. Things in chapter 3 make life  much  easier,
while  those  in  chapter  4 are strictly for those who know
what they are doing. Chapter 5 has definitions for the  jar-
gon  I  use and chapter 6 contains possible solutions to the
problems presented throughout the tutorial.

2. The Basics of Make
Make takes as input a file that tells a) which files  depend
on  which other files to be complete and b) what to do about
files that are ``out-of-date.'' 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, Make will look for Makefile and makefile
(in that order) in the current directory if you  don't  tell
it  otherwise.  To  specify a different makefile, use the -f
flag (e.g. ``make -f program.mk'').
A makefile has four different types of lines in it:
     + File dependency specifications
     + Creation commands
     + Variable assignments
     + Comments, include statements and  conditional  direc-
       tives
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 Make.

2.1. Dependency Lines
As mentioned in the introduction, 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  depen-
dencies  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 deter-
mined 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

                     December 24, 2022

Make -- A Tutorial                                  PSD:12-3

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.  Their exact semantics are as fol-
lows:
:    If a colon is used, a target on the line is  considered
     to be ``out-of-date'' (and in need of creation) if
     + any of the sources has been  modified  more  recently
       than the target, or
     + the target doesn't exist.
     Under this operation, 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:
     + any of the sources has been  modified  more  recently
       than the target, or
     + the target doesn't exist, or
     + the target has no sources.
     If the target is out-of-date according to these  rules,
     it  will  be  re-created. This operator also does some-
     thing else to the targets, but I'll go into that in the
     next section (``Shell Commands'').
Enough words, now for an example. Take that C program I men-
tioned  earlier.  Say  there are three C files (a.c, b.c and
c.c) each of which includes the file defs.h.  The  dependen-
cies between the files could then be expressed as follows:

        program         : a.o b.o c.o
        a.o b.o c.o     : defs.h
        a.o             : a.c
        b.o             : b.c
        c.o             : c.c

You may be wondering at this point, where a.o, b.o  and  c.o
came in and why they depend on defs.h and the C files don't.
The reason is quite simple: program cannot be made by  link-
ing  together  .c  files  --  it must be made from .o files.
Likewise, if you change defs.h, it isn't the .c  files  that
need  to  be  re-created, it's the .o 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  .o  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. E.g. a.o depends on both defs.h and a.c.
The order of the dependency lines in the makefile is  impor-
tant:  the  first target on the first dependency line in the

                     December 24, 2022

PSD:12-4                                  Make -- A Tutorial

makefile will be the one that gets made  if  you  don't  say
otherwise.  That's  why  program  comes first in the example
makefile, above.
Both targets and sources may contain  the  standard  C-Shell
wildcard  characters  ({,  },  *, ?, [, and ]), but the non-
curly-brace ones 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  ele-
     ment. 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  Make  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 con-
     tained in  the  list.  E.g.  [A-Za-z]  will  match  all
     letters, while [0123456789] will match all numbers.

2.2. Shell Commands
``Isn't that nice,'' you say  to  yourself,  ``but  how  are
files  actually `re-created,' as he likes to spell it?'' The
re-creation is accomplished by commands  you  place  in  the
makefile. These commands are passed to the MirBSD Korn shell
(better known as  ``/bin/mksh'')  to  be  executed  and  are
expected  to  do  what's necessary to update the target file
(Make 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 at a terminal, with one important exception:
each command in a makefile must be preceded by at least  one
tab.
Each target has associated with it a set of one or  more  of
these  shell  commands.  The  creation  script  for a target
should immediately follow the dependency line for that  tar-
get.  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 set of shell commands. This set
of shell commands will only be executed if the target on the
associated  dependency  line  is out-of-date with respect to

                     December 24, 2022

Make -- A Tutorial                                  PSD:12-5

the sources on that line, according to the rules I gave ear-
lier. I'll give you a good example of this later on.
To expand on the earlier makefile, you might add commands as
follows:

        program         : a.o b.o c.o
                cc a.o b.o c.o -o program
        a.o b.o c.o     : defs.h
        a.o             : a.c
                cc -c a.c
        b.o             : b.c
                cc -c b.c
        c.o             : c.c
                cc -c c.c

Something you should remember when writing  a  makefile  is,
the  commands  will  be executed if the target on the depen-
dency line is out-of-date, not the sources. In this example,
the command ``cc -c a.c'' will be executed if a.o is out-of-
date. Because of the `:' operator, this  means  that  should
a.c or defs.h have been modified more recently than a.o, the
command will be executed (a.o  will  be  considered  out-of-
date).
Remember how I said the only difference between  a  makefile
shell  command  and  a regular shell command was the leading
tab? I lied. There is another way in which makefile commands
differ from regular ones. The first two characters after the
initial whitespace are treated specially. If  they  are  any
combination  of `@' and `-', they cause Make to do different
things.
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, say ``echo Link-
ing index,'' it would be rather silly to see

        echo Linking index
        Linking index

so Make allows you  to  place  an  `@'  before  the  command
(``@echo  Linking index'') to prevent the command from being
printed.
The other special character is the `-'. In case  you  didn't
know,  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 0 if everything went ok and  non-zero  if  something
went  wrong. For this reason, Make will consider an error to
have occurred if one of the shells it invokes returns a non-
zero  status.  When it detects an error, Make's usual action
is to abort whatever it's doing and  exit  with  a  non-zero
status  itself  (any  other  targets that were being created
will continue being made, but nothing new will  be  started.
Make  will  exit after the last job finishes). This behavior

                     December 24, 2022

PSD:12-6                                  Make -- A Tutorial

can be altered, however, by placing a `-' at the front of  a
command  (``-mv  index  index.old''),  certain  command-line
arguments, or doing other things, to be detailed  later.  In
such  a case, the non-zero status is simply ignored and Make
keeps chugging along.
In Make -j mode, a set of shell commands attached to a  tar-
get  is  fed to a shell as a single script.  This is experi-
mental behavior from PMake's period which hasn't been  fixed
yet.
Make has a -B flag (it stands for backwards-compatible) that
forces  each command to be given to a separate shell. Unfor-
tunately, it also inhibits -j.
A target's shell script is fed to  the  shell  on  its  (the
shell's) input stream. This means that any commands, such as
ci that need to get input from the terminal won't work right
--  they'll  get  the shell's input, something they probably
won't find to their liking. A simple way around this  is  to
give a command like this:

        ci $(SRCS) < /dev/tty

This would force the program's input to come from the termi-
nal.  If  you can't do this for some reason, your only other
alternative is to use  Make  in  its  fullest  compatibility
mode. See Compatibility in chapter 4.

2.3. Variables
Make has the  ability  to  save  text  in  variables  to  be
recalled  later  at  your convenience. Variables in Make are
used much like variables in the  shell  and,  by  tradition,
consist of all upper-case letters (you don't have to use all
upper-case letters. In fact there's nothing to stop you from
calling  a  variable  @^&$%$. Just tradition). Variables are
assigned-to using lines of the form

        VARIABLE = value

appended-to by

        VARIABLE += value

conditionally assigned-to (if  the  variable  isn't  already
defined) by

        VARIABLE ?= value

and assigned-to 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

        VARIABLE := value

Any whitespace before value is stripped off. When appending,

                     December 24, 2022

Make -- A Tutorial                                  PSD:12-7

a  space is placed between the old value and the stuff being
appended.
The final way a variable may be assigned to is using

        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 new-
lines  (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:

        CWD             != pwd

Note: this command will be invoked each time the Makefile is
parsed,  regardless  of whether or not the result will actu-
ally be used for making targets. If the end result  is  only
needed for shell commands, it is much cheaper to use

        VARIABLE = `shell-command`

The value of a variable may be retrieved  by  enclosing  the
variable  name  in parentheses or curly braces and prefixing
the whole thing with a dollar sign.
For example, to  set  the  variable  CFLAGS  to  the  string
``-I/usr/local/include -O,'' you would place a line

        CFLAGS = -I/usr/local/include -O

in the makefile and use  the  word  $(CFLAGS)  wherever  you
would  like  the  string  -I/usr/local/include -O to appear.
This is called variable expansion.
To keep Make from substituting for a variable it knows, pre-
cede the dollar sign with another dollar sign. (e.g. to pass
${HOME} to the shell, use $${HOME}). This  causes  Make,  in
effect, to expand the $ macro, which expands to a single $.
There are two different times at  which  variable  expansion
occurs: When parsing a dependency line, the expansion occurs
immediately upon reading the line. If any variable used on a
dependency  line is undefined, Make will print a message and
exit. Variables in shell commands are expanded when the com-
mand is executed. Variables used inside another variable are
expanded whenever the outer variable is expanded (the expan-
sion  of  an inner variable has no effect on the outer vari-
able. I.e. 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 substi-
tuted 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):
     + Local variables.

                     December 24, 2022

PSD:12-8                                  Make -- A Tutorial

     + Command-line variables.
     + Global variables.
     + Environment variables.
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.

2.3.1. Local Variables
Each target can have as many as seven local variables. These
are variables that are only ``visible'' within that target's
shell commands 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.  POSIX  defines  short
names  for  these variables, which should be used for porta-
bility.  OpenBSD's Make has longer synonyms, which  will  be
used in the rest of this tutorial for clarity.
Four local variables are defined for all targets. They are:
     .TARGET
          The name of the target (POSIX: @).
     .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. (POSIX: ?)
     .ALLSRC
          The list of all sources for  this  target  in  the
          order  in  which they were given. (shorter: >, not
          POSIX).
     .PREFIX
          The target without  its  suffix  and  without  any
          leading     path.     E.g.    for    the    target
          ../../lib/compat/fsRead.c,  this  variable   would
          contain fsRead (POSIX: *) .
Three other local variables are set only for certain targets
under  special  circumstances.  These  are  the ``.IMPSRC,''
``.ARCHIVE,'' and ``.MEMBER'' variables. When they  are  set
and how they are used is described later.
Four of these variables may be used in sources as well as in
shell   commands.   These   are   ``.TARGET'',  ``.PREFIX'',
``.ARCHIVE'' and ``.MEMBER''. 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 exam-
ple,

        $(OBJS)         : $(.PREFIX).c

will create a dependency between each object  file  and  its
corresponding C source file.

2.3.2. Command-line Variables
Command-line variables are set when Make is first invoked by
giving  a  variable  assignment as one of the arguments. For

                     December 24, 2022

Make -- A Tutorial                                  PSD:12-9

example,

        make "CFLAGS = -I/usr/local/include -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 (the search
order, you see). Command-line variables may be set using any
of  the  four  assignment  operators,  though  only = and ?=
behave in a sane way, 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  (you  don't want to know). := and ?= will
work if the only variables used are in the  environment.  !=
is sort of pointless to use from the command line, since the
same effect can no doubt be accomplished using  the  shell's
own  command  substitution  mechanisms  (backquotes  and all
that).

2.3.3. Global Variables
Global  variables  are  those  set  or  appended-to  in  the
makefile.  There  are two classes of global variables: those
you set and those Make sets. As I said before, 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
Make  sets  (almost)  always  begin with a period and always
contain upper-case letters, only. The variables are as  fol-
lows:
     MAKE The name by which Make was invoked  is  stored  in
          this variable.
     .MAKEFLAGS
          All  the  relevant  flags  with  which  Make   was
          invoked. This does not include such things as -f.
Two  other  variables,  ``.INCLUDES''  and  ``.LIBS,''   are
covered in the section on special targets in chapter 3.
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 on global variables.

2.3.4. Environment Variables
Environment variables are passed by the shell  that  invoked
Make  and  are  given by Make to each shell it invokes. They
are expanded like any other variable,  but  they  cannot  be
altered in any way.
Using all these  variables,  you  can  compress  the  sample
makefile even more:

                     December 24, 2022

PSD:12-10                                 Make -- A Tutorial

        OBJS            = a.o b.o c.o
        program         : $(OBJS)
                cc $(.ALLSRC) -o $(.TARGET)
        $(OBJS)         : defs.h
        a.o             : a.c
                cc -c a.c
        b.o             : b.c
                cc -c b.c
        c.o             : c.c
                cc -c c.c

2.4. 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 in a shell command (though the shell will treat
it  as a comment, too). If, for some reason, you need to use
the `#' in a  variable  or  on  a  dependency  line,  put  a
backslash  in front of it. Make will compress the two into a
single `#'.

2.5. Parallelism
PMake was specifically designed to re-create several targets
at  once, when possible, when using the -j flag (see below),
but you do have to be careful at times.
There are several problems you are likely to encounter.  One
is  that some makefiles (and programs) are written in such a
way that it is impossible for two  targets  to  be  made  at
once.  The  program  xstr,  for example, always modifies the
files strings and x.c. There is no way to  change  it.  Thus
you  cannot  run two of them at once without something being
trashed. Similarly, if you have  commands  in  the  makefile
that  always  send  output to the same file, you will not be
able to make more than one target at once unless you  change
the  file  you use. You can, for instance, add a $$$$ to the
end of the file name to tack on the process ID of the  shell
executing  the  command (each $$ expands to a single $, thus
giving you the shell variable $$).
The other problem comes from improperly-specified  dependen-
cies  that  worked in sequential mode. While I don't want to
go into depth on how Make works (look in chapter 4 if you're
interested),  I  will  warn  you that files in two different
``levels'' of the dependency tree may be examined in a  dif-
ferent  order  in parallel mode than in sequential mode. For
example, given the makefile

        a               : b c
        b               : d

Make may examine the targets in the order c, d, b, a. If the
makefile's  author expected Make to abort before making c if
an error occurred while making b, or if b  needed  to  exist
before  c  was  made,  s/he will be sorely disappointed. The

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-11

dependencies are incomplete, since in both  these  cases,  c
would depend on b. So watch out.
Another problem you may face is that, while Make is  set  up
to  handle  the  output  from  multiple  jobs  in a graceful
fashion, the same is not so for input.  It  has  no  way  to
regulate input to different jobs, so if you use the redirec-
tion from /dev/tty I mentioned earlier, you must be  careful
not to run two of the jobs at once.

2.6. Writing and Debugging a Makefile
Now you know most of what's in a makefile, what  do  you  do
next?  There are two choices: (1) use one of the uncommonly-
available makefile generators or (2) write your own makefile
(I  leave  out  the  third choice of ignoring Make and doing
everything by hand as being  beyond  the  bounds  of  common
sense).
When faced with the writing of a  makefile,  it  is  usually
best  to start from first principles: just what are you try-
ing to do? What do you want the makefile finally to produce?
To begin with a somewhat traditional example, let's say  you
need  to  write  a  makefile to create a program, expr, that
takes standard infix expressions and converts them to prefix
form  (for  no  readily  apparent  reason). You've got three
source files, in  C,  that  make  up  the  program:  main.c,
parse.c, and output.c. Harking back to my pithy advice about
dependency lines, you write the first line of the file:

        expr            : main.o parse.o output.o

because you remember expr is made  from  .o  files,  not  .c
files. Similarly for the .o files you produce the lines:

        main.o          : main.c
        parse.o         : parse.c
        output.o        : output.c
        main.o parse.o output.o : defs.h

Great. You've now got the dependencies specified.  What  you
need now is commands. These commands, remember, must produce
the target on the dependency  line,  usually  by  using  the
sources  you've  listed. You remember about local variables?
Good, so it should come to you as no surprise when you write

        expr            : main.o parse.o output.o
                cc -o $(.TARGET) $(.ALLSRC)

Why use the variables? If  your  program  grows  to  produce
postfix  expressions  too (which, of course, requires a name
change or two), it is one fewer place you have to change the
file.  You  cannot  do  this  for the object files, however,
because they depend on their corresponding source files  and
defs.h, thus if you said

                cc -c $(.ALLSRC)

                     December 24, 2022

PSD:12-12                                 Make -- A Tutorial

you'd get (for main.o):

                cc -c main.c defs.h

which is wrong. So you round out  the  makefile  with  these
lines:

        main.o          : main.c
                cc -c main.c
        parse.o         : parse.c
                cc -c parse.c
        output.o        : output.c
                cc -c output.c

The makefile is now complete and will, in fact,  create  the
program  you  want it to without unnecessary compilations or
excessive typing on your part. There are  two  things  wrong
with  it,  however (aside from it being altogether too long,
something I'll address in chapter 3):
1)   The string  ``main.o  parse.o  output.o''  is  repeated
     twice,  necessitating  two changes when you add postfix
     (you were planning on that, weren't you?). This  is  in
     direct  violation  of  de  Boor's First Rule of writing
     makefiles:
     Anything that needs to be written more  than  once
     should be placed in a variable.
     I cannot emphasize this enough as being very  important
     to the maintenance of a makefile and its program.
2)   There is no way to alter the way compilations are  per-
     formed  short  of  editing  the makefile and making the
     change in all places. This  is  evil  and  violates  de
     Boor's  Second  Rule,  which  follows directly from the
     first:
     Any flags  or  programs  used  inside  a  makefile
     should  be  placed  in  a  variable so they may be
     changed,  temporarily  or  permanently,  with  the
     greatest ease.
The makefile should more properly read:

        OBJS            = main.o parse.o output.o
        expr            : $(OBJS)
                $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
        main.o          : main.c
                $(CC) $(CFLAGS) -c main.c
        parse.o         : parse.c
                $(CC) $(CFLAGS) -c parse.c
        output.o        : output.c
                $(CC) $(CFLAGS) -c output.c
        $(OBJS)         : defs.h

Alternatively, if you like the idea of dynamic sources  men-
tioned in section 2.3.1, you could write it like this:

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-13

        OBJS            = main.o parse.o output.o
        expr            : $(OBJS)
                $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
        $(OBJS)         : $(.PREFIX).c defs.h
                $(CC) $(CFLAGS) -c $(.PREFIX).c

These two rules and examples lead to de Boor's First  Corol-
lary:
     Variables are your friends.
Once  you've  written  the  makefile  comes  the  sometimes-
difficult  task  of  making  sure the darn thing works. Your
most helpful tool to make sure the makefile is at least syn-
tactically  correct  is the -n flag, which allows you to see
if Make will choke on the makefile. The second thing the  -n
flag  lets you do is see what Make would do without it actu-
ally doing it, thus you can make  sure  the  right  commands
would be executed were you to give Make its head.
When you find your makefile isn't behaving as you hoped, the
first  question that comes to mind (after ``What time is it,
anyway?'') is ``Why not?'' In answering this, one flag  will
serve you well: ``-d m.'' This causes Make to tell you as it
examines each target in the makefile and indicate why it  is
deciding  whatever  it  is  deciding.  You  can then use the
information printed for other targets to see where you  went
wrong.
Something to be especially careful about is circular  depen-
dencies. E.g.

        a               : b
        b               : c d
        d               : a

In this case, because of the way Make works, c is  the  only
thing  Make  will  examine, because d and a will effectively
fall off the edge of the universe, making it  impossible  to
examine b (or them, for that matter). Make will tell you (if
run in its normal mode) all  the  targets  involved  in  any
cycle it looked at (i.e. if you have two cycles in the graph
(naughty, naughty), but only try to make a target in one  of
them, Make will only tell you about that one. You'll have to
try to make the other to find the second cycle). When run as
Make, it will only print the first target in the cycle.

2.7. Invoking Make
Make comes with a wide variety of flags to choose from. They
may  appear  in  any  order,  interspersed with command-line
variable assignments and targets to create.  Some  of  these
flags are as follows:
-d what
     This causes Make to spew out debugging information that
     may  prove  useful  to you. If you can't figure out why
     Make is doing what it's doing, you might try using this
     flag.   The  what  parameter  is  a  string  of  single

                     December 24, 2022

PSD:12-14                                 Make -- A Tutorial

     characters  that  tell  Make  what  aspects   you   are
     interested in. Most of what I describe will make little
     sense to you, unless you've  dealt  with  Make  before.
     Just  remember  where this table is and come back to it
     as you read on. The characters and the information they
     produce are as follows:
     a    Archive searching and caching.
     c    Conditional evaluation.
     d    The searching and caching of directories.
     j    Various snippets of  information  related  to  the
          running  of  the multiple shells. Not particularly
          interesting.
     m    The making of each target: what  target  is  being
          examined; when it was last modified; whether it is
          out-of-date; etc.
     p    Makefile parsing.
     r    Remote execution.
     s    The application  of  suffix-transformation  rules.
          (See chapter 3)
     t    The maintenance of the list of targets.
     v    Variable assignment.
     Of these all, the m and s letters will be  most  useful
     to you. If the -d is the final argument or the argument
     from which it would get these key  letters  (see  below
     for  a  note about which argument would be used) begins
     with a -, all of these debugging  flags  will  be  set,
     resulting in massive amounts of output.
-f makefile
     Specify a makefile to read different from the  standard
     makefiles (Makefile or makefile). If makefile is ``-'',
     Make uses the standard input. This is useful for making
     quick and dirty makefiles...
-i   If you give this flag, Make will ignore non-zero status
     returned  by any of its shells. It's like placing a `-'
     before all the commands in the makefile.
-k   This is similar to -i in that it allows  Make  to  con-
     tinue  when it sees an error, but unlike -i, where Make
     continues blithely as if nothing went wrong, -k  causes
     it  to  recognize  the  error and only continue work on
     those things that don't depend on  the  target,  either
     directly  or indirectly (through depending on something
     that depends on it), whose creation returned the error.
     The `k' is for ``keep going''...
-m directory
     Tells  Make  another  place  to  search  for   included
     makefiles  via the <...> style.  Several -m options can
     be given to form a search path.  If this  construct  is
     used  the  default  system makefile search path is com-
     pletely overridden. To be explained in chapter 3,  sec-
     tion 3.2.
-n   This flag tells Make not to execute the commands needed
     to  update  the  out-of-date  targets  in the makefile.
     Rather, Make will simply print the  commands  it  would
     have executed and exit. This is particularly useful for

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-15

     checking the correctness of a makefile. If Make doesn't
     do  what  you  expect  it  to,  it's  a good chance the
     makefile is wrong.
-q   If you give Make this flag, it  will  not  try  to  re-
     create  anything.  It will just see if anything is out-
     of-date and exit non-zero if so.
-r   When Make starts up, it reads a default  makefile  that
     tells  it what sort of system it's on and gives it some
     idea of what to do if you don't tell it anything.  I'll
     tell  you about it in chapter 3. If you give this flag,
     Make won't read the default makefile.
-s   This causes Make to not print commands  before  they're
     executed. It is the equivalent of putting an `@' before
     every command in the makefile.
-t   Rather than try to re-create a target, Make will simply
     ``touch'' it so as to make it appear up-to-date. If the
     target didn't exist before, it will when Make finishes,
     but  if  the  target  did exist, it will appear to have
     been updated.
-B   Forces OpenBSD Make to be as POSIX-compatible as possi-
     ble. This includes:
     + Executing one shell per shell command
     + Using sequential mode.
-D variable
     Allows you to define a variable to have  ``1''  as  its
     value.   The  variable  is  a  global  variable,  not a
     command-line variable. This is useful mostly for people
     who  are  used  to  the  C compiler arguments and those
     using conditionals, which I'll get into in section 4.3
-I directory
     Tells  Make  another  place  to  search  for   included
     makefiles. Yet another thing to be explained in chapter
     3 (section 3.2, to be precise).
-P   When creating targets in parallel, several  shells  are
     executing  at  once,  each wanting to write its own two
     cent's-worth to the screen. This output  must  be  cap-
     tured  by  Make  in  some  way  in order to prevent the
     screen from being filled with garbage even more indeci-
     pherable  than  you  usually  see. Make has two ways of
     doing this, one of which provides for much cleaner out-
     put  and  a clear separation between the output of dif-
     ferent jobs, the other of which provides a more immedi-
     ate  response so one can tell what is really happening.
     The former is done by notifying you when  the  creation
     of  a target starts, capturing the output and transfer-
     ring it to the screen all at once  when  the  job  fin-
     ishes. The latter is done by catching the output of the
     shell (and its children)  and  buffering  it  until  an
     entire  line  is received, then printing that line pre-
     ceded by an indication of which job produced  the  out-
     put.  Since  I prefer this second method, it is the one
     used by default. The first method will be used  if  you
     give the -P flag to Make.
Flags without arguments may follow a single `-'. E.g.

                     December 24, 2022

PSD:12-16                                 Make -- A Tutorial

        make -f server.mk -DDEBUG -I/chip2/X/server/include -n

will cause Make to read server.mk  as  the  input  makefile,
define  the variable DEBUG as a global variable and look for
included makefiles in the directory /chip2/X/server/include.

2.8. Summary
A makefile is made of four types of lines:
     + Dependency lines
     + Creation commands
     + Variable assignments
     + Comments, include statements and  conditional  direc-
       tives
A dependency line is a list  of  one  or  more  targets,  an
operator  (`:',  `::',  or  `!'), and a list of zero or more
sources. Sources may contain  wildcards  and  certain  local
variables.
A creation command is a regular shell command preceded by  a
tab.  In addition, if the first two characters after the tab
(and other whitespace) are a combination of `@' or `-', Make
will  cause  the command to not be printed (if the character
is `@') or errors from it to be ignored (if  `-').  A  blank
line,  dependency  line  or variable assignment terminates a
creation script. There may be only one creation  script  for
each target with a `:' or `!' operator.
Variables are places to store text. They may be uncondition-
ally  assigned-to  using the `=' operator, appended-to using
the `+=' operator, conditionally (if the variable  is  unde-
fined)  assigned-to  with the `?=' operator, and assigned-to
with variable expansion with the `:=' operator.  The  output
of  a  shell command may be assigned to a variable using the
`!=' operator.   Variables  may  be  expanded  (their  value
inserted)  by  enclosing  their name in parentheses or curly
braces, preceded by a dollar sign.  A  dollar  sign  may  be
escaped with another dollar sign. Variables are not expanded
if Make doesn't know about them. There are seven local vari-
ables:   .TARGET,   .ALLSRC,   .OODATE,   .PREFIX,  .IMPSRC,
.ARCHIVE, and  .MEMBER.  Four  of  them  (.TARGET,  .PREFIX,
.ARCHIVE,  and  .MEMBER)  may  be  used to specify ``dynamic
sources.'' Variables are good. Know them.  Love  them.  Live
them.
Debugging of makefiles is best accomplished  using  the  -n,
and -d m flags.

2.9. Exercises
                            TBA

3. Short-cuts and Other Nice Things
Based on what I've told you so far, you may have gotten  the
impression  that Make 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

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-17

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 Unix 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 UNIX- has developed, naming conven-
tions,  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
object file that may be linked into any program; a file
with a .ms suffix is usually a text  file  to  be  pro-
cessed  by Troff with the -ms macro package, and so on.
One of the best aspects of Make comes from  its  under-
standing  of  how  the suffix of a file pertains to its
contents and Make's ability to do things  with  a  file
based  solely  on  its  suffix. This ability comes from
something known as a transformation rule. A transforma-
tion  rule specifies how to change a file with one suf-
fix 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 Make 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.
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
_________________________
- UNIX is a registered trademark of AT&T  Bell  Labora-
tories in the USA and other countries.

                     December 24, 2022

PSD:12-18                                 Make -- A Tutorial

          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 transforma-
          tion 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 Make 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[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 ``make'' will do  the  right  thing,  it's
much  more informative to type ``make -d s''. This will show
_________________________
  [1] This is also somewhat cleaner, I think, than  the
dynamic source solution presented in 2.6

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-19

you what Make is up to as it processes the  files.  In  this
case, Make 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  Make  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  automati-
cally  redefined  as  the  suffixes  they  involve  are  re-
entered.)
Another way to affect the search order is to make the depen-
dency  explicit.  In the above example, a.out depends on a.o
and b.o. Since a transformation exists from .o to .out, Make
uses that, as indicated by the ``using existing source a.o''
message.
The search for a transformation starts from  the  suffix  of

                     December 24, 2022

PSD:12-20                                 Make -- A Tutorial

the target and continues through all the defined transforma-
tions, 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  ``make  -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: Make starts with the target jive.out,  fig-
ures  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  complete-
ness,  and  to  give you a better idea of what Make actually
did with this three-step transformation, this is  what  Make
printed for the rest of the process:

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-21

        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 Make do  with  targets
that  have no known suffix? Make simply pretends it actually
has an empty suffix and searches for transformations accord-
ingly.  Those  special transformation rules involve just one
source suffix, like this:

        .o              :
                cc -o $(.TARGET) $(.IMPSRC)

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  some-
thing like

        #include "defs.h"

to include the contents of defs.h in the source  file.  Make
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  Make  searches  for
the file: the first way, Make will look for the file only in
the system makefile directory (or  directories)  The  system
makefile  directory search path can be overridden via the -m

                     December 24, 2022

PSD:12-22                                 Make -- A Tutorial

option. For files in double-quotes, the search is more  com-
plex:
     1)   The directory of the makefile that's including the
          file.
     2)   The  current  directory  (the  one  in  which  you
          invoked Make).
     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 Make  variables  in  the  filename--Make
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 fol-
lowing

        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 run-
ning 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. Make 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)

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-23

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  Make
manages to re-create everything without an error.

3.4. Target Attributes
Make allows you to give attributes to targets  by  means  of
special  sources.  Like  everything  else  Make  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
            Make  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 Make just
            can't find it. This may  prove  wrong,  but  the
            error  will  be  noted  later  on, not when Make
            tries to  create  the  target  so  marked.  This
            attribute  also prevents Make 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):

                     December 24, 2022

PSD:12-24                                 Make -- A Tutorial

                    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  vari-
            ables  of  targets  that depend on them (nor are
            they touched if Make 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  crea-
            tion 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 sys-
            tem  type),  while others (e.g. compiling a pro-
            gram with cc) must be  performed  on  a  machine
            with  the same architecture. Not all export sys-
            tems will support this attribute.
.IGNORE     Giving a target  the  .IGNORE  attribute  causes
            Make  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

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-25

            where MAKEINSTALL is some complex .USE rule (see
            below) that depends on the .ALLSRC variable con-
            taining 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 every-
            thing 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 tar-
            get 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  ear-
            lier, 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, Make 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 Make. This forces Make
            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

                    make -n

            and have it descend the directory tree (if  your

                     December 24, 2022

PSD:12-26                                 Make -- A Tutorial

            makefiles  are  set up correctly), printing what
            it would have executed if  you  hadn't  included
            the -n flag.
.NOEXPORT   If possible, Make will  attempt  to  export  the
            creation of all targets to another machine (this
            depends on how Make was configured).  Sometimes,
            the  creation  is  so simple, it is pointless to
            send it to another machine. If you give the tar-
            get  the  .NOEXPORT  attribute,  it  will be run
            locally, even if you've  given  Make  the  -L  0
            flag.
.NOTMAIN    Normally, if you do not specify a target to make
            in  any other way, Make will take the first tar-
            get 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  Make
            that  the target is definitely not the Main Tar-
            get. This allows you  to  place  targets  in  an
            included makefile and have Make create something
            else by default.
.PRECIOUS   When Make 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,
            Make will leave it  alone.  An  additional  side
            effect  of the `::' operator is to mark the tar-
            gets as .PRECIOUS.
.SILENT     Marking a target with this attribute  keeps  its
            commands  from  being  printed when they're exe-
            cuted, just as if they had an `@'  in  front  of
            them.
.USE        By giving a target this attribute, you  turn  it
            into Make's equivalent of a macro. When the tar-
            get 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  tar-
            get)  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:

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-27

                    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 target'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 Make. When you use one on a depen-
dency 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 Make 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  vari-
          able 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 every-
          thing has been re-created (so long  as  no  errors
          occurred).  It  also  serves the extra function of
          being a place on which Make can hang commands  you
          put  off to the end. Thus the script for this tar-
          get 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 Make. Some sys-
          tems will use these  sources  to  configure  them-
          selves.  You  should ask your system administrator

                     December 24, 2022

PSD:12-28                                 Make -- A Tutorial

          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 Make -- errors are ignored for all com-
          mands.
.INCLUDES The sources for this target are taken to  be  suf-
          fixes 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

          Make 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.)
.INTERRUPTWhen Make is interrupted, it will execute the com-
          mands 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 Make may not  have  been
          compiled  to  do this if the linker on your system
          doesn't accept the -L flag, though the .LIBS vari-
          able  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 Make, it will take the sources of
          this target as the targets to create.
.MAKEFLAGSThis target provides  a  way  for  you  to  always
          specify  flags for Make 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 Make 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

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-29

          that is used (you should, however, only  give  one
          suffix...).
.PATH     If you give sources for  this  target,  Make  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 direc-
          tories 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  expla-
          nation.
.PATHsuffixThis does a similar thing to .PATH, but  it  does
          it  only for files with the given suffix. The suf-
          fix 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.
.RECURSIVEThis target applies the .MAKE attribute to all its
          sources.  It does nothing if you don't give it any
          sources.
.SHELL    Make 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 Make the -s flag and no commands
          will be echoed.
.SUFFIXES This is used to give new file suffixes for Make to
          handle. Each source is a suffix Make should recog-
          nize. If you give a .SUFFIXES dependency line with
          no  sources,  Make  will forget about all the suf-
          fixes 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. Make 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}

                     December 24, 2022

PSD:12-30                                 Make -- A Tutorial

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 charac-
ters:
     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  occurrences 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 precedes these two characters,  however,
          they  lose  their special meaning. Variable expan-
          sion 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

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-31

          allowed to use any character except colon or  exc-
          lamation  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.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  (``exten-
          sion'').  So  ``$(OBJS:E)''  would  give  you ``.o
          .a.''
     R    This replaces each word by everything but the suf-
          fix  (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 sup-
ported. 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  dif-
     ferent  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,
     Make doesn't allow constructs like

                     December 24, 2022

PSD:12-32                                 Make -- A Tutorial

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

4. Make for Gods
This chapter is devoted to those  facilities  in  Make  that
allow  you to do a great deal in a makefile with very little
work, as well as do some things  you  couldn't  do  in  Make
without  a  great deal of work (and perhaps the use of other
programs). The problem with these features, is they must  be
handled with care, or you will end up with a mess.
Once more, I assume  a  greater  familiarity  with  UNIX  or
Sprite than I did in the previous two chapters.

4.1. Search Paths
Make supports the dispersal of files  into  multiple  direc-
tories by allowing you to specify places to look for sources
with .PATH targets in the makefile. The directories you give
as sources for these targets make up a ``search path.'' Only
those files used exclusively as sources are actually  sought
on  a search path, the assumption being that anything listed
as a target in the makefile can be created by  the  makefile
and thus should be in the current directory.
There are two types of search paths in Make: one is used for
all  types  of  files  (including included makefiles) and is
specified with a plain .PATH target (e.g. ``.PATH :  RCS''),
while  the  other  is specific to a certain type of file, as
indicated by the file's suffix. A specific  search  path  is
indicated by immediately following the .PATH with the suffix
of the file. For instance

        .PATH.h         : /sprite/lib/include /sprite/att/lib/include

would   tell   Make   to    look    in    the    directories
/sprite/lib/include   and  /sprite/att/lib/include  for  any
files whose suffix is .h.
The current directory is always consulted first to see if  a
file exists. Only if it cannot be found there are the direc-
tories in the specific search path, followed by those in the
general search path, consulted.
A search path is also used when expanding  wildcard  charac-
ters.  If  the  pattern has a recognizable suffix on it, the
path for that suffix will be used for the expansion.  Other-
wise the default search path is employed.
When a file is  found  in  some  directory  other  than  the
current  one,  all local variables that would have contained
the target's name (.ALLSRC, and .IMPSRC) will  instead  con-
tain  the  path  to  the file, as found by Make. Thus if you
have a file ../lib/mumble.c and a makefile

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-33

        .PATH.c         : ../lib
        mumble          : mumble.c
                $(CC) -o $(.TARGET) $(.ALLSRC)

the command executed to create mumble would be ``cc -o  mum-
ble  ../lib/mumble.c.''  (As  an  aside, the command in this
case isn't strictly necessary, since it will be found  using
transformation rules if it isn't given. This is because .out
is the null suffix by default and  a  transformation  exists
from .c to .out. Just thought I'd throw that in.)
If a file exists in two directories on the same search path,
the  file in the first directory on the path will be the one
Make uses. So if you have a large system  spread  over  many
directories, it would behoove you to follow a naming conven-
tion that avoids such conflicts.
Something you should know about the  way  search  paths  are
implemented is that each directory is read, and its contents
cached, exactly once -- when it is first encountered  --  so
any  changes  to  the directories while Make is running will
not be noted when searching for implicit sources,  nor  will
they  be  found when Make attempts to discover when the file
was last modified,  unless  the  file  was  created  in  the
current  directory.  While  people  have suggested that Make
should read the directories each time,  my  experience  sug-
gests  that the caching seldom causes problems. In addition,
not caching the directories  slows  things  down  enormously
because  of  Make's  attempts  to apply transformation rules
through non-existent files -- the number of extra filesystem
searches  is  truly  staggering,  especially  if  many files
without suffixes are used and the null suffix isn't  changed
from .out.

4.2. Archives and Libraries
UNIX and Sprite allow you to merge  files  into  an  archive
using  the ar command. Further, if the files are relocatable
object files, you can run ranlib  on  the  archive  and  get
yourself  a  library  that you can link into any program you
want. The main problem with  archives  is  they  double  the
space  you  need  to store the archived files, since there's
one copy in the archive and one  copy  out  by  itself.  The
problem  with  libraries is you usually think of them as -lm
rather than /usr/lib/libm.a and the  linker  thinks  they're
out-of-date if you so much as look at them.
Make solves the problem with archives  by  allowing  you  to
tell  it  to  examine  the files in the archives (so you can
remove the individual files  without  having  to  regenerate
them later). To handle the problem with libraries, Make adds
an additional way of deciding if a library is out-of-date:
+ If the table of contents is older than the library, or  is
  missing, the library is out-of-date.
A library is any target that looks like ``-lname''  or  that
ends  in  a  suffix  that  was marked as a library using the
.LIBS target. .a is so marked in the system makefile.

                     December 24, 2022

PSD:12-34                                 Make -- A Tutorial

Members of an archive  are  specified  as  ``archive(member[
member...])''.  Thus  ``'libdix.a(window.o)''  specifies the
file window.o in the archive  libdix.a.  You  may  also  use
wildcards  to  specify  the  members  of  the  archive. Just
remember that most the wildcard characters  will  only  find
existing files.
A file that is a member of an archive is treated  specially.
If  the  file  doesn't  exist, but it is in the archive, the
modification time recorded in the archive is  used  for  the
file  when  determining  if  the  file  is out-of-date. When
figuring out how to make an archived member target (not  the
file   itself,   but   the   file  in  the  archive  --  the
archive(member) target), special  care  is  taken  with  the
transformation rules, as follows:
+ archive(member) is made to depend on member.
+ The  transformation  from  the  member's  suffix  to   the
  archive's suffix is applied to the archive(member) target.
+ The archive(member)'s .TARGET variable is set to the  name
  of  the member if member is actually a target, or the path
  to the member file if member is only a source.
+ The .ARCHIVE variable for the  archive(member)  target  is
  set to the name of the archive.
+ The .MEMBER variable is set to the  actual  string  inside
  the  parentheses.  In most cases, this will be the same as
  the .TARGET variable.
+ The archive(member)'s place in the local variables of  the
  targets  that  depend  on  it is taken by the value of its
  .TARGET variable.
Thus, a program library could be created with the  following
makefile:

        .o.a            :
                ...
                rm -f $(.TARGET:T)
        OBJS            = obj1.o obj2.o obj3.o
        libprog.a       : libprog.a($(OBJS))
                ar cru $(.TARGET) $(.OODATE)
                ranlib $(.TARGET)

This will cause the three object files to  be  compiled  (if
the  corresponding  source  files  were  modified  after the
object file or, if that doesn't exist, the  archived  object
file),  the  out-of-date ones archived in libprog.a, a table
of contents placed in the  archive  and  the  newly-archived
object files to be removed.
All this is used in the makelib.mk system makefile to create
a single library with ease. This makefile looks like this:

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-35

        #
        # Rules for making libraries. The object files that make up the library are
        # removed once they are archived.
        #
        # To make several libraries in parallel, you should define the variable
        # "many_libraries". This will serialize the invocations of ranlib.
        #
        # To use, do something like this:
        #
        # OBJECTS = <files in the library>
        #
        # fish.a: fish.a($(OBJECTS)) MAKELIB
        #
        #

        #ifndef _MAKELIB_MK
        _MAKELIB_MK     =

        #include        <po.mk>

        .po.a .o.a      :
                ...
                rm -f $(.MEMBER)

        ARFLAGS         ?= crl

        #
        # Re-archive the out-of-date members and recreate the library's table of
        # contents using ranlib. If many_libraries is defined, put the ranlib off
        # til the end so many libraries can be made at once.
        #
        MAKELIB         : .USE .PRECIOUS
                ar $(ARFLAGS) $(.TARGET) $(.OODATE)
        #ifndef no_ranlib
        # ifdef many_libraries
                ...
        # endif many_libraries
                ranlib $(.TARGET)
        #endif no_ranlib

        #endif _MAKELIB_MK

4.3. On the Condition...
Like the C compiler before it, Make allows you to  configure
the makefile, based on the current environment, using condi-
tional statements. A conditional looks like this:

                     December 24, 2022

PSD:12-36                                 Make -- A Tutorial

        #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 com-
parisons as well. && represents logical AND; ||  is  logical
OR  and  ! is logical NOT.  The arithmetic and string opera-
tors 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  com-
         mand 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 Make 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  tar-
         gets).
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 (NOTE: an undefined variable in this con-
         text 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 unde-
         fined). This can be used to see if a variable  con-
         tains a given word, for example:

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-37

                 #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 stan-
dard 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  pro-
gram, 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 make -D DEBUG) will also  cause
the debug version to be made.

        #if defined(DEBUG) || make(debug)
        CFLAGS          += -g
        #else
        CFLAGS          += -O
        #endif

                     December 24, 2022

PSD:12-38                                 Make -- A Tutorial

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  annoy-
ance  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 ``make  draft  print''  since
you would use the same formatter for each target. As I said,
this all gets somewhat complicated.

4.4. A Shell is a Shell is a Shell
In normal operation,  the  Bourne  Shell  (better  known  as
``sh'')  is  used  to execute the commands to re-create tar-
gets. Make also allows you to specify a different shell  for
it  to  use when executing these commands. There are several
things Make must know about the shell you wish to use. These
things are specified as the sources for the .SHELL target by
keyword, as follows:
path=path
     Make needs to know where the shell actually resides, so
     it  can  execute  it.  If  you specify this and nothing
     else, Make will use the last component of the path  and
     look  in  its  table of the shells it knows and use the
     specification it finds, if any. Use this  if  you  just
     want  to  use  a  different  version of the Bourne or C
     Shell (yes, Make knows how to use the C Shell too).
name=name
     This is the name by which the shell is to be known.  It
     is  a  single word and, if no other keywords are speci-
     fied (other than path), it is the name  by  which  Make
     attempts  to  find a specification for it (as mentioned
     above). You can use this if you would just  rather  use
     the C Shell than the Bourne Shell (``.SHELL: name=csh''
     will do it).
quiet=echo-off command
     As mentioned before,  Make  actually  controls  whether
     commands  are  printed by introducing commands into the
     shell's input stream. This keyword, and the  next  two,
     control  what  those commands are. The quiet keyword is
     the command used to turn echoing off. Once it is turned
     off,  echoing is expected to remain off until the echo-
     on command is given.
echo=echo-on command
     The command Make should give to turn  echoing  back  on

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-39

     again.
filter=printed echo-off command
     Many shells will echo the echo-off command when  it  is
     given. This keyword tells Make in what format the shell
     actually prints the  echo-off  command.  Wherever  Make
     sees  this string in the shell's output, it will delete
     it and any following whitespace, up  to  and  including
     the  next  newline.  See the example at the end of this
     section for more details.
echoFlag=flag to turn echoing on
     Unless a target has been marked .SILENT, Make wants  to
     start the shell running with echoing on. To do this, it
     passes this flag to the shell as one of its  arguments.
     If  either this or the next flag begins with a `-', the
     flags will be passed to the  shell  as  separate  argu-
     ments. Otherwise, the two will be concatenated (if they
     are used at the same time, of course).
errFlag=flag to turn error checking on
     Likewise, unless  a  target  is  marked  .IGNORE,  Make
     wishes  error-checking to be on from the very start. To
     this end, it will pass this flag to  the  shell  as  an
     argument.  The  same  rules for an initial `-' apply as
     for the echoFlag.
check=command to turn error checking on
     Just as for echo-control, error-control is achieved  by
     inserting  commands into the shell's input stream. This
     is the command to make the shell check for  errors.  It
     also  serves  another purpose if the shell doesn't have
     error-control as commands, but I'll get into that in  a
     minute.  Again, once error checking has been turned on,
     it is expected to remain on  until  it  is  turned  off
     again.
ignore=command to turn error checking off
     This is the command Make uses to  turn  error  checking
     off.  It has another use if the shell doesn't do error-
     control, but I'll tell you about that...now.
hasErrCtl=yes or no
     This takes a value that is either yes or  no.  Now  you
     might  think that the existence of the check and ignore
     keywords would be enough to tell Make if the shell  can
     do  error-control,  but you'd be wrong. If hasErrCtl is
     yes, Make uses the  check  and  ignore  commands  in  a
     straight-forward  manner. If this is no, however, their
     use is rather different. In this case, the  check  com-
     mand  is  used as a template, in which the string %s is
     replaced by the command that's about to be executed, to
     produce a command for the shell that will echo the com-
     mand to be executed. The ignore command is also used as
     a template, again with %s replaced by the command to be
     executed, to produce a command that  will  execute  the
     command to be executed and ignore any error it returns.
     When these strings are used as templates, you must pro-
     vide newline(s) (``\n'') in the appropriate place(s).
The strings that follow these keywords may  be  enclosed  in

                     December 24, 2022

PSD:12-40                                 Make -- A Tutorial

single  or  double  quotes (the quotes will be stripped off)
and may contain the usual C backslash-characters (\n is new-
line,  \r  is  return, \b is backspace, \' escapes a single-
quote inside single-quotes, \" escapes a double-quote inside
double-quotes). Now for an example.
This  is  actually  the  contents  of  the  <shx.mk>  system
makefile,  and causes Make to use the Bourne Shell in such a
way that each command is printed as it is executed. That is,
if  more  than  one command is given on a line, each will be
printed separately. Similarly, each time the body of a  loop
is  executed, the commands within that loop will be printed,
etc. The specification runs like this:

        #
        # This is a shell specification to have the bourne shell echo
        # the commands just before executing them, rather than when it reads
        # them. Useful if you want to see how variables are being expanded, etc.
        #
        .SHELL  : path=/bin/mksh \
                quiet="set -" \
                echo="set -x" \
                filter="+ set - " \
                echoFlag=x \
                errFlag=e \
                hasErrCtl=yes \
                check="set -e" \
                ignore="set +e"

It tells Make the following:
+ The shell is located in the file /bin/mksh.  It  need  not
  tell  Make  that the name of the shell is mksh as Make can
  figure that out for itself (it's the last component of the
  path).
+ The command to stop echoing is set -.
+ The command to start echoing is set -x.
+ When the echo off command  is  executed,  the  shell  will
  print  +  set  -  (The  `+'  comes  from using the -x flag
  (rather than the -v flag Make usually  uses)).  Make  will
  remove  all occurrences of this string from the output, so
  you don't notice extra commands you didn't put there.
+ The flag the Bourne Shell will take to  start  echoing  in
  this  way  is the -x flag. The Bourne Shell will only take
  its flag arguments concatenated as its first argument,  so
  neither  this  nor the errFlag specification begins with a
  -.
+ The flag to use to turn error-checking on from  the  start
  is -e.
+ The shell can turn error-checking on and off, and the com-
  mands to do so are set +e and set -e, respectively.
This will cause Make to execute the two commands

        echo "+ cmd"
        sh -c 'cmd || true'

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-41

for each command for which errors are  to  be  ignored.  (In
case you are wondering, the thing for ignore tells the shell
to execute another  shell  without  error  checking  on  and
always exit 0, since the || causes the exit 0 to be executed
only if the first command exited non-zero, and if the  first
command  exited  zero,  the shell will also exit zero, since
that's the last command it executed).

4.5. Compatibility
There  are  three  (well,  3  1/2)  levels   of   backwards-
compatibility  built  into  Make.   Most makefiles will need
none at all. Some may need a little bit of work  to  operate
correctly  when  run in parallel. Each level encompasses the
previous levels (e.g. -B (one shell per command) implies -V)
The  three  levels are described in the following three sec-
tions.

4.5.1. DEFCON 3 -- Variable Expansion
As noted before, Make will not expand a variable  unless  it
knows  of  a  value  for  it.  This  can  cause problems for
makefiles that expect to leave variables undefined except in
special  circumstances (e.g. if more flags need to be passed
to the C compiler or the output from a text processor should
be  sent  to  a  different  printer).  If  the variables are
enclosed in curly braces (``${PRINTER}''),  the  shell  will
let them pass. If they are enclosed in parentheses, however,
the shell will declare a syntax error and the make will come
to a grinding halt.
You have two choices: change  the  makefile  to  define  the
variables  (their  values  can  be overridden on the command
line, since that's where they would have  been  set  if  you
used  Make,  anyway) or always give the -V flag (this can be
done with the .MAKEFLAGS target, if you want).

4.5.2. DEFCON 2 -- The Number of the Beast
Then there are the makefiles that expect  certain  commands,
such  as  changing  to  a different directory, to not affect
other commands in a target's creation script. You can  solve
this is either by going back to executing one shell per com-
mand (which is what the -B flag forces Make  to  do),  which
slows  the  process  down a good bit and requires you to use
semicolons and escaped newlines for shell constructs, or  by
changing the makefile to execute the offending command(s) in
a subshell (by placing the line  inside  parentheses),  like
so:

        install :: .MAKE
                (cd src; $(.MAKE) install)
                (cd lib; $(.MAKE) install)
                (cd man; $(.MAKE) install)

This will always execute the three makes  (even  if  the  -n
flag  was  given)  because  of the combination of the ``::''
operator and the .MAKE attribute. Each command  will  change

                     December 24, 2022

PSD:12-42                                 Make -- A Tutorial

to  the proper directory to perform the install, leaving the
main shell in the directory in which it started.

4.5.3. DEFCON 1 -- Imitation is the Not the Highest Form  of
Flattery
The final category of makefile is the one where  every  com-
mand  requires  input,  the  dependencies  are  incompletely
specified, or you simply cannot create more than one  target
at  a  time,  as mentioned earlier. In addition, you may not
have the time or desire  to  upgrade  the  makefile  to  run
smoothly  with  Make. If you are the conservative sort, this
is the compatibility mode for you. It is entered  by  giving
Make the -B flag. This includes:
+ No parallel execution.
+ Targets are made in  the  exact  order  specified  by  the
  makefile.  The  sources for each target are made in strict
  left-to-right order, etc.
+ A single Bourne shell is used  to  execute  each  command,
  thus  the  shell's $$ variable is useless, changing direc-
  tories doesn't work across command lines, etc.
+ If no special characters exist in  a  command  line,  Make
  will  break  the command into words itself and execute the
  command directly, without executing  a  shell  first.  The
  characters  that  cause Make to execute a shell are: #, =,
  |, ^, (, ), {, }, ;, &, <, >, *, ?, [, ], :, $, `, and  \.
  You  should  notice that these are all the characters that
  are given special meaning by the shell (except '  and    ,
  which Make deals with all by its lonesome).

4.6. The Way Things Work
When Make reads the makefile, it parses sources and  targets
into  nodes  in  a  graph. The graph is directed only in the
sense that Make knows which way is up.  Each  node  contains
not  only  links  to all its parents and children (the nodes
that depend on it and those on  which  it  depends,  respec-
tively), but also a count of the number of its children that
have already been processed.
The most important thing to know about how  Make  uses  this
graph  is  that the traversal is breadth-first and occurs in
two passes.
After Make has parsed the makefile, it begins with the nodes
the user has told it to make (either on the command line, or
via a .MAIN target, or by the target being the first in  the
file  not  labeled  with the .NOTMAIN attribute) placed in a
queue. It continues to take the node off the  front  of  the
queue,  mark it as something that needs to be made, pass the
node to Suff_FindDeps (mentioned earlier) to find any impli-
cit  sources for the node, and place all the node's children
that have yet to be marked at the end of the queue.  If  any
of  the  children is a .USE rule, its attributes are applied
to the  parent,  then  its  commands  are  appended  to  the
parent's list of commands and its children are linked to its
parent. The parent's unmade children counter is then  decre-
mented  (since  the  .USE node has been processed). You will

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-43

note that this allows a .USE node to have children that  are
.USE nodes and the rules will be applied in sequence. If the
node has no children, it is placed at  the  end  of  another
queue  to  be examined in the second pass. This process con-
tinues until the first queue is empty.
At this point, all the leaves of the graph are in the exami-
nation queue. Make removes the node at the head of the queue
and sees if it is out-of-date. If it is, it is passed  to  a
function  that  will execute the commands for the node asyn-
chronously. When the commands have completed, all the node's
parents  have their unmade children counter decremented and,
if the counter is then 0, they are placed on the examination
queue.  Likewise,  if  the  node  is  up-to-date. Only those
parents that were marked on the downward pass are  processed
in  this  way.  Thus Make traverses the graph back up to the
nodes the user instructed it to create. When the examination
queue is empty and no shells are running to create a target,
Make is finished.
Once all targets have been processed, Make executes the com-
mands  attached  to  the  .END  target, either explicitly or
through the use of an ellipsis in a shell script.  If  there
were no errors during the entire process but there are still
some targets unmade (Make keeps a running count of how  many
targets are left to be made), there is a cycle in the graph.
Make does a depth-first traversal of the graph to  find  all
the  targets  that  weren't  made and prints them out one by
one.

5. Answers to Exercises
(3.1)This is something of a trick question, for which I apo-
     logize.  The  trick comes from the UNIX definition of a
     suffix, which Make doesn't necessarily share. You  will
     have  noticed  that  all  the  suffixes  used  in  this
     tutorial (and in UNIX in general) begin with  a  period
     (.ms,  .c,  etc.). Now, Make's idea of a suffix is more
     like English's: it's the characters at  the  end  of  a
     word.  With this in mind, one possible solution to this
     problem goes as follows:

             .SUFFIXES       : ec.exe .exe ec.obj .obj .asm
             ec.objec.exe .obj.exe :
                     link -o $(.TARGET) $(.IMPSRC)
             .asmec.obj      :
                     asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
             .asm.obj        :
                     asm -o $(.TARGET) $(.IMPSRC)

(3.2)The trick to this one  lies  in  the  ``:=''  variable-
     assignment  operator  and the ``:S'' variable-expansion
     modifier. Basically  what  you  want  is  to  take  the
     pointer variable, so to speak, and transform it into an
     invocation of the variable  at  which  it  points.  You
     might try something like

                     December 24, 2022

PSD:12-44                                 Make -- A Tutorial

             $(PTR:S/^/\$(/:S/$/))

     which places ``$('' at the front of the  variable  name
     and  ``)''  at  the end, thus transforming ``VAR,'' for
     example, into ``$(VAR),'' which is just what  we  want.
     Unfortunately  (as you know if you've tried it), since,
     as it says in the hint, Make does no further  substitu-
     tion  on the result of a modified expansion, that's all
     you get. The solution is to make use of ``:='' to place
     that  string into yet another variable, then invoke the
     other variable directly:

             *PTR            := $(PTR:S/^/\$(/:S/$/)/)

     You can then use ``$(*PTR)'' to your heart's content.

6. Glossary of Jargon
attribute: A property given to a target that causes Make  to
     treat it differently.
command script: The lines immediately following a dependency
     line that specify commands to execute to create each of
     the targets on the dependency line. Each  line  in  the
     command script must begin with a tab.
command-line variable: A variable  defined  in  an  argument
     when  Make is first executed. Overrides all assignments
     to the same variable name in the makefile.
conditional: A construct much  like  that  used  in  C  that
     allows  a makefile to be configured on the fly based on
     the local environment, or on what is being made by that
     invocation of Make.
creation script: Commands  used  to  create  a  target.  See
     ``command script.''
dependency: The relationship between a source and a  target.
     This comes in three flavors, as indicated by the opera-
     tor between the target and  the  source.  `:'  gives  a
     straight  time-wise  dependency (if the target is older
     than the source, the target is out-of-date), while  `!'
     provides  simply  an  ordering and always considers the
     target out-of-date. `::' is  much  like  `:',  save  it
     creates  multiple  instances  of a target each of which
     depends on its own list of sources.
dynamic source: This refers to a source  that  has  a  local
     variable  invocation  in  it. It allows a single depen-
     dency line to specify a different source for each  tar-
     get on the line.
global variable: Any variable defined in a  makefile.  Takes
     precedence  over  variables defined in the environment,
     but not over command-line or local variables.
input graph: What Make constructs from a makefile.  Consists
     of  nodes  made of the targets in the makefile, and the
     links between them (the dependencies).  The  links  are
     directed  (from  source to target) and there may not be
     any cycles (loops) in the graph.

                     December 24, 2022

Make -- A Tutorial                                 PSD:12-45

local variable: A variable defined by Make visible only in a
     target's shell script. There are seven local variables,
     not all of which are defined for every target: .TARGET,
     .ALLSRC,   .OODATE,  .PREFIX,  .IMPSRC,  .ARCHIVE,  and
     .MEMBER. .TARGET, .PREFIX, .ARCHIVE, and .MEMBER may be
     used on dependency lines to create ``dynamic sources.''
makefile: A file that describes how a system  is  built.  If
     you   don't   know   what  it  is  after  reading  this
     tutorial....
modifier: A letter, following a colon, used to alter  how  a
     variable  is expanded. It has no effect on the variable
     itself.
operator: What separates a source from a target (on a depen-
     dency  line) and specifies the relationship between the
     two. There are three: `:', `::', and `!'.
search path: A list of directories in which a file should be
     sought. Make's view of the contents of directories in a
     search path does not change once the makefile has  been
     read.  A  file is sought on a search path only if it is
     exclusively a source.
shell: A program to which commands are passed  in  order  to
     create targets.
source: Anything to the right of an operator on a dependency
     line.  Targets  on  the  dependency  line  are  usually
     created from the sources.
special target: A target that  causes  Make  to  do  special
     things when it's encountered.
suffix: The tail end of a file name. Usually begins  with  a
     period, .c or .ms, e.g.
target: A word to the left of the operator on  a  dependency
     line.  More generally, any file that Make might create.
     A file may be (and often is) both a target and a source
     (what it is depends on how Make is looking at it at the
     time -- sort  of  like  the  wave/particle  duality  of
     light, you know).
transformation rule: A special construct in a makefile  that
     specifies  how to create a file of one type from a file
     of another, as indicated by their suffixes.
variable expansion: The process of substituting the value of
     a  variable  for  a  reference  to it. Expansion may be
     altered by means of modifiers.
variable: A place  in  which  to  store  text  that  may  be
     retrieved later. Also used to define the local environ-
     ment. Conditionals exist that test whether  a  variable
     is defined or not.

                     December 24, 2022

PSD:12-46                                 Make -- A Tutorial

                     Table of Contents

1.    Introduction ....................................    1
2.    The Basics of Make ..............................    2
2.1.  Dependency Lines ................................    2
2.2.  Shell Commands ..................................    4
2.3.  Variables .......................................    6
2.3.1.Local Variables .................................    8
2.3.2.Command-line Variables ..........................    8
2.3.3.Global Variables ................................    9
2.3.4.Environment Variables ...........................    9
2.4.  Comments ........................................   10
2.5.  Parallelism .....................................   10
2.6.  Writing and Debugging a Makefile ................   11
2.7.  Invoking Make ...................................   13
2.8.  Summary .........................................   16
2.9.  Exercises .......................................   16
3.    Short-cuts and Other Nice Things ................   16
3.1.  Transformation Rules ............................   17
3.2.  Including Other Makefiles .......................   21
3.3.  Saving Commands .................................   22
3.4.  Target Attributes ...............................   23
3.5.  Special Targets .................................   27
3.6.  Modifying Variable Expansion ....................   29
3.7.  More on Debugging ...............................   31
3.8.  More Exercises ..................................   31
4.    Make for Gods ...................................   32
4.1.  Search Paths ....................................   32
4.2.  Archives and Libraries ..........................   33
4.3.  On the Condition... .............................   35
4.4.  A Shell is a Shell is a Shell ...................   38
4.5.  Compatibility ...................................   41
4.5.1.DEFCON 3 -- Variable Expansion ..................   41
4.5.2.DEFCON 2 -- The Number of the Beast .............   41
4.5.3.DEFCON 1 -- Imitation is the Not the Highest
Form of Flattery ......................................   42
4.6.  The Way Things Work .............................   42
5.    Answers to Exercises ............................   43
6.    Glossary of Jargon ..............................   44

                     December 24, 2022

Generated on 2022-12-24 01:00:14 by $MirOS: src/scripts/roff2htm,v 1.113 2022/12/21 23:14:31 tg Exp $ — This product includes material provided by mirabilos.

These manual pages and other documentation are copyrighted by their respective writers; their sources are available at the project’s CVSweb, AnonCVS and other mirrors. The rest is Copyright © 2002–2022 MirBSD.

This manual page’s HTML representation is supposed to be valid XHTML/1.1; if not, please send a bug report — diffs preferred.

Kontakt / Impressum & Datenschutzerklärung