Skip to content
Snippets Groups Projects
Commit 588bd802 authored by sof's avatar sof
Browse files

[project @ 1997-08-17 18:38:36 by sof]

Removed:unused
parent d4df25a6
No related merge requests found
TOP=..
include $(TOP)/mk/boilerplate.mk
C_SRCS=msub.c
C_PROG=msub
CLEAN_FILES += $(C_PROG) $(C_OBJS)
include $(TOP)/mk/target.mk
/*
msub - Read file(s) and perform substitutions using values of
variables defined in makefile.
Syntax: msub [-f makefile] [file ...]
Multiple -f options may be given.
27 Mar 1990 Paul DuBois dubois@primate.wisc.edu
27 Mar 1990 V1.0. Created.
*/
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# define true (1)
# define false (0)
extern char *calloc ();
char *NewString();
typedef struct Def Def;
struct Def
{
char *var; /* variable name */
char *val; /* variable value */
int simple; /* whether value has been expanded */
Def *nextDef; /* next definition in list */
};
char *usage = "Usage: msub [ -f makefile ] file";
char *makefile = NULL;
Def *defList = NULL; /* list of definitions */
int nDefs = 0; /* number of definitions */
int changes;
main (argc, argv)
int argc;
char **argv;
{
FILE *f;
Def *dp;
int i, pass;
--argc;
++argv;
while (argc > 0 && argv[0][0] == '-')
{
if (strcmp (argv[0], "-f") == 0)
{
if (argc < 2)
panic (usage);
if ((f = fopen (makefile = argv[1], "r")) == NULL)
panic ("Can't open makefile");
ReadMake (f);
fclose (f);
argc -= 2;
argv += 2;
}
else
panic (usage); /* bad flag */
}
if (makefile == NULL) /* no -f options given */
{
if ((f = fopen ("makefile", "r")) == NULL)
{
if ((f = fopen ("Makefile", "r")) == NULL)
panic ("Can't open makefile");
}
ReadMake (f);
fclose (f);
}
EncodeEscapes ();
/* determine which values need expanding */
for (dp = defList; dp != NULL; dp = dp->nextDef)
dp->simple = !FindVarRef (dp->val, NULL, NULL, false);
/* expand values to eliminate embedded var references */
for (pass = 0; pass < nDefs; pass++)
{
changes = 0;
for (dp = defList; dp != NULL; dp = dp->nextDef)
{
if (!dp->simple)
Expand (dp);
}
if (changes == 0) /* loop while values expand */
break;
}
/* sanity check: shouldn't have to make more than nDefs passes */
if (pass >= nDefs)
panic ("Too many expansion passes. Something's wrong!");
DecodeEscapes ();
/* partain addition:
args of form "foo=bar" are taken to be defs to add in
*/
while (argc > 0 && strchr(argv[0], '=') && isalpha(argv[0][0]))
{
char name[BUFSIZ], *np, *p;
int len;
p = argv[0];
/* copied from below */
np = name;
while (isalnum (*p) || *p == '_')
*np++ = *p++;
*np = '\0';
while (isspace (*p)) /* skip whitespace */
++p;
if (*p++ == '=') /* it's a definition */
{
/* skip leading/trailing whitespace */
while (isspace (*p))
++p;
len = strlen (p);
while (len > 0 && isspace (p[len-1]))
p[--len] = '\0';
AddDef (name, p, 1);
/* find var refs in value -- NUKED */
/* FindAllVarRefs (p); */
/* also NUKED: continue; */
}
/* end of: copied from below */
--argc;
++argv;
}
/* end of partain addition */
/* read source file(s) and perform substitutions */
if (argc == 0)
DoSub (stdin);
else while (argc > 0)
{
if ((f = fopen (*argv, "r")) == NULL)
{
fprintf (stderr, "Can't open \"%s\". ", *argv);
panic ("Quitting.");
}
DoSub (f);
fclose (f);
--argc;
++argv;
}
exit (0);
}
/*
Read a makefile.
*/
ReadMake (f)
FILE *f;
{
char input[BUFSIZ * 4], name[BUFSIZ], *p, *np;
int i, len;
while (GetLine (f, input)) /* get line, check whether def'n */
{
for (p = input; isspace (*p); p++) { /* nop */ }
if (*p == '#' || *p == '\0') /* comment or blank line */
continue;
if (isalpha (*p)) /* look for var name */
{
np = name;
while (isalnum (*p) || *p == '_')
*np++ = *p++;
*np = '\0';
while (isspace (*p)) /* skip whitespace */
++p;
if (*p++ == '=') /* it's a definition */
{
/* skip leading/trailing whitespace */
while (isspace (*p))
++p;
len = strlen (p);
while (len > 0 && isspace (p[len-1]))
p[--len] = '\0';
AddDef (name, p, 1);
/* find var refs in value */
FindAllVarRefs (p);
continue;
}
}
/* not a definition; find var refs anywhere in line */
FindAllVarRefs (input);
}
}
/*
Find definition by variable name.
*/
Def *FindDefByVar (s)
char *s;
{
Def *dp;
for (dp = defList; dp != NULL; dp = dp->nextDef)
{
if (strcmp (dp->var, s) == 0)
return (dp);
}
return (NULL);
}
/*
Add a definition. If the name hasn't been seen yet, create a new
definition on the list. If the name has been seen, and replace is
non-zero, replace the current value with the new one. (replace will
be zero if we're just adding a variable which is known by its being
referenced somewhere.)
*/
AddDef (name, value, replace)
char *name, *value;
int replace;
{
Def *dp;
if ((dp = FindDefByVar (name)) == NULL)
{
if ((dp = (Def *) calloc (1, sizeof (Def))) == NULL)
panic ("AddDef: out of memory");
dp->var = NewString (name);
dp->val = NewString (value);
dp->simple = 0; /* assume not */
dp->nextDef = defList;
defList = dp;
++nDefs;
}
else if (replace)
{
free (dp->val);
dp->val = NewString (value);
}
}
/*
Replace instances of '$$' with a single ^A.
*/
EncodeEscapes ()
{
Def *dp;
char *p, *q;
for (dp = defList; dp != NULL; dp = dp->nextDef)
{
for (p = q = dp->val; *p != '\0'; p++, q++)
{
*q = *p;
if (*p == '$' && *(p+1) == '$')
{
*q = '\01';
p++;
}
}
*q = '\0';
}
}
/*
Replace instances of ^A with a '$'.
*/
DecodeEscapes ()
{
Def *dp;
char *p;
for (dp = defList; dp != NULL; dp = dp->nextDef)
{
for (p = dp->val; *p != '\0'; p++)
{
if (*p == '\01')
*p = '$';
}
}
}
/*
Find variable reference in variable value. begin is set to
the index of the '$' and end is set to the index of the closing
delimiter. (If either is NULL, it is not set.)
recogEsc is non-zero (true) if '$$' is recognized as an escaped '$'
and skipped. It will be true during initial searching for var refs
and while substituting in source files, false while expanding variable
values.
$v, ${}, ${1}, ${v), etc. are not accepted as valid and are ignored.
*/
FindVarRef (s, beg, end, recogEsc)
char *s;
int *beg, *end, recogEsc;
{
int i;
char c, delim;
i = 0;
for (;;)
{
while ((c = s[i]) != '\0')
{
if (c == '$')
{
if ((c = s[i+1]) == '{' || c == '(')
break;
if (recogEsc && c == '$') /* escaped $ */
++i;
}
++i;
}
if (c == '\0')
return (0); /* no reference */
if (beg != (int *) NULL)
*beg = i;
if (s[++i] == '{')
delim = '}';
else
delim = ')';
++i;
if (!isalpha (s[i])) /* must have at least one char, must */
continue; /* begin with letter */
while ((c = s[++i]) != '\0')
{
if (c == delim) /* find matching delim */
{
if (end != (int *) NULL)
*end = i;
return (1);
}
if (!isalnum (c) && c != '_')
break;
}
}
}
FindAllVarRefs (s)
char *s;
{
char name[BUFSIZ];
int begin, end;
while (FindVarRef (s, &begin, &end, true))
{
/* add with empty value if unknown, */
/* but don't replace value if known */
strncpy (name, s + begin + 2, end - begin - 2);
name[end-begin-2] = '\0';
AddDef (name, "", 0);
s += end + 1;
}
}
/*
Pull out a variable reference, skipping leading '$(' or '${' and
trailing ')' or '}'.
*/
YankVarRef (dst, src, begin, end)
char *dst, *src;
int begin, end;
{
strncpy (dst, src + begin + 2, end - begin - 2);
dst[end - begin - 2] = '\0';
}
/*
Look for variable references in a variable value and expand them
when possible. If a variable is referenced but never defined, it
disappears. If a variable is referenced, but its value has not itself
been fully expanded, defer expansion of value until another pass, at
which time the referenced variable might then be expanded. This
prevents infinite expansions on circular references.
*/
Expand (dp)
Def *dp;
{
Def *dp2;
/* partain: made 4 * BUFSIZ
char buf[BUFSIZ];
*/
char buf[4 * BUFSIZ];
int begin, end, vn;
while (FindVarRef (dp->val, &begin, &end, false))
{
YankVarRef (buf, dp->val, begin, end);
if ((dp2 = FindDefByVar (buf)) == NULL)
{
fprintf (stderr, "Expand error: can't find %s, ", buf);
panic ("quitting");
}
if (!dp2->simple) /* can't expand, give up for now */
break;
dp->val[begin] = '\0';
sprintf (buf, "%s%s%s", dp->val, dp2->val, &(dp->val)[end+1]);
free (dp->val);
dp->val = NewString (buf);
++changes;
}
dp->simple = !FindVarRef (dp->val, NULL, NULL, false);
}
/*
Read through file, performing substitutions for any variables
known from Makefile. If a variable reference is found that is
for an unknown variable, leave it alone, as it may be a reference
to a real variable in a shell script.
*/
DoSub (f)
FILE *f;
{
Def *dp;
char input[BUFSIZ], var[BUFSIZ], *p;
int begin, end;
while (fgets (input, sizeof (input), f) != NULL)
{
p = input;
while (FindVarRef (p, &begin, &end, true))
{
write (1, p, begin); /* write prefix */
YankVarRef (var, p, begin, end);
/* if var is known from makefile, write */
/* value, else just echo the reference */
if ((dp = FindDefByVar (var)) != NULL)
write (1, dp->val, strlen (dp->val));
else
write (1, p + begin, end - begin + 1);
p += end + 1;
}
write (1, p, strlen (p));
}
}
/*
Get next line from Makefile (combines continuation lines into one).
No overflow checking, oops.
*/
GetLine (f, lbuf)
FILE *f;
char *lbuf;
{
char buf[BUFSIZ];
int loop = 1, haveLine = 0, len;
*lbuf = '\0';
while (loop && fgets (buf, sizeof (buf), f) != NULL)
{
loop = 0;
haveLine = 1;
len = strlen (buf);
if (len > 0 && buf[len-1] == '\n') /* trim newline */
buf[--len] = '\0';
if (len > 0 && buf[len-1] == '\\') /* need continuation */
{
loop = 1;
buf[--len] = ' ';
}
strcat (lbuf, buf);
}
return (haveLine);
}
/*
Allocate space for a string, copy the string into it, and return
a pointer to the copy.
*/
char *NewString (s)
char *s;
{
char *p;
if ((p = calloc (1, strlen (s) + 1)) == NULL)
panic ("NewString: out of space");
return (strcpy (p, s));
}
panic (s)
char *s;
{
fprintf (stderr, "%s\n", s);
exit (1);
}
.TH MSUB 1
.SH NAME
msub \- substitute \fImake\fR variables into template to produce script
.SH SYNOPSIS
.B msub
[
.B \-e
] [
.B \-f
.I file
] [
.BI \+R str
] [
.BI \-R str
] [
.IB variable = value
] [
.I file
\&...
]
.SH DESCRIPTION
Makefiles often contain project configuration information specified as
.I make
variables, and it is often desirable to make use of that information in other
files.
.I msub
allows targets to be produced easily
from templates that contain references to those variables.
.I msub
is particularly useful in software projects that use
.IR imake
because virtually all configuration parameters are written into Makefiles
and are thus available for propagation into other files.
.PP
First
.I msub
reads the
.I Makefile
and finds all variable definition lines
of the form ``\fIvar\fR = \fIvalue\fR''.
Then
.I msub
reads any files named on the command line (or the standard input
if none), looks for references to those variables, and replaces the
references with the corresponding variable values.
References to undefined variables are replaced by the empty string.
The result is written to the standard output.
.PP
.I msub
takes the following options:
.TP
.B \-e
Environment variable values override assignments within Makefiles.
Normally assignments within Makefiles override environment variables.
.TP
.BI \-f \ file
By default, variable values are extracted from
.I Makefile
(or
.I makefile
if
.I Makefile
is missing) in the current directory.
If the
.B \-f
.I file
option is given, variable values are extracted from
.I file
instead.
Multiple
.B \-f
options may be specified.
(Note:
.I make
recognizes
.B \-f\ \-
to mean the
.I Makefile
should be read from
.IR stdin .
.I msub
doesn't, because template input may come from
.IR stdin .)
.TP
.BI +R str
.TP
.BI \-R str
The default variable reference indicators within templates
are the same as in Makefiles, i.e.,
``$('' and ``)'', and ``${'' and ``}''.
These can be changed with the
.B +R
and
.B \-R
options, which must be specified in pairs.
.B +R
specifies the string that initiates a variable reference and
.B \-R
specifies the string that terminates it.
Multiple pairs of reference indicators may be given.
.TP
.IB variable = value
Variable definition.
This definition overrides any regular definition for the specified
variable within the makefile itself or in the environment.
.SH EXAMPLES
Suppose you want to produce for a program a help file that indicates the local
e-mail address to which questions may be directed.
If this address is specified as the variable EMAILHELPADDR in the
.IR Makefile ,
you can write a template
.I helpfile.msub
like this:
.in +.5i
.nf
.sp .5v
\&.\|.\|.\|\fIstuff\fP\|.\|.\|.
Please direct questions to ${EMAILHELPADDR}.
\&.\|.\|.\|\fImore stuff\fP\|.\|.\|.
.sp .5v
.fi
.in
Process the template to produce the help file like this:
.in +.5i
.nf
.sp .5v
msub helpfile.msub > helpfile
.sp .5v
.fi
.in
If the
.I Makefile
contains the following lines:
.in +.5i
.nf
.sp .5v
EMAILHELPADDR = postmaster@$(DOMAIN)
DOMAIN = primate.wisc.edu
.sp .5v
.fi
.in
then
.I helpfile
will look like this:
.in +.5i
.nf
.sp .5v
\&.\|.\|.\|\fIstuff\fP\|.\|.\|.
Please direct questions to postmaster@primate.wisc.edu.
\&.\|.\|.\|\fImore stuff\fP\|.\|.\|.
.sp .5v
.fi
.in
.I msub
can produce executable scripts, too.
If the processor for which you're producing
the script allows variable references, make sure you refer to
.I make
variables in the template in a different way than the processor refers to
variables.
For instance, ``${\fIvar\fP}'' is ambiguous in a shell script template \- is
it a reference to a shell variable that you want
.I msub
to leave alone, or to a
.I make
variable for which you want
.I msub
to substitute the variable's value?
To resolve the ambiguity, you can refer to shell variables as ``$\fIvar\fP''
(which the shell recognizes, but
.I msub
doesn't), or you can use different indicators than the defaults to refer to
.I make
variables in the template.
For example, you could use ``@<'' and ``>@'' and refer to variables as
``@<\fIvar\fP>@''.
Suppose you have a simple shell script to print your home directory pathname.
You might write it like this:
.in +.5i
.nf
.sp .5v
#!@<SHELL>@
echo ${HOME}
.sp .5v
.fi
.in
Then you can process the template like this (note that you must quote the
arguments because ``<'' and ``>'' are special to the shell):
.in +.5i
.nf
.sp .5v
msub +R"@<" \-R">@" template > script
.sp .5v
.fi
.in
The
.B +R/\-R
arguments cause
.I msub
to recognize ``@<SHELL>@'' but not ``${HOME}''.
The result looks like this:
.in +.5i
.nf
.sp .5v
#!/bin/sh
echo ${HOME}
.sp .5v
.fi
.in
.SH "WHO-TO-BLAME"
Paul DuBois, dubois@primate.wisc.edu
.SH "BUGS AND RESTRICTIONS"
CC, YACC, etc., have implicit values in
.I make
even if they're not defined in the
.IR Makefile ,
which isn't true for
.IR msub .
However, when
.I msub
is used with
.IR imake ,
this isn't normally a problem since
.IR imake -generated
Makefiles tend to contain explicit definitions for every parameter in the
known universe.
.PP
.I msub
is more restrictive in the variable names it recognizes and
performs substitutions on than
.IR make .
.I make
allows single character names to be referenced as ``$\fIc\fP'', where
.I c
is a letter;
.I msub
doesn't.
.I msub
also ignores
.I make
variables like
``$*'' and ``$@''.
.PP
Recursive references in
.I Makefile
variables aren't expanded.
If a
.I Makefile
contains the definition ``X = ${X}'', X won't be expanded.
Indirectly recursive references aren't expanded, either.
If a
.I Makefile
contains the definitions ``X = ${Y}'' and ``Y = ${X}'',
neither X nor Y will be expanded.
(These actually aren't bugs in
.IR msub ,
they're problems in the
.IR Makefile .)
.PP
A comment at the end of a variable assignment line become part of the
variable's value.
If
.I Makefile
contains the following line:
.in +.5i
.nf
.sp .5v
SHELL = /bin/shell # default shell
.sp .5v
.fi
.in
then
.I msub
takes the value of SHELL to be ``/bin/shell # default shell''.
.SH
Theory of Operation
.LP
.I msub
reads the makefile in the current directory (or the file named after the
.B \-f
flag) and finds all variable definitions and references.
Definitions are lines of the form ``\fIname\fR = \fIvalue\fR''.
References are instances of ``$(\fIvar\fR)'' or ``${\fIvar\fR}'', either in
the value part of definition lines, or any part of dependency or command lines.
Then variable values are examined to eliminate references to other variables
by replacing the referenced variables with their values.
Variables which are referenced but never defined have a value of the empty
string.
.LP
Then
.I msub
reads any files named on the command line (or
.I stdin
if none) and looks for instances of ``$(\fIvar\fR)'' or ``${\fIvar\fR}'',
where
.I var
is known from the makefile, and substitutes the value of
.I var .
.SH
Purpose
.LP
The motivation for
.I msub
is that project configuration information is often contained in makefiles
and it is often desirable to make use of that information.
.I msub
allows files to be produced from templates that contain references
to makefile variables.
In this sense it is similar to the way that the C preprocessor
.I cpp
uses #define statements in header files; the
makefile is the implicit ``header'' file and definition lines are the
``#define'' statements.
.LP
Although it is possible to use
.I cpp
to perform substitutions on templates, a disadvantage of
.I cpp
is that it modifies its source in other ways (eg., lines beginning with
``#'' are special and comments (``/* ... */'') are deleted.
Also, it is necessary to pass a -D flag for every symbol to be substituted.
Similarly, for C programs it can be simpler to use
.I msub
to produce a single configuration header file from a template
than to pass a lot of -D flags to each invocation of
.I cc .
.LP
.I msub
performs no textual modification besides substitution of references
to makefile variables; it leaves everything else alone.
In particular,
references to variables not known from the makefile are left untouched.
This allows
.I msub
to be used to produce shell scripts that use variables of
their own.
.LP
.I msub
is particularly useful in software projects that use
.I imake .
Virtually all configuration information is propagated into makefiles
thus produced, so it is available for propagation into other files using
.I msub .
.SH
Problems
.LP
If
.I msub
is used to produce shell scripts, it is unwise for the script to use names
for its own variables that are the same as those in the makefile.
The problem can be avoided entirely if, for instance, variables in makefiles
are all uppercase and the scripts' own variables are lowercase or mixed case.
.LP
Circular references in makefile variables are not expanded.
If a makefile contains the definitions ``X = ${Y}'' and ``Y = ${X}'',
neither X nor Y will be expanded.
But that is really a problem with the makefile, not
.I msub .
.LP
.I make
recognizes
.B \-f\ \-
to mean the makefile should be read from
.I stdin .
.I msub
does not.
.LP
CC, YACC, etc., have implicit values in
.I make
even if not defined in the makefile.
Not true in
.I msub .
.LP
.I msub
is more restrictive in the variable names that it recognizes and
performs substitutions on.
Names that do not begin with a letter and
that contain any characters besides letters, digits and underscores are
ignored.
This means that variable names such as ``${1}'' are not recognized;
the reason for this is that shell scripts often use such variables and
they should be left untouched.
Also,
``$\fIx\fR'' is legal in
.I make
when
.I x
is a single character, but
.I msub
ignores such references.
.i msub
.LP
.I make
allows variables to be defined on the command line.
.I msub
does not.
.LP
If you try to be tricky with your variable references, e.g., by using
recursive references, you may get burned.
.LP
There is no way to prevent substitution, in a source file, of a reference to
a variable known from the makefile.
foo = ${bar}
bar = ${ugh}
ugh = Huh?
space = x
a = ${wildcard x}
c : ${xyz}
xyz = abc
dollar = $$
doubledollar = ${dollar}${dollar}
X1 = Y
X2 = 1
Y1 = y1
Y2 = ${${X1}${X2}}
foo = Huh?
bar = Huh?
ugh = Huh?
xy
${wildcard x}
This (abc) should be abc.
This should be a single dollar: $
This should be a double dollar: $$
So should this: $$
foo = ${foo}
bar = ${bar}
ugh = ${ugh}
${space}y
${a}
The stuff in parens should be "abc": (${xyz})
This should be a single dollar-sign: ${dollar}
This should be a double dollar-sign: ${dollar}${dollar}
So should this: ${doubledollar}
This should say "y1": ${Y2}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment