diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 index d8f1950cdcf8..85a53d5e0999 100644 --- a/usr.bin/make/make.1 +++ b/usr.bin/make/make.1 @@ -1,1218 +1,1246 @@ .\" 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. .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" $FreeBSD$ .\" .Dd March 19, 1994 .Dt MAKE 1 .Os .Sh NAME .Nm make .Nd maintain program dependencies .Sh SYNOPSIS .Nm make .Op Fl BPSXeiknqrstv .Op Fl D Ar variable .Op Fl d Ar flags .Op Fl E Ar variable .Op Fl f Ar makefile .Op Fl I Ar directory .Bk -words .Op Fl j Ar max_jobs .Op Fl m Ar directory .Ek .Op Fl V Ar variable .Op Ar variable Ns No = Ns Ar 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 describing dependency relationships between the generation of files and programs. The first of .Ql Pa makefile and .Ql Pa Makefile that can be found in either the current directory or a special object directory (see .Ql Va .OBJDIR ) will be read for this list of specifications. If the file .Ql Pa .depend can be found, it is also read (see .Xr mkdep 1 ) . .Pp This manual page is intended as a reference document only. For a more thorough introduction to .Nm and makefiles, please refer to .%T "Make \- A Tutorial" . .Pp The options are as follows: .Bl -tag -width Ds .It Fl B Try to be backwards compatible by executing a single shell per command and by executing the commands to make the sources of a dependency line in sequence. This is turned on by default unless .Fl j is used. .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 are to print debugging information. Argument .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 f Print debugging information about the execution of for loops. Currently a no-op. .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 l Print commands in Makefiles regardless of whether or not they are prefixed by @ or other "quiet" flags. Also known as "loud" behavior. .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 Ar variable Specify a variable whose environment value (if any) will override macro assignments within makefiles. .It Fl e Specify that environment values override macro assignments within makefiles for all variables. .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 \- , standard input is read. Multiple makefiles 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 (or directories, see the .Fl m option) 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 \- before each command line in the makefile. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. Turns compatibility mode off, unless the .Ar B flag is also specified. .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 m Ar directory Specify a directory in which to search for sys.mk and makefiles included via the <...> style. Multiple directories can be added to form a search path. This path will override the default system include path: .Pa /usr/share/mk . Furthermore, the system include path will be appended to the search path used for "..."-style inclusions (see the .Fl I option). .It Fl n Display the commands that would have been executed, but do not actually execute them. .It Fl P Collate the output of a given job and display it only when the job finishes, instead of mixing the output of parallel jobs together. This option has no effect unless .Fl j is used too. .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 Stop processing when an error is encountered. Default behaviour. This is needed to negate the .Fl k option during recursive builds. .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 Fl V Ar variable Print .Nm make Ns 's idea of the value of .Ar variable , in the global context. Do not build any targets. Multiple instances of this option may be specified; the variables will be printed one per line, with a blank line for each null or undefined variable. .It Fl v Be extra verbose. For multi-job makes, this will cause file banners to be generated. .It Fl X When using the .Fl V option to print the values of variables, do not recursively expand the values. .It Ar variable Ns No = Ns Ar 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 .Dq 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 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 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 is interrupted. .El .Pp Targets and sources may contain the shell wildcard expressions .Ql ? , .Ql * , .Ql [] and .Ql {} . The expressions .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 expression .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 .Nm 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 whitespace before the assigned .Ar value is removed; if the value is being appended, a single space is inserted between the previous contents of the variable and the appended value. .Pp Variables are expanded by surrounding the variable name with either curly braces .Pq Ql {} or parentheses .Pq Ql () and preceding it with a dollar sign .Pq Ql \&$ . If the variable name contains only a single letter, the surrounding braces or parentheses are not required. This shorter form is not recommended. .Pp Variable substitution occurs at two distinct times, depending on where the variable is being used. Variables in dependency lines are expanded as the line is read. Variables in shell commands are expanded when the shell command is executed. .Pp The four different classes of variables (in order of increasing precedence) are: .Bl -tag -width Ds .It Environment variables Variables defined as part of .Nm make Ns 's environment. .It Global variables Variables defined in the makefile or in included makefiles. .It Command line variables Variables defined as part of the command line. .It Local variables Variables that are defined specific to a certain target. The seven local variables are as follows: .Bl -tag -width ".ARCHIVE" .It Va .ALLSRC The list of all sources for this target; also known as .Ql Va \&> . .It Va .ARCHIVE The name of the archive file; also known as .Ql Va \&! . .It Va .IMPSRC The name/path of the source from which the target is to be transformed (the .Dq implied source); also known as .Ql Va \&< . .It Va .MEMBER The name of the archive member; also known as .Ql Va \&% . .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 \&< , .Ql Va \&% , .Ql Va ? , .Ql Va \&> , and .Ql Va * are permitted for backward compatibility and are not recommended. The six variables .Ql Va "@F" , .Ql Va "@D" , .Ql Va " .It Ic \&.include Ar \*qfile\*q Include the specified makefile. 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. .It Ic \&.undef Ar variable Un-define the specified global variable. Only global variables may be un-defined. .It Ic \&.error Ar message Terminate processing of the makefile immediately. The filename of the makefile, the line on which the error was encountered and the specified message are printed to standard output and .Nm make terminates with exit code 1. Variables in the message are expanded. .El .Pp Conditionals are used to determine which parts of the Makefile to process. They are used similarly to the conditionals supported by the C pre-processor. The following conditionals are supported: .Bl -tag -width Ds .It Xo .Ic \&.if .Oo \&! Oc Ns Ar expression .Op Ar operator expression ... .Xc Test the value of an expression. .It Xo .Ic .ifdef .Oo \&! Oc Ns Ar variable .Op Ar operator variable ... .Xc Test the value of a variable. .It Xo .Ic .ifndef .Oo \&! Oc Ns Ar variable .Op Ar operator variable ... .Xc Test the value of a variable. .It Xo .Ic .ifmake .Oo \&! Oc Ns Ar target .Op Ar operator target ... .Xc Test the target being built. .It Xo .Ic .ifnmake .Oo \&! Oc Ar target .Op Ar operator target ... .Xc Test the target being built. .It Ic .else Reverse the sense of the last conditional. .It Xo .Ic .elif .Oo \&! Oc Ar expression .Op Ar operator expression ... .Xc A combination of .Ql Ic .else followed by .Ql Ic .if . .It Xo .Ic .elifdef .Oo \&! Oc Ns Ar variable .Op Ar operator variable ... .Xc A combination of .Ql Ic .else followed by .Ql Ic .ifdef . .It Xo .Ic .elifndef .Oo \&! Oc Ns Ar variable .Op Ar operator variable ... .Xc A combination of .Ql Ic .else followed by .Ql Ic .ifndef . .It Xo .Ic .elifmake .Oo \&! Oc Ns Ar target .Op Ar operator target ... .Xc A combination of .Ql Ic .else followed by .Ql Ic .ifmake . .It Xo .Ic .elifnmake .Oo \&! Oc Ns Ar target .Op Ar operator target ... .Xc A combination of .Ql Ic .else followed by .Ql Ic .ifnmake . .It Ic .endif End the body of the conditional. .El .Pp The .Ar operator may be any one of the following: .Bl -tag -width "Cm XX" .It Cm \&|\&| logical OR .It Cm \&&& Logical .Tn AND ; of higher precedence than .Ql Ic || . .El .Pp As in C, .Nm 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 An .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 is evaluating one of these conditional expressions, and it encounters a word it doesn't recognize, either the .Dq make or .Dq 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 .Dq defined expression is applied. Similarly, if the form is .Ql Ic .ifmake or .Ql Ic .ifnmake , the .Dq 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 .Ar expression is evaluated, it is split into words. The iteration .Ar 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 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 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 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. .It Ic .WAIT If special .Ic .WAIT source is appears in a dependency line, the sources that precede it are made before the sources that succeed it in the line. Loops are not being detected and targets that form loops will be silently ignored. .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 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 .INCLUDES A list of suffixes that indicate files that can be included in a source file. The suffix must have already been declared with .Ic .SUFFIXES ; any suffix so declared will have the directories on its search path (see .Ic .PATH ) placed in the .Va .INCLUDES special variable, each preceeded by a .Fl I flag. .It Ic .INTERRUPT If .Nm is interrupted, the commands for this target will be executed. .It Ic .LIBS This does for libraries what .Ic .INCLUDES does for include files, except that the flag used is .Fl L . .It Ic .MAIN If no target is specified when .Nm is invoked, this target will be built. This is always set, either explicitly, or implicitly when .Nm selects the default target, to give the user a way to refer to the default target on the command line. .It Ic .MAKEFLAGS This target provides a way to specify flags for .Nm when the makefile is used. The flags are as if typed to the shell, though the .Fl f option will have no effect. .\" XXX: NOT YET!!!! .\" .It Ic .NOTPARALLEL .\" The named targets are executed in non parallel mode. If no targets are .\" specified, then all targets are executed in non parallel mode. .It Ic .NOTPARALLEL Disable parallel mode. .It Ic .NO_PARALLEL Same as above, for compatibility with other pmake variants. .It Ic .ORDER The named targets are made in sequence. .\" XXX: NOT YET!!!! .\" .It Ic .PARALLEL .\" The named targets are executed in parallel mode. If no targets are .\" specified, then all targets are executed in parallel mode. .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. Where possible, use of .Ic .PATH is preferred over use of the .Va VPATH variable. .It Ic .PATH\fIsuffix\fR The sources are directories which are to be searched for suffixed files not found in the current directory. .Nm Make first searches the suffixed search path, before reverting to the default path if the file is not found there. This form is required for .Ic .LIBS and .Ic .INCLUDES to work. .It Ic .PHONY Apply the .Ic .PHONY attribute to any specified sources. Targets with this attribute are always considered to be out of date. .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 COMPATIBILITY Older versions of .Nm used .Ev MAKE instead of .Ev MAKEFLAGS . This was removed for POSIX compatibility. The internal variable .Va MAKE is set to the same value as .Va .MAKE ; support for this may be removed in the future. .Pp Most of the more esoteric features of .Nm should probably be avoided for greater compatibility. .Sh ENVIRONMENT .Nm Make uses the following environment variables, if they exist: .Ev MACHINE , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , and .Ev PWD . .Sh FILES .Bl -tag -width /usr/share/doc/psd/12.make -compact .It Pa .depend list of dependencies .It Pa Makefile list of dependencies .It Pa makefile list of dependencies .It obj object directory .It Pa sys.mk system makefile .It Pa /usr/share/mk system makefile directory .It /usr/share/doc/psd/12.make PMake tutorial .It Pa /usr/obj default .Ev MAKEOBJDIRPREFIX directory. .El .Sh BUGS The determination of .Va .OBJDIR is contorted to the point of absurdity. .Pp In the presence of several .Ic .MAIN special targets, .Nm silently ignores all but the first. .Pp .Va .TARGETS is not set to the default target when .Nm is invoked without a target name and no .Ic .MAIN special target exists. .Pp The evaluation of .Ar expression in a test is very simple-minded. Currently, the only form that works is .Ql .if ${VAR} op something For instance, you should write tests as .Ql .if ${VAR} = "string" not the other way around, which doesn't work. .Pp For loops are expanded before tests, so a fragment such as: .Bd -literal \&.for TMACHINE in ${SHARED_ARCHS} \&.if ${TMACHINE} = ${MACHINE} ... \&.endif \&.endfor .Ed won't work, and should be rewritten the other way around. .Sh SEE ALSO .Xr mkdep 1 .Rs .%T "PMake - A Tutorial" .Re .Sh HISTORY A .Nm command appeared in .At v7 . diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c index d9de5b7de5ae..c0df0a086b00 100644 --- a/usr.bin/make/var.c +++ b/usr.bin/make/var.c @@ -1,2130 +1,2516 @@ /* * 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. * * @(#)var.c 8.3 (Berkeley) 3/19/94 */ #ifndef lint #include __RCSID("$FreeBSD$"); #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 +#include #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 */ static Lst allVars; /* List of all variables */ #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; /* Var*Pattern flags */ #define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */ +#define VAR_SUB_ONE 0x02 /* Apply substitution to one word */ +#define VAR_SUB_MATCHED 0x04 /* There was a match */ #define VAR_MATCH_START 0x08 /* Match at start of word */ #define VAR_MATCH_END 0x10 /* Match at end of word */ +#define VAR_NOSUBST 0x20 /* don't expand vars in VarGetPattern */ 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; } VarPattern; +typedef struct { + regex_t re; + int nsub; + regmatch_t *matches; + char *replace; + int flags; +} VarREPattern; + static int VarCmp __P((ClientData, ClientData)); static Var *VarFind __P((char *, GNode *, int)); static void VarAdd __P((char *, char *, GNode *)); static void VarDelete __P((ClientData)); static Boolean VarHead __P((char *, Boolean, Buffer, ClientData)); static Boolean VarTail __P((char *, Boolean, Buffer, ClientData)); static Boolean VarSuffix __P((char *, Boolean, Buffer, ClientData)); static Boolean VarRoot __P((char *, Boolean, Buffer, ClientData)); static Boolean VarMatch __P((char *, Boolean, Buffer, ClientData)); #ifdef SYSVVARSUB static Boolean VarSYSVMatch __P((char *, Boolean, Buffer, ClientData)); #endif static Boolean VarNoMatch __P((char *, Boolean, Buffer, ClientData)); +static void VarREError __P((int, regex_t *, const char *)); +static Boolean VarRESubstitute __P((char *, Boolean, Buffer, ClientData)); static Boolean VarSubstitute __P((char *, Boolean, Buffer, ClientData)); +static char *VarGetPattern __P((GNode *, int, char **, int, int *, int *, + VarPattern *)); static char *VarQuote __P((char *)); static char *VarModify __P((char *, Boolean (*)(char *, Boolean, Buffer, ClientData), ClientData)); static int VarPrintVar __P((ClientData, ClientData)); /*- *----------------------------------------------------------------------- * 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) ClientData v; /* VAR structure to compare */ ClientData name; /* name to look for */ { return (strcmp ((char *) name, ((Var *) 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 */ { Boolean localCheckEnvFirst; 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((unsigned char) 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; } /* * Note whether this is one of the specific variables we were told through * the -E flag to use environment-variable-override for. */ if (Lst_Find (envFirstVars, (ClientData)name, (int (*)(ClientData, ClientData)) strcmp) != NILLNODE) { localCheckEnvFirst = TRUE; } else { localCheckEnvFirst = FALSE; } /* * 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 ((var == NILLNODE) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL) && !checkEnvFirst && !localCheckEnvFirst) { var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); } if ((var == NILLNODE) && (flags & FIND_ENV)) { char *env; if ((env = getenv (name)) != NULL) { int len; v = (Var *) emalloc(sizeof(Var)); v->name = estrdup(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 || localCheckEnvFirst) && (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 = estrdup (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); (void) Lst_AtEnd (allVars, (ClientData) v); if (DEBUG(VAR)) { printf("%s:%s = %s\n", ctxt->name, name, val); } } /*- *----------------------------------------------------------------------- * VarDelete -- * Delete a variable and all the space associated with it. * * Results: * None * * Side Effects: * None *----------------------------------------------------------------------- */ static void VarDelete(vp) ClientData vp; { Var *v = (Var *) vp; free(v->name); Buf_Destroy(v->val, TRUE); free((Address) v); } /*- *----------------------------------------------------------------------- * 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); ln = Lst_Member(allVars, v); Lst_Remove(allVars, ln); VarDelete((ClientData) 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) { free(v->name); 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, frp) char *name; /* name to find */ GNode *ctxt; /* context in which to search for it */ char **frp; { Var *v; v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); *frp = NULL; if (v != (Var *) NIL) { char *p = ((char *)Buf_GetAll(v->val, (int *)NULL)); if (v->flags & VAR_FROM_ENV) { Buf_Destroy(v->val, FALSE); free((Address) v); *frp = p; } return p; } 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, dummy) 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 */ ClientData dummy; { 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(dummy ? TRUE : 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, dummy) 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 */ ClientData dummy; { 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 (dummy ? TRUE : 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, dummy) 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 */ ClientData dummy; { 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] = '.'; addSpace = TRUE; } return (dummy ? addSpace : 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, dummy) 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 */ ClientData dummy; { 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 (dummy ? TRUE : 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 */ ClientData pattern; /* Pattern the word must match */ { if (Str_Match(word, (char *) pattern)) { if (addSpace) { Buf_AddByte(buf, (Byte)' '); } addSpace = TRUE; Buf_AddBytes(buf, strlen(word), (Byte *)word); } return(addSpace); } #ifdef SYSVVARSUB /*- *----------------------------------------------------------------------- * 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, patp) 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 */ ClientData patp; /* Pattern the word must match */ { int len; char *ptr; VarPattern *pat = (VarPattern *) patp; 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); } #endif /*- *----------------------------------------------------------------------- * 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 */ ClientData pattern; /* Pattern the word must match */ { if (!Str_Match(word, (char *) 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, patternp) char *word; /* Word to modify */ Boolean addSpace; /* True if space should be added before * other characters */ Buffer buf; /* Buffer for result */ ClientData patternp; /* Pattern for substitution */ { register int wordLen; /* Length of word */ register char *cp; /* General pointer */ VarPattern *pattern = (VarPattern *) patternp; wordLen = strlen(word); if (1) { /* substitute in each word of the variable */ /* * Break substitution 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 * substitutions 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 || (pattern->flags & VAR_SUB_GLOBAL) == 0){ done = TRUE; } } 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: * addSpace was set TRUE if characters were added to the buffer. */ return (addSpace); } nosub: if (addSpace) { Buf_AddByte(buf, (Byte)' '); } Buf_AddBytes(buf, wordLen, (Byte *)word); return(TRUE); } +/*- + *----------------------------------------------------------------------- + * VarREError -- + * Print the error caused by a regcomp or regexec call. + * + * Results: + * None. + * + * Side Effects: + * An error gets printed. + * + *----------------------------------------------------------------------- + */ +static void +VarREError(err, pat, str) + int err; + regex_t *pat; + const char *str; +{ + char *errbuf; + int errlen; + + errlen = regerror(err, pat, 0, 0); + errbuf = emalloc(errlen); + regerror(err, pat, errbuf, errlen); + Error("%s: %s", str, errbuf); + free(errbuf); +} + + +/*- + *----------------------------------------------------------------------- + * VarRESubstitute -- + * Perform a regex 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 +VarRESubstitute(word, addSpace, buf, patternp) + char *word; + Boolean addSpace; + Buffer buf; + ClientData patternp; +{ + VarREPattern *pat; + int xrv; + char *wp; + char *rp; + int added; + int flags = 0; + +#define MAYBE_ADD_SPACE() \ + if (addSpace && !added) \ + Buf_AddByte(buf, ' '); \ + added = 1 + + added = 0; + wp = word; + pat = patternp; + + if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) == + (VAR_SUB_ONE|VAR_SUB_MATCHED)) + xrv = REG_NOMATCH; + else { + tryagain: + xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags); + } + + switch (xrv) { + case 0: + pat->flags |= VAR_SUB_MATCHED; + if (pat->matches[0].rm_so > 0) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, pat->matches[0].rm_so, wp); + } + + for (rp = pat->replace; *rp; rp++) { + if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf,rp[1]); + rp++; + } + else if ((*rp == '&') || + ((*rp == '\\') && isdigit((unsigned char)rp[1]))) { + int n; + char *subbuf; + int sublen; + char errstr[3]; + + if (*rp == '&') { + n = 0; + errstr[0] = '&'; + errstr[1] = '\0'; + } else { + n = rp[1] - '0'; + errstr[0] = '\\'; + errstr[1] = rp[1]; + errstr[2] = '\0'; + rp++; + } + + if (n > pat->nsub) { + Error("No subexpression %s", &errstr[0]); + subbuf = ""; + sublen = 0; + } else if ((pat->matches[n].rm_so == -1) && + (pat->matches[n].rm_eo == -1)) { + Error("No match for subexpression %s", &errstr[0]); + subbuf = ""; + sublen = 0; + } else { + subbuf = wp + pat->matches[n].rm_so; + sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so; + } + + if (sublen > 0) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, sublen, subbuf); + } + } else { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf, *rp); + } + } + wp += pat->matches[0].rm_eo; + if (pat->flags & VAR_SUB_GLOBAL) { + flags |= REG_NOTBOL; + if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf, *wp); + wp++; + + } + if (*wp) + goto tryagain; + } + if (*wp) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, strlen(wp), wp); + } + break; + default: + VarREError(xrv, &pat->re, "Unexpected regex error"); + /* fall through */ + case REG_NOMATCH: + if (*wp) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf,strlen(wp),wp); + } + break; + } + return(addSpace||added); +} + + /*- *----------------------------------------------------------------------- * 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 */ /* Function to use to modify them */ Boolean (*modProc) __P((char *, Boolean, Buffer, ClientData)); ClientData datum; /* Datum to pass it */ { Buffer buf; /* Buffer for the new string */ Boolean addSpace; /* TRUE if need to add a space to the * buffer before adding the trimmed * word */ char **av; /* word list [first word does not count] */ int ac, i; buf = Buf_Init (0); addSpace = FALSE; av = brk_string(str, &ac, FALSE); for (i = 1; i < ac; i++) addSpace = (*modProc)(av[i], addSpace, buf, datum); Buf_AddByte (buf, '\0'); str = (char *)Buf_GetAll (buf, (int *)NULL); Buf_Destroy (buf, FALSE); return (str); } +/*- + *----------------------------------------------------------------------- + * VarGetPattern -- + * Pass through the tstr 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 unless flags + * has VAR_NOSUBST set). + * Return the expanded string or NULL if the delimiter was missing + * If pattern is specified, handle escaped ampersands, and replace + * unescaped ampersands with the lhs of the pattern. + * + * Results: + * A string of all the words modified appropriately. + * If length is specified, return the string length of the buffer + * If flags is specified and the last character of the pattern is a + * $ set the VAR_MATCH_END bit of flags. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +static char * +VarGetPattern(ctxt, err, tstr, delim, flags, length, pattern) + GNode *ctxt; + int err; + char **tstr; + int delim; + int *flags; + int *length; + VarPattern *pattern; +{ + char *cp; + Buffer buf = Buf_Init(0); + int junk; + if (length == NULL) + length = &junk; + +#define IS_A_MATCH(cp, delim) \ + ((cp[0] == '\\') && ((cp[1] == delim) || \ + (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&')))) + + /* + * Skim through until the matching delimiter is found; + * pick up variable substitutions on the way. Also allow + * backslashes to quote the delimiter, $, and \, but don't + * touch other backslashes. + */ + for (cp = *tstr; *cp && (*cp != delim); cp++) { + if (IS_A_MATCH(cp, delim)) { + Buf_AddByte(buf, (Byte) cp[1]); + cp++; + } else if (*cp == '$') { + if (cp[1] == delim) { + if (flags == NULL) + Buf_AddByte(buf, (Byte) *cp); + else + /* + * Unescaped $ at end of pattern => anchor + * pattern at end. + */ + *flags |= VAR_MATCH_END; + } else { + if (flags == NULL || (*flags & VAR_NOSUBST) == 0) { + char *cp2; + int len; + Boolean freeIt; + + /* + * If unescaped dollar sign not before the + * delimiter, assume it's a variable + * substitution and recurse. + */ + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *) cp2); + if (freeIt) + free(cp2); + cp += len - 1; + } else { + char *cp2 = &cp[1]; + + if (*cp2 == '(' || *cp2 == '{') { + /* + * Find the end of this variable reference + * and suck it in without further ado. + * It will be interperated later. + */ + int have = *cp2; + int want = (*cp2 == '(') ? ')' : '}'; + int depth = 1; + + for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) { + if (cp2[-1] != '\\') { + if (*cp2 == have) + ++depth; + if (*cp2 == want) + --depth; + } + } + Buf_AddBytes(buf, cp2 - cp, (Byte *)cp); + cp = --cp2; + } else + Buf_AddByte(buf, (Byte) *cp); + } + } + } + else if (pattern && *cp == '&') + Buf_AddBytes(buf, pattern->leftLen, (Byte *)pattern->lhs); + else + Buf_AddByte(buf, (Byte) *cp); + } + + Buf_AddByte(buf, (Byte) '\0'); + + if (*cp != delim) { + *tstr = cp; + *length = 0; + return NULL; + } + else { + *tstr = ++cp; + cp = (char *) Buf_GetAll(buf, length); + *length -= 1; /* Don't count the NULL */ + Buf_Destroy(buf, FALSE); + return cp; + } +} + + /*- *----------------------------------------------------------------------- * VarQuote -- * Quote shell meta-characters in the string * * Results: * The quoted string * * Side Effects: * None. * *----------------------------------------------------------------------- */ static char * VarQuote(str) char *str; { Buffer buf; /* This should cover most shells :-( */ static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~"; buf = Buf_Init (MAKE_BSIZE); for (; *str; str++) { if (strchr(meta, *str) != NULL) Buf_AddByte(buf, (Byte)'\\'); Buf_AddByte(buf, (Byte)*str); } Buf_AddByte(buf, (Byte) '\0'); str = (char *)Buf_GetAll (buf, (int *)NULL); Buf_Destroy (buf, FALSE); return str; } /*- *----------------------------------------------------------------------- * 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 */ 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 */ register char startc=0; /* Starting character when variable in parens * or braces */ int cnt; /* Used to count brace pairs when variable in * in parens or braces */ char *start; + char delim; 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 */ int vlen; /* length of variable name, after embedded variable * expansion */ *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. */ /* XXX: It looks like $% and $! are reversed here */ 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 { /* build up expanded variable name in this buffer */ Buffer buf = Buf_Init(MAKE_BSIZE); startc = str[1]; endc = startc == '(' ? ')' : '}'; /* * Skip to the end character or a colon, whichever comes first, * replacing embedded variables as we go. */ for (tstr = str + 2; *tstr != '\0' && *tstr != endc && *tstr != ':'; tstr++) if (*tstr == '$') { int rlen; Boolean rfree; char* rval = Var_Parse(tstr, ctxt, err, &rlen, &rfree); if (rval == var_Error) { Fatal("Error expanding embedded variable."); } else if (rval != NULL) { Buf_AddBytes(buf, strlen(rval), (Byte *) rval); if (rfree) free(rval); } tstr += rlen - 1; } else Buf_AddByte(buf, (Byte) *tstr); if (*tstr == '\0') { /* * 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); } haveModifier = (*tstr == ':'); *tstr = '\0'; Buf_AddByte(buf, (Byte) '\0'); str = Buf_GetAll(buf, NULL); vlen = strlen(str); v = VarFind (str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && (vlen == 2) && (str[1] == 'F' || str[1] == '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[0]) { case '@': case '%': case '*': case '!': case '>': case '<': { char vname[2]; char *val; /* * Well, it's local -- go look for it. */ vname[0] = str[0]; 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 * put nested invocations in them... */ val = (char *)Buf_GetAll(v->val, (int *)NULL); if (str[1] == '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; Buf_Destroy(buf, TRUE); return(val); } break; } } } if (v == (Var *)NIL) { if (((vlen == 1) || (((vlen == 2) && (str[1] == 'F' || str[1] == '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[0]) { case '@': case '%': case '*': case '!': dynamic = TRUE; break; } } else if ((vlen > 2) && (str[0] == '.') && isupper((unsigned char) str[1]) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { int len; len = vlen - 1; if ((strncmp(str, ".TARGET", len) == 0) || (strncmp(str, ".ARCHIVE", len) == 0) || (strncmp(str, ".PREFIX", len) == 0) || (strncmp(str, ".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; Buf_Destroy(buf, TRUE); return(str); } else { Buf_Destroy(buf, TRUE); 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; } } Buf_Destroy(buf, TRUE); } 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 + * :C[g] + * Substitute for regex 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 'C': + { + VarREPattern pattern; + char *re; + int error; + + pattern.flags = 0; + delim = tstr[1]; + tstr += 2; + + cp = tstr; + + if ((re = VarGetPattern(ctxt, err, &cp, delim, NULL, + NULL, NULL)) == NULL) { + /* was: goto cleanup */ + *lengthPtr = cp - start + 1; + if (*freePtr) + free(str); + if (delim != '\0') + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + if ((pattern.replace = VarGetPattern(ctxt, err, &cp, + delim, NULL, NULL, NULL)) == NULL){ + free(re); + + /* was: goto cleanup */ + *lengthPtr = cp - start + 1; + if (*freePtr) + free(str); + if (delim != '\0') + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + for (;; cp++) { + switch (*cp) { + case 'g': + pattern.flags |= VAR_SUB_GLOBAL; + continue; + case '1': + pattern.flags |= VAR_SUB_ONE; + continue; + } + break; + } + + termc = *cp; + + error = regcomp(&pattern.re, re, REG_EXTENDED); + free(re); + if (error) { + *lengthPtr = cp - start + 1; + VarREError(error, &pattern.re, "RE substitution error"); + free(pattern.replace); + return (var_Error); + } + + pattern.nsub = pattern.re.re_nsub + 1; + if (pattern.nsub < 1) + pattern.nsub = 1; + if (pattern.nsub > 10) + pattern.nsub = 10; + pattern.matches = emalloc(pattern.nsub * + sizeof(regmatch_t)); + newStr = VarModify(str, VarRESubstitute, + (ClientData) &pattern); + regfree(&pattern.re); + free(pattern.replace); + free(pattern.matches); + break; + } case 'Q': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarQuote (str); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ 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*/ #ifdef SUNSHCMD case 's': if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { char *err; newStr = Cmd_Exec (str, &err); if (err) Error (err, str); cp = tstr + 2; termc = *cp; break; } /*FALLTHRU*/ #endif default: { #ifdef SYSVVARSUB /* * 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: =) */ cp = tstr; cnt = 1; while (*cp != '\0' && cnt) { if (*cp == '=') { eqFound = TRUE; /* continue looking for endc */ } else if (*cp == endc) cnt--; else if (*cp == startc) cnt++; if (cnt) cp++; } 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; cnt = 1; while (cnt) { if (*cp == endc) cnt--; else if (*cp == startc) cnt++; if (cnt) 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 #endif { 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; Buf_Destroy(v->val, TRUE); 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"); allVars = Lst_Init(FALSE); } void Var_End () { Lst_Destroy(allVars, VarDelete); } /****************** PRINT DEBUGGING INFO *****************/ static int VarPrintVar (vp, dummy) ClientData vp; ClientData dummy; { Var *v = (Var *) vp; printf ("%-16s = %s\n", v->name, (char *) Buf_GetAll(v->val, (int *)NULL)); return (dummy ? 0 : 0); } /*- *----------------------------------------------------------------------- * Var_Dump -- * print all variables in a context *----------------------------------------------------------------------- */ void Var_Dump (ctxt) GNode *ctxt; { Lst_ForEach (ctxt->context, VarPrintVar, (ClientData) 0); }