SysIII/usr/src/cmd/make/make.tm

'\"	@(#)/usr/src/cmd/make/make.tm	3.2
.po 5
.nr Pt 0
.ND "July 1, 1979"
.TL 49579-210 40320-1
An Augmented Version of Make
.AU "E. G. Bradford" EGB CB 5255 2804 1C-249
.TM 79-5255-1 5255-790701.01MF
.AS
.P
This paper describes an augmented version
of the \fBmake\fR command supplied with UNIX/TS.
With one debatable exception, this version
is completely upward compatible with the
UNIX/TS version.
In this paper, I describe and give examples only
of additional features. The reader is
assumed to have read or have available the
original \fBmake\fR paper by S. I. Feldman.\*F
.FS
Feldman, S. I., MAKE, A Program for Maintaining Computer Programs,
Computing Science Technical Report Number 57
.FE
Further developments for \fBmake\fR are also discussed.
.AE
.OK "make" "program building"
.MT 1
.H 1 "INTRODUCTION"
This paper will describe in some detail
an augmented version of the \fBmake\fR program
now running on the Columbus operating systems group
UNIX machine.
I will give some justification for the chosen
implementation and describe with examples
the additional features.
.H 1 "MOTIVATION FOR THE CURRENT IMPLEMENTATION"
The \fBmake\fR program was originally written
for personal use by S. I. Feldman.
However, it became popular on the
research UNIX machine and a more formal version
was built and installed for general use.
For the purpose of maintaining executable programs
in the Center 127 environment, it has served this purpose
well.
Further developments of \fBmake\fR have not been necessary
and thus have not been done.
.P
In Columbus \fBmake\fR was perceived as an excellent
program administrative tool and has been used
extensively in at least one project (NOCS)
for over two years. 
However, \fBmake\fR had many shortcomings:
handling of libraries was tedious;
handling of the SCCS filename format was difficult or impossible;
environment variables are completely ignored by \fBmake\fR;
and the general lack of ability to maintain files in a remote directory.
These shortcomings hindered large scale use of \fBmake\fR
as a program support tool.
.P
There were at least two avenues  for solving the
above problems. The first was a complete redesign.
This would probably mean a new syntax and of necessity
force new makefiles to be incompatible with old ones.
The advantages however would be a more general implementation
that would be \fIgrowable\fR. This point of view was not
chosen because of the compatibility problem.
The second and more tame point of view was to modify
the current implementation to handle the problems above.
This point of view had the advantage that if done carefully
it could be completely upward compatible. It was this second
avenue which was chosen.
.P
The additional features are within the original syntactic framework
of \fBmake\fR and few if any new syntactical entities have been introduced.
A notable exception is the \fIinclude\fR file capability.
Further, most of the additions result in a "Don't know how to make ..."
message from the old version of \fBmake\fR.
.H 1 "THE ADDITIONAL FEATURES"
The following paragraphs describe with examples the additional features
of the \fBmake\fR program.
In general, the examples are taken from existing
\fImakefiles\fR.  Also, the appendices are working
\fImakefiles\fR.
.H 2 "The Environment Variables"
Environment variables are read and added to the
macro definitions each time \fBmake\fR executes. Precedence
is a prime consideration in doing this properly.  Thus,
if the environment variable \fBCC\fR is set to \fBocc\fR,
does it override the command line? Does it override the
definition in the makefile?
To answer these questions I need to describe the order
in which \fBmake\fR does the macro assignments.
.P
First, a new macro, \fBMAKEFLAGS\fR, must be described.
\fBMAKEFLAGS\fR is maintained by \fBmake\fR. It
It is defined as the collection of
all input flag arguments into a string (without the minus sign).
It is exported, and
thus accessible to further invocations of \fBmake\fR.
Command line flags and assignments in the "makefile"
update \fBMAKEFLAGS\fR.
Thus, to describe how the environment
interacts with \fBmake\fR, we also need to consider
the \fBMAKEFLAGS\fR macro (environment variable).
.P
When executed \fBmake\fR assigns macro definitions in the following
order:
.AL 1 12
.LI
read the \fBMAKEFLAGS\fR environment variable. If it
is not present or null, the internal \fBmake\fR variable
\fBMAKEFLAGS\fR is set to the null string.
Otherwise, each letter in \fBMAKEFLAGS\fR is assumed to
be an input flag argument and is processed as such.
(The only exceptions are the "-f", "-p", and "-r" flags.)
.LI
read and set the input flags from the command line.
The command line adds to the previous settings from
the \fBMAKEFLAGS\fR environment variable.
.LI
read macro definitions from the command line.
These are made
\fInot resettable\fR. Thus any further assignments to these
names are ignored.
.LI
read the internal list of macro definitions.
These are found
in the file \fIfiles.c\fR of the source for \fBmake\fR.
(See Appendix A for the complete makefile which represents
the internally defined macros and rules.)
They give default definitions for the C compiler
(CC=cc), the assembler (AS=as), etc.
.LI
read the environment. The environment variables are
treated as macro definitions and marked as \fIexported\fR
(in the shell sense). Note, \fBMAKEFLAGS\fR will get read
again and set again.
However, since it is not an internally
defined variable (in \fIfiles.c\fR), this has the effect
of doing the same assignment twice. The exception
to this is when \fBMAKEFLAGS\fR is assigned on the
command line.
(The reason it was read previously, was to be able to turn
the debug flag on before anything else was done.)
.LI
read the \fImakefile\fR(s). The assignments in the \fImakefile(s)\fR
will override
the environment. This order was chosen so when
one reads a makefile and executes \fBmake\fR one
knows what to expect.
That is, one gets what one sees unless the "-e" flag is used.
The "-e" is an additional command line flag which
tells \fBmake\fR  to
have the environment override the \fImakefile\fR assignments.
Thus if \fBmake -e ...\fR is typed, the variables
in the environment override the definitions in the \fImakefile\fR.
(Note, there is no way to override the command
line assignments.)
Also note that if \fBMAKEFLAGS\fR is assigned it will override
the environment.
(This would be useful for further invocations of \fBmake\fR
from the current "makefile".)
.LE
.P
This description may be hard to follow. No doubt it is. It might
be more useful to list the precedence of assignments.
Thus, in order from least binding to most binding, we have:
.AL 1 12 1
.LI
internal definitions (from \fIfiles.c\fR)
.LI
environment
.LI
"makefile(s)"
.LI
command line
.LE
.P
The "-e" flag has the effect of changing the order to:
.AL 1 12 1
.LI
internal definitions (from \fIfiles.c\fR)
.LI
"makefile(s)"
.LI
environment
.LI
command line
.LE

This ordering is general enough to allow
a programmer to define a "makefile" or set of "makefiles"
whose parameters are dynamically definable.
.H 2 "Recursive Makefiles"
One other useful feature was added to \fBmake\fR concerning
the environment and recursive invocations.
If the sequence "$(MAKE)" appears anywhere in a shell
command line, the line will be executed even if
the "-n" flag is set. Since the "-n" flag is exported across
invocations of \fBmake\fR, (through the \fBMAKEFLAGS\fR variable)
the only thing which
will actually get executed is the \fBmake\fR command
itself. This feature is useful when a hierarchy
of \fImakefile(s)\fR describes a set of software
subsystems.  For testing purposes, \fBmake -n ...\fR
can be executed and everything that would have been done
will get printed out;
including output from lower level invocations of \fBmake\fR.
.H 2 "Format of Shell commands within \fBmake\fR"
\fBMake\fR remembers embedded newlines and tabs in shell
command sequences. Thus, if the programmer puts a
\fIfor\fR loop in the makefile with indentation, when
\fBmake\fR prints it out, it still has the indentation
and the backslashes in it. The output is still pipe-able to
the shell and is readable.
This is obviously a cosmetic change; no new
functionality is gained.
.H 2 "Archive Libraries"
\fBMake\fR has an intelligent interface to archive libraries.
Due to a lack of documentation,
most people are probably not aware of the current syntax
of addressing members of archive libraries.
The UNIX/TS version allows a user to name a member of a library
in the following manner:
.nf

	lib(object.o)

or

	lib((_localtime))

.fi
where the second method actually refers to an
entry point of an object file within the library.
(\fBMake\fR looks through the library,
locates the entry point and translates it to the correct
object file name.)
.P
To use the UNIX/TS \fBmake\fR to maintain
an archive library, the following type of \fBmakefile\fR is
required:
.nf

	lib::	lib(ctime.o)
		$(CC) -c -O ctime.c
		ar rv lib ctime.o
		rm ctime.o
	lib::	lib(fopen.o)
		$(CC) -c -O fopen.c
		ar rv lib fopen.o
		rm fopen.o
	...and so on for each object ...

.fi
This is tedious and error prone. Obviously,
the command sequences for adding a C file to a library
are the same for each invocation, the filename being the only
difference each time. (This is true in most cases.)
Similarly for assembler and YACC and LEX files.
.P
The current version gives the user access to a rule for
building libraries. The "handle" for the rule is the
".a" suffix. Thus a ".c.a" rule is the rule for
compiling a C source file and adding it to the library
and removing the ".o" cadaver.
Similarly, the ".y.a", the ".s.a" and the ".l.a" rules
rebuild YACC, assembler, and LEX files respectively.
The current archive rules defined internally are
".c.a", ".c~.a", and .s~.a". (The wiggle (~) syntax will
be described shortly.)
The user may define in his makefile any other rules
he may need.
.P
The above two-member library is then maintained with the
following shorter makefile:
.nf

	lib:	lib(ctime.o) lib(fopen.o)
		@echo lib up-to-date.

.fi
The internal rules are already defined to complete the
preceding library maintenance.
The actual ".c.a" rules is as follows:
.nf

	.c.a:
		$(CC) -c $(CFLAGS) $<
		ar rv $@ $*.o
		rm -f $*.o

.fi
Thus, the "$@" macro is the ".a" target (\fBlib\fR) and the "$<" and "$*"
macros are set to the out-of-date C file and the filename
sans suffix respectively (\fBctime.c\fR and \fBctime\fR).
The "$<" macro (in the preceding rule)
could have been changed to "$*.c".
.P
It might be useful to go into some detail about exactly
what \fBmake\fR thinks about when it sees  the construction
.nf

	lib:	lib(ctime.o)
		@echo lib up-to-date

