aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>1996-10-06 15:57:15 +0000
committerPeter Wemm <peter@FreeBSD.org>1996-10-06 15:57:15 +0000
commit6c7089bce55654cffa6258d1dcb973d642d95728 (patch)
tree5f3e20ab7ee6b1efaee5e053b7bbeb8a9960077d
parentfd28c66eb83168e5b0bab3ae45eeb379536f9f0a (diff)
downloadsrc-6c7089bce55654cffa6258d1dcb973d642d95728.tar.gz
src-6c7089bce55654cffa6258d1dcb973d642d95728.zip
Import the 4.4BSD-Lite2 version of make onto the vendor branch
"for reference". This doesn't change anything since all files have been touched.
Notes
Notes: svn path=/cvs2svn/branches/CHRISTOS/; revision=18756
-rw-r--r--usr.bin/make/Makefile19
-rw-r--r--usr.bin/make/PSD.doc/tutorial.ms3732
-rw-r--r--usr.bin/make/arch.c1091
-rw-r--r--usr.bin/make/bit.h100
-rw-r--r--usr.bin/make/buf.c436
-rw-r--r--usr.bin/make/buf.h80
-rw-r--r--usr.bin/make/compat.c650
-rw-r--r--usr.bin/make/cond.c1252
-rw-r--r--usr.bin/make/config.h92
-rw-r--r--usr.bin/make/dir.c1274
-rw-r--r--usr.bin/make/dir.h70
-rw-r--r--usr.bin/make/for.c302
-rw-r--r--usr.bin/make/hash.c418
-rw-r--r--usr.bin/make/hash.h116
-rw-r--r--usr.bin/make/job.c2668
-rw-r--r--usr.bin/make/job.h233
-rw-r--r--usr.bin/make/list.h298
-rw-r--r--usr.bin/make/lst.h163
-rw-r--r--usr.bin/make/lst.lib/lstAppend.c113
-rw-r--r--usr.bin/make/lst.lib/lstAtEnd.c70
-rw-r--r--usr.bin/make/lst.lib/lstAtFront.c71
-rw-r--r--usr.bin/make/lst.lib/lstClose.c77
-rw-r--r--usr.bin/make/lst.lib/lstConcat.c176
-rw-r--r--usr.bin/make/lst.lib/lstDatum.c71
-rw-r--r--usr.bin/make/lst.lib/lstDeQueue.c81
-rw-r--r--usr.bin/make/lst.lib/lstDestroy.c102
-rw-r--r--usr.bin/make/lst.lib/lstDupl.c99
-rw-r--r--usr.bin/make/lst.lib/lstEnQueue.c73
-rw-r--r--usr.bin/make/lst.lib/lstFind.c70
-rw-r--r--usr.bin/make/lst.lib/lstFindFrom.c94
-rw-r--r--usr.bin/make/lst.lib/lstFirst.c71
-rw-r--r--usr.bin/make/lst.lib/lstForEach.c72
-rw-r--r--usr.bin/make/lst.lib/lstForEachFrom.c111
-rw-r--r--usr.bin/make/lst.lib/lstInit.c76
-rw-r--r--usr.bin/make/lst.lib/lstInsert.c113
-rw-r--r--usr.bin/make/lst.lib/lstInt.h110
-rw-r--r--usr.bin/make/lst.lib/lstIsAtEnd.c81
-rw-r--r--usr.bin/make/lst.lib/lstIsEmpty.c69
-rw-r--r--usr.bin/make/lst.lib/lstLast.c71
-rw-r--r--usr.bin/make/lst.lib/lstMember.c69
-rw-r--r--usr.bin/make/lst.lib/lstNext.c114
-rw-r--r--usr.bin/make/lst.lib/lstOpen.c81
-rw-r--r--usr.bin/make/lst.lib/lstRemove.c131
-rw-r--r--usr.bin/make/lst.lib/lstReplace.c73
-rw-r--r--usr.bin/make/lst.lib/lstSucc.c73
-rw-r--r--usr.bin/make/main.c954
-rw-r--r--usr.bin/make/make.1890
-rw-r--r--usr.bin/make/make.c908
-rw-r--r--usr.bin/make/make.h366
-rw-r--r--usr.bin/make/nonints.h140
-rw-r--r--usr.bin/make/parse.c2661
-rw-r--r--usr.bin/make/pathnames.h39
-rw-r--r--usr.bin/make/sprite.h111
-rw-r--r--usr.bin/make/str.c491
-rw-r--r--usr.bin/make/suff.c2435
-rw-r--r--usr.bin/make/targ.c657
-rw-r--r--usr.bin/make/var.c2033
57 files changed, 27091 insertions, 0 deletions
diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile
new file mode 100644
index 000000000000..b6ae9fc12691
--- /dev/null
+++ b/usr.bin/make/Makefile
@@ -0,0 +1,19 @@
+# from: @(#)Makefile 5.2 (Berkeley) 12/28/90
+# $Id: Makefile,v 1.6 1994/06/30 05:33:39 cgd Exp $
+
+PROG= make
+CFLAGS+= -I${.CURDIR} -DPOSIX
+SRCS= arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \
+ make.c parse.c str.c suff.c targ.c var.c
+SRCS+= lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \
+ lstDatum.c lstDeQueue.c lstDestroy.c lstDupl.c lstEnQueue.c \
+ lstFind.c lstFindFrom.c lstFirst.c lstForEach.c lstForEachFrom.c \
+ lstInit.c lstInsert.c lstIsAtEnd.c lstIsEmpty.c lstLast.c \
+ lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c
+.PATH: ${.CURDIR}/lst.lib
+
+.if make(install)
+SUBDIR+= PSD.doc
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms
new file mode 100644
index 000000000000..3cd869d05289
--- /dev/null
+++ b/usr.bin/make/PSD.doc/tutorial.ms
@@ -0,0 +1,3732 @@
+.\" Copyright (c) 1988, 1989 by Adam de Boor
+.\" Copyright (c) 1989 by Berkeley Softworks
+.\" Copyright (c) 1988, 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tutorial.ms 8.4 (Berkeley) 4/28/95
+.\"
+.EH 'PSD:12-%''PMake \*- A Tutorial'
+.OH 'PMake \*- A Tutorial''PSD:12-%'
+.\" xH is a macro to provide numbered headers that are automatically stuffed
+.\" into a table-of-contents, properly indented, etc. If the first argument
+.\" is numeric, it is taken as the depth for numbering (as for .NH), else
+.\" the default (1) is assumed.
+.\"
+.\" $Id: tutorial.ms,v 1.4 89/01/08 20:20:22 adam Exp Locker: adam $
+.\"
+.\" @P The initial paragraph distance.
+.\" @Q The piece of section number to increment (or 0 if none given)
+.\" @R Section header.
+.\" @S Indent for toc entry
+.\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter)
+.de xH
+.NH \\$1
+\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.nr PD .1v
+.XS \\n%
+.ta 0.6i
+\\*(SN \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.XE
+.nr PD .3v
+..
+.\" CW is used to place a string in fixed-width or switch to a
+.\" fixed-width font.
+.\" C is a typewriter font for a laserwriter. Use something else if
+.\" you don't have one...
+.de CW
+.ie !\\n(.$ .ft C
+.el \&\\$3\fC\\$1\fP\\$2
+..
+.\" Anything I put in a display I want to be in fixed-width
+.am DS
+.CW
+..
+.\" The stuff in .No produces a little stop sign in the left margin
+.\" that says NOTE in it. Unfortunately, it does cause a break, but
+.\" hey. Can't have everything. In case you're wondering how I came
+.\" up with such weird commands, they came from running grn on a
+.\" gremlin file...
+.de No
+.br
+.ne 0.5i
+.po -0.5i
+.br
+.mk
+.nr g3 \\n(.f
+.nr g4 \\n(.s
+.sp -1
+.\" .st cf
+\D's -1u'\D't 5u'
+.sp -1
+\h'50u'\D'l 71u 0u'\D'l 50u 50u'\D'l 0u 71u'\D'l -50u 50u'\D'l -71u 0u'\D'l -50u -50u'\D'l 0u -71u'\D'l 50u -50u'
+.sp -1
+\D't 3u'
+.sp -1
+.sp 7u
+\h'53u'\D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'
+.sp -1
+.ft R
+.ps 6
+.nr g8 \\n(.d
+.ds g9 "NOTE
+.sp 74u
+\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9
+.sp |\\n(g8u
+.sp 166u
+\D't 3u'\D's -1u'
+.br
+.po
+.rt
+.ft \\n(g3
+.ps \\n(g4
+..
+.de Bp
+.ie !\\n(.$ .IP \(bu 2
+.el .IP "\&" 2
+..
+.po +.3i
+.TL
+PMake \*- A Tutorial
+.AU
+Adam de Boor
+.AI
+Berkeley Softworks
+2150 Shattuck Ave, Penthouse
+Berkeley, CA 94704
+adam@bsw.uu.net
+\&...!uunet!bsw!adam
+.FS
+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.
+.FE
+.PP
+.xH 1 Introduction
+.LP
+PMake is a program for creating other programs, or anything else you
+can think of for it to do. The basic idea behind PMake 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 modified). PMake takes these dependencies, which you
+must specify, and uses them to build whatever it is you want it to
+build.
+.LP
+PMake is almost fully-compatible with Make, with which you may already
+be familiar. PMake's most important feature is its ability to run
+several different jobs at once, making the creation of systems
+considerably faster. It also has a great deal more functionality than
+Make. Throughout the text, whenever something is mentioned that is an
+important difference between PMake and Make (i.e. something that will
+cause a makefile to fail if you don't do something about it), or is
+simply important, it will be flagged with a little sign in the left
+margin, like this:
+.No
+.LP
+This tutorial is divided into three main sections corresponding to basic,
+intermediate and advanced PMake usage. If you already know Make well,
+you will only need to skim chapter 2 (there are some aspects of
+PMake that I consider basic to its use that didn't exist in Make).
+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 jargon I use and chapter 6 contains possible
+solutions to the problems presented throughout the tutorial.
+.xH 1 The Basics of PMake
+.LP
+PMake 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
+.Ix 0 def makefile
+kept in the top-most directory of the system to be built. While you
+can call the makefile anything you want, PMake will look for
+.CW Makefile
+and
+.CW makefile
+(in that order) in the current directory if you don't tell it
+otherwise.
+.Ix 0 def makefile default
+To specify a different makefile, use the
+.B \-f
+flag (e.g.
+.CW "pmake -f program.mk" ''). ``
+.Ix 0 ref flags -f
+.Ix 0 ref makefile other
+.LP
+A makefile has four different types of lines in it:
+.RS
+.IP \(bu 2
+File dependency specifications
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+Any line may be continued over multiple lines by ending it with a
+backslash.
+.Ix 0 def "continuation line"
+The backslash, following newline and any initial whitespace
+on the following line are compressed into a single space before the
+input line is examined by PMake.
+.xH 2 Dependency Lines
+.LP
+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.
+.Ix 0 def "dependency"
+These are dependencies and are specified by means of dependency lines in
+the makefile.
+.LP
+.Ix 0 def "dependency line"
+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.
+.Ix 0 def target
+.Ix 0 def source
+.Ix 0 ref operator
+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.
+.LP
+Any file may be a target and any file may be a source, but the
+relationship between the two (or however many) is determined by the
+``operator'' that separates them.
+.Ix 0 def operator
+Three types of operators exist: one specifies that the datedness of a
+target is determined by the state of its sources, while another
+specifies other files (the sources) that need to be dealt with before
+the target can be re-created. The third operator is very similar to
+the first, with the additional condition that the target is
+out-of-date if it has no sources. These operations are represented by
+the colon, the exclamation point and the double-colon, respectively, and are
+mutually exclusive. Their exact semantics are as follows:
+.IP ":"
+.Ix 0 def operator colon
+.Ix 0 def :
+If a colon is used, a target on the line is considered to be
+``out-of-date'' (and in need of creation) if
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist.
+.RE
+.Ix 0 def out-of-date
+.IP "\&"
+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.
+.IP "!"
+.Ix 0 def operator force
+.Ix 0 def !
+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.
+.IP "::"
+.Ix 0 def operator double-colon
+.Ix 0 def ::
+If a double-colon is used, a target is out-of-date if:
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist, or
+.IP \(bu 2
+the target has no sources.
+.RE
+.IP "\&"
+If the target is out-of-date according to these rules, it will be re-created.
+This operator also does something else to the targets, but I'll go
+into that in the next section (``Shell Commands'').
+.LP
+Enough words, now for an example. Take that C program I mentioned
+earlier. Say there are three C files
+.CW a.c , (
+.CW b.c
+and
+.CW c.c )
+each of which
+includes the file
+.CW defs.h .
+The dependencies between the files could then be expressed as follows:
+.DS
+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
+.DE
+.LP
+You may be wondering at this point, where
+.CW a.o ,
+.CW b.o
+and
+.CW c.o
+came in and why
+.I they
+depend on
+.CW defs.h
+and the C files don't. The reason is quite simple:
+.CW program
+cannot be made by linking together .c files \*- it must be
+made from .o files. Likewise, if you change
+.CW 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.
+.LP
+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.
+.CW a.o
+depends on both
+.CW defs.h
+and
+.CW a.c .
+.Ix 0 ref dependency
+.No
+.LP
+The order of the dependency lines in the makefile is
+important: the first target on the first dependency line in the
+makefile will be the one that gets made if you don't say otherwise.
+That's why
+.CW program
+comes first in the example makefile, above.
+.LP
+Both targets and sources may contain the standard C-Shell wildcard
+characters
+.CW { , (
+.CW } ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+and
+.CW ] ),
+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:
+.IP \fB{}\fP
+These enclose a comma-separated list of options and cause the pattern
+to be expanded once for each element of the list. Each expansion
+contains a different element. For example,
+.CW src/{whiffle,beep,fish}.c
+expands to the three words
+.CW src/whiffle.c ,
+.CW src/beep.c ,
+and
+.CW src/fish.c .
+These braces may be nested and, unlike the other wildcard characters,
+the resulting words need not be actual files. All other wildcard
+characters are expanded using the files that exist when PMake is
+started.
+.IP \fB*\fP
+This matches zero or more characters of any sort.
+.CW src/*.c
+will expand to the same three words as above as long as
+.CW src
+contains those three files (and no other files that end in
+.CW .c ).
+.IP \fB?\fP
+Matches any single character.
+.IP \fB[]\fP
+This is known as a character class and contains either a list of
+single characters, or a series of character ranges
+.CW a-z , (
+for example means all characters between a and z), or both. It matches
+any single character contained in the list. E.g.
+.CW [A-Za-z]
+will match all letters, while
+.CW [0123456789]
+will match all numbers.
+.xH 2 Shell Commands
+.LP
+``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 Bourne shell (better known as
+``/bin/sh'') to be executed and are
+.Ix 0 ref shell
+.Ix 0 ref re-creation
+.Ix 0 ref update
+expected to do what's necessary to update the target file (PMake
+doesn't actually check to see if the target was created. It just
+assumes it's there).
+.Ix 0 ref target
+.LP
+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
+.I must
+be preceded by at least one tab.
+.LP
+Each target has associated with it a shell script made up of
+one or more of these shell commands. The creation script for a target
+should immediately follow the dependency line for that target. While
+any given target may appear on more than one dependency line, only one
+of these dependency lines may be followed by a creation script, unless
+the `::' operator was used on the dependency line.
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.No
+.LP
+If the double-colon was used, each dependency line for the target
+may be followed by a shell script. That script will only be executed
+if the target on the associated dependency line is out-of-date with
+respect to the sources on that line, according to the rules I gave
+earlier.
+I'll give you a good example of this later on.
+.LP
+To expand on the earlier makefile, you might add commands as follows:
+.DS
+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
+.DE
+.LP
+Something you should remember when writing a makefile is, the
+commands will be executed if the
+.I target
+on the dependency line is out-of-date, not the sources.
+.Ix 0 ref target
+.Ix 0 ref source
+.Ix 0 ref out-of-date
+In this example, the command
+.CW "cc \-c a.c" '' ``
+will be executed if
+.CW a.o
+is out-of-date. Because of the `:' operator,
+.Ix 0 ref :
+.Ix 0 ref operator colon
+this means that should
+.CW a.c
+.I or
+.CW defs.h
+have been modified more recently than
+.CW a.o ,
+the command will be executed
+.CW a.o "\&" (
+will be considered out-of-date).
+.Ix 0 ref out-of-date
+.LP
+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 PMake to do
+different things.
+.LP
+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
+.CW echo
+command, say
+.CW "echo Linking index" ,'' ``
+it would be
+rather silly to see
+.DS
+echo Linking index
+Linking index
+.DE
+.LP
+so PMake allows you to place an `@' before the command
+.CW "@echo Linking index" '') (``
+to prevent the command from being printed.
+.LP
+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, PMake will consider
+an error to have occurred if one of the shells it invokes returns a non-zero
+status. When it detects an error, PMake's usual action is to abort
+whatever it's doing and exit with a non-zero status itself (any other
+targets that were being created will continue being made, but nothing
+new will be started. PMake will exit after the last job finishes).
+This behavior can be altered, however, by placing a `\-' at the front
+of a command
+.CW "\-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 PMake keeps chugging
+along.
+.No
+.LP
+Because all the commands are given to a single shell to execute, such
+things as setting shell variables, changing directories, etc., last
+beyond the command in which they are found. This also allows shell
+compound commands (like
+.CW for
+loops) to be entered in a natural manner.
+Since this could cause problems for some makefiles that depend on
+each command being executed by a single shell, PMake has a
+.B \-B
+.Ix 0 ref compatibility
+.Ix 0 ref flags -B
+flag (it stands for backwards-compatible) that forces each command to
+be given to a separate shell. It also does several other things, all
+of which I discourage since they are now old-fashioned.\|.\|.\|.
+.No
+.LP
+A target's shell script is fed to the shell on its (the shell's) input stream.
+This means that any commands, such as
+.CW 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:
+.DS
+ci $(SRCS) < /dev/tty
+.DE
+This would force the program's input to come from the terminal. If you
+can't do this for some reason, your only other alternative is to use
+PMake in its fullest compatibility mode. See
+.B Compatibility
+in chapter 4.
+.Ix 0 ref compatibility
+.LP
+.xH 2 Variables
+.LP
+PMake, like Make before it, has the ability to save text in variables
+to be recalled later at your convenience. Variables in PMake are used
+much like variables in the shell and, by tradition, consist of
+all upper-case letters (you don't
+.I have
+to use all upper-case letters.
+In fact there's nothing to stop you from calling a variable
+.CW @^&$%$ .
+Just tradition). Variables are assigned-to using lines of the form
+.Ix 0 def variable assignment
+.DS
+VARIABLE = value
+.DE
+.Ix 0 def variable assignment
+appended-to by
+.DS
+VARIABLE += value
+.DE
+.Ix 0 def variable appending
+.Ix 0 def variable assignment appended
+.Ix 0 def +=
+conditionally assigned-to (if the variable isn't already defined) by
+.DS
+VARIABLE ?= value
+.DE
+.Ix 0 def variable assignment conditional
+.Ix 0 def ?=
+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
+.DS
+VARIABLE := value
+.DE
+.Ix 0 def variable assignment expanded
+.Ix 0 def :=
+.LP
+Any whitespace before
+.I value
+is stripped off. When appending, a space is placed between the old
+value and the stuff being appended.
+.LP
+The final way a variable may be assigned to is using
+.DS
+VARIABLE != shell-command
+.DE
+.Ix 0 def variable assignment shell-output
+.Ix 0 def !=
+In this case,
+.I shell-command
+has all its variables expanded (see below) and is passed off to a
+shell to execute. The output of the shell is then placed in the
+variable. Any newlines (other than the final one) are replaced by
+spaces before the assignment is made. This is typically used to find
+the current directory via a line like:
+.DS
+CWD != pwd
+.DE
+.LP
+.B Note:
+this is intended to be used to execute commands that produce small amounts
+of output (e.g. ``pwd''). The implementation is less than intelligent and will
+likely freeze if you execute something that produces thousands of
+bytes of output (8 Kb is the limit on many UNIX systems).
+.LP
+The value of a variable may be retrieved by enclosing the variable
+name in parentheses or curly braces and preceeding the whole thing
+with a dollar sign.
+.LP
+For example, to set the variable CFLAGS to the string
+.CW "\-I/sprite/src/lib/libc \-O" ,'' ``
+you would place a line
+.DS
+CFLAGS = \-I/sprite/src/lib/libc \-O
+.DE
+in the makefile and use the word
+.CW "$(CFLAGS)"
+wherever you would like the string
+.CW "\-I/sprite/src/lib/libc \-O"
+to appear. This is called variable expansion.
+.Ix 0 def variable expansion
+.No
+.LP
+Unlike Make, PMake will not expand a variable unless it knows
+the variable exists. E.g. if you have a
+.CW "${i}"
+in a shell command and you have not assigned a value to the variable
+.CW i
+(the empty string is considered a value, by the way), where Make would have
+substituted the empty string, PMake will leave the
+.CW "${i}"
+alone.
+To keep PMake from substituting for a variable it knows, precede the
+dollar sign with another dollar sign.
+(e.g. to pass
+.CW "${HOME}"
+to the shell, use
+.CW "$${HOME}" ).
+This causes PMake, in effect, to expand the
+.CW $
+macro, which expands to a single
+.CW $ .
+For compatibility, Make's style of variable expansion will be used
+if you invoke PMake with any of the compatibility flags (\c
+.B \-V ,
+.B \-B
+or
+.B \-M .
+The
+.B \-V
+flag alters just the variable expansion).
+.Ix 0 ref flags -V
+.Ix 0 ref flags -B
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.LP
+.Ix 0 ref variable expansion
+There are two different times at which variable expansion occurs:
+When parsing a dependency line, the expansion occurs immediately
+upon reading the line. If any variable used on a dependency line is
+undefined, PMake will print a message and exit.
+Variables in shell commands are expanded when the command is
+executed.
+Variables used inside another variable are expanded whenever the outer
+variable is expanded (the expansion of an inner variable has no effect
+on the outer variable. 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 substituted for the outer
+variable).
+.Ix 0 def variable types
+.LP
+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):
+.RS
+.IP \(bu 2
+Local variables.
+.Ix 0 ref variable local
+.IP \(bu 2
+Command-line variables.
+.Ix 0 ref variable command-line
+.IP \(bu 2
+Global variables.
+.Ix 0 ref variable global
+.IP \(bu 2
+Environment variables.
+.Ix 0 ref variable environment
+.RE
+.LP
+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.
+.xH 3 Local Variables
+.LP
+.Ix 0 def variable local
+Each target can have as many as seven local variables. These are
+variables that are only ``visible'' within that target's shell script
+and contain such things as the target's name, all of its sources (from
+all its dependency lines), those sources that were out-of-date, etc.
+Four local variables are defined for all targets. They are:
+.RS
+.IP ".TARGET"
+.Ix 0 def variable local .TARGET
+.Ix 0 def .TARGET
+The name of the target.
+.IP ".OODATE"
+.Ix 0 def variable local .OODATE
+.Ix 0 def .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.
+.IP ".ALLSRC"
+.Ix 0 def variable local .ALLSRC
+.Ix 0 def .ALLSRC
+The list of all sources for this target in the order in which they
+were given.
+.IP ".PREFIX"
+.Ix 0 def variable local .PREFIX
+.Ix 0 def .PREFIX
+The target without its suffix and without any leading path. E.g. for
+the target
+.CW ../../lib/compat/fsRead.c ,
+this variable would contain
+.CW fsRead .
+.RE
+.LP
+Three other local variables are set only for certain targets under
+special circumstances. These are the ``.IMPSRC,''
+.Ix 0 ref variable local .IMPSRC
+.Ix 0 ref .IMPSRC
+``.ARCHIVE,''
+.Ix 0 ref variable local .ARCHIVE
+.Ix 0 ref .ARCHIVE
+and ``.MEMBER''
+.Ix 0 ref variable local .MEMBER
+.Ix 0 ref .MEMBER
+variables. When they are set and how they are used is described later.
+.LP
+Four of these variables may be used in sources as well as in shell
+scripts.
+.Ix 0 def "dynamic source"
+.Ix 0 def source dynamic
+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,''
+.Rd 0
+allowing you to specify several dependency lines at once. For example,
+.DS
+$(OBJS) : $(.PREFIX).c
+.DE
+will create a dependency between each object file and its
+corresponding C source file.
+.xH 3 Command-line Variables
+.LP
+.Ix 0 def variable command-line
+Command-line variables are set when PMake is first invoked by giving a
+variable assignment as one of the arguments. For example,
+.DS
+pmake "CFLAGS = -I/sprite/src/lib/libc -O"
+.DE
+would make
+.CW CFLAGS
+be a command-line variable with the given value. Any assignments to
+.CW 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
+.CW =
+and
+.CW ?=
+behave as you would expect them to, mostly because assignments to
+command-line variables are performed before the makefile is read, thus
+the values set in the makefile are unavailable at the time.
+.CW +=
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+is the same as
+.CW = ,
+because the old value of the variable is sought only in the scope in
+which the assignment is taking place (for reasons of efficiency that I
+won't get into here).
+.CW :=
+and
+.CW ?=
+.Ix 0 ref :=
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable assignment conditional
+will work if the only variables used are in the environment.
+.CW !=
+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).
+.xH 3 Global Variables
+.LP
+.Ix 0 def variable global
+Global variables are those set or appended-to in the makefile.
+There are two classes of global variables: those you set and those PMake 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 PMake sets (almost) always begin with a
+period and always contain upper-case letters, only. The variables are
+as follows:
+.RS
+.IP .PMAKE
+.Ix 0 def variable global .PMAKE
+.Ix 0 def .PMAKE
+.Ix 0 def variable global MAKE
+.Ix 0 def MAKE
+The name by which PMake was invoked is stored in this variable. For
+compatibility, the name is also stored in the MAKE variable.
+.IP .MAKEFLAGS
+.Ix 0 def variable global .MAKEFLAGS
+.Ix 0 def .MAKEFLAGS variable
+.Ix 0 def variable global MFLAGS
+.Ix 0 def MFLAGS
+All the relevant flags with which PMake was invoked. This does not
+include such things as
+.B \-f
+or variable assignments. Again for compatibility, this value is stored
+in the MFLAGS variable as well.
+.RE
+.LP
+Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the
+section on special targets in chapter 3.
+.Ix 0 ref variable global .INCLUDES
+.Ix 0 ref variable global .LIBS
+.LP
+Global variables may be deleted using lines of the form:
+.Ix 0 def #undef
+.Ix 0 def variable deletion
+.DS
+#undef \fIvariable\fP
+.DE
+The
+.CW # ' `
+must be the first character on the line. Note that this may only be
+done on global variables.
+.xH 3 Environment Variables
+.LP
+.Ix 0 def variable environment
+Environment variables are passed by the shell that invoked PMake and
+are given by PMake to each shell it invokes. They are expanded like
+any other variable, but they cannot be altered in any way.
+.LP
+One special environment variable,
+.CW PMAKE ,
+.Ix 0 def variable environment PMAKE
+is examined by PMake for command-line flags, variable assignments,
+etc., it should always use. This variable is examined before the
+actual arguments to PMake are. In addition, all flags given to PMake,
+either through the
+.CW PMAKE
+variable or on the command line, are placed in this environment
+variable and exported to each shell PMake executes. Thus recursive
+invocations of PMake automatically receive the same flags as the
+top-most one.
+.LP
+Using all these variables, you can compress the sample makefile even more:
+.DS
+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
+.DE
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref .ALLSRC
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref .TARGET
+.Rd 3
+.xH 2 Comments
+.LP
+.Ix 0 def 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.
+PMake will compress the two into a single `#' (Note: this isn't true
+if PMake is operating in full-compatibility mode).
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.xH 2 Parallelism
+.No
+.LP
+PMake was specifically designed to re-create several targets at once,
+when possible. You do not have to do anything special to cause this to
+happen (unless PMake was configured to not act in parallel, in which
+case you will have to make use of the
+.B \-L
+and
+.B \-J
+flags (see below)),
+.Ix 0 ref flags -L
+.Ix 0 ref flags -J
+but you do have to be careful at times.
+.LP
+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
+.CW xstr ,
+for example,
+always modifies the files
+.CW strings
+and
+.CW 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
+.CW $$$$
+to the end of the file name to tack on the process ID of the shell
+executing the command (each
+.CW $$
+expands to a single
+.CW $ ,
+thus giving you the shell variable
+.CW $$ ).
+Since only one shell is used for all the
+commands, you'll get the same file name for each command in the
+script.
+.LP
+The other problem comes from improperly-specified dependencies that
+worked in Make because of its sequential, depth-first way of examining
+them. While I don't want to go into depth on how PMake
+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 different order in PMake than they were in Make. For
+example, given the makefile
+.DS
+a : b c
+b : d
+.DE
+PMake will examine the targets in the order
+.CW c ,
+.CW d ,
+.CW b ,
+.CW a .
+If the makefile's author expected PMake to abort before making
+.CW c
+if an error occurred while making
+.CW b ,
+or if
+.CW b
+needed to exist before
+.CW c
+was made,
+s/he will be sorely disappointed. The dependencies are
+incomplete, since in both these cases,
+.CW c
+would depend on
+.CW b .
+So watch out.
+.LP
+Another problem you may face is that, while PMake 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 redirection from
+.CW /dev/tty
+I mentioned earlier, you must be careful not to run two of the jobs at once.
+.xH 2 Writing and Debugging a Makefile
+.LP
+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 PMake and doing everything by hand as being beyond the bounds
+of common sense).
+.LP
+When faced with the writing of a makefile, it is usually best to start
+from first principles: just what
+.I are
+you trying to do? What do you want the makefile finally to produce?
+.LP
+To begin with a somewhat traditional example, let's say you need to
+write a makefile to create a program,
+.CW 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:
+.CW main.c ,
+.CW parse.c ,
+and
+.CW output.c .
+Harking back to my pithy advice about dependency lines, you write the
+first line of the file:
+.DS
+expr : main.o parse.o output.o
+.DE
+because you remember
+.CW expr
+is made from
+.CW .o
+files, not
+.CW .c
+files. Similarly for the
+.CW .o
+files you produce the lines:
+.DS
+main.o : main.c
+parse.o : parse.c
+output.o : output.c
+main.o parse.o output.o : defs.h
+.DE
+.LP
+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
+.DS
+expr : main.o parse.o output.o
+ cc -o $(.TARGET) $(.ALLSRC)
+.DE
+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
+.I and
+.CW defs.h ,
+thus if you said
+.DS
+ cc -c $(.ALLSRC)
+.DE
+you'd get (for
+.CW main.o ):
+.DS
+ cc -c main.c defs.h
+.DE
+which is wrong. So you round out the makefile with these lines:
+.DS
+main.o : main.c
+ cc -c main.c
+parse.o : parse.c
+ cc -c parse.c
+output.o : output.c
+ cc -c output.c
+.DE
+.LP
+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):
+.IP 1)
+The string
+.CW "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:
+.QP
+.I
+Anything that needs to be written more than once
+should be placed in a variable.
+.IP "\&"
+I cannot emphasize this enough as being very important to the
+maintenance of a makefile and its program.
+.IP 2)
+There is no way to alter the way compilations are performed 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:
+.QP
+.I
+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.
+.LP
+The makefile should more properly read:
+.DS
+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
+.DE
+Alternatively, if you like the idea of dynamic sources mentioned in
+section 2.3.1,
+.Rm 0 2.3.1
+.Rd 4
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+you could write it like this:
+.DS
+OBJS = main.o parse.o output.o
+expr : $(OBJS)
+ $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : $(.PREFIX).c defs.h
+ $(CC) $(CFLAGS) -c $(.PREFIX).c
+.DE
+These two rules and examples lead to de Boor's First Corollary:
+.QP
+.I
+Variables are your friends.
+.LP
+Once you've written the makefile comes the sometimes-difficult task of
+.Ix 0 ref debugging
+making sure the darn thing works. Your most helpful tool to make sure
+the makefile is at least syntactically correct is the
+.B \-n
+.Ix 0 ref flags -n
+flag, which allows you to see if PMake will choke on the makefile. The
+second thing the
+.B \-n
+flag lets you do is see what PMake would do without it actually doing
+it, thus you can make sure the right commands would be executed were
+you to give PMake its head.
+.LP
+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, two flags will serve you well:
+.CW "-d m" '' ``
+.Ix 0 ref flags -d
+and
+.CW "-p 2" .'' ``
+.Ix 0 ref flags -p
+The first causes PMake 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. The
+.CW "-p 2" '' ``
+flag makes PMake print out its internal state when it is done,
+allowing you to see that you forgot to make that one chapter depend on
+that file of macros you just got a new version of. The output from
+.CW "-p 2" '' ``
+is intended to resemble closely a real makefile, but with additional
+information provided and with variables expanded in those commands
+PMake actually printed or executed.
+.LP
+Something to be especially careful about is circular dependencies.
+.Ix 0 def dependency circular
+E.g.
+.DS
+a : b
+b : c d
+d : a
+.DE
+In this case, because of how PMake works,
+.CW c
+is the only thing PMake will examine, because
+.CW d
+and
+.CW a
+will effectively fall off the edge of the universe, making it
+impossible to examine
+.CW b
+(or them, for that matter).
+PMake 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, PMake 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.
+.xH 2 Invoking PMake
+.LP
+.Ix 0 ref flags
+.Ix 0 ref arguments
+.Ix 0 ref usage
+PMake 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.
+The flags are as follows:
+.IP "\fB\-d\fP \fIwhat\fP"
+.Ix 0 def flags -d
+.Ix 0 ref debugging
+This causes PMake to spew out debugging information that
+may prove useful to you. If you can't
+figure out why PMake is doing what it's doing, you might try using
+this flag. The
+.I what
+parameter is a string of single characters that tell PMake 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:
+.RS
+.IP a
+Archive searching and caching.
+.IP c
+Conditional evaluation.
+.IP d
+The searching and caching of directories.
+.IP j
+Various snippets of information related to the running of the multiple
+shells. Not particularly interesting.
+.IP m
+The making of each target: what target is being examined; when it was
+last modified; whether it is out-of-date; etc.
+.IP p
+Makefile parsing.
+.IP r
+Remote execution.
+.IP s
+The application of suffix-transformation rules. (See chapter 3)
+.IP t
+The maintenance of the list of targets.
+.IP v
+Variable assignment.
+.RE
+.IP "\&"
+Of these all, the
+.CW m
+and
+.CW s
+letters will be most useful to you.
+If the
+.B \-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
+.B \- ,
+all of these debugging flags will be set, resulting in massive amounts
+of output.
+.IP "\fB\-f\fP \fImakefile\fP"
+.Ix 0 def flags -f
+Specify a makefile to read different from the standard makefiles
+.CW Makefile "\&" (
+or
+.CW makefile ).
+.Ix 0 ref makefile default
+.Ix 0 ref makefile other
+If
+.I makefile
+is ``\-'', PMake uses the standard input. This is useful for making
+quick and dirty makefiles.\|.\|.
+.Ix 0 ref makefile "quick and dirty"
+.IP \fB\-h\fP
+.Ix 0 def flags -h
+Prints out a summary of the various flags PMake accepts. It can also
+be used to find out what level of concurrency was compiled into the
+version of PMake you are using (look at
+.B \-J
+and
+.B \-L )
+and various other information on how PMake was configured.
+.Ix 0 ref configuration
+.Ix 0 ref makefile system
+.IP \fB\-i\fP
+.Ix 0 def flags -i
+If you give this flag, PMake will ignore non-zero status returned
+by any of its shells. It's like placing a `\-' before all the commands
+in the makefile.
+.IP \fB\-k\fP
+.Ix 0 def flags -k
+This is similar to
+.B \-i
+in that it allows PMake to continue when it sees an error, but unlike
+.B \-i ,
+where PMake continues blithely as if nothing went wrong,
+.B \-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''.\|.\|.
+.Ix 0 ref target
+.IP \fB\-l\fP
+.Ix 0 def flags -l
+PMake has the ability to lock a directory against other
+people executing it in the same directory (by means of a file called
+``LOCK.make'' that it creates and checks for in the directory). This
+is a Good Thing because two people doing the same thing in the same place
+can be disastrous for the final product (too many cooks and all that).
+Whether this locking is the default is up to your system
+administrator. If locking is on,
+.B \-l
+will turn it off, and vice versa. Note that this locking will not
+prevent \fIyou\fP from invoking PMake twice in the same place \*- if
+you own the lock file, PMake will warn you about it but continue to execute.
+.IP \fB\-n\fP
+.Ix 0 def flags -n
+This flag tells PMake not to execute the commands needed to update the
+out-of-date targets in the makefile. Rather, PMake will simply print
+the commands it would have executed and exit. This is particularly
+useful for checking the correctness of a makefile. If PMake doesn't do
+what you expect it to, it's a good chance the makefile is wrong.
+.IP "\fB\-p\fP \fInumber\fP"
+.Ix 0 def flags -p
+.Ix 0 ref debugging
+This causes PMake to print its input in a reasonable form, though
+not necessarily one that would make immediate sense to anyone but me. The
+.I number
+is a bitwise-or of 1 and 2 where 1 means it should print the input
+before doing any processing and 2 says it should print it after
+everything has been re-created. Thus
+.CW "\-p 3"
+would print it twice\*-once before processing and once after (you
+might find the difference between the two interesting). This is mostly
+useful to me, but you may find it informative in some bizarre circumstances.
+.IP \fB\-q\fP
+.Ix 0 def flags -q
+If you give PMake 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.
+.IP \fB\-r\fP
+.Ix 0 def flags -r
+When PMake 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, PMake won't read the default makefile.
+.IP \fB\-s\fP
+.Ix 0 def flags -s
+This causes PMake to not print commands before they're executed. It
+is the equivalent of putting an `@' before every command in the
+makefile.
+.IP \fB\-t\fP
+.Ix 0 def flags -t
+Rather than try to re-create a target, PMake will simply ``touch'' it
+so as to make it appear up-to-date. If the target didn't exist before,
+it will when PMake finishes, but if the target did exist, it will
+appear to have been updated.
+.IP \fB\-v\fP
+.Ix 0 def flags -v
+This is a mixed-compatibility flag intended to mimic the System V
+version of Make. It is the same as giving
+.B \-B ,
+and
+.B \-V
+as well as turning off directory locking. Targets can still be created
+in parallel, however. This is the mode PMake will enter if it is
+invoked either as
+.CW smake '' ``
+or
+.CW vmake ''. ``
+.IP \fB\-x\fP
+.Ix 0 def flags -x
+This tells PMake it's ok to export jobs to other machines, if they're
+available. It is used when running in Make mode, as exporting in this
+mode tends to make things run slower than if the commands were just
+executed locally.
+.IP \fB\-B\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -B
+Forces PMake to be as backwards-compatible with Make as possible while
+still being itself.
+This includes:
+.RS
+.IP \(bu 2
+Executing one shell per shell command
+.IP \(bu 2
+Expanding anything that looks even vaguely like a variable, with the
+empty string replacing any variable PMake doesn't know.
+.IP \(bu 2
+Refusing to allow you to escape a `#' with a backslash.
+.IP \(bu 2
+Permitting undefined variables on dependency lines and conditionals
+(see below). Normally this causes PMake to abort.
+.RE
+.IP \fB\-C\fP
+.Ix 0 def flags -C
+This nullifies any and all compatibility mode flags you may have given
+or implied up to the time the
+.B \-C
+is encountered. It is useful mostly in a makefile that you wrote for PMake
+to avoid bad things happening when someone runs PMake as
+.CW make '' ``
+or has things set in the environment that tell it to be compatible.
+.B \-C
+is
+.I not
+placed in the
+.CW PMAKE
+environment variable or the
+.CW .MAKEFLAGS
+or
+.CW MFLAGS
+global variables.
+.Ix 0 ref variable environment PMAKE
+.Ix 0 ref variable global .MAKEFLAGS
+.Ix 0 ref variable global MFLAGS
+.Ix 0 ref .MAKEFLAGS variable
+.Ix 0 ref MFLAGS
+.IP "\fB\-D\fP \fIvariable\fP"
+.Ix 0 def flags -D
+Allows you to define a variable to have
+.CW 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
+.Rm 1 4.3
+.IP "\fB\-I\fP \fIdirectory\fP"
+.Ix 0 def flags -I
+Tells PMake another place to search for included makefiles. Yet
+another thing to be explained in chapter 3 (section 3.2, to be
+precise).
+.Rm 2 3.2
+.IP "\fB\-J\fP \fInumber\fP"
+.Ix 0 def flags -J
+Gives the absolute maximum number of targets to create at once on both
+local and remote machines.
+.IP "\fB\-L\fP \fInumber\fP"
+.Ix 0 def flags -L
+This specifies the maximum number of targets to create on the local
+machine at once. This may be 0, though you should be wary of doing
+this, as PMake may hang until a remote machine becomes available, if
+one is not available when it is started.
+.IP \fB\-M\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -M
+This is the flag that provides absolute, complete, full compatibility
+with Make. It still allows you to use all but a few of the features of
+PMake, but it is non-parallel. This is the mode PMake enters if you
+call it
+.CW make .'' ``
+.IP \fB\-P\fP
+.Ix 0 def flags -P
+.Ix 0 ref "output control"
+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 captured by PMake in some way in order to prevent
+the screen from being filled with garbage even more indecipherable
+than you usually see. PMake has two ways of doing this, one of which
+provides for much cleaner output and a clear separation between the
+output of different jobs, the other of which provides a more immediate
+response so one can tell what is really happpening. The former is done
+by notifying you when the creation of a target starts, capturing the
+output and transferring it to the screen all at once when the job
+finishes. 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 preceded by an indication of which job produced
+the output. Since I prefer this second method, it is the one used by
+default. The first method will be used if you give the
+.B \-P
+flag to PMake.
+.IP \fB\-V\fP
+.Ix 0 def flags -V
+As mentioned before, the
+.B \-V
+flag tells PMake to use Make's style of expanding variables,
+substituting the empty string for any variable it doesn't know.
+.IP \fB\-W\fP
+.Ix 0 def flags -W
+There are several times when PMake will print a message at you that is
+only a warning, i.e. it can continue to work in spite of your having
+done something silly (such as forgotten a leading tab for a shell
+command). Sometimes you are well aware of silly things you have done
+and would like PMake to stop bothering you. This flag tells it to shut
+up about anything non-fatal.
+.IP \fB\-X\fP
+.Ix 0 def flags -X
+This flag causes PMake to not attempt to export any jobs to another
+machine.
+.LP
+Several flags may follow a single `\-'. Those flags that require
+arguments take them from successive parameters. E.g.
+.DS
+pmake -fDnI server.mk DEBUG /chip2/X/server/include
+.DE
+will cause PMake to read
+.CW server.mk
+as the input makefile, define the variable
+.CW DEBUG
+as a global variable and look for included makefiles in the directory
+.CW /chip2/X/server/include .
+.xH 2 Summary
+.LP
+A makefile is made of four types of lines:
+.RS
+.IP \(bu 2
+Dependency lines
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+A dependency line is a list of one or more targets, an operator
+.CW : ', (`
+.CW :: ', `
+or
+.CW ! '), `
+and a list of zero or more sources. Sources may contain wildcards and
+certain local variables.
+.LP
+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
+.CW @ ' `
+or
+.CW - ', `
+PMake will cause the command to not be printed (if the character is
+.CW @ ') `
+or errors from it to be ignored (if
+.CW - '). `
+A blank line, dependency line or variable assignment terminates a
+creation script. There may be only one creation script for each target
+with a
+.CW : ' `
+or
+.CW ! ' `
+operator.
+.LP
+Variables are places to store text. They may be unconditionally
+assigned-to using the
+.CW = ' `
+.Ix 0 ref =
+.Ix 0 ref variable assignment
+operator, appended-to using the
+.CW += ' `
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+operator, conditionally (if the variable is undefined) assigned-to
+with the
+.CW ?= ' `
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment conditional
+operator, and assigned-to with variable expansion with the
+.CW := ' `
+.Ix 0 ref :=
+.Ix 0 ref variable assignment expanded
+operator. The output of a shell command may be assigned to a variable
+using the
+.CW != ' `
+.Ix 0 ref !=
+.Ix 0 ref variable assignment shell-output
+operator. Variables may be expanded (their value inserted) by enclosing
+their name in parentheses or curly braces, prceeded by a dollar sign.
+A dollar sign may be escaped with another dollar sign. Variables are
+not expanded if PMake doesn't know about them. There are seven local
+variables:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+Four of them
+.CW .TARGET , (
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER )
+may be used to specify ``dynamic sources.''
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+Variables are good. Know them. Love them. Live them.
+.LP
+Debugging of makefiles is best accomplished using the
+.B \-n ,
+.B "\-d m" ,
+and
+.B "\-p 2"
+flags.
+.xH 2 Exercises
+.ce
+\s+4\fBTBA\fP\s0
+.xH 1 Short-cuts and Other Nice Things
+.LP
+Based on what I've told you so far, you may have gotten the impression
+that PMake is just a way of storing away commands and making sure you
+don't forget to compile something. Good. That's just what it is.
+However, the ways I've described have been inelegant, at best, and
+painful, at worst.
+This chapter contains things that make the
+writing of makefiles easier and the makefiles themselves shorter and
+easier to modify (and, occasionally, simpler). In this chapter, I
+assume you are somewhat more
+familiar with Sprite (or UNIX, if that's what you're using) than I did
+in chapter 2, just so you're on your toes.
+So without further ado...
+.xH 2 Transformation Rules
+.LP
+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
+.UX
+has developed,
+naming conventions, with regard to suffixes, have also developed that have
+become almost as incontrovertible as Law. E.g. a file ending in
+.CW .c
+is assumed to contain C source code; one with a
+.CW .o
+suffix is assumed to be a compiled, relocatable object file that may
+be linked into any program; a file with a
+.CW .ms
+suffix is usually a text file to be processed by Troff with the \-ms
+macro package, and so on.
+One of the best aspects of both Make and PMake comes from their
+understanding of how the suffix of a file pertains to its contents and
+their ability to do things with a file based soley on its suffix. This
+ability comes from something known as a transformation rule. A
+transformation rule specifies how to change a file with one suffix
+into a file with another suffix.
+.LP
+A transformation rule looks much like a dependency line, except the
+target is made of two known suffixes stuck together. Suffixes are made
+known to PMake by placing them as sources on a dependency line whose
+target is the special target
+.CW .SUFFIXES .
+E.g.
+.DS
+\&.SUFFIXES : .o .c
+\&.c.o :
+ $(CC) $(CFLAGS) -c $(.IMPSRC)
+.DE
+The creation script attached to the target is used to transform a file with
+the first suffix (in this case,
+.CW .c )
+into a file with the second suffix (here,
+.CW .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
+.CW cc
+with the
+.CW \-c
+flag.
+This rule is taken straight from the system makefile. Many
+transformation rules (and suffixes) are defined there, and I refer you
+to it for more examples (type
+.CW "pmake -h" '' ``
+to find out where it is).
+.LP
+There are several things to note about the transformation rule given
+above:
+.RS
+.IP 1)
+The
+.CW .IMPSRC
+variable.
+.Ix 0 def variable local .IMPSRC
+.Ix 0 def .IMPSRC
+This variable is set to the ``implied source'' (the file from which
+the target is being created; the one with the first suffix), which, in this
+case, is the .c file.
+.IP 2)
+The
+.CW CFLAGS
+variable. Almost all of the transformation rules in the system
+makefile are set up using variables that you can alter in your
+makefile to tailor the rule to your needs. In this case, if you want
+all your C files to be compiled with the
+.B \-g
+flag, to provide information for
+.CW dbx ,
+you would set the
+.CW CFLAGS
+variable to contain
+.CW -g
+.CW "CFLAGS = -g" '') (``
+and PMake would take care of the rest.
+.RE
+.LP
+To give you a quick example, the makefile in 2.3.4
+.Rm 3 2.3.4
+could be changed to this:
+.DS
+OBJS = a.o b.o c.o
+program : $(OBJS)
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : defs.h
+.DE
+The transformation rule I gave above takes the place of the 6 lines\**
+.FS
+This is also somewhat cleaner, I think, than the dynamic source
+solution presented in 2.6
+.FE
+.Rm 4 2.6
+.DS
+a.o : a.c
+ cc -c a.c
+b.o : b.c
+ cc -c b.c
+c.o : c.c
+ cc -c c.c
+.DE
+.LP
+Now you may be wondering about the dependency between the
+.CW .o
+and
+.CW .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
+.I source .
+.LP
+For a more detailed example. Say you have a makefile like this:
+.DS
+a.out : a.o b.o
+ $(CC) $(.ALLSRC)
+.DE
+and a directory set up like this:
+.DS
+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
+.DE
+While just typing
+.CW pmake '' ``
+will do the right thing, it's much more informative to type
+.CW "pmake -d s" ''. ``
+This will show you what PMake is up to as it processes the files. In
+this case, PMake prints the following:
+.DS
+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
+.DE
+.LP
+.CW Suff_FindDeps
+is the name of a function in PMake that is called to check for implied
+sources for a target using transformation rules.
+The transformations it tries are, naturally
+enough, limited to the ones that have been defined (a transformation
+may be defined multiple times, by the way, but only the most recent
+one will be used). You will notice, however, that there is a definite
+order to the suffixes that are tried. This order is set by the
+relative positions of the suffixes on the
+.CW .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
+.CW .SUFFIXES
+dependency line with no sources) and redefine them in the order you
+want. (Previously-defined transformation rules will be automatically
+redefined as the suffixes they involve are re-entered.)
+.LP
+Another way to affect the search order is to make the dependency
+explicit. In the above example,
+.CW a.out
+depends on
+.CW a.o
+and
+.CW b.o .
+Since a transformation exists from
+.CW .o
+to
+.CW .out ,
+PMake uses that, as indicated by the
+.CW "using existing source a.o" '' ``
+message.
+.LP
+The search for a transformation starts from the suffix of the target
+and continues through all the defined transformations, in the order
+dictated by the suffix ranking, until an existing file with the same
+base (the target name minus the suffix and any leading directories) is
+found. At that point, one or more transformation rules will have been
+found to change the one existing file into the target.
+.LP
+For example, ignoring what's in the system makefile for now, say you
+have a makefile like this:
+.DS
+\&.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)
+.DE
+and the single file
+.CW jive.l .
+If you were to type
+.CW "pmake -rd ms jive.out" ,'' ``
+you would get the following output for
+.CW jive.out :
+.DS
+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"
+.DE
+and this is why: PMake starts with the target
+.CW jive.out ,
+figures out its suffix
+.CW .out ) (
+and looks for things it can transform to a
+.CW .out
+file. In this case, it only finds
+.CW .o ,
+so it looks for the file
+.CW jive.o .
+It fails to find it, so it looks for transformations into a
+.CW .o
+file. Again it has only one choice:
+.CW .c .
+So it looks for
+.CW jive.c
+and, as you know, fails to find it. At this point it has two choices:
+it can create the
+.CW .c
+file from either a
+.CW .y
+file or a
+.CW .l
+file. Since
+.CW .y
+came first on the
+.CW .SUFFIXES
+line, it checks for
+.CW jive.y
+first, but can't find it, so it looks for
+.CW jive.l
+and, lo and behold, there it is.
+At this point, it has defined a transformation path as follows:
+.CW .l
+\(->
+.CW .c
+\(->
+.CW .o
+\(->
+.CW .out
+and applies the transformation rules accordingly. For completeness,
+and to give you a better idea of what PMake actually did with this
+three-step transformation, this is what PMake printed for the rest of
+the process:
+.DS
+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
+.DE
+.LP
+One final question remains: what does PMake do with targets that have
+no known suffix? PMake simply pretends it actually has a known suffix
+and searches for transformations accordingly.
+The suffix it chooses is the source for the
+.CW .NULL
+.Ix 0 ref .NULL
+target mentioned later. In the system makefile,
+.CW .out
+is chosen as the ``null suffix''
+.Ix 0 def suffix null
+.Ix 0 def "null suffix"
+because most people use PMake to create programs. You are, however,
+free and welcome to change it to a suffix of your own choosing.
+The null suffix is ignored, however, when PMake is in compatibility
+mode (see chapter 4).
+.xH 2 Including Other Makefiles
+.Ix 0 def makefile inclusion
+.Rd 2
+.LP
+Just as for programs, it is often useful to extract certain parts of a
+makefile into another file and just include it in other makefiles
+somehow. Many compilers allow you say something like
+.DS
+#include "defs.h"
+.DE
+to include the contents of
+.CW defs.h
+in the source file. PMake allows you to do the same thing for
+makefiles, with the added ability to use variables in the filenames.
+An include directive in a makefile looks either like this:
+.DS
+#include <file>
+.DE
+or this
+.DS
+#include "file"
+.DE
+The difference between the two is where PMake searches for the file:
+the first way, PMake will look for
+the file only in the system makefile directory (to find out what that
+directory is, give PMake the
+.B \-h
+flag).
+.Ix 0 ref flags -h
+For files in double-quotes, the search is more complex:
+.RS
+.IP 1)
+The directory of the makefile that's including the file.
+.IP 2)
+The current directory (the one in which you invoked PMake).
+.IP 3)
+The directories given by you using
+.B \-I
+flags, in the order in which you gave them.
+.IP 4)
+Directories given by
+.CW .PATH
+dependency lines (see chapter 4).
+.IP 5)
+The system makefile directory.
+.RE
+.LP
+in that order.
+.LP
+You are free to use PMake variables in the filename\*-PMake will
+expand them before searching for the file. You must specify the
+searching method with either angle brackets or double-quotes
+.I outside
+of a variable expansion. I.e. the following
+.DS
+SYSTEM = <command.mk>
+
+#include $(SYSTEM)
+.DE
+won't work.
+.xH 2 Saving Commands
+.LP
+.Ix 0 def ...
+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,
+.CW 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
+.CW __.SYMDEF
+into which it stuffs information for the linker to use. Two of them
+running at once will overwrite each other's file and the result will
+be garbage for both parties). You might want a way to save the ranlib
+commands til the end so they can be run one after the other, thus
+keeping them from trashing each other's file. PMake allows you to do
+this by inserting an ellipsis (``.\|.\|.'') as a command between
+commands to be run at once and those to be run later.
+.LP
+So for the
+.CW ranlib
+case above, you might do this:
+.Rd 5
+.DS
+lib1.a : $(LIB1OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+
+lib2.a : $(LIB2OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+This would save both
+.DS
+ranlib $(.TARGET)
+.DE
+commands until the end, when they would run one after the other
+(using the correct value for the
+.CW .TARGET
+variable, of course).
+.LP
+Commands saved in this manner are only executed if PMake manages to
+re-create everything without an error.
+.xH 2 Target Attributes
+.LP
+PMake allows you to give attributes to targets by means of special
+sources. Like everything else PMake uses, these sources begin with a
+period and are made up of all upper-case letters. There are various
+reasons for using them, and I will try to give examples for most of
+them. Others you'll have to find uses for yourself. Think of it as ``an
+exercise for the reader.'' By placing one (or more) of these as a source on a
+dependency line, you are ``marking the target(s) with that
+attribute.'' That's just the way I phrase it, so you know.
+.LP
+Any attributes given as sources for a transformation rule are applied
+to the target of the transformation rule when the rule is applied.
+.Ix 0 def attributes
+.Ix 0 ref source
+.Ix 0 ref target
+.nr pw 12
+.IP .DONTCARE \n(pw
+.Ix 0 def attributes .DONTCARE
+.Ix 0 def .DONTCARE
+If a target is marked with this attribute and PMake can't figure out
+how to create it, it will ignore this fact and assume the file isn't
+really needed or actually exists and PMake just can't find it. This may prove
+wrong, but the error will be noted later on, not when PMake tries to create
+the target so marked. This attribute also prevents PMake from
+attempting to touch the target if it is given the
+.B \-t
+flag.
+.Ix 0 ref flags -t
+.IP .EXEC \n(pw
+.Ix 0 def attributes .EXEC
+.Ix 0 def .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
+.I 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
+.CW COMPILE
+rule, all will soon become clear, grasshopper):
+.DS
+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
+.DE
+.Ix 0 ref .USE
+.Ix 0 ref attributes .USE
+.Ix 0 ref variable local .ALLSRC
+.IP "\&"
+.CW .EXEC
+sources, don't appear in the local variables of targets that depend on
+them (nor are they touched if PMake is given the
+.B \-t
+flag).
+.Ix 0 ref flags -t
+Note that all the rules, not just that for
+.CW system ,
+include
+.CW init
+as a source. This is because none of the other targets can be made
+until
+.CW init
+has been made, thus they depend on it.
+.IP .EXPORT \n(pw
+.Ix 0 def attributes .EXPORT
+.Ix 0 def .EXPORT
+This is used to mark those targets whose creation should be sent to
+another machine if at all possible. This may be used by some
+exportation schemes if the exportation is expensive. You should ask
+your system administrator if it is necessary.
+.IP .EXPORTSAME \n(pw
+.Ix 0 def attributes .EXPORTSAME
+.Ix 0 def .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
+.CW nroff )
+can be performed the same on any architecture (CPU and
+operating system type), while others (e.g. compiling a program with
+.CW cc )
+must be performed on a machine with the same architecture. Not all
+export systems will support this attribute.
+.IP .IGNORE \n(pw
+.Ix 0 def attributes .IGNORE
+.Ix 0 def .IGNORE attribute
+Giving a target the
+.CW .IGNORE
+attribute causes PMake to ignore errors from any of the target's commands, as
+if they all had `\-' before them.
+.IP .INVISIBLE \n(pw
+.Ix 0 def attributes .INVISIBLE
+.Ix 0 def .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
+.DS
+prog1 : $(PROG1OBJS) prog2 MAKEINSTALL
+prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
+.DE
+where
+.CW MAKEINSTALL
+is some complex .USE rule (see below) that depends on the
+.Ix 0 ref .USE
+.CW .ALLSRC
+variable containing the right things. Without the
+.CW .INVISIBLE
+attribute for
+.CW prog2 ,
+the
+.CW 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.
+.IP .JOIN \n(pw
+.Ix 0 def attributes .JOIN
+.Ix 0 def .JOIN
+This is another way to avoid performing some operations in parallel
+while permitting everything else to be done so. Specifically it
+forces the target's shell script to be executed only if one or more of the
+sources was out-of-date. In addition, the target's name,
+in both its
+.CW .TARGET
+variable and all the local variables of any target that depends on it,
+is replaced by the value of its
+.CW .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
+.CW ranlib
+that I mentioned earlier, only this time it's more severe: you
+can't just put the ranlib off to the end since the program
+will need those libraries before it can be re-created. You can do
+something like this:
+.DS
+program : $(OBJS) libraries
+ cc -o $(.TARGET) $(.ALLSRC)
+
+libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
+ ranlib $(.OODATE)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref variable local .OODATE
+.Ix 0 ref .TARGET
+.Ix 0 ref .ALLSRC
+.Ix 0 ref .OODATE
+In this case, PMake will re-create the
+.CW $(OBJS)
+as necessary, along with
+.CW lib1.a ,
+.CW lib2.a ,
+.CW lib3.a
+and
+.CW lib4.a .
+It will then execute
+.CW ranlib
+on any library that was changed and set
+.CW program 's
+.CW .ALLSRC
+variable to contain what's in
+.CW $(OBJS)
+followed by
+.CW "lib1.a lib2.a lib3.a lib4.a" .'' ``
+In case you're wondering, it's called
+.CW .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
+.B \-t
+flag was given.
+.Ix 0 ref flags -t
+.IP .MAKE \n(pw
+.Ix 0 def attributes .MAKE
+.Ix 0 def .MAKE
+The
+.CW .MAKE
+attribute marks its target as being a recursive invocation of PMake.
+This forces PMake to execute the script associated with the target (if
+it's out-of-date) even if you gave the
+.B \-n
+or
+.B \-t
+flag. By doing this, you can start at the top of a system and type
+.DS
+pmake -n
+.DE
+and have it descend the directory tree (if your makefiles are set up
+correctly), printing what it would have executed if you hadn't
+included the
+.B \-n
+flag.
+.IP .NOEXPORT \n(pw
+.Ix 0 def attributes .NOEXPORT
+.Ix 0 def .NOEXPORT attribute
+If possible, PMake will attempt to export the creation of all targets to
+another machine (this depends on how PMake was configured). Sometimes,
+the creation is so simple, it is pointless to send it to another
+machine. If you give the target the
+.CW .NOEXPORT
+attribute, it will be run locally, even if you've given PMake the
+.B "\-L 0"
+flag.
+.IP .NOTMAIN \n(pw
+.Ix 0 def attributes .NOTMAIN
+.Ix 0 def .NOTMAIN
+Normally, if you do not specify a target to make in any other way,
+PMake will take the first target on the first dependency line of a
+makefile as the target to create. That target is known as the ``Main
+Target'' and is labeled as such if you print the dependencies out
+using the
+.B \-p
+flag.
+.Ix 0 ref flags -p
+Giving a target this attribute tells PMake that the target is
+definitely
+.I not
+the Main Target.
+This allows you to place targets in an included makefile and
+have PMake create something else by default.
+.IP .PRECIOUS \n(pw
+.Ix 0 def attributes .PRECIOUS
+.Ix 0 def .PRECIOUS attribute
+When PMake is interrupted (you type control-C at the keyboard), it
+will attempt to clean up after itself by removing any half-made
+targets. If a target has the
+.CW .PRECIOUS
+attribute, however, PMake will leave it alone. An additional side
+effect of the `::' operator is to mark the targets as
+.CW .PRECIOUS .
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.IP .SILENT \n(pw
+.Ix 0 def attributes .SILENT
+.Ix 0 def .SILENT attribute
+Marking a target with this attribute keeps its commands from being
+printed when they're executed, just as if they had an `@' in front of them.
+.IP .USE \n(pw
+.Ix 0 def attributes .USE
+.Ix 0 def .USE
+By giving a target this attribute, you turn it into PMake's equivalent
+of a macro. When the target is used as a source for another target,
+the other target acquires the commands, sources and attributes (except
+.CW .USE )
+of the source.
+If the target already has commands, the
+.CW .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.
+.IP "\&" \n(pw
+The typical .USE rule (as I call them) will use the sources of the
+target to which it is applied (as stored in the
+.CW .ALLSRC
+variable for the target) as its ``arguments,'' if you will.
+For example, you probably noticed that the commands for creating
+.CW lib1.a
+and
+.CW lib2.a
+in the example in section 3.3
+.Rm 5 3.3
+were exactly the same. You can use the
+.CW .USE
+attribute to eliminate the repetition, like so:
+.DS
+lib1.a : $(LIB1OBJS) MAKELIB
+lib2.a : $(LIB2OBJS) MAKELIB
+
+MAKELIB : .USE
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.IP "\&" \n(pw
+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
+.CW MAKELIB ) (
+does not appear in any of the targets's local variables.
+There is no limit to the number of times I could use the
+.CW MAKELIB
+rule. If there were more libraries, I could continue with
+.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' ``
+and so on and so forth.
+.xH 2 Special Targets
+.LP
+As there were in Make, so there are certain targets that have special
+meaning to PMake. When you use one on a dependency line, it is the
+only target that may appear on the left-hand-side of the operator.
+.Ix 0 ref target
+.Ix 0 ref 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:
+.nr pw 10
+.IP .BEGIN \n(pw
+.Ix 0 def .BEGIN
+Any commands attached to this target are executed before anything else
+is done. You can use it for any initialization that needs doing.
+.IP .DEFAULT \n(pw
+.Ix 0 def .DEFAULT
+This is sort of a .USE rule for any target (that was used only as a
+source) that PMake can't figure out any other way to create. It's only
+``sort of'' a .USE rule because only the shell script attached to the
+.CW .DEFAULT
+target is used. The
+.CW .IMPSRC
+variable of a target that inherits
+.CW .DEFAULT 's
+commands is set to the target's own name.
+.Ix 0 ref .IMPSRC
+.Ix 0 ref variable local .IMPSRC
+.IP .END \n(pw
+.Ix 0 def .END
+This serves a function similar to
+.CW .BEGIN ,
+in that commands attached to it are executed once everything has been
+re-created (so long as no errors occurred). It also serves the extra
+function of being a place on which PMake can hang commands you put off
+to the end. Thus the script for this target will be executed before
+any of the commands you save with the ``.\|.\|.''.
+.Ix 0 ref ...
+.IP .EXPORT \n(pw
+The sources for this target are passed to the exportation system compiled
+into PMake. Some systems will use these sources to configure
+themselves. You should ask your system administrator about this.
+.IP .IGNORE \n(pw
+.Ix 0 def .IGNORE target
+.Ix 0 ref .IGNORE attribute
+.Ix 0 ref attributes .IGNORE
+This target marks each of its sources with the
+.CW .IGNORE
+attribute. If you don't give it any sources, then it is like
+giving the
+.B \-i
+flag when you invoke PMake \*- errors are ignored for all commands.
+.Ix 0 ref flags -i
+.IP .INCLUDES \n(pw
+.Ix 0 def .INCLUDES target
+.Ix 0 def variable global .INCLUDES
+.Ix 0 def .INCLUDES variable
+The sources for this target are taken to be suffixes that indicate a
+file that can be included in a program source file.
+The suffix must have already been declared with
+.CW .SUFFIXES
+(see below).
+Any suffix so marked will have the directories on its search path
+(see
+.CW .PATH ,
+below) placed in the
+.CW .INCLUDES
+variable, each preceded by a
+.B \-I
+flag. This variable can then be used as an argument for the compiler
+in the normal fashion. The
+.CW .h
+suffix is already marked in this way in the system makefile.
+.Ix 0 ref makefile system
+E.g. if you have
+.DS
+\&.SUFFIXES : .bitmap
+\&.PATH.bitmap : /usr/local/X/lib/bitmaps
+\&.INCLUDES : .bitmap
+.DE
+PMake will place
+.CW "-I/usr/local/X/lib/bitmaps" '' ``
+in the
+.CW .INCLUDES
+variable and you can then say
+.DS
+cc $(.INCLUDES) -c xprogram.c
+.DE
+(Note: the
+.CW .INCLUDES
+variable is not actually filled in until the entire makefile has been read.)
+.IP .INTERRUPT \n(pw
+.Ix 0 def .INTERRUPT
+When PMake is interrupted,
+it will execute the commands in the script for this target, if it
+exists.
+.IP .LIBS \n(pw
+.Ix 0 def .LIBS target
+.Ix 0 def .LIBS variable
+.Ix 0 def variable global .LIBS
+This does for libraries what
+.CW .INCLUDES
+does for include files, except the flag used is
+.B \-L ,
+as required by those linkers that allow you to tell them where to find
+libraries. The variable used is
+.CW .LIBS .
+Be forewarned that PMake may not have been compiled to do this if the
+linker on your system doesn't accept the
+.B \-L
+flag, though the
+.CW .LIBS
+variable will always be defined once the makefile has been read.
+.IP .MAIN \n(pw
+.Ix 0 def .MAIN
+If you didn't give a target (or targets) to create when you invoked
+PMake, it will take the sources of this target as the targets to
+create.
+.IP .MAKEFLAGS \n(pw
+.Ix 0 def .MAKEFLAGS target
+This target provides a way for you to always specify flags for PMake
+when the makefile is used. The flags are just as they would be typed
+to the shell (except you can't use shell variables unless they're in
+the environment),
+though the
+.B \-f
+and
+.B \-r
+flags have no effect.
+.IP .NULL \n(pw
+.Ix 0 def .NULL
+.Ix 0 ref suffix null
+.Ix 0 ref "null suffix"
+This allows you to specify what suffix PMake should pretend a file has
+if, in fact, it has no known suffix. Only one suffix may be so
+designated. The last source on the dependency line is the suffix that
+is used (you should, however, only give one suffix.\|.\|.).
+.IP .PATH \n(pw
+.Ix 0 def .PATH
+If you give sources for this target, PMake will take them as
+directories in which to search for files it cannot find in the current
+directory. If you give no sources, it will clear out any directories
+added to the search path before. Since the effects of this all get
+very complex, I'll leave it til chapter four to give you a complete
+explanation.
+.IP .PATH\fIsuffix\fP \n(pw
+.Ix 0 ref .PATH
+This does a similar thing to
+.CW .PATH ,
+but it does it only for files with the given suffix. The suffix must
+have been defined already. Look at
+.B "Search Paths"
+(section 4.1)
+.Rm 6 4.1
+for more information.
+.IP .PRECIOUS \n(pw
+.Ix 0 def .PRECIOUS target
+.Ix 0 ref .PRECIOUS attribute
+.Ix 0 ref attributes .PRECIOUS
+Similar to
+.CW .IGNORE ,
+this gives the
+.CW .PRECIOUS
+attribute to each source on the dependency line, unless there are no
+sources, in which case the
+.CW .PRECIOUS
+attribute is given to every target in the file.
+.IP .RECURSIVE \n(pw
+.Ix 0 def .RECURSIVE
+.Ix 0 ref attributes .MAKE
+.Ix 0 ref .MAKE
+This target applies the
+.CW .MAKE
+attribute to all its sources. It does nothing if you don't give it any sources.
+.IP .SHELL \n(pw
+.Ix 0 def .SHELL
+PMake is not constrained to only using the Bourne shell to execute
+the commands you put in the makefile. You can tell it some other shell
+to use with this target. Check out
+.B "A Shell is a Shell is a Shell"
+(section 4.4)
+.Rm 7 4.4
+for more information.
+.IP .SILENT \n(pw
+.Ix 0 def .SILENT target
+.Ix 0 ref .SILENT attribute
+.Ix 0 ref attributes .SILENT
+When you use
+.CW .SILENT
+as a target, it applies the
+.CW .SILENT
+attribute to each of its sources. If there are no sources on the
+dependency line, then it is as if you gave PMake the
+.B \-s
+flag and no commands will be echoed.
+.IP .SUFFIXES \n(pw
+.Ix 0 def .SUFFIXES
+This is used to give new file suffixes for PMake to handle. Each
+source is a suffix PMake should recognize. If you give a
+.CW .SUFFIXES
+dependency line with no sources, PMake will forget about all the
+suffixes it knew (this also nukes the null suffix).
+For those targets that need to have suffixes defined, this is how you do it.
+.LP
+In addition to these targets, a line of the form
+.DS
+\fIattribute\fP : \fIsources\fP
+.DE
+applies the
+.I attribute
+to all the targets listed as
+.I sources .
+.xH 2 Modifying Variable Expansion
+.LP
+.Ix 0 def variable expansion modified
+.Ix 0 ref variable expansion
+.Ix 0 def variable modifiers
+Variables need not always be expanded verbatim. PMake defines several
+modifiers that may be applied to a variable's value before it is
+expanded. You apply a modifier by placing it after the variable name
+with a colon between the two, like so:
+.DS
+${\fIVARIABLE\fP:\fImodifier\fP}
+.DE
+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.
+.LP
+There are seven ways to modify a variable's expansion, most of which
+come from the C shell variable modification characters:
+.RS
+.IP "M\fIpattern\fP"
+.Ix 0 def :M
+.Ix 0 def modifier match
+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
+.I pattern .
+The pattern is a wildcard pattern like that used by the shell, where
+.CW *
+means 0 or more characters of any sort;
+.CW ?
+is any single character;
+.CW [abcd]
+matches any single character that is either `a', `b', `c' or `d'
+(there may be any number of characters between the brackets);
+.CW [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
+.CW <makedepend.mk>
+uses
+.CW "$(CFLAGS:M-[ID]*)" '' ``
+to extract all the
+.CW \-I
+and
+.CW \-D
+flags that would be passed to the C compiler. This allows it to
+properly locate include files and generate the correct dependencies.
+.IP "N\fIpattern\fP"
+.Ix 0 def :N
+.Ix 0 def modifier nomatch
+This is identical to
+.CW :M
+except it substitutes all words that don't match the given pattern.
+.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]"
+.Ix 0 def :S
+.Ix 0 def modifier substitute
+Causes the first occurrence of
+.I search-string
+in the variable to be replaced by
+.I replacement-string ,
+unless the
+.CW g
+flag is given at the end, in which case all occurences of the string
+are replaced. The substitution is performed on each word in the
+variable in turn. If
+.I search-string
+begins with a
+.CW ^ ,
+the string must match starting at the beginning of the word. If
+.I search-string
+ends with a
+.CW $ ,
+the string must match to the end of the word (these two may be
+combined to force an exact match). If a backslash preceeds these two
+characters, however, they lose their special meaning. Variable
+expansion also occurs in the normal fashion inside both the
+.I search-string
+and the
+.I replacement-string ,
+.B except
+that a backslash is used to prevent the expansion of a
+.CW $ ,
+not another dollar sign, as is usual.
+Note that
+.I search-string
+is just a string, not a pattern, so none of the usual
+regular-expression/wildcard characters have any special meaning save
+.CW ^
+and
+.CW $ .
+In the replacement string,
+the
+.CW &
+character is replaced by the
+.I search-string
+unless it is preceded by a backslash.
+You are allowed to use any character except
+colon or exclamation point to separate the two strings. This so-called
+delimiter character may be placed in either string by preceeding it
+with a backslash.
+.IP T
+.Ix 0 def :T
+.Ix 0 def modifier tail
+Replaces each word in the variable expansion by its last
+component (its ``tail''). For example, given
+.DS
+OBJS = ../lib/a.o b /usr/lib/libm.a
+TAILS = $(OBJS:T)
+.DE
+the variable
+.CW TAILS
+would expand to
+.CW "a.o b libm.a" .'' ``
+.IP H
+.Ix 0 def :H
+.Ix 0 def modifier head
+This is similar to
+.CW :T ,
+except that every word is replaced by everything but the tail (the
+``head''). Using the same definition of
+.CW OBJS ,
+the string
+.CW "$(OBJS:H)" '' ``
+would expand to
+.CW "../lib /usr/lib" .'' ``
+Note that the final slash on the heads is removed and
+anything without a head is replaced by the empty string.
+.IP E
+.Ix 0 def :E
+.Ix 0 def modifier extension
+.Ix 0 def modifier suffix
+.Ix 0 ref suffix "variable modifier"
+.CW :E
+replaces each word by its suffix (``extension''). So
+.CW "$(OBJS:E)" '' ``
+would give you
+.CW ".o .a" .'' ``
+.IP R
+.Ix 0 def :R
+.Ix 0 def modifier root
+.Ix 0 def modifier base
+This replaces each word by everything but the suffix (the ``root'' of
+the word).
+.CW "$(OBJS:R)" '' ``
+expands to ``
+.CW "../lib/a b /usr/lib/libm" .''
+.RE
+.LP
+In addition, the System V style of substitution is also supported.
+This looks like:
+.DS
+$(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP)
+.DE
+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.
+.xH 2 More on Debugging
+.xH 2 More Exercises
+.IP (3.1)
+You've got a set programs, each of which is created from its own
+assembly-language source file (suffix
+.CW .asm ).
+Each program can be assembled into two versions, one with error-checking
+code assembled in and one without. You could assemble them into files
+with different suffixes
+.CW .eobj \& (
+and
+.CW .obj ,
+for instance), but your linker only understands files that end in
+.CW .obj .
+To top it all off, the final executables
+.I must
+have the suffix
+.CW .exe .
+How can you still use transformation rules to make your life easier
+(Hint: assume the error-checking versions have
+.CW ec
+tacked onto their prefix)?
+.IP (3.2)
+Assume, for a moment or two, you want to perform a sort of
+``indirection'' by placing the name of a variable into another one,
+then you want to get the value of the first by expanding the second
+somehow. Unfortunately, PMake doesn't allow constructs like
+.DS I
+$($(FOO))
+.DE
+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.
+.xH 1 PMake for Gods
+.LP
+This chapter is devoted to those facilities in PMake 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.
+.LP
+Once more, I assume a greater familiarity with
+.UX
+or Sprite than I did in the previous two chapters.
+.xH 2 Search Paths
+.Rd 6
+.LP
+PMake supports the dispersal of files into multiple directories by
+allowing you to specify places to look for sources with
+.CW .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.
+.LP
+There are two types of search paths
+in PMake: one is used for all types of files (including included
+makefiles) and is specified with a plain
+.CW .PATH
+target (e.g.
+.CW ".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
+.CW .PATH
+with the suffix of the file. For instance
+.DS
+\&.PATH.h : /sprite/lib/include /sprite/att/lib/include
+.DE
+would tell PMake to look in the directories
+.CW /sprite/lib/include
+and
+.CW /sprite/att/lib/include
+for any files whose suffix is
+.CW .h .
+.LP
+The current directory is always consulted first to see if a file
+exists. Only if it cannot be found there are the directories in the
+specific search path, followed by those in the general search path,
+consulted.
+.LP
+A search path is also used when expanding wildcard characters. If the
+pattern has a recognizable suffix on it, the path for that suffix will
+be used for the expansion. Otherwise the default search path is employed.
+.LP
+When a file is found in some directory other than the current one, all
+local variables that would have contained the target's name
+.CW .ALLSRC , (
+and
+.CW .IMPSRC )
+will instead contain the path to the file, as found by PMake.
+Thus if you have a file
+.CW ../lib/mumble.c
+and a makefile
+.DS
+\&.PATH.c : ../lib
+mumble : mumble.c
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+.DE
+the command executed to create
+.CW mumble
+would be
+.CW "cc -o mumble ../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
+.CW .out
+is the null suffix by default and a transformation exists from
+.CW .c
+to
+.CW .out .
+Just thought I'd throw that in.)
+.LP
+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 PMake uses. So if
+you have a large system spread over many directories, it would behoove
+you to follow a naming convention that avoids such conflicts.
+.LP
+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 PMake is running will not be noted when searching
+for implicit sources, nor will they be found when PMake attempts to
+discover when the file was last modified, unless the file was created in the
+current directory. While people have suggested that PMake should read
+the directories each time, my experience suggests that the caching seldom
+causes problems. In addition, not caching the directories slows things
+down enormously because of PMake's attempts to apply transformation
+rules through non-existent files \*- the number of extra file-system
+searches is truly staggering, especially if many files without
+suffixes are used and the null suffix isn't changed from
+.CW .out .
+.xH 2 Archives and Libraries
+.LP
+.UX
+and Sprite allow you to merge files into an archive using the
+.CW ar
+command. Further, if the files are relocatable object files, you can
+run
+.CW 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
+.CW -lm
+rather than
+.CW /usr/lib/libm.a
+and the linker thinks they're out-of-date if you so much as look at
+them.
+.LP
+PMake 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, PMake adds an additional way of deciding if a library
+is out-of-date:
+.IP \(bu 2
+If the table of contents is older than the library, or is missing, the
+library is out-of-date.
+.LP
+A library is any target that looks like
+.CW \-l name'' ``
+or that ends in a suffix that was marked as a library using the
+.CW .LIBS
+target.
+.CW .a
+is so marked in the system makefile.
+.LP
+Members of an archive are specified as
+``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''.
+Thus
+.CW libdix.a(window.o) '' ``'
+specifies the file
+.CW window.o
+in the archive
+.CW libdix.a .
+You may also use wildcards to specify the members of the archive. Just
+remember that most the wildcard characters will only find
+.I existing
+files.
+.LP
+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
+\fIarchive\fP(\fImember\fP) target), special care is
+taken with the transformation rules, as follows:
+.IP \(bu 2
+\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP.
+.IP \(bu 2
+The transformation from the \fImember\fP's suffix to the
+\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target.
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s
+.CW .TARGET
+variable is set to the name of the \fImember\fP if \fImember\fP is
+actually a target, or the path to the member file if \fImember\fP is
+only a source.
+.IP \(bu 2
+The
+.CW .ARCHIVE
+variable for the \fIarchive\fP(\fImember\fP) target is set to the name
+of the \fIarchive\fP.
+.Ix 0 def variable local .ARCHIVE
+.Ix 0 def .ARCHIVE
+.IP \(bu 2
+The
+.CW .MEMBER
+variable is set to the actual string inside the parentheses. In most
+cases, this will be the same as the
+.CW .TARGET
+variable.
+.Ix 0 def variable local .MEMBER
+.Ix 0 def .MEMBER
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s place in the local variables of the
+targets that depend on it is taken by the value of its
+.CW .TARGET
+variable.
+.LP
+Thus, a program library could be created with the following makefile:
+.DS
+\&.o.a :
+ ...
+ rm -f $(.TARGET:T)
+OBJS = obj1.o obj2.o obj3.o
+libprog.a : libprog.a($(OBJS))
+ ar cru $(.TARGET) $(.OODATE)
+ ranlib $(.TARGET)
+.DE
+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
+.CW libprog.a ,
+a table of contents placed in the archive and the newly-archived
+object files to be removed.
+.LP
+All this is used in the
+.CW makelib.mk
+system makefile to create a single library with ease. This makefile
+looks like this:
+.DS
+.SM
+#
+# Rules for making libraries. The object files that make up the library are
+# removed once they are archived.
+#
+# To make several libararies 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
+.DE
+.xH 2 On the Condition...
+.Rd 1
+.LP
+Like the C compiler before it, PMake allows you to configure the makefile,
+based on the current environment, using conditional statements. A
+conditional looks like this:
+.DS
+#if \fIboolean expression\fP
+\fIlines\fP
+#elif \fIanother boolean expression\fP
+\fImore lines\fP
+#else
+\fIstill more lines\fP
+#endif
+.DE
+They may be nested to a maximum depth of 30 and may occur anywhere
+(except in a comment, of course). The
+.CW # '' ``
+must the very first character on the line.
+.LP
+Each
+.I "boolean expression"
+is made up of terms that look like function calls, the standard C
+boolean operators
+.CW && ,
+.CW || ,
+and
+.CW ! ,
+and the standard relational operators
+.CW == ,
+.CW != ,
+.CW > ,
+.CW >= ,
+.CW < ,
+and
+.CW <= ,
+with
+.CW ==
+and
+.CW !=
+being overloaded to allow string comparisons as well.
+.CW &&
+represents logical AND;
+.CW ||
+is logical OR and
+.CW !
+is logical NOT. The arithmetic and string operators take precedence
+over all three of these operators, while NOT takes precedence over
+AND, which takes precedence over OR. This precedence may be
+overridden with parentheses, and an expression may be parenthesized to
+your heart's content. Each term looks like a call on one of four
+functions:
+.nr pw 9
+.Ix 0 def make
+.Ix 0 def conditional make
+.Ix 0 def if make
+.IP make \n(pw
+The syntax is
+.CW make( \fItarget\fP\c
+.CW )
+where
+.I 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
+.CW .MAIN
+target (note that the sources for
+.CW .MAIN
+are only used if no targets were given on the command line).
+.IP defined \n(pw
+.Ix 0 def defined
+.Ix 0 def conditional defined
+.Ix 0 def if defined
+The syntax is
+.CW defined( \fIvariable\fP\c
+.CW )
+and is true if
+.I variable
+is defined. Certain variables are defined in the system makefile that
+identify the system on which PMake is being run.
+.IP exists \n(pw
+.Ix 0 def exists
+.Ix 0 def conditional exists
+.Ix 0 def if exists
+The syntax is
+.CW exists( \fIfile\fP\c
+.CW )
+and is true if the file can be found on the global search path (i.e.
+that defined by
+.CW .PATH
+targets, not by
+.CW .PATH \fIsuffix\fP
+targets).
+.IP empty \n(pw
+.Ix 0 def empty
+.Ix 0 def conditional empty
+.Ix 0 def if 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 context will cause at the very least a warning
+message about a malformed conditional, and at the worst will cause the
+process to stop once it has read the makefile. If you want to check
+for a variable being defined or empty, use the expression
+.CW !defined( \fIvar\fP\c ``
+.CW ") || empty(" \fIvar\fP\c
+.CW ) ''
+as the definition of
+.CW ||
+will prevent the
+.CW empty()
+from being evaluated and causing an error, if the variable is
+undefined). This can be used to see if a variable contains a given
+word, for example:
+.DS
+#if !empty(\fIvar\fP:M\fIword\fP)
+.DE
+.LP
+The arithmetic and string operators may only be used to test the value
+of a variable. The lefthand side must contain the variable expansion,
+while the righthand side contains either a string, enclosed in
+double-quotes, or a number. The standard C numeric conventions (except
+for specifying an octal number) apply to both sides. E.g.
+.DS
+#if $(OS) == 4.3
+
+#if $(MACHINE) == "sun3"
+
+#if $(LOAD_ADDR) < 0xc000
+.DE
+are all valid conditionals. In addition, the numeric value of a
+variable can be tested as a boolean as follows:
+.DS
+#if $(LOAD)
+.DE
+would see if
+.CW LOAD
+contains a non-zero value and
+.DS
+#if !$(LOAD)
+.DE
+would test if
+.CW LOAD
+contains a zero value.
+.LP
+In addition to the bare
+.CW #if ,'' ``
+there are other forms that apply one of the first two functions to each
+term. They are as follows:
+.DS
+ ifdef \fRdefined\fP
+ ifndef \fR!defined\fP
+ ifmake \fRmake\fP
+ ifnmake \fR!make\fP
+.DE
+There are also the ``else if'' forms:
+.CW elif ,
+.CW elifdef ,
+.CW elifndef ,
+.CW elifmake ,
+and
+.CW elifnmake .
+.LP
+For instance, if you wish to create two versions of a program, one of which
+is optimized (the production version) and the other of which is for debugging
+(has symbols for dbx), you have two choices: you can create two
+makefiles, one of which uses the
+.CW \-g
+flag for the compilation, while the other uses the
+.CW \-O
+flag, or you can use another target (call it
+.CW 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
+.CW DEBUG
+(say with
+.CW "pmake -D DEBUG" )
+will also cause the debug version to be made.
+.DS
+#if defined(DEBUG) || make(debug)
+CFLAGS += -g
+#else
+CFLAGS += -O
+#endif
+.DE
+There are, of course, problems with this approach. The most glaring
+annoyance is that if you want to go from making a debug version to
+making a production version, you have to remove all the object files,
+or you will get some optimized and some debug versions in the same
+program. Another annoyance is you have to be careful not to make two
+targets that ``conflict'' because of some conditionals in the
+makefile. For instance
+.DS
+#if make(print)
+FORMATTER = ditroff -Plaser_printer
+#endif
+#if make(draft)
+FORMATTER = nroff -Pdot_matrix_printer
+#endif
+.DE
+would wreak havok if you tried
+.CW "pmake draft print" '' ``
+since you would use the same formatter for each target. As I said,
+this all gets somewhat complicated.
+.xH 2 A Shell is a Shell is a Shell
+.Rd 7
+.LP
+In normal operation, the Bourne Shell (better known as
+.CW sh '') ``
+is used to execute the commands to re-create targets. PMake also allows you
+to specify a different shell for it to use when executing these
+commands. There are several things PMake must know about the shell you
+wish to use. These things are specified as the sources for the
+.CW .SHELL
+.Ix 0 ref .SHELL
+.Ix 0 ref target .SHELL
+target by keyword, as follows:
+.IP "\fBpath=\fP\fIpath\fP"
+PMake needs to know where the shell actually resides, so it can
+execute it. If you specify this and nothing else, PMake 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, PMake knows
+how to use the C Shell too).
+.IP "\fBname=\fP\fIname\fP"
+This is the name by which the shell is to be known. It is a single
+word and, if no other keywords are specified (other than
+.B path ),
+it is the name by which PMake 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
+.CW ".SHELL: name=csh" '' (``
+will do it).
+.IP "\fBquiet=\fP\fIecho-off command\fP"
+As mentioned before, PMake 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
+.B 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.
+.IP "\fBecho=\fP\fIecho-on command\fP"
+The command PMake should give to turn echoing back on again.
+.IP "\fBfilter=\fP\fIprinted echo-off command\fP"
+Many shells will echo the echo-off command when it is given. This
+keyword tells PMake in what format the shell actually prints the
+echo-off command. Wherever PMake 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.
+.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP"
+Unless a target has been marked
+.CW .SILENT ,
+PMake 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 arguments. Otherwise, the two will be concatenated
+(if they are used at the same time, of course).
+.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP"
+Likewise, unless a target is marked
+.CW .IGNORE ,
+PMake 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
+.B echoFlag .
+.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP"
+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.
+.IP "\fBignore=\fP\fIcommand to turn error checking off\fP"
+This is the command PMake 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.
+.IP "\fBhasErrCtl=\fP\fIyes or no\fP"
+This takes a value that is either
+.B yes
+or
+.B no .
+Now you might think that the existence of the
+.B check
+and
+.B ignore
+keywords would be enough to tell PMake if the shell can do
+error-control, but you'd be wrong. If
+.B hasErrCtl
+is
+.B yes ,
+PMake uses the check and ignore commands in a straight-forward manner.
+If this is
+.B no ,
+however, their use is rather different. In this case, the check
+command is used as a template, in which the string
+.B %s
+is replaced by the command that's about to be executed, to produce a
+command for the shell that will echo the command to be executed. The
+ignore command is also used as a template, again with
+.B %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 provide newline(s)
+.CW \en '') (``
+in the appropriate place(s).
+.LP
+The strings that follow these keywords may be enclosed in single or
+double quotes (the quotes will be stripped off) and may contain the
+usual C backslash-characters (\en is newline, \er is return, \eb is
+backspace, \e' escapes a single-quote inside single-quotes, \e"
+escapes a double-quote inside double-quotes). Now for an example.
+.LP
+This is actually the contents of the
+.CW <shx.mk>
+system makefile, and causes PMake 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:
+.DS
+#
+# 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/sh \e
+ quiet="set -" \e
+ echo="set -x" \e
+ filter="+ set - " \e
+ echoFlag=x \e
+ errFlag=e \e
+ hasErrCtl=yes \e
+ check="set -e" \e
+ ignore="set +e"
+.DE
+.LP
+It tells PMake the following:
+.Bp
+The shell is located in the file
+.CW /bin/sh .
+It need not tell PMake that the name of the shell is
+.CW sh
+as PMake can figure that out for itself (it's the last component of
+the path).
+.Bp
+The command to stop echoing is
+.CW "set -" .
+.Bp
+The command to start echoing is
+.CW "set -x" .
+.Bp
+When the echo off command is executed, the shell will print
+.CW "+ set - "
+(The `+' comes from using the
+.CW \-x
+flag (rather than the
+.CW \-v
+flag PMake usually uses)). PMake will remove all occurences of this
+string from the output, so you don't notice extra commands you didn't
+put there.
+.Bp
+The flag the Bourne Shell will take to start echoing in this way is
+the
+.CW \-x
+flag. The Bourne Shell will only take its flag arguments concatenated
+as its first argument, so neither this nor the
+.B errFlag
+specification begins with a \-.
+.Bp
+The flag to use to turn error-checking on from the start is
+.CW \-e .
+.Bp
+The shell can turn error-checking on and off, and the commands to do
+so are
+.CW "set +e"
+and
+.CW "set -e" ,
+respectively.
+.LP
+I should note that this specification is for Bourne Shells that are
+not part of Berkeley
+.UX ,
+as shells from Berkeley don't do error control. You can get a similar
+effect, however, by changing the last three lines to be:
+.DS
+ hasErrCtl=no \e
+ check="echo \e"+ %s\e"\en" \e
+ ignore="sh -c '%s || exit 0\en"
+.DE
+.LP
+This will cause PMake to execute the two commands
+.DS
+echo "+ \fIcmd\fP"
+sh -c '\fIcmd\fP || true'
+.DE
+for each command for which errors are to be ignored. (In case you are
+wondering, the thing for
+.CW ignore
+tells the shell to execute another shell without error checking on and
+always exit 0, since the
+.B ||
+causes the
+.CW "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).
+.xH 2 Compatibility
+.Ix 0 ref compatibility
+.LP
+There are three (well, 3 \(12) levels of backwards-compatibility built
+into PMake. 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 \-B
+(one shell per command) implies
+.B \-V )
+The three levels are described in the following three sections.
+.xH 3 DEFCON 3 \*- Variable Expansion
+.Ix 0 ref compatibility
+.LP
+As noted before, PMake 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
+.CW ${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.
+.LP
+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
+.B \-V
+flag (this can be done with the
+.CW .MAKEFLAGS
+target, if you want).
+.xH 3 DEFCON 2 \*- The Number of the Beast
+.Ix 0 ref compatibility
+.LP
+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 command (which is what the
+.B \-B
+flag forces PMake 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:
+.DS
+install :: .MAKE
+ (cd src; $(.PMAKE) install)
+ (cd lib; $(.PMAKE) install)
+ (cd man; $(.PMAKE) install)
+.DE
+.Ix 0 ref operator double-colon
+.Ix 0 ref variable global .PMAKE
+.Ix 0 ref .PMAKE
+.Ix 0 ref .MAKE
+.Ix 0 ref attribute .MAKE
+This will always execute the three makes (even if the
+.B \-n
+flag was given) because of the combination of the ``::'' operator and
+the
+.CW .MAKE
+attribute. Each command will change to the proper directory to perform
+the install, leaving the main shell in the directory in which it started.
+.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery"
+.Ix 0 ref compatibility
+.LP
+The final category of makefile is the one where every command 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 PMake. If you are the conservative sort, this is
+the compatibility mode for you. It is entered either by giving PMake
+the
+.B \-M
+flag (for Make), or by executing PMake as
+.CW make .'' ``
+In either case, PMake performs things exactly like Make (while still
+supporting most of the nice new features PMake provides). This
+includes:
+.IP \(bu 2
+No parallel execution.
+.IP \(bu 2
+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.
+.IP \(bu 2
+A single Bourne shell is used to execute each command, thus the
+shell's
+.CW $$
+variable is useless, changing directories doesn't work across command
+lines, etc.
+.IP \(bu 2
+If no special characters exist in a command line, PMake will break the
+command into words itself and execute the command directly, without
+executing a shell first. The characters that cause PMake to execute a
+shell are:
+.CW # ,
+.CW = ,
+.CW | ,
+.CW ^ ,
+.CW ( ,
+.CW ) ,
+.CW { ,
+.CW } ,
+.CW ; ,
+.CW & ,
+.CW < ,
+.CW > ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+.CW ] ,
+.CW : ,
+.CW $ ,
+.CW ` ,
+and
+.CW \e .
+You should notice that these are all the characters that are given
+special meaning by the shell (except
+.CW '
+and
+.CW " ,
+which PMake deals with all by its lonesome).
+.IP \(bu 2
+The use of the null suffix is turned off.
+.Ix 0 ref "null suffix"
+.Ix 0 ref suffix null
+.xH 2 The Way Things Work
+.LP
+When PMake reads the makefile, it parses sources and targets into
+nodes in a graph. The graph is directed only in the sense that PMake
+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, respectively), but also a count of the number of its
+children that have already been processed.
+.LP
+The most important thing to know about how PMake uses this graph is
+that the traversal is breadth-first and occurs in two passes.
+.LP
+After PMake has parsed the makefile, it begins with the nodes the user
+has told it to make (either on the command line, or via a
+.CW .MAIN
+target, or by the target being the first in the file not labeled with
+the
+.CW .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
+.CW Suff_FindDeps
+(mentioned earlier) to find any implicit 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
+.CW .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 decremented
+(since the
+.CW .USE
+node has been processed). You will note that this allows a
+.CW .USE
+node to have children that are
+.CW .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
+continues until the first queue is empty.
+.LP
+At this point, all the leaves of the graph are in the examination
+queue. PMake 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 asynchronously. 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 PMake 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, PMake is finished.
+.LP
+Once all targets have been processed, PMake executes the commands
+attached to the
+.CW .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 (PMake keeps a running count of how many
+targets are left to be made), there is a cycle in the graph. PMake does
+a depth-first traversal of the graph to find all the targets that
+weren't made and prints them out one by one.
+.xH 1 Answers to Exercises
+.IP (3.1)
+This is something of a trick question, for which I apologize. The
+trick comes from the UNIX definition of a suffix, which PMake 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
+.CW .ms , (
+.CW .c ,
+etc.). Now, PMake'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
+.Ix 0 def suffix
+solution to this problem goes as follows:
+.DS I
+\&.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)
+.DE
+.IP (3.2)
+The trick to this one lies in the ``:='' variable-assignment operator
+and the ``:S'' variable-expansion modifier.
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable expansion modified
+.Ix 0 ref modifier substitute
+.Ix 0 ref :S
+.Ix 0 ref :=
+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
+.DS I
+$(PTR:S/^/\e$(/:S/$/))
+.DE
+which places
+.CW $( '' ``
+at the front of the variable name and
+.CW ) '' ``
+at the end, thus transforming
+.CW VAR ,'' ``
+for example, into
+.CW $(VAR) ,'' ``
+which is just what we want. Unfortunately (as you know if you've tried
+it), since, as it says in the hint, PMake does no further substitution
+on the result of a modified expansion, that's \fIall\fP you get. The
+solution is to make use of ``:='' to place that string into yet
+another variable, then invoke the other variable directly:
+.DS I
+*PTR := $(PTR:S/^/\e$(/:S/$/)/)
+.DE
+You can then use
+.CW $(*PTR) '' ``
+to your heart's content.
+.de Gp
+.XP
+\&\fB\\$1:\fP
+..
+.xH 1 Glossary of Jargon
+.Gp "attribute"
+A property given to a target that causes PMake to treat it differently.
+.Gp "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.
+.Gp "command-line variable"
+A variable defined in an argument when PMake is first executed.
+Overrides all assignments to the same variable name in the makefile.
+.Gp "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 PMake.
+.Gp "creation script"
+Commands used to create a target. See ``command script.''
+.Gp "dependency"
+The relationship between a source and a target. This comes in three
+flavors, as indicated by the operator 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.
+.Gp "dynamic source"
+This refers to a source that has a local variable invocation in it. It
+allows a single dependency line to specify a different source for each
+target on the line.
+.Gp "global variable"
+Any variable defined in a makefile. Takes precedence over variables
+defined in the environment, but not over command-line or local variables.
+.Gp "input graph"
+What PMake 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.
+.Gp "local variable"
+A variable defined by PMake visible only in a target's shell script.
+There are seven local variables, not all of which are defined for
+every target:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+.CW .TARGET ,
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER
+may be used on dependency lines to create ``dynamic sources.''
+.Gp "makefile"
+A file that describes how a system is built. If you don't know what it
+is after reading this tutorial.\|.\|.\|.
+.Gp "modifier"
+A letter, following a colon, used to alter how a variable is expanded.
+It has no effect on the variable itself.
+.Gp "operator"
+What separates a source from a target (on a dependency line) and specifies
+the relationship between the two. There are three:
+.CW : ', `
+.CW :: ', `
+and
+.CW ! '. `
+.Gp "search path"
+A list of directories in which a file should be sought. PMake'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.
+.Gp "shell"
+A program to which commands are passed in order to create targets.
+.Gp "source"
+Anything to the right of an operator on a dependency line. Targets on
+the dependency line are usually created from the sources.
+.Gp "special target"
+A target that causes PMake to do special things when it's encountered.
+.Gp "suffix"
+The tail end of a file name. Usually begins with a period,
+.CW .c
+or
+.CW .ms ,
+e.g.
+.Gp "target"
+A word to the left of the operator on a dependency line. More
+generally, any file that PMake might create. A file may be (and often
+is) both a target and a source (what it is depends on how PMake is
+looking at it at the time \*- sort of like the wave/particle duality
+of light, you know).
+.Gp "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.
+.Gp "variable expansion"
+The process of substituting the value of a variable for a reference to
+it. Expansion may be altered by means of modifiers.
+.Gp "variable"
+A place in which to store text that may be retrieved later. Also used
+to define the local environment. Conditionals exist that test whether
+a variable is defined or not.
+.bp
+.\" Output table of contents last, with an entry for the index, making
+.\" sure to save and restore the last real page number for the index...
+.nr @n \n(PN+1
+.\" We are not generating an index
+.\" .XS \n(@n
+.\" Index
+.\" .XE
+.nr %% \n%
+.PX
+.nr % \n(%%
diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c
new file mode 100644
index 000000000000..64071dc6c535
--- /dev/null
+++ b/usr.bin/make/arch.c
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)arch.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * arch.c --
+ * Functions to manipulate libraries, archives and their members.
+ *
+ * Once again, cacheing/hashing comes into play in the manipulation
+ * of archives. The first time an archive is referenced, all of its members'
+ * headers are read and hashed and the archive closed again. All hashed
+ * archives are kept on a list which is searched each time an archive member
+ * is referenced.
+ *
+ * The interface to this module is:
+ * Arch_ParseArchive Given an archive specification, return a list
+ * of GNode's, one for each member in the spec.
+ * FAILURE is returned if the specification is
+ * invalid for some reason.
+ *
+ * Arch_Touch Alter the modification time of the archive
+ * member described by the given node to be
+ * the current time.
+ *
+ * Arch_TouchLib Update the modification time of the library
+ * described by the given node. This is special
+ * because it also updates the modification time
+ * of the library's table of contents.
+ *
+ * Arch_MTime Find the modification time of a member of
+ * an archive *in the archive*. The time is also
+ * placed in the member's GNode. Returns the
+ * modification time.
+ *
+ * Arch_MemTime Find the modification time of a member of
+ * an archive. Called when the member doesn't
+ * already exist. Looks in the archive for the
+ * modification time. Returns the modification
+ * time.
+ *
+ * Arch_FindLib Search for a library along a path. The
+ * library name in the GNode should be in
+ * -l<name> format.
+ *
+ * Arch_LibOODate Special function to decide if a library node
+ * is out-of-date.
+ *
+ * Arch_Init Initialize this module.
+ *
+ * Arch_End Cleanup this module.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <ar.h>
+#include <ranlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "config.h"
+
+static Lst archives; /* Lst of archives we've already examined */
+
+typedef struct Arch {
+ char *name; /* Name of archive */
+ Hash_Table members; /* All the members of the archive described
+ * by <name, struct ar_hdr *> key/value pairs */
+} Arch;
+
+static int ArchFindArchive __P((ClientData, ClientData));
+static void ArchFree __P((ClientData));
+static struct ar_hdr *ArchStatMember __P((char *, char *, Boolean));
+static FILE *ArchFindMember __P((char *, char *, struct ar_hdr *, char *));
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFree --
+ * Free memory used by an archive
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ArchFree(ap)
+ ClientData ap;
+{
+ Arch *a = (Arch *) ap;
+ Hash_Search search;
+ Hash_Entry *entry;
+
+ /* Free memory from hash entries */
+ for (entry = Hash_EnumFirst(&a->members, &search);
+ entry != (Hash_Entry *)NULL;
+ entry = Hash_EnumNext(&search))
+ free((Address) Hash_GetValue (entry));
+
+ free(a->name);
+ Hash_DeleteTable(&a->members);
+ free((Address) a);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_ParseArchive --
+ * Parse the archive specification in the given line and find/create
+ * the nodes for the specified archive members, placing their nodes
+ * on the given list.
+ *
+ * Results:
+ * SUCCESS if it was a valid specification. The linePtr is updated
+ * to point to the first non-space after the archive spec. The
+ * nodes for the members are placed on the given list.
+ *
+ * Side Effects:
+ * Some nodes may be created. The given list is extended.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Arch_ParseArchive (linePtr, nodeLst, ctxt)
+ char **linePtr; /* Pointer to start of specification */
+ Lst nodeLst; /* Lst on which to place the nodes */
+ GNode *ctxt; /* Context in which to expand variables */
+{
+ register char *cp; /* Pointer into line */
+ GNode *gn; /* New node */
+ char *libName; /* Library-part of specification */
+ char *memName; /* Member-part of specification */
+ char nameBuf[MAKE_BSIZE]; /* temporary place for node name */
+ char saveChar; /* Ending delimiter of member-name */
+ Boolean subLibName; /* TRUE if libName should have/had
+ * variable substitution performed on it */
+
+ libName = *linePtr;
+
+ subLibName = FALSE;
+
+ for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ Boolean freeIt;
+ char *result;
+
+ result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ subLibName = TRUE;
+ }
+
+ if (freeIt) {
+ free(result);
+ }
+ cp += length-1;
+ }
+ }
+
+ *cp++ = '\0';
+ if (subLibName) {
+ libName = Var_Subst(NULL, libName, ctxt, TRUE);
+ }
+
+
+ for (;;) {
+ /*
+ * First skip to the start of the member's name, mark that
+ * place and skip to the end of it (either white-space or
+ * a close paren).
+ */
+ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
+
+ while (*cp != '\0' && *cp != ')' && isspace (*cp)) {
+ cp++;
+ }
+ memName = cp;
+ while (*cp != '\0' && *cp != ')' && !isspace (*cp)) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ Boolean freeIt;
+ char *result;
+
+ result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ doSubst = TRUE;
+ }
+
+ if (freeIt) {
+ free(result);
+ }
+ cp += length;
+ } else {
+ cp++;
+ }
+ }
+
+ /*
+ * If the specification ends without a closing parenthesis,
+ * chances are there's something wrong (like a missing backslash),
+ * so it's better to return failure than allow such things to happen
+ */
+ if (*cp == '\0') {
+ printf("No closing parenthesis in archive specification\n");
+ return (FAILURE);
+ }
+
+ /*
+ * If we didn't move anywhere, we must be done
+ */
+ if (cp == memName) {
+ break;
+ }
+
+ saveChar = *cp;
+ *cp = '\0';
+
+ /*
+ * XXX: This should be taken care of intelligently by
+ * SuffExpandChildren, both for the archive and the member portions.
+ */
+ /*
+ * If member contains variables, try and substitute for them.
+ * This will slow down archive specs with dynamic sources, of course,
+ * since we'll be (non-)substituting them three times, but them's
+ * the breaks -- we need to do this since SuffExpandChildren calls
+ * us, otherwise we could assume the thing would be taken care of
+ * later.
+ */
+ if (doSubst) {
+ char *buf;
+ char *sacrifice;
+ char *oldMemName = memName;
+
+ memName = Var_Subst(NULL, memName, ctxt, TRUE);
+
+ /*
+ * Now form an archive spec and recurse to deal with nested
+ * variables and multi-word variable values.... The results
+ * are just placed at the end of the nodeLst we're returning.
+ */
+ buf = sacrifice = emalloc(strlen(memName)+strlen(libName)+3);
+
+ sprintf(buf, "%s(%s)", libName, memName);
+
+ if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
+ /*
+ * Must contain dynamic sources, so we can't deal with it now.
+ * Just create an ARCHV node for the thing and let
+ * SuffExpandChildren handle it...
+ */
+ gn = Targ_FindNode(buf, TARG_CREATE);
+
+ if (gn == NILGNODE) {
+ free(buf);
+ return(FAILURE);
+ } else {
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, (ClientData)gn);
+ }
+ } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
+ /*
+ * Error in nested call -- free buffer and return FAILURE
+ * ourselves.
+ */
+ free(buf);
+ return(FAILURE);
+ }
+ /*
+ * Free buffer and continue with our work.
+ */
+ free(buf);
+ } else if (Dir_HasWildcards(memName)) {
+ Lst members = Lst_Init(FALSE);
+ char *member;
+
+ Dir_Expand(memName, dirSearchPath, members);
+ while (!Lst_IsEmpty(members)) {
+ member = (char *)Lst_DeQueue(members);
+
+ sprintf(nameBuf, "%s(%s)", libName, member);
+ free(member);
+ gn = Targ_FindNode (nameBuf, TARG_CREATE);
+ if (gn == NILGNODE) {
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of
+ * the world knows it's an archive member, without having
+ * to constantly check for parentheses, so we type the
+ * thing with the OP_ARCHV bit before we place it on the
+ * end of the provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void) Lst_AtEnd (nodeLst, (ClientData)gn);
+ }
+ }
+ Lst_Destroy(members, NOFREE);
+ } else {
+ sprintf(nameBuf, "%s(%s)", libName, memName);
+ gn = Targ_FindNode (nameBuf, TARG_CREATE);
+ if (gn == NILGNODE) {
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of the
+ * world knows it's an archive member, without having to
+ * constantly check for parentheses, so we type the thing with
+ * the OP_ARCHV bit before we place it on the end of the
+ * provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void) Lst_AtEnd (nodeLst, (ClientData)gn);
+ }
+ }
+ if (doSubst) {
+ free(memName);
+ }
+
+ *cp = saveChar;
+ }
+
+ /*
+ * If substituted libName, free it now, since we need it no longer.
+ */
+ if (subLibName) {
+ free(libName);
+ }
+
+ /*
+ * We promised the pointer would be set up at the next non-space, so
+ * we must advance cp there before setting *linePtr... (note that on
+ * entrance to the loop, cp is guaranteed to point at a ')')
+ */
+ do {
+ cp++;
+ } while (*cp != '\0' && isspace (*cp));
+
+ *linePtr = cp;
+ return (SUCCESS);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindArchive --
+ * See if the given archive is the one we are looking for. Called
+ * From ArchStatMember and ArchFindMember via Lst_Find.
+ *
+ * Results:
+ * 0 if it is, non-zero if it isn't.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchFindArchive (ar, archName)
+ ClientData ar; /* Current list element */
+ ClientData archName; /* Name we want */
+{
+ return (strcmp ((char *) archName, ((Arch *) ar)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchStatMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member.
+ *
+ * Results:
+ * A pointer to the current struct ar_hdr structure for the member. Note
+ * That no position is returned, so this is not useful for touching
+ * archive members. This is mostly because we have no assurances that
+ * The archive will remain constant after we read all the headers, so
+ * there's not much point in remembering the position...
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static struct ar_hdr *
+ArchStatMember (archive, member, hash)
+ char *archive; /* Path to the archive */
+ char *member; /* Name of member. If it is a path, only the
+ * last component is used. */
+ Boolean hash; /* TRUE if archive should be hashed if not
+ * already so. */
+{
+#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1)
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ LstNode ln; /* Lst member containing archive descriptor */
+ Arch *ar; /* Archive descriptor */
+ Hash_Entry *he; /* Entry containing member's description */
+ struct ar_hdr arh; /* archive-member header for reading archive */
+ char memName[MAXPATHLEN+1];
+ /* Current member name while hashing. */
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr (member, '/');
+ if (cp != (char *) NULL) {
+ member = cp + 1;
+ }
+
+ ln = Lst_Find (archives, (ClientData) archive, ArchFindArchive);
+ if (ln != NILLNODE) {
+ ar = (Arch *) Lst_Datum (ln);
+
+ he = Hash_FindEntry (&ar->members, member);
+
+ if (he != (Hash_Entry *) NULL) {
+ return ((struct ar_hdr *) Hash_GetValue (he));
+ } else {
+ /* Try truncated name */
+ char copy[AR_MAX_NAME_LEN+1];
+ int len = strlen (member);
+
+ if (len > AR_MAX_NAME_LEN) {
+ len = AR_MAX_NAME_LEN;
+ strncpy(copy, member, AR_MAX_NAME_LEN);
+ copy[AR_MAX_NAME_LEN] = '\0';
+ }
+ if (he = Hash_FindEntry (&ar->members, copy))
+ return ((struct ar_hdr *) Hash_GetValue (he));
+ return ((struct ar_hdr *) NULL);
+ }
+ }
+
+ if (!hash) {
+ /*
+ * Caller doesn't want the thing hashed, just use ArchFindMember
+ * to read the header for the member out and close down the stream
+ * again. Since the archive is not to be hashed, we assume there's
+ * no need to allocate extra room for the header we're returning,
+ * so just declare it static.
+ */
+ static struct ar_hdr sarh;
+
+ arch = ArchFindMember(archive, member, &sarh, "r");
+
+ if (arch == (FILE *)NULL) {
+ return ((struct ar_hdr *)NULL);
+ } else {
+ fclose(arch);
+ return (&sarh);
+ }
+ }
+
+ /*
+ * We don't have this archive on the list yet, so we want to find out
+ * everything that's in it and cache it so we can get at it quickly.
+ */
+ arch = fopen (archive, "r");
+ if (arch == (FILE *) NULL) {
+ return ((struct ar_hdr *) NULL);
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread (magic, SARMAG, 1, arch) != 1) ||
+ (strncmp (magic, ARMAG, SARMAG) != 0)) {
+ fclose (arch);
+ return ((struct ar_hdr *) NULL);
+ }
+
+ ar = (Arch *)emalloc (sizeof (Arch));
+ ar->name = strdup (archive);
+ Hash_InitTable (&ar->members, -1);
+ memName[AR_MAX_NAME_LEN] = '\0';
+
+ while (fread ((char *)&arh, sizeof (struct ar_hdr), 1, arch) == 1) {
+ if (strncmp ( arh.ar_fmag, ARFMAG, sizeof (arh.ar_fmag)) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ fclose (arch);
+ Hash_DeleteTable (&ar->members);
+ free ((Address)ar);
+ return ((struct ar_hdr *) NULL);
+ } else {
+ (void) strncpy (memName, arh.ar_name, sizeof(arh.ar_name));
+ for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) {
+ continue;
+ }
+ cp[1] = '\0';
+
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit(memName[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
+
+ if (elen > MAXPATHLEN) {
+ fclose (arch);
+ Hash_DeleteTable (&ar->members);
+ free ((Address)ar);
+ return ((struct ar_hdr *) NULL);
+ }
+ if (fread (memName, elen, 1, arch) != 1) {
+ fclose (arch);
+ Hash_DeleteTable (&ar->members);
+ free ((Address)ar);
+ return ((struct ar_hdr *) NULL);
+ }
+ memName[elen] = '\0';
+ fseek (arch, -elen, 1);
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ printf("ArchStat: Extended format entry for %s\n", memName);
+ }
+ }
+#endif
+
+ he = Hash_CreateEntry (&ar->members, memName, (Boolean *)NULL);
+ Hash_SetValue (he, (ClientData)emalloc (sizeof (struct ar_hdr)));
+ memcpy ((Address)Hash_GetValue (he), (Address)&arh,
+ sizeof (struct ar_hdr));
+ }
+ /*
+ * We need to advance the stream's pointer to the start of the
+ * next header. Files are padded with newlines to an even-byte
+ * boundary, so we need to extract the size of the file from the
+ * 'size' field of the header and round it up during the seek.
+ */
+ arh.ar_size[sizeof(arh.ar_size)-1] = '\0';
+ size = (int) strtol(arh.ar_size, NULL, 10);
+ fseek (arch, (size + 1) & ~1, 1);
+ }
+
+ fclose (arch);
+
+ (void) Lst_AtEnd (archives, (ClientData) ar);
+
+ /*
+ * Now that the archive has been read and cached, we can look into
+ * the hash table to find the desired member's header.
+ */
+ he = Hash_FindEntry (&ar->members, member);
+
+ if (he != (Hash_Entry *) NULL) {
+ return ((struct ar_hdr *) Hash_GetValue (he));
+ } else {
+ return ((struct ar_hdr *) NULL);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member. If the archive is to be modified,
+ * the mode should be "r+", if not, it should be "r".
+ *
+ * Results:
+ * An FILE *, opened for reading and writing, positioned at the
+ * start of the member's struct ar_hdr, or NULL if the member was
+ * nonexistent. The current struct ar_hdr for member.
+ *
+ * Side Effects:
+ * The passed struct ar_hdr structure is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+static FILE *
+ArchFindMember (archive, member, arhPtr, mode)
+ char *archive; /* Path to the archive */
+ char *member; /* Name of member. If it is a path, only the
+ * last component is used. */
+ struct ar_hdr *arhPtr; /* Pointer to header structure to be filled in */
+ char *mode; /* The mode for opening the stream */
+{
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ int len, tlen;
+
+ arch = fopen (archive, mode);
+ if (arch == (FILE *) NULL) {
+ return ((FILE *) NULL);
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread (magic, SARMAG, 1, arch) != 1) ||
+ (strncmp (magic, ARMAG, SARMAG) != 0)) {
+ fclose (arch);
+ return ((FILE *) NULL);
+ }
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr (member, '/');
+ if (cp != (char *) NULL) {
+ member = cp + 1;
+ }
+ len = tlen = strlen (member);
+ if (len > sizeof (arhPtr->ar_name)) {
+ tlen = sizeof (arhPtr->ar_name);
+ }
+
+ while (fread ((char *)arhPtr, sizeof (struct ar_hdr), 1, arch) == 1) {
+ if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof (arhPtr->ar_fmag) ) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ fclose (arch);
+ return ((FILE *) NULL);
+ } else if (strncmp (member, arhPtr->ar_name, tlen) == 0) {
+ /*
+ * If the member's name doesn't take up the entire 'name' field,
+ * we have to be careful of matching prefixes. Names are space-
+ * padded to the right, so if the character in 'name' at the end
+ * of the matched string is anything but a space, this isn't the
+ * member we sought.
+ */
+ if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){
+ goto skip;
+ } else {
+ /*
+ * To make life easier, we reposition the file at the start
+ * of the header we just read before we return the stream.
+ * In a more general situation, it might be better to leave
+ * the file at the actual member, rather than its header, but
+ * not here...
+ */
+ fseek (arch, -sizeof(struct ar_hdr), 1);
+ return (arch);
+ }
+ } else
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(arhPtr->ar_name, AR_EFMT1,
+ sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit(arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]);
+ char ename[MAXPATHLEN];
+
+ if (elen > MAXPATHLEN) {
+ fclose (arch);
+ return NULL;
+ }
+ if (fread (ename, elen, 1, arch) != 1) {
+ fclose (arch);
+ return NULL;
+ }
+ ename[elen] = '\0';
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ printf("ArchFind: Extended format entry for %s\n", ename);
+ }
+ if (strncmp(ename, member, len) == 0) {
+ /* Found as extended name */
+ fseek (arch, -sizeof(struct ar_hdr) - elen, 1);
+ return (arch);
+ }
+ fseek (arch, -elen, 1);
+ goto skip;
+ } else
+#endif
+ {
+skip:
+ /*
+ * This isn't the member we're after, so we need to advance the
+ * stream's pointer to the start of the next header. Files are
+ * padded with newlines to an even-byte boundary, so we need to
+ * extract the size of the file from the 'size' field of the
+ * header and round it up during the seek.
+ */
+ arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0';
+ size = (int) strtol(arhPtr->ar_size, NULL, 10);
+ fseek (arch, (size + 1) & ~1, 1);
+ }
+ }
+
+ /*
+ * We've looked everywhere, but the member is not to be found. Close the
+ * archive and return NULL -- an error.
+ */
+ fclose (arch);
+ return ((FILE *) NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Touch --
+ * Touch a member of an archive.
+ *
+ * Results:
+ * The 'time' field of the member's header is updated.
+ *
+ * Side Effects:
+ * The modification time of the entire archive is also changed.
+ * For a library, this could necessitate the re-ranlib'ing of the
+ * whole thing.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Touch (gn)
+ GNode *gn; /* Node of member to touch */
+{
+ FILE * arch; /* Stream open to archive, positioned properly */
+ struct ar_hdr arh; /* Current header describing member */
+ char *p1, *p2;
+
+ arch = ArchFindMember(Var_Value (ARCHIVE, gn, &p1),
+ Var_Value (TARGET, gn, &p2),
+ &arh, "r+");
+ if (p1)
+ free(p1);
+ if (p2)
+ free(p2);
+ sprintf(arh.ar_date, "%-12ld", (long) now);
+
+ if (arch != (FILE *) NULL) {
+ (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
+ fclose (arch);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_TouchLib --
+ * Given a node which represents a library, touch the thing, making
+ * sure that the table of contents also is touched.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Both the modification time of the library and of the RANLIBMAG
+ * member are set to 'now'.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_TouchLib (gn)
+ GNode *gn; /* The node of the library to touch */
+{
+ FILE * arch; /* Stream open to archive */
+ struct ar_hdr arh; /* Header describing table of contents */
+ struct timeval times[2]; /* Times for utimes() call */
+
+ arch = ArchFindMember (gn->path, RANLIBMAG, &arh, "r+");
+ sprintf(arh.ar_date, "%-12ld", (long) now);
+
+ if (arch != (FILE *) NULL) {
+ (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
+ fclose (arch);
+
+ times[0].tv_sec = times[1].tv_sec = now;
+ times[0].tv_usec = times[1].tv_usec = 0;
+ utimes(gn->path, times);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MTime --
+ * Return the modification time of a member of an archive.
+ *
+ * Results:
+ * The modification time (seconds).
+ *
+ * Side Effects:
+ * The mtime field of the given node is filled in with the value
+ * returned by the function.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_MTime (gn)
+ GNode *gn; /* Node describing archive member */
+{
+ struct ar_hdr *arhPtr; /* Header of desired member */
+ int modTime; /* Modification time as an integer */
+ char *p1, *p2;
+
+ arhPtr = ArchStatMember (Var_Value (ARCHIVE, gn, &p1),
+ Var_Value (TARGET, gn, &p2),
+ TRUE);
+ if (p1)
+ free(p1);
+ if (p2)
+ free(p2);
+
+ if (arhPtr != (struct ar_hdr *) NULL) {
+ modTime = (int) strtol(arhPtr->ar_date, NULL, 10);
+ } else {
+ modTime = 0;
+ }
+
+ gn->mtime = modTime;
+ return (modTime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MemMTime --
+ * Given a non-existent archive member's node, get its modification
+ * time from its archived form, if it exists.
+ *
+ * Results:
+ * The modification time.
+ *
+ * Side Effects:
+ * The mtime field is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_MemMTime (gn)
+ GNode *gn;
+{
+ LstNode ln;
+ GNode *pgn;
+ char *nameStart,
+ *nameEnd;
+
+ if (Lst_Open (gn->parents) != SUCCESS) {
+ gn->mtime = 0;
+ return (0);
+ }
+ while ((ln = Lst_Next (gn->parents)) != NILLNODE) {
+ pgn = (GNode *) Lst_Datum (ln);
+
+ if (pgn->type & OP_ARCHV) {
+ /*
+ * If the parent is an archive specification and is being made
+ * and its member's name matches the name of the node we were
+ * given, record the modification time of the parent in the
+ * child. We keep searching its parents in case some other
+ * parent requires this child to exist...
+ */
+ nameStart = strchr (pgn->name, '(') + 1;
+ nameEnd = strchr (nameStart, ')');
+
+ if (pgn->make &&
+ strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
+ gn->mtime = Arch_MTime(pgn);
+ }
+ } else if (pgn->make) {
+ /*
+ * Something which isn't a library depends on the existence of
+ * this target, so it needs to exist.
+ */
+ gn->mtime = 0;
+ break;
+ }
+ }
+
+ Lst_Close (gn->parents);
+
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_FindLib --
+ * Search for a library along the given search path.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The node's 'path' field is set to the found path (including the
+ * actual file name, not -l...). If the system can handle the -L
+ * flag when linking (or we cannot find the library), we assume that
+ * the user has placed the .LIBRARIES variable in the final linking
+ * command (or the linker will know where to find it) and set the
+ * TARGET variable for this node to be the node's name. Otherwise,
+ * we set the TARGET variable to be the full path of the library,
+ * as returned by Dir_FindFile.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_FindLib (gn, path)
+ GNode *gn; /* Node of library to find */
+ Lst path; /* Search path */
+{
+ char *libName; /* file name for archive */
+
+ libName = (char *)emalloc (strlen (gn->name) + 6 - 2);
+ sprintf(libName, "lib%s.a", &gn->name[2]);
+
+ gn->path = Dir_FindFile (libName, path);
+
+ free (libName);
+
+#ifdef LIBRARIES
+ Var_Set (TARGET, gn->name, gn);
+#else
+ Var_Set (TARGET, gn->path == (char *) NULL ? gn->name : gn->path, gn);
+#endif LIBRARIES
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_LibOODate --
+ * Decide if a node with the OP_LIB attribute is out-of-date. Called
+ * from Make_OODate to make its life easier.
+ *
+ * There are several ways for a library to be out-of-date that are
+ * not available to ordinary files. In addition, there are ways
+ * that are open to regular files that are not available to
+ * libraries. A library that is only used as a source is never
+ * considered out-of-date by itself. This does not preclude the
+ * library's modification time from making its parent be out-of-date.
+ * A library will be considered out-of-date for any of these reasons,
+ * given that it is a target on a dependency line somewhere:
+ * Its modification time is less than that of one of its
+ * sources (gn->mtime < gn->cmtime).
+ * Its modification time is greater than the time at which the
+ * make began (i.e. it's been modified in the course
+ * of the make, probably by archiving).
+ * The modification time of one of its sources is greater than
+ * the one of its RANLIBMAG member (i.e. its table of contents
+ * is out-of-date). We don't compare of the archive time
+ * vs. TOC time because they can be too close. In my
+ * opinion we should not bother with the TOC at all since
+ * this is used by 'ar' rules that affect the data contents
+ * of the archive, not by ranlib rules, which affect the
+ * TOC.
+ *
+ * Results:
+ * TRUE if the library is out-of-date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The library will be hashed if it hasn't been already.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Arch_LibOODate (gn)
+ GNode *gn; /* The library's graph node */
+{
+ Boolean oodate;
+
+ if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
+ oodate = FALSE;
+ } else if ((gn->mtime > now) || (gn->mtime < gn->cmtime)) {
+ oodate = TRUE;
+ } else {
+ struct ar_hdr *arhPtr; /* Header for __.SYMDEF */
+ int modTimeTOC; /* The table-of-contents's mod time */
+
+ arhPtr = ArchStatMember (gn->path, RANLIBMAG, FALSE);
+
+ if (arhPtr != (struct ar_hdr *)NULL) {
+ modTimeTOC = (int) strtol(arhPtr->ar_date, NULL, 10);
+
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
+ }
+ oodate = (gn->cmtime > modTimeTOC);
+ } else {
+ /*
+ * A library w/o a table of contents is out-of-date
+ */
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ printf("No t.o.c....");
+ }
+ oodate = TRUE;
+ }
+ }
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Init --
+ * Initialize things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Init ()
+{
+ archives = Lst_Init (FALSE);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_End --
+ * Cleanup things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is freed
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_End ()
+{
+ Lst_Destroy(archives, ArchFree);
+}
diff --git a/usr.bin/make/bit.h b/usr.bin/make/bit.h
new file mode 100644
index 000000000000..53b3745a2515
--- /dev/null
+++ b/usr.bin/make/bit.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)bit.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*
+ * bit.h --
+ *
+ * Definition of macros for setting and clearing bits in an array
+ * of integers.
+ *
+ * It is assumed that "int" is 32 bits wide.
+ */
+
+#ifndef _BIT
+#define _BIT
+
+#include "sprite.h"
+
+#define BIT_NUM_BITS_PER_INT 32
+#define BIT_NUM_BITS_PER_BYTE 8
+
+#define Bit_NumInts(numBits) \
+ (((numBits)+BIT_NUM_BITS_PER_INT -1)/BIT_NUM_BITS_PER_INT)
+
+#define Bit_NumBytes(numBits) \
+ (Bit_NumInts(numBits) * sizeof(int))
+
+#define Bit_Alloc(numBits, bitArrayPtr) \
+ bitArrayPtr = (int *)malloc((unsigned)Bit_NumBytes(numBits)); \
+ Bit_Zero((numBits), (bitArrayPtr))
+
+#define Bit_Free(bitArrayPtr) \
+ free((char *)bitArrayPtr)
+
+#define Bit_Set(numBits, bitArrayPtr) \
+ ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] |= \
+ (1 << ((numBits) % BIT_NUM_BITS_PER_INT)))
+
+#define Bit_IsSet(numBits, bitArrayPtr) \
+ ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] & \
+ (1 << ((numBits) % BIT_NUM_BITS_PER_INT)))
+
+#define Bit_Clear(numBits, bitArrayPtr) \
+ ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] &= \
+ ~(1 << ((numBits) % BIT_NUM_BITS_PER_INT)))
+
+#define Bit_IsClear(numBits, bitArrayPtr) \
+ (!(Bit_IsSet((numBits), (bitArrayPtr))))
+
+#define Bit_Copy(numBits, srcArrayPtr, destArrayPtr) \
+ bcopy((char *)(srcArrayPtr), (char *)(destArrayPtr), \
+ Bit_NumBytes(numBits))
+
+#define Bit_Zero(numBits, bitArrayPtr) \
+ bzero((char *)(bitArrayPtr), Bit_NumBytes(numBits))
+
+extern int Bit_FindFirstSet();
+extern int Bit_FindFirstClear();
+extern Boolean Bit_Intersect();
+extern Boolean Bit_Union();
+extern Boolean Bit_AnySet();
+extern int *Bit_Expand();
+
+#endif /* _BIT */
diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c
new file mode 100644
index 000000000000..68a534db14bc
--- /dev/null
+++ b/usr.bin/make/buf.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)buf.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * buf.c --
+ * Functions for automatically-expanded buffers.
+ */
+
+#include "sprite.h"
+#include "make.h"
+#include "buf.h"
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+/*
+ * BufExpand --
+ * Expand the given buffer to hold the given number of additional
+ * bytes.
+ * Makes sure there's room for an extra NULL byte at the end of the
+ * buffer in case it holds a string.
+ */
+#define BufExpand(bp,nb) \
+ if (bp->left < (nb)+1) {\
+ int newSize = (bp)->size + max((nb)+1,BUF_ADD_INC); \
+ Byte *newBuf = (Byte *) realloc((bp)->buffer, newSize); \
+ \
+ (bp)->inPtr = newBuf + ((bp)->inPtr - (bp)->buffer); \
+ (bp)->outPtr = newBuf + ((bp)->outPtr - (bp)->buffer);\
+ (bp)->buffer = newBuf;\
+ (bp)->size = newSize;\
+ (bp)->left = newSize - ((bp)->inPtr - (bp)->buffer);\
+ }
+
+#define BUF_DEF_SIZE 256 /* Default buffer size */
+#define BUF_ADD_INC 256 /* Expansion increment when Adding */
+#define BUF_UNGET_INC 16 /* Expansion increment when Ungetting */
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_OvAddByte --
+ * Add a single byte to the buffer. left is zero or negative.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The buffer may be expanded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_OvAddByte (bp, byte)
+ register Buffer bp;
+ int byte;
+{
+ int nbytes = 1;
+ bp->left = 0;
+ BufExpand (bp, nbytes);
+
+ *bp->inPtr++ = byte;
+ bp->left--;
+
+ /*
+ * Null-terminate
+ */
+ *bp->inPtr = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_AddBytes --
+ * Add a number of bytes to the buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_AddBytes (bp, numBytes, bytesPtr)
+ register Buffer bp;
+ int numBytes;
+ Byte *bytesPtr;
+{
+
+ BufExpand (bp, numBytes);
+
+ memcpy (bp->inPtr, bytesPtr, numBytes);
+ bp->inPtr += numBytes;
+ bp->left -= numBytes;
+
+ /*
+ * Null-terminate
+ */
+ *bp->inPtr = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_UngetByte --
+ * Place the byte back at the beginning of the buffer.
+ *
+ * Results:
+ * SUCCESS if the byte was added ok. FAILURE if not.
+ *
+ * Side Effects:
+ * The byte is stuffed in the buffer and outPtr is decremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_UngetByte (bp, byte)
+ register Buffer bp;
+ int byte;
+{
+
+ if (bp->outPtr != bp->buffer) {
+ bp->outPtr--;
+ *bp->outPtr = byte;
+ } else if (bp->outPtr == bp->inPtr) {
+ *bp->inPtr = byte;
+ bp->inPtr++;
+ bp->left--;
+ *bp->inPtr = 0;
+ } else {
+ /*
+ * Yech. have to expand the buffer to stuff this thing in.
+ * We use a different expansion constant because people don't
+ * usually push back many bytes when they're doing it a byte at
+ * a time...
+ */
+ int numBytes = bp->inPtr - bp->outPtr;
+ Byte *newBuf;
+
+ newBuf = (Byte *)emalloc(bp->size + BUF_UNGET_INC);
+ memcpy ((char *)(newBuf+BUF_UNGET_INC), (char *)bp->outPtr, numBytes+1);
+ bp->outPtr = newBuf + BUF_UNGET_INC;
+ bp->inPtr = bp->outPtr + numBytes;
+ free ((char *)bp->buffer);
+ bp->buffer = newBuf;
+ bp->size += BUF_UNGET_INC;
+ bp->left = bp->size - (bp->inPtr - bp->buffer);
+ bp->outPtr -= 1;
+ *bp->outPtr = byte;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_UngetBytes --
+ * Push back a series of bytes at the beginning of the buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * outPtr is decremented and the bytes copied into the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_UngetBytes (bp, numBytes, bytesPtr)
+ register Buffer bp;
+ int numBytes;
+ Byte *bytesPtr;
+{
+
+ if (bp->outPtr - bp->buffer >= numBytes) {
+ bp->outPtr -= numBytes;
+ memcpy (bp->outPtr, bytesPtr, numBytes);
+ } else if (bp->outPtr == bp->inPtr) {
+ Buf_AddBytes (bp, numBytes, bytesPtr);
+ } else {
+ int curNumBytes = bp->inPtr - bp->outPtr;
+ Byte *newBuf;
+ int newBytes = max(numBytes,BUF_UNGET_INC);
+
+ newBuf = (Byte *)emalloc (bp->size + newBytes);
+ memcpy((char *)(newBuf+newBytes), (char *)bp->outPtr, curNumBytes+1);
+ bp->outPtr = newBuf + newBytes;
+ bp->inPtr = bp->outPtr + curNumBytes;
+ free ((char *)bp->buffer);
+ bp->buffer = newBuf;
+ bp->size += newBytes;
+ bp->left = bp->size - (bp->inPtr - bp->buffer);
+ bp->outPtr -= numBytes;
+ memcpy ((char *)bp->outPtr, (char *)bytesPtr, numBytes);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetByte --
+ * Return the next byte from the buffer. Actually returns an integer.
+ *
+ * Results:
+ * Returns BUF_ERROR if there's no byte in the buffer, or the byte
+ * itself if there is one.
+ *
+ * Side Effects:
+ * outPtr is incremented and both outPtr and inPtr will be reset if
+ * the buffer is emptied.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_GetByte (bp)
+ register Buffer bp;
+{
+ int res;
+
+ if (bp->inPtr == bp->outPtr) {
+ return (BUF_ERROR);
+ } else {
+ res = (int) *bp->outPtr;
+ bp->outPtr += 1;
+ if (bp->outPtr == bp->inPtr) {
+ bp->outPtr = bp->inPtr = bp->buffer;
+ bp->left = bp->size;
+ *bp->inPtr = 0;
+ }
+ return (res);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetBytes --
+ * Extract a number of bytes from the buffer.
+ *
+ * Results:
+ * The number of bytes gotten.
+ *
+ * Side Effects:
+ * The passed array is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_GetBytes (bp, numBytes, bytesPtr)
+ register Buffer bp;
+ int numBytes;
+ Byte *bytesPtr;
+{
+
+ if (bp->inPtr - bp->outPtr < numBytes) {
+ numBytes = bp->inPtr - bp->outPtr;
+ }
+ memcpy (bytesPtr, bp->outPtr, numBytes);
+ bp->outPtr += numBytes;
+
+ if (bp->outPtr == bp->inPtr) {
+ bp->outPtr = bp->inPtr = bp->buffer;
+ bp->left = bp->size;
+ *bp->inPtr = 0;
+ }
+ return (numBytes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetAll --
+ * Get all the available data at once.
+ *
+ * Results:
+ * A pointer to the data and the number of bytes available.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_GetAll (bp, numBytesPtr)
+ register Buffer bp;
+ int *numBytesPtr;
+{
+
+ if (numBytesPtr != (int *)NULL) {
+ *numBytesPtr = bp->inPtr - bp->outPtr;
+ }
+
+ return (bp->outPtr);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Discard --
+ * Throw away bytes in a buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The bytes are discarded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Discard (bp, numBytes)
+ register Buffer bp;
+ int numBytes;
+{
+
+ if (bp->inPtr - bp->outPtr <= numBytes) {
+ bp->inPtr = bp->outPtr = bp->buffer;
+ bp->left = bp->size;
+ *bp->inPtr = 0;
+ } else {
+ bp->outPtr += numBytes;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Size --
+ * Returns the number of bytes in the given buffer. Doesn't include
+ * the null-terminating byte.
+ *
+ * Results:
+ * The number of bytes.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_Size (buf)
+ Buffer buf;
+{
+ return (buf->inPtr - buf->outPtr);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Init --
+ * Initialize a buffer. If no initial size is given, a reasonable
+ * default is used.
+ *
+ * Results:
+ * A buffer to be given to other functions in this library.
+ *
+ * Side Effects:
+ * The buffer is created, the space allocated and pointers
+ * initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+Buffer
+Buf_Init (size)
+ int size; /* Initial size for the buffer */
+{
+ Buffer bp; /* New Buffer */
+
+ bp = (Buffer)emalloc(sizeof(*bp));
+
+ if (size <= 0) {
+ size = BUF_DEF_SIZE;
+ }
+ bp->left = bp->size = size;
+ bp->buffer = (Byte *)emalloc(size);
+ bp->inPtr = bp->outPtr = bp->buffer;
+ *bp->inPtr = 0;
+
+ return (bp);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Destroy --
+ * Nuke a buffer and all its resources.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The buffer is freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Destroy (buf, freeData)
+ Buffer buf; /* Buffer to destroy */
+ Boolean freeData; /* TRUE if the data should be destroyed as well */
+{
+
+ if (freeData) {
+ free ((char *)buf->buffer);
+ }
+ free ((char *)buf);
+}
diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h
new file mode 100644
index 000000000000..33313b8ff0ea
--- /dev/null
+++ b/usr.bin/make/buf.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)buf.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*-
+ * buf.h --
+ * Header for users of the buf library.
+ */
+
+#ifndef _BUF_H
+#define _BUF_H
+
+#include "sprite.h"
+
+typedef char Byte;
+
+typedef struct Buffer {
+ int size; /* Current size of the buffer */
+ int left; /* Space left (== size - (inPtr - buffer)) */
+ Byte *buffer; /* The buffer itself */
+ Byte *inPtr; /* Place to write to */
+ Byte *outPtr; /* Place to read from */
+} *Buffer;
+
+/* Buf_AddByte adds a single byte to a buffer. */
+#define Buf_AddByte(bp, byte) \
+ (void) (--(bp)->left <= 0 ? Buf_OvAddByte(bp, byte), 1 : \
+ (*(bp)->inPtr++ = (byte), *(bp)->inPtr = 0), 1)
+
+#define BUF_ERROR 256
+
+void Buf_OvAddByte __P((Buffer, int));
+void Buf_AddBytes __P((Buffer, int, Byte *));
+void Buf_UngetByte __P((Buffer, int));
+void Buf_UngetBytes __P((Buffer, int, Byte *));
+int Buf_GetByte __P((Buffer));
+int Buf_GetBytes __P((Buffer, int, Byte *));
+Byte *Buf_GetAll __P((Buffer, int *));
+void Buf_Discard __P((Buffer, int));
+int Buf_Size __P((Buffer));
+Buffer Buf_Init __P((int));
+void Buf_Destroy __P((Buffer, Boolean));
+
+#endif /* _BUF_H */
diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c
new file mode 100644
index 000000000000..9cab63c07345
--- /dev/null
+++ b/usr.bin/make/compat.c
@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)compat.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * compat.c --
+ * The routines in this file implement the full-compatibility
+ * mode of PMake. Most of the special functionality of PMake
+ * is available in this mode. Things not supported:
+ * - different shells.
+ * - friendly variable substitution.
+ *
+ * Interface:
+ * Compat_Run Initialize things for this module and recreate
+ * thems as need creatin'
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+extern int errno;
+
+/*
+ * The following array is used to make a fast determination of which
+ * characters are interpreted specially by the shell. If a command
+ * contains any of these characters, it is executed by the shell, not
+ * directly by us.
+ */
+
+static char meta[256];
+
+static GNode *curTarg = NILGNODE;
+static GNode *ENDNode;
+static void CompatInterrupt __P((int));
+static int CompatRunCommand __P((ClientData, ClientData));
+static int CompatMake __P((ClientData, ClientData));
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatInterrupt --
+ * Interrupt the creation of the current target and remove it if
+ * it ain't precious.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The target is removed and the process exits. If .INTERRUPT exists,
+ * its commands are run first WITH INTERRUPTS IGNORED..
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CompatInterrupt (signo)
+ int signo;
+{
+ GNode *gn;
+
+ if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) {
+ char *p1;
+ char *file = Var_Value (TARGET, curTarg, &p1);
+ struct stat st;
+
+ if (!noExecute && lstat(file, &st) != -1 && !S_ISDIR(st.st_mode) &&
+ unlink(file) != -1) {
+ printf ("*** %s removed\n", file);
+ }
+ if (p1)
+ free(p1);
+
+ /*
+ * Run .INTERRUPT only if hit with interrupt signal
+ */
+ if (signo == SIGINT) {
+ gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+ if (gn != NILGNODE) {
+ Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
+ }
+ }
+
+ }
+ exit (signo);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatRunCommand --
+ * Execute the next command for a target. If the command returns an
+ * error, the node's made field is set to ERROR and creation stops.
+ *
+ * Results:
+ * 0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ * The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CompatRunCommand (cmdp, gnp)
+ ClientData cmdp; /* Command to execute */
+ ClientData gnp; /* Node from which the command came */
+{
+ char *cmdStart; /* Start of expanded command */
+ register char *cp;
+ Boolean silent, /* Don't print command */
+ errCheck; /* Check errors */
+ union wait reason; /* Reason for child's death */
+ int status; /* Description of child's death */
+ int cpid; /* Child actually found */
+ ReturnStatus stat; /* Status of fork */
+ LstNode cmdNode; /* Node where current command is located */
+ char **av; /* Argument vector for thing to exec */
+ int argc; /* Number of arguments in av or 0 if not
+ * dynamically allocated */
+ Boolean local; /* TRUE if command should be executed
+ * locally */
+ char *cmd = (char *) cmdp;
+ GNode *gn = (GNode *) gnp;
+
+ /*
+ * Avoid clobbered variable warnings by forcing the compiler
+ * to ``unregister'' variables
+ */
+#if __GNUC__
+ (void) &av;
+ (void) &errCheck;
+#endif
+ silent = gn->type & OP_SILENT;
+ errCheck = !(gn->type & OP_IGNORE);
+
+ cmdNode = Lst_Member (gn->commands, (ClientData)cmd);
+ cmdStart = Var_Subst (NULL, cmd, gn, FALSE);
+
+ /*
+ * brk_string will return an argv with a NULL in av[1], thus causing
+ * execvp to choke and die horribly. Besides, how can we execute a null
+ * command? In any case, we warn the user that the command expanded to
+ * nothing (is this the right thing to do?).
+ */
+
+ if (*cmdStart == '\0') {
+ free(cmdStart);
+ Error("%s expands to empty string", cmd);
+ return(0);
+ } else {
+ cmd = cmdStart;
+ }
+ Lst_Replace (cmdNode, (ClientData)cmdStart);
+
+ if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
+ (void)Lst_AtEnd(ENDNode->commands, (ClientData)cmdStart);
+ return(0);
+ } else if (strcmp(cmdStart, "...") == 0) {
+ gn->type |= OP_SAVE_CMDS;
+ return(0);
+ }
+
+ while ((*cmd == '@') || (*cmd == '-')) {
+ if (*cmd == '@') {
+ silent = TRUE;
+ } else {
+ errCheck = FALSE;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char)*cmd))
+ cmd++;
+
+ /*
+ * Search for meta characters in the command. If there are no meta
+ * characters, there's no need to execute a shell to execute the
+ * command.
+ */
+ for (cp = cmd; !meta[(unsigned char)*cp]; cp++) {
+ continue;
+ }
+
+ /*
+ * Print the command before echoing if we're not supposed to be quiet for
+ * this one. We also print the command if -n given.
+ */
+ if (!silent || noExecute) {
+ printf ("%s\n", cmd);
+ fflush(stdout);
+ }
+
+ /*
+ * If we're not supposed to execute any commands, this is as far as
+ * we go...
+ */
+ if (noExecute) {
+ return (0);
+ }
+
+ if (*cp != '\0') {
+ /*
+ * If *cp isn't the null character, we hit a "meta" character and
+ * need to pass the command off to the shell. We give the shell the
+ * -e flag as well as -c if it's supposed to exit when it hits an
+ * error.
+ */
+ static char *shargv[4] = { "/bin/sh" };
+
+ shargv[1] = (errCheck ? "-ec" : "-c");
+ shargv[2] = cmd;
+ shargv[3] = (char *)NULL;
+ av = shargv;
+ argc = 0;
+ } else {
+ /*
+ * No meta-characters, so no need to exec a shell. Break the command
+ * into words to form an argument vector we can execute.
+ * brk_string sticks our name in av[0], so we have to
+ * skip over it...
+ */
+ av = brk_string(cmd, &argc, TRUE);
+ av += 1;
+ }
+
+ local = TRUE;
+
+ /*
+ * Fork and execute the single command. If the fork fails, we abort.
+ */
+ cpid = vfork();
+ if (cpid < 0) {
+ Fatal("Could not fork");
+ }
+ if (cpid == 0) {
+ if (local) {
+ execvp(av[0], av);
+ (void) write (2, av[0], strlen (av[0]));
+ (void) write (2, ": not found\n", sizeof(": not found"));
+ } else {
+ (void)execv(av[0], av);
+ }
+ exit(1);
+ }
+ free(cmdStart);
+ Lst_Replace (cmdNode, (ClientData) NULL);
+
+ /*
+ * The child is off and running. Now all we can do is wait...
+ */
+ while (1) {
+
+ while ((stat = wait((int *)&reason)) != cpid) {
+ if (stat == -1 && errno != EINTR) {
+ break;
+ }
+ }
+
+ if (stat > -1) {
+ if (WIFSTOPPED(reason)) {
+ status = reason.w_stopval; /* stopped */
+ } else if (WIFEXITED(reason)) {
+ status = reason.w_retcode; /* exited */
+ if (status != 0) {
+ printf ("*** Error code %d", status);
+ }
+ } else {
+ status = reason.w_termsig; /* signaled */
+ printf ("*** Signal %d", status);
+ }
+
+
+ if (!WIFEXITED(reason) || (status != 0)) {
+ if (errCheck) {
+ gn->made = ERROR;
+ if (keepgoing) {
+ /*
+ * Abort the current target, but let others
+ * continue.
+ */
+ printf (" (continuing)\n");
+ }
+ } else {
+ /*
+ * Continue executing commands for this target.
+ * If we return 0, this will happen...
+ */
+ printf (" (ignored)\n");
+ status = 0;
+ }
+ }
+ break;
+ } else {
+ Fatal ("error in wait: %d", stat);
+ /*NOTREACHED*/
+ }
+ }
+
+ return (status);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatMake --
+ * Make a target.
+ *
+ * Results:
+ * 0
+ *
+ * Side Effects:
+ * If an error is detected and not being ignored, the process exits.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CompatMake (gnp, pgnp)
+ ClientData gnp; /* The node to make */
+ ClientData pgnp; /* Parent to abort if necessary */
+{
+ GNode *gn = (GNode *) gnp;
+ GNode *pgn = (GNode *) pgnp;
+ if (gn->type & OP_USE) {
+ Make_HandleUse(gn, pgn);
+ } else if (gn->made == UNMADE) {
+ /*
+ * First mark ourselves to be made, then apply whatever transformations
+ * the suffix module thinks are necessary. Once that's done, we can
+ * descend and make all our children. If any of them has an error
+ * but the -k flag was given, our 'make' field will be set FALSE again.
+ * This is our signal to not attempt to do anything but abort our
+ * parent as well.
+ */
+ gn->make = TRUE;
+ gn->made = BEINGMADE;
+ Suff_FindDeps (gn);
+ Lst_ForEach (gn->children, CompatMake, (ClientData)gn);
+ if (!gn->make) {
+ gn->made = ABORTED;
+ pgn->make = FALSE;
+ return (0);
+ }
+
+ if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
+ char *p1;
+ Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
+ if (p1)
+ free(p1);
+ }
+
+ /*
+ * All the children were made ok. Now cmtime contains the modification
+ * time of the newest child, we need to find out if we exist and when
+ * we were modified last. The criteria for datedness are defined by the
+ * Make_OODate function.
+ */
+ if (DEBUG(MAKE)) {
+ printf("Examining %s...", gn->name);
+ }
+ if (! Make_OODate(gn)) {
+ gn->made = UPTODATE;
+ if (DEBUG(MAKE)) {
+ printf("up-to-date.\n");
+ }
+ return (0);
+ } else if (DEBUG(MAKE)) {
+ printf("out-of-date.\n");
+ }
+
+ /*
+ * If the user is just seeing if something is out-of-date, exit now
+ * to tell him/her "yes".
+ */
+ if (queryFlag) {
+ exit (-1);
+ }
+
+ /*
+ * We need to be re-made. We also have to make sure we've got a $?
+ * variable. To be nice, we also define the $> variable using
+ * Make_DoAllVar().
+ */
+ Make_DoAllVar(gn);
+
+ /*
+ * Alter our type to tell if errors should be ignored or things
+ * should not be printed so CompatRunCommand knows what to do.
+ */
+ if (Targ_Ignore (gn)) {
+ gn->type |= OP_IGNORE;
+ }
+ if (Targ_Silent (gn)) {
+ gn->type |= OP_SILENT;
+ }
+
+ if (Job_CheckCommands (gn, Fatal)) {
+ /*
+ * Our commands are ok, but we still have to worry about the -t
+ * flag...
+ */
+ if (!touchFlag) {
+ curTarg = gn;
+ Lst_ForEach (gn->commands, CompatRunCommand, (ClientData)gn);
+ curTarg = NILGNODE;
+ } else {
+ Job_Touch (gn, gn->type & OP_SILENT);
+ }
+ } else {
+ gn->made = ERROR;
+ }
+
+ if (gn->made != ERROR) {
+ /*
+ * If the node was made successfully, mark it so, update
+ * its modification time and timestamp all its parents. Note
+ * that for .ZEROTIME targets, the timestamping isn't done.
+ * This is to keep its state from affecting that of its parent.
+ */
+ gn->made = MADE;
+#ifndef RECHECK
+ /*
+ * We can't re-stat the thing, but we can at least take care of
+ * rules where a target depends on a source that actually creates
+ * the target, but only if it has changed, e.g.
+ *
+ * parse.h : parse.o
+ *
+ * parse.o : parse.y
+ * yacc -d parse.y
+ * cc -c y.tab.c
+ * mv y.tab.o parse.o
+ * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+ *
+ * In this case, if the definitions produced by yacc haven't
+ * changed from before, parse.h won't have been updated and
+ * gn->mtime will reflect the current modification time for
+ * parse.h. This is something of a kludge, I admit, but it's a
+ * useful one..
+ *
+ * XXX: People like to use a rule like
+ *
+ * FRC:
+ *
+ * To force things that depend on FRC to be made, so we have to
+ * check for gn->children being empty as well...
+ */
+ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
+ gn->mtime = now;
+ }
+#else
+ /*
+ * This is what Make does and it's actually a good thing, as it
+ * allows rules like
+ *
+ * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+ *
+ * to function as intended. Unfortunately, thanks to the stateless
+ * nature of NFS (and the speed of this program), there are times
+ * when the modification time of a file created on a remote
+ * machine will not be modified before the stat() implied by
+ * the Dir_MTime occurs, thus leading us to believe that the file
+ * is unchanged, wreaking havoc with files that depend on this one.
+ *
+ * I have decided it is better to make too much than to make too
+ * little, so this stuff is commented out unless you're sure it's
+ * ok.
+ * -- ardeb 1/12/88
+ */
+ if (noExecute || Dir_MTime(gn) == 0) {
+ gn->mtime = now;
+ }
+ if (gn->cmtime > gn->mtime)
+ gn->mtime = gn->cmtime;
+ if (DEBUG(MAKE)) {
+ printf("update time: %s\n", Targ_FmtTime(gn->mtime));
+ }
+#endif
+ if (!(gn->type & OP_EXEC)) {
+ pgn->childMade = TRUE;
+ Make_TimeStamp(pgn, gn);
+ }
+ } else if (keepgoing) {
+ pgn->make = FALSE;
+ } else {
+ printf ("\n\nStop.\n");
+ exit (1);
+ }
+ } else if (gn->made == ERROR) {
+ /*
+ * Already had an error when making this beastie. Tell the parent
+ * to abort.
+ */
+ pgn->make = FALSE;
+ } else {
+ if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
+ char *p1;
+ Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
+ if (p1)
+ free(p1);
+ }
+ switch(gn->made) {
+ case BEINGMADE:
+ Error("Graph cycles through %s\n", gn->name);
+ gn->made = ERROR;
+ pgn->make = FALSE;
+ break;
+ case MADE:
+ if ((gn->type & OP_EXEC) == 0) {
+ pgn->childMade = TRUE;
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ case UPTODATE:
+ if ((gn->type & OP_EXEC) == 0) {
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Run --
+ * Initialize this mode and start making.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Compat_Run(targs)
+ Lst targs; /* List of target nodes to re-create */
+{
+ char *cp; /* Pointer to string of shell meta-characters */
+ GNode *gn = NULL;/* Current root target */
+ int errors; /* Number of targets not remade due to errors */
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ signal(SIGINT, CompatInterrupt);
+ }
+ if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+ signal(SIGTERM, CompatInterrupt);
+ }
+ if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+ signal(SIGHUP, CompatInterrupt);
+ }
+ if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
+ signal(SIGQUIT, CompatInterrupt);
+ }
+
+ for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
+ meta[(unsigned char) *cp] = 1;
+ }
+ /*
+ * The null character serves as a sentinel in the string.
+ */
+ meta[0] = 1;
+
+ ENDNode = Targ_FindNode(".END", TARG_CREATE);
+ /*
+ * If the user has defined a .BEGIN target, execute the commands attached
+ * to it.
+ */
+ if (!queryFlag) {
+ gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+ if (gn != NILGNODE) {
+ Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
+ }
+ }
+
+ /*
+ * For each entry in the list of targets to create, call CompatMake on
+ * it to create the thing. CompatMake will leave the 'made' field of gn
+ * in one of several states:
+ * UPTODATE gn was already up-to-date
+ * MADE gn was recreated successfully
+ * ERROR An error occurred while gn was being created
+ * ABORTED gn was not remade because one of its inferiors
+ * could not be made due to errors.
+ */
+ errors = 0;
+ while (!Lst_IsEmpty (targs)) {
+ gn = (GNode *) Lst_DeQueue (targs);
+ CompatMake (gn, gn);
+
+ if (gn->made == UPTODATE) {
+ printf ("`%s' is up to date.\n", gn->name);
+ } else if (gn->made == ABORTED) {
+ printf ("`%s' not remade because of errors.\n", gn->name);
+ errors += 1;
+ }
+ }
+
+ /*
+ * If the user has defined a .END target, run its commands.
+ */
+ if (errors == 0) {
+ Lst_ForEach(ENDNode->commands, CompatRunCommand, (ClientData)gn);
+ }
+}
diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c
new file mode 100644
index 000000000000..6ea0d6a3d476
--- /dev/null
+++ b/usr.bin/make/cond.c
@@ -0,0 +1,1252 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)cond.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * cond.c --
+ * Functions to handle conditionals in a makefile.
+ *
+ * Interface:
+ * Cond_Eval Evaluate the conditional in the passed line.
+ *
+ */
+
+#include <ctype.h>
+#include <math.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+
+/*
+ * The parsing of conditional expressions is based on this grammar:
+ * E -> F || E
+ * E -> F
+ * F -> T && F
+ * F -> T
+ * T -> defined(variable)
+ * T -> make(target)
+ * T -> exists(file)
+ * T -> empty(varspec)
+ * T -> target(name)
+ * T -> symbol
+ * T -> $(varspec) op value
+ * T -> $(varspec) == "string"
+ * T -> $(varspec) != "string"
+ * T -> ( E )
+ * T -> ! T
+ * op -> == | != | > | < | >= | <=
+ *
+ * 'symbol' is some other symbol to which the default function (condDefProc)
+ * is applied.
+ *
+ * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
+ * will return And for '&' and '&&', Or for '|' and '||', Not for '!',
+ * LParen for '(', RParen for ')' and will evaluate the other terminal
+ * symbols, using either the default function or the function given in the
+ * terminal, and return the result as either True or False.
+ *
+ * All Non-Terminal functions (CondE, CondF and CondT) return Err on error.
+ */
+typedef enum {
+ And, Or, Not, True, False, LParen, RParen, EndOfFile, None, Err
+} Token;
+
+/*-
+ * Structures to handle elegantly the different forms of #if's. The
+ * last two fields are stored in condInvert and condDefProc, respectively.
+ */
+static int CondGetArg __P((char **, char **, char *, Boolean));
+static Boolean CondDoDefined __P((int, char *));
+static int CondStrMatch __P((ClientData, ClientData));
+static Boolean CondDoMake __P((int, char *));
+static Boolean CondDoExists __P((int, char *));
+static Boolean CondDoTarget __P((int, char *));
+static Boolean CondCvtArg __P((char *, double *));
+static Token CondToken __P((Boolean));
+static Token CondT __P((Boolean));
+static Token CondF __P((Boolean));
+static Token CondE __P((Boolean));
+
+static struct If {
+ char *form; /* Form of if */
+ int formlen; /* Length of form */
+ Boolean doNot; /* TRUE if default function should be negated */
+ Boolean (*defProc)(); /* Default function to apply */
+} ifs[] = {
+ { "ifdef", 5, FALSE, CondDoDefined },
+ { "ifndef", 6, TRUE, CondDoDefined },
+ { "ifmake", 6, FALSE, CondDoMake },
+ { "ifnmake", 7, TRUE, CondDoMake },
+ { "if", 2, FALSE, CondDoDefined },
+ { (char *)0, 0, FALSE, (Boolean (*)())0 }
+};
+
+static Boolean condInvert; /* Invert the default function */
+static Boolean (*condDefProc)(); /* Default function to apply */
+static char *condExpr; /* The expression to parse */
+static Token condPushBack=None; /* Single push-back token used in
+ * parsing */
+
+#define MAXIF 30 /* greatest depth of #if'ing */
+
+static Boolean condStack[MAXIF]; /* Stack of conditionals's values */
+static int condTop = MAXIF; /* Top-most conditional */
+static int skipIfLevel=0; /* Depth of skipped conditionals */
+static Boolean skipLine = FALSE; /* Whether the parse module is skipping
+ * lines */
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondPushBack --
+ * Push back the most recent token read. We only need one level of
+ * this, so the thing is just stored in 'condPushback'.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * condPushback is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CondPushBack (t)
+ Token t; /* Token to push back into the "stream" */
+{
+ condPushBack = t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetArg --
+ * Find the argument of a built-in function.
+ *
+ * Results:
+ * The length of the argument and the address of the argument.
+ *
+ * Side Effects:
+ * The pointer is set to point to the closing parenthesis of the
+ * function call.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondGetArg (linePtr, argPtr, func, parens)
+ char **linePtr;
+ char **argPtr;
+ char *func;
+ Boolean parens; /* TRUE if arg should be bounded by parens */
+{
+ register char *cp;
+ int argLen;
+ register Buffer buf;
+
+ cp = *linePtr;
+ if (parens) {
+ while (*cp != '(' && *cp != '\0') {
+ cp++;
+ }
+ if (*cp == '(') {
+ cp++;
+ }
+ }
+
+ if (*cp == '\0') {
+ /*
+ * No arguments whatsoever. Because 'make' and 'defined' aren't really
+ * "reserved words", we don't print a message. I think this is better
+ * than hitting the user with a warning message every time s/he uses
+ * the word 'make' or 'defined' at the beginning of a symbol...
+ */
+ *argPtr = cp;
+ return (0);
+ }
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+
+ /*
+ * Create a buffer for the argument and start it out at 16 characters
+ * long. Why 16? Why not?
+ */
+ buf = Buf_Init(16);
+
+ while ((strchr(" \t)&|", *cp) == (char *)NULL) && (*cp != '\0')) {
+ if (*cp == '$') {
+ /*
+ * Parse the variable spec and install it as part of the argument
+ * if it's valid. We tell Var_Parse to complain on an undefined
+ * variable, so we don't do it too. Nor do we return an error,
+ * though perhaps we should...
+ */
+ char *cp2;
+ int len;
+ Boolean doFree;
+
+ cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &doFree);
+
+ Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+ if (doFree) {
+ free(cp2);
+ }
+ cp += len;
+ } else {
+ Buf_AddByte(buf, (Byte)*cp);
+ cp++;
+ }
+ }
+
+ Buf_AddByte(buf, (Byte)'\0');
+ *argPtr = (char *)Buf_GetAll(buf, &argLen);
+ Buf_Destroy(buf, FALSE);
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+ if (parens && *cp != ')') {
+ Parse_Error (PARSE_WARNING, "Missing closing parenthesis for %s()",
+ func);
+ return (0);
+ } else if (parens) {
+ /*
+ * Advance pointer past close parenthesis.
+ */
+ cp++;
+ }
+
+ *linePtr = cp;
+ return (argLen);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoDefined --
+ * Handle the 'defined' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given variable is defined.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoDefined (argLen, arg)
+ int argLen;
+ char *arg;
+{
+ char savec = arg[argLen];
+ char *p1;
+ Boolean result;
+
+ arg[argLen] = '\0';
+ if (Var_Value (arg, VAR_CMD, &p1) != (char *)NULL) {
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+ if (p1)
+ free(p1);
+ arg[argLen] = savec;
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondStrMatch --
+ * Front-end for Str_Match so it returns 0 on match and non-zero
+ * on mismatch. Callback function for CondDoMake via Lst_Find
+ *
+ * Results:
+ * 0 if string matches pattern
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondStrMatch(string, pattern)
+ ClientData string;
+ ClientData pattern;
+{
+ return(!Str_Match((char *) string,(char *) pattern));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoMake --
+ * Handle the 'make' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given target is being made.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoMake (argLen, arg)
+ int argLen;
+ char *arg;
+{
+ char savec = arg[argLen];
+ Boolean result;
+
+ arg[argLen] = '\0';
+ if (Lst_Find (create, (ClientData)arg, CondStrMatch) == NILLNODE) {
+ result = FALSE;
+ } else {
+ result = TRUE;
+ }
+ arg[argLen] = savec;
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoExists --
+ * See if the given file exists.
+ *
+ * Results:
+ * TRUE if the file exists and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoExists (argLen, arg)
+ int argLen;
+ char *arg;
+{
+ char savec = arg[argLen];
+ Boolean result;
+ char *path;
+
+ arg[argLen] = '\0';
+ path = Dir_FindFile(arg, dirSearchPath);
+ if (path != (char *)NULL) {
+ result = TRUE;
+ free(path);
+ } else {
+ result = FALSE;
+ }
+ arg[argLen] = savec;
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoTarget --
+ * See if the given node exists and is an actual target.
+ *
+ * Results:
+ * TRUE if the node exists as a target and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoTarget (argLen, arg)
+ int argLen;
+ char *arg;
+{
+ char savec = arg[argLen];
+ Boolean result;
+ GNode *gn;
+
+ arg[argLen] = '\0';
+ gn = Targ_FindNode(arg, TARG_NOCREATE);
+ if ((gn != NILGNODE) && !OP_NOP(gn->type)) {
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+ arg[argLen] = savec;
+ return (result);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondCvtArg --
+ * Convert the given number into a double. If the number begins
+ * with 0x, it is interpreted as a hexadecimal integer
+ * and converted to a double from there. All other strings just have
+ * strtod called on them.
+ *
+ * Results:
+ * Sets 'value' to double value of string.
+ * Returns true if the string was a valid number, false o.w.
+ *
+ * Side Effects:
+ * Can change 'value' even if string is not a valid number.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondCvtArg(str, value)
+ register char *str;
+ double *value;
+{
+ if ((*str == '0') && (str[1] == 'x')) {
+ register long i;
+
+ for (str += 2, i = 0; *str; str++) {
+ int x;
+ if (isdigit((unsigned char) *str))
+ x = *str - '0';
+ else if (isxdigit((unsigned char) *str))
+ x = 10 + *str - isupper((unsigned char) *str) ? 'A' : 'a';
+ else
+ return FALSE;
+ i = (i << 4) + x;
+ }
+ *value = (double) i;
+ return TRUE;
+ }
+ else {
+ char *eptr;
+ *value = strtod(str, &eptr);
+ return *eptr == '\0';
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondToken --
+ * Return the next token from the input.
+ *
+ * Results:
+ * A Token for the next lexical token in the stream.
+ *
+ * Side Effects:
+ * condPushback will be set back to None if it is used.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondToken(doEval)
+ Boolean doEval;
+{
+ Token t;
+
+ if (condPushBack == None) {
+ while (*condExpr == ' ' || *condExpr == '\t') {
+ condExpr++;
+ }
+ switch (*condExpr) {
+ case '(':
+ t = LParen;
+ condExpr++;
+ break;
+ case ')':
+ t = RParen;
+ condExpr++;
+ break;
+ case '|':
+ if (condExpr[1] == '|') {
+ condExpr++;
+ }
+ condExpr++;
+ t = Or;
+ break;
+ case '&':
+ if (condExpr[1] == '&') {
+ condExpr++;
+ }
+ condExpr++;
+ t = And;
+ break;
+ case '!':
+ t = Not;
+ condExpr++;
+ break;
+ case '\n':
+ case '\0':
+ t = EndOfFile;
+ break;
+ case '$': {
+ char *lhs;
+ char *rhs;
+ char *op;
+ int varSpecLen;
+ Boolean doFree;
+
+ /*
+ * Parse the variable spec and skip over it, saving its
+ * value in lhs.
+ */
+ t = Err;
+ lhs = Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree);
+ if (lhs == var_Error) {
+ /*
+ * Even if !doEval, we still report syntax errors, which
+ * is what getting var_Error back with !doEval means.
+ */
+ return(Err);
+ }
+ condExpr += varSpecLen;
+
+ if (!isspace((unsigned char) *condExpr) &&
+ strchr("!=><", *condExpr) == NULL) {
+ Buffer buf;
+ char *cp;
+
+ buf = Buf_Init(0);
+
+ for (cp = lhs; *cp; cp++)
+ Buf_AddByte(buf, (Byte)*cp);
+
+ if (doFree)
+ free(lhs);
+
+ for (;*condExpr && !isspace((unsigned char) *condExpr);
+ condExpr++)
+ Buf_AddByte(buf, (Byte)*condExpr);
+
+ Buf_AddByte(buf, (Byte)'\0');
+ lhs = (char *)Buf_GetAll(buf, &varSpecLen);
+ Buf_Destroy(buf, FALSE);
+
+ doFree = TRUE;
+ }
+
+ /*
+ * Skip whitespace to get to the operator
+ */
+ while (isspace((unsigned char) *condExpr))
+ condExpr++;
+
+ /*
+ * Make sure the operator is a valid one. If it isn't a
+ * known relational operator, pretend we got a
+ * != 0 comparison.
+ */
+ op = condExpr;
+ switch (*condExpr) {
+ case '!':
+ case '=':
+ case '<':
+ case '>':
+ if (condExpr[1] == '=') {
+ condExpr += 2;
+ } else {
+ condExpr += 1;
+ }
+ break;
+ default:
+ op = "!=";
+ rhs = "0";
+
+ goto do_compare;
+ }
+ while (isspace((unsigned char) *condExpr)) {
+ condExpr++;
+ }
+ if (*condExpr == '\0') {
+ Parse_Error(PARSE_WARNING,
+ "Missing right-hand-side of operator");
+ goto error;
+ }
+ rhs = condExpr;
+do_compare:
+ if (*rhs == '"') {
+ /*
+ * Doing a string comparison. Only allow == and != for
+ * operators.
+ */
+ char *string;
+ char *cp, *cp2;
+ int qt;
+ Buffer buf;
+
+do_string_compare:
+ if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
+ Parse_Error(PARSE_WARNING,
+ "String comparison operator should be either == or !=");
+ goto error;
+ }
+
+ buf = Buf_Init(0);
+ qt = *rhs == '"' ? 1 : 0;
+
+ for (cp = &rhs[qt];
+ ((qt && (*cp != '"')) ||
+ (!qt && strchr(" \t)", *cp) == NULL)) &&
+ (*cp != '\0'); cp++) {
+ if ((*cp == '\\') && (cp[1] != '\0')) {
+ /*
+ * Backslash escapes things -- skip over next
+ * character, if it exists.
+ */
+ cp++;
+ Buf_AddByte(buf, (Byte)*cp);
+ } else if (*cp == '$') {
+ int len;
+ Boolean freeIt;
+
+ cp2 = Var_Parse(cp, VAR_CMD, doEval,&len, &freeIt);
+ if (cp2 != var_Error) {
+ Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+ if (freeIt) {
+ free(cp2);
+ }
+ cp += len - 1;
+ } else {
+ Buf_AddByte(buf, (Byte)*cp);
+ }
+ } else {
+ Buf_AddByte(buf, (Byte)*cp);
+ }
+ }
+
+ Buf_AddByte(buf, (Byte)0);
+
+ string = (char *)Buf_GetAll(buf, (int *)0);
+ Buf_Destroy(buf, FALSE);
+
+ if (DEBUG(COND)) {
+ printf("lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
+ lhs, string, op);
+ }
+ /*
+ * Null-terminate rhs and perform the comparison.
+ * t is set to the result.
+ */
+ if (*op == '=') {
+ t = strcmp(lhs, string) ? False : True;
+ } else {
+ t = strcmp(lhs, string) ? True : False;
+ }
+ free(string);
+ if (rhs == condExpr) {
+ if (!qt && *cp == ')')
+ condExpr = cp;
+ else
+ condExpr = cp + 1;
+ }
+ } else {
+ /*
+ * rhs is either a float or an integer. Convert both the
+ * lhs and the rhs to a double and compare the two.
+ */
+ double left, right;
+ char *string;
+
+ if (!CondCvtArg(lhs, &left))
+ goto do_string_compare;
+ if (*rhs == '$') {
+ int len;
+ Boolean freeIt;
+
+ string = Var_Parse(rhs, VAR_CMD, doEval,&len,&freeIt);
+ if (string == var_Error) {
+ right = 0.0;
+ } else {
+ if (!CondCvtArg(string, &right)) {
+ if (freeIt)
+ free(string);
+ goto do_string_compare;
+ }
+ if (freeIt)
+ free(string);
+ if (rhs == condExpr)
+ condExpr += len;
+ }
+ } else {
+ if (!CondCvtArg(rhs, &right))
+ goto do_string_compare;
+ if (rhs == condExpr) {
+ /*
+ * Skip over the right-hand side
+ */
+ while(!isspace((unsigned char) *condExpr) &&
+ (*condExpr != '\0')) {
+ condExpr++;
+ }
+ }
+ }
+
+ if (DEBUG(COND)) {
+ printf("left = %f, right = %f, op = %.2s\n", left,
+ right, op);
+ }
+ switch(op[0]) {
+ case '!':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto error;
+ }
+ t = (left != right ? True : False);
+ break;
+ case '=':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto error;
+ }
+ t = (left == right ? True : False);
+ break;
+ case '<':
+ if (op[1] == '=') {
+ t = (left <= right ? True : False);
+ } else {
+ t = (left < right ? True : False);
+ }
+ break;
+ case '>':
+ if (op[1] == '=') {
+ t = (left >= right ? True : False);
+ } else {
+ t = (left > right ? True : False);
+ }
+ break;
+ }
+ }
+error:
+ if (doFree)
+ free(lhs);
+ break;
+ }
+ default: {
+ Boolean (*evalProc)();
+ Boolean invert = FALSE;
+ char *arg;
+ int arglen;
+
+ if (strncmp (condExpr, "defined", 7) == 0) {
+ /*
+ * Use CondDoDefined to evaluate the argument and
+ * CondGetArg to extract the argument from the 'function
+ * call'.
+ */
+ evalProc = CondDoDefined;
+ condExpr += 7;
+ arglen = CondGetArg (&condExpr, &arg, "defined", TRUE);
+ if (arglen == 0) {
+ condExpr -= 7;
+ goto use_default;
+ }
+ } else if (strncmp (condExpr, "make", 4) == 0) {
+ /*
+ * Use CondDoMake to evaluate the argument and
+ * CondGetArg to extract the argument from the 'function
+ * call'.
+ */
+ evalProc = CondDoMake;
+ condExpr += 4;
+ arglen = CondGetArg (&condExpr, &arg, "make", TRUE);
+ if (arglen == 0) {
+ condExpr -= 4;
+ goto use_default;
+ }
+ } else if (strncmp (condExpr, "exists", 6) == 0) {
+ /*
+ * Use CondDoExists to evaluate the argument and
+ * CondGetArg to extract the argument from the
+ * 'function call'.
+ */
+ evalProc = CondDoExists;
+ condExpr += 6;
+ arglen = CondGetArg(&condExpr, &arg, "exists", TRUE);
+ if (arglen == 0) {
+ condExpr -= 6;
+ goto use_default;
+ }
+ } else if (strncmp(condExpr, "empty", 5) == 0) {
+ /*
+ * Use Var_Parse to parse the spec in parens and return
+ * True if the resulting string is empty.
+ */
+ int length;
+ Boolean doFree;
+ char *val;
+
+ condExpr += 5;
+
+ for (arglen = 0;
+ condExpr[arglen] != '(' && condExpr[arglen] != '\0';
+ arglen += 1)
+ continue;
+
+ if (condExpr[arglen] != '\0') {
+ val = Var_Parse(&condExpr[arglen - 1], VAR_CMD,
+ doEval, &length, &doFree);
+ if (val == var_Error) {
+ t = Err;
+ } else {
+ /*
+ * A variable is empty when it just contains
+ * spaces... 4/15/92, christos
+ */
+ char *p;
+ for (p = val; *p && isspace((unsigned char)*p); p++)
+ continue;
+ t = (*p == '\0') ? True : False;
+ }
+ if (doFree) {
+ free(val);
+ }
+ /*
+ * Advance condExpr to beyond the closing ). Note that
+ * we subtract one from arglen + length b/c length
+ * is calculated from condExpr[arglen - 1].
+ */
+ condExpr += arglen + length - 1;
+ } else {
+ condExpr -= 5;
+ goto use_default;
+ }
+ break;
+ } else if (strncmp (condExpr, "target", 6) == 0) {
+ /*
+ * Use CondDoTarget to evaluate the argument and
+ * CondGetArg to extract the argument from the
+ * 'function call'.
+ */
+ evalProc = CondDoTarget;
+ condExpr += 6;
+ arglen = CondGetArg(&condExpr, &arg, "target", TRUE);
+ if (arglen == 0) {
+ condExpr -= 6;
+ goto use_default;
+ }
+ } else {
+ /*
+ * The symbol is itself the argument to the default
+ * function. We advance condExpr to the end of the symbol
+ * by hand (the next whitespace, closing paren or
+ * binary operator) and set to invert the evaluation
+ * function if condInvert is TRUE.
+ */
+ use_default:
+ invert = condInvert;
+ evalProc = condDefProc;
+ arglen = CondGetArg(&condExpr, &arg, "", FALSE);
+ }
+
+ /*
+ * Evaluate the argument using the set function. If invert
+ * is TRUE, we invert the sense of the function.
+ */
+ t = (!doEval || (* evalProc) (arglen, arg) ?
+ (invert ? False : True) :
+ (invert ? True : False));
+ free(arg);
+ break;
+ }
+ }
+ } else {
+ t = condPushBack;
+ condPushBack = None;
+ }
+ return (t);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondT --
+ * Parse a single term in the expression. This consists of a terminal
+ * symbol or Not and a terminal symbol (not including the binary
+ * operators):
+ * T -> defined(variable) | make(target) | exists(file) | symbol
+ * T -> ! T | ( E )
+ *
+ * Results:
+ * True, False or Err.
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondT(doEval)
+ Boolean doEval;
+{
+ Token t;
+
+ t = CondToken(doEval);
+
+ if (t == EndOfFile) {
+ /*
+ * If we reached the end of the expression, the expression
+ * is malformed...
+ */
+ t = Err;
+ } else if (t == LParen) {
+ /*
+ * T -> ( E )
+ */
+ t = CondE(doEval);
+ if (t != Err) {
+ if (CondToken(doEval) != RParen) {
+ t = Err;
+ }
+ }
+ } else if (t == Not) {
+ t = CondT(doEval);
+ if (t == True) {
+ t = False;
+ } else if (t == False) {
+ t = True;
+ }
+ }
+ return (t);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondF --
+ * Parse a conjunctive factor (nice name, wot?)
+ * F -> T && F | T
+ *
+ * Results:
+ * True, False or Err
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondF(doEval)
+ Boolean doEval;
+{
+ Token l, o;
+
+ l = CondT(doEval);
+ if (l != Err) {
+ o = CondToken(doEval);
+
+ if (o == And) {
+ /*
+ * F -> T && F
+ *
+ * If T is False, the whole thing will be False, but we have to
+ * parse the r.h.s. anyway (to throw it away).
+ * If T is True, the result is the r.h.s., be it an Err or no.
+ */
+ if (l == True) {
+ l = CondF(doEval);
+ } else {
+ (void) CondF(FALSE);
+ }
+ } else {
+ /*
+ * F -> T
+ */
+ CondPushBack (o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondE --
+ * Main expression production.
+ * E -> F || E | F
+ *
+ * Results:
+ * True, False or Err.
+ *
+ * Side Effects:
+ * Tokens are, of course, consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondE(doEval)
+ Boolean doEval;
+{
+ Token l, o;
+
+ l = CondF(doEval);
+ if (l != Err) {
+ o = CondToken(doEval);
+
+ if (o == Or) {
+ /*
+ * E -> F || E
+ *
+ * A similar thing occurs for ||, except that here we make sure
+ * the l.h.s. is False before we bother to evaluate the r.h.s.
+ * Once again, if l is False, the result is the r.h.s. and once
+ * again if l is True, we parse the r.h.s. to throw it away.
+ */
+ if (l == False) {
+ l = CondE(doEval);
+ } else {
+ (void) CondE(FALSE);
+ }
+ } else {
+ /*
+ * E -> F
+ */
+ CondPushBack (o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_Eval --
+ * Evaluate the conditional in the passed line. The line
+ * looks like this:
+ * #<cond-type> <expr>
+ * where <cond-type> is any of if, ifmake, ifnmake, ifdef,
+ * ifndef, elif, elifmake, elifnmake, elifdef, elifndef
+ * and <expr> consists of &&, ||, !, make(target), defined(variable)
+ * and parenthetical groupings thereof.
+ *
+ * Results:
+ * COND_PARSE if should parse lines after the conditional
+ * COND_SKIP if should skip lines after the conditional
+ * COND_INVALID if not a valid conditional.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_Eval (line)
+ char *line; /* Line to parse */
+{
+ struct If *ifp;
+ Boolean isElse;
+ Boolean value = FALSE;
+ int level; /* Level at which to report errors. */
+
+ level = PARSE_FATAL;
+
+ for (line++; *line == ' ' || *line == '\t'; line++) {
+ continue;
+ }
+
+ /*
+ * Find what type of if we're dealing with. The result is left
+ * in ifp and isElse is set TRUE if it's an elif line.
+ */
+ if (line[0] == 'e' && line[1] == 'l') {
+ line += 2;
+ isElse = TRUE;
+ } else if (strncmp (line, "endif", 5) == 0) {
+ /*
+ * End of a conditional section. If skipIfLevel is non-zero, that
+ * conditional was skipped, so lines following it should also be
+ * skipped. Hence, we return COND_SKIP. Otherwise, the conditional
+ * was read so succeeding lines should be parsed (think about it...)
+ * so we return COND_PARSE, unless this endif isn't paired with
+ * a decent if.
+ */
+ if (skipIfLevel != 0) {
+ skipIfLevel -= 1;
+ return (COND_SKIP);
+ } else {
+ if (condTop == MAXIF) {
+ Parse_Error (level, "if-less endif");
+ return (COND_INVALID);
+ } else {
+ skipLine = FALSE;
+ condTop += 1;
+ return (COND_PARSE);
+ }
+ }
+ } else {
+ isElse = FALSE;
+ }
+
+ /*
+ * Figure out what sort of conditional it is -- what its default
+ * function is, etc. -- by looking in the table of valid "ifs"
+ */
+ for (ifp = ifs; ifp->form != (char *)0; ifp++) {
+ if (strncmp (ifp->form, line, ifp->formlen) == 0) {
+ break;
+ }
+ }
+
+ if (ifp->form == (char *) 0) {
+ /*
+ * Nothing fit. If the first word on the line is actually
+ * "else", it's a valid conditional whose value is the inverse
+ * of the previous if we parsed.
+ */
+ if (isElse && (line[0] == 's') && (line[1] == 'e')) {
+ if (condTop == MAXIF) {
+ Parse_Error (level, "if-less else");
+ return (COND_INVALID);
+ } else if (skipIfLevel == 0) {
+ value = !condStack[condTop];
+ } else {
+ return (COND_SKIP);
+ }
+ } else {
+ /*
+ * Not a valid conditional type. No error...
+ */
+ return (COND_INVALID);
+ }
+ } else {
+ if (isElse) {
+ if (condTop == MAXIF) {
+ Parse_Error (level, "if-less elif");
+ return (COND_INVALID);
+ } else if (skipIfLevel != 0) {
+ /*
+ * If skipping this conditional, just ignore the whole thing.
+ * If we don't, the user might be employing a variable that's
+ * undefined, for which there's an enclosing ifdef that
+ * we're skipping...
+ */
+ return(COND_SKIP);
+ }
+ } else if (skipLine) {
+ /*
+ * Don't even try to evaluate a conditional that's not an else if
+ * we're skipping things...
+ */
+ skipIfLevel += 1;
+ return(COND_SKIP);
+ }
+
+ /*
+ * Initialize file-global variables for parsing
+ */
+ condDefProc = ifp->defProc;
+ condInvert = ifp->doNot;
+
+ line += ifp->formlen;
+
+ while (*line == ' ' || *line == '\t') {
+ line++;
+ }
+
+ condExpr = line;
+ condPushBack = None;
+
+ switch (CondE(TRUE)) {
+ case True:
+ if (CondToken(TRUE) == EndOfFile) {
+ value = TRUE;
+ break;
+ }
+ goto err;
+ /*FALLTHRU*/
+ case False:
+ if (CondToken(TRUE) == EndOfFile) {
+ value = FALSE;
+ break;
+ }
+ /*FALLTHRU*/
+ case Err:
+ err:
+ Parse_Error (level, "Malformed conditional (%s)",
+ line);
+ return (COND_INVALID);
+ default:
+ break;
+ }
+ }
+ if (!isElse) {
+ condTop -= 1;
+ } else if ((skipIfLevel != 0) || condStack[condTop]) {
+ /*
+ * If this is an else-type conditional, it should only take effect
+ * if its corresponding if was evaluated and FALSE. If its if was
+ * TRUE or skipped, we return COND_SKIP (and start skipping in case
+ * we weren't already), leaving the stack unmolested so later elif's
+ * don't screw up...
+ */
+ skipLine = TRUE;
+ return (COND_SKIP);
+ }
+
+ if (condTop < 0) {
+ /*
+ * This is the one case where we can definitely proclaim a fatal
+ * error. If we don't, we're hosed.
+ */
+ Parse_Error (PARSE_FATAL, "Too many nested if's. %d max.", MAXIF);
+ return (COND_INVALID);
+ } else {
+ condStack[condTop] = value;
+ skipLine = !value;
+ return (value ? COND_PARSE : COND_SKIP);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_End --
+ * Make sure everything's clean at the end of a makefile.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Parse_Error will be called if open conditionals are around.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Cond_End()
+{
+ if (condTop != MAXIF) {
+ Parse_Error(PARSE_FATAL, "%d open conditional%s", MAXIF-condTop,
+ MAXIF-condTop == 1 ? "" : "s");
+ }
+ condTop = MAXIF;
+}
diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h
new file mode 100644
index 000000000000..1f70f3768aa3
--- /dev/null
+++ b/usr.bin/make/config.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)config.h 8.2 (Berkeley) 4/28/95
+ */
+
+#define DEFSHELL 1 /* Bourne shell */
+
+/*
+ * DEFMAXJOBS
+ * DEFMAXLOCAL
+ * These control the default concurrency. On no occasion will more
+ * than DEFMAXJOBS targets be created at once (locally or remotely)
+ * DEFMAXLOCAL is the highest number of targets which will be
+ * created on the local machine at once. Note that if you set this
+ * to 0, nothing will ever happen...
+ */
+#define DEFMAXJOBS 4
+#define DEFMAXLOCAL 1
+
+/*
+ * INCLUDES
+ * LIBRARIES
+ * These control the handling of the .INCLUDES and .LIBS variables.
+ * If INCLUDES is defined, the .INCLUDES variable will be filled
+ * from the search paths of those suffixes which are marked by
+ * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS
+ * See suff.c for more details.
+ */
+#define INCLUDES
+#define LIBRARIES
+
+/*
+ * LIBSUFF
+ * Is the suffix used to denote libraries and is used by the Suff module
+ * to find the search path on which to seek any -l<xx> targets.
+ *
+ * RECHECK
+ * If defined, Make_Update will check a target for its current
+ * modification time after it has been re-made, setting it to the
+ * starting time of the make only if the target still doesn't exist.
+ * Unfortunately, under NFS the modification time often doesn't
+ * get updated in time, so a target will appear to not have been
+ * re-made, causing later targets to appear up-to-date. On systems
+ * that don't have this problem, you should defined this. Under
+ * NFS you probably should not, unless you aren't exporting jobs.
+ *
+ * POSIX
+ * If the POSIX standard for Make is to be followed. There are
+ * several areas that I dislike, hence this constant.
+ */
+#define LIBSUFF ".a"
+#define RECHECK
+
+#ifndef RANLIBMAG
+#define RANLIBMAG "__.SYMDEF"
+#endif
+/*#define POSIX*/
diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c
new file mode 100644
index 000000000000..e515fbb731e1
--- /dev/null
+++ b/usr.bin/make/dir.c
@@ -0,0 +1,1274 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)dir.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * dir.c --
+ * Directory searching using wildcards and/or normal names...
+ * Used both for source wildcarding in the Makefile and for finding
+ * implicit sources.
+ *
+ * The interface for this module is:
+ * Dir_Init Initialize the module.
+ *
+ * Dir_End Cleanup the module.
+ *
+ * Dir_HasWildcards Returns TRUE if the name given it needs to
+ * be wildcard-expanded.
+ *
+ * Dir_Expand Given a pattern and a path, return a Lst of names
+ * which match the pattern on the search path.
+ *
+ * Dir_FindFile Searches for a file on a given search path.
+ * If it exists, the entire path is returned.
+ * Otherwise NULL is returned.
+ *
+ * Dir_MTime Return the modification time of a node. The file
+ * is searched for along the default search path.
+ * The path and mtime fields of the node are filled
+ * in.
+ *
+ * Dir_AddDir Add a directory to a search path.
+ *
+ * Dir_MakeFlags Given a search path and a command flag, create
+ * a string with each of the directories in the path
+ * preceded by the command flag and all of them
+ * separated by a space.
+ *
+ * Dir_Destroy Destroy an element of a search path. Frees up all
+ * things that can be freed for the element as long
+ * as the element is no longer referenced by any other
+ * search path.
+ * Dir_ClearPath Resets a search path to the empty list.
+ *
+ * For debugging:
+ * Dir_PrintDirectories Print stats about the directory cache.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+/*
+ * A search path consists of a Lst of Path structures. A Path structure
+ * has in it the name of the directory and a hash table of all the files
+ * in the directory. This is used to cut down on the number of system
+ * calls necessary to find implicit dependents and their like. Since
+ * these searches are made before any actions are taken, we need not
+ * worry about the directory changing due to creation commands. If this
+ * hampers the style of some makefiles, they must be changed.
+ *
+ * A list of all previously-read directories is kept in the
+ * openDirectories Lst. This list is checked first before a directory
+ * is opened.
+ *
+ * The need for the caching of whole directories is brought about by
+ * the multi-level transformation code in suff.c, which tends to search
+ * for far more files than regular make does. In the initial
+ * implementation, the amount of time spent performing "stat" calls was
+ * truly astronomical. The problem with hashing at the start is,
+ * of course, that pmake doesn't then detect changes to these directories
+ * during the course of the make. Three possibilities suggest themselves:
+ *
+ * 1) just use stat to test for a file's existence. As mentioned
+ * above, this is very inefficient due to the number of checks
+ * engendered by the multi-level transformation code.
+ * 2) use readdir() and company to search the directories, keeping
+ * them open between checks. I have tried this and while it
+ * didn't slow down the process too much, it could severely
+ * affect the amount of parallelism available as each directory
+ * open would take another file descriptor out of play for
+ * handling I/O for another job. Given that it is only recently
+ * that UNIX OS's have taken to allowing more than 20 or 32
+ * file descriptors for a process, this doesn't seem acceptable
+ * to me.
+ * 3) record the mtime of the directory in the Path structure and
+ * verify the directory hasn't changed since the contents were
+ * hashed. This will catch the creation or deletion of files,
+ * but not the updating of files. However, since it is the
+ * creation and deletion that is the problem, this could be
+ * a good thing to do. Unfortunately, if the directory (say ".")
+ * were fairly large and changed fairly frequently, the constant
+ * rehashing could seriously degrade performance. It might be
+ * good in such cases to keep track of the number of rehashes
+ * and if the number goes over a (small) limit, resort to using
+ * stat in its place.
+ *
+ * An additional thing to consider is that pmake is used primarily
+ * to create C programs and until recently pcc-based compilers refused
+ * to allow you to specify where the resulting object file should be
+ * placed. This forced all objects to be created in the current
+ * directory. This isn't meant as a full excuse, just an explanation of
+ * some of the reasons for the caching used here.
+ *
+ * One more note: the location of a target's file is only performed
+ * on the downward traversal of the graph and then only for terminal
+ * nodes in the graph. This could be construed as wrong in some cases,
+ * but prevents inadvertent modification of files when the "installed"
+ * directory for a file is provided in the search path.
+ *
+ * Another data structure maintained by this module is an mtime
+ * cache used when the searching of cached directories fails to find
+ * a file. In the past, Dir_FindFile would simply perform an access()
+ * call in such a case to determine if the file could be found using
+ * just the name given. When this hit, however, all that was gained
+ * was the knowledge that the file existed. Given that an access() is
+ * essentially a stat() without the copyout() call, and that the same
+ * filesystem overhead would have to be incurred in Dir_MTime, it made
+ * sense to replace the access() with a stat() and record the mtime
+ * in a cache for when Dir_MTime was actually called.
+ */
+
+Lst dirSearchPath; /* main search path */
+
+static Lst openDirectories; /* the list of all open directories */
+
+/*
+ * Variables for gathering statistics on the efficiency of the hashing
+ * mechanism.
+ */
+static int hits, /* Found in directory cache */
+ misses, /* Sad, but not evil misses */
+ nearmisses, /* Found under search path */
+ bigmisses; /* Sought by itself */
+
+static Path *dot; /* contents of current directory */
+static Hash_Table mtimes; /* Results of doing a last-resort stat in
+ * Dir_FindFile -- if we have to go to the
+ * system to find the file, we might as well
+ * have its mtime on record. XXX: If this is done
+ * way early, there's a chance other rules will
+ * have already updated the file, in which case
+ * we'll update it again. Generally, there won't
+ * be two rules to update a single file, so this
+ * should be ok, but... */
+
+
+static int DirFindName __P((ClientData, ClientData));
+static int DirMatchFiles __P((char *, Path *, Lst));
+static void DirExpandCurly __P((char *, char *, Lst, Lst));
+static void DirExpandInt __P((char *, Lst, Lst));
+static int DirPrintWord __P((ClientData, ClientData));
+static int DirPrintDir __P((ClientData, ClientData));
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Init --
+ * initialize things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Init ()
+{
+ dirSearchPath = Lst_Init (FALSE);
+ openDirectories = Lst_Init (FALSE);
+ Hash_InitTable(&mtimes, 0);
+
+ /*
+ * Since the Path structure is placed on both openDirectories and
+ * the path we give Dir_AddDir (which in this case is openDirectories),
+ * we need to remove "." from openDirectories and what better time to
+ * do it than when we have to fetch the thing anyway?
+ */
+ Dir_AddDir (openDirectories, ".");
+ dot = (Path *) Lst_DeQueue (openDirectories);
+
+ /*
+ * We always need to have dot around, so we increment its reference count
+ * to make sure it's not destroyed.
+ */
+ dot->refCount += 1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_End --
+ * cleanup things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_End()
+{
+ dot->refCount -= 1;
+ Dir_Destroy((ClientData) dot);
+ Dir_ClearPath(dirSearchPath);
+ Lst_Destroy(dirSearchPath, NOFREE);
+ Dir_ClearPath(openDirectories);
+ Lst_Destroy(openDirectories, NOFREE);
+ Hash_DeleteTable(&mtimes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindName --
+ * See if the Path structure describes the same directory as the
+ * given one by comparing their names. Called from Dir_AddDir via
+ * Lst_Find when searching the list of open directories.
+ *
+ * Results:
+ * 0 if it is the same. Non-zero otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+DirFindName (p, dname)
+ ClientData p; /* Current name */
+ ClientData dname; /* Desired name */
+{
+ return (strcmp (((Path *)p)->name, (char *) dname));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_HasWildcards --
+ * see if the given name has any wildcard characters in it
+ *
+ * Results:
+ * returns TRUE if the word should be expanded, FALSE otherwise
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Dir_HasWildcards (name)
+ char *name; /* name to check */
+{
+ register char *cp;
+
+ for (cp = name; *cp; cp++) {
+ switch(*cp) {
+ case '{':
+ case '[':
+ case '?':
+ case '*':
+ return (TRUE);
+ }
+ }
+ return (FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirMatchFiles --
+ * Given a pattern and a Path structure, see if any files
+ * match the pattern and add their names to the 'expansions' list if
+ * any do. This is incomplete -- it doesn't take care of patterns like
+ * src / *src / *.c properly (just *.c on any of the directories), but it
+ * will do for now.
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * File names are added to the expansions lst. The directory will be
+ * fully hashed when this is done.
+ *-----------------------------------------------------------------------
+ */
+static int
+DirMatchFiles (pattern, p, expansions)
+ char *pattern; /* Pattern to look for */
+ Path *p; /* Directory to search */
+ Lst expansions; /* Place to store the results */
+{
+ Hash_Search search; /* Index into the directory's table */
+ Hash_Entry *entry; /* Current entry in the table */
+ Boolean isDot; /* TRUE if the directory being searched is . */
+
+ isDot = (*p->name == '.' && p->name[1] == '\0');
+
+ for (entry = Hash_EnumFirst(&p->files, &search);
+ entry != (Hash_Entry *)NULL;
+ entry = Hash_EnumNext(&search))
+ {
+ /*
+ * See if the file matches the given pattern. Note we follow the UNIX
+ * convention that dot files will only be found if the pattern
+ * begins with a dot (note also that as a side effect of the hashing
+ * scheme, .* won't match . or .. since they aren't hashed).
+ */
+ if (Str_Match(entry->name, pattern) &&
+ ((entry->name[0] != '.') ||
+ (pattern[0] == '.')))
+ {
+ (void)Lst_AtEnd(expansions,
+ (isDot ? strdup(entry->name) :
+ str_concat(p->name, entry->name,
+ STR_ADDSLASH)));
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandCurly --
+ * Expand curly braces like the C shell. Does this recursively.
+ * Note the special case: if after the piece of the curly brace is
+ * done there are no wildcard characters in the result, the result is
+ * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is filled with the expansions...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandCurly(word, brace, path, expansions)
+ char *word; /* Entire word to expand */
+ char *brace; /* First curly brace in it */
+ Lst path; /* Search path to use */
+ Lst expansions; /* Place to store the expansions */
+{
+ char *end; /* Character after the closing brace */
+ char *cp; /* Current position in brace clause */
+ char *start; /* Start of current piece of brace clause */
+ int bracelevel; /* Number of braces we've seen. If we see a
+ * right brace when this is 0, we've hit the
+ * end of the clause. */
+ char *file; /* Current expansion */
+ int otherLen; /* The length of the other pieces of the
+ * expansion (chars before and after the
+ * clause in 'word') */
+ char *cp2; /* Pointer for checking for wildcards in
+ * expansion before calling Dir_Expand */
+
+ start = brace+1;
+
+ /*
+ * Find the end of the brace clause first, being wary of nested brace
+ * clauses.
+ */
+ for (end = start, bracelevel = 0; *end != '\0'; end++) {
+ if (*end == '{') {
+ bracelevel++;
+ } else if ((*end == '}') && (bracelevel-- == 0)) {
+ break;
+ }
+ }
+ if (*end == '\0') {
+ Error("Unterminated {} clause \"%s\"", start);
+ return;
+ } else {
+ end++;
+ }
+ otherLen = brace - word + strlen(end);
+
+ for (cp = start; cp < end; cp++) {
+ /*
+ * Find the end of this piece of the clause.
+ */
+ bracelevel = 0;
+ while (*cp != ',') {
+ if (*cp == '{') {
+ bracelevel++;
+ } else if ((*cp == '}') && (bracelevel-- <= 0)) {
+ break;
+ }
+ cp++;
+ }
+ /*
+ * Allocate room for the combination and install the three pieces.
+ */
+ file = emalloc(otherLen + cp - start + 1);
+ if (brace != word) {
+ strncpy(file, word, brace-word);
+ }
+ if (cp != start) {
+ strncpy(&file[brace-word], start, cp-start);
+ }
+ strcpy(&file[(brace-word)+(cp-start)], end);
+
+ /*
+ * See if the result has any wildcards in it. If we find one, call
+ * Dir_Expand right away, telling it to place the result on our list
+ * of expansions.
+ */
+ for (cp2 = file; *cp2 != '\0'; cp2++) {
+ switch(*cp2) {
+ case '*':
+ case '?':
+ case '{':
+ case '[':
+ Dir_Expand(file, path, expansions);
+ goto next;
+ }
+ }
+ if (*cp2 == '\0') {
+ /*
+ * Hit the end w/o finding any wildcards, so stick the expansion
+ * on the end of the list.
+ */
+ (void)Lst_AtEnd(expansions, file);
+ } else {
+ next:
+ free(file);
+ }
+ start = cp+1;
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandInt --
+ * Internal expand routine. Passes through the directories in the
+ * path one by one, calling DirMatchFiles for each. NOTE: This still
+ * doesn't handle patterns in directories...
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Things are added to the expansions list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandInt(word, path, expansions)
+ char *word; /* Word to expand */
+ Lst path; /* Path on which to look */
+ Lst expansions; /* Place to store the result */
+{
+ LstNode ln; /* Current node */
+ Path *p; /* Directory in the node */
+
+ if (Lst_Open(path) == SUCCESS) {
+ while ((ln = Lst_Next(path)) != NILLNODE) {
+ p = (Path *)Lst_Datum(ln);
+ DirMatchFiles(word, p, expansions);
+ }
+ Lst_Close(path);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirPrintWord --
+ * Print a word in the list of expansions. Callback for Dir_Expand
+ * when DEBUG(DIR), via Lst_ForEach.
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * The passed word is printed, followed by a space.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+DirPrintWord(word, dummy)
+ ClientData word;
+ ClientData dummy;
+{
+ printf("%s ", (char *) word);
+
+ return(dummy ? 0 : 0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Expand --
+ * Expand the given word into a list of words by globbing it looking
+ * in the directories on the given search path.
+ *
+ * Results:
+ * A list of words consisting of the files which exist along the search
+ * path matching the given pattern.
+ *
+ * Side Effects:
+ * Directories may be opened. Who knows?
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Expand (word, path, expansions)
+ char *word; /* the word to expand */
+ Lst path; /* the list of directories in which to find
+ * the resulting files */
+ Lst expansions; /* the list on which to place the results */
+{
+ char *cp;
+
+ if (DEBUG(DIR)) {
+ printf("expanding \"%s\"...", word);
+ }
+
+ cp = strchr(word, '{');
+ if (cp) {
+ DirExpandCurly(word, cp, path, expansions);
+ } else {
+ cp = strchr(word, '/');
+ if (cp) {
+ /*
+ * The thing has a directory component -- find the first wildcard
+ * in the string.
+ */
+ for (cp = word; *cp; cp++) {
+ if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') {
+ break;
+ }
+ }
+ if (*cp == '{') {
+ /*
+ * This one will be fun.
+ */
+ DirExpandCurly(word, cp, path, expansions);
+ return;
+ } else if (*cp != '\0') {
+ /*
+ * Back up to the start of the component
+ */
+ char *dirpath;
+
+ while (cp > word && *cp != '/') {
+ cp--;
+ }
+ if (cp != word) {
+ char sc;
+ /*
+ * If the glob isn't in the first component, try and find
+ * all the components up to the one with a wildcard.
+ */
+ sc = cp[1];
+ cp[1] = '\0';
+ dirpath = Dir_FindFile(word, path);
+ cp[1] = sc;
+ /*
+ * dirpath is null if can't find the leading component
+ * XXX: Dir_FindFile won't find internal components.
+ * i.e. if the path contains ../Etc/Object and we're
+ * looking for Etc, it won't be found. Ah well.
+ * Probably not important.
+ */
+ if (dirpath != (char *)NULL) {
+ char *dp = &dirpath[strlen(dirpath) - 1];
+ if (*dp == '/')
+ *dp = '\0';
+ path = Lst_Init(FALSE);
+ Dir_AddDir(path, dirpath);
+ DirExpandInt(cp+1, path, expansions);
+ Lst_Destroy(path, NOFREE);
+ }
+ } else {
+ /*
+ * Start the search from the local directory
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * Return the file -- this should never happen.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * First the files in dot
+ */
+ DirMatchFiles(word, dot, expansions);
+
+ /*
+ * Then the files in every other directory on the path.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ }
+ if (DEBUG(DIR)) {
+ Lst_ForEach(expansions, DirPrintWord, (ClientData) 0);
+ fputc('\n', stdout);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindFile --
+ * Find the file with the given name along the given search path.
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * If the file is found in a directory which is not on the path
+ * already (either 'name' is absolute or it is a relative path
+ * [ dir1/.../dirn/file ] which exists below one of the directories
+ * already on the search path), its directory is added to the end
+ * of the path on the assumption that there will be more files in
+ * that directory later on. Sometimes this is true. Sometimes not.
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_FindFile (name, path)
+ char *name; /* the file to find */
+ Lst path; /* the Lst of directories to search */
+{
+ register char *p1; /* pointer into p->name */
+ register char *p2; /* pointer into name */
+ LstNode ln; /* a list element */
+ register char *file; /* the current filename to check */
+ register Path *p; /* current path member */
+ register char *cp; /* index of first slash, if any */
+ Boolean hasSlash; /* true if 'name' contains a / */
+ struct stat stb; /* Buffer for stat, if necessary */
+ Hash_Entry *entry; /* Entry for mtimes table */
+
+ /*
+ * Find the final component of the name and note whether it has a
+ * slash in it (the name, I mean)
+ */
+ cp = strrchr (name, '/');
+ if (cp) {
+ hasSlash = TRUE;
+ cp += 1;
+ } else {
+ hasSlash = FALSE;
+ cp = name;
+ }
+
+ if (DEBUG(DIR)) {
+ printf("Searching for %s...", name);
+ }
+ /*
+ * No matter what, we always look for the file in the current directory
+ * before anywhere else and we *do not* add the ./ to it if it exists.
+ * This is so there are no conflicts between what the user specifies
+ * (fish.c) and what pmake finds (./fish.c).
+ */
+ if ((!hasSlash || (cp - name == 2 && *name == '.')) &&
+ (Hash_FindEntry (&dot->files, cp) != (Hash_Entry *)NULL)) {
+ if (DEBUG(DIR)) {
+ printf("in '.'\n");
+ }
+ hits += 1;
+ dot->hits += 1;
+ return (strdup (name));
+ }
+
+ if (Lst_Open (path) == FAILURE) {
+ if (DEBUG(DIR)) {
+ printf("couldn't open path, file not found\n");
+ }
+ misses += 1;
+ return ((char *) NULL);
+ }
+
+ /*
+ * We look through all the directories on the path seeking one which
+ * contains the final component of the given name and whose final
+ * component(s) match the name's initial component(s). If such a beast
+ * is found, we concatenate the directory name and the final component
+ * and return the resulting string. If we don't find any such thing,
+ * we go on to phase two...
+ */
+ while ((ln = Lst_Next (path)) != NILLNODE) {
+ p = (Path *) Lst_Datum (ln);
+ if (DEBUG(DIR)) {
+ printf("%s...", p->name);
+ }
+ if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) {
+ if (DEBUG(DIR)) {
+ printf("here...");
+ }
+ if (hasSlash) {
+ /*
+ * If the name had a slash, its initial components and p's
+ * final components must match. This is false if a mismatch
+ * is encountered before all of the initial components
+ * have been checked (p2 > name at the end of the loop), or
+ * we matched only part of one of the components of p
+ * along with all the rest of them (*p1 != '/').
+ */
+ p1 = p->name + strlen (p->name) - 1;
+ p2 = cp - 2;
+ while (p2 >= name && p1 >= p->name && *p1 == *p2) {
+ p1 -= 1; p2 -= 1;
+ }
+ if (p2 >= name || (p1 >= p->name && *p1 != '/')) {
+ if (DEBUG(DIR)) {
+ printf("component mismatch -- continuing...");
+ }
+ continue;
+ }
+ }
+ file = str_concat (p->name, cp, STR_ADDSLASH);
+ if (DEBUG(DIR)) {
+ printf("returning %s\n", file);
+ }
+ Lst_Close (path);
+ p->hits += 1;
+ hits += 1;
+ return (file);
+ } else if (hasSlash) {
+ /*
+ * If the file has a leading path component and that component
+ * exactly matches the entire name of the current search
+ * directory, we assume the file doesn't exist and return NULL.
+ */
+ for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) {
+ continue;
+ }
+ if (*p1 == '\0' && p2 == cp - 1) {
+ if (DEBUG(DIR)) {
+ printf("must be here but isn't -- returing NULL\n");
+ }
+ Lst_Close (path);
+ return ((char *) NULL);
+ }
+ }
+ }
+
+ /*
+ * We didn't find the file on any existing members of the directory.
+ * If the name doesn't contain a slash, that means it doesn't exist.
+ * If it *does* contain a slash, however, there is still hope: it
+ * could be in a subdirectory of one of the members of the search
+ * path. (eg. /usr/include and sys/types.h. The above search would
+ * fail to turn up types.h in /usr/include, but it *is* in
+ * /usr/include/sys/types.h) If we find such a beast, we assume there
+ * will be more (what else can we assume?) and add all but the last
+ * component of the resulting name onto the search path (at the
+ * end). This phase is only performed if the file is *not* absolute.
+ */
+ if (!hasSlash) {
+ if (DEBUG(DIR)) {
+ printf("failed.\n");
+ }
+ misses += 1;
+ return ((char *) NULL);
+ }
+
+ if (*name != '/') {
+ Boolean checkedDot = FALSE;
+
+ if (DEBUG(DIR)) {
+ printf("failed. Trying subdirectories...");
+ }
+ (void) Lst_Open (path);
+ while ((ln = Lst_Next (path)) != NILLNODE) {
+ p = (Path *) Lst_Datum (ln);
+ if (p != dot) {
+ file = str_concat (p->name, name, STR_ADDSLASH);
+ } else {
+ /*
+ * Checking in dot -- DON'T put a leading ./ on the thing.
+ */
+ file = strdup(name);
+ checkedDot = TRUE;
+ }
+ if (DEBUG(DIR)) {
+ printf("checking %s...", file);
+ }
+
+
+ if (stat (file, &stb) == 0) {
+ if (DEBUG(DIR)) {
+ printf("got it.\n");
+ }
+
+ Lst_Close (path);
+
+ /*
+ * We've found another directory to search. We know there's
+ * a slash in 'file' because we put one there. We nuke it after
+ * finding it and call Dir_AddDir to add this new directory
+ * onto the existing search path. Once that's done, we restore
+ * the slash and triumphantly return the file name, knowing
+ * that should a file in this directory every be referenced
+ * again in such a manner, we will find it without having to do
+ * numerous numbers of access calls. Hurrah!
+ */
+ cp = strrchr (file, '/');
+ *cp = '\0';
+ Dir_AddDir (path, file);
+ *cp = '/';
+
+ /*
+ * Save the modification time so if it's needed, we don't have
+ * to fetch it again.
+ */
+ if (DEBUG(DIR)) {
+ printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime),
+ file);
+ }
+ entry = Hash_CreateEntry(&mtimes, (char *) file,
+ (Boolean *)NULL);
+ Hash_SetValue(entry, (long)stb.st_mtime);
+ nearmisses += 1;
+ return (file);
+ } else {
+ free (file);
+ }
+ }
+
+ if (DEBUG(DIR)) {
+ printf("failed. ");
+ }
+ Lst_Close (path);
+
+ if (checkedDot) {
+ /*
+ * Already checked by the given name, since . was in the path,
+ * so no point in proceeding...
+ */
+ if (DEBUG(DIR)) {
+ printf("Checked . already, returning NULL\n");
+ }
+ return(NULL);
+ }
+ }
+
+ /*
+ * Didn't find it that way, either. Sigh. Phase 3. Add its directory
+ * onto the search path in any case, just in case, then look for the
+ * thing in the hash table. If we find it, grand. We return a new
+ * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
+ * Note that if the directory holding the file doesn't exist, this will
+ * do an extra search of the final directory on the path. Unless something
+ * weird happens, this search won't succeed and life will be groovy.
+ *
+ * Sigh. We cannot add the directory onto the search path because
+ * of this amusing case:
+ * $(INSTALLDIR)/$(FILE): $(FILE)
+ *
+ * $(FILE) exists in $(INSTALLDIR) but not in the current one.
+ * When searching for $(FILE), we will find it in $(INSTALLDIR)
+ * b/c we added it here. This is not good...
+ */
+#ifdef notdef
+ cp[-1] = '\0';
+ Dir_AddDir (path, name);
+ cp[-1] = '/';
+
+ bigmisses += 1;
+ ln = Lst_Last (path);
+ if (ln == NILLNODE) {
+ return ((char *) NULL);
+ } else {
+ p = (Path *) Lst_Datum (ln);
+ }
+
+ if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) {
+ return (strdup (name));
+ } else {
+ return ((char *) NULL);
+ }
+#else /* !notdef */
+ if (DEBUG(DIR)) {
+ printf("Looking for \"%s\"...", name);
+ }
+
+ bigmisses += 1;
+ entry = Hash_FindEntry(&mtimes, name);
+ if (entry != (Hash_Entry *)NULL) {
+ if (DEBUG(DIR)) {
+ printf("got it (in mtime cache)\n");
+ }
+ return(strdup(name));
+ } else if (stat (name, &stb) == 0) {
+ entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL);
+ if (DEBUG(DIR)) {
+ printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime),
+ name);
+ }
+ Hash_SetValue(entry, (long)stb.st_mtime);
+ return (strdup (name));
+ } else {
+ if (DEBUG(DIR)) {
+ printf("failed. Returning NULL\n");
+ }
+ return ((char *)NULL);
+ }
+#endif /* notdef */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MTime --
+ * Find the modification time of the file described by gn along the
+ * search path dirSearchPath.
+ *
+ * Results:
+ * The modification time or 0 if it doesn't exist
+ *
+ * Side Effects:
+ * The modification time is placed in the node's mtime slot.
+ * If the node didn't have a path entry before, and Dir_FindFile
+ * found one for it, the full name is placed in the path slot.
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_MTime (gn)
+ GNode *gn; /* the file whose modification time is
+ * desired */
+{
+ char *fullName; /* the full pathname of name */
+ struct stat stb; /* buffer for finding the mod time */
+ Hash_Entry *entry;
+
+ if (gn->type & OP_ARCHV) {
+ return Arch_MTime (gn);
+ } else if (gn->path == (char *)NULL) {
+ fullName = Dir_FindFile (gn->name, dirSearchPath);
+ } else {
+ fullName = gn->path;
+ }
+
+ if (fullName == (char *)NULL) {
+ fullName = strdup(gn->name);
+ }
+
+ entry = Hash_FindEntry(&mtimes, fullName);
+ if (entry != (Hash_Entry *)NULL) {
+ /*
+ * Only do this once -- the second time folks are checking to
+ * see if the file was actually updated, so we need to actually go
+ * to the file system.
+ */
+ if (DEBUG(DIR)) {
+ printf("Using cached time %s for %s\n",
+ Targ_FmtTime((time_t)(long)Hash_GetValue(entry)), fullName);
+ }
+ stb.st_mtime = (time_t)(long)Hash_GetValue(entry);
+ Hash_DeleteEntry(&mtimes, entry);
+ } else if (stat (fullName, &stb) < 0) {
+ if (gn->type & OP_MEMBER) {
+ if (fullName != gn->path)
+ free(fullName);
+ return Arch_MemMTime (gn);
+ } else {
+ stb.st_mtime = 0;
+ }
+ }
+ if (fullName && gn->path == (char *)NULL) {
+ gn->path = fullName;
+ }
+
+ gn->mtime = stb.st_mtime;
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_AddDir --
+ * Add the given name to the end of the given path. The order of
+ * the arguments is backwards so ParseDoDependency can do a
+ * Lst_ForEach of its list of paths...
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * A structure is added to the list and the directory is
+ * read and hashed.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_AddDir (path, name)
+ Lst path; /* the path to which the directory should be
+ * added */
+ char *name; /* the name of the directory to add */
+{
+ LstNode ln; /* node in case Path structure is found */
+ register Path *p; /* pointer to new Path structure */
+ DIR *d; /* for reading directory */
+ register struct dirent *dp; /* entry in directory */
+
+ ln = Lst_Find (openDirectories, (ClientData)name, DirFindName);
+ if (ln != NILLNODE) {
+ p = (Path *)Lst_Datum (ln);
+ if (Lst_Member(path, (ClientData)p) == NILLNODE) {
+ p->refCount += 1;
+ (void)Lst_AtEnd (path, (ClientData)p);
+ }
+ } else {
+ if (DEBUG(DIR)) {
+ printf("Caching %s...", name);
+ fflush(stdout);
+ }
+
+ if ((d = opendir (name)) != (DIR *) NULL) {
+ p = (Path *) emalloc (sizeof (Path));
+ p->name = strdup (name);
+ p->hits = 0;
+ p->refCount = 1;
+ Hash_InitTable (&p->files, -1);
+
+ /*
+ * Skip the first two entries -- these will *always* be . and ..
+ */
+ (void)readdir(d);
+ (void)readdir(d);
+
+ while ((dp = readdir (d)) != (struct dirent *) NULL) {
+#ifdef sun
+ /*
+ * The sun directory library doesn't check for a 0 inode
+ * (0-inode slots just take up space), so we have to do
+ * it ourselves.
+ */
+ if (dp->d_fileno == 0) {
+ continue;
+ }
+#endif /* sun */
+ (void)Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL);
+ }
+ (void) closedir (d);
+ (void)Lst_AtEnd (openDirectories, (ClientData)p);
+ (void)Lst_AtEnd (path, (ClientData)p);
+ }
+ if (DEBUG(DIR)) {
+ printf("done\n");
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_CopyDir --
+ * Callback function for duplicating a search path via Lst_Duplicate.
+ * Ups the reference count for the directory.
+ *
+ * Results:
+ * Returns the Path it was given.
+ *
+ * Side Effects:
+ * The refCount of the path is incremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+ClientData
+Dir_CopyDir(p)
+ ClientData p;
+{
+ ((Path *) p)->refCount += 1;
+
+ return ((ClientData)p);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MakeFlags --
+ * Make a string by taking all the directories in the given search
+ * path and preceding them by the given flag. Used by the suffix
+ * module to create variables for compilers based on suffix search
+ * paths.
+ *
+ * Results:
+ * The string mentioned above. Note that there is no space between
+ * the given flag and each directory. The empty string is returned if
+ * Things don't go well.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_MakeFlags (flag, path)
+ char *flag; /* flag which should precede each directory */
+ Lst path; /* list of directories */
+{
+ char *str; /* the string which will be returned */
+ char *tstr; /* the current directory preceded by 'flag' */
+ LstNode ln; /* the node of the current directory */
+ Path *p; /* the structure describing the current directory */
+
+ str = strdup ("");
+
+ if (Lst_Open (path) == SUCCESS) {
+ while ((ln = Lst_Next (path)) != NILLNODE) {
+ p = (Path *) Lst_Datum (ln);
+ tstr = str_concat (flag, p->name, 0);
+ str = str_concat (str, tstr, STR_ADDSPACE | STR_DOFREE);
+ }
+ Lst_Close (path);
+ }
+
+ return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Destroy --
+ * Nuke a directory descriptor, if possible. Callback procedure
+ * for the suffixes module when destroying a search path.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * If no other path references this directory (refCount == 0),
+ * the Path and all its data are freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Destroy (pp)
+ ClientData pp; /* The directory descriptor to nuke */
+{
+ Path *p = (Path *) pp;
+ p->refCount -= 1;
+
+ if (p->refCount == 0) {
+ LstNode ln;
+
+ ln = Lst_Member (openDirectories, (ClientData)p);
+ (void) Lst_Remove (openDirectories, ln);
+
+ Hash_DeleteTable (&p->files);
+ free((Address)p->name);
+ free((Address)p);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_ClearPath --
+ * Clear out all elements of the given search path. This is different
+ * from destroying the list, notice.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The path is set to the empty list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_ClearPath(path)
+ Lst path; /* Path to clear */
+{
+ Path *p;
+ while (!Lst_IsEmpty(path)) {
+ p = (Path *)Lst_DeQueue(path);
+ Dir_Destroy((ClientData) p);
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Concat --
+ * Concatenate two paths, adding the second to the end of the first.
+ * Makes sure to avoid duplicates.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Reference counts for added dirs are upped.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Concat(path1, path2)
+ Lst path1; /* Dest */
+ Lst path2; /* Source */
+{
+ LstNode ln;
+ Path *p;
+
+ for (ln = Lst_First(path2); ln != NILLNODE; ln = Lst_Succ(ln)) {
+ p = (Path *)Lst_Datum(ln);
+ if (Lst_Member(path1, (ClientData)p) == NILLNODE) {
+ p->refCount += 1;
+ (void)Lst_AtEnd(path1, (ClientData)p);
+ }
+ }
+}
+
+/********** DEBUG INFO **********/
+void
+Dir_PrintDirectories()
+{
+ LstNode ln;
+ Path *p;
+
+ printf ("#*** Directory Cache:\n");
+ printf ("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
+ hits, misses, nearmisses, bigmisses,
+ (hits+bigmisses+nearmisses ?
+ hits * 100 / (hits + bigmisses + nearmisses) : 0));
+ printf ("# %-20s referenced\thits\n", "directory");
+ if (Lst_Open (openDirectories) == SUCCESS) {
+ while ((ln = Lst_Next (openDirectories)) != NILLNODE) {
+ p = (Path *) Lst_Datum (ln);
+ printf ("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits);
+ }
+ Lst_Close (openDirectories);
+ }
+}
+
+static int DirPrintDir (p, dummy)
+ ClientData p;
+ ClientData dummy;
+{
+ printf ("%s ", ((Path *) p)->name);
+ return (dummy ? 0 : 0);
+}
+
+void
+Dir_PrintPath (path)
+ Lst path;
+{
+ Lst_ForEach (path, DirPrintDir, (ClientData)0);
+}
diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h
new file mode 100644
index 000000000000..81d76d11504d
--- /dev/null
+++ b/usr.bin/make/dir.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)dir.h 8.2 (Berkeley) 4/28/95
+ */
+
+/* dir.h --
+ */
+
+#ifndef _DIR
+#define _DIR
+
+typedef struct Path {
+ char *name; /* Name of directory */
+ int refCount; /* Number of paths with this directory */
+ int hits; /* the number of times a file in this
+ * directory has been found */
+ Hash_Table files; /* Hash table of files in directory */
+} Path;
+
+void Dir_Init __P((void));
+void Dir_End __P((void));
+Boolean Dir_HasWildcards __P((char *));
+void Dir_Expand __P((char *, Lst, Lst));
+char *Dir_FindFile __P((char *, Lst));
+int Dir_MTime __P((GNode *));
+void Dir_AddDir __P((Lst, char *));
+char *Dir_MakeFlags __P((char *, Lst));
+void Dir_ClearPath __P((Lst));
+void Dir_Concat __P((Lst, Lst));
+void Dir_PrintDirectories __P((void));
+void Dir_PrintPath __P((Lst));
+void Dir_Destroy __P((ClientData));
+ClientData Dir_CopyDir __P((ClientData));
+
+#endif /* _DIR */
diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c
new file mode 100644
index 000000000000..3ab3cba33dce
--- /dev/null
+++ b/usr.bin/make/for.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)for.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * for.c --
+ * Functions to handle loops in a makefile.
+ *
+ * Interface:
+ * For_Eval Evaluate the loop in the passed line.
+ * For_Run Run accumulated loop
+ *
+ */
+
+#include <ctype.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+
+/*
+ * For statements are of the form:
+ *
+ * .for <variable> in <varlist>
+ * ...
+ * .endfor
+ *
+ * The trick is to look for the matching end inside for for loop
+ * To do that, we count the current nesting level of the for loops.
+ * and the .endfor statements, accumulating all the statements between
+ * the initial .for loop and the matching .endfor;
+ * then we evaluate the for loop for each variable in the varlist.
+ */
+
+static int forLevel = 0; /* Nesting level */
+static char *forVar; /* Iteration variable */
+static Buffer forBuf; /* Commands in loop */
+static Lst forLst; /* List of items */
+
+/*
+ * State of a for loop.
+ */
+typedef struct _For {
+ Buffer buf; /* Unexpanded buffer */
+ char* var; /* Index name */
+ Lst lst; /* List of variables */
+} For;
+
+static int ForExec __P((ClientData, ClientData));
+
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Eval --
+ * Evaluate the for loop in the passed line. The line
+ * looks like this:
+ * .for <variable> in <varlist>
+ *
+ * Results:
+ * TRUE: We found a for loop, or we are inside a for loop
+ * FALSE: We did not find a for loop, or we found the end of the for
+ * for loop.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+For_Eval (line)
+ char *line; /* Line to parse */
+{
+ char *ptr = line, *sub, *wrd;
+ int level; /* Level at which to report errors. */
+
+ level = PARSE_FATAL;
+
+
+ if (forLevel == 0) {
+ Buffer buf;
+ int varlen;
+
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+ /*
+ * If we are not in a for loop quickly determine if the statement is
+ * a for.
+ */
+ if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
+ !isspace((unsigned char) ptr[3]))
+ return FALSE;
+ ptr += 3;
+
+ /*
+ * we found a for loop, and now we are going to parse it.
+ */
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+
+ /*
+ * Grab the variable
+ */
+ buf = Buf_Init(0);
+ for (wrd = ptr; *ptr && !isspace((unsigned char) *ptr); ptr++)
+ continue;
+ Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd);
+
+ forVar = (char *) Buf_GetAll(buf, &varlen);
+ if (varlen == 0) {
+ Parse_Error (level, "missing variable in for");
+ return 0;
+ }
+ Buf_Destroy(buf, FALSE);
+
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+
+ /*
+ * Grab the `in'
+ */
+ if (ptr[0] != 'i' || ptr[1] != 'n' ||
+ !isspace((unsigned char) ptr[2])) {
+ Parse_Error (level, "missing `in' in for");
+ printf("%s\n", ptr);
+ return 0;
+ }
+ ptr += 3;
+
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+
+ /*
+ * Make a list with the remaining words
+ */
+ forLst = Lst_Init(FALSE);
+ buf = Buf_Init(0);
+ sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
+
+#define ADDWORD() \
+ Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \
+ Buf_AddByte(buf, (Byte) '\0'), \
+ Lst_AtEnd(forLst, (ClientData) Buf_GetAll(buf, &varlen)), \
+ Buf_Destroy(buf, FALSE)
+
+ for (ptr = sub; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ for (wrd = ptr; *ptr; ptr++)
+ if (isspace((unsigned char) *ptr)) {
+ ADDWORD();
+ buf = Buf_Init(0);
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+ wrd = ptr--;
+ }
+ if (DEBUG(FOR))
+ (void) fprintf(stderr, "For: Iterator %s List %s\n", forVar, sub);
+ if (ptr - wrd > 0)
+ ADDWORD();
+ else
+ Buf_Destroy(buf, TRUE);
+ free((Address) sub);
+
+ forBuf = Buf_Init(0);
+ forLevel++;
+ return 1;
+ }
+ else if (*ptr == '.') {
+
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ if (strncmp(ptr, "endfor", 6) == 0 &&
+ (isspace((unsigned char) ptr[6]) || !ptr[6])) {
+ if (DEBUG(FOR))
+ (void) fprintf(stderr, "For: end for %d\n", forLevel);
+ if (--forLevel < 0) {
+ Parse_Error (level, "for-less endfor");
+ return 0;
+ }
+ }
+ else if (strncmp(ptr, "for", 3) == 0 &&
+ isspace((unsigned char) ptr[3])) {
+ forLevel++;
+ if (DEBUG(FOR))
+ (void) fprintf(stderr, "For: new loop %d\n", forLevel);
+ }
+ }
+
+ if (forLevel != 0) {
+ Buf_AddBytes(forBuf, strlen(line), (Byte *) line);
+ Buf_AddByte(forBuf, (Byte) '\n');
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ForExec --
+ * Expand the for loop for this index and push it in the Makefile
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ForExec(namep, argp)
+ ClientData namep;
+ ClientData argp;
+{
+ char *name = (char *) namep;
+ For *arg = (For *) argp;
+ int len;
+ Var_Set(arg->var, name, VAR_GLOBAL);
+ if (DEBUG(FOR))
+ (void) fprintf(stderr, "--- %s = %s\n", arg->var, name);
+ Parse_FromString(Var_Subst(arg->var, (char *) Buf_GetAll(arg->buf, &len),
+ VAR_GLOBAL, FALSE));
+ Var_Delete(arg->var, VAR_GLOBAL);
+
+ return 0;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Run --
+ * Run the for loop, immitating the actions of an include file
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+For_Run()
+{
+ For arg;
+
+ if (forVar == NULL || forBuf == NULL || forLst == NULL)
+ return;
+ arg.var = forVar;
+ arg.buf = forBuf;
+ arg.lst = forLst;
+ forVar = NULL;
+ forBuf = NULL;
+ forLst = NULL;
+
+ Lst_ForEach(arg.lst, ForExec, (ClientData) &arg);
+
+ free((Address)arg.var);
+ Lst_Destroy(arg.lst, (void (*) __P((ClientData))) free);
+ Buf_Destroy(arg.buf, TRUE);
+}
diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c
new file mode 100644
index 000000000000..37e839862b56
--- /dev/null
+++ b/usr.bin/make/hash.c
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)hash.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/* hash.c --
+ *
+ * This module contains routines to manipulate a hash table.
+ * See hash.h for a definition of the structure of the hash
+ * table. Hash tables grow automatically as the amount of
+ * information increases.
+ */
+#include "sprite.h"
+#include "make.h"
+#include "hash.h"
+
+/*
+ * Forward references to local procedures that are used before they're
+ * defined:
+ */
+
+static void RebuildTable __P((Hash_Table *));
+
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
+
+#define rebuildLimit 8
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ *
+ * This routine just sets up the hash table.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is allocated for the initial bucket area.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_InitTable(t, numBuckets)
+ register Hash_Table *t; /* Structure to use to hold table. */
+ int numBuckets; /* How many buckets to create for starters.
+ * This number is rounded up to a power of
+ * two. If <= 0, a reasonable default is
+ * chosen. The table will grow in size later
+ * as needed. */
+{
+ register int i;
+ register struct Hash_Entry **hp;
+
+ /*
+ * Round up the size to a power of two.
+ */
+ if (numBuckets <= 0)
+ i = 16;
+ else {
+ for (i = 2; i < numBuckets; i <<= 1)
+ continue;
+ }
+ t->numEntries = 0;
+ t->size = i;
+ t->mask = i - 1;
+ t->bucketPtr = hp = (struct Hash_Entry **)emalloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteTable --
+ *
+ * This routine removes everything from a hash table
+ * and frees up the memory space it occupied (except for
+ * the space in the Hash_Table structure).
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Lots of memory is freed up.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteTable(t)
+ Hash_Table *t;
+{
+ register struct Hash_Entry **hp, *h, *nexth = NULL;
+ register int i;
+
+ for (hp = t->bucketPtr, i = t->size; --i >= 0;) {
+ for (h = *hp++; h != NULL; h = nexth) {
+ nexth = h->next;
+ free((char *)h);
+ }
+ }
+ free((char *)t->bucketPtr);
+
+ /*
+ * Set up the hash table to cause memory faults on any future access
+ * attempts until re-initialization.
+ */
+ t->bucketPtr = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_FindEntry --
+ *
+ * Searches a hash table for an entry corresponding to key.
+ *
+ * Results:
+ * The return value is a pointer to the entry for key,
+ * if key was present in the table. If key was not
+ * present, NULL is returned.
+ *
+ * Side Effects:
+ * None.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_FindEntry(t, key)
+ Hash_Table *t; /* Hash table to search. */
+ char *key; /* A hash key. */
+{
+ register Hash_Entry *e;
+ register unsigned h;
+ register char *p;
+
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next)
+ if (e->namehash == h && strcmp(e->name, p) == 0)
+ return (e);
+ return (NULL);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_CreateEntry --
+ *
+ * Searches a hash table for an entry corresponding to
+ * key. If no entry is found, then one is created.
+ *
+ * Results:
+ * The return value is a pointer to the entry. If *newPtr
+ * isn't NULL, then *newPtr is filled in with TRUE if a
+ * new entry was created, and FALSE if an entry already existed
+ * with the given key.
+ *
+ * Side Effects:
+ * Memory may be allocated, and the hash buckets may be modified.
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_CreateEntry(t, key, newPtr)
+ register Hash_Table *t; /* Hash table to search. */
+ char *key; /* A hash key. */
+ Boolean *newPtr; /* Filled in with TRUE if new entry created,
+ * FALSE otherwise. */
+{
+ register Hash_Entry *e;
+ register unsigned h;
+ register char *p;
+ int keylen;
+ struct Hash_Entry **hp;
+
+ /*
+ * Hash the key. As a side effect, save the length (strlen) of the
+ * key in case we need to create the entry.
+ */
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ keylen = p - key;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) {
+ if (e->namehash == h && strcmp(e->name, p) == 0) {
+ if (newPtr != NULL)
+ *newPtr = FALSE;
+ return (e);
+ }
+ }
+
+ /*
+ * The desired entry isn't there. Before allocating a new entry,
+ * expand the table if necessary (and this changes the resulting
+ * bucket chain).
+ */
+ if (t->numEntries >= rebuildLimit * t->size)
+ RebuildTable(t);
+ e = (Hash_Entry *) emalloc(sizeof(*e) + keylen);
+ hp = &t->bucketPtr[h & t->mask];
+ e->next = *hp;
+ *hp = e;
+ e->clientData = NULL;
+ e->namehash = h;
+ (void) strcpy(e->name, p);
+ t->numEntries++;
+
+ if (newPtr != NULL)
+ *newPtr = TRUE;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteEntry --
+ *
+ * Delete the given hash table entry and free memory associated with
+ * it.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Hash chain that entry lives in is modified and memory is freed.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteEntry(t, e)
+ Hash_Table *t;
+ Hash_Entry *e;
+{
+ register Hash_Entry **hp, *p;
+
+ if (e == NULL)
+ return;
+ for (hp = &t->bucketPtr[e->namehash & t->mask];
+ (p = *hp) != NULL; hp = &p->next) {
+ if (p == e) {
+ *hp = p->next;
+ free((char *)p);
+ t->numEntries--;
+ return;
+ }
+ }
+ (void) write(2, "bad call to Hash_DeleteEntry\n", 29);
+ abort();
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumFirst --
+ * This procedure sets things up for a complete search
+ * of all entries recorded in the hash table.
+ *
+ * Results:
+ * The return value is the address of the first entry in
+ * the hash table, or NULL if the table is empty.
+ *
+ * Side Effects:
+ * The information in searchPtr is initialized so that successive
+ * calls to Hash_Next will return successive HashEntry's
+ * from the table.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumFirst(t, searchPtr)
+ Hash_Table *t; /* Table to be searched. */
+ register Hash_Search *searchPtr;/* Area in which to keep state
+ * about search.*/
+{
+ searchPtr->tablePtr = t;
+ searchPtr->nextIndex = 0;
+ searchPtr->hashEntryPtr = NULL;
+ return Hash_EnumNext(searchPtr);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumNext --
+ * This procedure returns successive entries in the hash table.
+ *
+ * Results:
+ * The return value is a pointer to the next HashEntry
+ * in the table, or NULL when the end of the table is
+ * reached.
+ *
+ * Side Effects:
+ * The information in searchPtr is modified to advance to the
+ * next entry.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumNext(searchPtr)
+ register Hash_Search *searchPtr; /* Area used to keep state about
+ search. */
+{
+ register Hash_Entry *e;
+ Hash_Table *t = searchPtr->tablePtr;
+
+ /*
+ * The hashEntryPtr field points to the most recently returned
+ * entry, or is nil if we are starting up. If not nil, we have
+ * to start at the next one in the chain.
+ */
+ e = searchPtr->hashEntryPtr;
+ if (e != NULL)
+ e = e->next;
+ /*
+ * If the chain ran out, or if we are starting up, we need to
+ * find the next nonempty chain.
+ */
+ while (e == NULL) {
+ if (searchPtr->nextIndex >= t->size)
+ return (NULL);
+ e = t->bucketPtr[searchPtr->nextIndex++];
+ }
+ searchPtr->hashEntryPtr = e;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * RebuildTable --
+ * This local routine makes a new hash table that
+ * is larger than the old one.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The entire hash table is moved, so any bucket numbers
+ * from the old table are invalid.
+ *
+ *---------------------------------------------------------
+ */
+
+static void
+RebuildTable(t)
+ register Hash_Table *t;
+{
+ register Hash_Entry *e, *next = NULL, **hp, **xp;
+ register int i, mask;
+ register Hash_Entry **oldhp;
+ int oldsize;
+
+ oldhp = t->bucketPtr;
+ oldsize = i = t->size;
+ i <<= 1;
+ t->size = i;
+ t->mask = mask = i - 1;
+ t->bucketPtr = hp = (struct Hash_Entry **) emalloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+ for (hp = oldhp, i = oldsize; --i >= 0;) {
+ for (e = *hp++; e != NULL; e = next) {
+ next = e->next;
+ xp = &t->bucketPtr[e->namehash & mask];
+ e->next = *xp;
+ *xp = e;
+ }
+ }
+ free((char *)oldhp);
+}
diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h
new file mode 100644
index 000000000000..63e8783726ce
--- /dev/null
+++ b/usr.bin/make/hash.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)hash.h 8.2 (Berkeley) 4/28/95
+ */
+
+/* hash.h --
+ *
+ * This file contains definitions used by the hash module,
+ * which maintains hash tables.
+ */
+
+#ifndef _HASH
+#define _HASH
+
+/*
+ * The following defines one entry in the hash table.
+ */
+
+typedef struct Hash_Entry {
+ struct Hash_Entry *next; /* Used to link together all the
+ * entries associated with the same
+ * bucket. */
+ ClientData clientData; /* Arbitrary piece of data associated
+ * with key. */
+ unsigned namehash; /* hash value of key */
+ char name[1]; /* key string */
+} Hash_Entry;
+
+typedef struct Hash_Table {
+ struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one
+ * for each bucket in the table. */
+ int size; /* Actual size of array. */
+ int numEntries; /* Number of entries in the table. */
+ int mask; /* Used to select bits for hashing. */
+} Hash_Table;
+
+/*
+ * The following structure is used by the searching routines
+ * to record where we are in the search.
+ */
+
+typedef struct Hash_Search {
+ Hash_Table *tablePtr; /* Table being searched. */
+ int nextIndex; /* Next bucket to check (after current). */
+ Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */
+} Hash_Search;
+
+/*
+ * Macros.
+ */
+
+/*
+ * ClientData Hash_GetValue(h)
+ * Hash_Entry *h;
+ */
+
+#define Hash_GetValue(h) ((h)->clientData)
+
+/*
+ * Hash_SetValue(h, val);
+ * Hash_Entry *h;
+ * char *val;
+ */
+
+#define Hash_SetValue(h, val) ((h)->clientData = (ClientData) (val))
+
+/*
+ * Hash_Size(n) returns the number of words in an object of n bytes
+ */
+
+#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int))
+
+void Hash_InitTable __P((Hash_Table *, int));
+void Hash_DeleteTable __P((Hash_Table *));
+Hash_Entry *Hash_FindEntry __P((Hash_Table *, char *));
+Hash_Entry *Hash_CreateEntry __P((Hash_Table *, char *, Boolean *));
+void Hash_DeleteEntry __P((Hash_Table *, Hash_Entry *));
+Hash_Entry *Hash_EnumFirst __P((Hash_Table *, Hash_Search *));
+Hash_Entry *Hash_EnumNext __P((Hash_Search *));
+
+#endif /* _HASH */
diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c
new file mode 100644
index 000000000000..166fd974797a
--- /dev/null
+++ b/usr.bin/make/job.c
@@ -0,0 +1,2668 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)job.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * job.c --
+ * handle the creation etc. of our child processes.
+ *
+ * Interface:
+ * Job_Make Start the creation of the given target.
+ *
+ * Job_CatchChildren Check for and handle the termination of any
+ * children. This must be called reasonably
+ * frequently to keep the whole make going at
+ * a decent clip, since job table entries aren't
+ * removed until their process is caught this way.
+ * Its single argument is TRUE if the function
+ * should block waiting for a child to terminate.
+ *
+ * Job_CatchOutput Print any output our children have produced.
+ * Should also be called fairly frequently to
+ * keep the user informed of what's going on.
+ * If no output is waiting, it will block for
+ * a time given by the SEL_* constants, below,
+ * or until output is ready.
+ *
+ * Job_Init Called to intialize this module. in addition,
+ * any commands attached to the .BEGIN target
+ * are executed before this function returns.
+ * Hence, the makefile must have been parsed
+ * before this function is called.
+ *
+ * Job_Full Return TRUE if the job table is filled.
+ *
+ * Job_Empty Return TRUE if the job table is completely
+ * empty.
+ *
+ * Job_ParseShell Given the line following a .SHELL target, parse
+ * the line as a shell specification. Returns
+ * FAILURE if the spec was incorrect.
+ *
+ * Job_End Perform any final processing which needs doing.
+ * This includes the execution of any commands
+ * which have been/were attached to the .END
+ * target. It should only be called when the
+ * job table is empty.
+ *
+ * Job_AbortAll Abort all currently running jobs. It doesn't
+ * handle output or do anything for the jobs,
+ * just kills them. It should only be called in
+ * an emergency, as it were.
+ *
+ * Job_CheckCommands Verify that the commands for a target are
+ * ok. Provide them if necessary and possible.
+ *
+ * Job_Touch Update a target without really updating it.
+ *
+ * Job_Wait Wait for all currently-running jobs to finish.
+ */
+
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+
+extern int errno;
+
+/*
+ * error handling variables
+ */
+static int errors = 0; /* number of errors reported */
+static int aborting = 0; /* why is the make aborting? */
+#define ABORT_ERROR 1 /* Because of an error */
+#define ABORT_INTERRUPT 2 /* Because it was interrupted */
+#define ABORT_WAIT 3 /* Waiting for jobs to finish */
+
+
+/*
+ * post-make command processing. The node postCommands is really just the
+ * .END target but we keep it around to avoid having to search for it
+ * all the time.
+ */
+static GNode *postCommands; /* node containing commands to execute when
+ * everything else is done */
+static int numCommands; /* The number of commands actually printed
+ * for a target. Should this number be
+ * 0, no shell will be executed. */
+
+
+/*
+ * Return values from JobStart.
+ */
+#define JOB_RUNNING 0 /* Job is running */
+#define JOB_ERROR 1 /* Error in starting the job */
+#define JOB_FINISHED 2 /* The job is already finished */
+#define JOB_STOPPED 3 /* The job is stopped */
+
+/*
+ * tfile is the name of a file into which all shell commands are put. It is
+ * used over by removing it before the child shell is executed. The XXXXX in
+ * the string are replaced by the pid of the make process in a 5-character
+ * field with leading zeroes.
+ */
+static char tfile[] = TMPPAT;
+
+
+/*
+ * Descriptions for various shells.
+ */
+static Shell shells[] = {
+ /*
+ * CSH description. The csh can do echo control by playing
+ * with the setting of the 'echo' shell variable. Sadly,
+ * however, it is unable to do error control nicely.
+ */
+{
+ "csh",
+ TRUE, "unset verbose", "set verbose", "unset verbose", 10,
+ FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"",
+ "v", "e",
+},
+ /*
+ * SH description. Echo control is also possible and, under
+ * sun UNIX anyway, one can even control error checking.
+ */
+{
+ "sh",
+ TRUE, "set -", "set -v", "set -", 5,
+ FALSE, "echo \"%s\"\n", "sh -c '%s || exit 0'\n",
+ "v", "e",
+},
+ /*
+ * UNKNOWN.
+ */
+{
+ (char *)0,
+ FALSE, (char *)0, (char *)0, (char *)0, 0,
+ FALSE, (char *)0, (char *)0,
+ (char *)0, (char *)0,
+}
+};
+static Shell *commandShell = &shells[DEFSHELL];/* this is the shell to
+ * which we pass all
+ * commands in the Makefile.
+ * It is set by the
+ * Job_ParseShell function */
+static char *shellPath = (char *) NULL, /* full pathname of
+ * executable image */
+ *shellName; /* last component of shell */
+
+
+static int maxJobs; /* The most children we can run at once */
+static int maxLocal; /* The most local ones we can have */
+int nJobs; /* The number of children currently running */
+int nLocal; /* The number of local children */
+Lst jobs; /* The structures that describe them */
+Boolean jobFull; /* Flag to tell when the job table is full. It
+ * is set TRUE when (1) the total number of
+ * running jobs equals the maximum allowed or
+ * (2) a job can only be run locally, but
+ * nLocal equals maxLocal */
+#ifndef RMT_WILL_WATCH
+static fd_set outputs; /* Set of descriptors of pipes connected to
+ * the output channels of children */
+#endif
+
+GNode *lastNode; /* The node for which output was most recently
+ * produced. */
+char *targFmt; /* Format string to use to head output from a
+ * job when it's not the most-recent job heard
+ * from */
+#define TARG_FMT "--- %s ---\n" /* Default format */
+
+/*
+ * When JobStart attempts to run a job remotely but can't, and isn't allowed
+ * to run the job locally, or when Job_CatchChildren detects a job that has
+ * been migrated home, the job is placed on the stoppedJobs queue to be run
+ * when the next job finishes.
+ */
+Lst stoppedJobs; /* Lst of Job structures describing
+ * jobs that were stopped due to concurrency
+ * limits or migration home */
+
+
+#if defined(USE_PGRP) && defined(SYSV)
+#define KILL(pid,sig) killpg (-(pid),(sig))
+#else
+# if defined(USE_PGRP)
+#define KILL(pid,sig) killpg ((pid),(sig))
+# else
+#define KILL(pid,sig) kill ((pid),(sig))
+# endif
+#endif
+
+static int JobCondPassSig __P((ClientData, ClientData));
+static void JobPassSig __P((int));
+static int JobCmpPid __P((ClientData, ClientData));
+static int JobPrintCommand __P((ClientData, ClientData));
+static int JobSaveCommand __P((ClientData, ClientData));
+static void JobFinish __P((Job *, union wait));
+static void JobExec __P((Job *, char **));
+static void JobMakeArgv __P((Job *, char **));
+static void JobRestart __P((Job *));
+static int JobStart __P((GNode *, int, Job *));
+static void JobDoOutput __P((Job *, Boolean));
+static Shell *JobMatchShell __P((char *));
+static void JobInterrupt __P((int));
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCondPassSig --
+ * Pass a signal to a job if the job is remote or if USE_PGRP
+ * is defined.
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * None, except the job may bite it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobCondPassSig(jobp, signop)
+ ClientData jobp; /* Job to biff */
+ ClientData signop; /* Signal to send it */
+{
+ Job *job = (Job *) jobp;
+ int signo = *(int *) signop;
+#ifdef RMT_WANTS_SIGNALS
+ if (job->flags & JOB_REMOTE) {
+ (void)Rmt_Signal(job, signo);
+ } else {
+ KILL(job->pid, signo);
+ }
+#else
+ /*
+ * Assume that sending the signal to job->pid will signal any remote
+ * job as well.
+ */
+ KILL(job->pid, signo);
+#endif
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPassSig --
+ * Pass a signal on to all remote jobs and to all local jobs if
+ * USE_PGRP is defined, then die ourselves.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * We die by the same signal.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobPassSig(signo)
+ int signo; /* The signal number we've received */
+{
+ int mask;
+
+ Lst_ForEach(jobs, JobCondPassSig, (ClientData)(long)signo);
+
+ /*
+ * Deal with proper cleanup based on the signal received. We only run
+ * the .INTERRUPT target if the signal was in fact an interrupt. The other
+ * three termination signals are more of a "get out *now*" command.
+ */
+ if (signo == SIGINT) {
+ JobInterrupt(TRUE);
+ } else if ((signo == SIGHUP) || (signo == SIGTERM) || (signo == SIGQUIT)) {
+ JobInterrupt(FALSE);
+ }
+
+ /*
+ * Leave gracefully if SIGQUIT, rather than core dumping.
+ */
+ if (signo == SIGQUIT) {
+ Finish(0);
+ }
+
+ /*
+ * Send ourselves the signal now we've given the message to everyone else.
+ * Note we block everything else possible while we're getting the signal.
+ * This ensures that all our jobs get continued when we wake up before
+ * we take any other signal.
+ */
+ mask = sigblock(0);
+ (void) sigsetmask(~0 & ~(1 << (signo-1)));
+ signal(signo, SIG_DFL);
+
+ kill(getpid(), signo);
+
+ signo = SIGCONT;
+ Lst_ForEach(jobs, JobCondPassSig, (ClientData) &signo);
+
+ sigsetmask(mask);
+ signal(signo, JobPassSig);
+
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCmpPid --
+ * Compare the pid of the job with the given pid and return 0 if they
+ * are equal. This function is called from Job_CatchChildren via
+ * Lst_Find to find the job descriptor of the finished job.
+ *
+ * Results:
+ * 0 if the pid's match
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+JobCmpPid (job, pid)
+ ClientData job; /* job to examine */
+ ClientData pid; /* process id desired */
+{
+ return ( *(int *) pid - ((Job *) job)->pid);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPrintCommand --
+ * Put out another command for the given job. If the command starts
+ * with an @ or a - we process it specially. In the former case,
+ * so long as the -s and -n flags weren't given to make, we stick
+ * a shell-specific echoOff command in the script. In the latter,
+ * we ignore errors for the entire job, unless the shell has error
+ * control.
+ * If the command is just "..." we take all future commands for this
+ * job to be commands to be executed once the entire graph has been
+ * made and return non-zero to signal that the end of the commands
+ * was reached. These commands are later attached to the postCommands
+ * node and executed by Job_End when all things are done.
+ * This function is called from JobStart via Lst_ForEach.
+ *
+ * Results:
+ * Always 0, unless the command was "..."
+ *
+ * Side Effects:
+ * If the command begins with a '-' and the shell has no error control,
+ * the JOB_IGNERR flag is set in the job descriptor.
+ * If the command is "..." and we're not ignoring such things,
+ * tailCmds is set to the successor node of the cmd.
+ * numCommands is incremented if the command is actually printed.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobPrintCommand (cmdp, jobp)
+ ClientData cmdp; /* command string to print */
+ ClientData jobp; /* job for which to print it */
+{
+ Boolean noSpecials; /* true if we shouldn't worry about
+ * inserting special commands into
+ * the input stream. */
+ Boolean shutUp = FALSE; /* true if we put a no echo command
+ * into the command file */
+ Boolean errOff = FALSE; /* true if we turned error checking
+ * off before printing the command
+ * and need to turn it back on */
+ char *cmdTemplate; /* Template to use when printing the
+ * command */
+ char *cmdStart; /* Start of expanded command */
+ LstNode cmdNode; /* Node for replacing the command */
+ char *cmd = (char *) cmdp;
+ Job *job = (Job *) jobp;
+
+ noSpecials = (noExecute && ! (job->node->type & OP_MAKE));
+
+ if (strcmp (cmd, "...") == 0) {
+ job->node->type |= OP_SAVE_CMDS;
+ if ((job->flags & JOB_IGNDOTS) == 0) {
+ job->tailCmds = Lst_Succ (Lst_Member (job->node->commands,
+ (ClientData)cmd));
+ return (1);
+ }
+ return (0);
+ }
+
+#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) printf (fmt, arg); fprintf (job->cmdFILE, fmt, arg)
+
+ numCommands += 1;
+
+ /*
+ * For debugging, we replace each command with the result of expanding
+ * the variables in the command.
+ */
+ cmdNode = Lst_Member (job->node->commands, (ClientData)cmd);
+ cmdStart = cmd = Var_Subst (NULL, cmd, job->node, FALSE);
+ Lst_Replace (cmdNode, (ClientData)cmdStart);
+
+ cmdTemplate = "%s\n";
+
+ /*
+ * Check for leading @' and -'s to control echoing and error checking.
+ */
+ while (*cmd == '@' || *cmd == '-') {
+ if (*cmd == '@') {
+ shutUp = TRUE;
+ } else {
+ errOff = TRUE;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char) *cmd))
+ cmd++;
+
+ if (shutUp) {
+ if (! (job->flags & JOB_SILENT) && !noSpecials &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF ("%s\n", commandShell->echoOff);
+ } else {
+ shutUp = FALSE;
+ }
+ }
+
+ if (errOff) {
+ if ( ! (job->flags & JOB_IGNERR) && !noSpecials) {
+ if (commandShell->hasErrCtl) {
+ /*
+ * we don't want the error-control commands showing
+ * up either, so we turn off echoing while executing
+ * them. We could put another field in the shell
+ * structure to tell JobDoOutput to look for this
+ * string too, but why make it any more complex than
+ * it already is?
+ */
+ if (! (job->flags & JOB_SILENT) && !shutUp &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF ("%s\n", commandShell->echoOff);
+ DBPRINTF ("%s\n", commandShell->ignErr);
+ DBPRINTF ("%s\n", commandShell->echoOn);
+ } else {
+ DBPRINTF ("%s\n", commandShell->ignErr);
+ }
+ } else if (commandShell->ignErr &&
+ (*commandShell->ignErr != '\0'))
+ {
+ /*
+ * The shell has no error control, so we need to be
+ * weird to get it to ignore any errors from the command.
+ * If echoing is turned on, we turn it off and use the
+ * errCheck template to echo the command. Leave echoing
+ * off so the user doesn't see the weirdness we go through
+ * to ignore errors. Set cmdTemplate to use the weirdness
+ * instead of the simple "%s\n" template.
+ */
+ if (! (job->flags & JOB_SILENT) && !shutUp &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF ("%s\n", commandShell->echoOff);
+ DBPRINTF (commandShell->errCheck, cmd);
+ shutUp = TRUE;
+ }
+ cmdTemplate = commandShell->ignErr;
+ /*
+ * The error ignoration (hee hee) is already taken care
+ * of by the ignErr template, so pretend error checking
+ * is still on.
+ */
+ errOff = FALSE;
+ } else {
+ errOff = FALSE;
+ }
+ } else {
+ errOff = FALSE;
+ }
+ }
+
+ DBPRINTF (cmdTemplate, cmd);
+
+ if (errOff) {
+ /*
+ * If echoing is already off, there's no point in issuing the
+ * echoOff command. Otherwise we issue it and pretend it was on
+ * for the whole command...
+ */
+ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
+ DBPRINTF ("%s\n", commandShell->echoOff);
+ shutUp = TRUE;
+ }
+ DBPRINTF ("%s\n", commandShell->errCheck);
+ }
+ if (shutUp) {
+ DBPRINTF ("%s\n", commandShell->echoOn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobSaveCommand --
+ * Save a command to be executed when everything else is done.
+ * Callback function for JobFinish...
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The command is tacked onto the end of postCommands's commands list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobSaveCommand (cmd, gn)
+ ClientData cmd;
+ ClientData gn;
+{
+ cmd = (ClientData) Var_Subst (NULL, (char *) cmd, (GNode *) gn, FALSE);
+ (void)Lst_AtEnd (postCommands->commands, cmd);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFinish --
+ * Do final processing for the given job including updating
+ * parents and starting new jobs as available/necessary. Note
+ * that we pay no attention to the JOB_IGNERR flag here.
+ * This is because when we're called because of a noexecute flag
+ * or something, jstat.w_status is 0 and when called from
+ * Job_CatchChildren, the status is zeroed if it s/b ignored.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Some nodes may be put on the toBeMade queue.
+ * Final commands for the job are placed on postCommands.
+ *
+ * If we got an error and are aborting (aborting == ABORT_ERROR) and
+ * the job list is now empty, we are done for the day.
+ * If we recognized an error (errors !=0), we set the aborting flag
+ * to ABORT_ERROR so no more jobs will be started.
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobFinish (job, status)
+ Job *job; /* job to finish */
+ union wait status; /* sub-why job went away */
+{
+ Boolean done;
+
+ if ((WIFEXITED(status) &&
+ (((status.w_retcode != 0) && !(job->flags & JOB_IGNERR)))) ||
+ (WIFSIGNALED(status) && (status.w_termsig != SIGCONT)))
+ {
+ /*
+ * If it exited non-zero and either we're doing things our
+ * way or we're not ignoring errors, the job is finished.
+ * Similarly, if the shell died because of a signal
+ * the job is also finished. In these
+ * cases, finish out the job's output before printing the exit
+ * status...
+ */
+ if (usePipes) {
+#ifdef RMT_WILL_WATCH
+ Rmt_Ignore(job->inPipe);
+#else
+ FD_CLR(job->inPipe, &outputs);
+#endif /* RMT_WILL_WATCH */
+ if (job->outPipe != job->inPipe) {
+ (void)close (job->outPipe);
+ }
+ JobDoOutput (job, TRUE);
+ (void)close (job->inPipe);
+ } else {
+ (void)close (job->outFd);
+ JobDoOutput (job, TRUE);
+ }
+
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ fclose(job->cmdFILE);
+ }
+ done = TRUE;
+ } else if (WIFEXITED(status) && status.w_retcode != 0) {
+ /*
+ * Deal with ignored errors in -B mode. We need to print a message
+ * telling of the ignored error as well as setting status.w_status
+ * to 0 so the next command gets run. To do this, we set done to be
+ * TRUE if in -B mode and the job exited non-zero. Note we don't
+ * want to close down any of the streams until we know we're at the
+ * end.
+ */
+ done = TRUE;
+ } else {
+ /*
+ * No need to close things down or anything.
+ */
+ done = FALSE;
+ }
+
+ if (done ||
+ WIFSTOPPED(status) ||
+ (WIFSIGNALED(status) && (status.w_termsig == SIGCONT)) ||
+ DEBUG(JOB))
+ {
+ FILE *out;
+
+ if (!usePipes && (job->flags & JOB_IGNERR)) {
+ /*
+ * If output is going to a file and this job is ignoring
+ * errors, arrange to have the exit status sent to the
+ * output file as well.
+ */
+ out = fdopen (job->outFd, "w");
+ } else {
+ out = stdout;
+ }
+
+ if (WIFEXITED(status)) {
+ if (status.w_retcode != 0) {
+ if (usePipes && job->node != lastNode) {
+ fprintf (out, targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ fprintf (out, "*** Error code %d%s\n", status.w_retcode,
+ (job->flags & JOB_IGNERR) ? " (ignored)" : "");
+
+ if (job->flags & JOB_IGNERR) {
+ status.w_status = 0;
+ }
+ } else if (DEBUG(JOB)) {
+ if (usePipes && job->node != lastNode) {
+ fprintf (out, targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ fprintf (out, "*** Completed successfully\n");
+ }
+ } else if (WIFSTOPPED(status)) {
+ if (usePipes && job->node != lastNode) {
+ fprintf (out, targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ if (! (job->flags & JOB_REMIGRATE)) {
+ fprintf (out, "*** Stopped -- signal %d\n", status.w_stopsig);
+ }
+ job->flags |= JOB_RESUME;
+ (void)Lst_AtEnd(stoppedJobs, (ClientData)job);
+ fflush(out);
+ return;
+ } else if (status.w_termsig == SIGCONT) {
+ /*
+ * If the beastie has continued, shift the Job from the stopped
+ * list to the running one (or re-stop it if concurrency is
+ * exceeded) and go and get another child.
+ */
+ if (job->flags & (JOB_RESUME|JOB_REMIGRATE|JOB_RESTART)) {
+ if (usePipes && job->node != lastNode) {
+ fprintf (out, targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ fprintf (out, "*** Continued\n");
+ }
+ if (! (job->flags & JOB_CONTINUING)) {
+ JobRestart(job);
+ } else {
+ Lst_AtEnd(jobs, (ClientData)job);
+ nJobs += 1;
+ if (! (job->flags & JOB_REMOTE)) {
+ nLocal += 1;
+ }
+ if (nJobs == maxJobs) {
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Job queue is full.\n");
+ }
+ }
+ }
+ fflush(out);
+ return;
+ } else {
+ if (usePipes && job->node != lastNode) {
+ fprintf (out, targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ fprintf (out, "*** Signal %d\n", status.w_termsig);
+ }
+
+ fflush (out);
+ }
+
+ /*
+ * Now handle the -B-mode stuff. If the beast still isn't finished,
+ * try and restart the job on the next command. If JobStart says it's
+ * ok, it's ok. If there's an error, this puppy is done.
+ */
+ if ((status.w_status == 0) &&
+ !Lst_IsAtEnd (job->node->commands))
+ {
+ switch (JobStart (job->node,
+ job->flags & JOB_IGNDOTS,
+ job))
+ {
+ case JOB_RUNNING:
+ done = FALSE;
+ break;
+ case JOB_ERROR:
+ done = TRUE;
+ status.w_retcode = 1;
+ break;
+ case JOB_FINISHED:
+ /*
+ * If we got back a JOB_FINISHED code, JobStart has already
+ * called Make_Update and freed the job descriptor. We set
+ * done to false here to avoid fake cycles and double frees.
+ * JobStart needs to do the update so we can proceed up the
+ * graph when given the -n flag..
+ */
+ done = FALSE;
+ break;
+ }
+ } else {
+ done = TRUE;
+ }
+
+
+ if (done &&
+ (aborting != ABORT_ERROR) &&
+ (aborting != ABORT_INTERRUPT) &&
+ (status.w_status == 0))
+ {
+ /*
+ * As long as we aren't aborting and the job didn't return a non-zero
+ * status that we shouldn't ignore, we call Make_Update to update
+ * the parents. In addition, any saved commands for the node are placed
+ * on the .END target.
+ */
+ if (job->tailCmds != NILLNODE) {
+ Lst_ForEachFrom (job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ (ClientData)job->node);
+ }
+ job->node->made = MADE;
+ Make_Update (job->node);
+ free((Address)job);
+ } else if (status.w_status) {
+ errors += 1;
+ free((Address)job);
+ }
+
+ while (!errors && !jobFull && !Lst_IsEmpty(stoppedJobs)) {
+ JobRestart((Job *)Lst_DeQueue(stoppedJobs));
+ }
+
+ /*
+ * Set aborting if any error.
+ */
+ if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) {
+ /*
+ * If we found any errors in this batch of children and the -k flag
+ * wasn't given, we set the aborting flag so no more jobs get
+ * started.
+ */
+ aborting = ABORT_ERROR;
+ }
+
+ if ((aborting == ABORT_ERROR) && Job_Empty()) {
+ /*
+ * If we are aborting and the job table is now empty, we finish.
+ */
+ (void) unlink (tfile);
+ Finish (errors);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Touch --
+ * Touch the given target. Called by JobStart when the -t flag was
+ * given
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The data modification of the file is changed. In addition, if the
+ * file did not exist, it is created.
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Touch (gn, silent)
+ GNode *gn; /* the node of the file to touch */
+ Boolean silent; /* TRUE if should not print messages */
+{
+ int streamID; /* ID of stream opened to do the touch */
+ struct timeval times[2]; /* Times for utimes() call */
+
+ if (gn->type & (OP_JOIN|OP_USE|OP_EXEC|OP_OPTIONAL)) {
+ /*
+ * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
+ * and, as such, shouldn't really be created.
+ */
+ return;
+ }
+
+ if (!silent) {
+ printf ("touch %s\n", gn->name);
+ }
+
+ if (noExecute) {
+ return;
+ }
+
+ if (gn->type & OP_ARCHV) {
+ Arch_Touch (gn);
+ } else if (gn->type & OP_LIB) {
+ Arch_TouchLib (gn);
+ } else {
+ char *file = gn->path ? gn->path : gn->name;
+
+ times[0].tv_sec = times[1].tv_sec = now;
+ times[0].tv_usec = times[1].tv_usec = 0;
+ if (utimes(file, times) < 0){
+ streamID = open (file, O_RDWR | O_CREAT, 0666);
+
+ if (streamID >= 0) {
+ char c;
+
+ /*
+ * Read and write a byte to the file to change the
+ * modification time, then close the file.
+ */
+ if (read(streamID, &c, 1) == 1) {
+ lseek(streamID, 0L, L_SET);
+ write(streamID, &c, 1);
+ }
+
+ (void)close (streamID);
+ } else
+ printf("*** couldn't touch %s: %s", file, strerror(errno));
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CheckCommands --
+ * Make sure the given node has all the commands it needs.
+ *
+ * Results:
+ * TRUE if the commands list is/was ok.
+ *
+ * Side Effects:
+ * The node will have commands from the .DEFAULT rule added to it
+ * if it needs them.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_CheckCommands (gn, abortProc)
+ GNode *gn; /* The target whose commands need
+ * verifying */
+ void (*abortProc) __P((char *, ...));
+ /* Function to abort with message */
+{
+ if (OP_NOP(gn->type) && Lst_IsEmpty (gn->commands) &&
+ (gn->type & OP_LIB) == 0) {
+ /*
+ * No commands. Look for .DEFAULT rule from which we might infer
+ * commands
+ */
+ if ((DEFAULT != NILGNODE) && !Lst_IsEmpty(DEFAULT->commands)) {
+ char *p1;
+ /*
+ * Make only looks for a .DEFAULT if the node was never the
+ * target of an operator, so that's what we do too. If
+ * a .DEFAULT was given, we substitute its commands for gn's
+ * commands and set the IMPSRC variable to be the target's name
+ * The DEFAULT node acts like a transformation rule, in that
+ * gn also inherits any attributes or sources attached to
+ * .DEFAULT itself.
+ */
+ Make_HandleUse(DEFAULT, gn);
+ Var_Set (IMPSRC, Var_Value (TARGET, gn, &p1), gn);
+ if (p1)
+ free(p1);
+ } else if (Dir_MTime (gn) == 0) {
+ /*
+ * The node wasn't the target of an operator we have no .DEFAULT
+ * rule to go on and the target doesn't already exist. There's
+ * nothing more we can do for this branch. If the -k flag wasn't
+ * given, we stop in our tracks, otherwise we just don't update
+ * this node's parents so they never get examined.
+ */
+ if (gn->type & OP_OPTIONAL) {
+ printf ("make: don't know how to make %s (ignored)\n",
+ gn->name);
+ } else if (keepgoing) {
+ printf ("make: don't know how to make %s (continuing)\n",
+ gn->name);
+ return (FALSE);
+ } else {
+ (*abortProc) ("make: don't know how to make %s. Stop",
+ gn->name);
+ return(FALSE);
+ }
+ }
+ }
+ return (TRUE);
+}
+#ifdef RMT_WILL_WATCH
+/*-
+ *-----------------------------------------------------------------------
+ * JobLocalInput --
+ * Handle a pipe becoming readable. Callback function for Rmt_Watch
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * JobDoOutput is called.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobLocalInput(stream, job)
+ int stream; /* Stream that's ready (ignored) */
+ Job *job; /* Job to which the stream belongs */
+{
+ JobDoOutput(job, FALSE);
+}
+#endif /* RMT_WILL_WATCH */
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobExec --
+ * Execute the shell for the given job. Called from JobStart and
+ * JobRestart.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * A shell is executed, outputs is altered and the Job structure added
+ * to the job table.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobExec(job, argv)
+ Job *job; /* Job to execute */
+ char **argv;
+{
+ int cpid; /* ID of new child */
+
+ if (DEBUG(JOB)) {
+ int i;
+
+ printf("Running %s %sly\n", job->node->name,
+ job->flags&JOB_REMOTE?"remote":"local");
+ printf("\tCommand: ");
+ for (i = 0; argv[i] != (char *)NULL; i++) {
+ printf("%s ", argv[i]);
+ }
+ printf("\n");
+ }
+
+ /*
+ * Some jobs produce no output and it's disconcerting to have
+ * no feedback of their running (since they produce no output, the
+ * banner with their name in it never appears). This is an attempt to
+ * provide that feedback, even if nothing follows it.
+ */
+ if ((lastNode != job->node) && (job->flags & JOB_FIRST) &&
+ !(job->flags & JOB_SILENT))
+ {
+ printf(targFmt, job->node->name);
+ lastNode = job->node;
+ }
+
+#ifdef RMT_NO_EXEC
+ if (job->flags & JOB_REMOTE) {
+ goto jobExecFinish;
+ }
+#endif /* RMT_NO_EXEC */
+
+ if ((cpid = vfork()) == -1) {
+ Punt ("Cannot fork");
+ } else if (cpid == 0) {
+
+ /*
+ * Must duplicate the input stream down to the child's input and
+ * reset it to the beginning (again). Since the stream was marked
+ * close-on-exec, we must clear that bit in the new input.
+ */
+ (void) dup2(fileno(job->cmdFILE), 0);
+ fcntl(0, F_SETFD, 0);
+ lseek(0, 0, L_SET);
+
+ if (usePipes) {
+ /*
+ * Set up the child's output to be routed through the pipe
+ * we've created for it.
+ */
+ (void) dup2 (job->outPipe, 1);
+ } else {
+ /*
+ * We're capturing output in a file, so we duplicate the
+ * descriptor to the temporary file into the standard
+ * output.
+ */
+ (void) dup2 (job->outFd, 1);
+ }
+ /*
+ * The output channels are marked close on exec. This bit was
+ * duplicated by the dup2 (on some systems), so we have to clear
+ * it before routing the shell's error output to the same place as
+ * its standard output.
+ */
+ fcntl(1, F_SETFD, 0);
+ (void) dup2 (1, 2);
+
+#ifdef USE_PGRP
+ /*
+ * We want to switch the child into a different process family so
+ * we can kill it and all its descendants in one fell swoop,
+ * by killing its process family, but not commit suicide.
+ */
+
+ (void) setpgrp(0, getpid());
+#endif USE_PGRP
+
+ (void) execv (shellPath, argv);
+ (void) write (2, "Could not execute shell\n",
+ sizeof ("Could not execute shell"));
+ _exit (1);
+ } else {
+ job->pid = cpid;
+
+ if (usePipes && (job->flags & JOB_FIRST) ) {
+ /*
+ * The first time a job is run for a node, we set the current
+ * position in the buffer to the beginning and mark another
+ * stream to watch in the outputs mask
+ */
+ job->curPos = 0;
+
+#ifdef RMT_WILL_WATCH
+ Rmt_Watch(job->inPipe, JobLocalInput, job);
+#else
+ FD_SET(job->inPipe, &outputs);
+#endif /* RMT_WILL_WATCH */
+ }
+
+ if (job->flags & JOB_REMOTE) {
+ job->rmtID = 0;
+ } else {
+ nLocal += 1;
+ /*
+ * XXX: Used to not happen if CUSTOMS. Why?
+ */
+ if (job->cmdFILE != stdout) {
+ fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+ }
+ }
+
+#ifdef RMT_NO_EXEC
+jobExecFinish:
+#endif
+ /*
+ * Now the job is actually running, add it to the table.
+ */
+ nJobs += 1;
+ (void)Lst_AtEnd (jobs, (ClientData)job);
+ if (nJobs == maxJobs) {
+ jobFull = TRUE;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMakeArgv --
+ * Create the argv needed to execute the shell for a given job.
+ *
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobMakeArgv(job, argv)
+ Job *job;
+ char **argv;
+{
+ int argc;
+ static char args[10]; /* For merged arguments */
+
+ argv[0] = shellName;
+ argc = 1;
+
+ if ((commandShell->exit && (*commandShell->exit != '-')) ||
+ (commandShell->echo && (*commandShell->echo != '-')))
+ {
+ /*
+ * At least one of the flags doesn't have a minus before it, so
+ * merge them together. Have to do this because the *(&(@*#*&#$#
+ * Bourne shell thinks its second argument is a file to source.
+ * Grrrr. Note the ten-character limitation on the combined arguments.
+ */
+ (void)sprintf(args, "-%s%s",
+ ((job->flags & JOB_IGNERR) ? "" :
+ (commandShell->exit ? commandShell->exit : "")),
+ ((job->flags & JOB_SILENT) ? "" :
+ (commandShell->echo ? commandShell->echo : "")));
+
+ if (args[1]) {
+ argv[argc] = args;
+ argc++;
+ }
+ } else {
+ if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
+ argv[argc] = commandShell->exit;
+ argc++;
+ }
+ if (!(job->flags & JOB_SILENT) && commandShell->echo) {
+ argv[argc] = commandShell->echo;
+ argc++;
+ }
+ }
+ argv[argc] = (char *)NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestart --
+ * Restart a job that stopped for some reason.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * jobFull will be set if the job couldn't be run.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestart(job)
+ Job *job; /* Job to restart */
+{
+ if (job->flags & JOB_REMIGRATE) {
+ if (DEBUG(JOB)) {
+ printf("Remigrating %x\n", job->pid);
+ }
+ if (nLocal != maxLocal) {
+ /*
+ * Job cannot be remigrated, but there's room on the local
+ * machine, so resume the job and note that another
+ * local job has started.
+ */
+ if (DEBUG(JOB)) {
+ printf("resuming on local machine\n");
+ }
+ KILL(job->pid, SIGCONT);
+ nLocal +=1;
+ job->flags &= ~(JOB_REMIGRATE|JOB_RESUME);
+ } else {
+ /*
+ * Job cannot be restarted. Mark the table as full and
+ * place the job back on the list of stopped jobs.
+ */
+ if (DEBUG(JOB)) {
+ printf("holding\n");
+ }
+ (void)Lst_AtFront(stoppedJobs, (ClientData)job);
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Job queue is full.\n");
+ }
+ return;
+ }
+
+ (void)Lst_AtEnd(jobs, (ClientData)job);
+ nJobs += 1;
+ if (nJobs == maxJobs) {
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Job queue is full.\n");
+ }
+ }
+ } else if (job->flags & JOB_RESTART) {
+ /*
+ * Set up the control arguments to the shell. This is based on the
+ * flags set earlier for this job. If the JOB_IGNERR flag is clear,
+ * the 'exit' flag of the commandShell is used to cause it to exit
+ * upon receiving an error. If the JOB_SILENT flag is clear, the
+ * 'echo' flag of the commandShell is used to get it to start echoing
+ * as soon as it starts processing commands.
+ */
+ char *argv[4];
+
+ JobMakeArgv(job, argv);
+
+ if (DEBUG(JOB)) {
+ printf("Restarting %s...", job->node->name);
+ }
+ if (((nLocal >= maxLocal) && ! (job->flags & JOB_SPECIAL))) {
+ /*
+ * Can't be exported and not allowed to run locally -- put it
+ * back on the hold queue and mark the table full
+ */
+ if (DEBUG(JOB)) {
+ printf("holding\n");
+ }
+ (void)Lst_AtFront(stoppedJobs, (ClientData)job);
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Job queue is full.\n");
+ }
+ return;
+ } else {
+ /*
+ * Job may be run locally.
+ */
+ if (DEBUG(JOB)) {
+ printf("running locally\n");
+ }
+ job->flags &= ~JOB_REMOTE;
+ }
+ JobExec(job, argv);
+ } else {
+ /*
+ * The job has stopped and needs to be restarted. Why it stopped,
+ * we don't know...
+ */
+ if (DEBUG(JOB)) {
+ printf("Resuming %s...", job->node->name);
+ }
+ if (((job->flags & JOB_REMOTE) ||
+ (nLocal < maxLocal) ||
+ (((job->flags & JOB_SPECIAL)) &&
+ (maxLocal == 0))) &&
+ (nJobs != maxJobs))
+ {
+ /*
+ * If the job is remote, it's ok to resume it as long as the
+ * maximum concurrency won't be exceeded. If it's local and
+ * we haven't reached the local concurrency limit already (or the
+ * job must be run locally and maxLocal is 0), it's also ok to
+ * resume it.
+ */
+ Boolean error;
+ extern int errno;
+ union wait status;
+
+#ifdef RMT_WANTS_SIGNALS
+ if (job->flags & JOB_REMOTE) {
+ error = !Rmt_Signal(job, SIGCONT);
+ } else
+#endif /* RMT_WANTS_SIGNALS */
+ error = (KILL(job->pid, SIGCONT) != 0);
+
+ if (!error) {
+ /*
+ * Make sure the user knows we've continued the beast and
+ * actually put the thing in the job table.
+ */
+ job->flags |= JOB_CONTINUING;
+ status.w_termsig = SIGCONT;
+ JobFinish(job, status);
+
+ job->flags &= ~(JOB_RESUME|JOB_CONTINUING);
+ if (DEBUG(JOB)) {
+ printf("done\n");
+ }
+ } else {
+ Error("couldn't resume %s: %s",
+ job->node->name, strerror(errno));
+ status.w_status = 0;
+ status.w_retcode = 1;
+ JobFinish(job, status);
+ }
+ } else {
+ /*
+ * Job cannot be restarted. Mark the table as full and
+ * place the job back on the list of stopped jobs.
+ */
+ if (DEBUG(JOB)) {
+ printf("table full\n");
+ }
+ (void)Lst_AtFront(stoppedJobs, (ClientData)job);
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Job queue is full.\n");
+ }
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobStart --
+ * Start a target-creation process going for the target described
+ * by the graph node gn.
+ *
+ * Results:
+ * JOB_ERROR if there was an error in the commands, JOB_FINISHED
+ * if there isn't actually anything left to do for the job and
+ * JOB_RUNNING if the job has been started.
+ *
+ * Side Effects:
+ * A new Job node is created and added to the list of running
+ * jobs. PMake is forked and a child shell created.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobStart (gn, flags, previous)
+ GNode *gn; /* target to create */
+ int flags; /* flags for the job to override normal ones.
+ * e.g. JOB_SPECIAL or JOB_IGNDOTS */
+ Job *previous; /* The previous Job structure for this node,
+ * if any. */
+{
+ register Job *job; /* new job descriptor */
+ char *argv[4]; /* Argument vector to shell */
+ static int jobno = 0; /* job number of catching output in a file */
+ Boolean cmdsOK; /* true if the nodes commands were all right */
+ Boolean local; /* Set true if the job was run locally */
+ Boolean noExec; /* Set true if we decide not to run the job */
+
+ if (previous != (Job *)NULL) {
+ previous->flags &= ~ (JOB_FIRST|JOB_IGNERR|JOB_SILENT|JOB_REMOTE);
+ job = previous;
+ } else {
+ job = (Job *) emalloc (sizeof (Job));
+ if (job == (Job *)NULL) {
+ Punt("JobStart out of memory");
+ }
+ flags |= JOB_FIRST;
+ }
+
+ job->node = gn;
+ job->tailCmds = NILLNODE;
+
+ /*
+ * Set the initial value of the flags for this job based on the global
+ * ones and the node's attributes... Any flags supplied by the caller
+ * are also added to the field.
+ */
+ job->flags = 0;
+ if (Targ_Ignore (gn)) {
+ job->flags |= JOB_IGNERR;
+ }
+ if (Targ_Silent (gn)) {
+ job->flags |= JOB_SILENT;
+ }
+ job->flags |= flags;
+
+ /*
+ * Check the commands now so any attributes from .DEFAULT have a chance
+ * to migrate to the node
+ */
+ if (job->flags & JOB_FIRST) {
+ cmdsOK = Job_CheckCommands(gn, Error);
+ } else {
+ cmdsOK = TRUE;
+ }
+
+ /*
+ * If the -n flag wasn't given, we open up OUR (not the child's)
+ * temporary file to stuff commands in it. The thing is rd/wr so we don't
+ * need to reopen it to feed it to the shell. If the -n flag *was* given,
+ * we just set the file to be stdout. Cute, huh?
+ */
+ if ((gn->type & OP_MAKE) || (!noExecute && !touchFlag)) {
+ /*
+ * We're serious here, but if the commands were bogus, we're
+ * also dead...
+ */
+ if (!cmdsOK) {
+ DieHorribly();
+ }
+
+ job->cmdFILE = fopen (tfile, "w+");
+ if (job->cmdFILE == (FILE *) NULL) {
+ Punt ("Could not open %s", tfile);
+ }
+ fcntl(fileno(job->cmdFILE), F_SETFD, 1);
+ /*
+ * Send the commands to the command file, flush all its buffers then
+ * rewind and remove the thing.
+ */
+ noExec = FALSE;
+
+ /*
+ * used to be backwards; replace when start doing multiple commands
+ * per shell.
+ */
+ if (compatMake) {
+ /*
+ * Be compatible: If this is the first time for this node,
+ * verify its commands are ok and open the commands list for
+ * sequential access by later invocations of JobStart.
+ * Once that is done, we take the next command off the list
+ * and print it to the command file. If the command was an
+ * ellipsis, note that there's nothing more to execute.
+ */
+ if ((job->flags&JOB_FIRST) && (Lst_Open(gn->commands) != SUCCESS)){
+ cmdsOK = FALSE;
+ } else {
+ LstNode ln = Lst_Next (gn->commands);
+
+ if ((ln == NILLNODE) ||
+ JobPrintCommand ((char *)Lst_Datum (ln), job))
+ {
+ noExec = TRUE;
+ Lst_Close (gn->commands);
+ }
+ if (noExec && !(job->flags & JOB_FIRST)) {
+ /*
+ * If we're not going to execute anything, the job
+ * is done and we need to close down the various
+ * file descriptors we've opened for output, then
+ * call JobDoOutput to catch the final characters or
+ * send the file to the screen... Note that the i/o streams
+ * are only open if this isn't the first job.
+ * Note also that this could not be done in
+ * Job_CatchChildren b/c it wasn't clear if there were
+ * more commands to execute or not...
+ */
+ if (usePipes) {
+#ifdef RMT_WILL_WATCH
+ Rmt_Ignore(job->inPipe);
+#else
+ FD_CLR(job->inPipe, &outputs);
+#endif
+ if (job->outPipe != job->inPipe) {
+ (void)close (job->outPipe);
+ }
+ JobDoOutput (job, TRUE);
+ (void)close (job->inPipe);
+ } else {
+ (void)close (job->outFd);
+ JobDoOutput (job, TRUE);
+ }
+ }
+ }
+ } else {
+ /*
+ * We can do all the commands at once. hooray for sanity
+ */
+ numCommands = 0;
+ Lst_ForEach (gn->commands, JobPrintCommand, (ClientData)job);
+
+ /*
+ * If we didn't print out any commands to the shell script,
+ * there's not much point in executing the shell, is there?
+ */
+ if (numCommands == 0) {
+ noExec = TRUE;
+ }
+ }
+ } else if (noExecute) {
+ /*
+ * Not executing anything -- just print all the commands to stdout
+ * in one fell swoop. This will still set up job->tailCmds correctly.
+ */
+ if (lastNode != gn) {
+ printf (targFmt, gn->name);
+ lastNode = gn;
+ }
+ job->cmdFILE = stdout;
+ /*
+ * Only print the commands if they're ok, but don't die if they're
+ * not -- just let the user know they're bad and keep going. It
+ * doesn't do any harm in this case and may do some good.
+ */
+ if (cmdsOK) {
+ Lst_ForEach(gn->commands, JobPrintCommand, (ClientData)job);
+ }
+ /*
+ * Don't execute the shell, thank you.
+ */
+ noExec = TRUE;
+ } else {
+ /*
+ * Just touch the target and note that no shell should be executed.
+ * Set cmdFILE to stdout to make life easier. Check the commands, too,
+ * but don't die if they're no good -- it does no harm to keep working
+ * up the graph.
+ */
+ job->cmdFILE = stdout;
+ Job_Touch (gn, job->flags&JOB_SILENT);
+ noExec = TRUE;
+ }
+
+ /*
+ * If we're not supposed to execute a shell, don't.
+ */
+ if (noExec) {
+ /*
+ * Unlink and close the command file if we opened one
+ */
+ if (job->cmdFILE != stdout) {
+ (void) unlink (tfile);
+ fclose(job->cmdFILE);
+ } else {
+ fflush (stdout);
+ }
+
+ /*
+ * We only want to work our way up the graph if we aren't here because
+ * the commands for the job were no good.
+ */
+ if (cmdsOK) {
+ if (aborting == 0) {
+ if (job->tailCmds != NILLNODE) {
+ Lst_ForEachFrom(job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ (ClientData)job->node);
+ }
+ Make_Update(job->node);
+ }
+ free((Address)job);
+ return(JOB_FINISHED);
+ } else {
+ free((Address)job);
+ return(JOB_ERROR);
+ }
+ } else {
+ fflush (job->cmdFILE);
+ (void) unlink (tfile);
+ }
+
+ /*
+ * Set up the control arguments to the shell. This is based on the flags
+ * set earlier for this job.
+ */
+ JobMakeArgv(job, argv);
+
+ /*
+ * If we're using pipes to catch output, create the pipe by which we'll
+ * get the shell's output. If we're using files, print out that we're
+ * starting a job and then set up its temporary-file name. This is just
+ * tfile with two extra digits tacked on -- jobno.
+ */
+ if (job->flags & JOB_FIRST) {
+ if (usePipes) {
+ int fd[2];
+ (void) pipe(fd);
+ job->inPipe = fd[0];
+ job->outPipe = fd[1];
+ (void)fcntl (job->inPipe, F_SETFD, 1);
+ (void)fcntl (job->outPipe, F_SETFD, 1);
+ } else {
+ printf ("Remaking `%s'\n", gn->name);
+ fflush (stdout);
+ sprintf (job->outFile, "%s%02d", tfile, jobno);
+ jobno = (jobno + 1) % 100;
+ job->outFd = open(job->outFile,O_WRONLY|O_CREAT|O_APPEND,0600);
+ (void)fcntl (job->outFd, F_SETFD, 1);
+ }
+ }
+
+ local = TRUE;
+
+ if (local && (((nLocal >= maxLocal) &&
+ !(job->flags & JOB_SPECIAL) &&
+ (maxLocal != 0))))
+ {
+ /*
+ * The job can only be run locally, but we've hit the limit of
+ * local concurrency, so put the job on hold until some other job
+ * finishes. Note that the special jobs (.BEGIN, .INTERRUPT and .END)
+ * may be run locally even when the local limit has been reached
+ * (e.g. when maxLocal == 0), though they will be exported if at
+ * all possible.
+ */
+ jobFull = TRUE;
+
+ if (DEBUG(JOB)) {
+ printf("Can only run job locally.\n");
+ }
+ job->flags |= JOB_RESTART;
+ (void)Lst_AtEnd(stoppedJobs, (ClientData)job);
+ } else {
+ if ((nLocal >= maxLocal) && local) {
+ /*
+ * If we're running this job locally as a special case (see above),
+ * at least say the table is full.
+ */
+ jobFull = TRUE;
+ if (DEBUG(JOB)) {
+ printf("Local job queue is full.\n");
+ }
+ }
+ JobExec(job, argv);
+ }
+ return(JOB_RUNNING);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobDoOutput --
+ * This function is called at different times depending on
+ * whether the user has specified that output is to be collected
+ * via pipes or temporary files. In the former case, we are called
+ * whenever there is something to read on the pipe. We collect more
+ * output from the given job and store it in the job's outBuf. If
+ * this makes up a line, we print it tagged by the job's identifier,
+ * as necessary.
+ * If output has been collected in a temporary file, we open the
+ * file and read it line by line, transfering it to our own
+ * output channel until the file is empty. At which point we
+ * remove the temporary file.
+ * In both cases, however, we keep our figurative eye out for the
+ * 'noPrint' line for the shell from which the output came. If
+ * we recognize a line, we don't print it. If the command is not
+ * alone on the line (the character after it is not \0 or \n), we
+ * do print whatever follows it.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * curPos may be shifted as may the contents of outBuf.
+ *-----------------------------------------------------------------------
+ */
+static void
+JobDoOutput (job, finish)
+ register Job *job; /* the job whose output needs printing */
+ Boolean finish; /* TRUE if this is the last time we'll be
+ * called for this job */
+{
+ Boolean gotNL = FALSE; /* true if got a newline */
+ register int nr; /* number of bytes read */
+ register int i; /* auxiliary index into outBuf */
+ register int max; /* limit for i (end of current data) */
+ int nRead; /* (Temporary) number of bytes read */
+
+ FILE *oFILE; /* Stream pointer to shell's output file */
+ char inLine[132];
+
+
+ if (usePipes) {
+ /*
+ * Read as many bytes as will fit in the buffer.
+ */
+end_loop:
+
+ nRead = read (job->inPipe, &job->outBuf[job->curPos],
+ JOB_BUFSIZE - job->curPos);
+ if (nRead < 0) {
+ if (DEBUG(JOB)) {
+ perror("JobDoOutput(piperead)");
+ }
+ nr = 0;
+ } else {
+ nr = nRead;
+ }
+
+ /*
+ * If we hit the end-of-file (the job is dead), we must flush its
+ * remaining output, so pretend we read a newline if there's any
+ * output remaining in the buffer.
+ * Also clear the 'finish' flag so we stop looping.
+ */
+ if ((nr == 0) && (job->curPos != 0)) {
+ job->outBuf[job->curPos] = '\n';
+ nr = 1;
+ finish = FALSE;
+ } else if (nr == 0) {
+ finish = FALSE;
+ }
+
+ /*
+ * Look for the last newline in the bytes we just got. If there is
+ * one, break out of the loop with 'i' as its index and gotNL set
+ * TRUE.
+ */
+ max = job->curPos + nr;
+ for (i = job->curPos + nr - 1; i >= job->curPos; i--) {
+ if (job->outBuf[i] == '\n') {
+ gotNL = TRUE;
+ break;
+ } else if (job->outBuf[i] == '\0') {
+ /*
+ * Why?
+ */
+ job->outBuf[i] = ' ';
+ }
+ }
+
+ if (!gotNL) {
+ job->curPos += nr;
+ if (job->curPos == JOB_BUFSIZE) {
+ /*
+ * If we've run out of buffer space, we have no choice
+ * but to print the stuff. sigh.
+ */
+ gotNL = TRUE;
+ i = job->curPos;
+ }
+ }
+ if (gotNL) {
+ /*
+ * Need to send the output to the screen. Null terminate it
+ * first, overwriting the newline character if there was one.
+ * So long as the line isn't one we should filter (according
+ * to the shell description), we print the line, preceeded
+ * by a target banner if this target isn't the same as the
+ * one for which we last printed something.
+ * The rest of the data in the buffer are then shifted down
+ * to the start of the buffer and curPos is set accordingly.
+ */
+ job->outBuf[i] = '\0';
+ if (i >= job->curPos) {
+ register char *cp, *ecp;
+
+ cp = job->outBuf;
+ if (commandShell->noPrint) {
+ ecp = Str_FindSubstring(job->outBuf,
+ commandShell->noPrint);
+ while (ecp != (char *)NULL) {
+ if (cp != ecp) {
+ *ecp = '\0';
+ if (job->node != lastNode) {
+ printf (targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the non-printable comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ printf ("%s", cp);
+ }
+ cp = ecp + commandShell->noPLen;
+ if (cp != &job->outBuf[i]) {
+ /*
+ * Still more to print, look again after skipping
+ * the whitespace following the non-printable
+ * command....
+ */
+ cp++;
+ while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ }
+ ecp = Str_FindSubstring (cp,
+ commandShell->noPrint);
+ } else {
+ break;
+ }
+ }
+ }
+
+ /*
+ * There's still more in that thar buffer. This time, though,
+ * we know there's no newline at the end, so we add one of
+ * our own free will.
+ */
+ if (*cp != '\0') {
+ if (job->node != lastNode) {
+ printf (targFmt, job->node->name);
+ lastNode = job->node;
+ }
+ printf ("%s\n", cp);
+ }
+
+ fflush (stdout);
+ }
+ if (i < max - 1) {
+ /* shift the remaining characters down */
+ memcpy ( job->outBuf, &job->outBuf[i + 1], max - (i + 1));
+ job->curPos = max - (i + 1);
+
+ } else {
+ /*
+ * We have written everything out, so we just start over
+ * from the start of the buffer. No copying. No nothing.
+ */
+ job->curPos = 0;
+ }
+ }
+ if (finish) {
+ /*
+ * If the finish flag is true, we must loop until we hit
+ * end-of-file on the pipe. This is guaranteed to happen eventually
+ * since the other end of the pipe is now closed (we closed it
+ * explicitly and the child has exited). When we do get an EOF,
+ * finish will be set FALSE and we'll fall through and out.
+ */
+ goto end_loop;
+ }
+ } else {
+ /*
+ * We've been called to retrieve the output of the job from the
+ * temporary file where it's been squirreled away. This consists of
+ * opening the file, reading the output line by line, being sure not
+ * to print the noPrint line for the shell we used, then close and
+ * remove the temporary file. Very simple.
+ *
+ * Change to read in blocks and do FindSubString type things as for
+ * pipes? That would allow for "@echo -n..."
+ */
+ oFILE = fopen (job->outFile, "r");
+ if (oFILE != (FILE *) NULL) {
+ printf ("Results of making %s:\n", job->node->name);
+ while (fgets (inLine, sizeof(inLine), oFILE) != NULL) {
+ register char *cp, *ecp, *endp;
+
+ cp = inLine;
+ endp = inLine + strlen(inLine);
+ if (endp[-1] == '\n') {
+ *--endp = '\0';
+ }
+ if (commandShell->noPrint) {
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ while (ecp != (char *)NULL) {
+ if (cp != ecp) {
+ *ecp = '\0';
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the non-printable comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ printf ("%s", cp);
+ }
+ cp = ecp + commandShell->noPLen;
+ if (cp != endp) {
+ /*
+ * Still more to print, look again after skipping
+ * the whitespace following the non-printable
+ * command....
+ */
+ cp++;
+ while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ }
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ } else {
+ break;
+ }
+ }
+ }
+
+ /*
+ * There's still more in that thar buffer. This time, though,
+ * we know there's no newline at the end, so we add one of
+ * our own free will.
+ */
+ if (*cp != '\0') {
+ printf ("%s\n", cp);
+ }
+ }
+ fclose (oFILE);
+ (void) unlink (job->outFile);
+ }
+ }
+ fflush(stdout);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchChildren --
+ * Handle the exit of a child. Called from Make_Make.
+ *
+ * Results:
+ * none.
+ *
+ * Side Effects:
+ * The job descriptor is removed from the list of children.
+ *
+ * Notes:
+ * We do waits, blocking or not, according to the wisdom of our
+ * caller, until there are no more children to report. For each
+ * job, call JobFinish to finish things off. This will take care of
+ * putting jobs on the stoppedJobs queue.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_CatchChildren (block)
+ Boolean block; /* TRUE if should block on the wait. */
+{
+ int pid; /* pid of dead child */
+ register Job *job; /* job descriptor for dead child */
+ LstNode jnode; /* list element for finding job */
+ union wait status; /* Exit/termination status */
+
+ /*
+ * Don't even bother if we know there's no one around.
+ */
+ if (nLocal == 0) {
+ return;
+ }
+
+ while ((pid = wait3((int *)&status, (block?0:WNOHANG)|WUNTRACED,
+ (struct rusage *)0)) > 0)
+ {
+ if (DEBUG(JOB))
+ printf("Process %d exited or stopped.\n", pid);
+
+
+ jnode = Lst_Find (jobs, (ClientData)&pid, JobCmpPid);
+
+ if (jnode == NILLNODE) {
+ if (WIFSIGNALED(status) && (status.w_termsig == SIGCONT)) {
+ jnode = Lst_Find(stoppedJobs, (ClientData) &pid, JobCmpPid);
+ if (jnode == NILLNODE) {
+ Error("Resumed child (%d) not in table", pid);
+ continue;
+ }
+ job = (Job *)Lst_Datum(jnode);
+ (void)Lst_Remove(stoppedJobs, jnode);
+ } else {
+ Error ("Child (%d) not in table?", pid);
+ continue;
+ }
+ } else {
+ job = (Job *) Lst_Datum (jnode);
+ (void)Lst_Remove (jobs, jnode);
+ nJobs -= 1;
+ if (jobFull && DEBUG(JOB)) {
+ printf("Job queue is no longer full.\n");
+ }
+ jobFull = FALSE;
+ nLocal -= 1;
+ }
+
+ JobFinish (job, status);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchOutput --
+ * Catch the output from our children, if we're using
+ * pipes do so. Otherwise just block time until we get a
+ * signal (most likely a SIGCHLD) since there's no point in
+ * just spinning when there's nothing to do and the reaping
+ * of a child can wait for a while.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Output is read from pipes if we're piping.
+ * -----------------------------------------------------------------------
+ */
+void
+Job_CatchOutput ()
+{
+ int nfds;
+ struct timeval timeout;
+ fd_set readfds;
+ register LstNode ln;
+ register Job *job;
+#ifdef RMT_WILL_WATCH
+ int pnJobs; /* Previous nJobs */
+#endif
+
+ fflush(stdout);
+#ifdef RMT_WILL_WATCH
+ pnJobs = nJobs;
+
+ /*
+ * It is possible for us to be called with nJobs equal to 0. This happens
+ * if all the jobs finish and a job that is stopped cannot be run
+ * locally (eg if maxLocal is 0) and cannot be exported. The job will
+ * be placed back on the stoppedJobs queue, Job_Empty() will return false,
+ * Make_Run will call us again when there's nothing for which to wait.
+ * nJobs never changes, so we loop forever. Hence the check. It could
+ * be argued that we should sleep for a bit so as not to swamp the
+ * exportation system with requests. Perhaps we should.
+ *
+ * NOTE: IT IS THE RESPONSIBILITY OF Rmt_Wait TO CALL Job_CatchChildren
+ * IN A TIMELY FASHION TO CATCH ANY LOCALLY RUNNING JOBS THAT EXIT.
+ * It may use the variable nLocal to determine if it needs to call
+ * Job_CatchChildren (if nLocal is 0, there's nothing for which to
+ * wait...)
+ */
+ while (nJobs != 0 && pnJobs == nJobs) {
+ Rmt_Wait();
+ }
+#else
+ if (usePipes) {
+ readfds = outputs;
+ timeout.tv_sec = SEL_SEC;
+ timeout.tv_usec = SEL_USEC;
+
+ if ((nfds = select (FD_SETSIZE, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout)) < 0)
+ {
+ return;
+ } else {
+ if (Lst_Open (jobs) == FAILURE) {
+ Punt ("Cannot open job table");
+ }
+ while (nfds && (ln = Lst_Next (jobs)) != NILLNODE) {
+ job = (Job *) Lst_Datum (ln);
+ if (FD_ISSET(job->inPipe, &readfds)) {
+ JobDoOutput (job, FALSE);
+ nfds -= 1;
+ }
+ }
+ Lst_Close (jobs);
+ }
+ }
+#endif /* RMT_WILL_WATCH */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Make --
+ * Start the creation of a target. Basically a front-end for
+ * JobStart used by the Make module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Another job is started.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Make (gn)
+ GNode *gn;
+{
+ (void)JobStart (gn, 0, (Job *)NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Init --
+ * Initialize the process module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * lists and counters are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Init (maxproc, maxlocal)
+ int maxproc; /* the greatest number of jobs which may be
+ * running at one time */
+ int maxlocal; /* the greatest number of local jobs which may
+ * be running at once. */
+{
+ GNode *begin; /* node for commands to do at the very start */
+
+ sprintf (tfile, "/tmp/make%05d", getpid());
+
+ jobs = Lst_Init (FALSE);
+ stoppedJobs = Lst_Init(FALSE);
+ maxJobs = maxproc;
+ maxLocal = maxlocal;
+ nJobs = 0;
+ nLocal = 0;
+ jobFull = FALSE;
+
+ aborting = 0;
+ errors = 0;
+
+ lastNode = NILGNODE;
+
+ if (maxJobs == 1) {
+ /*
+ * If only one job can run at a time, there's no need for a banner,
+ * no is there?
+ */
+ targFmt = "";
+ } else {
+ targFmt = TARG_FMT;
+ }
+
+ if (shellPath == (char *) NULL) {
+ /*
+ * The user didn't specify a shell to use, so we are using the
+ * default one... Both the absolute path and the last component
+ * must be set. The last component is taken from the 'name' field
+ * of the default shell description pointed-to by commandShell.
+ * All default shells are located in _PATH_DEFSHELLDIR.
+ */
+ shellName = commandShell->name;
+ shellPath = str_concat (_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
+ }
+
+ if (commandShell->exit == (char *)NULL) {
+ commandShell->exit = "";
+ }
+ if (commandShell->echo == (char *)NULL) {
+ commandShell->echo = "";
+ }
+
+ /*
+ * Catch the four signals that POSIX specifies if they aren't ignored.
+ * JobPassSig will take care of calling JobInterrupt if appropriate.
+ */
+ if (signal (SIGINT, SIG_IGN) != SIG_IGN) {
+ signal (SIGINT, JobPassSig);
+ }
+ if (signal (SIGHUP, SIG_IGN) != SIG_IGN) {
+ signal (SIGHUP, JobPassSig);
+ }
+ if (signal (SIGQUIT, SIG_IGN) != SIG_IGN) {
+ signal (SIGQUIT, JobPassSig);
+ }
+ if (signal (SIGTERM, SIG_IGN) != SIG_IGN) {
+ signal (SIGTERM, JobPassSig);
+ }
+ /*
+ * There are additional signals that need to be caught and passed if
+ * either the export system wants to be told directly of signals or if
+ * we're giving each job its own process group (since then it won't get
+ * signals from the terminal driver as we own the terminal)
+ */
+#if defined(RMT_WANTS_SIGNALS) || defined(USE_PGRP)
+ if (signal (SIGTSTP, SIG_IGN) != SIG_IGN) {
+ signal (SIGTSTP, JobPassSig);
+ }
+ if (signal (SIGTTOU, SIG_IGN) != SIG_IGN) {
+ signal (SIGTTOU, JobPassSig);
+ }
+ if (signal (SIGTTIN, SIG_IGN) != SIG_IGN) {
+ signal (SIGTTIN, JobPassSig);
+ }
+ if (signal (SIGWINCH, SIG_IGN) != SIG_IGN) {
+ signal (SIGWINCH, JobPassSig);
+ }
+#endif
+
+ begin = Targ_FindNode (".BEGIN", TARG_NOCREATE);
+
+ if (begin != NILGNODE) {
+ JobStart (begin, JOB_SPECIAL, (Job *)0);
+ while (nJobs) {
+ Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+ Job_CatchChildren (!usePipes);
+#endif /* RMT_WILL_WATCH */
+ }
+ }
+ postCommands = Targ_FindNode (".END", TARG_CREATE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Full --
+ * See if the job table is full. It is considered full if it is OR
+ * if we are in the process of aborting OR if we have
+ * reached/exceeded our local quota. This prevents any more jobs
+ * from starting up.
+ *
+ * Results:
+ * TRUE if the job table is full, FALSE otherwise
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_Full ()
+{
+ return (aborting || jobFull);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Empty --
+ * See if the job table is empty. Because the local concurrency may
+ * be set to 0, it is possible for the job table to become empty,
+ * while the list of stoppedJobs remains non-empty. In such a case,
+ * we want to restart as many jobs as we can.
+ *
+ * Results:
+ * TRUE if it is. FALSE if it ain't.
+ *
+ * Side Effects:
+ * None.
+ *
+ * -----------------------------------------------------------------------
+ */
+Boolean
+Job_Empty ()
+{
+ if (nJobs == 0) {
+ if (!Lst_IsEmpty(stoppedJobs) && !aborting) {
+ /*
+ * The job table is obviously not full if it has no jobs in
+ * it...Try and restart the stopped jobs.
+ */
+ jobFull = FALSE;
+ while (!jobFull && !Lst_IsEmpty(stoppedJobs)) {
+ JobRestart((Job *)Lst_DeQueue(stoppedJobs));
+ }
+ return(FALSE);
+ } else {
+ return(TRUE);
+ }
+ } else {
+ return(FALSE);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMatchShell --
+ * Find a matching shell in 'shells' given its final component.
+ *
+ * Results:
+ * A pointer to the Shell structure.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Shell *
+JobMatchShell (name)
+ char *name; /* Final component of shell path */
+{
+ register Shell *sh; /* Pointer into shells table */
+ Shell *match; /* Longest-matching shell */
+ register char *cp1,
+ *cp2;
+ char *eoname;
+
+ eoname = name + strlen (name);
+
+ match = (Shell *) NULL;
+
+ for (sh = shells; sh->name != NULL; sh++) {
+ for (cp1 = eoname - strlen (sh->name), cp2 = sh->name;
+ *cp1 != '\0' && *cp1 == *cp2;
+ cp1++, cp2++) {
+ continue;
+ }
+ if (*cp1 != *cp2) {
+ continue;
+ } else if (match == (Shell *) NULL ||
+ strlen (match->name) < strlen (sh->name)) {
+ match = sh;
+ }
+ }
+ return (match == (Shell *) NULL ? sh : match);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ParseShell --
+ * Parse a shell specification and set up commandShell, shellPath
+ * and shellName appropriately.
+ *
+ * Results:
+ * FAILURE if the specification was incorrect.
+ *
+ * Side Effects:
+ * commandShell points to a Shell structure (either predefined or
+ * created from the shell spec), shellPath is the full path of the
+ * shell described by commandShell, while shellName is just the
+ * final component of shellPath.
+ *
+ * Notes:
+ * A shell specification consists of a .SHELL target, with dependency
+ * operator, followed by a series of blank-separated words. Double
+ * quotes can be used to use blanks in words. A backslash escapes
+ * anything (most notably a double-quote and a space) and
+ * provides the functionality it does in C. Each word consists of
+ * keyword and value separated by an equal sign. There should be no
+ * unnecessary spaces in the word. The keywords are as follows:
+ * name Name of shell.
+ * path Location of shell. Overrides "name" if given
+ * quiet Command to turn off echoing.
+ * echo Command to turn echoing on
+ * filter Result of turning off echoing that shouldn't be
+ * printed.
+ * echoFlag Flag to turn echoing on at the start
+ * errFlag Flag to turn error checking on at the start
+ * hasErrCtl True if shell has error checking control
+ * check Command to turn on error checking if hasErrCtl
+ * is TRUE or template of command to echo a command
+ * for which error checking is off if hasErrCtl is
+ * FALSE.
+ * ignore Command to turn off error checking if hasErrCtl
+ * is TRUE or template of command to execute a
+ * command so as to ignore any errors it returns if
+ * hasErrCtl is FALSE.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Job_ParseShell (line)
+ char *line; /* The shell spec */
+{
+ char **words;
+ int wordCount;
+ register char **argv;
+ register int argc;
+ char *path;
+ Shell newShell;
+ Boolean fullSpec = FALSE;
+
+ while (isspace (*line)) {
+ line++;
+ }
+ words = brk_string (line, &wordCount, TRUE);
+
+ memset ((Address)&newShell, 0, sizeof(newShell));
+
+ /*
+ * Parse the specification by keyword
+ */
+ for (path = (char *)NULL, argc = wordCount - 1, argv = words + 1;
+ argc != 0;
+ argc--, argv++) {
+ if (strncmp (*argv, "path=", 5) == 0) {
+ path = &argv[0][5];
+ } else if (strncmp (*argv, "name=", 5) == 0) {
+ newShell.name = &argv[0][5];
+ } else {
+ if (strncmp (*argv, "quiet=", 6) == 0) {
+ newShell.echoOff = &argv[0][6];
+ } else if (strncmp (*argv, "echo=", 5) == 0) {
+ newShell.echoOn = &argv[0][5];
+ } else if (strncmp (*argv, "filter=", 7) == 0) {
+ newShell.noPrint = &argv[0][7];
+ newShell.noPLen = strlen(newShell.noPrint);
+ } else if (strncmp (*argv, "echoFlag=", 9) == 0) {
+ newShell.echo = &argv[0][9];
+ } else if (strncmp (*argv, "errFlag=", 8) == 0) {
+ newShell.exit = &argv[0][8];
+ } else if (strncmp (*argv, "hasErrCtl=", 10) == 0) {
+ char c = argv[0][10];
+ newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
+ (c != 'T') && (c != 't'));
+ } else if (strncmp (*argv, "check=", 6) == 0) {
+ newShell.errCheck = &argv[0][6];
+ } else if (strncmp (*argv, "ignore=", 7) == 0) {
+ newShell.ignErr = &argv[0][7];
+ } else {
+ Parse_Error (PARSE_FATAL, "Unknown keyword \"%s\"",
+ *argv);
+ return (FAILURE);
+ }
+ fullSpec = TRUE;
+ }
+ }
+
+ if (path == (char *)NULL) {
+ /*
+ * If no path was given, the user wants one of the pre-defined shells,
+ * yes? So we find the one s/he wants with the help of JobMatchShell
+ * and set things up the right way. shellPath will be set up by
+ * Job_Init.
+ */
+ if (newShell.name == (char *)NULL) {
+ Parse_Error (PARSE_FATAL, "Neither path nor name specified");
+ return (FAILURE);
+ } else {
+ commandShell = JobMatchShell (newShell.name);
+ shellName = newShell.name;
+ }
+ } else {
+ /*
+ * The user provided a path. If s/he gave nothing else (fullSpec is
+ * FALSE), try and find a matching shell in the ones we know of.
+ * Else we just take the specification at its word and copy it
+ * to a new location. In either case, we need to record the
+ * path the user gave for the shell.
+ */
+ shellPath = path;
+ path = strrchr (path, '/');
+ if (path == (char *)NULL) {
+ path = shellPath;
+ } else {
+ path += 1;
+ }
+ if (newShell.name != (char *)NULL) {
+ shellName = newShell.name;
+ } else {
+ shellName = path;
+ }
+ if (!fullSpec) {
+ commandShell = JobMatchShell (shellName);
+ } else {
+ commandShell = (Shell *) emalloc(sizeof(Shell));
+ *commandShell = newShell;
+ }
+ }
+
+ if (commandShell->echoOn && commandShell->echoOff) {
+ commandShell->hasEchoCtl = TRUE;
+ }
+
+ if (!commandShell->hasErrCtl) {
+ if (commandShell->errCheck == (char *)NULL) {
+ commandShell->errCheck = "";
+ }
+ if (commandShell->ignErr == (char *)NULL) {
+ commandShell->ignErr = "%s\n";
+ }
+ }
+
+ /*
+ * Do not free up the words themselves, since they might be in use by the
+ * shell specification...
+ */
+ free (words);
+ return SUCCESS;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobInterrupt --
+ * Handle the receipt of an interrupt.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed. Another job will be started if the
+ * .INTERRUPT target was given.
+ *-----------------------------------------------------------------------
+ */
+static void
+JobInterrupt (runINTERRUPT)
+ int runINTERRUPT; /* Non-zero if commands for the .INTERRUPT
+ * target should be executed */
+{
+ LstNode ln; /* element in job table */
+ Job *job; /* job descriptor in that element */
+ GNode *interrupt; /* the node describing the .INTERRUPT target */
+
+ aborting = ABORT_INTERRUPT;
+
+ (void)Lst_Open (jobs);
+ while ((ln = Lst_Next (jobs)) != NILLNODE) {
+ job = (Job *) Lst_Datum (ln);
+
+ if (!Targ_Precious (job->node)) {
+ char *file = (job->node->path == (char *)NULL ?
+ job->node->name :
+ job->node->path);
+ struct stat st;
+ if (!noExecute && lstat(file, &st) != -1 && !S_ISDIR(st.st_mode) &&
+ unlink(file) != -1) {
+ Error ("*** %s removed", file);
+ }
+ }
+#ifdef RMT_WANTS_SIGNALS
+ if (job->flags & JOB_REMOTE) {
+ /*
+ * If job is remote, let the Rmt module do the killing.
+ */
+ if (!Rmt_Signal(job, SIGINT)) {
+ /*
+ * If couldn't kill the thing, finish it out now with an
+ * error code, since no exit report will come in likely.
+ */
+ union wait status;
+
+ status.w_status = 0;
+ status.w_retcode = 1;
+ JobFinish(job, status);
+ }
+ } else if (job->pid) {
+ KILL(job->pid, SIGINT);
+ }
+#else
+ if (job->pid) {
+ KILL(job->pid, SIGINT);
+ }
+#endif /* RMT_WANTS_SIGNALS */
+ }
+ Lst_Close (jobs);
+
+ if (runINTERRUPT && !touchFlag) {
+ interrupt = Targ_FindNode (".INTERRUPT", TARG_NOCREATE);
+ if (interrupt != NILGNODE) {
+ ignoreErrors = FALSE;
+
+ JobStart (interrupt, JOB_IGNDOTS, (Job *)0);
+ while (nJobs) {
+ Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+ Job_CatchChildren (!usePipes);
+#endif /* RMT_WILL_WATCH */
+ }
+ }
+ }
+ (void) unlink (tfile);
+ exit (0);
+}
+
+/*
+ *-----------------------------------------------------------------------
+ * Job_End --
+ * Do final processing such as the running of the commands
+ * attached to the .END target.
+ *
+ * Results:
+ * Number of errors reported.
+ *
+ * Side Effects:
+ * The process' temporary file (tfile) is removed if it still
+ * existed.
+ *-----------------------------------------------------------------------
+ */
+int
+Job_End ()
+{
+ if (postCommands != NILGNODE && !Lst_IsEmpty (postCommands->commands)) {
+ if (errors) {
+ Error ("Errors reported so .END ignored");
+ } else {
+ JobStart (postCommands, JOB_SPECIAL | JOB_IGNDOTS,
+ (Job *)0);
+
+ while (nJobs) {
+ Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+ Job_CatchChildren (!usePipes);
+#endif /* RMT_WILL_WATCH */
+ }
+ }
+ }
+ (void) unlink (tfile);
+ return(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Wait --
+ * Waits for all running jobs to finish and returns. Sets 'aborting'
+ * to ABORT_WAIT to prevent other jobs from starting.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Currently running jobs finish.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Wait()
+{
+ aborting = ABORT_WAIT;
+ while (nJobs != 0) {
+ Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+ Job_CatchChildren(!usePipes);
+#endif /* RMT_WILL_WATCH */
+ }
+ aborting = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_AbortAll --
+ * Abort all currently running jobs without handling output or anything.
+ * This function is to be called only in the event of a major
+ * error. Most definitely NOT to be called from JobInterrupt.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed, not just the firstborn
+ *-----------------------------------------------------------------------
+ */
+void
+Job_AbortAll ()
+{
+ LstNode ln; /* element in job table */
+ Job *job; /* the job descriptor in that element */
+ int foo;
+
+ aborting = ABORT_ERROR;
+
+ if (nJobs) {
+
+ (void)Lst_Open (jobs);
+ while ((ln = Lst_Next (jobs)) != NILLNODE) {
+ job = (Job *) Lst_Datum (ln);
+
+ /*
+ * kill the child process with increasingly drastic signals to make
+ * darn sure it's dead.
+ */
+#ifdef RMT_WANTS_SIGNALS
+ if (job->flags & JOB_REMOTE) {
+ Rmt_Signal(job, SIGINT);
+ Rmt_Signal(job, SIGKILL);
+ } else {
+ KILL(job->pid, SIGINT);
+ KILL(job->pid, SIGKILL);
+ }
+#else
+ KILL(job->pid, SIGINT);
+ KILL(job->pid, SIGKILL);
+#endif /* RMT_WANTS_SIGNALS */
+ }
+ }
+
+ /*
+ * Catch as many children as want to report in at first, then give up
+ */
+ while (wait3(&foo, WNOHANG, (struct rusage *)0) > 0)
+ continue;
+ (void) unlink (tfile);
+}
diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h
new file mode 100644
index 000000000000..5feb43fb6e43
--- /dev/null
+++ b/usr.bin/make/job.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)job.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*-
+ * job.h --
+ * Definitions pertaining to the running of jobs in parallel mode.
+ * Exported from job.c for the use of remote-execution modules.
+ */
+#ifndef _JOB_H_
+#define _JOB_H_
+
+#define TMPPAT "/tmp/makeXXXXX"
+
+/*
+ * The SEL_ constants determine the maximum amount of time spent in select
+ * before coming out to see if a child has finished. SEL_SEC is the number of
+ * seconds and SEL_USEC is the number of micro-seconds
+ */
+#define SEL_SEC 0
+#define SEL_USEC 500000
+
+
+/*-
+ * Job Table definitions.
+ *
+ * Each job has several things associated with it:
+ * 1) The process id of the child shell
+ * 2) The graph node describing the target being made by this job
+ * 3) A LstNode for the first command to be saved after the job
+ * completes. This is NILLNODE if there was no "..." in the job's
+ * commands.
+ * 4) An FILE* for writing out the commands. This is only
+ * used before the job is actually started.
+ * 5) A union of things used for handling the shell's output. Different
+ * parts of the union are used based on the value of the usePipes
+ * flag. If it is true, the output is being caught via a pipe and
+ * the descriptors of our pipe, an array in which output is line
+ * buffered and the current position in that buffer are all
+ * maintained for each job. If, on the other hand, usePipes is false,
+ * the output is routed to a temporary file and all that is kept
+ * is the name of the file and the descriptor open to the file.
+ * 6) An identifier provided by and for the exclusive use of the
+ * Rmt module.
+ * 7) A word of flags which determine how the module handles errors,
+ * echoing, etc. for the job
+ *
+ * The job "table" is kept as a linked Lst in 'jobs', with the number of
+ * active jobs maintained in the 'nJobs' variable. At no time will this
+ * exceed the value of 'maxJobs', initialized by the Job_Init function.
+ *
+ * When a job is finished, the Make_Update function is called on each of the
+ * parents of the node which was just remade. This takes care of the upward
+ * traversal of the dependency graph.
+ */
+#define JOB_BUFSIZE 1024
+typedef struct Job {
+ int pid; /* The child's process ID */
+ GNode *node; /* The target the child is making */
+ LstNode tailCmds; /* The node of the first command to be
+ * saved when the job has been run */
+ FILE *cmdFILE; /* When creating the shell script, this is
+ * where the commands go */
+ int rmtID; /* ID returned from Rmt module */
+ short flags; /* Flags to control treatment of job */
+#define JOB_IGNERR 0x001 /* Ignore non-zero exits */
+#define JOB_SILENT 0x002 /* no output */
+#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally
+ * if we can't export it and maxLocal is 0 */
+#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing
+ * commands */
+#define JOB_REMOTE 0x010 /* Job is running remotely */
+#define JOB_FIRST 0x020 /* Job is first job for the node */
+#define JOB_REMIGRATE 0x040 /* Job needs to be remigrated */
+#define JOB_RESTART 0x080 /* Job needs to be completely restarted */
+#define JOB_RESUME 0x100 /* Job needs to be resumed b/c it stopped,
+ * for some reason */
+#define JOB_CONTINUING 0x200 /* We are in the process of resuming this job.
+ * Used to avoid infinite recursion between
+ * JobFinish and JobRestart */
+ union {
+ struct {
+ int op_inPipe; /* Input side of pipe associated
+ * with job's output channel */
+ int op_outPipe; /* Output side of pipe associated with
+ * job's output channel */
+ char op_outBuf[JOB_BUFSIZE + 1];
+ /* Buffer for storing the output of the
+ * job, line by line */
+ int op_curPos; /* Current position in op_outBuf */
+ } o_pipe; /* data used when catching the output via
+ * a pipe */
+ struct {
+ char of_outFile[sizeof(TMPPAT)+2];
+ /* Name of file to which shell output
+ * was rerouted */
+ int of_outFd; /* Stream open to the output
+ * file. Used to funnel all
+ * from a single job to one file
+ * while still allowing
+ * multiple shell invocations */
+ } o_file; /* Data used when catching the output in
+ * a temporary file */
+ } output; /* Data for tracking a shell's output */
+} Job;
+
+#define outPipe output.o_pipe.op_outPipe
+#define inPipe output.o_pipe.op_inPipe
+#define outBuf output.o_pipe.op_outBuf
+#define curPos output.o_pipe.op_curPos
+#define outFile output.o_file.of_outFile
+#define outFd output.o_file.of_outFd
+
+
+/*-
+ * Shell Specifications:
+ * Each shell type has associated with it the following information:
+ * 1) The string which must match the last character of the shell name
+ * for the shell to be considered of this type. The longest match
+ * wins.
+ * 2) A command to issue to turn off echoing of command lines
+ * 3) A command to issue to turn echoing back on again
+ * 4) What the shell prints, and its length, when given the echo-off
+ * command. This line will not be printed when received from the shell
+ * 5) A boolean to tell if the shell has the ability to control
+ * error checking for individual commands.
+ * 6) The string to turn this checking on.
+ * 7) The string to turn it off.
+ * 8) The command-flag to give to cause the shell to start echoing
+ * commands right away.
+ * 9) The command-flag to cause the shell to Lib_Exit when an error is
+ * detected in one of the commands.
+ *
+ * Some special stuff goes on if a shell doesn't have error control. In such
+ * a case, errCheck becomes a printf template for echoing the command,
+ * should echoing be on and ignErr becomes another printf template for
+ * executing the command while ignoring the return status. If either of these
+ * strings is empty when hasErrCtl is FALSE, the command will be executed
+ * anyway as is and if it causes an error, so be it.
+ */
+typedef struct Shell {
+ char *name; /* the name of the shell. For Bourne and C
+ * shells, this is used only to find the
+ * shell description when used as the single
+ * source of a .SHELL target. For user-defined
+ * shells, this is the full path of the shell.
+ */
+ Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */
+ char *echoOff; /* command to turn off echo */
+ char *echoOn; /* command to turn it back on again */
+ char *noPrint; /* command to skip when printing output from
+ * shell. This is usually the command which
+ * was executed to turn off echoing */
+ int noPLen; /* length of noPrint command */
+ Boolean hasErrCtl; /* set if can control error checking for
+ * individual commands */
+ char *errCheck; /* string to turn error checking on */
+ char *ignErr; /* string to turn off error checking */
+ /*
+ * command-line flags
+ */
+ char *echo; /* echo commands */
+ char *exit; /* exit on error */
+} Shell;
+
+
+extern char *targFmt; /* Format string for banner that separates
+ * output from multiple jobs. Contains a
+ * single %s where the name of the node being
+ * made should be put. */
+extern GNode *lastNode; /* Last node for which a banner was printed.
+ * If Rmt module finds it necessary to print
+ * a banner, it should set this to the node
+ * for which the banner was printed */
+extern int nJobs; /* Number of jobs running (local and remote) */
+extern int nLocal; /* Number of jobs running locally */
+extern Lst jobs; /* List of active job descriptors */
+extern Lst stoppedJobs; /* List of jobs that are stopped or didn't
+ * quite get started */
+extern Boolean jobFull; /* Non-zero if no more jobs should/will start*/
+
+
+void Job_Touch __P((GNode *, Boolean));
+Boolean Job_CheckCommands __P((GNode *, void (*abortProc )(char *, ...)));
+void Job_CatchChildren __P((Boolean));
+void Job_CatchOutput __P((void));
+void Job_Make __P((GNode *));
+void Job_Init __P((int, int));
+Boolean Job_Full __P((void));
+Boolean Job_Empty __P((void));
+ReturnStatus Job_ParseShell __P((char *));
+int Job_End __P((void));
+void Job_Wait __P((void));
+void Job_AbortAll __P((void));
+void JobFlagForMigration __P((int));
+
+#endif /* _JOB_H_ */
diff --git a/usr.bin/make/list.h b/usr.bin/make/list.h
new file mode 100644
index 000000000000..41b9a1930293
--- /dev/null
+++ b/usr.bin/make/list.h
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)list.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*
+ * list.h --
+ *
+ * Structures, macros, and routines exported by the List module.
+ */
+
+#ifndef _LIST
+#define _LIST
+
+#ifndef _SPRITE
+#include "sprite.h"
+#endif _SPRITE
+
+/*
+ * This module defines the list abstraction, which enables one to link
+ * together arbitrary data structures. Lists are doubly-linked and
+ * circular. A list contains a header followed by its real members, if
+ * any. (An empty list therefore consists of a single element, the
+ * header, whose nextPtr and prevPtr fields point to itself). To refer
+ * to a list as a whole, the user keeps a pointer to the header; that
+ * header is initialized by a call to List_Init(), which creates an empty
+ * list given a pointer to a List_Links structure (described below).
+ *
+ * The links are contained in a two-element structure called List_Links.
+ * A list joins List_Links records (that is, each List_Links structure
+ * points to other List_Links structures), but if the List_Links is the
+ * first field within a larger structure, then the larger structures are
+ * effectively linked together as follows:
+ *
+ * header
+ * (List_Links) first elt. second elt.
+ * ----------------- ----------------- -----------------
+ * ..-> | nextPtr | ----> | List_Links | ----> | List_Links |----..
+ * | - - - - - - - | | | | |
+ * ..-- | prevPtr | <---- | | <---- | |<---..
+ * ----------------- - --- --- --- - - --- --- --- -
+ * | rest of | | rest of |
+ * | structure | | structure |
+ * | | | |
+ * | ... | | ... |
+ * ----------------- -----------------
+ *
+ * It is possible to link structures through List_Links fields that are
+ * not at the beginning of the larger structure, but it is then necessary
+ * to perform pointer arithmetic to find the beginning of the larger
+ * structure, given a pointer to some point within it.
+ *
+ * A typical structure might be something like:
+ *
+ * typedef struct {
+ * List_Links links;
+ * char ch;
+ * integer flags;
+ * } EditChar;
+ *
+ * Before an element is inserted in a list for the first time, it must
+ * be initialized by calling the macro List_InitElement().
+ */
+
+
+/*
+ * data structure for lists
+ */
+
+typedef struct List_Links {
+ struct List_Links *prevPtr;
+ struct List_Links *nextPtr;
+} List_Links;
+
+/*
+ * procedures
+ */
+
+void List_Init(); /* initialize a header to a list */
+void List_Insert(); /* insert an element into a list */
+void List_Remove(); /* remove an element from a list */
+void List_Move(); /* move an element elsewhere in a list */
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_InitElement --
+ *
+ * Initialize a list element. Must be called before an element is first
+ * inserted into a list.
+ *
+ * ----------------------------------------------------------------------------
+ */
+#define List_InitElement(elementPtr) \
+ (elementPtr)->prevPtr = (List_Links *) NIL; \
+ (elementPtr)->nextPtr = (List_Links *) NIL;
+
+/*
+ * Macros for stepping through or selecting parts of lists
+ */
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * LIST_FORALL --
+ *
+ * Macro to loop through a list and perform an operation on each member.
+ *
+ * Usage: LIST_FORALL(headerPtr, itemPtr) {
+ * / *
+ * * operation on itemPtr, which points to successive members
+ * * of the list
+ * *
+ * * It may be appropriate to first assign
+ * * foobarPtr = (Foobar *) itemPtr;
+ * * to refer to the entire Foobar structure.
+ * * /
+ * }
+ *
+ * Note: itemPtr must be a List_Links pointer variable, and headerPtr
+ * must evaluate to a pointer to a List_Links structure.
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define LIST_FORALL(headerPtr, itemPtr) \
+ for (itemPtr = List_First(headerPtr); \
+ !List_IsAtEnd((headerPtr),itemPtr); \
+ itemPtr = List_Next(itemPtr))
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_IsEmpty --
+ *
+ * Macro: Boolean value, TRUE if the given list does not contain any
+ * members.
+ *
+ * Usage: if (List_IsEmpty(headerPtr)) ...
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define List_IsEmpty(headerPtr) \
+ ((headerPtr) == (headerPtr)->nextPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_IsAtEnd --
+ *
+ * Macro: Boolean value, TRUE if itemPtr is after the end of headerPtr
+ * (i.e., itemPtr is the header of the list).
+ *
+ * Usage: if (List_IsAtEnd(headerPtr, itemPtr)) ...
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+
+#define List_IsAtEnd(headerPtr, itemPtr) \
+ ((itemPtr) == (headerPtr))
+
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_First --
+ *
+ * Macro to return the first member in a list, which is the header if
+ * the list is empty.
+ *
+ * Usage: firstPtr = List_First(headerPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define List_First(headerPtr) ((headerPtr)->nextPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Last --
+ *
+ * Macro to return the last member in a list, which is the header if
+ * the list is empty.
+ *
+ * Usage: lastPtr = List_Last(headerPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define List_Last(headerPtr) ((headerPtr)->prevPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Prev --
+ *
+ * Macro to return the member preceding the given member in its list.
+ * If the given list member is the first element in the list, List_Prev
+ * returns the list header.
+ *
+ * Usage: prevPtr = List_Prev(itemPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define List_Prev(itemPtr) ((itemPtr)->prevPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Next --
+ *
+ * Macro to return the member following the given member in its list.
+ * If the given list member is the last element in the list, List_Next
+ * returns the list header.
+ *
+ * Usage: nextPtr = List_Next(itemPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define List_Next(itemPtr) ((itemPtr)->nextPtr)
+
+
+/*
+ * ----------------------------------------------------------------------------
+ * The List_Insert procedure takes two arguments. The first argument
+ * is a pointer to the structure to be inserted into a list, and
+ * the second argument is a pointer to the list member after which
+ * the new element is to be inserted. Macros are used to determine
+ * which existing member will precede the new one.
+ *
+ * The List_Move procedure takes a destination argument with the same
+ * semantics as List_Insert.
+ *
+ * The following macros define where to insert the new element
+ * in the list:
+ *
+ * LIST_AFTER(itemPtr) -- insert after itemPtr
+ * LIST_BEFORE(itemPtr) -- insert before itemPtr
+ * LIST_ATFRONT(headerPtr) -- insert at front of list
+ * LIST_ATREAR(headerPtr) -- insert at end of list
+ *
+ * For example,
+ *
+ * List_Insert(itemPtr, LIST_AFTER(otherPtr));
+ *
+ * will insert itemPtr following otherPtr in the list containing otherPtr.
+ * ----------------------------------------------------------------------------
+ */
+
+#define LIST_AFTER(itemPtr) ((List_Links *) itemPtr)
+
+#define LIST_BEFORE(itemPtr) (((List_Links *) itemPtr)->prevPtr)
+
+#define LIST_ATFRONT(headerPtr) ((List_Links *) headerPtr)
+
+#define LIST_ATREAR(headerPtr) (((List_Links *) headerPtr)->prevPtr)
+
+#endif /* _LIST */
diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h
new file mode 100644
index 000000000000..8d8abe04c814
--- /dev/null
+++ b/usr.bin/make/lst.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lst.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*-
+ * lst.h --
+ * Header for using the list library
+ */
+#ifndef _LST_H_
+#define _LST_H_
+
+#include <sprite.h>
+#if __STDC__
+#include <stdlib.h>
+#endif
+
+/*
+ * basic typedef. This is what the Lst_ functions handle
+ */
+
+typedef struct Lst *Lst;
+typedef struct LstNode *LstNode;
+
+#define NILLST ((Lst) NIL)
+#define NILLNODE ((LstNode) NIL)
+
+/*
+ * NOFREE can be used as the freeProc to Lst_Destroy when the elements are
+ * not to be freed.
+ * NOCOPY performs similarly when given as the copyProc to Lst_Duplicate.
+ */
+#define NOFREE ((void (*) __P((ClientData))) 0)
+#define NOCOPY ((ClientData (*) __P((ClientData))) 0)
+
+#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */
+#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */
+
+/*
+ * Creation/destruction functions
+ */
+/* Create a new list */
+Lst Lst_Init __P((Boolean));
+/* Duplicate an existing list */
+Lst Lst_Duplicate __P((Lst, ClientData (*)(ClientData)));
+/* Destroy an old one */
+void Lst_Destroy __P((Lst, void (*)(ClientData)));
+/* True if list is empty */
+Boolean Lst_IsEmpty __P((Lst));
+
+/*
+ * Functions to modify a list
+ */
+/* Insert an element before another */
+ReturnStatus Lst_Insert __P((Lst, LstNode, ClientData));
+/* Insert an element after another */
+ReturnStatus Lst_Append __P((Lst, LstNode, ClientData));
+/* Place an element at the front of a lst. */
+ReturnStatus Lst_AtFront __P((Lst, ClientData));
+/* Place an element at the end of a lst. */
+ReturnStatus Lst_AtEnd __P((Lst, ClientData));
+/* Remove an element */
+ReturnStatus Lst_Remove __P((Lst, LstNode));
+/* Replace a node with a new value */
+ReturnStatus Lst_Replace __P((LstNode, ClientData));
+/* Concatenate two lists */
+ReturnStatus Lst_Concat __P((Lst, Lst, int));
+
+/*
+ * Node-specific functions
+ */
+/* Return first element in list */
+LstNode Lst_First __P((Lst));
+/* Return last element in list */
+LstNode Lst_Last __P((Lst));
+/* Return successor to given element */
+LstNode Lst_Succ __P((LstNode));
+/* Get datum from LstNode */
+ClientData Lst_Datum __P((LstNode));
+
+/*
+ * Functions for entire lists
+ */
+/* Find an element in a list */
+LstNode Lst_Find __P((Lst, ClientData,
+ int (*)(ClientData, ClientData)));
+/* Find an element starting from somewhere */
+LstNode Lst_FindFrom __P((Lst, LstNode, ClientData,
+ int (*cProc)(ClientData, ClientData)));
+/*
+ * See if the given datum is on the list. Returns the LstNode containing
+ * the datum
+ */
+LstNode Lst_Member __P((Lst, ClientData));
+/* Apply a function to all elements of a lst */
+void Lst_ForEach __P((Lst, int (*)(ClientData, ClientData),
+ ClientData));
+/*
+ * Apply a function to all elements of a lst starting from a certain point.
+ * If the list is circular, the application will wrap around to the
+ * beginning of the list again.
+ */
+void Lst_ForEachFrom __P((Lst, LstNode,
+ int (*)(ClientData, ClientData),
+ ClientData));
+/*
+ * these functions are for dealing with a list as a table, of sorts.
+ * An idea of the "current element" is kept and used by all the functions
+ * between Lst_Open() and Lst_Close().
+ */
+/* Open the list */
+ReturnStatus Lst_Open __P((Lst));
+/* Next element please */
+LstNode Lst_Next __P((Lst));
+/* Done yet? */
+Boolean Lst_IsAtEnd __P((Lst));
+/* Finish table access */
+void Lst_Close __P((Lst));
+
+/*
+ * for using the list as a queue
+ */
+/* Place an element at tail of queue */
+ReturnStatus Lst_EnQueue __P((Lst, ClientData));
+/* Remove an element from head of queue */
+ClientData Lst_DeQueue __P((Lst));
+
+#endif /* _LST_H_ */
diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c
new file mode 100644
index 000000000000..93800db01a0b
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAppend.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstAppend.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstAppend.c --
+ * Add a new node with a new datum after an existing node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Append --
+ * Create a new node and add it to the given list after the given node.
+ *
+ * Results:
+ * SUCCESS if all went well.
+ *
+ * Side Effects:
+ * A new ListNode is created and linked in to the List. The lastPtr
+ * field of the List will be altered if ln is the last node in the
+ * list. lastPtr and firstPtr will alter if the list was empty and
+ * ln was NILLNODE.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Append (l, ln, d)
+ Lst l; /* affected list */
+ LstNode ln; /* node after which to append the datum */
+ ClientData d; /* said datum */
+{
+ register List list;
+ register ListNode lNode;
+ register ListNode nLNode;
+
+ if (LstValid (l) && (ln == NILLNODE && LstIsEmpty (l))) {
+ goto ok;
+ }
+
+ if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+ ok:
+
+ list = (List)l;
+ lNode = (ListNode)ln;
+
+ PAlloc (nLNode, ListNode);
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (lNode == NilListNode) {
+ if (list->isCirc) {
+ nLNode->nextPtr = nLNode->prevPtr = nLNode;
+ } else {
+ nLNode->nextPtr = nLNode->prevPtr = NilListNode;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode;
+ nLNode->nextPtr = lNode->nextPtr;
+
+ lNode->nextPtr = nLNode;
+ if (nLNode->nextPtr != NilListNode) {
+ nLNode->nextPtr->prevPtr = nLNode;
+ }
+
+ if (lNode == list->lastPtr) {
+ list->lastPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c
new file mode 100644
index 000000000000..e61a28c0673f
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtEnd.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstAtEnd.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstAtEnd.c --
+ * Add a node at the end of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtEnd --
+ * Add a node to the end of the given list
+ *
+ * Results:
+ * SUCCESS if life is good.
+ *
+ * Side Effects:
+ * A new ListNode is created and added to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtEnd (l, d)
+ Lst l; /* List to which to add the datum */
+ ClientData d; /* Datum to add */
+{
+ register LstNode end;
+
+ end = Lst_Last (l);
+ return (Lst_Append (l, end, d));
+}
diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c
new file mode 100644
index 000000000000..5e8b7f1ab4c2
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtFront.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstAtFront.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstAtFront.c --
+ * Add a node at the front of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtFront --
+ * Place a piece of data at the front of a list
+ *
+ * Results:
+ * SUCCESS or FAILURE
+ *
+ * Side Effects:
+ * A new ListNode is created and stuck at the front of the list.
+ * hence, firstPtr (and possible lastPtr) in the list are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtFront (l, d)
+ Lst l;
+ ClientData d;
+{
+ register LstNode front;
+
+ front = Lst_First (l);
+ return (Lst_Insert (l, front, d));
+}
diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c
new file mode 100644
index 000000000000..6f9174f3db8a
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstClose.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstClose.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstClose.c --
+ * Close a list for sequential access.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Close --
+ * Close a list which was opened for sequential access.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The list is closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Close (l)
+ Lst l; /* The list to close */
+{
+ register List list = (List) l;
+
+ if (LstValid(l) == TRUE) {
+ list->isOpen = FALSE;
+ list->atEnd = Unknown;
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c
new file mode 100644
index 000000000000..e7726a862b60
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstConcat.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstConcat.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * listConcat.c --
+ * Function to concatentate two lists.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Concat --
+ * Concatenate two lists. New elements are created to hold the data
+ * elements, if specified, but the elements themselves are not copied.
+ * If the elements should be duplicated to avoid confusion with another
+ * list, the Lst_Duplicate function should be called first.
+ * If LST_CONCLINK is specified, the second list is destroyed since
+ * its pointers have been corrupted and the list is no longer useable.
+ *
+ * Results:
+ * SUCCESS if all went well. FAILURE otherwise.
+ *
+ * Side Effects:
+ * New elements are created and appended the the first list.
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Concat (l1, l2, flags)
+ Lst l1; /* The list to which l2 is to be appended */
+ Lst l2; /* The list to append to l1 */
+ int flags; /* LST_CONCNEW if LstNode's should be duplicated
+ * LST_CONCLINK if should just be relinked */
+{
+ register ListNode ln; /* original LstNode */
+ register ListNode nln; /* new LstNode */
+ register ListNode last; /* the last element in the list. Keeps
+ * bookkeeping until the end */
+ register List list1 = (List)l1;
+ register List list2 = (List)l2;
+
+ if (!LstValid (l1) || !LstValid (l2)) {
+ return (FAILURE);
+ }
+
+ if (flags == LST_CONCLINK) {
+ if (list2->firstPtr != NilListNode) {
+ /*
+ * We set the nextPtr of the
+ * last element of list two to be NIL to make the loop easier and
+ * so we don't need an extra case should the first list turn
+ * out to be non-circular -- the final element will already point
+ * to NIL space and the first element will be untouched if it
+ * existed before and will also point to NIL space if it didn't.
+ */
+ list2->lastPtr->nextPtr = NilListNode;
+ /*
+ * So long as the second list isn't empty, we just link the
+ * first element of the second list to the last element of the
+ * first list. If the first list isn't empty, we then link the
+ * last element of the list to the first element of the second list
+ * The last element of the second list, if it exists, then becomes
+ * the last element of the first list.
+ */
+ list2->firstPtr->prevPtr = list1->lastPtr;
+ if (list1->lastPtr != NilListNode) {
+ list1->lastPtr->nextPtr = list2->firstPtr;
+ } else {
+ list1->firstPtr = list2->firstPtr;
+ }
+ list1->lastPtr = list2->lastPtr;
+ }
+ if (list1->isCirc && list1->firstPtr != NilListNode) {
+ /*
+ * If the first list is supposed to be circular and it is (now)
+ * non-empty, we must make sure it's circular by linking the
+ * first element to the last and vice versa
+ */
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ }
+ free ((Address)l2);
+ } else if (list2->firstPtr != NilListNode) {
+ /*
+ * We set the nextPtr of the last element of list 2 to be nil to make
+ * the loop less difficult. The loop simply goes through the entire
+ * second list creating new LstNodes and filling in the nextPtr, and
+ * prevPtr to fit into l1 and its datum field from the
+ * datum field of the corresponding element in l2. The 'last' node
+ * follows the last of the new nodes along until the entire l2 has
+ * been appended. Only then does the bookkeeping catch up with the
+ * changes. During the first iteration of the loop, if 'last' is nil,
+ * the first list must have been empty so the newly-created node is
+ * made the first node of the list.
+ */
+ list2->lastPtr->nextPtr = NilListNode;
+ for (last = list1->lastPtr, ln = list2->firstPtr;
+ ln != NilListNode;
+ ln = ln->nextPtr)
+ {
+ PAlloc (nln, ListNode);
+ nln->datum = ln->datum;
+ if (last != NilListNode) {
+ last->nextPtr = nln;
+ } else {
+ list1->firstPtr = nln;
+ }
+ nln->prevPtr = last;
+ nln->flags = nln->useCount = 0;
+ last = nln;
+ }
+
+ /*
+ * Finish bookkeeping. The last new element becomes the last element
+ * of list one.
+ */
+ list1->lastPtr = last;
+
+ /*
+ * The circularity of both list one and list two must be corrected
+ * for -- list one because of the new nodes added to it; list two
+ * because of the alteration of list2->lastPtr's nextPtr to ease the
+ * above for loop.
+ */
+ if (list1->isCirc) {
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ } else {
+ last->nextPtr = NilListNode;
+ }
+
+ if (list2->isCirc) {
+ list2->lastPtr->nextPtr = list2->firstPtr;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c
new file mode 100644
index 000000000000..6b0988e0fa2b
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDatum.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstDatum.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstDatum.c --
+ * Return the datum associated with a list node.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Datum --
+ * Return the datum stored in the given node.
+ *
+ * Results:
+ * The datum or (ick!) NIL if the node is invalid.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+ClientData
+Lst_Datum (ln)
+ LstNode ln;
+{
+ if (ln != NILLNODE) {
+ return (((ListNode)ln)->datum);
+ } else {
+ return ((ClientData) NIL);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c
new file mode 100644
index 000000000000..4b901257b001
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDeQueue.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstDeQueue.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstDeQueue.c --
+ * Remove the node and return its datum from the head of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_DeQueue --
+ * Remove and return the datum at the head of the given list.
+ *
+ * Results:
+ * The datum in the node at the head or (ick) NIL if the list
+ * is empty.
+ *
+ * Side Effects:
+ * The head node is removed from the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ClientData
+Lst_DeQueue (l)
+ Lst l;
+{
+ ClientData rd;
+ register ListNode tln;
+
+ tln = (ListNode) Lst_First (l);
+ if (tln == NilListNode) {
+ return ((ClientData) NIL);
+ }
+
+ rd = tln->datum;
+ if (Lst_Remove (l, (LstNode)tln) == FAILURE) {
+ return ((ClientData) NIL);
+ } else {
+ return (rd);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c
new file mode 100644
index 000000000000..56ea5c528fe6
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDestroy.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstDestroy.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstDestroy.c --
+ * Nuke a list and all its resources
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Destroy --
+ * Destroy a list and free all its resources. If the freeProc is
+ * given, it is called with the datum from each node in turn before
+ * the node is freed.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is freed in its entirety.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Destroy (l, freeProc)
+ Lst l;
+ register void (*freeProc) __P((ClientData));
+{
+ register ListNode ln;
+ register ListNode tln = NilListNode;
+ register List list = (List)l;
+
+ if (l == NILLST || ! l) {
+ /*
+ * Note the check for l == (Lst)0 to catch uninitialized static Lst's.
+ * Gross, but useful.
+ */
+ return;
+ }
+
+ /* To ease scanning */
+ if (list->lastPtr != NilListNode)
+ list->lastPtr->nextPtr = NilListNode;
+ else {
+ free ((Address)l);
+ return;
+ }
+
+ if (freeProc) {
+ for (ln = list->firstPtr; ln != NilListNode; ln = tln) {
+ tln = ln->nextPtr;
+ (*freeProc) (ln->datum);
+ free ((Address)ln);
+ }
+ } else {
+ for (ln = list->firstPtr; ln != NilListNode; ln = tln) {
+ tln = ln->nextPtr;
+ free ((Address)ln);
+ }
+ }
+
+ free ((Address)l);
+}
diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c
new file mode 100644
index 000000000000..184b386228d3
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDupl.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstDupl.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * listDupl.c --
+ * Duplicate a list. This includes duplicating the individual
+ * elements.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Duplicate --
+ * Duplicate an entire list. If a function to copy a ClientData is
+ * given, the individual client elements will be duplicated as well.
+ *
+ * Results:
+ * The new Lst structure or NILLST if failure.
+ *
+ * Side Effects:
+ * A new list is created.
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Duplicate (l, copyProc)
+ Lst l; /* the list to duplicate */
+ /* A function to duplicate each ClientData */
+ ClientData (*copyProc) __P((ClientData));
+{
+ register Lst nl;
+ register ListNode ln;
+ register List list = (List)l;
+
+ if (!LstValid (l)) {
+ return (NILLST);
+ }
+
+ nl = Lst_Init (list->isCirc);
+ if (nl == NILLST) {
+ return (NILLST);
+ }
+
+ ln = list->firstPtr;
+ while (ln != NilListNode) {
+ if (copyProc != NOCOPY) {
+ if (Lst_AtEnd (nl, (*copyProc) (ln->datum)) == FAILURE) {
+ return (NILLST);
+ }
+ } else if (Lst_AtEnd (nl, ln->datum) == FAILURE) {
+ return (NILLST);
+ }
+
+ if (list->isCirc && ln == list->lastPtr) {
+ ln = NilListNode;
+ } else {
+ ln = ln->nextPtr;
+ }
+ }
+
+ return (nl);
+}
diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c
new file mode 100644
index 000000000000..1016c88d2834
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstEnQueue.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstEnQueue.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstEnQueue.c--
+ * Treat the list as a queue and place a datum at its end
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_EnQueue --
+ * Add the datum to the tail of the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE as returned by Lst_Append.
+ *
+ * Side Effects:
+ * the lastPtr field is altered all the time and the firstPtr field
+ * will be altered if the list used to be empty.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_EnQueue (l, d)
+ Lst l;
+ ClientData d;
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+
+ return (Lst_Append (l, Lst_Last(l), d));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c
new file mode 100644
index 000000000000..bab85ccd173c
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFind.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstFind.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstFind.c --
+ * Find a node on a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Find --
+ * Find a node on the given list using the given comparison function
+ * and the given datum.
+ *
+ * Results:
+ * The found node or NILLNODE if none matches.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Find (l, d, cProc)
+ Lst l;
+ ClientData d;
+ int (*cProc) __P((ClientData, ClientData));
+{
+ return (Lst_FindFrom (l, Lst_First(l), d, cProc));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c
new file mode 100644
index 000000000000..08de990afe5c
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFindFrom.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstFindFrom.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstFindFrom.c --
+ * Find a node on a list from a given starting point. Used by Lst_Find.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_FindFrom --
+ * Search for a node starting and ending with the given one on the
+ * given list using the passed datum and comparison function to
+ * determine when it has been found.
+ *
+ * Results:
+ * The found node or NILLNODE
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_FindFrom (l, ln, d, cProc)
+ Lst l;
+ register LstNode ln;
+ register ClientData d;
+ register int (*cProc) __P((ClientData, ClientData));
+{
+ register ListNode tln;
+ Boolean found = FALSE;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return (NILLNODE);
+ }
+
+ tln = (ListNode)ln;
+
+ do {
+ if ((*cProc) (tln->datum, d) == 0) {
+ found = TRUE;
+ break;
+ } else {
+ tln = tln->nextPtr;
+ }
+ } while (tln != (ListNode)ln && tln != NilListNode);
+
+ if (found) {
+ return ((LstNode)tln);
+ } else {
+ return (NILLNODE);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c
new file mode 100644
index 000000000000..9a95018777ab
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFirst.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstFirst.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstFirst.c --
+ * Return the first node of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_First --
+ * Return the first node on the given list.
+ *
+ * Results:
+ * The first node or NILLNODE if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_First (l)
+ Lst l;
+{
+ if (!LstValid (l) || LstIsEmpty (l)) {
+ return (NILLNODE);
+ } else {
+ return ((LstNode)((List)l)->firstPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c
new file mode 100644
index 000000000000..1d6ba9396dc6
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEach.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstForEach.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstForeach.c --
+ * Perform a given function on all elements of a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEach --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if Lst_ForEach should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+void
+Lst_ForEach (l, proc, d)
+ Lst l;
+ register int (*proc) __P((ClientData, ClientData));
+ register ClientData d;
+{
+ Lst_ForEachFrom(l, Lst_First(l), proc, d);
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c
new file mode 100644
index 000000000000..7c9e51e749f0
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEachFrom.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstForEachFrom.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * lstForEachFrom.c --
+ * Perform a given function on all elements of a list starting from
+ * a given point.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEachFrom --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if traversal should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+void
+Lst_ForEachFrom (l, ln, proc, d)
+ Lst l;
+ LstNode ln;
+ register int (*proc)();
+ register ClientData d;
+{
+ register ListNode tln = (ListNode)ln;
+ register List list = (List)l;
+ register ListNode next;
+ Boolean done;
+ int result;
+
+ if (!LstValid (list) || LstIsEmpty (list)) {
+ return;
+ }
+
+ do {
+ /*
+ * Take care of having the current element deleted out from under
+ * us.
+ */
+
+ next = tln->nextPtr;
+
+ (void) tln->useCount++;
+ result = (*proc) (tln->datum, d);
+ (void) tln->useCount--;
+
+ /*
+ * We're done with the traversal if
+ * - nothing's been added after the current node and
+ * - the next node to examine is the first in the queue or
+ * doesn't exist.
+ */
+ done = (next == tln->nextPtr &&
+ (next == NilListNode || next == list->firstPtr));
+
+ next = tln->nextPtr;
+
+ if (tln->flags & LN_DELETED) {
+ free((char *)tln);
+ }
+ tln = next;
+ } while (!result && !LstIsEmpty(list) && !done);
+
+}
diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c
new file mode 100644
index 000000000000..17276be6f1e7
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInit.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstInit.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * init.c --
+ * Initialize a new linked list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Init --
+ * Create and initialize a new list.
+ *
+ * Results:
+ * The created list.
+ *
+ * Side Effects:
+ * A list is created, what else?
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Init(circ)
+ Boolean circ; /* TRUE if the list should be made circular */
+{
+ register List nList;
+
+ PAlloc (nList, List);
+
+ nList->firstPtr = NilListNode;
+ nList->lastPtr = NilListNode;
+ nList->isOpen = FALSE;
+ nList->isCirc = circ;
+ nList->atEnd = Unknown;
+
+ return ((Lst)nList);
+}
diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c
new file mode 100644
index 000000000000..724924adf071
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInsert.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstInsert.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstInsert.c --
+ * Insert a new datum before an old one
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Insert --
+ * Insert a new node with the given piece of data before the given
+ * node in the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * the firstPtr field will be changed if ln is the first node in the
+ * list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Insert (l, ln, d)
+ Lst l; /* list to manipulate */
+ LstNode ln; /* node before which to insert d */
+ ClientData d; /* datum to be inserted */
+{
+ register ListNode nLNode; /* new lnode for d */
+ register ListNode lNode = (ListNode)ln;
+ register List list = (List)l;
+
+
+ /*
+ * check validity of arguments
+ */
+ if (LstValid (l) && (LstIsEmpty (l) && ln == NILLNODE))
+ goto ok;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ ok:
+ PAlloc (nLNode, ListNode);
+
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (ln == NILLNODE) {
+ if (list->isCirc) {
+ nLNode->prevPtr = nLNode->nextPtr = nLNode;
+ } else {
+ nLNode->prevPtr = nLNode->nextPtr = NilListNode;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode->prevPtr;
+ nLNode->nextPtr = lNode;
+
+ if (nLNode->prevPtr != NilListNode) {
+ nLNode->prevPtr->nextPtr = nLNode;
+ }
+ lNode->prevPtr = nLNode;
+
+ if (lNode == list->firstPtr) {
+ list->firstPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h
new file mode 100644
index 000000000000..1253660f8e81
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInt.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstInt.h 8.2 (Berkeley) 4/28/95
+ */
+
+/*-
+ * lstInt.h --
+ * Internals for the list library
+ */
+#ifndef _LSTINT_H_
+#define _LSTINT_H_
+
+#include "lst.h"
+
+typedef struct ListNode {
+ struct ListNode *prevPtr; /* previous element in list */
+ struct ListNode *nextPtr; /* next in list */
+ short useCount:8, /* Count of functions using the node.
+ * node may not be deleted until count
+ * goes to 0 */
+ flags:8; /* Node status flags */
+ ClientData datum; /* datum associated with this element */
+} *ListNode;
+/*
+ * Flags required for synchronization
+ */
+#define LN_DELETED 0x0001 /* List node should be removed when done */
+
+#define NilListNode ((ListNode)-1)
+
+typedef enum {
+ Head, Middle, Tail, Unknown
+} Where;
+
+typedef struct {
+ ListNode firstPtr; /* first node in list */
+ ListNode lastPtr; /* last node in list */
+ Boolean isCirc; /* true if the list should be considered
+ * circular */
+/*
+ * fields for sequential access
+ */
+ Where atEnd; /* Where in the list the last access was */
+ Boolean isOpen; /* true if list has been Lst_Open'ed */
+ ListNode curPtr; /* current node, if open. NilListNode if
+ * *just* opened */
+ ListNode prevPtr; /* Previous node, if open. Used by
+ * Lst_Remove */
+} *List;
+
+#define NilList ((List)-1)
+
+/*
+ * PAlloc (var, ptype) --
+ * Allocate a pointer-typedef structure 'ptype' into the variable 'var'
+ */
+#define PAlloc(var,ptype) var = (ptype) malloc (sizeof (*var))
+
+/*
+ * LstValid (l) --
+ * Return TRUE if the list l is valid
+ */
+#define LstValid(l) (((Lst)l == NILLST) ? FALSE : TRUE)
+
+/*
+ * LstNodeValid (ln, l) --
+ * Return TRUE if the LstNode ln is valid with respect to l
+ */
+#define LstNodeValid(ln, l) ((((LstNode)ln) == NILLNODE) ? FALSE : TRUE)
+
+/*
+ * LstIsEmpty (l) --
+ * TRUE if the list l is empty.
+ */
+#define LstIsEmpty(l) (((List)l)->firstPtr == NilListNode)
+
+#endif _LSTINT_H_
diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c
new file mode 100644
index 000000000000..7fdf68dca49c
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsAtEnd.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstIsAtEnd.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstIsAtEnd.c --
+ * Tell if the current node is at the end of the list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsAtEnd --
+ * Return true if have reached the end of the given list.
+ *
+ * Results:
+ * TRUE if at the end of the list (this includes the list not being
+ * open or being invalid) or FALSE if not. We return TRUE if the list
+ * is invalid or unopend so as to cause the caller to exit its loop
+ * asap, the assumption being that the loop is of the form
+ * while (!Lst_IsAtEnd (l)) {
+ * ...
+ * }
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsAtEnd (l)
+ Lst l;
+{
+ register List list = (List) l;
+
+ return (!LstValid (l) || !list->isOpen ||
+ (list->atEnd == Head) || (list->atEnd == Tail));
+}
+
diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c
new file mode 100644
index 000000000000..da475611bfa8
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsEmpty.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstIsEmpty.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstIsEmpty.c --
+ * A single function to decide if a list is empty
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsEmpty --
+ * Return TRUE if the given list is empty.
+ *
+ * Results:
+ * TRUE if the list is empty, FALSE otherwise.
+ *
+ * Side Effects:
+ * None.
+ *
+ * A list is considered empty if its firstPtr == NilListNode (or if
+ * the list itself is NILLIST).
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsEmpty (l)
+ Lst l;
+{
+ return ( ! LstValid (l) || LstIsEmpty(l));
+}
+
diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c
new file mode 100644
index 000000000000..a87c50c0f837
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstLast.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstLast.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstLast.c --
+ * Return the last element of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Last --
+ * Return the last node on the list l.
+ *
+ * Results:
+ * The requested node or NILLNODE if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Last (l)
+ Lst l;
+{
+ if (!LstValid(l) || LstIsEmpty (l)) {
+ return (NILLNODE);
+ } else {
+ return ((LstNode)((List)l)->lastPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c
new file mode 100644
index 000000000000..a6145ee64fd3
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstMember.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstMember.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * lstMember.c --
+ * See if a given datum is on a given list.
+ */
+
+#include "lstInt.h"
+
+LstNode
+Lst_Member (l, d)
+ Lst l;
+ ClientData d;
+{
+ List list = (List) l;
+ register ListNode lNode;
+
+ lNode = list->firstPtr;
+ if (lNode == NilListNode) {
+ return NILLNODE;
+ }
+
+ do {
+ if (lNode->datum == d) {
+ return (LstNode)lNode;
+ }
+ lNode = lNode->nextPtr;
+ } while (lNode != NilListNode && lNode != list->firstPtr);
+
+ return NILLNODE;
+}
diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c
new file mode 100644
index 000000000000..b6986d866d59
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstNext.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstNext.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstNext.c --
+ * Return the next node for a list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Next --
+ * Return the next node for the given list.
+ *
+ * Results:
+ * The next node or NILLNODE if the list has yet to be opened. Also
+ * if the list is non-circular and the end has been reached, NILLNODE
+ * is returned.
+ *
+ * Side Effects:
+ * the curPtr field is updated.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Next (l)
+ Lst l;
+{
+ register ListNode tln;
+ register List list = (List)l;
+
+ if ((LstValid (l) == FALSE) ||
+ (list->isOpen == FALSE)) {
+ return (NILLNODE);
+ }
+
+ list->prevPtr = list->curPtr;
+
+ if (list->curPtr == NilListNode) {
+ if (list->atEnd == Unknown) {
+ /*
+ * If we're just starting out, atEnd will be Unknown.
+ * Then we want to start this thing off in the right
+ * direction -- at the start with atEnd being Middle.
+ */
+ list->curPtr = tln = list->firstPtr;
+ list->atEnd = Middle;
+ } else {
+ tln = NilListNode;
+ list->atEnd = Tail;
+ }
+ } else {
+ tln = list->curPtr->nextPtr;
+ list->curPtr = tln;
+
+ if (tln == list->firstPtr || tln == NilListNode) {
+ /*
+ * If back at the front, then we've hit the end...
+ */
+ list->atEnd = Tail;
+ } else {
+ /*
+ * Reset to Middle if gone past first.
+ */
+ list->atEnd = Middle;
+ }
+ }
+
+ return ((LstNode)tln);
+}
+
diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c
new file mode 100644
index 000000000000..9da8cfab5769
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstOpen.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstOpen.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstOpen.c --
+ * Open a list for sequential access. The sequential functions access the
+ * list in a slightly different way. CurPtr points to their idea of the
+ * current node in the list and they access the list based on it.
+ * If the list is circular, Lst_Next and Lst_Prev will go around
+ * the list forever. Lst_IsAtEnd must be used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Open --
+ * Open a list for sequential access. A list can still be searched,
+ * etc., without confusing these functions.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * isOpen is set TRUE and curPtr is set to NilListNode so the
+ * other sequential functions no it was just opened and can choose
+ * the first element accessed based on this.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Open (l)
+ register Lst l;
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+ ((List) l)->isOpen = TRUE;
+ ((List) l)->atEnd = LstIsEmpty (l) ? Head : Unknown;
+ ((List) l)->curPtr = NilListNode;
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c
new file mode 100644
index 000000000000..98eb3ce53493
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstRemove.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstRemove.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstRemove.c --
+ * Remove an element from a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Remove --
+ * Remove the given node from the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The list's firstPtr will be set to NilListNode if ln is the last
+ * node on the list. firsPtr and lastPtr will be altered if ln is
+ * either the first or last node, respectively, on the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Remove (l, ln)
+ Lst l;
+ LstNode ln;
+{
+ register List list = (List) l;
+ register ListNode lNode = (ListNode) ln;
+
+ if (!LstValid (l) ||
+ !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ /*
+ * unlink it from the list
+ */
+ if (lNode->nextPtr != NilListNode) {
+ lNode->nextPtr->prevPtr = lNode->prevPtr;
+ }
+ if (lNode->prevPtr != NilListNode) {
+ lNode->prevPtr->nextPtr = lNode->nextPtr;
+ }
+
+ /*
+ * if either the firstPtr or lastPtr of the list point to this node,
+ * adjust them accordingly
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = lNode->nextPtr;
+ }
+ if (list->lastPtr == lNode) {
+ list->lastPtr = lNode->prevPtr;
+ }
+
+ /*
+ * Sequential access stuff. If the node we're removing is the current
+ * node in the list, reset the current node to the previous one. If the
+ * previous one was non-existent (prevPtr == NilListNode), we set the
+ * end to be Unknown, since it is.
+ */
+ if (list->isOpen && (list->curPtr == lNode)) {
+ list->curPtr = list->prevPtr;
+ if (list->curPtr == NilListNode) {
+ list->atEnd = Unknown;
+ }
+ }
+
+ /*
+ * the only way firstPtr can still point to ln is if ln is the last
+ * node on the list (the list is circular, so lNode->nextptr == lNode in
+ * this case). The list is, therefore, empty and is marked as such
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = NilListNode;
+ }
+
+ /*
+ * note that the datum is unmolested. The caller must free it as
+ * necessary and as expected.
+ */
+ if (lNode->useCount == 0) {
+ free ((Address)ln);
+ } else {
+ lNode->flags |= LN_DELETED;
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c
new file mode 100644
index 000000000000..8adb6895aec1
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstReplace.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstReplace.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstReplace.c --
+ * Replace the datum in a node with a new datum
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Replace --
+ * Replace the datum in the given node with the new datum
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The datum field fo the node is altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Replace (ln, d)
+ register LstNode ln;
+ ClientData d;
+{
+ if (ln == NILLNODE) {
+ return (FAILURE);
+ } else {
+ ((ListNode) ln)->datum = d;
+ return (SUCCESS);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c
new file mode 100644
index 000000000000..a5875f686874
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstSucc.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lstSucc.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * LstSucc.c --
+ * return the successor to a given node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Succ --
+ * Return the sucessor to the given node on its list.
+ *
+ * Results:
+ * The successor of the node, if it exists (note that on a circular
+ * list, if the node is the only one in the list, it is its own
+ * successor).
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Succ (ln)
+ LstNode ln;
+{
+ if (ln == NILLNODE) {
+ return (NILLNODE);
+ } else {
+ return ((LstNode) ((ListNode) ln)->nextPtr);
+ }
+}
+
diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c
new file mode 100644
index 000000000000..8607fbb08bb6
--- /dev/null
+++ b/usr.bin/make/main.c
@@ -0,0 +1,954 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1988, 1989, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)main.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+
+/*-
+ * main.c --
+ * The main file for this entire program. Exit routines etc
+ * reside here.
+ *
+ * Utility functions defined in this file:
+ * Main_ParseArgLine Takes a line of arguments, breaks them and
+ * treats them as if they were given when first
+ * invoked. Used by the parse module to implement
+ * the .MFLAGS target.
+ *
+ * Error Print a tagged error message. The global
+ * MAKE variable must have been defined. This
+ * takes a format string and two optional
+ * arguments for it.
+ *
+ * Fatal Print an error message and exit. Also takes
+ * a format string and two arguments.
+ *
+ * Punt Aborts all jobs and exits with a message. Also
+ * takes a format string and two arguments.
+ *
+ * Finish Finish things up by printing the number of
+ * errors which occured, as passed to it, and
+ * exiting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#if __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+
+#ifndef DEFMAXLOCAL
+#define DEFMAXLOCAL DEFMAXJOBS
+#endif DEFMAXLOCAL
+
+#define MAKEFLAGS ".MAKEFLAGS"
+
+Lst create; /* Targets to be made */
+time_t now; /* Time at start of make */
+GNode *DEFAULT; /* .DEFAULT node */
+Boolean allPrecious; /* .PRECIOUS given on line by itself */
+
+static Boolean noBuiltins; /* -r flag */
+static Lst makefiles; /* ordered list of makefiles to read */
+int maxJobs; /* -J argument */
+static int maxLocal; /* -L argument */
+Boolean compatMake; /* -B argument */
+Boolean debug; /* -d flag */
+Boolean noExecute; /* -n flag */
+Boolean keepgoing; /* -k flag */
+Boolean queryFlag; /* -q flag */
+Boolean touchFlag; /* -t flag */
+Boolean usePipes; /* !-P flag */
+Boolean ignoreErrors; /* -i flag */
+Boolean beSilent; /* -s flag */
+Boolean oldVars; /* variable substitution style */
+Boolean checkEnvFirst; /* -e flag */
+static Boolean jobsRunning; /* TRUE if the jobs might be running */
+
+static Boolean ReadMakefile();
+static void usage();
+
+static char *curdir; /* startup directory */
+static char *objdir; /* where we chdir'ed to */
+
+/*-
+ * MainParseArgs --
+ * Parse a given argument vector. Called from main() and from
+ * Main_ParseArgLine() when the .MAKEFLAGS target is used.
+ *
+ * XXX: Deal with command line overriding .MAKEFLAGS in makefile
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Various global and local flags will be set depending on the flags
+ * given
+ */
+static void
+MainParseArgs(argc, argv)
+ int argc;
+ char **argv;
+{
+ extern int optind;
+ extern char *optarg;
+ int c;
+
+ optind = 1; /* since we're called more than once */
+#ifdef notyet
+# define OPTFLAGS "BD:I:L:PSd:ef:ij:knqrst"
+#else
+# define OPTFLAGS "D:I:d:ef:ij:knqrst"
+#endif
+rearg: while((c = getopt(argc, argv, OPTFLAGS)) != EOF) {
+ switch(c) {
+ case 'D':
+ Var_Set(optarg, "1", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+ break;
+ case 'I':
+ Parse_AddIncludeDir(optarg);
+ Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+ break;
+#ifdef notyet
+ case 'B':
+ compatMake = TRUE;
+ break;
+ case 'L':
+ maxLocal = atoi(optarg);
+ Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+ break;
+ case 'P':
+ usePipes = FALSE;
+ Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL);
+ break;
+ case 'S':
+ keepgoing = FALSE;
+ Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
+ break;
+#endif
+ case 'd': {
+ char *modules = optarg;
+
+ for (; *modules; ++modules)
+ switch (*modules) {
+ case 'A':
+ debug = ~0;
+ break;
+ case 'a':
+ debug |= DEBUG_ARCH;
+ break;
+ case 'c':
+ debug |= DEBUG_COND;
+ break;
+ case 'd':
+ debug |= DEBUG_DIR;
+ break;
+ case 'f':
+ debug |= DEBUG_FOR;
+ break;
+ case 'g':
+ if (modules[1] == '1') {
+ debug |= DEBUG_GRAPH1;
+ ++modules;
+ }
+ else if (modules[1] == '2') {
+ debug |= DEBUG_GRAPH2;
+ ++modules;
+ }
+ break;
+ case 'j':
+ debug |= DEBUG_JOB;
+ break;
+ case 'm':
+ debug |= DEBUG_MAKE;
+ break;
+ case 's':
+ debug |= DEBUG_SUFF;
+ break;
+ case 't':
+ debug |= DEBUG_TARG;
+ break;
+ case 'v':
+ debug |= DEBUG_VAR;
+ break;
+ default:
+ (void)fprintf(stderr,
+ "make: illegal argument to d option -- %c\n",
+ *modules);
+ usage();
+ }
+ Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+ break;
+ }
+ case 'e':
+ checkEnvFirst = TRUE;
+ Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
+ break;
+ case 'f':
+ (void)Lst_AtEnd(makefiles, (ClientData)optarg);
+ break;
+ case 'i':
+ ignoreErrors = TRUE;
+ Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
+ break;
+ case 'j':
+ maxJobs = atoi(optarg);
+ Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+ break;
+ case 'k':
+ keepgoing = TRUE;
+ Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
+ break;
+ case 'n':
+ noExecute = TRUE;
+ Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL);
+ break;
+ case 'q':
+ queryFlag = TRUE;
+ /* Kind of nonsensical, wot? */
+ Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL);
+ break;
+ case 'r':
+ noBuiltins = TRUE;
+ Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL);
+ break;
+ case 's':
+ beSilent = TRUE;
+ Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL);
+ break;
+ case 't':
+ touchFlag = TRUE;
+ Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL);
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ }
+
+ oldVars = TRUE;
+
+ /*
+ * See if the rest of the arguments are variable assignments and
+ * perform them if so. Else take them to be targets and stuff them
+ * on the end of the "create" list.
+ */
+ for (argv += optind, argc -= optind; *argv; ++argv, --argc)
+ if (Parse_IsVar(*argv))
+ Parse_DoVar(*argv, VAR_CMD);
+ else {
+ if (!**argv)
+ Punt("illegal (null) argument.");
+ if (**argv == '-') {
+ if ((*argv)[1])
+ optind = 0; /* -flag... */
+ else
+ optind = 1; /* - */
+ goto rearg;
+ }
+ (void)Lst_AtEnd(create, (ClientData)strdup(*argv));
+ }
+}
+
+/*-
+ * Main_ParseArgLine --
+ * Used by the parse module when a .MFLAGS or .MAKEFLAGS target
+ * is encountered and by main() when reading the .MAKEFLAGS envariable.
+ * Takes a line of arguments and breaks it into its
+ * component words and passes those words and the number of them to the
+ * MainParseArgs function.
+ * The line should have all its leading whitespace removed.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Only those that come from the various arguments.
+ */
+void
+Main_ParseArgLine(line)
+ char *line; /* Line to fracture */
+{
+ char **argv; /* Manufactured argument vector */
+ int argc; /* Number of arguments in argv */
+
+ if (line == NULL)
+ return;
+ for (; *line == ' '; ++line)
+ continue;
+ if (!*line)
+ return;
+
+ argv = brk_string(line, &argc, TRUE);
+ MainParseArgs(argc, argv);
+}
+
+/*-
+ * main --
+ * The main function, for obvious reasons. Initializes variables
+ * and a few modules, then parses the arguments give it in the
+ * environment and on the command line. Reads the system makefile
+ * followed by either Makefile, makefile or the file given by the
+ * -f argument. Sets the .MAKEFLAGS PMake variable based on all the
+ * flags it has received by then uses either the Make or the Compat
+ * module to create the initial list of targets.
+ *
+ * Results:
+ * If -q was given, exits -1 if anything was out-of-date. Else it exits
+ * 0.
+ *
+ * Side Effects:
+ * The program exits when done. Targets are created. etc. etc. etc.
+ */
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ Lst targs; /* target nodes to create -- passed to Make_Init */
+ Boolean outOfDate = TRUE; /* FALSE if all targets up to date */
+ struct stat sb, sa;
+ char *p, *p1, *path, *pwd, *getenv(), *getwd();
+ char mdpath[MAXPATHLEN + 1];
+ char obpath[MAXPATHLEN + 1];
+ char cdpath[MAXPATHLEN + 1];
+ struct utsname utsname;
+ char *machine = getenv("MACHINE");
+
+ /*
+ * Find where we are and take care of PWD for the automounter...
+ * All this code is so that we know where we are when we start up
+ * on a different machine with pmake.
+ */
+ curdir = cdpath;
+ if (getcwd(curdir, MAXPATHLEN) == NULL) {
+ (void)fprintf(stderr, "make: %s.\n", strerror(errno));
+ exit(2);
+ }
+
+ if (stat(curdir, &sa) == -1) {
+ (void)fprintf(stderr, "make: %s: %s.\n",
+ curdir, strerror(errno));
+ exit(2);
+ }
+
+ if ((pwd = getenv("PWD")) != NULL) {
+ if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino &&
+ sa.st_dev == sb.st_dev)
+ (void) strcpy(curdir, pwd);
+ }
+
+ /*
+ * Get the name of this type of MACHINE from utsname
+ * so we can share an executable for similar machines.
+ * (i.e. m68k: amiga hp300, mac68k, sun3, ...)
+ *
+ * Note that while MACHINE is decided at run-time,
+ * MACHINE_ARCH is always known at compile time.
+ */
+ if (!machine) {
+ if (uname(&utsname)) {
+ perror("make: uname");
+ exit(2);
+ }
+ machine = utsname.machine;
+ }
+
+ /*
+ * if the MAKEOBJDIR (or by default, the _PATH_OBJDIR) directory
+ * exists, change into it and build there. Once things are
+ * initted, have to add the original directory to the search path,
+ * and modify the paths for the Makefiles apropriately. The
+ * current directory is also placed as a variable for make scripts.
+ */
+ if (!(path = getenv("MAKEOBJDIR"))) {
+ path = _PATH_OBJDIR;
+ (void) sprintf(mdpath, "%s.%s", path, machine);
+ }
+ else
+ (void) strncpy(mdpath, path, MAXPATHLEN + 1);
+
+ if (stat(mdpath, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+
+ if (chdir(mdpath)) {
+ (void)fprintf(stderr, "make warning: %s: %s.\n",
+ mdpath, strerror(errno));
+ objdir = curdir;
+ }
+ else {
+ if (mdpath[0] != '/') {
+ (void) sprintf(obpath, "%s/%s", curdir, mdpath);
+ objdir = obpath;
+ }
+ else
+ objdir = mdpath;
+ }
+ }
+ else {
+ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+
+ if (chdir(path)) {
+ (void)fprintf(stderr, "make warning: %s: %s.\n",
+ path, strerror(errno));
+ objdir = curdir;
+ }
+ else {
+ if (path[0] != '/') {
+ (void) sprintf(obpath, "%s/%s", curdir,
+ path);
+ objdir = obpath;
+ }
+ else
+ objdir = obpath;
+ }
+ }
+ else
+ objdir = curdir;
+ }
+
+ setenv("PWD", objdir, 1);
+
+ create = Lst_Init(FALSE);
+ makefiles = Lst_Init(FALSE);
+ beSilent = FALSE; /* Print commands as executed */
+ ignoreErrors = FALSE; /* Pay attention to non-zero returns */
+ noExecute = FALSE; /* Execute all commands */
+ keepgoing = FALSE; /* Stop on error */
+ allPrecious = FALSE; /* Remove targets when interrupted */
+ queryFlag = FALSE; /* This is not just a check-run */
+ noBuiltins = FALSE; /* Read the built-in rules */
+ touchFlag = FALSE; /* Actually update targets */
+ usePipes = TRUE; /* Catch child output in pipes */
+ debug = 0; /* No debug verbosity, please. */
+ jobsRunning = FALSE;
+
+ maxJobs = DEFMAXJOBS; /* Set default max concurrency */
+ maxLocal = DEFMAXLOCAL; /* Set default local max concurrency */
+#ifdef notyet
+ compatMake = FALSE; /* No compat mode */
+#else
+ compatMake = TRUE; /* No compat mode */
+#endif
+
+
+ /*
+ * Initialize the parsing, directory and variable modules to prepare
+ * for the reading of inclusion paths and variable settings on the
+ * command line
+ */
+ Dir_Init(); /* Initialize directory structures so -I flags
+ * can be processed correctly */
+ Parse_Init(); /* Need to initialize the paths of #include
+ * directories */
+ Var_Init(); /* As well as the lists of variables for
+ * parsing arguments */
+ str_init();
+ if (objdir != curdir)
+ Dir_AddDir(dirSearchPath, curdir);
+ Var_Set(".CURDIR", curdir, VAR_GLOBAL);
+ Var_Set(".OBJDIR", objdir, VAR_GLOBAL);
+
+ /*
+ * Initialize various variables.
+ * MAKE also gets this name, for compatibility
+ * .MAKEFLAGS gets set to the empty string just in case.
+ * MFLAGS also gets initialized empty, for compatibility.
+ */
+ Var_Set("MAKE", argv[0], VAR_GLOBAL);
+ Var_Set(MAKEFLAGS, "", VAR_GLOBAL);
+ Var_Set("MFLAGS", "", VAR_GLOBAL);
+ Var_Set("MACHINE", machine, VAR_GLOBAL);
+#ifdef MACHINE_ARCH
+ Var_Set("MACHINE_ARCH", MACHINE_ARCH, VAR_GLOBAL);
+#endif
+
+ /*
+ * First snag any flags out of the MAKE environment variable.
+ * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
+ * in a different format).
+ */
+#ifdef POSIX
+ Main_ParseArgLine(getenv("MAKEFLAGS"));
+#else
+ Main_ParseArgLine(getenv("MAKE"));
+#endif
+
+ MainParseArgs(argc, argv);
+
+ /*
+ * Initialize archive, target and suffix modules in preparation for
+ * parsing the makefile(s)
+ */
+ Arch_Init();
+ Targ_Init();
+ Suff_Init();
+
+ DEFAULT = NILGNODE;
+ (void)time(&now);
+
+ /*
+ * Set up the .TARGETS variable to contain the list of targets to be
+ * created. If none specified, make the variable empty -- the parser
+ * will fill the thing in with the default or .MAIN target.
+ */
+ if (!Lst_IsEmpty(create)) {
+ LstNode ln;
+
+ for (ln = Lst_First(create); ln != NILLNODE;
+ ln = Lst_Succ(ln)) {
+ char *name = (char *)Lst_Datum(ln);
+
+ Var_Append(".TARGETS", name, VAR_GLOBAL);
+ }
+ } else
+ Var_Set(".TARGETS", "", VAR_GLOBAL);
+
+ /*
+ * Read in the built-in rules first, followed by the specified makefile,
+ * if it was (makefile != (char *) NULL), or the default Makefile and
+ * makefile, in that order, if it wasn't.
+ */
+ if (!noBuiltins && !ReadMakefile(_PATH_DEFSYSMK))
+ Fatal("make: no system rules (%s).", _PATH_DEFSYSMK);
+
+ if (!Lst_IsEmpty(makefiles)) {
+ LstNode ln;
+
+ ln = Lst_Find(makefiles, (ClientData)NULL, ReadMakefile);
+ if (ln != NILLNODE)
+ Fatal("make: cannot open %s.", (char *)Lst_Datum(ln));
+ } else if (!ReadMakefile("makefile"))
+ (void)ReadMakefile("Makefile");
+
+ (void)ReadMakefile(".depend");
+
+ Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
+ if (p1)
+ free(p1);
+
+ /* Install all the flags into the MAKE envariable. */
+ if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1)) != NULL) && *p)
+#ifdef POSIX
+ setenv("MAKEFLAGS", p, 1);
+#else
+ setenv("MAKE", p, 1);
+#endif
+ if (p1)
+ free(p1);
+
+ /*
+ * For compatibility, look at the directories in the VPATH variable
+ * and add them to the search path, if the variable is defined. The
+ * variable's value is in the same format as the PATH envariable, i.e.
+ * <directory>:<directory>:<directory>...
+ */
+ if (Var_Exists("VPATH", VAR_CMD)) {
+ char *vpath, *path, *cp, savec;
+ /*
+ * GCC stores string constants in read-only memory, but
+ * Var_Subst will want to write this thing, so store it
+ * in an array
+ */
+ static char VPATH[] = "${VPATH}";
+
+ vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE);
+ path = vpath;
+ do {
+ /* skip to end of directory */
+ for (cp = path; *cp != ':' && *cp != '\0'; cp++)
+ continue;
+ /* Save terminator character so know when to stop */
+ savec = *cp;
+ *cp = '\0';
+ /* Add directory to search path */
+ Dir_AddDir(dirSearchPath, path);
+ *cp = savec;
+ path = cp + 1;
+ } while (savec == ':');
+ (void)free((Address)vpath);
+ }
+
+ /*
+ * Now that all search paths have been read for suffixes et al, it's
+ * time to add the default search path to their lists...
+ */
+ Suff_DoPaths();
+
+ /* print the initial graph, if the user requested it */
+ if (DEBUG(GRAPH1))
+ Targ_PrintGraph(1);
+
+ /*
+ * Have now read the entire graph and need to make a list of targets
+ * to create. If none was given on the command line, we consult the
+ * parsing module to find the main target(s) to create.
+ */
+ if (Lst_IsEmpty(create))
+ targs = Parse_MainName();
+ else
+ targs = Targ_FindList(create, TARG_CREATE);
+
+/*
+ * this was original amMake -- want to allow parallelism, so put this
+ * back in, eventually.
+ */
+ if (!compatMake) {
+ /*
+ * Initialize job module before traversing the graph, now that
+ * any .BEGIN and .END targets have been read. This is done
+ * only if the -q flag wasn't given (to prevent the .BEGIN from
+ * being executed should it exist).
+ */
+ if (!queryFlag) {
+ if (maxLocal == -1)
+ maxLocal = maxJobs;
+ Job_Init(maxJobs, maxLocal);
+ jobsRunning = TRUE;
+ }
+
+ /* Traverse the graph, checking on all the targets */
+ outOfDate = Make_Run(targs);
+ } else
+ /*
+ * Compat_Init will take care of creating all the targets as
+ * well as initializing the module.
+ */
+ Compat_Run(targs);
+
+ Lst_Destroy(targs, NOFREE);
+ Lst_Destroy(makefiles, NOFREE);
+ Lst_Destroy(create, (void (*) __P((ClientData))) free);
+
+ /* print the graph now it's been processed if the user requested it */
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+
+ Suff_End();
+ Targ_End();
+ Arch_End();
+ str_end();
+ Var_End();
+ Parse_End();
+ Dir_End();
+
+ if (queryFlag && outOfDate)
+ return(1);
+ else
+ return(0);
+}
+
+/*-
+ * ReadMakefile --
+ * Open and parse the given makefile.
+ *
+ * Results:
+ * TRUE if ok. FALSE if couldn't open file.
+ *
+ * Side Effects:
+ * lots
+ */
+static Boolean
+ReadMakefile(fname)
+ char *fname; /* makefile to read */
+{
+ extern Lst parseIncPath, sysIncPath;
+ FILE *stream;
+ char *name, path[MAXPATHLEN + 1];
+
+ if (!strcmp(fname, "-")) {
+ Parse_File("(stdin)", stdin);
+ Var_Set("MAKEFILE", "", VAR_GLOBAL);
+ } else {
+ if ((stream = fopen(fname, "r")) != NULL)
+ goto found;
+ /* if we've chdir'd, rebuild the path name */
+ if (curdir != objdir && *fname != '/') {
+ (void)sprintf(path, "%s/%s", curdir, fname);
+ if ((stream = fopen(path, "r")) != NULL) {
+ fname = path;
+ goto found;
+ }
+ }
+ /* look in -I and system include directories. */
+ name = Dir_FindFile(fname, parseIncPath);
+ if (!name)
+ name = Dir_FindFile(fname, sysIncPath);
+ if (!name || !(stream = fopen(name, "r")))
+ return(FALSE);
+ fname = name;
+ /*
+ * set the MAKEFILE variable desired by System V fans -- the
+ * placement of the setting here means it gets set to the last
+ * makefile specified, as it is set by SysV make.
+ */
+found: Var_Set("MAKEFILE", fname, VAR_GLOBAL);
+ Parse_File(fname, stream);
+ (void)fclose(stream);
+ }
+ return(TRUE);
+}
+
+/*-
+ * Error --
+ * Print an error message given its format.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The message is printed.
+ */
+/* VARARGS */
+void
+#if __STDC__
+Error(char *fmt, ...)
+#else
+Error(va_alist)
+ va_dcl
+#endif
+{
+ va_list ap;
+#if __STDC__
+ va_start(ap, fmt);
+#else
+ char *fmt;
+
+ va_start(ap);
+ fmt = va_arg(ap, char *);
+#endif
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+}
+
+/*-
+ * Fatal --
+ * Produce a Fatal error message. If jobs are running, waits for them
+ * to finish.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+/* VARARGS */
+void
+#if __STDC__
+Fatal(char *fmt, ...)
+#else
+Fatal(va_alist)
+ va_dcl
+#endif
+{
+ va_list ap;
+#if __STDC__
+ va_start(ap, fmt);
+#else
+ char *fmt;
+
+ va_start(ap);
+ fmt = va_arg(ap, char *);
+#endif
+ if (jobsRunning)
+ Job_Wait();
+
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+ exit(2); /* Not 1 so -q can distinguish error */
+}
+
+/*
+ * Punt --
+ * Major exception once jobs are being created. Kills all jobs, prints
+ * a message and exits.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed indiscriminately and the program Lib_Exits
+ */
+/* VARARGS */
+void
+#if __STDC__
+Punt(char *fmt, ...)
+#else
+Punt(va_alist)
+ va_dcl
+#endif
+{
+ va_list ap;
+#if __STDC__
+ va_start(ap, fmt);
+#else
+ char *fmt;
+
+ va_start(ap);
+ fmt = va_arg(ap, char *);
+#endif
+
+ (void)fprintf(stderr, "make: ");
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ DieHorribly();
+}
+
+/*-
+ * DieHorribly --
+ * Exit without giving a message.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A big one...
+ */
+void
+DieHorribly()
+{
+ if (jobsRunning)
+ Job_AbortAll();
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+ exit(2); /* Not 1, so -q can distinguish error */
+}
+
+/*
+ * Finish --
+ * Called when aborting due to errors in child shell to signal
+ * abnormal exit.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+void
+Finish(errors)
+ int errors; /* number of errors encountered in Make_Make */
+{
+ Fatal("%d error%s", errors, errors == 1 ? "" : "s");
+}
+
+/*
+ * emalloc --
+ * malloc, but die on error.
+ */
+char *
+emalloc(len)
+ size_t len;
+{
+ char *p;
+
+ if ((p = (char *) malloc(len)) == NULL)
+ enomem();
+ return(p);
+}
+
+/*
+ * enomem --
+ * die when out of memory.
+ */
+void
+enomem()
+{
+ (void)fprintf(stderr, "make: %s.\n", strerror(errno));
+ exit(2);
+}
+
+/*
+ * usage --
+ * exit with usage message
+ */
+static void
+usage()
+{
+ (void)fprintf(stderr,
+"usage: make [-eiknqrst] [-D variable] [-d flags] [-f makefile ]\n\
+ [-I directory] [-j max_jobs] [variable=value]\n");
+ exit(2);
+}
+
+
+int
+PrintAddr(a, b)
+ ClientData a;
+ ClientData b;
+{
+ printf("%lx ", (unsigned long) a);
+ return b ? 0 : 0;
+}
diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1
new file mode 100644
index 000000000000..b1392cc6e944
--- /dev/null
+++ b/usr.bin/make/make.1
@@ -0,0 +1,890 @@
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)make.1 8.8 (Berkeley) 6/13/95
+.\"
+.Dd June 13, 1995
+.Dt MAKE 1
+.Os
+.Sh NAME
+.Nm make
+.Nd maintain program dependencies
+.Sh SYNOPSIS
+.Nm make
+.Op Fl eiknqrstv
+.Op Fl D Ar variable
+.Op Fl d Ar flags
+.Op Fl f Ar makefile
+.Op Fl I Ar directory
+.Bk -words
+.Op Fl j Ar max_jobs
+.Ek
+.Op Ar variable=value
+.Op Ar target ...
+.Sh DESCRIPTION
+.Nm Make
+is a program designed to simplify the maintenance of other programs.
+Its input is a list of specifications as to the files upon which programs
+and other files depend.
+If the file
+.Ql Pa makefile
+exists, it is read for this list of specifications.
+If it does not exist, the file
+.Ql Pa Makefile
+is read.
+If the file
+.Ql Pa .depend
+exists, it is read (see
+.Xr mkdep 1) .
+.Pp
+This manual page is intended as a reference document only.
+For a more thorough description of
+.Nm make
+and makefiles, please refer to
+.%T "Make \- A Tutorial" .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar variable
+Define
+.Ar variable
+to be 1, in the global context.
+.It Fl d Ar flags
+Turn on debugging, and specify which portions of
+.Nm make
+are to print debugging information.
+.Ar Flags
+is one or more of the following:
+.Bl -tag -width Ds
+.It Ar A
+Print all possible debugging information;
+equivalent to specifying all of the debugging flags.
+.It Ar a
+Print debugging information about archive searching and caching.
+.It Ar c
+Print debugging information about conditional evaluation.
+.It Ar d
+Print debugging information about directory searching and caching.
+.It Ar "g1"
+Print the input graph before making anything.
+.It Ar "g2"
+Print the input graph after making everything, or before exiting
+on error.
+.It Ar j
+Print debugging information about running multiple shells.
+.It Ar m
+Print debugging information about making targets, including modification
+dates.
+.It Ar s
+Print debugging information about suffix-transformation rules.
+.It Ar t
+Print debugging information about target list maintenance.
+.It Ar v
+Print debugging information about variable assignment.
+.El
+.It Fl e
+Specify that environmental variables override macro assignments within
+makefiles.
+.It Fl f Ar makefile
+Specify a makefile to read instead of the default
+.Ql Pa makefile
+and
+.Ql Pa Makefile .
+If
+.Ar makefile
+is
+.Ql Fl ,
+standard input is read.
+Multiple makefile's may be specified, and are read in the order specified.
+.It Fl I Ar directory
+Specify a directory in which to search for makefiles and included makefiles.
+The system makefile directory is automatically included as part of this
+list.
+.It Fl i
+Ignore non-zero exit of shell commands in the makefile.
+Equivalent to specifying
+.Ql Fl
+before each command line in the makefile.
+.It Fl j Ar max_jobs
+Specify the maximum number of jobs that
+.Nm make
+may have running at any one time.
+.It Fl k
+Continue processing after errors are encountered, but only on those targets
+that do not depend on the target whose creation caused the error.
+.It Fl n
+Display the commands that would have been executed, but do not actually
+execute them.
+.It Fl q
+Do not execute any commands, but exit 0 if the specified targets are
+up-to-date and 1, otherwise.
+.It Fl r
+Do not use the built-in rules specified in the system makefile.
+.It Fl s
+Do not echo any commands as they are executed.
+Equivalent to specifying
+.Ql Ic @
+before each command line in the makefile.
+.It Fl t
+Rather than re-building a target as specified in the makefile, create it
+or update its modification time to make it appear up-to-date.
+.It Ar variable=value
+Set the value of the variable
+.Ar variable
+to
+.Ar value .
+.El
+.Pp
+There are seven different types of lines in a makefile: file dependency
+specifications, shell commands, variable assignments, include statements,
+conditional directives, for loops, and comments.
+.Pp
+In general, lines may be continued from one line to the next by ending
+them with a backslash
+.Pq Ql \e .
+The trailing newline character and initial whitespace on the following
+line are compressed into a single space.
+.Sh FILE DEPENDENCY SPECIFICATIONS
+Dependency lines consist of one or more targets, an operator, and zero
+or more sources.
+This creates a relationship where the targets ``depend'' on the sources
+and are usually created from them.
+The exact relationship between the target and the source is determined
+by the operator that separates them.
+The three operators are as follows:
+.Bl -tag -width flag
+.It Ic \&:
+A target is considered out-of-date if its modification time is less than
+those of any of its sources.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm make
+is interrupted.
+.It Ic \&!
+Targets are always re-created, but not until all sources have been
+examined and re-created as necessary.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm make
+is interrupted.
+.It Ic \&::
+If no sources are specified, the target is always re-created.
+Otherwise, a target is considered out-of-date if any of its sources has
+been modified more recently than the target.
+Sources for a target do not accumulate over dependency lines when this
+operator is used.
+The target will not be removed if
+.Nm make
+is interrupted.
+.El
+.Pp
+Targets and sources may contain the shell wildcard values
+.Ql ? ,
+.Ql * ,
+.Ql []
+and
+.Ql {} .
+The values
+.Ql ? ,
+.Ql *
+and
+.Ql []
+may only be used as part of the final
+component of the target or source, and must be used to describe existing
+files.
+The value
+.Ql {}
+need not necessarily be used to describe existing files.
+Expansion is in directory order, not alphabetically as done in the shell.
+.Sh SHELL COMMANDS
+Each target may have associated with it a series of shell commands, normally
+used to create the target.
+Each of the commands in this script
+.Em must
+be preceded by a tab.
+While any target may appear on a dependency line, only one of these
+dependencies may be followed by a creation script, unless the
+.Ql Ic ::
+operator is used.
+.Pp
+If the first or first two characters of the command line are
+.Ql Ic @
+and/or
+.Ql Ic \- ,
+the command is treated specially.
+A
+.Ql Ic @
+causes the command not to be echoed before it is executed.
+A
+.Ql Ic \-
+causes any non-zero exit status of the command line to be ignored.
+.Sh VARIABLE ASSIGNMENTS
+Variables in make are much like variables in the shell, and, by tradition,
+consist of all upper-case letters.
+The five operators that can be used to assign values to variables are as
+follows:
+.Bl -tag -width Ds
+.It Ic \&=
+Assign the value to the variable.
+Any previous value is overridden.
+.It Ic \&+=
+Append the value to the current value of the variable.
+.It Ic \&?=
+Assign the value to the variable if it is not already defined.
+.It Ic \&:=
+Assign with expansion, i.e. expand the value before assigning it
+to the variable.
+Normally, expansion is not done until the variable is referenced.
+.It Ic \&!=
+Expand the value and pass it to the shell for execution and assign
+the result to the variable.
+Any newlines in the result are replaced with spaces.
+.El
+.Pp
+Any white-space before the assigned
+.Ar value
+is removed; if the value is being appended, a single space is inserted
+between the previous contents of the variable and the appended value.
+.Pp
+Variables are expanded by surrounding the variable name with either
+curly braces
+.Pq Ql {}
+or parentheses
+.Pq Ql ()
+and preceding it with
+a dollar sign
+.Pq Ql \&$ .
+If the variable name contains only a single letter, the surrounding
+braces or parentheses are not required.
+This shorter form is not recommended.
+.Pp
+Variable substitution occurs at two distinct times, depending on where
+the variable is being used.
+Variables in dependency lines are expanded as the line is read.
+Variables in shell commands are expanded when the shell command is
+executed.
+.Pp
+The four different classes of variables (in order of increasing precedence)
+are:
+.Bl -tag -width Ds
+.It Environment variables
+Variables defined as part of
+.Nm make Ns 's
+environment.
+.It Global variables
+Variables defined in the makefile or in included makefiles.
+.It Command line variables
+Variables defined as part of the command line.
+.It Local variables
+Variables that are defined specific to a certain target.
+The seven local variables are as follows:
+.Bl -tag -width ".ARCHIVE"
+.It Va .ALLSRC
+The list of all sources for this target; also known as
+.Ql Va \&> .
+.It Va .ARCHIVE
+The name of the archive file.
+.It Va .IMPSRC
+The name/path of the source from which the target is to be transformed
+(the ``implied'' source); also known as
+.Ql Va \&< .
+.It Va .MEMBER
+The name of the archive member.
+.It Va .OODATE
+The list of sources for this target that were deemed out-of-date; also
+known as
+.Ql Va \&? .
+.It Va .PREFIX
+The file prefix of the file, containing only the file portion, no suffix
+or preceding directory components; also known as
+.Ql Va * .
+.It Va .TARGET
+The name of the target; also known as
+.Ql Va @ .
+.El
+.Pp
+The shorter forms
+.Ql Va @ ,
+.Ql Va ? ,
+.Ql Va \&>
+and
+.Ql Va *
+are permitted for backward
+compatibility with historical makefiles and are not recommended.
+The six variables
+.Ql Va "@F" ,
+.Ql Va "@D" ,
+.Ql Va "<F" ,
+.Ql Va "<D" ,
+.Ql Va "*F"
+and
+.Ql Va "*D"
+are
+permitted for compatibility with
+.At V
+makefiles and are not recommended.
+.Pp
+Four of the local variables may be used in sources on dependency lines
+because they expand to the proper value for each target on the line.
+These variables are
+.Ql Va .TARGET ,
+.Ql Va .PREFIX ,
+.Ql Va .ARCHIVE ,
+and
+.Ql Va .MEMBER .
+.Pp
+In addition,
+.Nm make
+sets or knows about the following variables:
+.Bl -tag -width MAKEFLAGS
+.It Va \&$
+A single dollar sign
+.Ql \&$ ,
+i.e.
+.Ql \&$$
+expands to a single dollar
+sign.
+.It Va .MAKE
+The name that
+.Nm make
+was executed with
+.Pq Va argv Op 0
+.It Va .CURDIR
+A path to the directory where
+.Nm make
+was executed.
+.It Va .OBJDIR
+A path to the directory where the targets are built.
+.It Ev MAKEFLAGS
+The environment variable
+.Ql Ev MAKEFLAGS
+may contain anything that
+may be specified on
+.Nm make Ns 's
+command line.
+Anything specified on
+.Nm make Ns 's
+command line is appended to the
+.Ql Ev MAKEFLAGS
+variable which is then
+entered into the environment for all programs which
+.Nm make
+executes.
+.El
+.Pp
+Variable expansion may be modified to select or modify each word of the
+variable (where a ``word'' is white-space delimited sequence of characters).
+The general format of a variable expansion is as follows:
+.Pp
+.Dl {variable[:modifier[:...]]}
+.Pp
+Each modifier begins with a colon and one of the following
+special characters.
+The colon may be escaped with a backslash
+.Pq Ql \e .
+.Bl -tag -width Cm E\&
+.It Cm E
+Replaces each word in the variable with its suffix.
+.It Cm H
+Replaces each word in the variable with everything but the last component.
+.It Cm M Ns Ar pattern
+Select only those words that match the rest of the modifier.
+The standard shell wildcard characters
+.Pf ( Ql * ,
+.Ql ? ,
+and
+.Ql Op )
+may
+be used.
+The wildcard characters may be escaped with a backslash
+.Pq Ql \e .
+.It Cm N Ns Ar pattern
+This is identical to
+.Ql Cm M ,
+but selects all words which do not match
+the rest of the modifier.
+.It Cm R
+Replaces each word in the variable with everything but its suffix.
+.Sm off
+.It Cm S No \&/ Ar old_pattern Xo
+.No \&/ Ar new_pattern
+.No \&/ Op Cm g
+.Xc
+.Sm on
+Modify the first occurrence of
+.Ar old_pattern
+in each word to be replaced with
+.Ar new_pattern .
+If a
+.Ql g
+is appended to the last slash of the pattern, all occurrences
+in each word are replaced.
+If
+.Ar old_pattern
+begins with a carat
+.Pq Ql ^ ,
+.Ar old_pattern
+is anchored at the beginning of each word.
+If
+.Ar old_pattern
+ends with a dollar sign
+.Pq Ql \&$ ,
+it is anchored at the end of each word.
+Inside
+.Ar new_string ,
+an ampersand
+.Pq Ql &
+is replaced by
+.Ar old_pattern .
+Any character may be used as a delimiter for the parts of the modifier
+string.
+The anchoring, ampersand and delimiter characters may be escaped with a
+backslash
+.Pq Ql \e .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the expansion
+of a dollar sign
+.Pq Ql \&$
+not a preceding dollar sign as is usual.
+.It Cm T
+Replaces each word in the variable with its last component.
+.It Ar old_string=new_string
+This is the
+.At V
+style variable substitution.
+It must be the last modifier specified.
+If
+.Ar old_string
+or
+.Ar new_string
+do not contain the pattern matching character
+.Ar %
+then it is assumed that they are
+anchored at the end of each word, so only suffixes or entire
+words may be replaced. Otherwise
+.Ar %
+is the substring of
+.Ar old_string
+to be replaced in
+.Ar new_string
+.El
+.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS
+Makefile inclusion, conditional structures and for loops reminiscent
+of the C programming language are provided in
+.Nm make .
+All such structures are identified by a line beginning with a single
+dot
+.Pq Ql \&.
+character.
+Files are included with either
+.Ql .include <file>
+or
+.Ql .include \*qfile\*q .
+Variables between the angle brackets or double quotes are expanded
+to form the file name.
+If angle brackets are used, the included makefile is expected to be in
+the system makefile directory.
+If double quotes are used, the including makefile's directory and any
+directories specified using the
+.Fl I
+option are searched before the system
+makefile directory.
+.Pp
+Conditional expressions are also preceded by a single dot as the first
+character of a line.
+The possible conditionals are as follows:
+.Bl -tag -width Ds
+.It Ic .undef Ar variable
+Un-define the specified global variable.
+Only global variables may be un-defined.
+.It Xo
+.Ic \&.if
+.Oo \&! Oc Ns Ar expression
+.Op Ar operator expression ...
+.Xc
+Test the value of an expression.
+.It Xo
+.Ic .ifdef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+Test the value of a variable.
+.It Xo
+.Ic .ifndef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+Test the value of a variable.
+.It Xo
+.Ic .ifmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+Test the target being built.
+.It Xo
+.Ic .ifnmake
+.Oo \&! Oc Ar target
+.Op Ar operator target ...
+.Xc
+Test the target being built.
+.It Ic .else
+Reverse the sense of the last conditional.
+.It Xo
+.Ic .elif
+.Oo \&! Oc Ar expression
+.Op Ar operator expression ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .if .
+.It Xo
+.Ic .elifdef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifdef .
+.It Xo
+.Ic .elifndef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifndef .
+.It Xo
+.Ic .elifmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifmake .
+.It Xo
+.Ic .elifnmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifnmake .
+.It Ic .endif
+End the body of the conditional.
+.El
+.Pp
+The
+.Ar operator
+may be any one of the following:
+.Bl -tag -width "Cm XX"
+.It Cm \&|\&|
+logical OR
+.It Cm \&&&
+Logical
+.Tn AND ;
+of higher precedence than
+.Dq .
+.El
+.Pp
+As in C,
+.Nm make
+will only evaluate a conditional as far as is necessary to determine
+its value.
+Parenthesis may be used to change the order of evaluation.
+The boolean operator
+.Ql Ic \&!
+may be used to logically negate an entire
+conditional.
+It is of higher precedence than
+.Ql Ic \&&& .
+.Pp
+The value of
+.Ar expression
+may be any of the following:
+.Bl -tag -width Ic defined
+.It Ic defined
+Takes a variable name as an argument and evaluates to true if the variable
+has been defined.
+.It Ic make
+Takes a target name as an argument and evaluates to true if the target
+was specified as part of
+.Nm make Ns 's
+command line or was declared the default target (either implicitly or
+explicitly, see
+.Va .MAIN )
+before the line containing the conditional.
+.It Ic empty
+Takes a variable, with possible modifiers, and evaluates to true if
+the expansion of the variable would result in an empty string.
+.It Ic exists
+Takes a file name as an argument and evaluates to true if the file exists.
+The file is searched for on the system search path (see
+.Va .PATH ) .
+.It Ic target
+Takes a target name as an argument and evaluates to true if the target
+has been defined.
+.El
+.Pp
+.Ar Expression
+may also be an arithmetic or string comparison. Variable expansion is
+performed on both sides of the comparison, after which the integral
+values are compared. A value is interpreted as hexadecimal if it is
+preceded by 0x, otherwise it is decimal; octal numbers are not supported.
+The standard C relational operators are all supported. If after
+variable expansion, either the left or right hand side of a
+.Ql Ic ==
+or
+.Ql Ic "!="
+operator is not an integral value, then
+string comparison is performed between the expanded
+variables.
+If no relational operator is given, it is assumed that the expanded
+variable is being compared against 0.
+.Pp
+When
+.Nm make
+is evaluating one of these conditional expression, and it encounters
+a word it doesn't recognize, either the ``make'' or ``defined''
+expression is applied to it, depending on the form of the conditional.
+If the form is
+.Ql Ic .ifdef
+or
+.Ql Ic .ifndef ,
+the ``defined'' expression
+is applied.
+Similarly, if the form is
+.Ql Ic .ifmake
+or
+.Ql Ic .ifnmake , the ``make''
+expression is applied.
+.Pp
+If the conditional evaluates to true the parsing of the makefile continues
+as before.
+If it evaluates to false, the following lines are skipped.
+In both cases this continues until a
+.Ql Ic .else
+or
+.Ql Ic .endif
+is found.
+.Pp
+For loops are typically used to apply a set of rules to a list of files.
+The syntax of a for loop is:
+.Bl -tag -width Ds
+.It Xo
+.Ic \&.for
+.Ar variable
+.Ic in
+.Ar expression
+.Xc
+.It Xo
+<make-rules>
+.Xc
+.It Xo
+.Ic \&.endfor
+.Xc
+.El
+After the for
+.Ic expression
+is evaluated, it is split into words. The
+iteration
+.Ic variable
+is successively set to each word, and substituted in the
+.Ic make-rules
+inside the body of the for loop.
+.Sh COMMENTS
+Comments begin with a hash
+.Pq Ql \&#
+character, anywhere but in a shell
+command line, and continue to the end of the line.
+.Sh SPECIAL SOURCES
+.Bl -tag -width Ic .IGNORE
+.It Ic .IGNORE
+Ignore any errors from the commands associated with this target, exactly
+as if they all were preceded by a dash
+.Pq Ql \- .
+.It Ic .MAKE
+Execute the commands associated with this target even if the
+.Fl n
+or
+.Fl t
+options were specified.
+Normally used to mark recursive
+.Nm make Ns 's .
+.It Ic .NOTMAIN
+Normally
+.Nm make
+selects the first target it encounters as the default target to be built
+if no target was specified.
+This source prevents this target from being selected.
+.It Ic .OPTIONAL
+If a target is marked with this attribute and
+.Nm make
+can't figure out how to create it, it will ignore this fact and assume
+the file isn't needed or already exists.
+.It Ic .PHONY
+If this special target is present in a Makefile, all sources for the
+target will considered to be `phony' targets. I.e. they don't refer
+to real files, and they will always be considered out-of-date. This
+is useful in case one wants to create a target called `install', in
+a directory where a file `install' already exists.
+.It Ic .PRECIOUS
+When
+.Nm make
+is interrupted, it removes any partially made targets.
+This source prevents the target from being removed.
+.It Ic .SILENT
+Do not echo any of the commands associated with this target, exactly
+as if they all were preceded by an at sign
+.Pq Ql @ .
+.It Ic .USE
+Turn the target into
+.Nm make Ns 's .
+version of a macro.
+When the target is used as a source for another target, the other target
+acquires the commands, sources, and attributes (except for
+.Ic .USE )
+of the
+source.
+If the target already has commands, the
+.Ic .USE
+target's commands are appended
+to them.
+.El
+.Sh "SPECIAL TARGETS"
+Special targets may not be included with other targets, i.e. they must be
+the only target specified.
+.Bl -tag -width Ic .BEGIN
+.It Ic .BEGIN
+Any command lines attached to this target are executed before anything
+else is done.
+.It Ic .DEFAULT
+This is sort of a
+.Ic .USE
+rule for any target (that was used only as a
+source) that
+.Nm make
+can't figure out any other way to create.
+Only the shell script is used.
+The
+.Ic .IMPSRC
+variable of a target that inherits
+.Ic .DEFAULT Ns 's
+commands is set
+to the target's own name.
+.It Ic .END
+Any command lines attached to this target are executed after everything
+else is done.
+.It Ic .IGNORE
+Mark each of the sources with the
+.Ic .IGNORE
+attribute.
+If no sources are specified, this is the equivalent of specifying the
+.Fl i
+option.
+.It Ic .INTERRUPT
+If
+.Nm make
+is interrupted, the commands for this target will be executed.
+.It Ic .MAIN
+If no target is specified when
+.Nm make
+is invoked, this target will be built.
+.It Ic .MAKEFLAGS
+This target provides a way to specify flags for
+.Nm make
+when the makefile is used.
+The flags are as if typed to the shell, though the
+.Fl f
+option will have
+no effect.
+.It Ic .PATH
+The sources are directories which are to be searched for files not
+found in the current directory.
+If no sources are specified, any previously specified directories are
+deleted.
+.It Ic .PRECIOUS
+Apply the
+.Ic .PRECIOUS
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .PRECIOUS
+attribute is applied to every
+target in the file.
+.It Ic .SILENT
+Apply the
+.Ic .SILENT
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .SILENT
+attribute is applied to every
+command in the file.
+.It Ic .SUFFIXES
+Each source specifies a suffix to
+.Nm make .
+If no sources are specified, any previous specified suffices are deleted.
+.Sh ENVIRONMENT
+.Nm Make
+utilizes the following environment variables, if they exist:
+.Ev MAKE ,
+.Ev MAKEFLAGS
+and
+.Ev MAKEOBJDIR .
+.Sh FILES
+.Bl -tag -width /usr/share/mk -compact
+.It .depend
+list of dependencies
+.It Makefile
+list of dependencies
+.It makefile
+list of dependencies
+.It sys.mk
+system makefile
+.It /usr/share/mk
+system makefile directory
+.El
+.Sh SEE ALSO
+.Xr mkdep 1
+.Sh HISTORY
+A
+.Nm Make
+command appeared in
+.At v7 .
diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c
new file mode 100644
index 000000000000..6d17b2ed87f3
--- /dev/null
+++ b/usr.bin/make/make.c
@@ -0,0 +1,908 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)make.c 8.3 (Berkeley) 6/13/95";
+#endif /* not lint */
+
+/*-
+ * make.c --
+ * The functions which perform the examination of targets and
+ * their suitability for creation
+ *
+ * Interface:
+ * Make_Run Initialize things for the module and recreate
+ * whatever needs recreating. Returns TRUE if
+ * work was (or would have been) done and FALSE
+ * otherwise.
+ *
+ * Make_Update Update all parents of a given child. Performs
+ * various bookkeeping chores like the updating
+ * of the cmtime field of the parent, filling
+ * of the IMPSRC context variable, etc. It will
+ * place the parent on the toBeMade queue if it
+ * should be.
+ *
+ * Make_TimeStamp Function to set the parent's cmtime field
+ * based on a child's modification time.
+ *
+ * Make_DoAllVar Set up the various local variables for a
+ * target, including the .ALLSRC variable, making
+ * sure that any variable that needs to exist
+ * at the very least has the empty value.
+ *
+ * Make_OODate Determine if a target is out-of-date.
+ *
+ * Make_HandleUse See if a child is a .USE node for a parent
+ * and perform the .USE actions if so.
+ */
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+
+static Lst toBeMade; /* The current fringe of the graph. These
+ * are nodes which await examination by
+ * MakeOODate. It is added to by
+ * Make_Update and subtracted from by
+ * MakeStartJobs */
+static int numNodes; /* Number of nodes to be processed. If this
+ * is non-zero when Job_Empty() returns
+ * TRUE, there's a cycle in the graph */
+
+static int MakeAddChild __P((ClientData, ClientData));
+static int MakeAddAllSrc __P((ClientData, ClientData));
+static int MakeTimeStamp __P((ClientData, ClientData));
+static int MakeHandleUse __P((ClientData, ClientData));
+static Boolean MakeStartJobs __P((void));
+static int MakePrintStatus __P((ClientData, ClientData));
+/*-
+ *-----------------------------------------------------------------------
+ * Make_TimeStamp --
+ * Set the cmtime field of a parent node based on the mtime stamp in its
+ * child. Called from MakeOODate via Lst_ForEach.
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * The cmtime of the parent node will be changed if the mtime
+ * field of the child is greater than it.
+ *-----------------------------------------------------------------------
+ */
+int
+Make_TimeStamp (pgn, cgn)
+ GNode *pgn; /* the current parent */
+ GNode *cgn; /* the child we've just examined */
+{
+ if (cgn->mtime > pgn->cmtime) {
+ pgn->cmtime = cgn->mtime;
+ }
+ return (0);
+}
+
+static int
+MakeTimeStamp (pgn, cgn)
+ ClientData pgn; /* the current parent */
+ ClientData cgn; /* the child we've just examined */
+{
+ return Make_TimeStamp((GNode *) pgn, (GNode *) cgn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_OODate --
+ * See if a given node is out of date with respect to its sources.
+ * Used by Make_Run when deciding which nodes to place on the
+ * toBeMade queue initially and by Make_Update to screen out USE and
+ * EXEC nodes. In the latter case, however, any other sort of node
+ * must be considered out-of-date since at least one of its children
+ * will have been recreated.
+ *
+ * Results:
+ * TRUE if the node is out of date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The mtime field of the node and the cmtime field of its parents
+ * will/may be changed.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_OODate (gn)
+ register GNode *gn; /* the node to check */
+{
+ Boolean oodate;
+
+ /*
+ * Certain types of targets needn't even be sought as their datedness
+ * doesn't depend on their modification time...
+ */
+ if ((gn->type & (OP_JOIN|OP_USE|OP_EXEC|OP_PHONY)) == 0) {
+ (void) Dir_MTime (gn);
+ if (DEBUG(MAKE)) {
+ if (gn->mtime != 0) {
+ printf ("modified %s...", Targ_FmtTime(gn->mtime));
+ } else {
+ printf ("non-existent...");
+ }
+ }
+ }
+
+ /*
+ * A target is remade in one of the following circumstances:
+ * its modification time is smaller than that of its youngest child
+ * and it would actually be run (has commands or type OP_NOP)
+ * it's the object of a force operator
+ * it has no children, was on the lhs of an operator and doesn't exist
+ * already.
+ *
+ * Libraries are only considered out-of-date if the archive module says
+ * they are.
+ *
+ * These weird rules are brought to you by Backward-Compatability and
+ * the strange people who wrote 'Make'.
+ */
+ if (gn->type & OP_PHONY) {
+ /*
+ * A PHONY node is always out of date
+ */
+ if (DEBUG(MAKE))
+ printf("phony...");
+ return TRUE;
+ } else if (gn->type & OP_USE) {
+ /*
+ * If the node is a USE node it is *never* out of date
+ * no matter *what*.
+ */
+ if (DEBUG(MAKE)) {
+ printf(".USE node...");
+ }
+ oodate = FALSE;
+ } else if (gn->type & OP_LIB) {
+ if (DEBUG(MAKE)) {
+ printf("library...");
+ }
+
+ /*
+ * always out of date if no children and :: target
+ */
+
+ oodate = Arch_LibOODate (gn) ||
+ ((gn->cmtime == 0) && (gn->type & OP_DOUBLEDEP));
+ } else if (gn->type & OP_JOIN) {
+ /*
+ * A target with the .JOIN attribute is only considered
+ * out-of-date if any of its children was out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ printf(".JOIN node...");
+ }
+ oodate = gn->childMade;
+ } else if (gn->type & (OP_FORCE|OP_EXEC)) {
+ /*
+ * A node which is the object of the force (!) operator or which has
+ * the .EXEC attribute is always considered out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->type & OP_FORCE) {
+ printf("! operator...");
+ } else {
+ printf(".EXEC node...");
+ }
+ }
+ oodate = TRUE;
+ } else if ((gn->mtime < gn->cmtime) ||
+ ((gn->cmtime == 0) &&
+ ((gn->mtime==0) || (gn->type & OP_DOUBLEDEP))))
+ {
+ /*
+ * A node whose modification time is less than that of its
+ * youngest child or that has no children (cmtime == 0) and
+ * either doesn't exist (mtime == 0) or was the object of a
+ * :: operator is out-of-date. Why? Because that's the way Make does
+ * it.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->mtime < gn->cmtime) {
+ printf("modified before source...");
+ } else if (gn->mtime == 0) {
+ printf("non-existent and no sources...");
+ } else {
+ printf(":: operator and no sources...");
+ }
+ }
+ oodate = TRUE;
+ } else {
+#if 0
+ /* WHY? */
+ if (DEBUG(MAKE)) {
+ printf("source %smade...", gn->childMade ? "" : "not ");
+ }
+ oodate = gn->childMade;
+#else
+ oodate = FALSE;
+#endif /* 0 */
+ }
+
+ /*
+ * If the target isn't out-of-date, the parents need to know its
+ * modification time. Note that targets that appear to be out-of-date
+ * but aren't, because they have no commands and aren't of type OP_NOP,
+ * have their mtime stay below their children's mtime to keep parents from
+ * thinking they're out-of-date.
+ */
+ if (!oodate) {
+ Lst_ForEach (gn->parents, MakeTimeStamp, (ClientData)gn);
+ }
+
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddChild --
+ * Function used by Make_Run to add a child to the list l.
+ * It will only add the child if its make field is FALSE.
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The given list is extended
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddChild (gnp, lp)
+ ClientData gnp; /* the node to add */
+ ClientData lp; /* the list to which to add it */
+{
+ GNode *gn = (GNode *) gnp;
+ Lst l = (Lst) lp;
+ if (!gn->make && !(gn->type & OP_USE)) {
+ (void)Lst_EnQueue (l, (ClientData)gn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_HandleUse --
+ * Function called by Make_Run and SuffApplyTransform on the downward
+ * pass to handle .USE and transformation nodes. A callback function
+ * for Lst_ForEach, it implements the .USE and transformation
+ * functionality by copying the node's commands, type flags
+ * and children to the parent node. Should be called before the
+ * children are enqueued to be looked at by MakeAddChild.
+ *
+ * A .USE node is much like an explicit transformation rule, except
+ * its commands are always added to the target node, even if the
+ * target already has commands.
+ *
+ * Results:
+ * returns 0.
+ *
+ * Side Effects:
+ * Children and commands may be added to the parent and the parent's
+ * type may be changed.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Make_HandleUse (cgn, pgn)
+ register GNode *cgn; /* The .USE node */
+ register GNode *pgn; /* The target of the .USE node */
+{
+ register GNode *gn; /* A child of the .USE node */
+ register LstNode ln; /* An element in the children list */
+
+ if (cgn->type & (OP_USE|OP_TRANSFORM)) {
+ if ((cgn->type & OP_USE) || Lst_IsEmpty(pgn->commands)) {
+ /*
+ * .USE or transformation and target has no commands -- append
+ * the child's commands to the parent.
+ */
+ (void) Lst_Concat (pgn->commands, cgn->commands, LST_CONCNEW);
+ }
+
+ if (Lst_Open (cgn->children) == SUCCESS) {
+ while ((ln = Lst_Next (cgn->children)) != NILLNODE) {
+ gn = (GNode *)Lst_Datum (ln);
+
+ if (Lst_Member (pgn->children, gn) == NILLNODE) {
+ (void) Lst_AtEnd (pgn->children, gn);
+ (void) Lst_AtEnd (gn->parents, pgn);
+ pgn->unmade += 1;
+ }
+ }
+ Lst_Close (cgn->children);
+ }
+
+ pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_TRANSFORM);
+
+ /*
+ * This child node is now "made", so we decrement the count of
+ * unmade children in the parent... We also remove the child
+ * from the parent's list to accurately reflect the number of decent
+ * children the parent has. This is used by Make_Run to decide
+ * whether to queue the parent or examine its children...
+ */
+ if (cgn->type & OP_USE) {
+ pgn->unmade -= 1;
+ }
+ }
+ return (0);
+}
+static int
+MakeHandleUse (pgn, cgn)
+ ClientData pgn; /* the current parent */
+ ClientData cgn; /* the child we've just examined */
+{
+ return Make_HandleUse((GNode *) pgn, (GNode *) cgn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Update --
+ * Perform update on the parents of a node. Used by JobFinish once
+ * a node has been dealt with and by MakeStartJobs if it finds an
+ * up-to-date node.
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The unmade field of pgn is decremented and pgn may be placed on
+ * the toBeMade queue if this field becomes 0.
+ *
+ * If the child was made, the parent's childMade field will be set true
+ * and its cmtime set to now.
+ *
+ * If the child wasn't made, the cmtime field of the parent will be
+ * altered if the child's mtime is big enough.
+ *
+ * Finally, if the child is the implied source for the parent, the
+ * parent's IMPSRC variable is set appropriately.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_Update (cgn)
+ register GNode *cgn; /* the child node */
+{
+ register GNode *pgn; /* the parent node */
+ register char *cname; /* the child's name */
+ register LstNode ln; /* Element in parents and iParents lists */
+ char *p1;
+
+ cname = Var_Value (TARGET, cgn, &p1);
+ if (p1)
+ free(p1);
+
+ /*
+ * If the child was actually made, see what its modification time is
+ * now -- some rules won't actually update the file. If the file still
+ * doesn't exist, make its mtime now.
+ */
+ if (cgn->made != UPTODATE) {
+#ifndef RECHECK
+ /*
+ * We can't re-stat the thing, but we can at least take care of rules
+ * where a target depends on a source that actually creates the
+ * target, but only if it has changed, e.g.
+ *
+ * parse.h : parse.o
+ *
+ * parse.o : parse.y
+ * yacc -d parse.y
+ * cc -c y.tab.c
+ * mv y.tab.o parse.o
+ * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+ *
+ * In this case, if the definitions produced by yacc haven't changed
+ * from before, parse.h won't have been updated and cgn->mtime will
+ * reflect the current modification time for parse.h. This is
+ * something of a kludge, I admit, but it's a useful one..
+ * XXX: People like to use a rule like
+ *
+ * FRC:
+ *
+ * To force things that depend on FRC to be made, so we have to
+ * check for gn->children being empty as well...
+ */
+ if (!Lst_IsEmpty(cgn->commands) || Lst_IsEmpty(cgn->children)) {
+ cgn->mtime = now;
+ }
+#else
+ /*
+ * This is what Make does and it's actually a good thing, as it
+ * allows rules like
+ *
+ * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+ *
+ * to function as intended. Unfortunately, thanks to the stateless
+ * nature of NFS (by which I mean the loose coupling of two clients
+ * using the same file from a common server), there are times
+ * when the modification time of a file created on a remote
+ * machine will not be modified before the local stat() implied by
+ * the Dir_MTime occurs, thus leading us to believe that the file
+ * is unchanged, wreaking havoc with files that depend on this one.
+ *
+ * I have decided it is better to make too much than to make too
+ * little, so this stuff is commented out unless you're sure it's ok.
+ * -- ardeb 1/12/88
+ */
+ /*
+ * Christos, 4/9/92: If we are saving commands pretend that
+ * the target is made now. Otherwise archives with ... rules
+ * don't work!
+ */
+ if (noExecute || (cgn->type & OP_SAVE_CMDS) || Dir_MTime(cgn) == 0) {
+ cgn->mtime = now;
+ }
+ if (DEBUG(MAKE)) {
+ printf("update time: %s\n", Targ_FmtTime(cgn->mtime));
+ }
+#endif
+ }
+
+ if (Lst_Open (cgn->parents) == SUCCESS) {
+ while ((ln = Lst_Next (cgn->parents)) != NILLNODE) {
+ pgn = (GNode *)Lst_Datum (ln);
+ if (pgn->make) {
+ pgn->unmade -= 1;
+
+ if ( ! (cgn->type & (OP_EXEC|OP_USE))) {
+ if (cgn->made == MADE) {
+ pgn->childMade = TRUE;
+ if (pgn->cmtime < cgn->mtime) {
+ pgn->cmtime = cgn->mtime;
+ }
+ } else {
+ (void)Make_TimeStamp (pgn, cgn);
+ }
+ }
+ if (pgn->unmade == 0) {
+ /*
+ * Queue the node up -- any unmade predecessors will
+ * be dealt with in MakeStartJobs.
+ */
+ (void)Lst_EnQueue (toBeMade, (ClientData)pgn);
+ } else if (pgn->unmade < 0) {
+ Error ("Graph cycles through %s", pgn->name);
+ }
+ }
+ }
+ Lst_Close (cgn->parents);
+ }
+ /*
+ * Deal with successor nodes. If any is marked for making and has an unmade
+ * count of 0, has not been made and isn't in the examination queue,
+ * it means we need to place it in the queue as it restrained itself
+ * before.
+ */
+ for (ln = Lst_First(cgn->successors); ln != NILLNODE; ln = Lst_Succ(ln)) {
+ GNode *succ = (GNode *)Lst_Datum(ln);
+
+ if (succ->make && succ->unmade == 0 && succ->made == UNMADE &&
+ Lst_Member(toBeMade, (ClientData)succ) == NILLNODE)
+ {
+ (void)Lst_EnQueue(toBeMade, (ClientData)succ);
+ }
+ }
+
+ /*
+ * Set the .PREFIX and .IMPSRC variables for all the implied parents
+ * of this node.
+ */
+ if (Lst_Open (cgn->iParents) == SUCCESS) {
+ char *p1;
+ char *cpref = Var_Value(PREFIX, cgn, &p1);
+
+ while ((ln = Lst_Next (cgn->iParents)) != NILLNODE) {
+ pgn = (GNode *)Lst_Datum (ln);
+ if (pgn->make) {
+ Var_Set (IMPSRC, cname, pgn);
+ Var_Set (PREFIX, cpref, pgn);
+ }
+ }
+ if (p1)
+ free(p1);
+ Lst_Close (cgn->iParents);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddAllSrc --
+ * Add a child's name to the ALLSRC and OODATE variables of the given
+ * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only
+ * if it has not been given the .EXEC, .USE or .INVISIBLE attributes.
+ * .EXEC and .USE children are very rarely going to be files, so...
+ * A child is added to the OODATE variable if its modification time is
+ * later than that of its parent, as defined by Make, except if the
+ * parent is a .JOIN node. In that case, it is only added to the OODATE
+ * variable if it was actually made (since .JOIN nodes don't have
+ * modification times, the comparison is rather unfair...)..
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The ALLSRC variable for the given node is extended.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddAllSrc (cgnp, pgnp)
+ ClientData cgnp; /* The child to add */
+ ClientData pgnp; /* The parent to whose ALLSRC variable it should be */
+ /* added */
+{
+ GNode *cgn = (GNode *) cgnp;
+ GNode *pgn = (GNode *) pgnp;
+ if ((cgn->type & (OP_EXEC|OP_USE|OP_INVISIBLE)) == 0) {
+ char *child;
+ char *p1;
+
+ child = Var_Value(TARGET, cgn, &p1);
+ Var_Append (ALLSRC, child, pgn);
+ if (pgn->type & OP_JOIN) {
+ if (cgn->made == MADE) {
+ Var_Append(OODATE, child, pgn);
+ }
+ } else if ((pgn->mtime < cgn->mtime) ||
+ (cgn->mtime >= now && cgn->made == MADE))
+ {
+ /*
+ * It goes in the OODATE variable if the parent is younger than the
+ * child or if the child has been modified more recently than
+ * the start of the make. This is to keep pmake from getting
+ * confused if something else updates the parent after the
+ * make starts (shouldn't happen, I know, but sometimes it
+ * does). In such a case, if we've updated the kid, the parent
+ * is likely to have a modification time later than that of
+ * the kid and anything that relies on the OODATE variable will
+ * be hosed.
+ *
+ * XXX: This will cause all made children to go in the OODATE
+ * variable, even if they're not touched, if RECHECK isn't defined,
+ * since cgn->mtime is set to now in Make_Update. According to
+ * some people, this is good...
+ */
+ Var_Append(OODATE, child, pgn);
+ }
+ if (p1)
+ free(p1);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_DoAllVar --
+ * Set up the ALLSRC and OODATE variables. Sad to say, it must be
+ * done separately, rather than while traversing the graph. This is
+ * because Make defined OODATE to contain all sources whose modification
+ * times were later than that of the target, *not* those sources that
+ * were out-of-date. Since in both compatibility and native modes,
+ * the modification time of the parent isn't found until the child
+ * has been dealt with, we have to wait until now to fill in the
+ * variable. As for ALLSRC, the ordering is important and not
+ * guaranteed when in native mode, so it must be set here, too.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The ALLSRC and OODATE variables of the given node is filled in.
+ * If the node is a .JOIN node, its TARGET variable will be set to
+ * match its ALLSRC variable.
+ *-----------------------------------------------------------------------
+ */
+void
+Make_DoAllVar (gn)
+ GNode *gn;
+{
+ Lst_ForEach (gn->children, MakeAddAllSrc, (ClientData) gn);
+
+ if (!Var_Exists (OODATE, gn)) {
+ Var_Set (OODATE, "", gn);
+ }
+ if (!Var_Exists (ALLSRC, gn)) {
+ Var_Set (ALLSRC, "", gn);
+ }
+
+ if (gn->type & OP_JOIN) {
+ char *p1;
+ Var_Set (TARGET, Var_Value (ALLSRC, gn, &p1), gn);
+ if (p1)
+ free(p1);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeStartJobs --
+ * Start as many jobs as possible.
+ *
+ * Results:
+ * If the query flag was given to pmake, no job will be started,
+ * but as soon as an out-of-date target is found, this function
+ * returns TRUE. At all other times, this function returns FALSE.
+ *
+ * Side Effects:
+ * Nodes are removed from the toBeMade queue and job table slots
+ * are filled.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+MakeStartJobs ()
+{
+ register GNode *gn;
+
+ while (!Job_Full() && !Lst_IsEmpty (toBeMade)) {
+ gn = (GNode *) Lst_DeQueue (toBeMade);
+ if (DEBUG(MAKE)) {
+ printf ("Examining %s...", gn->name);
+ }
+ /*
+ * Make sure any and all predecessors that are going to be made,
+ * have been.
+ */
+ if (!Lst_IsEmpty(gn->preds)) {
+ LstNode ln;
+
+ for (ln = Lst_First(gn->preds); ln != NILLNODE; ln = Lst_Succ(ln)){
+ GNode *pgn = (GNode *)Lst_Datum(ln);
+
+ if (pgn->make && pgn->made == UNMADE) {
+ if (DEBUG(MAKE)) {
+ printf("predecessor %s not made yet.\n", pgn->name);
+ }
+ break;
+ }
+ }
+ /*
+ * If ln isn't nil, there's a predecessor as yet unmade, so we
+ * just drop this node on the floor. When the node in question
+ * has been made, it will notice this node as being ready to
+ * make but as yet unmade and will place the node on the queue.
+ */
+ if (ln != NILLNODE) {
+ continue;
+ }
+ }
+
+ numNodes--;
+ if (Make_OODate (gn)) {
+ if (DEBUG(MAKE)) {
+ printf ("out-of-date\n");
+ }
+ if (queryFlag) {
+ return (TRUE);
+ }
+ Make_DoAllVar (gn);
+ Job_Make (gn);
+ } else {
+ if (DEBUG(MAKE)) {
+ printf ("up-to-date\n");
+ }
+ gn->made = UPTODATE;
+ if (gn->type & OP_JOIN) {
+ /*
+ * Even for an up-to-date .JOIN node, we need it to have its
+ * context variables so references to it get the correct
+ * value for .TARGET when building up the context variables
+ * of its parent(s)...
+ */
+ Make_DoAllVar (gn);
+ }
+
+ Make_Update (gn);
+ }
+ }
+ return (FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakePrintStatus --
+ * Print the status of a top-level node, viz. it being up-to-date
+ * already or not created due to an error in a lower level.
+ * Callback function for Make_Run via Lst_ForEach.
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * A message may be printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakePrintStatus(gnp, cyclep)
+ ClientData gnp; /* Node to examine */
+ ClientData cyclep; /* True if gn->unmade being non-zero implies
+ * a cycle in the graph, not an error in an
+ * inferior */
+{
+ GNode *gn = (GNode *) gnp;
+ Boolean cycle = *(Boolean *) cyclep;
+ if (gn->made == UPTODATE) {
+ printf ("`%s' is up to date.\n", gn->name);
+ } else if (gn->unmade != 0) {
+ if (cycle) {
+ Boolean t = TRUE;
+ /*
+ * If printing cycles and came to one that has unmade children,
+ * print out the cycle by recursing on its children. Note a
+ * cycle like:
+ * a : b
+ * b : c
+ * c : b
+ * will cause this to erroneously complain about a being in
+ * the cycle, but this is a good approximation.
+ */
+ if (gn->made == CYCLE) {
+ Error("Graph cycles through `%s'", gn->name);
+ gn->made = ENDCYCLE;
+ Lst_ForEach(gn->children, MakePrintStatus, (ClientData) &t);
+ gn->made = UNMADE;
+ } else if (gn->made != ENDCYCLE) {
+ gn->made = CYCLE;
+ Lst_ForEach(gn->children, MakePrintStatus, (ClientData) &t);
+ }
+ } else {
+ printf ("`%s' not remade because of errors.\n", gn->name);
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Run --
+ * Initialize the nodes to remake and the list of nodes which are
+ * ready to be made by doing a breadth-first traversal of the graph
+ * starting from the nodes in the given list. Once this traversal
+ * is finished, all the 'leaves' of the graph are in the toBeMade
+ * queue.
+ * Using this queue and the Job module, work back up the graph,
+ * calling on MakeStartJobs to keep the job table as full as
+ * possible.
+ *
+ * Results:
+ * TRUE if work was done. FALSE otherwise.
+ *
+ * Side Effects:
+ * The make field of all nodes involved in the creation of the given
+ * targets is set to 1. The toBeMade list is set to contain all the
+ * 'leaves' of these subgraphs.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_Run (targs)
+ Lst targs; /* the initial list of targets */
+{
+ register GNode *gn; /* a temporary pointer */
+ register Lst examine; /* List of targets to examine */
+ int errors; /* Number of errors the Job module reports */
+
+ toBeMade = Lst_Init (FALSE);
+
+ examine = Lst_Duplicate(targs, NOCOPY);
+ numNodes = 0;
+
+ /*
+ * Make an initial downward pass over the graph, marking nodes to be made
+ * as we go down. We call Suff_FindDeps to find where a node is and
+ * to get some children for it if it has none and also has no commands.
+ * If the node is a leaf, we stick it on the toBeMade queue to
+ * be looked at in a minute, otherwise we add its children to our queue
+ * and go on about our business.
+ */
+ while (!Lst_IsEmpty (examine)) {
+ gn = (GNode *) Lst_DeQueue (examine);
+
+ if (!gn->make) {
+ gn->make = TRUE;
+ numNodes++;
+
+ /*
+ * Apply any .USE rules before looking for implicit dependencies
+ * to make sure everything has commands that should...
+ */
+ Lst_ForEach (gn->children, MakeHandleUse, (ClientData)gn);
+ Suff_FindDeps (gn);
+
+ if (gn->unmade != 0) {
+ Lst_ForEach (gn->children, MakeAddChild, (ClientData)examine);
+ } else {
+ (void)Lst_EnQueue (toBeMade, (ClientData)gn);
+ }
+ }
+ }
+
+ Lst_Destroy (examine, NOFREE);
+
+ if (queryFlag) {
+ /*
+ * We wouldn't do any work unless we could start some jobs in the
+ * next loop... (we won't actually start any, of course, this is just
+ * to see if any of the targets was out of date)
+ */
+ return (MakeStartJobs());
+ } else {
+ /*
+ * Initialization. At the moment, no jobs are running and until some
+ * get started, nothing will happen since the remaining upward
+ * traversal of the graph is performed by the routines in job.c upon
+ * the finishing of a job. So we fill the Job table as much as we can
+ * before going into our loop.
+ */
+ (void) MakeStartJobs();
+ }
+
+ /*
+ * Main Loop: The idea here is that the ending of jobs will take
+ * care of the maintenance of data structures and the waiting for output
+ * will cause us to be idle most of the time while our children run as
+ * much as possible. Because the job table is kept as full as possible,
+ * the only time when it will be empty is when all the jobs which need
+ * running have been run, so that is the end condition of this loop.
+ * Note that the Job module will exit if there were any errors unless the
+ * keepgoing flag was given.
+ */
+ while (!Job_Empty ()) {
+ Job_CatchOutput ();
+ Job_CatchChildren (!usePipes);
+ (void)MakeStartJobs();
+ }
+
+ errors = Job_End();
+
+ /*
+ * Print the final status of each target. E.g. if it wasn't made
+ * because some inferior reported an error.
+ */
+ errors = ((errors == 0) && (numNodes != 0));
+ Lst_ForEach(targs, MakePrintStatus, (ClientData) &errors);
+
+ return (TRUE);
+}
diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h
new file mode 100644
index 000000000000..3af67f7e4157
--- /dev/null
+++ b/usr.bin/make/make.h
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents