commit 95bef729766af92a2bde4dcc8eff56c2924b0d9f Author: rgrimes Date: Fri May 27 12:33:43 1994 +0000 BSD 4.4 Lite Usr.bin Sources diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile new file mode 100644 index 000000000000..8ee4ba6a4001 --- /dev/null +++ b/usr.bin/make/Makefile @@ -0,0 +1,14 @@ +# @(#)Makefile 8.1 (Berkeley) 6/6/93 + +PROG= make +CFLAGS+=-I${.CURDIR} +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 + +.include diff --git a/usr.bin/make/PSD.doc/Makefile b/usr.bin/make/PSD.doc/Makefile new file mode 100644 index 000000000000..acac84f3edc6 --- /dev/null +++ b/usr.bin/make/PSD.doc/Makefile @@ -0,0 +1,7 @@ +# @(#)Makefile 8.1 (Berkeley) 8/14/93 + +DIR= psd/12.make +SRCS= tutorial.ms +MACROS= -ms + +.include diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms new file mode 100644 index 000000000000..eca49d947383 --- /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.1 (Berkeley) 8/18/93 +.\" +.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 +.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 = + +#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 +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 = +# +# fish.a: fish.a($(OBJECTS)) MAKELIB +# +# + +#ifndef _MAKELIB_MK +_MAKELIB_MK = + +#include + +\&.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 +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..ad7f61f14b05 --- /dev/null +++ b/usr.bin/make/arch.c @@ -0,0 +1,955 @@ +/* + * 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.2 (Berkeley) 1/2/94"; +#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 format. + * + * Arch_LibOODate Special function to decide if a library node + * is out-of-date. + * + * Arch_Init Initialize this module. + */ + +#include +#include +#include +#include +#include +#include +#include +#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 key/value pairs */ +} Arch; + +static int ArchFindArchive __P((Arch *, char *)); +static struct ar_hdr *ArchStatMember __P((char *, char *, Boolean)); +static FILE *ArchFindMember __P((char *, char *, struct ar_hdr *, char *)); + +/*- + *----------------------------------------------------------------------- + * 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) + Arch *ar; /* Current list element */ + char *archName; /* Name we want */ +{ + return (strcmp (archName, 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]; + int len; + 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[AR_MAX_NAME_LEN+1]; + /* Current member name while hashing. The name is + * truncated to AR_MAX_NAME_LEN bytes, but we need + * room for the null byte... */ + char copy[AR_MAX_NAME_LEN+1]; + /* Holds copy of last path element from member, if + * it has to be truncated, so we don't have to + * figure it out again once the table is hashed. */ + + /* + * 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 = 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'; + member = copy; + } + + 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 { + 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'; + + he = Hash_CreateEntry (&ar->members, strdup (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'; + (void) sscanf (arh.ar_size, "%10d", &size); + 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; + + 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 = strlen (member); + if (len > sizeof (arhPtr->ar_name)) { + len = 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, len) == 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 (len != sizeof(arhPtr->ar_name) && arhPtr->ar_name[len] != ' '){ + continue; + } 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 { + /* + * 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'; + (void)sscanf (arhPtr->ar_size, "%10d", &size); + 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 */ + + arch = ArchFindMember(Var_Value (ARCHIVE, gn), + Var_Value (TARGET, gn), + &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); + } +} + +/*- + *----------------------------------------------------------------------- + * 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 */ + + arhPtr = ArchStatMember (Var_Value (ARCHIVE, gn), + Var_Value (TARGET, gn), + TRUE); + if (arhPtr != (struct ar_hdr *) NULL) { + (void)sscanf (arhPtr->ar_date, "%12d", &modTime); + } 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). + * Its modification time doesn't agree with the modification + * time of its RANLIBMAG member (i.e. its table of contents + * is out-of-date). + * + * + * 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) { + (void)sscanf (arhPtr->ar_date, "%12d", &modTimeTOC); + + if (DEBUG(ARCH) || DEBUG(MAKE)) { + printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); + } + oodate = (gn->mtime > 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); +} diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c new file mode 100644 index 000000000000..3d9e9d63d246 --- /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.1 (Berkeley) 6/6/93"; +#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; +{ + + bp->left = 0; + BufExpand (bp, 1); + + *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..63c85c075924 --- /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.1 (Berkeley) 6/6/93 + */ + +/*- + * buf.h -- + * Header for users of the buf library. + */ + +#ifndef _BUF_H +#define _BUF_H + +#include "sprite.h" + +typedef unsigned 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_AddBytes __P((Buffer, int, Byte *)); +void Buf_Destroy __P((Buffer, Boolean)); +void Buf_Discard __P((Buffer, int)); +Byte *Buf_GetAll __P((Buffer, int *)); +int Buf_GetByte __P((Buffer)); +int Buf_GetBytes __P((Buffer, int, Byte *)); +Buffer Buf_Init __P((int)); +void Buf_OvAddByte __P((Buffer, int)); +int Buf_Size __P((Buffer)); +void Buf_UngetByte __P((Buffer, int)); +void Buf_UngetBytes __P((Buffer, int, Byte *)); + +#endif /* _BUF_H */ diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c new file mode 100644 index 000000000000..a14e2967e312 --- /dev/null +++ b/usr.bin/make/compat.c @@ -0,0 +1,641 @@ +/* + * 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.2 (Berkeley) 3/19/94"; +#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 +#include +#include + +#include +#include +#include +#include + +#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((char *, GNode *)); +static int CompatMake __P((GNode *, GNode *)); + +/*- + *----------------------------------------------------------------------- + * 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; +{ + struct stat sb; + GNode *gn; + + if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) { + char *file = Var_Value (TARGET, curTarg); + + if (!stat(file, &sb) && S_ISREG(sb.st_mode) && + unlink (file) == SUCCESS) { + printf ("*** %s removed\n", file); + } + + /* + * 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 (0); +} + +/*- + *----------------------------------------------------------------------- + * 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 (cmd, gn) + char *cmd; /* Command to execute */ + GNode *gn; /* 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 */ + + /* + * 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') { + 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); + 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); + } + + /* + * The child is off and running. Now all we can do is wait... + */ + while (1) { + int id; + + if (!local) { + id = 0; + } + + 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 (gn, pgn) + GNode *gn; /* The node to make */ + GNode *pgn; /* Parent to abort if necessary */ +{ + 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) { + Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn); + } + + /* + * 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) { + Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn); + } + 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..cec9e1e1379e --- /dev/null +++ b/usr.bin/make/cond.c @@ -0,0 +1,1247 @@ +/* + * 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.2 (Berkeley) 1/2/94"; +#endif /* not lint */ + +/*- + * cond.c -- + * Functions to handle conditionals in a makefile. + * + * Interface: + * Cond_Eval Evaluate the conditional in the passed line. + * + */ + +#include +#include +#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((char *, char *)); +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]; + Boolean result; + + arg[argLen] = '\0'; + if (Var_Value (arg, VAR_CMD) != (char *)NULL) { + result = TRUE; + } else { + result = FALSE; + } + 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) + char *string; + char *pattern; +{ + return(!Str_Match(string,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(*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(*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(*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(*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(*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) + { + /* void */ ; + } + 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(*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: + * # + * where is any of if, ifmake, ifnmake, ifdef, + * ifndef, elif, elifmake, elifnmake, elifdef, elifndef + * and 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..c00ee336a205 --- /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.1 (Berkeley) 6/6/93 + */ + +#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 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..a3093ad94241 --- /dev/null +++ b/usr.bin/make/dir.c @@ -0,0 +1,1238 @@ +/* + * 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.2 (Berkeley) 1/2/94"; +#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_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 +#include +#include +#include +#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((Path *, char *)); +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((char *)); +static int DirPrintDir __P((Path *)); + +/*- + *----------------------------------------------------------------------- + * 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; +} + +/*- + *----------------------------------------------------------------------- + * 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) + Path *p; /* Current name */ + char *dname; /* Desired name */ +{ + return (strcmp (p->name, 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) + char *word; +{ + printf("%s ", word); + + return(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, NULL); + 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 == *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, 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, 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 = 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) Hash_GetValue(entry)), fullName); + } + stb.st_mtime = (time_t)Hash_GetValue(entry); + Hash_DeleteEntry(&mtimes, entry); + } else if (stat (fullName, &stb) < 0) { + if (gn->type & OP_MEMBER) { + 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) + Path *p; /* Directory descriptor to copy */ +{ + 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 (p) + Path *p; /* The directory descriptor to nuke */ +{ + 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(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) Path *p; { printf ("%s ", p->name); return (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..09edcd68e962 --- /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.1 (Berkeley) 6/6/93 + */ + +/* 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_AddDir __P((Lst, char *)); +void Dir_ClearPath __P((Lst)); +void Dir_Concat __P((Lst, Lst)); +ClientData + Dir_CopyDir __P((Path *)); +void Dir_Destroy __P((Path *)); +void Dir_Expand __P((char *, Lst, Lst)); +char *Dir_FindFile __P((char *, Lst)); +Boolean Dir_HasWildcards __P((char *)); +void Dir_Init __P((void)); +char *Dir_MakeFlags __P((char *, Lst)); +int Dir_MTime __P((GNode *)); +void Dir_PrintDirectories __P((void)); +void Dir_PrintPath __P((Lst)); + +#endif /* _DIR */ diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c new file mode 100644 index 000000000000..e16d5ff076ec --- /dev/null +++ b/usr.bin/make/for.c @@ -0,0 +1,296 @@ +/* + * 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.1 (Berkeley) 6/6/93"; +#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 +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "buf.h" + +/* + * For statements are of the form: + * + * .for in + * ... + * .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. + */ +struct For { + Buffer buf; /* Unexpanded buffer */ + char* var; /* Index name */ + Lst lst; /* List of variables */ +}; + +static int ForExec __P((char *, struct For *)); + + + + +/*- + *----------------------------------------------------------------------- + * For_Eval -- + * Evaluate the for loop in the passed line. The line + * looks like this: + * .for in + * + * 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(*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(ptr[3])) + return FALSE; + ptr += 3; + + /* + * we found a for loop, and now we are going to parse it. + */ + while (*ptr && isspace(*ptr)) + ptr++; + + /* + * Grab the variable + */ + buf = Buf_Init(0); + for (wrd = ptr; *ptr && !isspace(*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(*ptr)) + ptr++; + + /* + * Grab the `in' + */ + if (ptr[0] != 'i' || ptr[1] != 'n' || !isspace(ptr[2])) { + Parse_Error (level, "missing `in' in for"); + printf("%s\n", ptr); + return 0; + } + ptr += 3; + + while (*ptr && isspace(*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(*ptr); ptr++) + continue; + + for (wrd = ptr; *ptr; ptr++) + if (isspace(*ptr)) { + ADDWORD(); + buf = Buf_Init(0); + while (*ptr && isspace(*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(*ptr); ptr++) + continue; + + if (strncmp(ptr, "endfor", 6) == 0 && (isspace(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(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(name, arg) + char *name; + struct For *arg; +{ + 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() +{ + struct 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, 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..5a6fe0baf540 --- /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.1 (Berkeley) 6/6/93"; +#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..9679a877a995 --- /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.1 (Berkeley) 6/6/93 + */ + +/* 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)) + +Hash_Entry *Hash_CreateEntry __P((Hash_Table *, char *, Boolean *)); +void Hash_DeleteEntry __P((Hash_Table *, Hash_Entry *)); +void Hash_DeleteTable __P((Hash_Table *)); +Hash_Entry *Hash_EnumFirst __P((Hash_Table *, Hash_Search *)); +Hash_Entry *Hash_EnumNext __P((Hash_Search *)); +Hash_Entry *Hash_FindEntry __P((Hash_Table *, char *)); +void Hash_InitTable __P((Hash_Table *, int)); + +#endif /* _HASH */ diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c new file mode 100644 index 000000000000..e2729ea0468d --- /dev/null +++ b/usr.bin/make/job.c @@ -0,0 +1,2661 @@ +/* + * 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.2 (Berkeley) 3/19/94"; +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 */ +static int nJobs; /* The number of children currently running */ +static int nLocal; /* The number of local children */ +static Lst jobs; /* The structures that describe them */ +static 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 + +static GNode *lastNode; /* The node for which output was most recently + * produced. */ +static 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. + */ +static 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((Job *, int)); +static void JobPassSig __P((int)); +static int JobCmpPid __P((Job *, int)); +static int JobPrintCommand __P((char *, Job *)); +static int JobSaveCommand __P((char *, GNode *)); +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(job, signo) + Job *job; /* Job to biff */ + int signo; /* Signal to send it */ +{ +#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)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); + + Lst_ForEach(jobs, JobCondPassSig, (ClientData)SIGCONT); + + 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) + int pid; /* process id desired */ + Job *job; /* job to examine */ +{ + return (pid - 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 (cmd, job) + char *cmd; /* command string to print */ + Job *job; /* 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 */ + + 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) + char *cmd; + GNode *gn; +{ + cmd = Var_Subst (NULL, cmd, gn, FALSE); + (void)Lst_AtEnd (postCommands->commands, (ClientData)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((const 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)) { + /* + * 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), gn); + } 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 */ + short 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); + + 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 */ + struct stat sb; + + 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); + if (!stat(file, &sb) && S_ISREG(sb.st_mode) && + unlink (file) == 0) { + 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..26089904aa61 --- /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.1 (Berkeley) 6/6/93 + */ + +/*- + * 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 JobFlagForMigration __P((int)); +void Job_AbortAll __P((void)); +void Job_CatchChildren __P((Boolean)); +void Job_CatchOutput __P((void)); +Boolean Job_CheckCommands __P((GNode *, + void (*abortProc )(const char *, ...))); +Boolean Job_Empty __P((void)); +int Job_End __P((void)); +Boolean Job_Full __P((void)); +void Job_Init __P((int, int)); +void Job_Make __P((GNode *)); +ReturnStatus Job_ParseShell __P((char *)); +void Job_Touch __P((GNode *, Boolean)); +void Job_Wait __P((void)); + +#endif /* _JOB_H_ */ diff --git a/usr.bin/make/list.h b/usr.bin/make/list.h new file mode 100644 index 000000000000..13dd586806f5 --- /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.1 (Berkeley) 6/6/93 + */ + +/* + * 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..3cd997d94dd7 --- /dev/null +++ b/usr.bin/make/lst.h @@ -0,0 +1,145 @@ +/* + * 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.1 (Berkeley) 6/6/93 + */ + +/*- + * lst.h -- + * Header for using the list library + */ +#ifndef _LST_H_ +#define _LST_H_ + +#include +#if __STDC__ +#include +#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 (*)()) 0) +#define NOCOPY ((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 + */ +Lst Lst_Init(); /* Create a new list */ +Lst Lst_Duplicate(); /* Duplicate an existing list */ +void Lst_Destroy(); /* Destroy an old one */ + +int Lst_Length(); /* Find the length of a list */ +Boolean Lst_IsEmpty(); /* True if list is empty */ + +/* + * Functions to modify a list + */ +ReturnStatus Lst_Insert(); /* Insert an element before another */ +ReturnStatus Lst_Append(); /* Insert an element after another */ +ReturnStatus Lst_AtFront(); /* Place an element at the front of + * a lst. */ +ReturnStatus Lst_AtEnd(); /* Place an element at the end of a + * lst. */ +ReturnStatus Lst_Remove(); /* Remove an element */ +ReturnStatus Lst_Replace(); /* Replace a node with a new value */ +ReturnStatus Lst_Move(); /* Move an element to another place */ +ReturnStatus Lst_Concat(); /* Concatenate two lists */ + +/* + * Node-specific functions + */ +LstNode Lst_First(); /* Return first element in list */ +LstNode Lst_Last(); /* Return last element in list */ +LstNode Lst_Succ(); /* Return successor to given element */ +LstNode Lst_Pred(); /* Return predecessor to given + * element */ +ClientData Lst_Datum(); /* Get datum from LstNode */ + +/* + * Functions for entire lists + */ +LstNode Lst_Find(); /* Find an element in a list */ +LstNode Lst_FindFrom(); /* Find an element starting from + * somewhere */ +LstNode Lst_Member(); /* See if the given datum is on the + * list. Returns the LstNode containing + * the datum */ +int Lst_Index(); /* Returns the index of a datum in the + * list, starting from 0 */ +void Lst_ForEach(); /* Apply a function to all elements of + * a lst */ +void Lst_ForEachFrom(); /* 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. */ +/* + * 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(). + */ +ReturnStatus Lst_Open(); /* Open the list */ +LstNode Lst_Prev(); /* Previous element */ +LstNode Lst_Cur(); /* The current element, please */ +LstNode Lst_Next(); /* Next element please */ +Boolean Lst_IsAtEnd(); /* Done yet? */ +void Lst_Close(); /* Finish table access */ + +/* + * for using the list as a queue + */ +ReturnStatus Lst_EnQueue(); /* Place an element at tail of queue */ +ClientData Lst_DeQueue(); /* Remove an element from head of + * queue */ + +#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..0a1d52b24506 --- /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.1 (Berkeley) 6/6/93"; +#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..dce8a07f77dc --- /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.1 (Berkeley) 6/6/93"; +#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..58a235d2a0ed --- /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.1 (Berkeley) 6/6/93"; +#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..624e6c6dc3b4 --- /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.1 (Berkeley) 6/6/93"; +#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..505d49f058ea --- /dev/null +++ b/usr.bin/make/lst.lib/lstConcat.c @@ -0,0 +1,174 @@ +/* + * 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.1 (Berkeley) 6/6/93"; +#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; + } + 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..542898b29ae7 --- /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.1 (Berkeley) 6/6/93"; +#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..921889ae910e --- /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.1 (Berkeley) 6/6/93"; +#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..3dedbf1c5379 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDestroy.c @@ -0,0 +1,98 @@ +/* + * 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.1 (Berkeley) 6/6/93"; +#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)(); +{ + 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; + } + + if (freeProc) { + for (ln = list->firstPtr; + ln != NilListNode && tln != list->firstPtr; + ln = tln) { + tln = ln->nextPtr; + (*freeProc) (ln->datum); + free ((Address)ln); + } + } else { + for (ln = list->firstPtr; + ln != NilListNode && tln != list->firstPtr; + 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..302bb30b866a --- /dev/null +++ b/usr.bin/make/lst.lib/lstDupl.c @@ -0,0 +1,98 @@ +/* + * 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.1 (Berkeley) 6/6/93"; +#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 */ + ClientData (*copyProc)(); /* A function to duplicate each 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..14e36e3d0dbd --- /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.1 (Berkeley) 6/6/93"; +#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..1efdc5458874 --- /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.1 (Berkeley) 6/6/93"; +#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)(); +{ + 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..e1da0337b909 --- /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.1 (Berkeley) 6/6/93"; +#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)(); +{ + 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..95e0893893d4 --- /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.1 (Berkeley) 6/6/93"; +#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..9fbdca597ea8 --- /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.1 (Berkeley) 6/6/93"; +#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)(); + 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..f19357f79050 --- /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.1 (Berkeley) 6/6/93"; +#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..c548c7fd0306 --- /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.1 (Berkeley) 6/6/93"; +#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..45577c4b040b --- /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.1 (Berkeley) 6/6/93"; +#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..0296558df3d3 --- /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.1 (Berkeley) 6/6/93 + */ + +/*- + * 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..2a8fc2f56798 --- /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.1 (Berkeley) 6/6/93"; +#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..c2a6509d6425 --- /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.1 (Berkeley) 6/6/93"; +#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..176aafb8c211 --- /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.1 (Berkeley) 6/6/93"; +#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..23070b7d2f1f --- /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.1 (Berkeley) 6/6/93"; +#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..0745b1cffccb --- /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.1 (Berkeley) 6/6/93"; +#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..f1decd7e1941 --- /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.1 (Berkeley) 6/6/93"; +#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..48a4c003cdc3 --- /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.1 (Berkeley) 6/6/93"; +#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..344631a17e53 --- /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.1 (Berkeley) 6/6/93"; +#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..06b354d9a030 --- /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.1 (Berkeley) 6/6/93"; +#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..7b3f5a547f32 --- /dev/null +++ b/usr.bin/make/main.c @@ -0,0 +1,911 @@ +/* + * 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.3 (Berkeley) 3/19/94"; +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#if __STDC__ +#include +#else +#include +#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; + char 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)*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); + 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, *path, *pwd, *getenv(), *getwd(); + char mdpath[MAXPATHLEN + 1]; + char obpath[MAXPATHLEN + 1]; + char cdpath[MAXPATHLEN + 1]; + + /* + * 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 (getwd(curdir) == NULL) { + (void)fprintf(stderr, "make: %s.\n", curdir); + 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); + } + + + /* + * 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 */ + 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); +#ifdef MACHINE + Var_Set("MACHINE", MACHINE, VAR_GLOBAL); +#endif +#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), VAR_GLOBAL); + + /* Install all the flags into the MAKE envariable. */ + if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL)) != NULL) && *p) +#ifdef POSIX + setenv("MAKEFLAGS", p, 1); +#else + setenv("MAKE", p, 1); +#endif + + /* + * 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. + * ::... + */ + 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); + + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + 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(const 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(const 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(const 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) + u_int len; +{ + char *p; + + if (!(p = malloc(len))) + 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); +} diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 new file mode 100644 index 000000000000..98879652578d --- /dev/null +++ b/usr.bin/make/make.1 @@ -0,0 +1,883 @@ +.\" 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.4 (Berkeley) 3/19/94 +.\" +.Dd March 19, 1994 +.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 parenthesis +.Pq Ql () +and preceding it with +a dollar sign +.Pq Ql \&$ . +If the variable name contains only a single letter, the surrounding +braces or parenthesis 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 " +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 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. +Parentheses 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 + +.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 .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..458bc66173d1 --- /dev/null +++ b/usr.bin/make/make.c @@ -0,0 +1,859 @@ +/* + * 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.1 (Berkeley) 6/6/93"; +#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((GNode *, Lst)); +static int MakeAddAllSrc __P((GNode *, GNode *)); +static Boolean MakeStartJobs __P((void)); +static int MakePrintStatus __P((GNode *, Boolean)); +/*- + *----------------------------------------------------------------------- + * 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) + register GNode *pgn; /* the current parent */ + register GNode *cgn; /* the child we've just examined */ +{ + if (cgn->mtime > pgn->cmtime) { + pgn->cmtime = cgn->mtime; + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * 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)) == 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_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..."); + } + oodate = Arch_LibOODate (gn); + } 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, Make_TimeStamp, (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 (gn, l) + GNode *gn; /* the node to add */ + Lst l; /* the list to which to add it */ +{ + 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); +} + +/*- + *----------------------------------------------------------------------- + * 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 */ + + cname = Var_Value (TARGET, cgn); + + /* + * 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 *cpref = Var_Value(PREFIX, cgn); + + 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); + } + } + 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 (cgn, pgn) + GNode *cgn; /* The child to add */ + GNode *pgn; /* The parent to whose ALLSRC variable it should be */ + /* added */ +{ + if ((cgn->type & (OP_EXEC|OP_USE|OP_INVISIBLE)) == 0) { + register char *child; + + child = Var_Value(TARGET, cgn); + 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); + } + } + 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, gn); + + if (!Var_Exists (OODATE, gn)) { + Var_Set (OODATE, "", gn); + } + if (!Var_Exists (ALLSRC, gn)) { + Var_Set (ALLSRC, "", gn); + } + + if (gn->type & OP_JOIN) { + Var_Set (TARGET, Var_Value (ALLSRC, gn), gn); + } +} + +/*- + *----------------------------------------------------------------------- + * 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(gn, cycle) + GNode *gn; /* Node to examine */ + Boolean cycle; /* True if gn->unmade being non-zero implies + * a cycle in the graph, not an error in an + * inferior */ +{ + if (gn->made == UPTODATE) { + printf ("`%s' is up to date.\n", gn->name); + } else if (gn->unmade != 0) { + if (cycle) { + /* + * 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)TRUE); + gn->made = UNMADE; + } else if (gn->made != ENDCYCLE) { + gn->made = CYCLE; + Lst_ForEach(gn->children, MakePrintStatus, (ClientData)TRUE); + } + } 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, Make_HandleUse, (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. + */ + Lst_ForEach(targs, MakePrintStatus, + (ClientData)((errors == 0) && (numNodes != 0))); + + return (TRUE); +} diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h new file mode 100644 index 000000000000..506d98a84222 --- /dev/null +++ b/usr.bin/make/make.h @@ -0,0 +1,357 @@ +/* + * 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. + * + * @(#)make.h 8.1 (Berkeley) 6/6/93 + */ + +/*- + * make.h -- + * The global definitions for pmake + */ + +#ifndef _MAKE_H_ +#define _MAKE_H_ + +#include +#include +#include +#include +#include +#if __STDC__ +#include +#include +#endif +#include "sprite.h" +#include "lst.h" +#include "config.h" +#include "buf.h" + +/*- + * The structure for an individual graph node. Each node has several + * pieces of data associated with it. + * 1) the name of the target it describes + * 2) the location of the target file in the file system. + * 3) the type of operator used to define its sources (qv. parse.c) + * 4) whether it is involved in this invocation of make + * 5) whether the target has been remade + * 6) whether any of its children has been remade + * 7) the number of its children that are, as yet, unmade + * 8) its modification time + * 9) the modification time of its youngest child (qv. make.c) + * 10) a list of nodes for which this is a source + * 11) a list of nodes on which this depends + * 12) a list of nodes that depend on this, as gleaned from the + * transformation rules. + * 13) a list of nodes of the same name created by the :: operator + * 14) a list of nodes that must be made (if they're made) before + * this node can be, but that do no enter into the datedness of + * this node. + * 15) a list of nodes that must be made (if they're made) after + * this node is, but that do not depend on this node, in the + * normal sense. + * 16) a Lst of ``local'' variables that are specific to this target + * and this target only (qv. var.c [$@ $< $?, etc.]) + * 17) a Lst of strings that are commands to be given to a shell + * to create this target. + */ +typedef struct GNode { + char *name; /* The target's name */ + char *path; /* The full pathname of the file */ + int type; /* Its type (see the OP flags, below) */ + + Boolean make; /* TRUE if this target needs to be remade */ + enum { + UNMADE, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED, + CYCLE, ENDCYCLE, + } made; /* Set to reflect the state of processing + * on this node: + * UNMADE - Not examined yet + * BEINGMADE - Target is already being made. + * Indicates a cycle in the graph. (compat + * mode only) + * MADE - Was out-of-date and has been made + * UPTODATE - Was already up-to-date + * ERROR - An error occured while it was being + * made (used only in compat mode) + * ABORTED - The target was aborted due to + * an error making an inferior (compat). + * CYCLE - Marked as potentially being part of + * a graph cycle. If we come back to a + * node marked this way, it is printed + * and 'made' is changed to ENDCYCLE. + * ENDCYCLE - the cycle has been completely + * printed. Go back and unmark all its + * members. + */ + Boolean childMade; /* TRUE if one of this target's children was + * made */ + int unmade; /* The number of unmade children */ + + int mtime; /* Its modification time */ + int cmtime; /* The modification time of its youngest + * child */ + + Lst iParents; /* Links to parents for which this is an + * implied source, if any */ + Lst cohorts; /* Other nodes for the :: operator */ + Lst parents; /* Nodes that depend on this one */ + Lst children; /* Nodes on which this one depends */ + Lst successors; /* Nodes that must be made after this one */ + Lst preds; /* Nodes that must be made before this one */ + + Lst context; /* The local variables */ + Lst commands; /* Creation commands */ + + struct _Suff *suffix; /* Suffix for the node (determined by + * Suff_FindDeps and opaque to everyone + * but the Suff module) */ +} GNode; + +/* + * Manifest constants + */ +#define NILGNODE ((GNode *) NIL) + +/* + * The OP_ constants are used when parsing a dependency line as a way of + * communicating to other parts of the program the way in which a target + * should be made. These constants are bitwise-OR'ed together and + * placed in the 'type' field of each node. Any node that has + * a 'type' field which satisfies the OP_NOP function was never never on + * the lefthand side of an operator, though it may have been on the + * righthand side... + */ +#define OP_DEPENDS 0x00000001 /* Execution of commands depends on + * kids (:) */ +#define OP_FORCE 0x00000002 /* Always execute commands (!) */ +#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids + * per line (::) */ +#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) + +#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't + * exist and can't be created */ +#define OP_USE 0x00000010 /* Use associated commands for parents */ +#define OP_EXEC 0x00000020 /* Target is never out of date, but always + * execute commands anyway. Its time + * doesn't matter, so it has none...sort + * of */ +#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ +#define OP_PRECIOUS 0x00000080 /* Don't remove the target when + * interrupted */ +#define OP_SILENT 0x00000100 /* Don't echo commands when executed */ +#define OP_MAKE 0x00000200 /* Target is a recurrsive make so its + * commands should always be executed when + * it is out of date, regardless of the + * state of the -n or -t flags */ +#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its + * children was out-of-date */ +#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. + * I.e. it doesn't show up in the parents's + * local variables. */ +#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main + * target' processing in parse.c */ +/* Attributes applied by PMake */ +#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ +#define OP_MEMBER 0x40000000 /* Target is a member of an archive */ +#define OP_LIB 0x20000000 /* Target is a library */ +#define OP_ARCHV 0x10000000 /* Target is an archive construct */ +#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. + * Used when parsing to catch multiple + * commands for a target */ +#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ +#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ + +/* + * OP_NOP will return TRUE if the node with the given type was not the + * object of a dependency operator + */ +#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) + +/* + * The TARG_ constants are used when calling the Targ_FindNode and + * Targ_FindList functions in targ.c. They simply tell the functions what to + * do if the desired node(s) is (are) not found. If the TARG_CREATE constant + * is given, a new, empty node will be created for the target, placed in the + * table of all targets and its address returned. If TARG_NOCREATE is given, + * a NIL pointer will be returned. + */ +#define TARG_CREATE 0x01 /* create node if not found */ +#define TARG_NOCREATE 0x00 /* don't create it */ + +/* + * There are several places where expandable buffers are used (parse.c and + * var.c). This constant is merely the starting point for those buffers. If + * lines tend to be much shorter than this, it would be best to reduce BSIZE. + * If longer, it should be increased. Reducing it will cause more copying to + * be done for longer lines, but will save space for shorter ones. In any + * case, it ought to be a power of two simply because most storage allocation + * schemes allocate in powers of two. + */ +#define MAKE_BSIZE 256 /* starting size for expandable buffers */ + +/* + * These constants are all used by the Str_Concat function to decide how the + * final string should look. If STR_ADDSPACE is given, a space will be + * placed between the two strings. If STR_ADDSLASH is given, a '/' will + * be used instead of a space. If neither is given, no intervening characters + * will be placed between the two strings in the final output. If the + * STR_DOFREE bit is set, the two input strings will be freed before + * Str_Concat returns. + */ +#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ +#define STR_DOFREE 0x02 /* free source strings after concatenation */ +#define STR_ADDSLASH 0x04 /* add a slash when Str_Concat'ing */ + +/* + * Error levels for parsing. PARSE_FATAL means the process cannot continue + * once the makefile has been parsed. PARSE_WARNING means it can. Passed + * as the first argument to Parse_Error. + */ +#define PARSE_WARNING 2 +#define PARSE_FATAL 1 + +/* + * Values returned by Cond_Eval. + */ +#define COND_PARSE 0 /* Parse the next lines */ +#define COND_SKIP 1 /* Skip the next lines */ +#define COND_INVALID 2 /* Not a conditional statement */ + +/* + * Definitions for the "local" variables. Used only for clarity. + */ +#define TARGET "@" /* Target of dependency */ +#define OODATE "?" /* All out-of-date sources */ +#define ALLSRC ">" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ + +#define FTARGET "@F" /* file part of TARGET */ +#define DTARGET "@D" /* directory part of TARGET */ +#define FIMPSRC " are kept in the 'sysIncPath' Lst. The + * targets currently being defined are kept in the 'targets' Lst. + * + * The variables 'fname' and 'lineno' are used to track the name + * of the current file and the line number in that file so that error + * messages can be more meaningful. + * + * Interface: + * Parse_Init Initialization function which must be + * called before anything else in this module + * is used. + * + * Parse_File Function used to parse a makefile. It must + * be given the name of the file, which should + * already have been opened, and a function + * to call to read a character from the file. + * + * Parse_IsVar Returns TRUE if the given line is a + * variable assignment. Used by MainParseArgs + * to determine if an argument is a target + * or a variable assignment. Used internally + * for pretty much the same thing... + * + * Parse_Error Function called when an error occurs in + * parsing. Used by the variable and + * conditional modules. + * Parse_MainName Returns a Lst of the main target to create. + */ + +#if __STDC__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" +#include "buf.h" +#include "pathnames.h" + +/* + * These values are returned by ParseEOF to tell Parse_File whether to + * CONTINUE parsing, i.e. it had only reached the end of an include file, + * or if it's DONE. + */ +#define CONTINUE 1 +#define DONE 0 +static Lst targets; /* targets we're working on */ +static Boolean inLine; /* true if currently in a dependency + * line or its commands */ +typedef struct { + char *str; + char *ptr; +} PTR; + +static char *fname; /* name of current file (for errors) */ +static int lineno; /* line number in current file */ +static FILE *curFILE = NULL; /* current makefile */ + +static PTR *curPTR = NULL; /* current makefile */ + +static int fatals = 0; + +static GNode *mainNode; /* The main target to create. This is the + * first target on the first dependency + * line in the first makefile */ +/* + * Definitions for handling #include specifications + */ +typedef struct IFile { + char *fname; /* name of previous file */ + int lineno; /* saved line number */ + FILE * F; /* the open stream */ + PTR * p; /* the char pointer */ +} IFile; + +static Lst includes; /* stack of IFiles generated by + * #includes */ +Lst parseIncPath; /* list of directories for "..." includes */ +Lst sysIncPath; /* list of directories for <...> includes */ + +/*- + * specType contains the SPECial TYPE of the current target. It is + * Not if the target is unspecial. If it *is* special, however, the children + * are linked as children of the parent but not vice versa. This variable is + * set in ParseDoDependency + */ +typedef enum { + Begin, /* .BEGIN */ + Default, /* .DEFAULT */ + End, /* .END */ + Ignore, /* .IGNORE */ + Includes, /* .INCLUDES */ + Interrupt, /* .INTERRUPT */ + Libs, /* .LIBS */ + MFlags, /* .MFLAGS or .MAKEFLAGS */ + Main, /* .MAIN and we don't have anything user-specified to + * make */ + NoExport, /* .NOEXPORT */ + Not, /* Not special */ + NotParallel, /* .NOTPARALELL */ + Null, /* .NULL */ + Order, /* .ORDER */ + ExPath, /* .PATH */ + Precious, /* .PRECIOUS */ + ExShell, /* .SHELL */ + Silent, /* .SILENT */ + SingleShell, /* .SINGLESHELL */ + Suffixes, /* .SUFFIXES */ + Attribute /* Generic attribute */ +} ParseSpecial; + +static ParseSpecial specType; + +/* + * Predecessor node for handling .ORDER. Initialized to NILGNODE when .ORDER + * seen, then set to each successive source on the line. + */ +static GNode *predecessor; + +/* + * The parseKeywords table is searched using binary search when deciding + * if a target or source is special. The 'spec' field is the ParseSpecial + * type of the keyword ("Not" if the keyword isn't special as a target) while + * the 'op' field is the operator to apply to the list of targets if the + * keyword is used as a source ("0" if the keyword isn't special as a source) + */ +static struct { + char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + int op; /* Operator when used as a source */ +} parseKeywords[] = { +{ ".BEGIN", Begin, 0 }, +{ ".DEFAULT", Default, 0 }, +{ ".OPTIONAL", Attribute, OP_OPTIONAL }, +{ ".END", End, 0 }, +{ ".EXEC", Attribute, OP_EXEC }, +{ ".IGNORE", Ignore, OP_IGNORE }, +{ ".INCLUDES", Includes, 0 }, +{ ".INTERRUPT", Interrupt, 0 }, +{ ".INVISIBLE", Attribute, OP_INVISIBLE }, +{ ".JOIN", Attribute, OP_JOIN }, +{ ".LIBS", Libs, 0 }, +{ ".MAIN", Main, 0 }, +{ ".MAKE", Attribute, OP_MAKE }, +{ ".MAKEFLAGS", MFlags, 0 }, +{ ".MFLAGS", MFlags, 0 }, +{ ".NOTMAIN", Attribute, OP_NOTMAIN }, +{ ".NOTPARALLEL", NotParallel, 0 }, +{ ".NULL", Null, 0 }, +{ ".ORDER", Order, 0 }, +{ ".PATH", ExPath, 0 }, +{ ".PRECIOUS", Precious, OP_PRECIOUS }, +{ ".RECURSIVE", Attribute, OP_MAKE }, +{ ".SHELL", ExShell, 0 }, +{ ".SILENT", Silent, OP_SILENT }, +{ ".SINGLESHELL", SingleShell, 0 }, +{ ".SUFFIXES", Suffixes, 0 }, +{ ".USE", Attribute, OP_USE }, +}; + +static int ParseFindKeyword __P((char *)); +static int ParseLinkSrc __P((GNode *, GNode *)); +static int ParseDoOp __P((GNode *, int)); +static void ParseDoSrc __P((int, char *)); +static int ParseFindMain __P((GNode *)); +static int ParseAddDir __P((Lst, char *)); +static int ParseClearPath __P((Lst)); +static void ParseDoDependency __P((char *)); +static int ParseAddCmd __P((GNode *, char *)); +static int ParseReadc __P((void)); +static void ParseUnreadc __P((int)); +static int ParseHasCommands __P((GNode *)); +static void ParseDoInclude __P((char *)); +#ifdef SYSVINCLUDE +static void ParseTraditionalInclude __P((char *)); +#endif +static int ParseEOF __P((int)); +static char *ParseReadLine __P((void)); +static char *ParseSkipLine __P((int)); +static void ParseFinishLine __P((void)); + +/*- + *---------------------------------------------------------------------- + * ParseFindKeyword -- + * Look in the table of keywords for one matching the given string. + * + * Results: + * The index of the keyword, or -1 if it isn't there. + * + * Side Effects: + * None + *---------------------------------------------------------------------- + */ +static int +ParseFindKeyword (str) + char *str; /* String to find */ +{ + register int start, + end, + cur; + register int diff; + + start = 0; + end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + + do { + cur = start + ((end - start) / 2); + diff = strcmp (str, parseKeywords[cur].name); + + if (diff == 0) { + return (cur); + } else if (diff < 0) { + end = cur - 1; + } else { + start = cur + 1; + } + } while (start <= end); + return (-1); +} + +/*- + * Parse_Error -- + * Error message abort function for parsing. Prints out the context + * of the error (line number and file) as well as the message with + * two optional arguments. + * + * Results: + * None + * + * Side Effects: + * "fatals" is incremented if the level is PARSE_FATAL. + */ +/* VARARGS */ +void +#if __STDC__ +Parse_Error(int type, const char *fmt, ...) +#else +Parse_Error(va_alist) + va_dcl +#endif +{ + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */ + char *fmt; + + va_start(ap); + type = va_arg(ap, int); + fmt = va_arg(ap, char *); +#endif + + (void)fprintf(stderr, "\"%s\", line %d: ", fname, lineno); + if (type == PARSE_WARNING) + (void)fprintf(stderr, "warning: "); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + if (type == PARSE_FATAL) + fatals += 1; +} + +/*- + *--------------------------------------------------------------------- + * ParseLinkSrc -- + * Link the parent node to its new child. Used in a Lst_ForEach by + * ParseDoDependency. If the specType isn't 'Not', the parent + * isn't linked as a parent of the child. + * + * Results: + * Always = 0 + * + * Side Effects: + * New elements are added to the parents list of cgn and the + * children list of cgn. the unmade field of pgn is updated + * to reflect the additional child. + *--------------------------------------------------------------------- + */ +static int +ParseLinkSrc (pgn, cgn) + GNode *pgn; /* The parent node */ + GNode *cgn; /* The child node */ +{ + if (Lst_Member (pgn->children, (ClientData)cgn) == NILLNODE) { + (void)Lst_AtEnd (pgn->children, (ClientData)cgn); + if (specType == Not) { + (void)Lst_AtEnd (cgn->parents, (ClientData)pgn); + } + pgn->unmade += 1; + } + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoOp -- + * Apply the parsed operator to the given target node. Used in a + * Lst_ForEach call by ParseDoDependency once all targets have + * been found and their operator parsed. If the previous and new + * operators are incompatible, a major error is taken. + * + * Results: + * Always 0 + * + * Side Effects: + * The type field of the node is altered to reflect any new bits in + * the op. + *--------------------------------------------------------------------- + */ +static int +ParseDoOp (gn, op) + GNode *gn; /* The node to which the operator is to be + * applied */ + int op; /* The operator to apply */ +{ + /* + * If the dependency mask of the operator and the node don't match and + * the node has actually had an operator applied to it before, and + * the operator actually has some dependency information in it, complain. + */ + if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && + !OP_NOP(gn->type) && !OP_NOP(op)) + { + Parse_Error (PARSE_FATAL, "Inconsistent operator for %s", gn->name); + return (1); + } + + if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { + /* + * If the node was the object of a :: operator, we need to create a + * new instance of it for the children and commands on this dependency + * line. The new instance is placed on the 'cohorts' list of the + * initial one (note the initial one is not on its own cohorts list) + * and the new instance is linked to all parents of the initial + * instance. + */ + register GNode *cohort; + LstNode ln; + + cohort = Targ_NewGN(gn->name); + /* + * Duplicate links to parents so graph traversal is simple. Perhaps + * some type bits should be duplicated? + * + * Make the cohort invisible as well to avoid duplicating it into + * other variables. True, parents of this target won't tend to do + * anything with their local variables, but better safe than + * sorry. + */ + Lst_ForEach(gn->parents, ParseLinkSrc, (ClientData)cohort); + cohort->type = OP_DOUBLEDEP|OP_INVISIBLE; + (void)Lst_AtEnd(gn->cohorts, (ClientData)cohort); + + /* + * Replace the node in the targets list with the new copy + */ + ln = Lst_Member(targets, (ClientData)gn); + Lst_Replace(ln, (ClientData)cohort); + gn = cohort; + } + /* + * We don't want to nuke any previous flags (whatever they were) so we + * just OR the new operator into the old + */ + gn->type |= op; + + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoSrc -- + * Given the name of a source, figure out if it is an attribute + * and apply it to the targets if it is. Else decide if there is + * some attribute which should be applied *to* the source because + * of some special target and apply it if so. Otherwise, make the + * source be a child of the targets in the list 'targets' + * + * Results: + * None + * + * Side Effects: + * Operator bits may be added to the list of targets or to the source. + * The targets may have a new source added to their lists of children. + *--------------------------------------------------------------------- + */ +static void +ParseDoSrc (tOp, src) + int tOp; /* operator (if any) from special targets */ + char *src; /* name of the source to handle */ +{ + int op; /* operator (if any) from special source */ + GNode *gn; + + op = 0; + if (*src == '.' && isupper (src[1])) { + int keywd = ParseFindKeyword(src); + if (keywd != -1) { + op = parseKeywords[keywd].op; + } + } + if (op != 0) { + Lst_ForEach (targets, ParseDoOp, (ClientData)op); + } else if (specType == Main) { + /* + * If we have noted the existence of a .MAIN, it means we need + * to add the sources of said target to the list of things + * to create. The string 'src' is likely to be free, so we + * must make a new copy of it. Note that this will only be + * invoked if the user didn't specify a target on the command + * line. This is to allow #ifmake's to succeed, or something... + */ + (void) Lst_AtEnd (create, (ClientData)strdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user cna + * employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); + } else if (specType == Order) { + /* + * Create proper predecessor/successor links between the previous + * source and the current one. + */ + gn = Targ_FindNode(src, TARG_CREATE); + if (predecessor != NILGNODE) { + (void)Lst_AtEnd(predecessor->successors, (ClientData)gn); + (void)Lst_AtEnd(gn->preds, (ClientData)predecessor); + } + /* + * The current source now becomes the predecessor for the next one. + */ + predecessor = gn; + } else { + /* + * If the source is not an attribute, we need to find/create + * a node for it. After that we can apply any operator to it + * from a special target or link it to its parents, as + * appropriate. + * + * In the case of a source that was the object of a :: operator, + * the attribute is applied to all of its instances (as kept in + * the 'cohorts' list of the node) or all the cohorts are linked + * to all the targets. + */ + gn = Targ_FindNode (src, TARG_CREATE); + if (tOp) { + gn->type |= tOp; + } else { + Lst_ForEach (targets, ParseLinkSrc, (ClientData)gn); + } + if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) { + register GNode *cohort; + register LstNode ln; + + for (ln=Lst_First(gn->cohorts); ln != NILLNODE; ln = Lst_Succ(ln)){ + cohort = (GNode *)Lst_Datum(ln); + if (tOp) { + cohort->type |= tOp; + } else { + Lst_ForEach(targets, ParseLinkSrc, (ClientData)cohort); + } + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFindMain -- + * Find a real target in the list and set it to be the main one. + * Called by ParseDoDependency when a main target hasn't been found + * yet. + * + * Results: + * 0 if main not found yet, 1 if it is. + * + * Side Effects: + * mainNode is changed and Targ_SetMain is called. + * + *----------------------------------------------------------------------- + */ +static int +ParseFindMain(gn) + GNode *gn; /* Node to examine */ +{ + if ((gn->type & (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)) == 0) { + mainNode = gn; + Targ_SetMain(gn); + return (1); + } else { + return (0); + } +} + +/*- + *----------------------------------------------------------------------- + * ParseAddDir -- + * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_AddDir. + * + *----------------------------------------------------------------------- + */ +static int +ParseAddDir(path, name) + Lst path; + char *name; +{ + Dir_AddDir(path, name); + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseClearPath -- + * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_ClearPath + * + *----------------------------------------------------------------------- + */ +static int +ParseClearPath(path) + Lst path; +{ + Dir_ClearPath(path); + return(0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoDependency -- + * Parse the dependency line in line. + * + * Results: + * None + * + * Side Effects: + * The nodes of the sources are linked as children to the nodes of the + * targets. Some nodes may be created. + * + * We parse a dependency line by first extracting words from the line and + * finding nodes in the list of all targets with that name. This is done + * until a character is encountered which is an operator character. Currently + * these are only ! and :. At this point the operator is parsed and the + * pointer into the line advanced until the first source is encountered. + * The parsed operator is applied to each node in the 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * The sources are read in much the same way as the targets were except + * that now they are expanded using the wildcarding scheme of the C-Shell + * and all instances of the resulting words in the list of all targets + * are found. Each of the resulting nodes is then linked to each of the + * targets as one of its children. + * Certain targets are handled specially. These are the ones detailed + * by the specType variable. + * The storing of transformation rules is also taken care of here. + * A target is recognized as a transformation rule by calling + * Suff_IsTransform. If it is a transformation rule, its node is gotten + * from the suffix module via Suff_AddTransform rather than the standard + * Targ_FindNode in the target module. + *--------------------------------------------------------------------- + */ +static void +ParseDoDependency (line) + char *line; /* the line to parse */ +{ + register char *cp; /* our current position */ + register GNode *gn; /* a general purpose temporary node */ + register int op; /* the operator on the line */ + char savec; /* a place to save a character */ + Lst paths; /* List of search paths to alter when parsing + * a list of .PATH targets */ + int tOp; /* operator from special target */ + Lst sources; /* list of source names after expansion */ + Lst curTargs; /* list of target names to be found and added + * to the targets list */ + + tOp = 0; + + specType = Not; + paths = (Lst)NULL; + + curTargs = Lst_Init(FALSE); + + do { + for (cp = line; + *cp && !isspace (*cp) && + (*cp != '!') && (*cp != ':') && (*cp != '('); + cp++) + { + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the puppy + * so we can safely advance beyond it...There should be + * no errors in this, as they would have been discovered + * in the initial Var_Subst and we wouldn't be here. + */ + int length; + Boolean freeIt; + char *result; + + result=Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); + + if (freeIt) { + free(result); + } + cp += length-1; + } + continue; + } + if (*cp == '(') { + /* + * Archives must be handled specially to make sure the OP_ARCHV + * flag is set in their 'type' field, for one thing, and because + * things like "archive(file1.o file2.o file3.o)" are permissible. + * Arch_ParseArchive will set 'line' to be the first non-blank + * after the archive-spec. It creates/finds nodes for the members + * and places them on the given list, returning SUCCESS if all + * went well and FAILURE if there was an error in the + * specification. On error, line should remain untouched. + */ + if (Arch_ParseArchive (&line, targets, VAR_CMD) != SUCCESS) { + Parse_Error (PARSE_FATAL, + "Error in archive specification: \"%s\"", line); + return; + } else { + continue; + } + } + savec = *cp; + + if (!*cp) { + /* + * Ending a dependency line without an operator is a Bozo + * no-no + */ + Parse_Error (PARSE_FATAL, "Need an operator"); + return; + } + *cp = '\0'; + /* + * Have a word in line. See if it's a special target and set + * specType to match it. + */ + if (*line == '.' && isupper (line[1])) { + /* + * See if the target is a special target that must have it + * or its sources handled specially. + */ + int keywd = ParseFindKeyword(line); + if (keywd != -1) { + if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + return; + } + + specType = parseKeywords[keywd].spec; + tOp = parseKeywords[keywd].op; + + /* + * Certain special targets have special semantics: + * .PATH Have to set the dirSearchPath + * variable too + * .MAIN Its sources are only used if + * nothing has been specified to + * create. + * .DEFAULT Need to create a node to hang + * commands on, but we don't want + * it in the graph, nor do we want + * it to be the Main Target, so we + * create it, set OP_NOTMAIN and + * add it to the list, setting + * DEFAULT to the new node for + * later use. We claim the node is + * A transformation rule to make + * life easier later, when we'll + * use Make_HandleUse to actually + * apply the .DEFAULT commands. + * .BEGIN + * .END + * .INTERRUPT Are not to be considered the + * main target. + * .NOTPARALLEL Make only one target at a time. + * .SINGLESHELL Create a shell for each command. + * .ORDER Must set initial predecessor to NIL + */ + switch (specType) { + case ExPath: + if (paths == NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, (ClientData)dirSearchPath); + break; + case Main: + if (!Lst_IsEmpty(create)) { + specType = Not; + } + break; + case Begin: + case End: + case Interrupt: + gn = Targ_FindNode(line, TARG_CREATE); + gn->type |= OP_NOTMAIN; + (void)Lst_AtEnd(targets, (ClientData)gn); + break; + case Default: + gn = Targ_NewGN(".DEFAULT"); + gn->type |= (OP_NOTMAIN|OP_TRANSFORM); + (void)Lst_AtEnd(targets, (ClientData)gn); + DEFAULT = gn; + break; + case NotParallel: + { + extern int maxJobs; + + maxJobs = 1; + break; + } + case SingleShell: + compatMake = 1; + break; + case Order: + predecessor = NILGNODE; + break; + default: + break; + } + } else if (strncmp (line, ".PATH", 5) == 0) { + /* + * .PATH has to be handled specially. + * Call on the suffix module to give us a path to + * modify. + */ + Lst path; + + specType = ExPath; + path = Suff_GetPath (&line[5]); + if (path == NILLST) { + Parse_Error (PARSE_FATAL, + "Suffix '%s' not defined (yet)", + &line[5]); + return; + } else { + if (paths == (Lst)NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, (ClientData)path); + } + } + } + + /* + * Have word in line. Get or create its node and stick it at + * the end of the targets list + */ + if ((specType == Not) && (*line != '\0')) { + if (Dir_HasWildcards(line)) { + /* + * Targets are to be sought only in the current directory, + * so create an empty path for the thing. Note we need to + * use Dir_Destroy in the destruction of the path as the + * Dir module could have added a directory to the path... + */ + Lst emptyPath = Lst_Init(FALSE); + + Dir_Expand(line, emptyPath, curTargs); + + Lst_Destroy(emptyPath, Dir_Destroy); + } else { + /* + * No wildcards, but we want to avoid code duplication, + * so create a list with the word on it. + */ + (void)Lst_AtEnd(curTargs, (ClientData)line); + } + + while(!Lst_IsEmpty(curTargs)) { + char *targName = (char *)Lst_DeQueue(curTargs); + + if (!Suff_IsTransform (targName)) { + gn = Targ_FindNode (targName, TARG_CREATE); + } else { + gn = Suff_AddTransform (targName); + } + + (void)Lst_AtEnd (targets, (ClientData)gn); + } + } else if (specType == ExPath && *line != '.' && *line != '\0') { + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); + } + + *cp = savec; + /* + * If it is a special type and not .PATH, it's the only target we + * allow on this line... + */ + if (specType != Not && specType != ExPath) { + Boolean warn = FALSE; + + while ((*cp != '!') && (*cp != ':') && *cp) { + if (*cp != ' ' && *cp != '\t') { + warn = TRUE; + } + cp++; + } + if (warn) { + Parse_Error(PARSE_WARNING, "Extra target ignored"); + } + } else { + while (*cp && isspace (*cp)) { + cp++; + } + } + line = cp; + } while ((*line != '!') && (*line != ':') && *line); + + /* + * Don't need the list of target names anymore... + */ + Lst_Destroy(curTargs, NOFREE); + + if (!Lst_IsEmpty(targets)) { + switch(specType) { + default: + Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); + break; + case Default: + case Begin: + case End: + case Interrupt: + /* + * These four create nodes on which to hang commands, so + * targets shouldn't be empty... + */ + case Not: + /* + * Nothing special here -- targets can be empty if it wants. + */ + break; + } + } + + /* + * Have now parsed all the target names. Must parse the operator next. The + * result is left in op . + */ + if (*cp == '!') { + op = OP_FORCE; + } else if (*cp == ':') { + if (cp[1] == ':') { + op = OP_DOUBLEDEP; + cp++; + } else { + op = OP_DEPENDS; + } + } else { + Parse_Error (PARSE_FATAL, "Missing dependency operator"); + return; + } + + cp++; /* Advance beyond operator */ + + Lst_ForEach (targets, ParseDoOp, (ClientData)op); + + /* + * Get to the first source + */ + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + + /* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * a .PATH removes all directories from the search path(s). + */ + if (!*line) { + switch (specType) { + case Suffixes: + Suff_ClearSuffixes (); + break; + case Precious: + allPrecious = TRUE; + break; + case Ignore: + ignoreErrors = TRUE; + break; + case Silent: + beSilent = TRUE; + break; + case ExPath: + Lst_ForEach(paths, ParseClearPath, (ClientData)NULL); + break; + default: + break; + } + } else if (specType == MFlags) { + /* + * Call on functions in main.c to deal with these arguments and + * set the initial character to a null-character so the loop to + * get sources won't get anything + */ + Main_ParseArgLine (line); + *line = '\0'; + } else if (specType == ExShell) { + if (Job_ParseShell (line) != SUCCESS) { + Parse_Error (PARSE_FATAL, "improper shell specification"); + return; + } + *line = '\0'; + } else if ((specType == NotParallel) || (specType == SingleShell)) { + *line = '\0'; + } + + /* + * NOW GO FOR THE SOURCES + */ + if ((specType == Suffixes) || (specType == ExPath) || + (specType == Includes) || (specType == Libs) || + (specType == Null)) + { + while (*line) { + /* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. + * + * If the target was .SUFFIXES, we take each source as a + * suffix and add it to the list of suffixes maintained by the + * Suff module. + * + * If the target was a .PATH, we add the source as a directory + * to search on the search path. + * + * If it was .INCLUDES, the source is taken to be the suffix of + * files which will be #included and whose search path should + * be present in the .INCLUDES variable. + * + * If it was .LIBS, the source is taken to be the suffix of + * files which are considered libraries and whose search path + * should be present in the .LIBS variable. + * + * If it was .NULL, the source is the suffix to use when a file + * has no valid suffix. + */ + char savec; + while (*cp && !isspace (*cp)) { + cp++; + } + savec = *cp; + *cp = '\0'; + switch (specType) { + case Suffixes: + Suff_AddSuffix (line); + break; + case ExPath: + Lst_ForEach(paths, ParseAddDir, (ClientData)line); + break; + case Includes: + Suff_AddInclude (line); + break; + case Libs: + Suff_AddLib (line); + break; + case Null: + Suff_SetNull (line); + break; + default: + break; + } + *cp = savec; + if (savec != '\0') { + cp++; + } + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + } + if (paths) { + Lst_Destroy(paths, NOFREE); + } + } else { + while (*line) { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + while (*cp && !isspace (*cp)) { + if ((*cp == '(') && (cp > line) && (cp[-1] != '$')) { + /* + * Only stop for a left parenthesis if it isn't at the + * start of a word (that'll be for variable changes + * later) and isn't preceded by a dollar sign (a dynamic + * source). + */ + break; + } else { + cp++; + } + } + + if (*cp == '(') { + GNode *gn; + + sources = Lst_Init (FALSE); + if (Arch_ParseArchive (&line, sources, VAR_CMD) != SUCCESS) { + Parse_Error (PARSE_FATAL, + "Error in source archive spec \"%s\"", line); + return; + } + + while (!Lst_IsEmpty (sources)) { + gn = (GNode *) Lst_DeQueue (sources); + ParseDoSrc (tOp, gn->name); + } + Lst_Destroy (sources, NOFREE); + cp = line; + } else { + if (*cp) { + *cp = '\0'; + cp += 1; + } + + ParseDoSrc (tOp, line); + } + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + } + } + + if (mainNode == NILGNODE) { + /* + * If we have yet to decide on a main target to make, in the + * absence of any user input, we want the first target on + * the first dependency line that is actually a real target + * (i.e. isn't a .USE or .EXEC rule) to be made. + */ + Lst_ForEach (targets, ParseFindMain, (ClientData)0); + } + +} + +/*- + *--------------------------------------------------------------------- + * Parse_IsVar -- + * Return TRUE if the passed line is a variable assignment. A variable + * assignment consists of a single word followed by optional whitespace + * followed by either a += or an = operator. + * This function is used both by the Parse_File function and main when + * parsing the command-line arguments. + * + * Results: + * TRUE if it is. FALSE if it ain't + * + * Side Effects: + * none + *--------------------------------------------------------------------- + */ +Boolean +Parse_IsVar (line) + register char *line; /* the line to check */ +{ + register Boolean wasSpace = FALSE; /* set TRUE if found a space */ + register Boolean haveName = FALSE; /* Set TRUE if have a variable name */ + + /* + * Skip to variable name + */ + while ((*line == ' ') || (*line == '\t')) { + line++; + } + + while (*line != '=') { + if (*line == '\0') { + /* + * end-of-line -- can't be a variable assignment. + */ + return (FALSE); + } else if ((*line == ' ') || (*line == '\t')) { + /* + * there can be as much white space as desired so long as there is + * only one word before the operator + */ + wasSpace = TRUE; + } else if (wasSpace && haveName) { + /* + * Stop when an = operator is found. + */ + if ((*line == '+') || (*line == ':') || (*line == '?') || + (*line == '!')) { + break; + } + + /* + * This is the start of another word, so not assignment. + */ + return (FALSE); + } else { + haveName = TRUE; + wasSpace = FALSE; + } + line++; + } + + /* + * A final check: if we stopped on a +, ?, ! or :, the next character must + * be an = or it ain't a valid assignment + */ + if (((*line == '+') || + (*line == '?') || + (*line == ':') || + (*line == '!')) && + (line[1] != '=')) + { + return (FALSE); + } else { + return (haveName); + } +} + +/*- + *--------------------------------------------------------------------- + * Parse_DoVar -- + * Take the variable assignment in the passed line and do it in the + * global context. + * + * Note: There is a lexical ambiguity with assignment modifier characters + * in variable names. This routine interprets the character before the = + * as a modifier. Therefore, an assignment like + * C++=/usr/bin/CC + * is interpreted as "C+ +=" instead of "C++ =". + * + * Results: + * none + * + * Side Effects: + * the variable structure of the given variable name is altered in the + * global context. + *--------------------------------------------------------------------- + */ +void +Parse_DoVar (line, ctxt) + char *line; /* a line guaranteed to be a variable + * assignment. This reduces error checks */ + GNode *ctxt; /* Context in which to do the assignment */ +{ + char *cp; /* pointer into line */ + enum { + VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL + } type; /* Type of assignment */ + char *opc; /* ptr to operator character to + * null-terminate the variable name */ + /* + * Avoid clobbered variable warnings by forcing the compiler + * to ``unregister'' variables + */ +#if __GNUC__ + (void) &cp; + (void) &line; +#endif + + /* + * Skip to variable name + */ + while ((*line == ' ') || (*line == '\t')) { + line++; + } + + /* + * Skip to operator character, nulling out whitespace as we go + */ + for (cp = line + 1; *cp != '='; cp++) { + if (isspace (*cp)) { + *cp = '\0'; + } + } + opc = cp-1; /* operator is the previous character */ + *cp++ = '\0'; /* nuke the = */ + + /* + * Check operator type + */ + switch (*opc) { + case '+': + type = VAR_APPEND; + *opc = '\0'; + break; + + case '?': + /* + * If the variable already has a value, we don't do anything. + */ + *opc = '\0'; + if (Var_Exists(line, ctxt)) { + return; + } else { + type = VAR_NORMAL; + } + break; + + case ':': + type = VAR_SUBST; + *opc = '\0'; + break; + + case '!': + type = VAR_SHELL; + *opc = '\0'; + break; + + default: + type = VAR_NORMAL; + break; + } + + while (isspace (*cp)) { + cp++; + } + + if (type == VAR_APPEND) { + Var_Append (line, cp, ctxt); + } else if (type == VAR_SUBST) { + /* + * Allow variables in the old value to be undefined, but leave their + * invocation alone -- this is done by forcing oldVars to be false. + * XXX: This can cause recursive variables, but that's not hard to do, + * and this allows someone to do something like + * + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * + * And not get an error. + */ + Boolean oldOldVars = oldVars; + + oldVars = FALSE; + cp = Var_Subst(NULL, cp, ctxt, FALSE); + oldVars = oldOldVars; + + Var_Set(line, cp, ctxt); + free(cp); + } else if (type == VAR_SHELL) { + char *args[4]; /* Args for invoking the shell */ + int fds[2]; /* Pipe streams */ + int cpid; /* Child PID */ + int pid; /* PID from wait() */ + Boolean freeCmd; /* TRUE if the command needs to be freed, i.e. + * if any variable expansion was performed */ + + /* + * Avoid clobbered variable warnings by forcing the compiler + * to ``unregister'' variables + */ +#if __GNUC__ + (void) &freeCmd; +#endif + /* + * Set up arguments for shell + */ + args[0] = "sh"; + args[1] = "-c"; + if (strchr(cp, '$') != (char *)NULL) { + /* + * There's a dollar sign in the command, so perform variable + * expansion on the whole thing. The resulting string will need + * freeing when we're done, so set freeCmd to TRUE. + */ + args[2] = Var_Subst(NULL, cp, VAR_CMD, TRUE); + freeCmd = TRUE; + } else { + args[2] = cp; + freeCmd = FALSE; + } + args[3] = (char *)NULL; + + /* + * Open a pipe for fetching its output + */ + pipe(fds); + + /* + * Fork + */ + cpid = vfork(); + if (cpid == 0) { + /* + * Close input side of pipe + */ + close(fds[0]); + + /* + * Duplicate the output stream to the shell's output, then + * shut the extra thing down. Note we don't fetch the error + * stream...why not? Why? + */ + dup2(fds[1], 1); + close(fds[1]); + + execv("/bin/sh", args); + _exit(1); + } else if (cpid < 0) { + /* + * Couldn't fork -- tell the user and make the variable null + */ + Parse_Error(PARSE_WARNING, "Couldn't exec \"%s\"", cp); + Var_Set(line, "", ctxt); + } else { + int status; + int cc; + Buffer buf; + char *res; + + /* + * No need for the writing half + */ + close(fds[1]); + + buf = Buf_Init (MAKE_BSIZE); + + do { + char result[BUFSIZ]; + cc = read(fds[0], result, sizeof(result)); + if (cc > 0) + Buf_AddBytes(buf, cc, (unsigned char *) result); + } + while (cc > 0 || (cc == -1 && errno == EINTR)); + + /* + * Close the input side of the pipe. + */ + close(fds[0]); + + /* + * Wait for the process to exit. + */ + while(((pid = wait(&status)) != cpid) && (pid >= 0)) + continue; + + res = (char *)Buf_GetAll (buf, &cc); + Buf_Destroy (buf, FALSE); + + if (cc == 0) { + /* + * Couldn't read the child's output -- tell the user and + * set the variable to null + */ + Parse_Error(PARSE_WARNING, "Couldn't read shell's output"); + } + + if (status) { + /* + * Child returned an error -- tell the user but still use + * the result. + */ + Parse_Error(PARSE_WARNING, "\"%s\" returned non-zero", cp); + } + + /* + * Null-terminate the result, convert newlines to spaces and + * install it in the variable. + */ + res[cc] = '\0'; + cp = &res[cc] - 1; + + if (*cp == '\n') { + /* + * A final newline is just stripped + */ + *cp-- = '\0'; + } + while (cp >= res) { + if (*cp == '\n') { + *cp = ' '; + } + cp--; + } + Var_Set(line, res, ctxt); + free(res); + + } + if (freeCmd) { + free(args[2]); + } + } else { + /* + * Normal assignment -- just do it. + */ + Var_Set (line, cp, ctxt); + } +} + +/*- + * ParseAddCmd -- + * Lst_ForEach function to add a command line to all targets + * + * Results: + * Always 0 + * + * Side Effects: + * A new element is added to the commands list of the node. + */ +static int +ParseAddCmd(gn, cmd) + GNode *gn; /* the node to which the command is to be added */ + char *cmd; /* the command to add */ +{ + /* if target already supplied, ignore commands */ + if (!(gn->type & OP_HAS_COMMANDS)) + (void)Lst_AtEnd(gn->commands, (ClientData)cmd); + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseHasCommands -- + * Callback procedure for Parse_File when destroying the list of + * targets on the last dependency line. Marks a target as already + * having commands if it does, to keep from having shell commands + * on multiple dependency lines. + * + * Results: + * Always 0. + * + * Side Effects: + * OP_HAS_COMMANDS may be set for the target. + * + *----------------------------------------------------------------------- + */ +static int +ParseHasCommands(gn) + GNode *gn; /* Node to examine */ +{ + if (!Lst_IsEmpty(gn->commands)) { + gn->type |= OP_HAS_COMMANDS; + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Parse_AddIncludeDir -- + * Add a directory to the path searched for included makefiles + * bracketed by double-quotes. Used by functions in main.c + * + * Results: + * None. + * + * Side Effects: + * The directory is appended to the list. + * + *----------------------------------------------------------------------- + */ +void +Parse_AddIncludeDir (dir) + char *dir; /* The name of the directory to add */ +{ + Dir_AddDir (parseIncPath, dir); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoInclude -- + * Push to another file. + * + * The input is the line minus the #include. A file spec is a string + * enclosed in <> or "". The former is looked for only in sysIncPath. + * The latter in . and the directories specified by -I command line + * options + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +static void +ParseDoInclude (file) + char *file; /* file specification */ +{ + char *fullname; /* full pathname of file */ + IFile *oldFile; /* state associated with current file */ + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + Boolean isSystem; /* TRUE if makefile is a system makefile */ + + /* + * Skip to delimiter character so we know where to look + */ + while ((*file == ' ') || (*file == '\t')) { + file++; + } + + if ((*file != '"') && (*file != '<')) { + Parse_Error (PARSE_FATAL, + ".include filename must be delimited by '\"' or '<'"); + return; + } + + /* + * Set the search path on which to find the include file based on the + * characters which bracket its name. Angle-brackets imply it's + * a system Makefile while double-quotes imply it's a user makefile + */ + if (*file == '<') { + isSystem = TRUE; + endc = '>'; + } else { + isSystem = FALSE; + endc = '"'; + } + + /* + * Skip to matching delimiter + */ + for (cp = ++file; *cp && *cp != endc; cp++) { + continue; + } + + if (*cp != endc) { + Parse_Error (PARSE_FATAL, + "Unclosed %cinclude filename. '%c' expected", + '.', endc); + return; + } + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Var_Subst (NULL, file, VAR_CMD, FALSE); + + /* + * Now we know the file's name and its search path, we attempt to + * find the durn thing. A return of NULL indicates the file don't + * exist. + */ + if (!isSystem) { + /* + * Include files contained in double-quotes are first searched for + * relative to the including file's location. We don't want to + * cd there, of course, so we just tack on the old file's + * leading path components and call Dir_FindFile to see if + * we can locate the beast. + */ + char *prefEnd; + + prefEnd = strrchr (fname, '/'); + if (prefEnd != (char *)NULL) { + char *newName; + + *prefEnd = '\0'; + newName = str_concat (fname, file, STR_ADDSLASH); + fullname = Dir_FindFile (newName, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(newName, dirSearchPath); + } + free (newName); + *prefEnd = '/'; + } else { + fullname = (char *)NULL; + } + } else { + fullname = (char *)NULL; + } + + if (fullname == (char *)NULL) { + /* + * System makefile or makefile wasn't found in same directory as + * included makefile. Search for it first on the -I search path, + * then on the .PATH search path, if not found in a -I directory. + * XXX: Suffix specific? + */ + fullname = Dir_FindFile (file, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(file, dirSearchPath); + } + } + + if (fullname == (char *)NULL) { + /* + * Still haven't found the makefile. Look for it on the system + * path as a last resort. + */ + fullname = Dir_FindFile(file, sysIncPath); + } + + if (fullname == (char *) NULL) { + *cp = endc; + Parse_Error (PARSE_FATAL, "Could not find %s", file); + return; + } + + /* + * Once we find the absolute path to the file, we get to save all the + * state from the current file before we can start reading this + * include file. The state is stored in an IFile structure which + * is placed on a list with other IFile structures. The list makes + * a very nice stack to track how we got here... + */ + oldFile = (IFile *) emalloc (sizeof (IFile)); + oldFile->fname = fname; + + oldFile->F = curFILE; + oldFile->p = curPTR; + oldFile->lineno = lineno; + + (void) Lst_AtFront (includes, (ClientData)oldFile); + + /* + * Once the previous state has been saved, we can get down to reading + * the new file. We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. Naturally enough, we start reading at line number 0. + */ + fname = fullname; + lineno = 0; + + curFILE = fopen (fullname, "r"); + curPTR = NULL; + if (curFILE == (FILE * ) NULL) { + Parse_Error (PARSE_FATAL, "Cannot open %s", fullname); + /* + * Pop to previous file + */ + (void) ParseEOF(0); + } +} + + +/*- + *--------------------------------------------------------------------- + * Parse_FromString -- + * Start Parsing from the given string + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +void +Parse_FromString(str) + char *str; +{ + IFile *oldFile; /* state associated with this file */ + + if (DEBUG(FOR)) + (void) fprintf(stderr, "%s\n----\n", str); + + oldFile = (IFile *) emalloc (sizeof (IFile)); + oldFile->lineno = lineno; + oldFile->fname = fname; + oldFile->F = curFILE; + oldFile->p = curPTR; + + (void) Lst_AtFront (includes, (ClientData)oldFile); + + curFILE = NULL; + curPTR = (PTR *) emalloc (sizeof (PTR)); + curPTR->str = curPTR->ptr = str; + lineno = 0; + fname = strdup(fname); +} + + +#ifdef SYSVINCLUDE +/*- + *--------------------------------------------------------------------- + * ParseTraditionalInclude -- + * Push to another file. + * + * The input is the line minus the "include". The file name is + * the string following the "include". + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +static void +ParseTraditionalInclude (file) + char *file; /* file specification */ +{ + char *fullname; /* full pathname of file */ + IFile *oldFile; /* state associated with current file */ + char *cp; /* current position in file spec */ + char *prefEnd; + + /* + * Skip over whitespace + */ + while ((*file == ' ') || (*file == '\t')) { + file++; + } + + if (*file == '\0') { + Parse_Error (PARSE_FATAL, + "Filename missing from \"include\""); + return; + } + + /* + * Skip to end of line or next whitespace + */ + for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) { + continue; + } + + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Var_Subst (NULL, file, VAR_CMD, FALSE); + + /* + * Now we know the file's name, we attempt to find the durn thing. + * A return of NULL indicates the file don't exist. + * + * Include files are first searched for relative to the including + * file's location. We don't want to cd there, of course, so we + * just tack on the old file's leading path components and call + * Dir_FindFile to see if we can locate the beast. + * XXX - this *does* search in the current directory, right? + */ + + prefEnd = strrchr (fname, '/'); + if (prefEnd != (char *)NULL) { + char *newName; + + *prefEnd = '\0'; + newName = str_concat (fname, file, STR_ADDSLASH); + fullname = Dir_FindFile (newName, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(newName, dirSearchPath); + } + free (newName); + *prefEnd = '/'; + } else { + fullname = (char *)NULL; + } + + if (fullname == (char *)NULL) { + /* + * System makefile or makefile wasn't found in same directory as + * included makefile. Search for it first on the -I search path, + * then on the .PATH search path, if not found in a -I directory. + * XXX: Suffix specific? + */ + fullname = Dir_FindFile (file, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(file, dirSearchPath); + } + } + + if (fullname == (char *)NULL) { + /* + * Still haven't found the makefile. Look for it on the system + * path as a last resort. + */ + fullname = Dir_FindFile(file, sysIncPath); + } + + if (fullname == (char *) NULL) { + Parse_Error (PARSE_FATAL, "Could not find %s", file); + return; + } + + /* + * Once we find the absolute path to the file, we get to save all the + * state from the current file before we can start reading this + * include file. The state is stored in an IFile structure which + * is placed on a list with other IFile structures. The list makes + * a very nice stack to track how we got here... + */ + oldFile = (IFile *) emalloc (sizeof (IFile)); + oldFile->fname = fname; + + oldFile->F = curFILE; + oldFile->p = curPTR; + oldFile->lineno = lineno; + + (void) Lst_AtFront (includes, (ClientData)oldFile); + + /* + * Once the previous state has been saved, we can get down to reading + * the new file. We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. Naturally enough, we start reading at line number 0. + */ + fname = fullname; + lineno = 0; + + curFILE = fopen (fullname, "r"); + curPTR = NULL; + if (curFILE == (FILE * ) NULL) { + Parse_Error (PARSE_FATAL, "Cannot open %s", fullname); + /* + * Pop to previous file + */ + (void) ParseEOF(1); + } +} +#endif + +/*- + *--------------------------------------------------------------------- + * ParseEOF -- + * Called when EOF is reached in the current file. If we were reading + * an include file, the includes stack is popped and things set up + * to go back to reading the previous file at the previous location. + * + * Results: + * CONTINUE if there's more to do. DONE if not. + * + * Side Effects: + * The old curFILE, is closed. The includes list is shortened. + * lineno, curFILE, and fname are changed if CONTINUE is returned. + *--------------------------------------------------------------------- + */ +static int +ParseEOF (opened) + int opened; +{ + IFile *ifile; /* the state on the top of the includes stack */ + + if (Lst_IsEmpty (includes)) { + return (DONE); + } + + ifile = (IFile *) Lst_DeQueue (includes); + free ((Address) fname); + fname = ifile->fname; + lineno = ifile->lineno; + if (opened && curFILE) + (void) fclose (curFILE); + if (curPTR) { + free((Address) curPTR->str); + free((Address) curPTR); + } + curFILE = ifile->F; + curPTR = ifile->p; + free ((Address)ifile); + return (CONTINUE); +} + +/*- + *--------------------------------------------------------------------- + * ParseReadc -- + * Read a character from the current file + * + * Results: + * The character that was read + * + * Side Effects: + *--------------------------------------------------------------------- + */ +static int +ParseReadc() +{ + if (curFILE) + return fgetc(curFILE); + + if (curPTR && *curPTR->ptr) + return *curPTR->ptr++; + return EOF; +} + + +/*- + *--------------------------------------------------------------------- + * ParseUnreadc -- + * Put back a character to the current file + * + * Results: + * None. + * + * Side Effects: + *--------------------------------------------------------------------- + */ +static void +ParseUnreadc(c) + int c; +{ + if (curFILE) { + ungetc(c, curFILE); + return; + } + if (curPTR) { + *--(curPTR->ptr) = c; + return; + } +} + + +/* ParseSkipLine(): + * Grab the next line + */ +static char * +ParseSkipLine(skip) + int skip; /* Skip lines that don't start with . */ +{ + char *line; + int c, lastc = '\0', lineLength; + Buffer buf; + + c = ParseReadc(); + + if (skip) { + /* + * Skip lines until get to one that begins with a + * special char. + */ + while ((c != '.') && (c != EOF)) { + while (((c != '\n') || (lastc == '\\')) && (c != EOF)) + { + /* + * Advance to next unescaped newline + */ + if ((lastc = c) == '\n') { + lineno++; + } + c = ParseReadc(); + } + lineno++; + + lastc = c; + c = ParseReadc (); + } + } + + if (c == EOF) { + Parse_Error (PARSE_FATAL, "Unclosed conditional/for loop"); + return ((char *)NULL); + } + + /* + * Read the entire line into buf + */ + buf = Buf_Init (MAKE_BSIZE); + if (c != '\n') { + do { + Buf_AddByte (buf, (Byte)c); + c = ParseReadc(); + } while ((c != '\n') && (c != EOF)); + } + lineno++; + + Buf_AddByte (buf, (Byte)'\0'); + line = (char *)Buf_GetAll (buf, &lineLength); + Buf_Destroy (buf, FALSE); + return line; +} + + +/*- + *--------------------------------------------------------------------- + * ParseReadLine -- + * Read an entire line from the input file. Called only by Parse_File. + * To facilitate escaped newlines and what have you, a character is + * buffered in 'lastc', which is '\0' when no characters have been + * read. When we break out of the loop, c holds the terminating + * character and lastc holds a character that should be added to + * the line (unless we don't read anything but a terminator). + * + * Results: + * A line w/o its newline + * + * Side Effects: + * Only those associated with reading a character + *--------------------------------------------------------------------- + */ +static char * +ParseReadLine () +{ + Buffer buf; /* Buffer for current line */ + register int c; /* the current character */ + register int lastc; /* The most-recent character */ + Boolean semiNL; /* treat semi-colons as newlines */ + Boolean ignDepOp; /* TRUE if should ignore dependency operators + * for the purposes of setting semiNL */ + Boolean ignComment; /* TRUE if should ignore comments (in a + * shell command */ + char *line; /* Result */ + int lineLength; /* Length of result */ + + semiNL = FALSE; + ignDepOp = FALSE; + ignComment = FALSE; + + /* + * Handle special-characters at the beginning of the line. Either a + * leading tab (shell command) or pound-sign (possible conditional) + * forces us to ignore comments and dependency operators and treat + * semi-colons as semi-colons (by leaving semiNL FALSE). This also + * discards completely blank lines. + */ + for (;;) { + c = ParseReadc(); + + if (c == '\t') { + ignComment = ignDepOp = TRUE; + break; + } else if (c == '\n') { + lineno++; + } else if (c == '#') { + ParseUnreadc(c); + break; + } else { + /* + * Anything else breaks out without doing anything + */ + break; + } + } + + if (c != EOF) { + lastc = c; + buf = Buf_Init(MAKE_BSIZE); + + while (((c = ParseReadc ()) != '\n' || (lastc == '\\')) && + (c != EOF)) + { +test_char: + switch(c) { + case '\n': + /* + * Escaped newline: read characters until a non-space or an + * unescaped newline and replace them all by a single space. + * This is done by storing the space over the backslash and + * dropping through with the next nonspace. If it is a + * semi-colon and semiNL is TRUE, it will be recognized as a + * newline in the code below this... + */ + lineno++; + lastc = ' '; + while ((c = ParseReadc ()) == ' ' || c == '\t') { + continue; + } + if (c == EOF || c == '\n') { + goto line_read; + } else { + /* + * Check for comments, semiNL's, etc. -- easier than + * ParseUnreadc(c); continue; + */ + goto test_char; + } + /*NOTREACHED*/ + break; + + case ';': + /* + * Semi-colon: Need to see if it should be interpreted as a + * newline + */ + if (semiNL) { + /* + * To make sure the command that may be following this + * semi-colon begins with a tab, we push one back into the + * input stream. This will overwrite the semi-colon in the + * buffer. If there is no command following, this does no + * harm, since the newline remains in the buffer and the + * whole line is ignored. + */ + ParseUnreadc('\t'); + goto line_read; + } + break; + case '=': + if (!semiNL) { + /* + * Haven't seen a dependency operator before this, so this + * must be a variable assignment -- don't pay attention to + * dependency operators after this. + */ + ignDepOp = TRUE; + } else if (lastc == ':' || lastc == '!') { + /* + * Well, we've seen a dependency operator already, but it + * was the previous character, so this is really just an + * expanded variable assignment. Revert semi-colons to + * being just semi-colons again and ignore any more + * dependency operators. + * + * XXX: Note that a line like "foo : a:=b" will blow up, + * but who'd write a line like that anyway? + */ + ignDepOp = TRUE; semiNL = FALSE; + } + break; + case '#': + if (!ignComment) { + if (compatMake || (lastc != '\\')) { + /* + * If the character is a hash mark and it isn't escaped + * (or we're being compatible), the thing is a comment. + * Skip to the end of the line. + */ + do { + c = ParseReadc(); + } while ((c != '\n') && (c != EOF)); + goto line_read; + } else { + /* + * Don't add the backslash. Just let the # get copied + * over. + */ + lastc = c; + continue; + } + } + break; + case ':': + case '!': + if (!ignDepOp && (c == ':' || c == '!')) { + /* + * A semi-colon is recognized as a newline only on + * dependency lines. Dependency lines are lines with a + * colon or an exclamation point. Ergo... + */ + semiNL = TRUE; + } + break; + } + /* + * Copy in the previous character and save this one in lastc. + */ + Buf_AddByte (buf, (Byte)lastc); + lastc = c; + + } + line_read: + lineno++; + + if (lastc != '\0') { + Buf_AddByte (buf, (Byte)lastc); + } + Buf_AddByte (buf, (Byte)'\0'); + line = (char *)Buf_GetAll (buf, &lineLength); + Buf_Destroy (buf, FALSE); + + if (line[0] == '.') { + /* + * The line might be a conditional. Ask the conditional module + * about it and act accordingly + */ + switch (Cond_Eval (line)) { + case COND_SKIP: + /* + * Skip to next conditional that evaluates to COND_PARSE. + */ + do { + free (line); + line = ParseSkipLine(1); + } while (line && Cond_Eval(line) != COND_PARSE); + if (line == NULL) + break; + /*FALLTHRU*/ + case COND_PARSE: + free ((Address) line); + line = ParseReadLine(); + break; + case COND_INVALID: + if (For_Eval(line)) { + int ok; + free(line); + do { + /* + * Skip after the matching end + */ + line = ParseSkipLine(0); + if (line == NULL) { + Parse_Error (PARSE_FATAL, + "Unexpected end of file in for loop.\n"); + break; + } + ok = For_Eval(line); + free(line); + } + while (ok); + if (line != NULL) + For_Run(); + line = ParseReadLine(); + } + break; + } + } + return (line); + + } else { + /* + * Hit end-of-file, so return a NULL line to indicate this. + */ + return((char *)NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFinishLine -- + * Handle the end of a dependency group. + * + * Results: + * Nothing. + * + * Side Effects: + * inLine set FALSE. 'targets' list destroyed. + * + *----------------------------------------------------------------------- + */ +static void +ParseFinishLine() +{ + extern int Suff_EndTransform(); + + if (inLine) { + Lst_ForEach(targets, Suff_EndTransform, (ClientData)NULL); + Lst_Destroy (targets, ParseHasCommands); + inLine = FALSE; + } +} + + +/*- + *--------------------------------------------------------------------- + * Parse_File -- + * Parse a file into its component parts, incorporating it into the + * current dependency graph. This is the main function and controls + * almost every other function in this module + * + * Results: + * None + * + * Side Effects: + * Loads. Nodes are added to the list of all targets, nodes and links + * are added to the dependency graph. etc. etc. etc. + *--------------------------------------------------------------------- + */ +void +Parse_File(name, stream) + char *name; /* the name of the file being read */ + FILE * stream; /* Stream open to makefile to parse */ +{ + register char *cp, /* pointer into the line */ + *line; /* the line we're working on */ + + inLine = FALSE; + fname = name; + curFILE = stream; + lineno = 0; + fatals = 0; + + do { + while ((line = ParseReadLine ()) != NULL) { + if (*line == '.') { + /* + * Lines that begin with the special character are either + * include or undef directives. + */ + for (cp = line + 1; isspace (*cp); cp++) { + continue; + } + if (strncmp (cp, "include", 7) == 0) { + ParseDoInclude (cp + 7); + goto nextLine; + } else if (strncmp(cp, "undef", 5) == 0) { + char *cp2; + for (cp += 5; isspace(*cp); cp++) { + continue; + } + + for (cp2 = cp; !isspace(*cp2) && (*cp2 != '\0'); cp2++) { + continue; + } + + *cp2 = '\0'; + + Var_Delete(cp, VAR_GLOBAL); + goto nextLine; + } + } + if (*line == '#') { + /* If we're this far, the line must be a comment. */ + goto nextLine; + } + + if (*line == '\t' +#ifdef POSIX + || *line == ' ' +#endif + ) + { + /* + * If a line starts with a tab (or space in POSIX-land), it + * can only hope to be a creation command. + */ + shellCommand: + for (cp = line + 1; isspace (*cp); cp++) { + continue; + } + if (*cp) { + if (inLine) { + /* + * So long as it's not a blank line and we're actually + * in a dependency spec, add the command to the list of + * commands of all targets in the dependency spec + */ + Lst_ForEach (targets, ParseAddCmd, (ClientData)cp); + continue; + } else { + Parse_Error (PARSE_FATAL, + "Unassociated shell command \"%.20s\"", + cp); + } + } +#ifdef SYSVINCLUDE + } else if (strncmp (line, "include", 7) == 0 && + strchr(line, ':') == NULL) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude (line + 7); + goto nextLine; +#endif + } else if (Parse_IsVar (line)) { + ParseFinishLine(); + Parse_DoVar (line, VAR_GLOBAL); + } else { + /* + * We now know it's a dependency line so it needs to have all + * variables expanded before being parsed. Tell the variable + * module to complain if some variable is undefined... + * To make life easier on novices, if the line is indented we + * first make sure the line has a dependency operator in it. + * If it doesn't have an operator and we're in a dependency + * line's script, we assume it's actually a shell command + * and add it to the current list of targets. + * + * Note that POSIX declares all lines that start with + * whitespace are shell commands, so there's no need to check + * here... + */ + Boolean nonSpace = FALSE; + + cp = line; +#ifndef POSIX + if (line[0] == ' ') { + while ((*cp != ':') && (*cp != '!') && (*cp != '\0')) { + if (!isspace(*cp)) { + nonSpace = TRUE; + } + cp++; + } + } + + if (*cp == '\0') { + if (inLine) { + Parse_Error (PARSE_WARNING, + "Shell command needs a leading tab"); + goto shellCommand; + } else if (nonSpace) { + Parse_Error (PARSE_FATAL, "Missing operator"); + } + } else { +#endif + ParseFinishLine(); + + cp = Var_Subst (NULL, line, VAR_CMD, TRUE); + free (line); + line = cp; + + /* + * Need a non-circular list for the target nodes + */ + targets = Lst_Init (FALSE); + inLine = TRUE; + + ParseDoDependency (line); +#ifndef POSIX + } +#endif + } + + nextLine: + + free (line); + } + /* + * Reached EOF, but it may be just EOF of an include file... + */ + } while (ParseEOF(1) == CONTINUE); + + /* + * Make sure conditionals are clean + */ + Cond_End(); + + if (fatals) { + fprintf (stderr, "Fatal errors encountered -- cannot continue\n"); + exit (1); + } +} + +/*- + *--------------------------------------------------------------------- + * Parse_Init -- + * initialize the parsing module + * + * Results: + * none + * + * Side Effects: + * the parseIncPath list is initialized... + *--------------------------------------------------------------------- + */ +void +Parse_Init () +{ + char *cp = NULL, *start; + /* avoid faults on read-only strings */ + static char syspath[] = _PATH_DEFSYSPATH; + + mainNode = NILGNODE; + parseIncPath = Lst_Init (FALSE); + sysIncPath = Lst_Init (FALSE); + includes = Lst_Init (FALSE); + + /* + * Add the directories from the DEFSYSPATH (more than one may be given + * as dir1:...:dirn) to the system include path. + */ + for (start = syspath; *start != '\0'; start = cp) { + for (cp = start; *cp != '\0' && *cp != ':'; cp++) + continue; + if (*cp == '\0') { + Dir_AddDir(sysIncPath, start); + } else { + *cp++ = '\0'; + Dir_AddDir(sysIncPath, start); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Parse_MainName -- + * Return a Lst of the main target to create for main()'s sake. If + * no such target exists, we Punt with an obnoxious error message. + * + * Results: + * A Lst of the single node to create. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Lst +Parse_MainName() +{ + Lst main; /* result list */ + + main = Lst_Init (FALSE); + + if (mainNode == NILGNODE) { + Punt ("make: no target to make.\n"); + /*NOTREACHED*/ + } else if (mainNode->type & OP_DOUBLEDEP) { + (void) Lst_AtEnd (main, (ClientData)mainNode); + Lst_Concat(main, mainNode->cohorts, LST_CONCNEW); + } + else + (void) Lst_AtEnd (main, (ClientData)mainNode); + return (main); +} diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h new file mode 100644 index 000000000000..19f29efc36e3 --- /dev/null +++ b/usr.bin/make/pathnames.h @@ -0,0 +1,39 @@ +/* + * 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. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +#define _PATH_OBJDIR "obj" +#define _PATH_DEFSHELLDIR "/bin" +#define _PATH_DEFSYSMK "sys.mk" +#define _PATH_DEFSYSPATH "/usr/share/mk" diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h new file mode 100644 index 000000000000..797e4533f3c4 --- /dev/null +++ b/usr.bin/make/sprite.h @@ -0,0 +1,114 @@ +/* + * 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. + * + * @(#)sprite.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * sprite.h -- + * + * Common constants and type declarations for Sprite. + */ + +#ifndef _SPRITE +#define _SPRITE + + +/* + * A boolean type is defined as an integer, not an enum. This allows a + * boolean argument to be an expression that isn't strictly 0 or 1 valued. + */ + +typedef int Boolean; +#ifndef TRUE +#define TRUE 1 +#endif TRUE +#ifndef FALSE +#define FALSE 0 +#endif FALSE + +/* + * Functions that must return a status can return a ReturnStatus to + * indicate success or type of failure. + */ + +typedef int ReturnStatus; + +/* + * The following statuses overlap with the first 2 generic statuses + * defined in status.h: + * + * SUCCESS There was no error. + * FAILURE There was a general error. + */ + +#define SUCCESS 0x00000000 +#define FAILURE 0x00000001 + + +/* + * A nil pointer must be something that will cause an exception if + * referenced. There are two nils: the kernels nil and the nil used + * by user processes. + */ + +#define NIL (~0) +#define USER_NIL 0 +#ifndef NULL +#define NULL 0 +#endif NULL + +/* + * An address is just a pointer in C. It is defined as a character pointer + * so that address arithmetic will work properly, a byte at a time. + */ + +typedef char *Address; + +/* + * ClientData is an uninterpreted word. It is defined as an int so that + * kdbx will not interpret client data as a string. Unlike an "Address", + * client data will generally not be used in arithmetic. + */ + +typedef int *ClientData; + +#ifdef notdef +#include "status.h" +#endif + +#endif _SPRITE diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c new file mode 100644 index 000000000000..84e95eb1b403 --- /dev/null +++ b/usr.bin/make/str.c @@ -0,0 +1,439 @@ +/*- + * 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[] = "@(#)str.c 8.4 (Berkeley) 3/21/94"; +#endif /* not lint */ + +#include "make.h" + +/*- + * str_concat -- + * concatenate the two strings, inserting a space or slash between them, + * freeing them if requested. + * + * returns -- + * the resulting string in allocated space. + */ +char * +str_concat(s1, s2, flags) + char *s1, *s2; + int flags; +{ + register int len1, len2; + register char *result; + + /* get the length of both strings */ + len1 = strlen(s1); + len2 = strlen(s2); + + /* allocate length plus separator plus EOS */ + result = emalloc((u_int)(len1 + len2 + 2)); + + /* copy first string into place */ + memcpy(result, s1, len1); + + /* add separator character */ + if (flags & STR_ADDSPACE) { + result[len1] = ' '; + ++len1; + } else if (flags & STR_ADDSLASH) { + result[len1] = '/'; + ++len1; + } + + /* copy second string plus EOS into place */ + memcpy(result + len1, s2, len2 + 1); + + /* free original strings */ + if (flags & STR_DOFREE) { + (void)free(s1); + (void)free(s2); + } + return(result); +} + +/*- + * brk_string -- + * Fracture a string into an array of words (as delineated by tabs or + * spaces) taking quotation marks into account. Leading tabs/spaces + * are ignored. + * + * returns -- + * Pointer to the array of pointers to the words. To make life easier, + * the first word is always the value of the .MAKE variable. + */ +char ** +brk_string(str, store_argc) + register char *str; + int *store_argc; +{ + static int argmax, curlen; + static char **argv, *buf; + register int argc, ch; + register char inquote, *p, *start, *t; + int len; + + /* save off pmake variable */ + if (!argv) { + argv = (char **)emalloc((argmax = 50) * sizeof(char *)); + argv[0] = Var_Value(".MAKE", VAR_GLOBAL); + } + + /* skip leading space chars. */ + for (; *str == ' ' || *str == '\t'; ++str) + continue; + + /* allocate room for a copy of the string */ + if ((len = strlen(str) + 1) > curlen) + buf = emalloc(curlen = len); + + /* + * copy the string; at the same time, parse backslashes, + * quotes and build the argument list. + */ + argc = 1; + inquote = '\0'; + for (p = str, start = t = buf;; ++p) { + switch(ch = *p) { + case '"': + case '\'': + if (inquote) + if (inquote == ch) + inquote = '\0'; + else + break; + else + inquote = (char) ch; + continue; + case ' ': + case '\t': + if (inquote) + break; + if (!start) + continue; + /* FALLTHROUGH */ + case '\n': + case '\0': + /* + * end of a token -- make sure there's enough argv + * space and save off a pointer. + */ + *t++ = '\0'; + if (argc == argmax) { + argmax *= 2; /* ramp up fast */ + if (!(argv = (char **)realloc(argv, + argmax * sizeof(char *)))) + enomem(); + } + argv[argc++] = start; + start = (char *)NULL; + if (ch == '\n' || ch == '\0') + goto done; + continue; + case '\\': + switch (ch = *++p) { + case '\0': + case '\n': + /* hmmm; fix it up as best we can */ + ch = '\\'; + --p; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + } + break; + } + if (!start) + start = t; + *t++ = (char) ch; + } +done: argv[argc] = (char *)NULL; + *store_argc = argc; + return(argv); +} + +/* + * Str_FindSubstring -- See if a string contains a particular substring. + * + * Results: If string contains substring, the return value is the location of + * the first matching instance of substring in string. If string doesn't + * contain substring, the return value is NULL. Matching is done on an exact + * character-for-character basis with no wildcards or special characters. + * + * Side effects: None. + */ +char * +Str_FindSubstring(string, substring) + register char *string; /* String to search. */ + char *substring; /* Substring to find in string */ +{ + register char *a, *b; + + /* + * First scan quickly through the two strings looking for a single- + * character match. When it's found, then compare the rest of the + * substring. + */ + + for (b = substring; *string != 0; string += 1) { + if (*string != *b) + continue; + a = string; + for (;;) { + if (*b == 0) + return(string); + if (*a++ != *b++) + break; + } + b = substring; + } + return((char *) NULL); +} + +/* + * Str_Match -- + * + * See if a particular string matches a particular pattern. + * + * Results: Non-zero is returned if string matches pattern, 0 otherwise. The + * matching operation permits the following special characters in the + * pattern: *?\[] (see the man page for details on what these mean). + * + * Side effects: None. + */ +int +Str_Match(string, pattern) + register char *string; /* String */ + register char *pattern; /* Pattern */ +{ + char c2; + + for (;;) { + /* + * See if we're at the end of both the pattern and the + * string. If, we succeeded. If we're at the end of the + * pattern but not at the end of the string, we failed. + */ + if (*pattern == 0) + return(!*string); + if (*string == 0 && *pattern != '*') + return(0); + /* + * Check for a "*" as the next pattern character. It matches + * any substring. We handle this by calling ourselves + * recursively for each postfix of string, until either we + * match or we reach the end of the string. + */ + if (*pattern == '*') { + pattern += 1; + if (*pattern == 0) + return(1); + while (*string != 0) { + if (Str_Match(string, pattern)) + return(1); + ++string; + } + return(0); + } + /* + * Check for a "?" as the next pattern character. It matches + * any single character. + */ + if (*pattern == '?') + goto thisCharOK; + /* + * Check for a "[" as the next pattern character. It is + * followed by a list of characters that are acceptable, or + * by a range (two characters separated by "-"). + */ + if (*pattern == '[') { + ++pattern; + for (;;) { + if ((*pattern == ']') || (*pattern == 0)) + return(0); + if (*pattern == *string) + break; + if (pattern[1] == '-') { + c2 = pattern[2]; + if (c2 == 0) + return(0); + if ((*pattern <= *string) && + (c2 >= *string)) + break; + if ((*pattern >= *string) && + (c2 <= *string)) + break; + pattern += 2; + } + ++pattern; + } + while ((*pattern != ']') && (*pattern != 0)) + ++pattern; + goto thisCharOK; + } + /* + * If the next pattern character is '/', just strip off the + * '/' so we do exact matching on the character that follows. + */ + if (*pattern == '\\') { + ++pattern; + if (*pattern == 0) + return(0); + } + /* + * There's no special character. Just make sure that the + * next characters of each string match. + */ + if (*pattern != *string) + return(0); +thisCharOK: ++pattern; + ++string; + } +} + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVMatch -- + * Check word against pattern for a match (% is wild), + * + * Results: + * Returns the beginning position of a match or null. The number + * of characters matched is returned in len. + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +char * +Str_SYSVMatch(word, pattern, len) + char *word; /* Word to examine */ + char *pattern; /* Pattern to examine against */ + int *len; /* Number of characters to substitute */ +{ + char *p = pattern; + char *w = word; + char *m; + + if (*p == '\0') { + /* Null pattern is the whole string */ + *len = strlen(w); + return w; + } + + if ((m = strchr(p, '%')) != NULL) { + /* check that the prefix matches */ + for (; p != m && *w && *w == *p; w++, p++) + continue; + + if (p != m) + return NULL; /* No match */ + + if (*++p == '\0') { + /* No more pattern, return the rest of the string */ + *len = strlen(w); + return w; + } + } + + m = w; + + /* Find a matching tail */ + do + if (strcmp(p, w) == 0) { + *len = w - m; + return m; + } + while (*w++ != '\0'); + + return NULL; +} + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVSubst -- + * Substitute '%' on the pattern with len characters from src. + * If the pattern does not contain a '%' prepend len characters + * from src. + * + * Results: + * None + * + * Side Effects: + * Places result on buf + * + *----------------------------------------------------------------------- + */ +void +Str_SYSVSubst(buf, pat, src, len) + Buffer buf; + char *pat; + char *src; + int len; +{ + char *m; + + if ((m = strchr(pat, '%')) != NULL) { + /* Copy the prefix */ + Buf_AddBytes(buf, m - pat, (Byte *) pat); + /* skip the % */ + pat = m + 1; + } + + /* Copy the pattern */ + Buf_AddBytes(buf, len, (Byte *) src); + + /* append the rest */ + Buf_AddBytes(buf, strlen(pat), (Byte *) pat); +} diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c new file mode 100644 index 000000000000..b8eaa5afdc22 --- /dev/null +++ b/usr.bin/make/suff.c @@ -0,0 +1,2218 @@ +/* + * 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[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94"; +#endif /* not lint */ + +/*- + * suff.c -- + * Functions to maintain suffix lists and find implicit dependents + * using suffix transformation rules + * + * Interface: + * Suff_Init Initialize all things to do with suffixes. + * + * Suff_DoPaths This function is used to make life easier + * when searching for a file according to its + * suffix. It takes the global search path, + * as defined using the .PATH: target, and appends + * its directories to the path of each of the + * defined suffixes, as specified using + * .PATH: targets. In addition, all + * directories given for suffixes labeled as + * include files or libraries, using the .INCLUDES + * or .LIBS targets, are played with using + * Dir_MakeFlags to create the .INCLUDES and + * .LIBS global variables. + * + * Suff_ClearSuffixes Clear out all the suffixes and defined + * transformations. + * + * Suff_IsTransform Return TRUE if the passed string is the lhs + * of a transformation rule. + * + * Suff_AddSuffix Add the passed string as another known suffix. + * + * Suff_GetPath Return the search path for the given suffix. + * + * Suff_AddInclude Mark the given suffix as denoting an include + * file. + * + * Suff_AddLib Mark the given suffix as denoting a library. + * + * Suff_AddTransform Add another transformation to the suffix + * graph. Returns GNode suitable for framing, I + * mean, tacking commands, attributes, etc. on. + * + * Suff_SetNull Define the suffix to consider the suffix of + * any file that doesn't have a known one. + * + * Suff_FindDeps Find implicit sources for and the location of + * a target based on its suffix. Returns the + * bottom-most node added to the graph or NILGNODE + * if the target had no implicit sources. + */ + +#include +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "bit.h" + +static Lst sufflist; /* Lst of suffixes */ +static Lst transforms; /* Lst of transformation rules */ + +static int sNum = 0; /* Counter for assigning suffix numbers */ + +/* + * Structure describing an individual suffix. + */ +typedef struct _Suff { + char *name; /* The suffix itself */ + int nameLen; /* Length of the suffix */ + short flags; /* Type of suffix */ +#define SUFF_INCLUDE 0x01 /* One which is #include'd */ +#define SUFF_LIBRARY 0x02 /* One which contains a library */ +#define SUFF_NULL 0x04 /* The empty suffix */ + Lst searchPath; /* The path along which files of this suffix + * may be found */ + int sNum; /* The suffix number */ + Lst parents; /* Suffixes we have a transformation to */ + Lst children; /* Suffixes we have a transformation from */ +} Suff; + +/* + * Structure used in the search for implied sources. + */ +typedef struct _Src { + char *file; /* The file to look for */ + char *pref; /* Prefix from which file was formed */ + Suff *suff; /* The suffix on the file */ + struct _Src *parent; /* The Src for which this is a source */ + GNode *node; /* The node describing the file */ + int children; /* Count of existing children (so we don't free + * this thing too early or never nuke it) */ +} Src; + +/* + * A structure for passing more than one argument to the Lst-library-invoked + * function... + */ +typedef struct { + Lst l; + Src *s; +} LstSrc; + +static Suff *suffNull; /* The NULL suffix for this run */ +static Suff *emptySuff; /* The empty suffix required for POSIX + * single-suffix transformation rules */ + + +static char *SuffStrIsPrefix __P((char *, char *)); +static char *SuffSuffIsSuffix __P((Suff *, char *)); +static int SuffSuffIsSuffixP __P((Suff *, char *)); +static int SuffSuffHasNameP __P((Suff *, char *)); +static int SuffSuffIsPrefix __P((Suff *, char *)); +static int SuffGNHasNameP __P((GNode *, char *)); +static void SuffFree __P((Suff *)); +static Suff* SuffCopy __P((Suff *)); +static void SuffInsert __P((Lst, Suff *)); +static Boolean SuffParseTransform __P((char *, Suff **, Suff **)); +static int SuffRebuildGraph __P((GNode *, Suff *)); +static int SuffAddSrc __P((Suff *, LstSrc *)); +static void SuffAddLevel __P((Lst, Src *)); +static void SuffFreeSrc __P((Src *)); +static Src *SuffFindThem __P((Lst)); +static Src *SuffFindCmds __P((Src *)); +static int SuffExpandChildren __P((GNode *, GNode *)); +static Boolean SuffApplyTransform __P((GNode *, GNode *, Suff *, Suff *)); +static void SuffFindArchiveDeps __P((GNode *)); +static void SuffFindNormalDeps __P((GNode *)); +static int SuffPrintName __P((Suff *)); +static int SuffPrintSuff __P((Suff *)); +static int SuffPrintTrans __P((GNode *)); + + /*************** Lst Predicates ****************/ +/*- + *----------------------------------------------------------------------- + * SuffStrIsPrefix -- + * See if pref is a prefix of str. + * + * Results: + * NULL if it ain't, pointer to character in str after prefix if so + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static char * +SuffStrIsPrefix (pref, str) + register char *pref; /* possible prefix */ + register char *str; /* string to check */ +{ + while (*str && *pref == *str) { + pref++; + str++; + } + + return (*pref ? NULL : str); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffix -- + * See if suff is a suffix of str. Str should point to THE END of the + * string to check. (THE END == the null byte) + * + * Results: + * NULL if it ain't, pointer to character in str before suffix if + * it is. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static char * +SuffSuffIsSuffix (s, str) + register Suff *s; /* possible suffix */ + char *str; /* string to examine */ +{ + register char *p1; /* Pointer into suffix name */ + register char *p2; /* Pointer into string being examined */ + + p1 = s->name + s->nameLen; + p2 = str; + + while (p1 >= s->name && *p1 == *p2) { + p1--; + p2--; + } + + return (p1 == s->name - 1 ? p2 : NULL); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffixP -- + * Predicate form of SuffSuffIsSuffix. Passed as the callback function + * to Lst_Find. + * + * Results: + * 0 if the suffix is the one desired, non-zero if not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static int +SuffSuffIsSuffixP(s, str) + Suff *s; + char *str; +{ + return(!SuffSuffIsSuffix(s, str)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffHasNameP -- + * Callback procedure for finding a suffix based on its name. Used by + * Suff_GetPath. + * + * Results: + * 0 if the suffix is of the given name. non-zero otherwise. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffHasNameP (s, sname) + Suff *s; /* Suffix to check */ + char *sname; /* Desired name */ +{ + return (strcmp (sname, s->name)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsPrefix -- + * See if the suffix described by s is a prefix of the string. Care + * must be taken when using this to search for transformations and + * what-not, since there could well be two suffixes, one of which + * is a prefix of the other... + * + * Results: + * 0 if s is a prefix of str. non-zero otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffIsPrefix (s, str) + Suff *s; /* suffix to compare */ + char *str; /* string to examine */ +{ + return (SuffStrIsPrefix (s->name, str) == NULL ? 1 : 0); +} + +/*- + *----------------------------------------------------------------------- + * SuffGNHasNameP -- + * See if the graph node has the desired name + * + * Results: + * 0 if it does. non-zero if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffGNHasNameP (gn, name) + GNode *gn; /* current node we're looking at */ + char *name; /* name we're looking for */ +{ + return (strcmp (name, gn->name)); +} + + /*********** Maintenance Functions ************/ +/*- + *----------------------------------------------------------------------- + * SuffFree -- + * Free up all memory associated with the given suffix structure. + * + * Results: + * none + * + * Side Effects: + * the suffix entry is detroyed + *----------------------------------------------------------------------- + */ +static void +SuffFree (s) + Suff *s; +{ + Lst_Destroy (s->children, NOFREE); + Lst_Destroy (s->parents, NOFREE); + Lst_Destroy (s->searchPath, Dir_Destroy); + free ((Address)s->name); + free ((Address)s); +} + +/*- + *----------------------------------------------------------------------- + * SuffInsert -- + * Insert the suffix into the list keeping the list ordered by suffix + * numbers. + * + * Results: + * None + * + * Side Effects: + * Not really + *----------------------------------------------------------------------- + */ +static void +SuffInsert (l, s) + Lst l; /* the list where in s should be inserted */ + Suff *s; /* the suffix to insert */ +{ + LstNode ln; /* current element in l we're examining */ + Suff *s2 = NULL; /* the suffix descriptor in this element */ + + if (Lst_Open (l) == FAILURE) { + return; + } + while ((ln = Lst_Next (l)) != NILLNODE) { + s2 = (Suff *) Lst_Datum (ln); + if (s2->sNum >= s->sNum) { + break; + } + } + + Lst_Close (l); + if (DEBUG(SUFF)) { + printf("inserting %s(%d)...", s->name, s->sNum); + } + if (ln == NILLNODE) { + if (DEBUG(SUFF)) { + printf("at end of list\n"); + } + (void)Lst_AtEnd (l, (ClientData)s); + } else if (s2->sNum != s->sNum) { + if (DEBUG(SUFF)) { + printf("before %s(%d)\n", s2->name, s2->sNum); + } + (void)Lst_Insert (l, ln, (ClientData)s); + } else if (DEBUG(SUFF)) { + printf("already there\n"); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_ClearSuffixes -- + * This is gross. Nuke the list of suffixes but keep all transformation + * rules around. The transformation graph is destroyed in this process, + * but we leave the list of rules so when a new graph is formed the rules + * will remain. + * This function is called from the parse module when a + * .SUFFIXES:\n line is encountered. + * + * Results: + * none + * + * Side Effects: + * the sufflist and its graph nodes are destroyed + *----------------------------------------------------------------------- + */ +void +Suff_ClearSuffixes () +{ + Lst_Destroy (sufflist, SuffFree); + + sufflist = Lst_Init(FALSE); + sNum = 0; + suffNull = emptySuff; +} + +/*- + *----------------------------------------------------------------------- + * SuffParseTransform -- + * Parse a transformation string to find its two component suffixes. + * + * Results: + * TRUE if the string is a valid transformation and FALSE otherwise. + * + * Side Effects: + * The passed pointers are overwritten. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffParseTransform(str, srcPtr, targPtr) + char *str; /* String being parsed */ + Suff **srcPtr; /* Place to store source of trans. */ + Suff **targPtr; /* Place to store target of trans. */ +{ + register LstNode srcLn; /* element in suffix list of trans source*/ + register Suff *src; /* Source of transformation */ + register LstNode targLn; /* element in suffix list of trans target*/ + register char *str2; /* Extra pointer (maybe target suffix) */ + LstNode singleLn; /* element in suffix list of any suffix + * that exactly matches str */ + Suff *single = NULL;/* Source of possible transformation to + * null suffix */ + + srcLn = NILLNODE; + singleLn = NILLNODE; + + /* + * Loop looking first for a suffix that matches the start of the + * string and then for one that exactly matches the rest of it. If + * we can find two that meet these criteria, we've successfully + * parsed the string. + */ + for (;;) { + if (srcLn == NILLNODE) { + srcLn = Lst_Find(sufflist, (ClientData)str, SuffSuffIsPrefix); + } else { + srcLn = Lst_FindFrom (sufflist, Lst_Succ(srcLn), (ClientData)str, + SuffSuffIsPrefix); + } + if (srcLn == NILLNODE) { + /* + * Ran out of source suffixes -- no such rule + */ + if (singleLn != NILLNODE) { + /* + * Not so fast Mr. Smith! There was a suffix that encompassed + * the entire string, so we assume it was a transformation + * to the null suffix (thank you POSIX). We still prefer to + * find a double rule over a singleton, hence we leave this + * check until the end. + * + * XXX: Use emptySuff over suffNull? + */ + *srcPtr = single; + *targPtr = SuffCopy(suffNull); + return(TRUE); + } + return (FALSE); + } + src = (Suff *) Lst_Datum (srcLn); + str2 = str + src->nameLen; + if (*str2 == '\0') { + single = src; + singleLn = srcLn; + } else { + targLn = Lst_Find(sufflist, (ClientData)str2, SuffSuffHasNameP); + if (targLn != NILLNODE) { + *srcPtr = src; + *targPtr = (Suff *)Lst_Datum(targLn); + return (TRUE); + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_IsTransform -- + * Return TRUE if the given string is a transformation rule + * + * + * Results: + * TRUE if the string is a concatenation of two known suffixes. + * FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Suff_IsTransform (str) + char *str; /* string to check */ +{ + Suff *src, *targ; + + return (SuffParseTransform(str, &src, &targ)); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddTransform -- + * Add the transformation rule described by the line to the + * list of rules and place the transformation itself in the graph + * + * Results: + * The node created for the transformation in the transforms list + * + * Side Effects: + * The node is placed on the end of the transforms Lst and links are + * made between the two suffixes mentioned in the target name + *----------------------------------------------------------------------- + */ +GNode * +Suff_AddTransform (line) + char *line; /* name of transformation to add */ +{ + GNode *gn; /* GNode of transformation rule */ + Suff *s, /* source suffix */ + *t; /* target suffix */ + LstNode ln; /* Node for existing transformation */ + + ln = Lst_Find (transforms, (ClientData)line, SuffGNHasNameP); + if (ln == NILLNODE) { + /* + * Make a new graph node for the transformation. It will be filled in + * by the Parse module. + */ + gn = Targ_NewGN (line); + (void)Lst_AtEnd (transforms, (ClientData)gn); + } else { + /* + * New specification for transformation rule. Just nuke the old list + * of commands so they can be filled in again... We don't actually + * free the commands themselves, because a given command can be + * attached to several different transformations. + */ + gn = (GNode *) Lst_Datum (ln); + Lst_Destroy (gn->commands, NOFREE); + Lst_Destroy (gn->children, NOFREE); + gn->commands = Lst_Init (FALSE); + gn->children = Lst_Init (FALSE); + } + + gn->type = OP_TRANSFORM; + + (void)SuffParseTransform(line, &s, &t); + + /* + * link the two together in the proper relationship and order + */ + if (DEBUG(SUFF)) { + printf("defining transformation from `%s' to `%s'\n", + s->name, t->name); + } + SuffInsert (t->children, s); + SuffInsert (s->parents, t); + + return (gn); +} + +/*- + *----------------------------------------------------------------------- + * Suff_EndTransform -- + * Handle the finish of a transformation definition, removing the + * transformation from the graph if it has neither commands nor + * sources. This is a callback procedure for the Parse module via + * Lst_ForEach + * + * Results: + * === 0 + * + * Side Effects: + * If the node has no commands or children, the children and parents + * lists of the affected suffices are altered. + * + *----------------------------------------------------------------------- + */ +int +Suff_EndTransform(gn) + GNode *gn; /* Node for transformation */ +{ + if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && + Lst_IsEmpty(gn->children)) + { + Suff *s, *t; + LstNode ln; + + (void)SuffParseTransform(gn->name, &s, &t); + + if (DEBUG(SUFF)) { + printf("deleting transformation from %s to %s\n", + s->name, t->name); + } + + /* + * Remove the source from the target's children list. We check for a + * nil return to handle a beanhead saying something like + * .c.o .c.o: + * + * We'll be called twice when the next target is seen, but .c and .o + * are only linked once... + */ + ln = Lst_Member(t->children, (ClientData)s); + if (ln != NILLNODE) { + (void)Lst_Remove(t->children, ln); + } + + /* + * Remove the target from the source's parents list + */ + ln = Lst_Member(s->parents, (ClientData)t); + if (ln != NILLNODE) { + (void)Lst_Remove(s->parents, ln); + } + } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) { + printf("transformation %s complete\n", gn->name); + } + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffRebuildGraph -- + * Called from Suff_AddSuffix via Lst_ForEach to search through the + * list of existing transformation rules and rebuild the transformation + * graph when it has been destroyed by Suff_ClearSuffixes. If the + * given rule is a transformation involving this suffix and another, + * existing suffix, the proper relationship is established between + * the two. + * + * Results: + * Always 0. + * + * Side Effects: + * The appropriate links will be made between this suffix and + * others if transformation rules exist for it. + * + *----------------------------------------------------------------------- + */ +static int +SuffRebuildGraph(transform, s) + GNode *transform; /* Transformation to test */ + Suff *s; /* Suffix to rebuild */ +{ + register char *cp; + register LstNode ln; + register Suff *s2; + + /* + * First see if it is a transformation from this suffix. + */ + cp = SuffStrIsPrefix(s->name, transform->name); + if (cp != (char *)NULL) { + ln = Lst_Find(sufflist, (ClientData)cp, SuffSuffHasNameP); + if (ln != NILLNODE) { + /* + * Found target. Link in and return, since it can't be anything + * else. + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s2->children, s); + SuffInsert(s->parents, s2); + return(0); + } + } + + /* + * Not from, maybe to? + */ + cp = SuffSuffIsSuffix(s, transform->name + strlen(transform->name)); + if (cp != (char *)NULL) { + /* + * Null-terminate the source suffix in order to find it. + */ + cp[1] = '\0'; + ln = Lst_Find(sufflist, (ClientData)transform->name, SuffSuffHasNameP); + /* + * Replace the start of the target suffix + */ + cp[1] = s->name[0]; + if (ln != NILLNODE) { + /* + * Found it -- establish the proper relationship + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s->children, s2); + SuffInsert(s2->parents, s); + } + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddSuffix -- + * Add the suffix in string to the end of the list of known suffixes. + * Should we restructure the suffix graph? Make doesn't... + * + * Results: + * None + * + * Side Effects: + * A GNode is created for the suffix and a Suff structure is created and + * added to the suffixes list unless the suffix was already known. + *----------------------------------------------------------------------- + */ +void +Suff_AddSuffix (str) + char *str; /* the name of the suffix to add */ +{ + Suff *s; /* new suffix descriptor */ + LstNode ln; + + ln = Lst_Find (sufflist, (ClientData)str, SuffSuffHasNameP); + if (ln == NILLNODE) { + s = (Suff *) emalloc (sizeof (Suff)); + + s->name = strdup (str); + s->nameLen = strlen (s->name); + s->searchPath = Lst_Init (FALSE); + s->children = Lst_Init (FALSE); + s->parents = Lst_Init (FALSE); + s->sNum = sNum++; + s->flags = 0; + + (void)Lst_AtEnd (sufflist, (ClientData)s); + /* + * Look for any existing transformations from or to this suffix. + * XXX: Only do this after a Suff_ClearSuffixes? + */ + Lst_ForEach (transforms, SuffRebuildGraph, (ClientData)s); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_GetPath -- + * Return the search path for the given suffix, if it's defined. + * + * Results: + * The searchPath for the desired suffix or NILLST if the suffix isn't + * defined. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Lst +Suff_GetPath (sname) + char *sname; +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln == NILLNODE) { + return (NILLST); + } else { + s = (Suff *) Lst_Datum (ln); + return (s->searchPath); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_DoPaths -- + * Extend the search paths for all suffixes to include the default + * search path. + * + * Results: + * None. + * + * Side Effects: + * The searchPath field of all the suffixes is extended by the + * directories in dirSearchPath. If paths were specified for the + * ".h" suffix, the directories are stuffed into a global variable + * called ".INCLUDES" with each directory preceeded by a -I. The same + * is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is -L. + *----------------------------------------------------------------------- + */ +void +Suff_DoPaths() +{ + register Suff *s; + register LstNode ln; + Lst inIncludes; /* Cumulative .INCLUDES path */ + Lst inLibs; /* Cumulative .LIBS path */ + + if (Lst_Open (sufflist) == FAILURE) { + return; + } + + inIncludes = Lst_Init(FALSE); + inLibs = Lst_Init(FALSE); + + while ((ln = Lst_Next (sufflist)) != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + if (!Lst_IsEmpty (s->searchPath)) { +#ifdef INCLUDES + if (s->flags & SUFF_INCLUDE) { + Dir_Concat(inIncludes, s->searchPath); + } +#endif /* INCLUDES */ +#ifdef LIBRARIES + if (s->flags & SUFF_LIBRARY) { + Dir_Concat(inLibs, s->searchPath); + } +#endif /* LIBRARIES */ + Dir_Concat(s->searchPath, dirSearchPath); + } else { + Lst_Destroy (s->searchPath, Dir_Destroy); + s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); + } + } + + Var_Set(".INCLUDES", Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); + Var_Set(".LIBS", Dir_MakeFlags("-L", inLibs), VAR_GLOBAL); + + Lst_Destroy(inIncludes, Dir_Destroy); + Lst_Destroy(inLibs, Dir_Destroy); + + Lst_Close (sufflist); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddInclude -- + * Add the given suffix as a type of file which gets included. + * Called from the parse module when a .INCLUDES line is parsed. + * The suffix must have already been defined. + * + * Results: + * None. + * + * Side Effects: + * The SUFF_INCLUDE bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddInclude (sname) + char *sname; /* Name of suffix to mark */ +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + s->flags |= SUFF_INCLUDE; + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddLib -- + * Add the given suffix as a type of file which is a library. + * Called from the parse module when parsing a .LIBS line. The + * suffix must have been defined via .SUFFIXES before this is + * called. + * + * Results: + * None. + * + * Side Effects: + * The SUFF_LIBRARY bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddLib (sname) + char *sname; /* Name of suffix to mark */ +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + s->flags |= SUFF_LIBRARY; + } +} + + /********** Implicit Source Search Functions *********/ + +/*- + *----------------------------------------------------------------------- + * SuffAddSrc -- + * Add a suffix as a Src structure to the given list with its parent + * being the given Src structure. If the suffix is the null suffix, + * the prefix is used unaltered as the file name in the Src structure. + * + * Results: + * always returns 0 + * + * Side Effects: + * A Src structure is created and tacked onto the end of the list + *----------------------------------------------------------------------- + */ +static int +SuffAddSrc (s, ls) + Suff *s; /* suffix for which to create a Src structure */ + LstSrc *ls; /* list and parent for the new Src */ +{ + Src *s2; /* new Src structure */ + Src *targ; /* Target structure */ + + targ = ls->s; + + if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { + /* + * If the suffix has been marked as the NULL suffix, also create a Src + * structure for a file with no suffix attached. Two birds, and all + * that... + */ + s2 = (Src *) emalloc (sizeof (Src)); + s2->file = strdup(targ->pref); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NILGNODE; + s2->suff = s; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd (ls->l, (ClientData)s2); + } + s2 = (Src *) emalloc (sizeof (Src)); + s2->file = str_concat (targ->pref, s->name, 0); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NILGNODE; + s2->suff = s; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd (ls->l, (ClientData)s2); + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffAddLevel -- + * Add all the children of targ as Src structures to the given list + * + * Results: + * None + * + * Side Effects: + * Lots of structures are created and added to the list + *----------------------------------------------------------------------- + */ +static void +SuffAddLevel (l, targ) + Lst l; /* list to which to add the new level */ + Src *targ; /* Src structure to use as the parent */ +{ + LstSrc ls; + + ls.s = targ; + ls.l = l; + + Lst_ForEach (targ->suff->children, SuffAddSrc, (ClientData)&ls); +} + +/*- + *---------------------------------------------------------------------- + * SuffFreeSrc -- + * Free all memory associated with a Src structure + * + * Results: + * None + * + * Side Effects: + * The memory is free'd. + *---------------------------------------------------------------------- + */ +static void +SuffFreeSrc (s) + Src *s; +{ + free ((Address)s->file); + if (!s->parent) { + free((Address)s->pref); + } else if (--s->parent->children == 0 && s->parent->parent) { + /* + * Parent has no more children, now we're gone, and it's not + * at the top of the tree, so blow it away too. + */ + SuffFreeSrc(s->parent); + } + free ((Address)s); +} + +/*- + *----------------------------------------------------------------------- + * SuffFindThem -- + * Find the first existing file/target in the list srcs + * + * Results: + * The lowest structure in the chain of transformations + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Src * +SuffFindThem (srcs) + Lst srcs; /* list of Src structures to search through */ +{ + Src *s; /* current Src */ + Src *rs; /* returned Src */ + + rs = (Src *) NULL; + + while (!Lst_IsEmpty (srcs)) { + s = (Src *) Lst_DeQueue (srcs); + + if (DEBUG(SUFF)) { + printf ("\ttrying %s...", s->file); + } + /* + * A file is considered to exist if either a node exists in the + * graph for it or the file actually exists. + */ + if ((Targ_FindNode(s->file, TARG_NOCREATE) != NILGNODE) || + (Dir_FindFile (s->file, s->suff->searchPath) != (char *) NULL)) + { + if (DEBUG(SUFF)) { + printf ("got it\n"); + } + rs = s; + break; + } else { + if (DEBUG(SUFF)) { + printf ("not there\n"); + } + SuffAddLevel (srcs, s); + } + } + return (rs); +} + +/*- + *----------------------------------------------------------------------- + * SuffFindCmds -- + * See if any of the children of the target in the Src structure is + * one from which the target can be transformed. If there is one, + * a Src structure is put together for it and returned. + * + * Results: + * The Src structure of the "winning" child, or NIL if no such beast. + * + * Side Effects: + * A Src structure may be allocated. + * + *----------------------------------------------------------------------- + */ +static Src * +SuffFindCmds (targ) + Src *targ; /* Src structure to play with */ +{ + LstNode ln; /* General-purpose list node */ + register GNode *t, /* Target GNode */ + *s; /* Source GNode */ + int prefLen;/* The length of the defined prefix */ + Suff *suff; /* Suffix on matching beastie */ + Src *ret; /* Return value */ + char *cp; + + t = targ->node; + (void) Lst_Open (t->children); + prefLen = strlen (targ->pref); + + while ((ln = Lst_Next (t->children)) != NILLNODE) { + s = (GNode *)Lst_Datum (ln); + + cp = strrchr (s->name, '/'); + if (cp == (char *)NULL) { + cp = s->name; + } else { + cp++; + } + if (strncmp (cp, targ->pref, prefLen) == 0) { + /* + * The node matches the prefix ok, see if it has a known + * suffix. + */ + ln = Lst_Find (sufflist, (ClientData)&cp[prefLen], + SuffSuffHasNameP); + if (ln != NILLNODE) { + /* + * It even has a known suffix, see if there's a transformation + * defined between the node's suffix and the target's suffix. + * + * XXX: Handle multi-stage transformations here, too. + */ + suff = (Suff *)Lst_Datum (ln); + + if (Lst_Member (suff->parents, + (ClientData)targ->suff) != NILLNODE) + { + /* + * Hot Damn! Create a new Src structure to describe + * this transformation (making sure to duplicate the + * source node's name so Suff_FindDeps can free it + * again (ick)), and return the new structure. + */ + ret = (Src *)emalloc (sizeof(Src)); + ret->file = strdup(s->name); + ret->pref = targ->pref; + ret->suff = suff; + ret->parent = targ; + ret->node = s; + ret->children = 0; + targ->children += 1; + if (DEBUG(SUFF)) { + printf ("\tusing existing source %s\n", s->name); + } + return (ret); + } + } + } + } + Lst_Close (t->children); + return ((Src *)NULL); +} + +/*- + *----------------------------------------------------------------------- + * SuffExpandChildren -- + * Expand the names of any children of a given node that contain + * variable invocations or file wildcards into actual targets. + * + * Results: + * === 0 (continue) + * + * Side Effects: + * The expanded node is removed from the parent's list of children, + * and the parent's unmade counter is decremented, but other nodes + * may be added. + * + *----------------------------------------------------------------------- + */ +static int +SuffExpandChildren(cgn, pgn) + GNode *cgn; /* Child to examine */ + GNode *pgn; /* Parent node being processed */ +{ + GNode *gn; /* New source 8) */ + LstNode prevLN; /* Node after which new source should be put */ + LstNode ln; /* List element for old source */ + char *cp; /* Expanded value */ + + /* + * New nodes effectively take the place of the child, so place them + * after the child + */ + prevLN = Lst_Member(pgn->children, (ClientData)cgn); + + /* + * First do variable expansion -- this takes precedence over + * wildcard expansion. If the result contains wildcards, they'll be gotten + * to later since the resulting words are tacked on to the end of + * the children list. + */ + if (strchr(cgn->name, '$') != (char *)NULL) { + if (DEBUG(SUFF)) { + printf("Expanding \"%s\"...", cgn->name); + } + cp = Var_Subst(NULL, cgn->name, pgn, TRUE); + + if (cp != (char *)NULL) { + Lst members = Lst_Init(FALSE); + + if (cgn->type & OP_ARCHV) { + /* + * Node was an archive(member) target, so we want to call + * on the Arch module to find the nodes for us, expanding + * variables in the parent's context. + */ + char *sacrifice = cp; + + (void)Arch_ParseArchive(&sacrifice, members, pgn); + } else { + /* + * Break the result into a vector of strings whose nodes + * we can find, then add those nodes to the members list. + * Unfortunately, we can't use brk_string b/c it + * doesn't understand about variable specifications with + * spaces in them... + */ + char *start; + char *initcp = cp; /* For freeing... */ + + for (start = cp; *start == ' ' || *start == '\t'; start++) + continue; + for (cp = start; *cp != '\0'; cp++) { + if (*cp == ' ' || *cp == '\t') { + /* + * White-space -- terminate element, find the node, + * add it, skip any further spaces. + */ + *cp++ = '\0'; + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, (ClientData)gn); + while (*cp == ' ' || *cp == '\t') { + cp++; + } + /* + * Adjust cp for increment at start of loop, but + * set start to first non-space. + */ + start = cp--; + } else if (*cp == '$') { + /* + * Start of a variable spec -- contact variable module + * to find the end so we can skip over it. + */ + char *junk; + int len; + Boolean doFree; + + junk = Var_Parse(cp, pgn, TRUE, &len, &doFree); + if (junk != var_Error) { + cp += len - 1; + } + + if (doFree) { + free(junk); + } + } else if (*cp == '\\' && *cp != '\0') { + /* + * Escaped something -- skip over it + */ + cp++; + } + } + + if (cp != start) { + /* + * Stuff left over -- add it to the list too + */ + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, (ClientData)gn); + } + /* + * Point cp back at the beginning again so the variable value + * can be freed. + */ + cp = initcp; + } + /* + * Add all elements of the members list to the parent node. + */ + while(!Lst_IsEmpty(members)) { + gn = (GNode *)Lst_DeQueue(members); + + if (DEBUG(SUFF)) { + printf("%s...", gn->name); + } + if (Lst_Member(pgn->children, (ClientData)gn) == NILLNODE) { + (void)Lst_Append(pgn->children, prevLN, (ClientData)gn); + prevLN = Lst_Succ(prevLN); + (void)Lst_AtEnd(gn->parents, (ClientData)pgn); + pgn->unmade++; + } + } + Lst_Destroy(members, NOFREE); + /* + * Free the result + */ + free((char *)cp); + } + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + ln = Lst_Member(pgn->children, (ClientData)cgn); + pgn->unmade--; + Lst_Remove(pgn->children, ln); + if (DEBUG(SUFF)) { + printf("\n"); + } + } else if (Dir_HasWildcards(cgn->name)) { + Lst exp; /* List of expansions */ + Lst path; /* Search path along which to expand */ + + /* + * Find a path along which to expand the word. + * + * If the word has a known suffix, use that path. + * If it has no known suffix and we're allowed to use the null + * suffix, use its path. + * Else use the default system search path. + */ + cp = cgn->name + strlen(cgn->name); + ln = Lst_Find(sufflist, (ClientData)cp, SuffSuffIsSuffixP); + + if (DEBUG(SUFF)) { + printf("Wildcard expanding \"%s\"...", cgn->name); + } + + if (ln != NILLNODE) { + Suff *s = (Suff *)Lst_Datum(ln); + + if (DEBUG(SUFF)) { + printf("suffix is \"%s\"...", s->name); + } + path = s->searchPath; + } else { + /* + * Use default search path + */ + path = dirSearchPath; + } + + /* + * Expand the word along the chosen path + */ + exp = Lst_Init(FALSE); + Dir_Expand(cgn->name, path, exp); + + while (!Lst_IsEmpty(exp)) { + /* + * Fetch next expansion off the list and find its GNode + */ + cp = (char *)Lst_DeQueue(exp); + + if (DEBUG(SUFF)) { + printf("%s...", cp); + } + gn = Targ_FindNode(cp, TARG_CREATE); + + /* + * If gn isn't already a child of the parent, make it so and + * up the parent's count of unmade children. + */ + if (Lst_Member(pgn->children, (ClientData)gn) == NILLNODE) { + (void)Lst_Append(pgn->children, prevLN, (ClientData)gn); + prevLN = Lst_Succ(prevLN); + (void)Lst_AtEnd(gn->parents, (ClientData)pgn); + pgn->unmade++; + } + } + + /* + * Nuke what's left of the list + */ + Lst_Destroy(exp, NOFREE); + + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + ln = Lst_Member(pgn->children, (ClientData)cgn); + pgn->unmade--; + Lst_Remove(pgn->children, ln); + if (DEBUG(SUFF)) { + printf("\n"); + } + } + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffApplyTransform -- + * Apply a transformation rule, given the source and target nodes + * and suffixes. + * + * Results: + * TRUE if successful, FALSE if not. + * + * Side Effects: + * The source and target are linked and the commands from the + * transformation are added to the target node's commands list. + * All attributes but OP_DEPMASK and OP_TRANSFORM are applied + * to the target. The target also inherits all the sources for + * the transformation rule. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffApplyTransform(tGn, sGn, t, s) + GNode *tGn; /* Target node */ + GNode *sGn; /* Source node */ + Suff *t; /* Target suffix */ + Suff *s; /* Source suffix */ +{ + LstNode ln; /* General node */ + char *tname; /* Name of transformation rule */ + GNode *gn; /* Node for same */ + + if (Lst_Member(tGn->children, (ClientData)sGn) == NILLNODE) { + /* + * Not already linked, so form the proper links between the + * target and source. + */ + (void)Lst_AtEnd(tGn->children, (ClientData)sGn); + (void)Lst_AtEnd(sGn->parents, (ClientData)tGn); + tGn->unmade += 1; + } + + if ((sGn->type & OP_OPMASK) == OP_DOUBLEDEP) { + /* + * When a :: node is used as the implied source of a node, we have + * to link all its cohorts in as sources as well. Only the initial + * sGn gets the target in its iParents list, however, as that + * will be sufficient to get the .IMPSRC variable set for tGn + */ + for (ln=Lst_First(sGn->cohorts); ln != NILLNODE; ln=Lst_Succ(ln)) { + gn = (GNode *)Lst_Datum(ln); + + if (Lst_Member(tGn->children, (ClientData)gn) == NILLNODE) { + /* + * Not already linked, so form the proper links between the + * target and source. + */ + (void)Lst_AtEnd(tGn->children, (ClientData)gn); + (void)Lst_AtEnd(gn->parents, (ClientData)tGn); + tGn->unmade += 1; + } + } + } + /* + * Locate the transformation rule itself + */ + tname = str_concat(s->name, t->name, 0); + ln = Lst_Find(transforms, (ClientData)tname, SuffGNHasNameP); + free(tname); + + if (ln == NILLNODE) { + /* + * Not really such a transformation rule (can happen when we're + * called to link an OP_MEMBER and OP_ARCHV node), so return + * FALSE. + */ + return(FALSE); + } + + gn = (GNode *)Lst_Datum(ln); + + if (DEBUG(SUFF)) { + printf("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); + } + + /* + * Record last child for expansion purposes + */ + ln = Lst_Last(tGn->children); + + /* + * Pass the buck to Make_HandleUse to apply the rule + */ + (void)Make_HandleUse(gn, tGn); + + /* + * Deal with wildcards and variables in any acquired sources + */ + ln = Lst_Succ(ln); + if (ln != NILLNODE) { + Lst_ForEachFrom(tGn->children, ln, + SuffExpandChildren, (ClientData)tGn); + } + + /* + * Keep track of another parent to which this beast is transformed so + * the .IMPSRC variable can be set correctly for the parent. + */ + (void)Lst_AtEnd(sGn->iParents, (ClientData)tGn); + + return(TRUE); +} + + +/*- + *----------------------------------------------------------------------- + * SuffFindArchiveDeps -- + * Locate dependencies for an OP_ARCHV node. + * + * Results: + * None + * + * Side Effects: + * Same as Suff_FindDeps + * + *----------------------------------------------------------------------- + */ +static void +SuffFindArchiveDeps(gn) + GNode *gn; /* Node for which to locate dependencies */ +{ + char *eoarch; /* End of archive portion */ + char *eoname; /* End of member portion */ + GNode *mem; /* Node for member */ + static char *copy[] = { /* Variables to be copied from the member node */ + TARGET, /* Must be first */ + PREFIX, /* Must be second */ + }; + char *vals[sizeof(copy)/sizeof(copy[0])]; + int i; /* Index into copy and vals */ + Suff *ms; /* Suffix descriptor for member */ + char *name; /* Start of member's name */ + + /* + * The node is an archive(member) pair. so we must find a + * suffix for both of them. + */ + eoarch = strchr (gn->name, '('); + eoname = strchr (eoarch, ')'); + + *eoname = '\0'; /* Nuke parentheses during suffix search */ + *eoarch = '\0'; /* So a suffix can be found */ + + name = eoarch + 1; + + /* + * To simplify things, call Suff_FindDeps recursively on the member now, + * so we can simply compare the member's .PREFIX and .TARGET variables + * to locate its suffix. This allows us to figure out the suffix to + * use for the archive without having to do a quadratic search over the + * suffix list, backtracking for each one... + */ + mem = Targ_FindNode(name, TARG_CREATE); + Suff_FindDeps(mem); + + /* + * Create the link between the two nodes right off + */ + if (Lst_Member(gn->children, (ClientData)mem) == NILLNODE) { + (void)Lst_AtEnd(gn->children, (ClientData)mem); + (void)Lst_AtEnd(mem->parents, (ClientData)gn); + gn->unmade += 1; + } + + /* + * Copy in the variables from the member node to this one. + */ + for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) { + vals[i] = Var_Value(copy[i], mem); + Var_Set(copy[i], vals[i], gn); + } + + ms = mem->suffix; + if (ms == NULL) { + /* + * Didn't know what it was -- use .NULL suffix if not in make mode + */ + if (DEBUG(SUFF)) { + printf("using null suffix\n"); + } + ms = suffNull; + } + + + /* + * Set the other two local variables required for this target. + */ + Var_Set (MEMBER, name, gn); + Var_Set (ARCHIVE, gn->name, gn); + + if (ms != NULL) { + /* + * Member has a known suffix, so look for a transformation rule from + * it to a possible suffix of the archive. Rather than searching + * through the entire list, we just look at suffixes to which the + * member's suffix may be transformed... + */ + LstNode ln; + + /* + * Use first matching suffix... + */ + ln = Lst_Find(ms->parents, eoarch, SuffSuffIsSuffixP); + + if (ln != NILLNODE) { + /* + * Got one -- apply it + */ + if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) && + DEBUG(SUFF)) + { + printf("\tNo transformation from %s -> %s\n", + ms->name, ((Suff *)Lst_Datum(ln))->name); + } + } + } + + /* + * Replace the opening and closing parens now we've no need of the separate + * pieces. + */ + *eoarch = '('; *eoname = ')'; + + /* + * Pretend gn appeared to the left of a dependency operator so + * the user needn't provide a transformation from the member to the + * archive. + */ + if (OP_NOP(gn->type)) { + gn->type |= OP_DEPENDS; + } + + /* + * Flag the member as such so we remember to look in the archive for + * its modification time. + */ + mem->type |= OP_MEMBER; +} + +/*- + *----------------------------------------------------------------------- + * SuffFindNormalDeps -- + * Locate implicit dependencies for regular targets. + * + * Results: + * None. + * + * Side Effects: + * Same as Suff_FindDeps... + * + *----------------------------------------------------------------------- + */ +static void +SuffFindNormalDeps(gn) + GNode *gn; /* Node for which to find sources */ +{ + char *eoname; /* End of name */ + char *sopref; /* Start of prefix */ + LstNode ln; /* Next suffix node to check */ + Lst srcs; /* List of sources at which to look */ + Lst targs; /* List of targets to which things can be + * transformed. They all have the same file, + * but different suff and pref fields */ + Src *bottom; /* Start of found transformation path */ + Src *src; /* General Src pointer */ + char *pref; /* Prefix to use */ + Src *targ; /* General Src target pointer */ + + + eoname = gn->name + strlen(gn->name); + + sopref = gn->name; + + /* + * Begin at the beginning... + */ + ln = Lst_First(sufflist); + srcs = Lst_Init(FALSE); + targs = Lst_Init(FALSE); + + /* + * We're caught in a catch-22 here. On the one hand, we want to use any + * transformation implied by the target's sources, but we can't examine + * the sources until we've expanded any variables/wildcards they may hold, + * and we can't do that until we've set up the target's local variables + * and we can't do that until we know what the proper suffix for the + * target is (in case there are two suffixes one of which is a suffix of + * the other) and we can't know that until we've found its implied + * source, which we may not want to use if there's an existing source + * that implies a different transformation. + * + * In an attempt to get around this, which may not work all the time, + * but should work most of the time, we look for implied sources first, + * checking transformations to all possible suffixes of the target, + * use what we find to set the target's local variables, expand the + * children, then look for any overriding transformations they imply. + * Should we find one, we discard the one we found before. + */ + while(ln != NILLNODE) { + /* + * Look for next possible suffix... + */ + ln = Lst_FindFrom(sufflist, ln, eoname, SuffSuffIsSuffixP); + + if (ln != NILLNODE) { + int prefLen; /* Length of the prefix */ + Src *targ; + + /* + * Allocate a Src structure to which things can be transformed + */ + targ = (Src *)emalloc(sizeof(Src)); + targ->file = strdup(gn->name); + targ->suff = (Suff *)Lst_Datum(ln); + targ->node = gn; + targ->parent = (Src *)NULL; + targ->children = 0; + + /* + * Allocate room for the prefix, whose end is found by subtracting + * the length of the suffix from the end of the name. + */ + prefLen = (eoname - targ->suff->nameLen) - sopref; + targ->pref = emalloc(prefLen + 1); + memcpy(targ->pref, sopref, prefLen); + targ->pref[prefLen] = '\0'; + + /* + * Add nodes from which the target can be made + */ + SuffAddLevel(srcs, targ); + + /* + * Record the target so we can nuke it + */ + (void)Lst_AtEnd(targs, (ClientData)targ); + + /* + * Search from this suffix's successor... + */ + ln = Lst_Succ(ln); + } + } + + /* + * Handle target of unknown suffix... + */ + if (Lst_IsEmpty(targs) && suffNull != NULL) { + if (DEBUG(SUFF)) { + printf("\tNo known suffix on %s. Using .NULL suffix\n", gn->name); + } + + targ = (Src *)emalloc(sizeof(Src)); + targ->file = strdup(gn->name); + targ->suff = suffNull; + targ->node = gn; + targ->parent = (Src *)NULL; + targ->children = 0; + targ->pref = strdup(sopref); + + SuffAddLevel(srcs, targ); + (void)Lst_AtEnd(targs, (ClientData)targ); + } + + /* + * Using the list of possible sources built up from the target suffix(es), + * try and find an existing file/target that matches. + */ + bottom = SuffFindThem(srcs); + + if (bottom == (Src *)NULL) { + /* + * No known transformations -- use the first suffix found for setting + * the local variables. + */ + if (!Lst_IsEmpty(targs)) { + targ = (Src *)Lst_Datum(Lst_First(targs)); + } else { + targ = (Src *)NULL; + } + } else { + /* + * Work up the transformation path to find the suffix of the + * target to which the transformation was made. + */ + for (targ = bottom; targ->parent != NULL; targ = targ->parent) + continue; + } + + /* + * The .TARGET variable we always set to be the name at this point, + * since it's only set to the path if the thing is only a source and + * if it's only a source, it doesn't matter what we put here as far + * as expanding sources is concerned, since it has none... + */ + Var_Set(TARGET, gn->name, gn); + + pref = (targ != NULL) ? targ->pref : gn->name; + Var_Set(PREFIX, pref, gn); + + /* + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. + */ + Lst_ForEach(gn->children, SuffExpandChildren, (ClientData)gn); + + if (targ == NULL) { + if (DEBUG(SUFF)) { + printf("\tNo valid suffix on %s\n", gn->name); + } + +sfnd_abort: + /* + * Deal with finding the thing on the default search path if the + * node is only a source (not on the lhs of a dependency operator + * or [XXX] it has neither children or commands). + */ + if (OP_NOP(gn->type) || + (Lst_IsEmpty(gn->children) && Lst_IsEmpty(gn->commands))) + { + gn->path = Dir_FindFile(gn->name, + (targ == NULL ? dirSearchPath : + targ->suff->searchPath)); + if (gn->path != NULL) { + Var_Set(TARGET, gn->path, gn); + + if (targ != NULL) { + /* + * Suffix known for the thing -- trim the suffix off + * the path to form the proper .PREFIX variable. + */ + int len = strlen(gn->path); + char savec; + + gn->suffix = targ->suff; + + savec = gn->path[len-targ->suff->nameLen]; + gn->path[len-targ->suff->nameLen] = '\0'; + + Var_Set(PREFIX, gn->path, gn); + + gn->path[len-targ->suff->nameLen] = savec; + } else { + /* + * The .PREFIX gets the full path if the target has + * no known suffix. + */ + gn->suffix = NULL; + + Var_Set(PREFIX, gn->path, gn); + } + } + } else { + /* + * Not appropriate to search for the thing -- set the + * path to be the name so Dir_MTime won't go grovelling for + * it. + */ + gn->suffix = (targ == NULL) ? NULL : targ->suff; + gn->path = gn->name; + } + + goto sfnd_return; + } + + /* + * If the suffix indicates that the target is a library, mark that in + * the node's type field. + */ + if (targ->suff->flags & SUFF_LIBRARY) { + gn->type |= OP_LIB; + } + + /* + * Check for overriding transformation rule implied by sources + */ + if (!Lst_IsEmpty(gn->children)) { + src = SuffFindCmds(targ); + + if (src != (Src *)NULL) { + /* + * Free up all the Src structures in the transformation path + * up to, but not including, the parent node. + */ + while (bottom && bottom->parent != NULL) { + Src *p = bottom->parent; + + SuffFreeSrc(bottom); + bottom = p; + } + bottom = src; + } + } + + if (bottom == NULL) { + /* + * No idea from where it can come -- return now. + */ + goto sfnd_abort; + } + + /* + * We now have a list of Src structures headed by 'bottom' and linked via + * their 'parent' pointers. What we do next is create links between + * source and target nodes (which may or may not have been created) + * and set the necessary local variables in each target. The + * commands for each target are set from the commands of the + * transformation rule used to get from the src suffix to the targ + * suffix. Note that this causes the commands list of the original + * node, gn, to be replaced by the commands of the final + * transformation rule. Also, the unmade field of gn is incremented. + * Etc. + */ + if (bottom->node == NILGNODE) { + bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); + } + + for (src = bottom; src->parent != (Src *)NULL; src = src->parent) { + targ = src->parent; + + src->node->suffix = src->suff; + + if (targ->node == NILGNODE) { + targ->node = Targ_FindNode(targ->file, TARG_CREATE); + } + + SuffApplyTransform(targ->node, src->node, + targ->suff, src->suff); + + if (targ->node != gn) { + /* + * Finish off the dependency-search process for any nodes + * between bottom and gn (no point in questing around the + * filesystem for their implicit source when it's already + * known). Note that the node can't have any sources that + * need expanding, since SuffFindThem will stop on an existing + * node, so all we need to do is set the standard and System V + * variables. + */ + targ->node->type |= OP_DEPS_FOUND; + + Var_Set(PREFIX, targ->pref, targ->node); + + Var_Set(TARGET, targ->node->name, targ->node); + } + } + + gn->suffix = src->suff; + + /* + * So Dir_MTime doesn't go questing for it... + */ + gn->path = gn->name; + + /* + * Nuke the transformation path and the Src structures left over in the + * two lists. + */ + SuffFreeSrc(bottom); + +sfnd_return: + Lst_Destroy(srcs, SuffFreeSrc); + Lst_Destroy(targs, SuffFreeSrc); + +} + + + + +/*- + *----------------------------------------------------------------------- + * Suff_FindDeps -- + * Find implicit sources for the target described by the graph node + * gn + * + * Results: + * Nothing. + * + * Side Effects: + * Nodes are added to the graph below the passed-in node. The nodes + * are marked to have their IMPSRC variable filled in. The + * PREFIX variable is set for the given node and all its + * implied children. + * + * Notes: + * The path found by this target is the shortest path in the + * transformation graph, which may pass through non-existent targets, + * to an existing target. The search continues on all paths from the + * root suffix until a file is found. I.e. if there's a path + * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but + * the .c and .l files don't, the search will branch out in + * all directions from .o and again from all the nodes on the + * next level until the .l,v node is encountered. + * + *----------------------------------------------------------------------- + */ +void +Suff_FindDeps (gn) + GNode *gn; /* node we're dealing with */ +{ + if (gn->type & OP_DEPS_FOUND) { + /* + * If dependencies already found, no need to do it again... + */ + return; + } else { + gn->type |= OP_DEPS_FOUND; + } + + if (DEBUG(SUFF)) { + printf ("Suff_FindDeps (%s)\n", gn->name); + } + + if (gn->type & OP_ARCHV) { + SuffFindArchiveDeps(gn); + } else if (gn->type & OP_LIB) { + /* + * If the node is a library, it is the arch module's job to find it + * and set the TARGET variable accordingly. We merely provide the + * search path, assuming all libraries end in ".a" (if the suffix + * hasn't been defined, there's nothing we can do for it, so we just + * set the TARGET variable to the node's name in order to give it a + * value). + */ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)LIBSUFF, SuffSuffHasNameP); + if (ln != NILLNODE) { + gn->suffix = s = (Suff *) Lst_Datum (ln); + Arch_FindLib (gn, s->searchPath); + } else { + gn->suffix = NULL; + Var_Set (TARGET, gn->name, gn); + } + /* + * Because a library (-lfoo) target doesn't follow the standard + * filesystem conventions, we don't set the regular variables for + * the thing. .PREFIX is simply made empty... + */ + Var_Set(PREFIX, "", gn); + } else { + SuffFindNormalDeps(gn); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_SetNull -- + * Define which suffix is the null suffix. + * + * Results: + * None. + * + * Side Effects: + * 'suffNull' is altered. + * + * Notes: + * Need to handle the changing of the null suffix gracefully so the + * old transformation rules don't just go away. + * + *----------------------------------------------------------------------- + */ +void +Suff_SetNull(name) + char *name; /* Name of null suffix */ +{ + Suff *s; + LstNode ln; + + ln = Lst_Find(sufflist, (ClientData)name, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *)Lst_Datum(ln); + if (suffNull != (Suff *)NULL) { + suffNull->flags &= ~SUFF_NULL; + } + s->flags |= SUFF_NULL; + /* + * XXX: Here's where the transformation mangling would take place + */ + suffNull = s; + } else { + Parse_Error (PARSE_WARNING, "Desired null suffix %s not defined.", + name); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_Init -- + * Initialize suffixes module + * + * Results: + * None + * + * Side Effects: + * Many + *----------------------------------------------------------------------- + */ +void +Suff_Init () +{ + sufflist = Lst_Init (FALSE); + transforms = Lst_Init (FALSE); + + sNum = 0; + /* + * Create null suffix for single-suffix rules (POSIX). The thing doesn't + * actually go on the suffix list or everyone will think that's its + * suffix. + */ + emptySuff = suffNull = (Suff *) emalloc (sizeof (Suff)); + + suffNull->name = strdup (""); + suffNull->nameLen = 0; + suffNull->searchPath = Lst_Init (FALSE); + Dir_Concat(suffNull->searchPath, dirSearchPath); + suffNull->children = Lst_Init (FALSE); + suffNull->parents = Lst_Init (FALSE); + suffNull->sNum = sNum++; + suffNull->flags = SUFF_NULL; + +} + + +/*- + *----------------------------------------------------------------------- + * SuffCopy -- + * Create a copy of the source suffix. + * Currently does not copy children or parents + * + * Results: + * a new suffix is returned + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +static Suff * +SuffCopy(s) + Suff *s; +{ + Suff *n = (Suff *) emalloc (sizeof (Suff)); + n->name = strdup (s->name); + n->nameLen = s->nameLen; + n->searchPath = Lst_Init (FALSE); + Dir_Concat(suffNull->searchPath, s->searchPath); + n->children = Lst_Init (FALSE); + n->parents = Lst_Init (FALSE); + n->sNum = s->sNum; + n->flags = s->flags; + return n; +} + + +/********************* DEBUGGING FUNCTIONS **********************/ + +static int SuffPrintName(s) Suff *s; {printf ("%s ", s->name); return (0);} + +static int +SuffPrintSuff (s) + Suff *s; +{ + int flags; + int flag; + + printf ("# `%s'", s->name); + + flags = s->flags; + if (flags) { + fputs (" (", stdout); + while (flags) { + flag = 1 << (ffs(flags) - 1); + flags &= ~flag; + switch (flag) { + case SUFF_NULL: + printf ("NULL"); + break; + case SUFF_INCLUDE: + printf ("INCLUDE"); + break; + case SUFF_LIBRARY: + printf ("LIBRARY"); + break; + } + fputc(flags ? '|' : ')', stdout); + } + } + fputc ('\n', stdout); + printf ("#\tTo: "); + Lst_ForEach (s->parents, SuffPrintName, (ClientData)0); + fputc ('\n', stdout); + printf ("#\tFrom: "); + Lst_ForEach (s->children, SuffPrintName, (ClientData)0); + fputc ('\n', stdout); + printf ("#\tSearch Path: "); + Dir_PrintPath (s->searchPath); + fputc ('\n', stdout); + return (0); +} + +static int +SuffPrintTrans (t) + GNode *t; +{ + extern int Targ_PrintCmd(); + + printf ("%-16s: ", t->name); + Targ_PrintType (t->type); + fputc ('\n', stdout); + Lst_ForEach (t->commands, Targ_PrintCmd, (ClientData)0); + fputc ('\n', stdout); + return(0); +} + +void +Suff_PrintAll() +{ + printf ("#*** Suffixes:\n"); + Lst_ForEach (sufflist, SuffPrintSuff, (ClientData)0); + + printf ("#*** Transformations:\n"); + Lst_ForEach (transforms, SuffPrintTrans, (ClientData)0); +} diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c new file mode 100644 index 000000000000..7e1475a50473 --- /dev/null +++ b/usr.bin/make/targ.c @@ -0,0 +1,585 @@ +/* + * 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[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94"; +#endif /* not lint */ + +/*- + * targ.c -- + * Functions for maintaining the Lst allTargets. Target nodes are + * kept in two structures: a Lst, maintained by the list library, and a + * hash table, maintained by the hash library. + * + * Interface: + * Targ_Init Initialization procedure. + * + * Targ_NewGN Create a new GNode for the passed target + * (string). The node is *not* placed in the + * hash table, though all its fields are + * initialized. + * + * Targ_FindNode Find the node for a given target, creating + * and storing it if it doesn't exist and the + * flags are right (TARG_CREATE) + * + * Targ_FindList Given a list of names, find nodes for all + * of them. If a name doesn't exist and the + * TARG_NOCREATE flag was given, an error message + * is printed. Else, if a name doesn't exist, + * its node is created. + * + * Targ_Ignore Return TRUE if errors should be ignored when + * creating the given target. + * + * Targ_Silent Return TRUE if we should be silent when + * creating the given target. + * + * Targ_Precious Return TRUE if the target is precious and + * should not be removed if we are interrupted. + * + * Debugging: + * Targ_PrintGraph Print out the entire graphm all variables + * and statistics for the directory cache. Should + * print something for suffixes, too, but... + */ + +#include +#include +#include "make.h" +#include "hash.h" +#include "dir.h" + +static Lst allTargets; /* the list of all targets found so far */ +static Hash_Table targets; /* a hash table of same */ + +#define HTSIZE 191 /* initial size of hash table */ + +/*- + *----------------------------------------------------------------------- + * Targ_Init -- + * Initialize this module + * + * Results: + * None + * + * Side Effects: + * The allTargets list and the targets hash table are initialized + *----------------------------------------------------------------------- + */ +void +Targ_Init () +{ + allTargets = Lst_Init (FALSE); + Hash_InitTable (&targets, HTSIZE); +} + +/*- + *----------------------------------------------------------------------- + * Targ_NewGN -- + * Create and initialize a new graph node + * + * Results: + * An initialized graph node with the name field filled with a copy + * of the passed name + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +GNode * +Targ_NewGN (name) + char *name; /* the name to stick in the new node */ +{ + register GNode *gn; + + gn = (GNode *) emalloc (sizeof (GNode)); + gn->name = strdup (name); + gn->path = (char *) 0; + if (name[0] == '-' && name[1] == 'l') { + gn->type = OP_LIB; + } else { + gn->type = 0; + } + gn->unmade = 0; + gn->make = FALSE; + gn->made = UNMADE; + gn->childMade = FALSE; + gn->mtime = gn->cmtime = 0; + gn->iParents = Lst_Init (FALSE); + gn->cohorts = Lst_Init (FALSE); + gn->parents = Lst_Init (FALSE); + gn->children = Lst_Init (FALSE); + gn->successors = Lst_Init(FALSE); + gn->preds = Lst_Init(FALSE); + gn->context = Lst_Init (FALSE); + gn->commands = Lst_Init (FALSE); + gn->suffix = NULL; + + return (gn); +} + +/*- + *----------------------------------------------------------------------- + * Targ_FindNode -- + * Find a node in the list using the given name for matching + * + * Results: + * The node in the list if it was. If it wasn't, return NILGNODE of + * flags was TARG_NOCREATE or the newly created and initialized node + * if it was TARG_CREATE + * + * Side Effects: + * Sometimes a node is created and added to the list + *----------------------------------------------------------------------- + */ +GNode * +Targ_FindNode (name, flags) + char *name; /* the name to find */ + int flags; /* flags governing events when target not + * found */ +{ + GNode *gn; /* node in that element */ + Hash_Entry *he; /* New or used hash entry for node */ + Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ + /* an entry for the node */ + + + if (flags & TARG_CREATE) { + he = Hash_CreateEntry (&targets, name, &isNew); + if (isNew) { + gn = Targ_NewGN (name); + Hash_SetValue (he, gn); + (void) Lst_AtEnd (allTargets, (ClientData)gn); + } + } else { + he = Hash_FindEntry (&targets, name); + } + + if (he == (Hash_Entry *) NULL) { + return (NILGNODE); + } else { + return ((GNode *) Hash_GetValue (he)); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_FindList -- + * Make a complete list of GNodes from the given list of names + * + * Results: + * A complete list of graph nodes corresponding to all instances of all + * the names in names. + * + * Side Effects: + * If flags is TARG_CREATE, nodes will be created for all names in + * names which do not yet have graph nodes. If flags is TARG_NOCREATE, + * an error message will be printed for each name which can't be found. + * ----------------------------------------------------------------------- + */ +Lst +Targ_FindList (names, flags) + Lst names; /* list of names to find */ + int flags; /* flags used if no node is found for a given + * name */ +{ + Lst nodes; /* result list */ + register LstNode ln; /* name list element */ + register GNode *gn; /* node in tLn */ + char *name; + + nodes = Lst_Init (FALSE); + + if (Lst_Open (names) == FAILURE) { + return (nodes); + } + while ((ln = Lst_Next (names)) != NILLNODE) { + name = (char *)Lst_Datum(ln); + gn = Targ_FindNode (name, flags); + if (gn != NILGNODE) { + /* + * Note: Lst_AtEnd must come before the Lst_Concat so the nodes + * are added to the list in the order in which they were + * encountered in the makefile. + */ + (void) Lst_AtEnd (nodes, (ClientData)gn); + if (gn->type & OP_DOUBLEDEP) { + (void)Lst_Concat (nodes, gn->cohorts, LST_CONCNEW); + } + } else if (flags == TARG_NOCREATE) { + Error ("\"%s\" -- target unknown.", name); + } + } + Lst_Close (names); + return (nodes); +} + +/*- + *----------------------------------------------------------------------- + * Targ_Ignore -- + * Return true if should ignore errors when creating gn + * + * Results: + * TRUE if should ignore errors + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Ignore (gn) + GNode *gn; /* node to check for */ +{ + if (ignoreErrors || gn->type & OP_IGNORE) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Silent -- + * Return true if be silent when creating gn + * + * Results: + * TRUE if should be silent + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Silent (gn) + GNode *gn; /* node to check for */ +{ + if (beSilent || gn->type & OP_SILENT) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Precious -- + * See if the given target is precious + * + * Results: + * TRUE if it is precious. FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Precious (gn) + GNode *gn; /* the node to check */ +{ + if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) { + return (TRUE); + } else { + return (FALSE); + } +} + +/******************* DEBUG INFO PRINTING ****************/ + +static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ +/*- + *----------------------------------------------------------------------- + * Targ_SetMain -- + * Set our idea of the main target we'll be creating. Used for + * debugging output. + * + * Results: + * None. + * + * Side Effects: + * "mainTarg" is set to the main target's node. + *----------------------------------------------------------------------- + */ +void +Targ_SetMain (gn) + GNode *gn; /* The main target we'll create */ +{ + mainTarg = gn; +} + +static int +/*ARGSUSED*/ +TargPrintName (gn, ppath) + GNode *gn; + int ppath; +{ + printf ("%s ", gn->name); +#ifdef notdef + if (ppath) { + if (gn->path) { + printf ("[%s] ", gn->path); + } + if (gn == mainTarg) { + printf ("(MAIN NAME) "); + } + } +#endif /* notdef */ + return (0); +} + + +int +Targ_PrintCmd (cmd) + char *cmd; +{ + printf ("\t%s\n", cmd); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Targ_FmtTime -- + * Format a modification time in some reasonable way and return it. + * + * Results: + * The time reformatted. + * + * Side Effects: + * The time is placed in a static area, so it is overwritten + * with each call. + * + *----------------------------------------------------------------------- + */ +char * +Targ_FmtTime (time) + time_t time; +{ + struct tm *parts; + static char buf[40]; + static char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + parts = localtime(&time); + + sprintf (buf, "%d:%02d:%02d %s %d, 19%d", + parts->tm_hour, parts->tm_min, parts->tm_sec, + months[parts->tm_mon], parts->tm_mday, parts->tm_year); + return(buf); +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintType -- + * Print out a type field giving only those attributes the user can + * set. + * + * Results: + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +void +Targ_PrintType (type) + register int type; +{ + register int tbit; + +#ifdef __STDC__ +#define PRINTBIT(attr) case CONCAT(OP_,attr): printf("." #attr " "); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG)) printf("." #attr " "); break +#else +#define PRINTBIT(attr) case CONCAT(OP_,attr): printf(".attr "); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG)) printf(".attr "); break +#endif /* __STDC__ */ + + type &= ~OP_OPMASK; + + while (type) { + tbit = 1 << (ffs(type) - 1); + type &= ~tbit; + + switch(tbit) { + PRINTBIT(OPTIONAL); + PRINTBIT(USE); + PRINTBIT(EXEC); + PRINTBIT(IGNORE); + PRINTBIT(PRECIOUS); + PRINTBIT(SILENT); + PRINTBIT(MAKE); + PRINTBIT(JOIN); + PRINTBIT(INVISIBLE); + PRINTBIT(NOTMAIN); + PRINTDBIT(LIB); + /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ + case OP_MEMBER: if (DEBUG(TARG)) printf(".MEMBER "); break; + PRINTDBIT(ARCHV); + } + } +} + +/*- + *----------------------------------------------------------------------- + * TargPrintNode -- + * print the contents of a node + *----------------------------------------------------------------------- + */ +static int +TargPrintNode (gn, pass) + GNode *gn; + int pass; +{ + if (!OP_NOP(gn->type)) { + printf("#\n"); + if (gn == mainTarg) { + printf("# *** MAIN TARGET ***\n"); + } + if (pass == 2) { + if (gn->unmade) { + printf("# %d unmade children\n", gn->unmade); + } else { + printf("# No unmade children\n"); + } + if (! (gn->type & (OP_JOIN|OP_USE|OP_EXEC))) { + if (gn->mtime != 0) { + printf("# last modified %s: %s\n", + Targ_FmtTime(gn->mtime), + (gn->made == UNMADE ? "unmade" : + (gn->made == MADE ? "made" : + (gn->made == UPTODATE ? "up-to-date" : + "error when made")))); + } else if (gn->made != UNMADE) { + printf("# non-existent (maybe): %s\n", + (gn->made == MADE ? "made" : + (gn->made == UPTODATE ? "up-to-date" : + (gn->made == ERROR ? "error when made" : + "aborted")))); + } else { + printf("# unmade\n"); + } + } + if (!Lst_IsEmpty (gn->iParents)) { + printf("# implicit parents: "); + Lst_ForEach (gn->iParents, TargPrintName, (ClientData)0); + fputc ('\n', stdout); + } + } + if (!Lst_IsEmpty (gn->parents)) { + printf("# parents: "); + Lst_ForEach (gn->parents, TargPrintName, (ClientData)0); + fputc ('\n', stdout); + } + + printf("%-16s", gn->name); + switch (gn->type & OP_OPMASK) { + case OP_DEPENDS: + printf(": "); break; + case OP_FORCE: + printf("! "); break; + case OP_DOUBLEDEP: + printf(":: "); break; + } + Targ_PrintType (gn->type); + Lst_ForEach (gn->children, TargPrintName, (ClientData)0); + fputc ('\n', stdout); + Lst_ForEach (gn->commands, Targ_PrintCmd, (ClientData)0); + printf("\n\n"); + if (gn->type & OP_DOUBLEDEP) { + Lst_ForEach (gn->cohorts, TargPrintNode, (ClientData)pass); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * TargPrintOnlySrc -- + * Print only those targets that are just a source. + * + * Results: + * 0. + * + * Side Effects: + * The name of each file is printed preceeded by #\t + * + *----------------------------------------------------------------------- + */ +static int +TargPrintOnlySrc(gn) + GNode *gn; +{ + if (OP_NOP(gn->type)) { + printf("#\t%s [%s]\n", gn->name, + gn->path ? gn->path : gn->name); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintGraph -- + * print the entire graph. heh heh + * + * Results: + * none + * + * Side Effects: + * lots o' output + *----------------------------------------------------------------------- + */ +void +Targ_PrintGraph (pass) + int pass; /* Which pass this is. 1 => no processing + * 2 => processing done */ +{ + printf("#*** Input graph:\n"); + Lst_ForEach (allTargets, TargPrintNode, (ClientData)pass); + printf("\n\n"); + printf("#\n# Files that are only sources:\n"); + Lst_ForEach (allTargets, TargPrintOnlySrc); + printf("#*** Global Variables:\n"); + Var_Dump (VAR_GLOBAL); + printf("#*** Command-line Variables:\n"); + Var_Dump (VAR_CMD); + printf("\n"); + Dir_PrintDirectories(); + printf("\n"); + Suff_PrintAll(); +} diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c new file mode 100644 index 000000000000..fc50a0fcb74f --- /dev/null +++ b/usr.bin/make/var.c @@ -0,0 +1,1986 @@ +/* + * 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[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; +#endif /* not lint */ + +/*- + * var.c -- + * Variable-handling functions + * + * Interface: + * Var_Set Set the value of a variable in the given + * context. The variable is created if it doesn't + * yet exist. The value and variable name need not + * be preserved. + * + * Var_Append Append more characters to an existing variable + * in the given context. The variable needn't + * exist already -- it will be created if it doesn't. + * A space is placed between the old value and the + * new one. + * + * Var_Exists See if a variable exists. + * + * Var_Value Return the value of a variable in a context or + * NULL if the variable is undefined. + * + * Var_Subst Substitute named variable, or all variables if + * NULL in a string using + * the given context as the top-most one. If the + * third argument is non-zero, Parse_Error is + * called if any variables are undefined. + * + * Var_Parse Parse a variable expansion from a string and + * return the result and the number of characters + * consumed. + * + * Var_Delete Delete a variable in a context. + * + * Var_Init Initialize this module. + * + * Debugging: + * Var_Dump Print out all variables defined in the given + * context. + * + * XXX: There's a lot of duplication in these functions. + */ + +#include +#include "make.h" +#include "buf.h" + +/* + * This is a harmless return value for Var_Parse that can be used by Var_Subst + * to determine if there was an error in parsing -- easier than returning + * a flag, as things outside this module don't give a hoot. + */ +char var_Error[] = ""; + +/* + * Similar to var_Error, but returned when the 'err' flag for Var_Parse is + * set false. Why not just use a constant? Well, gcc likes to condense + * identical string instances... + */ +static char varNoError[] = ""; + +/* + * Internally, variables are contained in four different contexts. + * 1) the environment. They may not be changed. If an environment + * variable is appended-to, the result is placed in the global + * context. + * 2) the global context. Variables set in the Makefile are located in + * the global context. It is the penultimate context searched when + * substituting. + * 3) the command-line context. All variables set on the command line + * are placed in this context. They are UNALTERABLE once placed here. + * 4) the local context. Each target has associated with it a context + * list. On this list are located the structures describing such + * local variables as $(@) and $(*) + * The four contexts are searched in the reverse order from which they are + * listed. + */ +GNode *VAR_GLOBAL; /* variables from the makefile */ +GNode *VAR_CMD; /* variables defined on the command-line */ + +#define FIND_CMD 0x1 /* look in VAR_CMD when searching */ +#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ +#define FIND_ENV 0x4 /* look in the environment also */ + +typedef struct Var { + char *name; /* the variable's name */ + Buffer val; /* its value */ + int flags; /* miscellaneous status flags */ +#define VAR_IN_USE 1 /* Variable's value currently being used. + * Used to avoid recursion */ +#define VAR_FROM_ENV 2 /* Variable comes from the environment */ +#define VAR_JUNK 4 /* Variable is a junk variable that + * should be destroyed when done with + * it. Used by Var_Parse for undefined, + * modified variables */ +} Var; + +typedef struct { + char *lhs; /* String to match */ + int leftLen; /* Length of string */ + char *rhs; /* Replacement string (w/ &'s removed) */ + int rightLen; /* Length of replacement */ + int flags; +#define VAR_SUB_GLOBAL 1 /* Apply substitution globally */ +#define VAR_MATCH_START 2 /* Match at start of word */ +#define VAR_MATCH_END 4 /* Match at end of word */ +#define VAR_NO_SUB 8 /* Substitution is non-global and already done */ +} VarPattern; + +static int VarCmp __P((Var *, char *)); +static Var *VarFind __P((char *, GNode *, int)); +static void VarAdd __P((char *, char *, GNode *)); +static Boolean VarHead __P((char *, Boolean, Buffer)); +static Boolean VarTail __P((char *, Boolean, Buffer)); +static Boolean VarSuffix __P((char *, Boolean, Buffer)); +static Boolean VarRoot __P((char *, Boolean, Buffer)); +static Boolean VarMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSYSVMatch __P((char *, Boolean, Buffer, VarPattern *)); +static Boolean VarNoMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSubstitute __P((char *, Boolean, Buffer, VarPattern *)); +static char *VarModify __P((char *, Boolean (*modProc )(), ClientData)); +static int VarPrintVar __P((Var *)); + +/*- + *----------------------------------------------------------------------- + * VarCmp -- + * See if the given variable matches the named one. Called from + * Lst_Find when searching for a variable of a given name. + * + * Results: + * 0 if they match. non-zero otherwise. + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +static int +VarCmp (v, name) + Var *v; /* VAR structure to compare */ + char *name; /* name to look for */ +{ + return (strcmp (name, v->name)); +} + +/*- + *----------------------------------------------------------------------- + * VarFind -- + * Find the given variable in the given context and any other contexts + * indicated. + * + * Results: + * A pointer to the structure describing the desired variable or + * NIL if the variable does not exist. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Var * +VarFind (name, ctxt, flags) + char *name; /* name to find */ + GNode *ctxt; /* context in which to find it */ + int flags; /* FIND_GLOBAL set means to look in the + * VAR_GLOBAL context as well. + * FIND_CMD set means to look in the VAR_CMD + * context also. + * FIND_ENV set means to look in the + * environment */ +{ + LstNode var; + Var *v; + + /* + * If the variable name begins with a '.', it could very well be one of + * the local ones. We check the name against all the local variables + * and substitute the short version in for 'name' if it matches one of + * them. + */ + if (*name == '.' && isupper(name[1])) + switch (name[1]) { + case 'A': + if (!strcmp(name, ".ALLSRC")) + name = ALLSRC; + if (!strcmp(name, ".ARCHIVE")) + name = ARCHIVE; + break; + case 'I': + if (!strcmp(name, ".IMPSRC")) + name = IMPSRC; + break; + case 'M': + if (!strcmp(name, ".MEMBER")) + name = MEMBER; + break; + case 'O': + if (!strcmp(name, ".OODATE")) + name = OODATE; + break; + case 'P': + if (!strcmp(name, ".PREFIX")) + name = PREFIX; + break; + case 'T': + if (!strcmp(name, ".TARGET")) + name = TARGET; + break; + } + /* + * First look for the variable in the given context. If it's not there, + * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, + * depending on the FIND_* flags in 'flags' + */ + var = Lst_Find (ctxt->context, (ClientData)name, VarCmp); + + if ((var == NILLNODE) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { + var = Lst_Find (VAR_CMD->context, (ClientData)name, VarCmp); + } + if (!checkEnvFirst && (var == NILLNODE) && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + } + if ((var == NILLNODE) && (flags & FIND_ENV)) { + char *env; + + if ((env = getenv (name)) != NULL) { + /* + * If the variable is found in the environment, we only duplicate + * its value (since eVarVal was allocated on the stack). The name + * doesn't need duplication since it's always in the environment + */ + int len; + + v = (Var *) emalloc(sizeof(Var)); + v->name = name; + + len = strlen(env); + + v->val = Buf_Init(len); + Buf_AddBytes(v->val, len, (Byte *)env); + + v->flags = VAR_FROM_ENV; + return (v); + } else if (checkEnvFirst && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *)Lst_Datum(var)); + } + } else { + return((Var *)NIL); + } + } else if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *) Lst_Datum (var)); + } +} + +/*- + *----------------------------------------------------------------------- + * VarAdd -- + * Add a new variable of name name and value val to the given context + * + * Results: + * None + * + * Side Effects: + * The new variable is placed at the front of the given context + * The name and val arguments are duplicated so they may + * safely be freed. + *----------------------------------------------------------------------- + */ +static void +VarAdd (name, val, ctxt) + char *name; /* name of variable to add */ + char *val; /* value to set it to */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + int len; + + v = (Var *) emalloc (sizeof (Var)); + + v->name = strdup (name); + + len = val ? strlen(val) : 0; + v->val = Buf_Init(len+1); + Buf_AddBytes(v->val, len, (Byte *)val); + + v->flags = 0; + + (void) Lst_AtFront (ctxt->context, (ClientData)v); + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Delete -- + * Remove a variable from a context. + * + * Results: + * None. + * + * Side Effects: + * The Var structure is removed and freed. + * + *----------------------------------------------------------------------- + */ +void +Var_Delete(name, ctxt) + char *name; + GNode *ctxt; +{ + LstNode ln; + + if (DEBUG(VAR)) { + printf("%s:delete %s\n", ctxt->name, name); + } + ln = Lst_Find(ctxt->context, (ClientData)name, VarCmp); + if (ln != NILLNODE) { + register Var *v; + + v = (Var *)Lst_Datum(ln); + Lst_Remove(ctxt->context, ln); + Buf_Destroy(v->val, TRUE); + free(v->name); + free((char *)v); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Set -- + * Set the variable name to the value val in the given context. + * + * Results: + * None. + * + * Side Effects: + * If the variable doesn't yet exist, a new record is created for it. + * Else the old value is freed and the new one stuck in its place + * + * Notes: + * The variable is searched for only in its context before being + * created in that context. I.e. if the context is VAR_GLOBAL, + * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only + * VAR_CMD->context is searched. This is done to avoid the literally + * thousands of unnecessary strcmp's that used to be done to + * set, say, $(@) or $(<). + *----------------------------------------------------------------------- + */ +void +Var_Set (name, val, ctxt) + char *name; /* name of variable to set */ + char *val; /* value to give to the variable */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + + /* + * We only look for a variable in the given context since anything set + * here will override anything in a lower context, so there's not much + * point in searching them all just to save a bit of memory... + */ + v = VarFind (name, ctxt, 0); + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_Discard(v->val, Buf_Size(v->val)); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } + } + /* + * Any variables given on the command line are automatically exported + * to the environment (as per POSIX standard) + */ + if (ctxt == VAR_CMD) { + setenv(name, val, 1); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Append -- + * The variable of the given name has the given value appended to it in + * the given context. + * + * Results: + * None + * + * Side Effects: + * If the variable doesn't exist, it is created. Else the strings + * are concatenated (with a space in between). + * + * Notes: + * Only if the variable is being sought in the global context is the + * environment searched. + * XXX: Knows its calling circumstances in that if called with ctxt + * an actual target, it will only search that context since only + * a local variable could be being appended to. This is actually + * a big win and must be tolerated. + *----------------------------------------------------------------------- + */ +void +Var_Append (name, val, ctxt) + char *name; /* Name of variable to modify */ + char *val; /* String to append to it */ + GNode *ctxt; /* Context in which this should occur */ +{ + register Var *v; + + v = VarFind (name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); + + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_AddByte(v->val, (Byte)' '); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, + (char *) Buf_GetAll(v->val, (int *)NULL)); + } + + if (v->flags & VAR_FROM_ENV) { + /* + * If the original variable came from the environment, we + * have to install it in the global context (we could place + * it in the environment, but then we should provide a way to + * export other variables...) + */ + v->flags &= ~VAR_FROM_ENV; + Lst_AtFront(ctxt->context, (ClientData)v); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Exists -- + * See if the given variable exists. + * + * Results: + * TRUE if it does, FALSE if it doesn't + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Var_Exists(name, ctxt) + char *name; /* Variable to find */ + GNode *ctxt; /* Context in which to start search */ +{ + Var *v; + + v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); + + if (v == (Var *)NIL) { + return(FALSE); + } else if (v->flags & VAR_FROM_ENV) { + Buf_Destroy(v->val, TRUE); + free((char *)v); + } + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * Var_Value -- + * Return the value of the named variable in the given context + * + * Results: + * The value if the variable exists, NULL if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Var_Value (name, ctxt) + char *name; /* name to find */ + GNode *ctxt; /* context in which to search for it */ +{ + Var *v; + + v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v != (Var *) NIL) { + return ((char *)Buf_GetAll(v->val, (int *)NULL)); + } else { + return ((char *) NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * VarHead -- + * Remove the tail of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarHead (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* True if need to add a space to the buffer + * before sticking in the head */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + slash = strrchr (word, '/'); + if (slash != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *slash = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *slash = '/'; + return (TRUE); + } else { + /* + * If no directory part, give . (q.v. the POSIX standard) + */ + if (addSpace) { + Buf_AddBytes(buf, 2, (Byte *)" ."); + } else { + Buf_AddByte(buf, (Byte)'.'); + } + return(TRUE); + } +} + +/*- + *----------------------------------------------------------------------- + * VarTail -- + * Remove the head of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarTail (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to stick a space in the + * buffer before adding the tail */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + slash = strrchr (word, '/'); + if (slash != (char *)NULL) { + *slash++ = '\0'; + Buf_AddBytes (buf, strlen(slash), (Byte *)slash); + slash[-1] = '/'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarSuffix -- + * Place the suffix of the given word in the given buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The suffix from the word is placed in the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSuffix (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space before placing + * the suffix in the buffer */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + dot = strrchr (word, '.'); + if (dot != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *dot++ = '\0'; + Buf_AddBytes (buf, strlen (dot), (Byte *)dot); + dot[-1] = '.'; + return (TRUE); + } else { + return (addSpace); + } +} + +/*- + *----------------------------------------------------------------------- + * VarRoot -- + * Remove the suffix of the given word and place the result in the + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarRoot (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space to the buffer + * before placing the root in it */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + dot = strrchr (word, '.'); + if (dot != (char *)NULL) { + *dot = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *dot = '.'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the :M modifier. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarMatch (word, addSpace, buf, pattern) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + char *pattern; /* Pattern the word must match */ +{ + if (Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + return(addSpace); +} + + + +/*- + *----------------------------------------------------------------------- + * VarSYSVMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the System V % + * modifiers. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSYSVMatch (word, addSpace, buf, pat) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + VarPattern *pat; /* Pattern the word must match */ +{ + int len; + char *ptr; + + if (addSpace) + Buf_AddByte(buf, (Byte)' '); + + addSpace = TRUE; + + if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) + Str_SYSVSubst(buf, pat->rhs, ptr, len); + else + Buf_AddBytes(buf, strlen(word), (Byte *) word); + + return(addSpace); +} + + +/*- + *----------------------------------------------------------------------- + * VarNoMatch -- + * Place the word in the buffer if it doesn't match the given pattern. + * Callback function for VarModify to implement the :N modifier. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarNoMatch (word, addSpace, buf, pattern) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + char *pattern; /* Pattern the word must match */ +{ + if (!Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + return(addSpace); +} + + +/*- + *----------------------------------------------------------------------- + * VarSubstitute -- + * Perform a string-substitution on the given word, placing the + * result in the passed buffer. + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSubstitute (word, addSpace, buf, pattern) + char *word; /* Word to modify */ + Boolean addSpace; /* True if space should be added before + * other characters */ + Buffer buf; /* Buffer for result */ + register VarPattern *pattern; /* Pattern for substitution */ +{ + register int wordLen; /* Length of word */ + register char *cp; /* General pointer */ + + wordLen = strlen(word); + if ((pattern->flags & VAR_NO_SUB) == 0) { + /* + * Still substituting -- break it down into simple anchored cases + * and if none of them fits, perform the general substitution case. + */ + if ((pattern->flags & VAR_MATCH_START) && + (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Anchored at start and beginning of word matches pattern + */ + if ((pattern->flags & VAR_MATCH_END) && + (wordLen == pattern->leftLen)) { + /* + * Also anchored at end and matches to the end (word + * is same length as pattern) add space and rhs only + * if rhs is non-null. + */ + if (pattern->rightLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, pattern->rightLen, + (Byte *)pattern->rhs); + } + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Doesn't match to end -- copy word wholesale + */ + goto nosub; + } else { + /* + * Matches at start but need to copy in trailing characters + */ + if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){ + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + Buf_AddBytes(buf, wordLen - pattern->leftLen, + (Byte *)(word + pattern->leftLen)); + } + } else if (pattern->flags & VAR_MATCH_START) { + /* + * Had to match at start of word and didn't -- copy whole word. + */ + goto nosub; + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Anchored at end, Find only place match could occur (leftLen + * characters from the end of the word) and see if it does. Note + * that because the $ will be left at the end of the lhs, we have + * to use strncmp. + */ + cp = word + (wordLen - pattern->leftLen); + if ((cp >= word) && + (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Match found. If we will place characters in the buffer, + * add a space before hand as indicated by addSpace, then + * stuff in the initial, unmatched part of the word followed + * by the right-hand-side. + */ + if (((cp - word) + pattern->rightLen) != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, cp - word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + } else { + /* + * Had to match at end and didn't. Copy entire word. + */ + goto nosub; + } + } else { + /* + * Pattern is unanchored: search for the pattern in the word using + * String_FindSubstring, copying unmatched portions and the + * right-hand-side for each match found, handling non-global + * subsititutions correctly, etc. When the loop is done, any + * remaining part of the word (word and wordLen are adjusted + * accordingly through the loop) is copied straight into the + * buffer. + * addSpace is set FALSE as soon as a space is added to the + * buffer. + */ + register Boolean done; + int origSize; + + done = FALSE; + origSize = Buf_Size(buf); + while (!done) { + cp = Str_FindSubstring(word, pattern->lhs); + if (cp != (char *)NULL) { + if (addSpace && (((cp - word) + pattern->rightLen) != 0)){ + Buf_AddByte(buf, (Byte)' '); + addSpace = FALSE; + } + Buf_AddBytes(buf, cp-word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + wordLen -= (cp - word) + pattern->leftLen; + word = cp + pattern->leftLen; + if (wordLen == 0) { + done = TRUE; + } + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + done = TRUE; + pattern->flags |= VAR_NO_SUB; + } + } else { + done = TRUE; + } + } + if (wordLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + } + /* + * If added characters to the buffer, need to add a space + * before we add any more. If we didn't add any, just return + * the previous value of addSpace. + */ + return ((Buf_Size(buf) != origSize) || addSpace); + } + /* + * Common code for anchored substitutions: if performed a substitution + * and it's not supposed to be global, mark the pattern as requiring + * no more substitutions. addSpace was set TRUE if characters were + * added to the buffer. + */ + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + pattern->flags |= VAR_NO_SUB; + } + return (addSpace); + } + nosub: + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarModify -- + * Modify each of the words of the passed string using the given + * function. Used to implement all modifiers. + * + * Results: + * A string of all the words modified appropriately. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarModify (str, modProc, datum) + char *str; /* String whose words should be trimmed */ + Boolean (*modProc)(); /* Function to use to modify them */ + ClientData datum; /* Datum to pass it */ +{ + Buffer buf; /* Buffer for the new string */ + register char *cp; /* Pointer to end of current word */ + char endc; /* Character that ended the word */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the trimmed + * word */ + + buf = Buf_Init (0); + cp = str; + addSpace = FALSE; + + for (;;) { + /* + * Skip to next word and place cp at its end. + */ + while (isspace (*str)) { + str++; + } + for (cp = str; *cp != '\0' && !isspace (*cp); cp++) + continue; + if (cp == str) { + /* + * If we didn't go anywhere, we must be done! + */ + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); + } + /* + * Nuke terminating character, but save it in endc b/c if str was + * some variable's value, it would not be good to screw it + * over... + */ + endc = *cp; + *cp = '\0'; + + addSpace = (* modProc) (str, addSpace, buf, datum); + + if (endc) { + *cp++ = endc; + } + str = cp; + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Parse -- + * Given the start of a variable invocation, extract the variable + * name and find its value, then modify it according to the + * specification. + * + * Results: + * The (possibly-modified) value of the variable or var_Error if the + * specification is invalid. The length of the specification is + * placed in *lengthPtr (for invalid specifications, this is just + * 2...?). + * A Boolean in *freePtr telling whether the returned string should + * be freed by the caller. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_Parse (str, ctxt, err, lengthPtr, freePtr) + char *str; /* The string to parse */ + GNode *ctxt; /* The context for the variable */ + Boolean err; /* TRUE if undefined variables are an error */ + int *lengthPtr; /* OUT: The length of the specification */ + Boolean *freePtr; /* OUT: TRUE if caller should free result */ +{ + register char *tstr; /* Pointer into str */ + Var *v; /* Variable in invocation */ + register char *cp; /* Secondary pointer into str (place marker + * for tstr) */ + Boolean haveModifier;/* TRUE if have modifiers for the variable */ + register char endc; /* Ending character when variable in parens + * or braces */ + char *start; + Boolean dynamic; /* TRUE if the variable is local and we're + * expanding it in a non-local context. This + * is done to support dynamic sources. The + * result is just the invocation, unaltered */ + + *freePtr = FALSE; + dynamic = FALSE; + start = str; + + if (str[1] != '(' && str[1] != '{') { + /* + * If it's not bounded by braces of some sort, life is much simpler. + * We just need to check for the first character and return the + * value if it exists. + */ + char name[2]; + + name[0] = str[1]; + name[1] = '\0'; + + v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v == (Var *)NIL) { + *lengthPtr = 2; + + if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[1]) { + case '@': + return("$(.TARGET)"); + case '%': + return("$(.ARCHIVE)"); + case '*': + return("$(.PREFIX)"); + case '!': + return("$(.MEMBER)"); + } + } + /* + * Error + */ + return (err ? var_Error : varNoError); + } else { + haveModifier = FALSE; + tstr = &str[1]; + endc = str[1]; + } + } else { + endc = str[1] == '(' ? ')' : '}'; + + /* + * Skip to the end character or a colon, whichever comes first. + */ + for (tstr = str + 2; + *tstr != '\0' && *tstr != endc && *tstr != ':'; + tstr++) + { + continue; + } + if (*tstr == ':') { + haveModifier = TRUE; + } else if (*tstr != '\0') { + haveModifier = FALSE; + } else { + /* + * If we never did find the end character, return NULL + * right now, setting the length to be the distance to + * the end of the string, since that's what make does. + */ + *lengthPtr = tstr - str; + return (var_Error); + } + *tstr = '\0'; + + v = VarFind (str + 2, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && + ((tstr-str) == 4) && (str[3] == 'F' || str[3] == 'D')) + { + /* + * Check for bogus D and F forms of local variables since we're + * in a local context and the name is the right length. + */ + switch(str[2]) { + case '@': + case '%': + case '*': + case '!': + case '>': + case '<': + { + char vname[2]; + char *val; + + /* + * Well, it's local -- go look for it. + */ + vname[0] = str[2]; + vname[1] = '\0'; + v = VarFind(vname, ctxt, 0); + + if (v != (Var *)NIL) { + /* + * No need for nested expansion or anything, as we're + * the only one who sets these things and we sure don't + * but nested invocations in them... + */ + val = (char *)Buf_GetAll(v->val, (int *)NULL); + + if (str[3] == 'D') { + val = VarModify(val, VarHead, (ClientData)0); + } else { + val = VarModify(val, VarTail, (ClientData)0); + } + /* + * Resulting string is dynamically allocated, so + * tell caller to free it. + */ + *freePtr = TRUE; + *lengthPtr = tstr-start+1; + *tstr = endc; + return(val); + } + break; + } + } + } + + if (v == (Var *)NIL) { + if ((((tstr-str) == 3) || + ((((tstr-str) == 4) && (str[3] == 'F' || + str[3] == 'D')))) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[2]) { + case '@': + case '%': + case '*': + case '!': + dynamic = TRUE; + break; + } + } else if (((tstr-str) > 4) && (str[2] == '.') && + isupper(str[3]) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + int len; + + len = (tstr-str) - 3; + if ((strncmp(str+2, ".TARGET", len) == 0) || + (strncmp(str+2, ".ARCHIVE", len) == 0) || + (strncmp(str+2, ".PREFIX", len) == 0) || + (strncmp(str+2, ".MEMBER", len) == 0)) + { + dynamic = TRUE; + } + } + + if (!haveModifier) { + /* + * No modifiers -- have specification length so we can return + * now. + */ + *lengthPtr = tstr - start + 1; + *tstr = endc; + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + return(str); + } else { + return (err ? var_Error : varNoError); + } + } else { + /* + * Still need to get to the end of the variable specification, + * so kludge up a Var structure for the modifications + */ + v = (Var *) emalloc(sizeof(Var)); + v->name = &str[1]; + v->val = Buf_Init(1); + v->flags = VAR_JUNK; + } + } + } + + if (v->flags & VAR_IN_USE) { + Fatal("Variable %s is recursive.", v->name); + /*NOTREACHED*/ + } else { + v->flags |= VAR_IN_USE; + } + /* + * Before doing any modification, we have to make sure the value + * has been fully expanded. If it looks like recursion might be + * necessary (there's a dollar sign somewhere in the variable's value) + * we just call Var_Subst to do any other substitutions that are + * necessary. Note that the value returned by Var_Subst will have + * been dynamically-allocated, so it will need freeing when we + * return. + */ + str = (char *)Buf_GetAll(v->val, (int *)NULL); + if (strchr (str, '$') != (char *)NULL) { + str = Var_Subst(NULL, str, ctxt, err); + *freePtr = TRUE; + } + + v->flags &= ~VAR_IN_USE; + + /* + * Now we need to apply any modifiers the user wants applied. + * These are: + * :M words which match the given . + * is of the standard file + * wildcarding form. + * :S[g] + * Substitute for in the value + * :H Substitute the head of each word + * :T Substitute the tail of each word + * :E Substitute the extension (minus '.') of + * each word + * :R Substitute the root of each word + * (pathname minus the suffix). + * :lhs=rhs Like :S, but the rhs goes to the end of + * the invocation. + */ + if ((str != (char *)NULL) && haveModifier) { + /* + * Skip initial colon while putting it back. + */ + *tstr++ = ':'; + while (*tstr != endc) { + char *newStr; /* New value to return */ + char termc; /* Character which terminated scan */ + + if (DEBUG(VAR)) { + printf("Applying :%c to \"%s\"\n", *tstr, str); + } + switch (*tstr) { + case 'N': + case 'M': + { + char *pattern; + char *cp2; + Boolean copy; + + copy = FALSE; + for (cp = tstr + 1; + *cp != '\0' && *cp != ':' && *cp != endc; + cp++) + { + if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)){ + copy = TRUE; + cp++; + } + } + termc = *cp; + *cp = '\0'; + if (copy) { + /* + * Need to compress the \:'s out of the pattern, so + * allocate enough room to hold the uncompressed + * pattern (note that cp started at tstr+1, so + * cp - tstr takes the null byte into account) and + * compress the pattern into the space. + */ + pattern = emalloc(cp - tstr); + for (cp2 = pattern, cp = tstr + 1; + *cp != '\0'; + cp++, cp2++) + { + if ((*cp == '\\') && + (cp[1] == ':' || cp[1] == endc)) { + cp++; + } + *cp2 = *cp; + } + *cp2 = '\0'; + } else { + pattern = &tstr[1]; + } + if (*tstr == 'M' || *tstr == 'm') { + newStr = VarModify(str, VarMatch, (ClientData)pattern); + } else { + newStr = VarModify(str, VarNoMatch, + (ClientData)pattern); + } + if (copy) { + free(pattern); + } + break; + } + case 'S': + { + VarPattern pattern; + register char delim; + Buffer buf; /* Buffer for patterns */ + + pattern.flags = 0; + delim = tstr[1]; + tstr += 2; + /* + * If pattern begins with '^', it is anchored to the + * start of the word -- skip over it and flag pattern. + */ + if (*tstr == '^') { + pattern.flags |= VAR_MATCH_START; + tstr += 1; + } + + buf = Buf_Init(0); + + /* + * Pass through the lhs looking for 1) escaped delimiters, + * '$'s and backslashes (place the escaped character in + * uninterpreted) and 2) unescaped $'s that aren't before + * the delimiter (expand the variable substitution). + * The result is left in the Buffer buf. + */ + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '$') || + (cp[1] == '\\'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if (*cp == '$') { + if (cp[1] != delim) { + /* + * If unescaped dollar sign not before the + * delimiter, assume it's a variable + * substitution and recurse. + */ + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (freeIt) { + free(cp2); + } + cp += len - 1; + } else { + /* + * Unescaped $ at end of pattern => anchor + * pattern at end. + */ + pattern.flags |= VAR_MATCH_END; + } + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If lhs didn't end with the delimiter, complain and + * return NULL + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + /* + * Fetch pattern and destroy buffer, but preserve the data + * in it, since that's our lhs. Note that Buf_GetAll + * will return the actual number of bytes, which includes + * the null byte, so we have to decrement the length by + * one. + */ + pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen); + pattern.leftLen--; + Buf_Destroy(buf, FALSE); + + /* + * Now comes the replacement string. Three things need to + * be done here: 1) need to compress escaped delimiters and + * ampersands and 2) need to replace unescaped ampersands + * with the l.h.s. (since this isn't regexp, we can do + * it right here) and 3) expand any variable substitutions. + */ + buf = Buf_Init(0); + + tstr = cp + 1; + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '&') || + (cp[1] == '\\') || + (cp[1] == '$'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if ((*cp == '$') && (cp[1] != delim)) { + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + cp += len - 1; + if (freeIt) { + free(cp2); + } + } else if (*cp == '&') { + Buf_AddBytes(buf, pattern.leftLen, + (Byte *)pattern.lhs); + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If didn't end in delimiter character, complain + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen); + pattern.rightLen--; + Buf_Destroy(buf, FALSE); + + /* + * Check for global substitution. If 'g' after the final + * delimiter, substitution is global and is marked that + * way. + */ + cp++; + if (*cp == 'g') { + pattern.flags |= VAR_SUB_GLOBAL; + cp++; + } + + termc = *cp; + newStr = VarModify(str, VarSubstitute, + (ClientData)&pattern); + /* + * Free the two strings. + */ + free(pattern.lhs); + free(pattern.rhs); + break; + } + case 'T': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarTail, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'H': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarHead, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'E': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarSuffix, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'R': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarRoot, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + default: { + /* + * This can either be a bogus modifier or a System-V + * substitution command. + */ + VarPattern pattern; + Boolean eqFound; + + pattern.flags = 0; + eqFound = FALSE; + /* + * First we make a pass through the string trying + * to verify it is a SYSV-make-style translation: + * it must be: =) + */ + for (cp = tstr; *cp != '\0' && *cp != endc; cp++) { + if (*cp == '=') { + eqFound = TRUE; + /* continue looking for endc */ + } + } + if (*cp == endc && eqFound) { + + /* + * Now we break this sucker into the lhs and + * rhs. We must null terminate them of course. + */ + for (cp = tstr; *cp != '='; cp++) + continue; + pattern.lhs = tstr; + pattern.leftLen = cp - tstr; + *cp++ = '\0'; + + pattern.rhs = cp; + while (*cp != endc) { + cp++; + } + pattern.rightLen = cp - pattern.rhs; + *cp = '\0'; + + /* + * SYSV modifications happen through the whole + * string. Note the pattern is anchored at the end. + */ + newStr = VarModify(str, VarSYSVMatch, + (ClientData)&pattern); + + /* + * Restore the nulled characters + */ + pattern.lhs[pattern.leftLen] = '='; + pattern.rhs[pattern.rightLen] = endc; + termc = endc; + } else { + Error ("Unknown modifier '%c'\n", *tstr); + for (cp = tstr+1; + *cp != ':' && *cp != endc && *cp != '\0'; + cp++) + continue; + termc = *cp; + newStr = var_Error; + } + } + } + if (DEBUG(VAR)) { + printf("Result is \"%s\"\n", newStr); + } + + if (*freePtr) { + free (str); + } + str = newStr; + if (str != var_Error) { + *freePtr = TRUE; + } else { + *freePtr = FALSE; + } + if (termc == '\0') { + Error("Unclosed variable specification for %s", v->name); + } else if (termc == ':') { + *cp++ = termc; + } else { + *cp = termc; + } + tstr = cp; + } + *lengthPtr = tstr - start + 1; + } else { + *lengthPtr = tstr - start + 1; + *tstr = endc; + } + + if (v->flags & VAR_FROM_ENV) { + Boolean destroy = FALSE; + + if (str != (char *)Buf_GetAll(v->val, (int *)NULL)) { + destroy = TRUE; + } else { + /* + * Returning the value unmodified, so tell the caller to free + * the thing. + */ + *freePtr = TRUE; + } + Buf_Destroy(v->val, destroy); + free((Address)v); + } else if (v->flags & VAR_JUNK) { + /* + * Perform any free'ing needed and set *freePtr to FALSE so the caller + * doesn't try to free a static pointer. + */ + if (*freePtr) { + free(str); + } + *freePtr = FALSE; + free((Address)v); + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + } else { + str = var_Error; + } + } + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_Subst -- + * Substitute for all variables in the given string in the given context + * If undefErr is TRUE, Parse_Error will be called when an undefined + * variable is encountered. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. The old string must be freed by the caller + *----------------------------------------------------------------------- + */ +char * +Var_Subst (var, str, ctxt, undefErr) + char *var; /* Named variable || NULL for all */ + char *str; /* the string in which to substitute */ + GNode *ctxt; /* the context wherein to find variables */ + Boolean undefErr; /* TRUE if undefineds are an error */ +{ + Buffer buf; /* Buffer for forming things */ + char *val; /* Value to substitute for a variable */ + int length; /* Length of the variable invocation */ + Boolean doFree; /* Set true if val should be freed */ + static Boolean errorReported; /* Set true if an error has already + * been reported to prevent a plethora + * of messages when recursing */ + + buf = Buf_Init (MAKE_BSIZE); + errorReported = FALSE; + + while (*str) { + if (var == NULL && (*str == '$') && (str[1] == '$')) { + /* + * A dollar sign may be escaped either with another dollar sign. + * In such a case, we skip over the escape character and store the + * dollar sign into the buffer directly. + */ + str++; + Buf_AddByte(buf, (Byte)*str); + str++; + } else if (*str != '$') { + /* + * Skip as many characters as possible -- either to the end of + * the string or to the next dollar sign (variable invocation). + */ + char *cp; + + for (cp = str++; *str != '$' && *str != '\0'; str++) + continue; + Buf_AddBytes(buf, str - cp, (Byte *)cp); + } else { + if (var != NULL) { + int expand; + for (;;) { + if (str[1] != '(' && str[1] != '{') { + if (str[1] != *var) { + Buf_AddBytes(buf, 2, (Byte *) str); + str += 2; + expand = FALSE; + } + else + expand = TRUE; + break; + } + else { + char *p; + + /* + * Scan up to the end of the variable name. + */ + for (p = &str[2]; *p && + *p != ':' && *p != ')' && *p != '}'; p++) + if (*p == '$') + break; + /* + * A variable inside the variable. We cannot expand + * the external variable yet, so we try again with + * the nested one + */ + if (*p == '$') { + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + continue; + } + + if (strncmp(var, str + 2, p - str - 2) != 0 || + var[p - str - 2] != '\0') { + /* + * Not the variable we want to expand, scan + * until the next variable + */ + for (;*p != '$' && *p != '\0'; p++) + continue; + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + expand = FALSE; + } + else + expand = TRUE; + break; + } + } + if (!expand) + continue; + } + + val = Var_Parse (str, ctxt, undefErr, &length, &doFree); + + /* + * When we come down here, val should either point to the + * value of this variable, suitably modified, or be NULL. + * Length should be the total length of the potential + * variable invocation (from $ to end character...) + */ + if (val == var_Error || val == varNoError) { + /* + * If performing old-time variable substitution, skip over + * the variable and continue with the substitution. Otherwise, + * store the dollar sign and advance str so we continue with + * the string... + */ + if (oldVars) { + str += length; + } else if (undefErr) { + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!errorReported) { + Parse_Error (PARSE_FATAL, + "Undefined variable \"%.*s\"",length,str); + } + str += length; + errorReported = TRUE; + } else { + Buf_AddByte (buf, (Byte)*str); + str += 1; + } + } else { + /* + * We've now got a variable structure to store in. But first, + * advance the string pointer. + */ + str += length; + + /* + * Copy all the characters from the variable value straight + * into the new string. + */ + Buf_AddBytes (buf, strlen (val), (Byte *)val); + if (doFree) { + free ((Address)val); + } + } + } + } + + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetTail -- + * Return the tail from each of a list of words. Used to set the + * System V local variables. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetTail(file) + char *file; /* Filename to modify */ +{ + return(VarModify(file, VarTail, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetHead -- + * Find the leading components of a (list of) filename(s). + * XXX: VarHead does not replace foo by ., as (sun) System V make + * does. + * + * Results: + * The leading components. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetHead(file) + char *file; /* Filename to manipulate */ +{ + return(VarModify(file, VarHead, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_Init -- + * Initialize the module + * + * Results: + * None + * + * Side Effects: + * The VAR_CMD and VAR_GLOBAL contexts are created + *----------------------------------------------------------------------- + */ +void +Var_Init () +{ + VAR_GLOBAL = Targ_NewGN ("Global"); + VAR_CMD = Targ_NewGN ("Command"); + +} + +/****************** PRINT DEBUGGING INFO *****************/ +static int +VarPrintVar (v) + Var *v; +{ + printf ("%-16s = %s\n", v->name, (char *) Buf_GetAll(v->val, (int *)NULL)); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Var_Dump -- + * print all variables in a context + *----------------------------------------------------------------------- + */ +void +Var_Dump (ctxt) + GNode *ctxt; +{ + Lst_ForEach (ctxt->context, VarPrintVar); +}