.fi
Assume the object in the library is out of date with respect
to \fIctime.c\fR. Also, there is no \fIctime.o\fR file.
To itself, \fBmake\fR thinks
.AL 1 12
.LI
I must do \fIlib\fR.
.LI
To do \fIlib\fR, I must do each dependent of \fIlib\fR.
.LI
I must do \fIlib(ctime.o)\fR.
.LI
To do \fIlib(ctime.o)\fR I must do each dependent
of \fIlib(ctime.o)\fR. (There are none).
.LI
Use my internal rules to try to build \fIlib(ctime.o)\fR.
(There is no explicit rule.)
Note that \fIlib(ctime.o)\fR has a parenthesis, '(', in the
name so I identify the target suffix as ".a".
(This is the key. There is no explicit ".a" at the end of the
\fIlib\fR library name. The parenthesis forces the ".a" suffix.)
In this sense, the ".a" is hardwired into \fBmake\fR.
.LI
Since I am working on a ".a" suffix I must break
the name \fIlib(ctime.o)\fR up into \fIlib\fR and \fIctime.o\fR.
I now define the two macros "$@" (=\fIlib\fR) and "$*" (=\fIctime\fR).
.LI
Look for a rule ".X.a" and a file "$*.X".
The first "X" (in the .SUFFIXES list) which fulfills these conditions is "c" so
the rule is ".c.a" and the file is \fIctime.c\fR.
I set "$<" to be \fIctime.c\fR and execute the rule.
(In fact, \fBmake\fR must then do "ctime.c". However,
the search of the current directory yields no other
candidates, whence, the search ends.)
.LI
The library has been updated. I must now
do the rule associated with the "lib:" dependency; namely
.nf

	echo lib up-to-date

.fi
.LE
It should be noted that to let \fIctime.o\fR have dependencies
the following syntax is required:
.nf

	lib(ctime.o):	$(INCDIR)/stdio.h

.fi
Thus, explicit references to ".o" files are unnecessary. There is also
a new macro for referencing the archive member name when this form
is used. "$%" is evaluated each time "$@" is evaluated. If there
is no current archive member, "$%" is null. If an archive member
exists, then "$%" evaluates to the expression between the parenthesis.
.P
An example makefile for a larger library is given in Appendix B.
The reader will note also, that there are no \fIlingering\fR
"*.o" files left around.
The result is a library maintained directly from the
source files (or more generally from the SCCS files!).
.H 2 "SCCS File Names -- The Wiggle"
The syntax of \fBmake\fR does not directly
permit referencing of prefixes. For most types
of files on UNIX machines this is acceptable since
nearly everyone uses a suffix to distinguish different
types of files. SCCS files are the exception.
Here, "s." precedes the filename part of the complete pathname.
.P
To allow \fBmake\fR easy access
to the prefix "s." requires either
a redefinition of the rule naming syntax of \fBmake\fR or
a \fItrick\fR.
I used a trick.
The trick is to use the wiggle (~) as an identifier
of SCCS files. Hence, ".c~.o" refers to the rule
which transforms an SCCS C source file into an object.
Specifically, the internal rule is:
.nf

	.c~.o:
		$(GET) $(GFLAGS) -p $< > $*.c
		$(CC) $(CFLAGS) -c $*.c
		-rm -f $*.c

.fi
(The motivation for the "-p" flag associated
with the $(GET) command above is obscure and debatable.
For the purpose of this discussion
we can assume the following apparently equivalent rule:
.nf

	.c~.o:
		$(GET) $(GFLAGS) $<
		$(CC) $(CFLAGS) -c $*.c
		-rm -f $*.c

.fi
Suffice it to say, that when doing a generic
build, the "make" should not fail because a file happens
to be left out in the SCCS directory.)
.P
Thus the wiggle appended to any suffix transforms the
file search into an SCCS filename search with the actual suffix
named by the dot and all characters up to (but not including)
the wiggle.
.P
The following SCCS suffixes are internally defined:
.nf

		.c~
		.y~
		.s~
		.sh~
		.h~

.fi

The following rules involving SCCS transformations
are internally defined:
.nf

		.c~:
		.sh~:
		.c~.o:
		.s~.o:
		.y~.o:
		.l~.o:
		.y~.c:
		.c~.a:
		.s~.a:
		.h~.h:

.fi
.P
Obviously, the user can define other rules and suffixes which
may prove useful. The \fIwiggle\fR gives him a handle on
the SCCS filename format so that this is possible.
.H 2 "The Null Suffix"
In the UNIX/TS source code, there are many
commands which consist of a single source file.
\fIcat.c\fR, \fIdd.c\fR, \fIecho.c\fR, and \fIdate.c\fR are a few
well known ones. It seemed a pity to maintain an object
of such files for \fBmake\fR's pleasure.
The current implementation supports single suffix rules,
or if one prefers, a null suffix. Thus, to maintain the
above files one needs a \fImakefile\fR of the following form:
.nf

	.c:
		$(CC) -n -O $< -o $@

.fi
(In fact, this ".c:" rule is internally
defined so no \fImakefile\fR is necessary at all!)
One then need only type
.nf

	make cat dd echo date

.fi
and all four C source files are passed through
the above shell command line associated with the ".c:" rule.
The internally defined single suffix
rules are:
.nf

		.c:
		.c~:
		.sh:
		.sh~:

.fi

Others may be added in the \fImakefile\fR by the user.
.H 2 "Include Files"
\fBMake\fR has an include file capability. If the string "include"
appears as the first seven letters of a line in a \fImakefile\fR
and is followed
by a blank or a tab
the following string
is assumed to be a file name which
the current invocation of \fBmake\fR
will read.
The file descriptors are stacked for reading
\fIinclude\fR files so no more that about sixteen levels of
nested includes is supported.
Does that bother anyone?
.H 2 "Invisible SCCS \fIMakefiles\fR"
SCCS \fImakefiles\fR are invisible to \fBmake\fR.
That is, if \fBmake\fR is typed and only a file named
\fIs.makefile\fR exists, \fBmake\fR will \fIget(I)\fR it,
read it and remove it.
Likewise for "-f"
arguments and \fIinclude\fR files.
.H 2 "Dynamic Dependency Parameters"
A new dependency parameter has been defined.
It has meaning only on the dependency line in a makefile.
It is "$$@".
"$$@" refers to the current "thing" at the left of
the colon (which is "$@"). Also the form "$$(@F)" exists
which allows access to the file part of "$@".
Thus, in the following:
.nf

	cat:	$$@.c

.fi
the dependency is translated at execution time to
the string "cat.c". This is useful for building a whole
raft of executable files, each of which has only one source file.
For instance the UNIX/TS command directory would have a \fImakefile\fR
like:
.nf

	CMDS = cat dd echo date cc cmp comm ar ld chown

	$(CMDS):	$$@.c
		$(CC) -O $? -o $@

.fi
Obviously, this is a subset of all the single file programs.
For multiple file programs, usually a directory is
allocated and a separate \fImakefile\fR is made.
For any particular file which has a peculiar
compilation procedure, a specific entry must be made in
the \fImakefile\fR.
.P
The second useful form of the dependency parameter is "$$(@F)".
It represents the filename part of "$$@". Again,
it is evaluated at execution time. Its usefulness
shows up when trying to maintain the "/usr/include"
directory from a makefile in the "/usr/src/head" directory.
Thus the "/usr/src/head/makefile" would look like:
.nf

	INCDIR = /usr/include

	INCLUDES = \\
		$(INCDIR)/stdio.h \\
		$(INCDIR)/pwd.h \\
		$(INCIDR)/dir.h \\
		$(INCDIR)/a.out.h

	$(INCLUDES):	$$(@F)
		cp $? $@
		chmod 0444 $@

.fi
would completely maintain the "/usr/include" directory
whenever one of the above files in "/usr/src/head" was updated.
.H 2 "Extensions of $*, $@, and $<"
The internally generated macros "$*", "$@", and "$<"
are useful generic terms for current targets
and out-of-date relatives.
To this list has been added the following related
macros: "$(@D)", "$(@F)", "$(*D)", "$(*F)", "$(<D)",
and "$(<F)".
The "D" refers to the directory part of the
single letter macro.
The "F" refers to the filename part of the
single letter macro.
These additions are
useful when building hierarchical makefiles.
They allow access to directory names for purposes
of using the `cd' command of the shell.
Thus, a shell command can be:
.nf

	cd $(<D); $(MAKE) $(<F)

.fi
An interesting example of the use of these features can
be found in the set of \fImakefiles\fR which maintain
the Columbus UNIX operating system.
They may be seen in Appendix C.
.H 2 "Output Translations"
Macros in shell commands can now be translated when evaluated.
The form is as follows:
.nf
	$(macro:string1=string2)
.fi
The meaning is as follows: $(macro) is evaluated.
For each appearance of string1 in the evaluated macro
string2 is substituted. The meaning of finding string1 in $(macro)
is that the evaluated $(macro) is considered  as a bunch of strings
each delimited by whitespace (blanks or tabs).
Thus the occurrence of string1 in $(macro) means that a regular expression
of the following form has been found:
.nf
	.*<string1>[TAB|BLANK]
.fi
This particular form was chosen because \fBmake\fR usually concerns itself
with suffixes. A more general regular expression match could be implemented
if the need arises.
The usefulness of this type of translation occurs when maintaining
archive libraries. Now, all that is necessary is to accumulate the
out-of-date members and write a shell script which can handle all the
C programs (i.e. those file ending in ".c"). Thus the following
fragment will optimize the executions of make for maintaining and
archive library:
.nf
	$(LIB):	$(LIB)(a.o) $(LIB)(b.o) $(LIB)c.o)
		$(CC) -c $(CFLAGS) $(?:.o=.c)
		ar rv $(LIB) $?
		rm $?
.fi
Here, finally, is a legitimate use for the double colon. A dependency
of the preceding form would be necessary for each of the different
types of source files (suffices) which define the archive library.
These translations are added in an effort to make more general
use of the wealth of information which \fBmake\fR generates.
.H 2 "The Test \fBmakefile\fR"
A test \fImakefile\fR was written to explicitly
test each of the new features. When shipped to a
new machine and compiled, \fBmake\fR is assumed to
work if the test \fImakefile\fR executes without error.
.H 1 "INCOMPATIBILITIES WITH OLD VERSION"
The only known incompatibility with UNIX/TS \fBmake\fR
is seen in the following example makefile:
.nf

	all:	cat dd

	dd:	dd.o
		$(CC) -o $@ $?

	cat:	cat.o
		$(CC) -o $@ $?

.fi
UNIX/TS \fBmake\fR
will not complain that \fIall\fR does not have a rule associated
with it. The current version will.  The current version is
a strict interpretation of the original paper by
S. I. Feldman
and as such is described by his paper. The UNIX/TS \fBmake\fR
is wrong (according to Feldman's paper) but people have
learned (through trial and error) to use it in this fashion
and would resist the change.
The differences amount to one line of code in the
file \fIdoname.c\fR which is noted in my source code.
Furthermore, the "-b" option tells \fBmake\fR to revert
to the old method, whereby old makefiles can be run with
this new version of \fBmake\fR.
Any other differences are unintentional.
.H 1 "FUTURE DEVELOPMENTS"
Further developments of the \fBmake\fR program that have
been considered include redefinition of the colon, some
other type of comparison that "newer versus older",
searching out include files, and looking for source files
in "other" directories.
I will try to address each of these features.
.P
Redefinition of the colon would effectively give
the user the ability to provide \fBmake\fR with
information from a file, other than the time.
Within the current syntax of \fBmake\fR this does not
seem too difficult:
.nf

	make :=program ...

.fi
However, I can not see (nor has anyone pointed out to me)
a use for such a feature.
This would apparently be related to the second item above;
namely, a different comparison
other than the difference between
two long integers, (the comparison by time).
The basic problem, as I see it, is that
when it comes time to do the ":" thing,
\fBmake\fR does not know what information to
pass to the program. \fBMake\fR looks at
each dependency individually and not in subsets.
This prevents passing information like:
.nf

	program cat cat.c

.fi
to a user defined \fIcolon\fR routine because \fBmake\fR
does not know both \fIcat\fR and \fIcat.c\fR at the same time.
(There is an interesting "hidden" variable in
this version of \fBmake\fR: "$!". It represents the
current predecessor tree. In the following \fImakefile\fR:
.nf

	all:	cat
		@echo cat up-to-date

	cat:	cat.c
		echo $!

.fi
when the "echo $!" is executed, "$!" evaluates to
.nf

	cat.c cat all

.fi
which is not \fIall\fR that useful! Further, it occasionally
prints a message
.nf

	$! nulled, predecessor circle

.fi
This message means that the predecessors of a file
are circular. The actual evaluation
of the "$!" macro was aborted, and its value set to null. Otherwise
there is no effect.)
.P
The searching out of include files has been mentioned
many times as an improvement. This would require \fBmake\fR
to look through every line of every source file
mentioned in the makefile every time it is executed.
This would slow \fBmake\fR down to a slow crawl and
slowly defeat its usefulness.
.P
Having \fBmake\fR look in other directories for
file entries sounds useful.
However, interesting problems arise when this is considered.
What if the file in a remote directory is
out of date with respect to a file in the current directory?
Does \fBmake\fR rebuild the remote file?
(The user may not have write permission in that directory.)
Also, how are the shell commands parameterized
to be able to "see" the remote files.
Further, how does \fBmake\fR (or the programmer) guarantee
the resulting target file is in the remote
directory? (CC leaves the object in the current directory, which
would mean that part of a command line might have to refer to
a remote file while the rest of it would refer to
a relative in the current directory.)
I do not deny the usefulness of using remote directories,
but cannot see a consistent solution to locating results.
(I would be glad to here from anyone who
can clear up the mess.)
.H 1 CONCLUSION
The development described herein, produced a version of \fBmake\fR
which now serves the Columbus operating system group for
maintenance of all of the UNIX source files.
The \fImakefiles\fR supplied from the UNIX/TS
support group are edited slightly (for the annoying incompatibility
described above) and used intact. When a large improvement
can be made, they are rewritten. When no makefile exists,
(C library and
most of the "cmd" directory)
a \fImakefile\fR is written.
There are no "*.rc" files left in the source.
This gives a single interface to rebuilding parts
or all of the UNIX software.
.P
I feel that although the size of \fBmake\fR has grown from
.nf

	16320+3772+6352 = 26444b = 063514b

.fi
on the Center 127 machine to
.nf

	21632+4850+8748 = 35230b = 0104636b

.fi
on the Columbus machine, the trade of size for
functionality is worthwhile.
The unfortunate by product of such a development
is that there are two versions of the
\fBmake\fR program.
I leave it to the reader to decide on the merits or lack of same
on this issue.
.SG egb
.NS 4
Appendix A-Internal Definitions
Appendix B-Example Library Makefile
Appendix C-Example Recursive Use of Makefiles
.NE
.bp
.DS 2
APPENDIX A
.DE
.P
The following \fImakefile\fR
will exactly reproduce the internal
rules of the current version of
\fBmake\fR. Thus if \fBmake -r ...\fR
is typed and a \fImakefile\fR includes
this \fImakefile\fR the results would be identical
to excluding the "-r" option and the \fIinclude\fR
line in the makefile.
.nf

	#	LIST OF SUFFIXES
	.SUFFIXES: .o .c .c~ .y .y~ .l .l~ .s .s~ .sh .sh~ .h .h~
	
	#	PRESET VARIABLES
	MAKE=make
	YACC=yacc
	YFLAGS=
	LEX=lex
	LFLAGS=
	LD=ld
	LDFLAGS=
	CC=cc
	CFLAGS=-O
	AS=as
	ASFLAGS=
	GET=get
	GFLAGS=
	
	#	SINGLE SUFFIX RULES
	.c:
		$(CC) -n -O $< -o $@
	.c~:
		$(GET) $(GFLAGS) -p $< > $*.c
		$(CC) -n -O $*.c -o $*
		-rm -f $*.c
	.sh:
		cp $< $@
	.sh~:
		$(GET) $(GFLAGS) -p $< > $*.sh
		cp $*.sh $*
		-rm -f $*.sh
	
	#	DOUBLE SUFFIX RULES
	.c.o:
		$(CC) $(CFLAGS) -c $<
	.c~.o:
		$(GET) $(GFLAGS) -p $< > $*.c
		$(CC) $(CFLAGS) -c $*.c
		-rm -f $*.c
	.c~.c:
		$(GET) $(GFLAGS) -p $< > $*.c
	.s.o:
		$(AS) $(ASFLAGS) -o $@ $<
	.s~.o:
		$(GET) $(GFLAGS) -p $< > $*.s
		$(AS) $(ASFLAGS) -o $*.o $*.s
		-rm -f $*.s
	.y.o:
		$(YACC) $(YFLAGS) $<
		$(CC) $(CFLAGS) -c y.tab.c
		rm y.tab.c
		mv y.tab.o $@
	.y~.o:
		$(GET) $(GFLAGS) -p $< > $*.y
		$(YACC) $(YFLAGS) $*.y
		$(CC) $(CFLAGS) -c y.tab.c
		rm -f y.tab.c $*.y
		mv y.tab.o $*.o
	.l.o:
		$(LEX) $(LFLAGS) $<
		$(CC) $(CFLAGS) -c lex.yy.c
		rm lex.yy.c
		mv lex.yy.o $@
	.l~.o:
		$(GET) $(GFLAGS) -p $< > $*.l
		$(LEX) $(LFLAGS) $*.l
		$(CC) $(CFLAGS) -c lex.yy.c
		rm -f lex.yy.c $*.l
		mv lex.yy.o $*.o
	.y.c :
		$(YACC) $(YFLAGS) $<
		mv y.tab.c $@
	.y~.c :
		$(GET) $(GFLAGS) -p $< > $*.y
		$(YACC) $(YFLAGS) $*.y
		mv y.tab.c $*.c
		-rm -f $*.y
	.l.c :
		$(LEX) $<
		mv lex.yy.c $@
	.c.a:
		$(CC) -c $(CFLAGS) $<
		ar rv $@ $*.o
		rm -f $*.o
	.c~.a:
		$(GET) $(GFLAGS) -p $< > $*.c
		$(CC) -c $(CFLAGS) $*.c
		ar rv $@ $*.o
		rm -f $*.[co]
	.s~.a:
		$(GET) $(GFLAGS) -p $< > $*.s
		$(AS) $(ASFLAGS) -o $*.o $*.s
		ar rv $@ $*.o
		-rm -f $*.[so]
	.h~.h:
		$(GET) $(GFLAGS) -p $< > $*.h

.fi
.bp
.DS 2
APPENDIX B
.DE
.P
The following library maintaining makefile
is from my current work on LSX. It
completely maintains the LSX operating system
library.
.nf

	#	@(#)/usr/src/cmd/make/make.tm	3.2
	LIB = lsxlib
	
	#	I have a banner printing pr.
	PR = vpr -b LSX
	
	INSDIR = /r1/flop0/
	INS = eval
	
	lsx::	$(LIB) low.o mch.o
		ld -x low.o mch.o $(LIB)
		mv a.out lsx
		@size lsx
	
	#	Here, I have used $(INS) as either `:' or `eval'.
	lsx::
		$(INS) 'cp lsx $(INSDIR)lsx && \\
			strip $(INSDIR)lsx  && \\
			ls -l $(INSDIR)lsx'
	
	print:
		$(PR) header.s low.s mch.s *.h *.c Makefile
	
	$(LIB): \\
		$(LIB)(clock.o) \\
		$(LIB)(main.o) \\
		$(LIB)(tty.o) \\
		$(LIB)(trap.o) \\
		$(LIB)(sysent.o) \\
		$(LIB)(sys2.o) \\
		$(LIB)(sys3.o) \\
		$(LIB)(sys4.o) \\
		$(LIB)(sys1.o) \\
		$(LIB)(sig.o) \\
		$(LIB)(fio.o) \\
		$(LIB)(kl.o) \\
		$(LIB)(alloc.o) \\
		$(LIB)(nami.o) \\
		$(LIB)(iget.o) \\
		$(LIB)(rdwri.o) \\
		$(LIB)(subr.o) \\
		$(LIB)(bio.o) \\
		$(LIB)(decfd.o) \\
		$(LIB)(slp.o) \\
		$(LIB)(space.o) \\
		$(LIB)(puts.o)
		@echo $(LIB) now up-to-date.
	
	
	.s.o:
		as -o $*.o header.s $*.s
	.o.a:
		ar rv $@ $<
		rm -f $<
	
	.s.a:
		as -o $*.o header.s $*.s
		ar rv $@ $*.o
		rm -f $*.o
	
	.PRECIOUS:	$(LIB)

.fi
.bp
.DS 2
APPENDIX C
.DE
.P
The following set of \fImakefiles\fR
maintain the UNIX operating system
for Columbus UNIX. They reside in the following
relative directories on the Columbus operating
systems group machine: "ucb", "ucb/os", "ucb/io", "ucb/head/sys".
Each one is named "70.mk".
The following command forces a complete rebuild of the operating
system:
.nf

	FRC=FRC make -f 70.mk

.fi
where the current directory is "ucb".
Here, I have used some of the conventions described
in A. Chellis' paper, \fIProposed Structure for UNIX/TS
and UNIX/RT Makefiles\fR (MF78-8234-73).
FRC is a convention for \fIF\fRo\fIRC\fRing
\fBmake\fR to completely rebuild a target starting
from scratch.
.bp
.DS 2
./ucb makefile
.DE
.nf

	#	@(#)/usr/src/cmd/make/make.tm	3.2
	#	ucb/70.mk makefile
	
	VERSION = 70
	
	DEPS = \\
		os/low.$(VERSION).o \\
		os/mch.$(VERSION).o \\
		os/conf.$(VERSION).o \\
		os/lib1.$(VERSION).a \\
		io/lib2.$(VERSION).a
	
	#	This makefile will re-load unix.$(VERSION) if any
	#	of the $(DEPS) is out-of-date wrt unix.$(VERSION).
	#	Note, it will not go out and check each member
	#	of the libraries. To do this, the FRC macro must
	#	be defined.

	unix.$(VERSION):	$(DEPS) $(FRC)
		load -s $(VERSION)
	
	$(DEPS):	$(FRC)
		cd $(@D); $(MAKE) -f $(VERSION).mk $(@F)
	
	all:	unix.$(VERSION)
		@echo unix.$(VERSION) up-to-date.
	
	includes:
		cd head/sys; $(MAKE) -f $(VERSION).mk
	
	FRC:	includes;

.fi
.bp
.DS 2
./ucb/os makefile
.DE
.nf

	
	#	@(#)/usr/src/cmd/make/make.tm	3.2
	#	ucb/os/70.mk makefile
	
	VERSION = 70
	
	LIB = lib1.$(VERSION).a
	COMPOOL=
	
	LIBOBJS = \\
		$(LIB)(main.o) \\
		$(LIB)(alloc.o) \\
		$(LIB)(iget.o) \\
		$(LIB)(prf.o) \\
		$(LIB)(rdwri.o) \\
		$(LIB)(slp.o) \\
		$(LIB)(subr.o) \\
		$(LIB)(text.o) \\
		$(LIB)(trap.o) \\
		$(LIB)(sig.o) \\
		$(LIB)(sysent.o) \\
		$(LIB)(sys1.o) \\
		$(LIB)(sys2.o) \\
		$(LIB)(sys3.o) \\
		$(LIB)(sys4.o) \\
		$(LIB)(sys5.o) \\
		$(LIB)(syscb.o) \\
		$(LIB)(maus.o) \\
		$(LIB)(messag.o) \\
		$(LIB)(nami.o) \\
		$(LIB)(fio.o) \\
		$(LIB)(clock.o) \\
		$(LIB)(acct.o) \\
		$(LIB)(errlog.o)
	
	
	ALL = \\
		conf.$(VERSION).o \\
		low.$(VERSION).o \\
		mch.$(VERSION).o \\
		$(LIB)
	
	all:	$(ALL)
		@echo \`$(ALL)\' now up-to-date.
	
	$(LIB)::	$(LIBOBJS)
	
	$(LIBOBJS):	$(FRC)
	
	FRC:
		rm -f $(LIB)
	
	clobber:	cleanup
		-rm -f $(LIB)
	
	clean cleanup:
	
	install:	all
	
	.PRECIOUS:	$(LIB)

.fi
.bp
.DS 2
./ucb/io makefile
.DE
.nf

	
	#	@(#)/usr/src/cmd/make/make.tm	3.2
	#	ucb/io/70.mk makefile
	
	VERSION = 70
	
	LIB = lib2.$(VERSION).a
	COMPOOL=
	
	LIB2OBJS = \\
		$(LIB)(mx1.o) \\
		$(LIB)(mx2.o) \\
		$(LIB)(bio.o) \\
		$(LIB)(tty.o) \\
		$(LIB)(malloc.o) \\
		$(LIB)(pipe.o) \\
		$(LIB)(dhdm.o) \\
		$(LIB)(dh.o) \\
		$(LIB)(dhfdm.o) \\
		$(LIB)(dj.o) \\
		$(LIB)(dn.o) \\
		$(LIB)(ds40.o) \\
		$(LIB)(dz.o) \\
		$(LIB)(alarm.o) \\
		$(LIB)(hf.o) \\
		$(LIB)(hps.o) \\
		$(LIB)(hpmap.o) \\
		$(LIB)(hp45.o) \\
		$(LIB)(hs.o) \\
		$(LIB)(ht.o) \\
		$(LIB)(jy.o) \\
		$(LIB)(kl.o) \\
		$(LIB)(lfh.o) \\
		$(LIB)(lp.o) \\
		$(LIB)(mem.o) \\
		$(LIB)(nmpipe.o) \\
		$(LIB)(rf.o) \\
		$(LIB)(rk.o) \\
		$(LIB)(rp.o) \\
		$(LIB)(rx.o) \\
		$(LIB)(sys.o) \\
		$(LIB)(trans.o) \\
		$(LIB)(ttdma.o) \\
		$(LIB)(tec.o) \\
		$(LIB)(tex.o) \\
		$(LIB)(tm.o) \\
		$(LIB)(vp.o) \\
		$(LIB)(vs.o) \\
		$(LIB)(vtlp.o) \\
		$(LIB)(vt11.o) \\
		$(LIB)(fakevtlp.o) \\
		$(LIB)(vt61.o) \\
		$(LIB)(vt100.o) \\
		$(LIB)(vtmon.o) \\
		$(LIB)(vtdbg.o) \\
		$(LIB)(vtutil.o) \\
		$(LIB)(vtast.o) \\
		$(LIB)(partab.o) \\
		$(LIB)(rh.o) \\
		$(LIB)(devstart.o) \\
		$(LIB)(dmc11.o) \\
		$(LIB)(rop.o) \\
		$(LIB)(ioctl.o) \\
		$(LIB)(fakemx.o)
	
	all:	$(LIB)
		@echo $(LIB) is now up-to-date.
	
	$(LIB)::	$(LIB2OBJS)
	
	$(LIB2OBJS):	$(FRC)
	
	FRC:
		rm -f $(LIB)
	
	clobber: cleanup
		-rm -f $(LIB) *.o
	
	clean cleanup:
	
	install:	all
	
	.PRECIOUS:	$(LIB)
	
	.s.a:
		$(AS) $(ASFLAGS) -o $*.o $<
		ar rcv $@ $*.o
		rm $*.o

.fi
.bp
.DS 2
./ucb/head/sys makefile
.DE
.nf

	
	#	@(#)/usr/src/cmd/make/make.tm	3.2
	#	ucb/head/sys/70.mk makefile
	
	COMPOOL = /usr/include/sys
	
	HEADERS = \\
		$(COMPOOL)/buf.h \\
		$(COMPOOL)/bufx.h \\
		$(COMPOOL)/conf.h \\
		$(COMPOOL)/confx.h \\
		$(COMPOOL)/crtctl.h \\
		$(COMPOOL)/dir.h \\
		$(COMPOOL)/dm11.h \\
		$(COMPOOL)/elog.h \\
		$(COMPOOL)/file.h \\
		$(COMPOOL)/filex.h \\
		$(COMPOOL)/filsys.h \\
		$(COMPOOL)/ino.h \\
		$(COMPOOL)/inode.h \\
		$(COMPOOL)/inodex.h \\
		$(COMPOOL)/ioctl.h \\
		$(COMPOOL)/ipcomm.h \\
		$(COMPOOL)/ipcommx.h \\
		$(COMPOOL)/lfsh.h \\
		$(COMPOOL)/lock.h \\
		$(COMPOOL)/maus.h \\
		$(COMPOOL)/mx.h \\
		$(COMPOOL)/param.h \\
		$(COMPOOL)/proc.h \\
		$(COMPOOL)/procx.h \\
		$(COMPOOL)/reg.h \\
		$(COMPOOL)/seg.h \\
		$(COMPOOL)/sgtty.h \\
		$(COMPOOL)/sigdef.h \\
		$(COMPOOL)/sprof.h \\
		$(COMPOOL)/sprofx.h \\
		$(COMPOOL)/stat.h \\
		$(COMPOOL)/syserr.h \\
		$(COMPOOL)/sysmes.h \\
		$(COMPOOL)/sysmesx.h \\
		$(COMPOOL)/systm.h \\
		$(COMPOOL)/text.h \\
		$(COMPOOL)/textx.h \\
		$(COMPOOL)/timeb.h \\
		$(COMPOOL)/trans.h \\
		$(COMPOOL)/tty.h \\
		$(COMPOOL)/ttyx.h \\
		$(COMPOOL)/types.h \\
		$(COMPOOL)/user.h \\
		$(COMPOOL)/userx.h \\
		$(COMPOOL)/version.h \\
		$(COMPOOL)/votrax.h \\
		$(COMPOOL)/vt11.h \\
		$(COMPOOL)/vtmn.h
	
	all:	$(FRC) $(HEADERS)
		@echo Headers are now up to date.
	
	$(HEADERS):	s.$$/
		$(GET) -s -p $(GFLAGS) $? > xtemp
		move xtemp 444 src sys $@
	
	FRC:
		rm -f $(HEADERS)
	
	.PRECIOUS:	$(HEADERS)
	
	
	.h~.h:
		get -s $<
	
	.DEFAULT:
		cpmv $? 444 src sys $@

.fi
.if t .CS 22
.if n .CS 24