[net.sources] buildmake: a preprocessor to provide extended syntax for makefiles

mann@Navajo.ARPA (10/06/84)

#! /bin/sh
: This is a shar archive.  Extract with sh, not csh.
echo x - buildmake.1
cat > buildmake.1 << '926!Funky!Stuff!'
.TH BUILDMAKE 1  "4 April 1984"
.SU
.SH NAME
buildmake \- preprocessor to provide extended syntax for makefiles
.SH SYNOPSIS
.B buildmake [
-f filename -Dx1=y1 -Dx2=y2 ...
.B ]
.SH DESCRIPTION
.I Buildmake
is a preprocessor that translates "buildfiles" into
makefiles for use by the
.IR make (1)
program.
A buildfile has the same syntax as a makefile, with the addition of
two features.  A line of the form "#include \fIfilename\fP"
causes the named file to be inserted in the output, replacing
the #include directive.
An #ifdef/#else/#endif construct is also available, allowing
sections of a buildfile to be conditionally included in the constructed
makefile depending on whether it has defined 
a given symbol at the point the #ifdef is seen, as shown below.
.PP
.IP
.nf
NAME1=yes
#ifdef NAME1
This will be included in the output
#else NAME1
This will not be included
#endif NAME1
#ifdef NAME2
This will not be included unless NAME2 is defined elsewhere
#endif NAME2
.fi
.PP
The \-D command line option is used to define symbols on the command line.
The option -DNAME=value causes the line "NAME=value"
to be inserted in the constructed makefile
and causes NAME to be considered "defined" in subsequent #ifdef statements.
.PP
The \-f option is used to specify the name of the input file, which
defaults to "buildfile."  The output file is always named "makefile."
.SH "SEE ALSO"
make(1)
.SH AUTHOR
Marvin Theimer, Stanford.
926!Funky!Stuff!
echo x - buildfile
cat > buildfile << '926!Funky!Stuff!'
INSTALLDIR = /usr/stanford/bin

buildmake: buildmake.c
	cc -o buildmake buildmake.c

install:
	install -q -s buildmake $(INSTALLDIR)
	install -q -c buildmake.1 /usr/man/man1/buildmake.1

clean:
	rm -f *BAK *CKP buildmake
cleanbak:
	rm -f *BAK *CKP

build:
	buildmake

xbuild:
	buildmake -DX=1
926!Funky!Stuff!
echo x - buildmake.c
cat > buildmake.c << '926!Funky!Stuff!'
/*
 * Program to output a makefile from a buildfile.
 *
 * Written by Marvin Theimer 6/83
 *
 * Modified -- RJN -- 9/20/83
 *	- Added '-f' option
 *	- Removed the need for "buildmake.sh" by having buildmake
 *	  create the temporary makefile and link it to "makefile"
 *	  upon success.
 * Marvin Theimer, 11/25/83
 *	Changed MaxLine to 200 from 82 to allow handling of long input
 *	lines.  Fixed fprintf statement in ErrorExit to contain the correct
 *	number of arguments.  Added a string arg. to ErrorExit to allow
 *	more informational error exits.  ErrorExit now unlinks the tmp 
 *	file when exitting.
 * Marvin Theimer, 1/10/83
 *	Added #include capability.
 */

#include <stdio.h>
#include <errno.h>

#define MaxLine 200
#define MaxBf 32

#define BlankType 0
#define CommentType 1
#define MacroDefnType 2
#define IfdefType 3
#define ElseType 4
#define EndifType 5
#define IncludeType 6
#define OtherType 7

struct MacroRec
  {
    char *macName;
    char *macDefn;
    struct MacroRec *next;
  };

char TmpMakeFileName[] = "BUILDXXXXXXXX";	/* X's are for mktemp() */
FILE *Bf[MaxBf], *fopen();
int CurrentBf = 0;
char Line[MaxLine];
int LineNo = 0;
struct MacroRec *Macros = NULL;

char *GetLine();
char *substr();

extern char *malloc();
extern int errno;

main(argc, argv)
    int argc;
    char **argv;
  {
    int nArg = 1;
    char *line;
    int indx;
    char *BuildFileName = "buildfile";

    /* Reopen stdout as "makefile" */
    if ( freopen( mktemp( TmpMakeFileName ), "w", stdout ) == 0 )
      {
	fprintf( stderr, "Cannot open for writing " );
	perror( TmpMakeFileName );
	exit( errno );
      }
    /*
     * Process input arguments to build program.
     */

    while (nArg < argc)
      {
	
        if ( argv[nArg][0] == '-' ) 
	    switch (argv[nArg][1] )
	      {
                case 'D':
	            strcpy(Line, &(argv[nArg][2]) );
	            indx = strlen(Line);
	            Line[indx++] = '\n';/* Make it look like an input line. */
	            Line[indx] = '\0';
	            ProcessStatement(Line);
		    break;

	        case 'f':
		    nArg++;
	            BuildFileName = argv[nArg];
	      	    break;
	 
	      }
	nArg++;
      }

    /* 
     * Open the buildfile for reading. 
     */


    Bf[0] = fopen(BuildFileName, "r");
    if (Bf[0] == NULL)
      {	
        perror( BuildFileName );
	exit( errno );
      }

    /*
     * Print out a warning about automated output.
     */

    printf("# WARNING: DO NOT EDIT.\n");
    printf("# THIS FILE WAS AUTOMATICALLY GENERATED USING THE\n");
    printf("# BUILDMAKE PROGRAM.\n\n");

    /*
     * Parse the file.
     */

    line = GetLine();
    while (line != NULL)
      {
        ProcessStatement(line);
	line = GetLine();
      }

    /*
     * Close the buildfile.
     */

    fclose( Bf[0] );

    if ( link( TmpMakeFileName, "makefile" ) != 0 )
    if ( link( TmpMakeFileName, "makefile" ) != 0 )
        if ( errno != EEXIST || unlink( "makefile" ) != 0 
	     || link( TmpMakeFileName, "makefile" ) != 0 )
	  {
	    fprintf( stderr, "Error linking to " );
	    perror( "makefile" );
	    fprintf( stderr, "Makefile exists as \"%s\"\n", TmpMakeFileName );
	    exit( errno );
	  }
	  
    if ( unlink( TmpMakeFileName ) != 0 )
      {
	fprintf( stderr, "Cannot unlink " );
        perror( TmpMakeFileName );
	exit( errno );
      }
    
  }


LineType(line)
    char *line;
  {
    int t;
    char *p = line;

    while ((*p == ' ') || (*p == '\t'))
	p++;
    if (*p == '\n')
	return(BlankType);
    if (*p == '#')
      {
	if (substr(p, "#ifdef") != NULL)
	    return(IfdefType);
	else if (substr(p, "#else") != NULL)
	    return(ElseType);
	else if (substr(p, "#endif") != NULL)
	    return(EndifType);
	else if (substr(p, "#include") != NULL)
	    return(IncludeType);
	else
	    return(CommentType);
      }
    if (MacroDefn(p))
	return(MacroDefnType);
    return(OtherType);
  }


MacroDefn(line)
    char *line;			/* Assumed to be pointing to first
				   non-white-space character in the line. */
  {
    char *p = line;

    while ((*p != '\n') && (*p != '='))
	p++;
    if (*p == '=')
      {
        AddMacroDefn(line, p);
	return(1);
      }
    else
	return(0);
  }


AddMacroDefn(line, eqPtr)
    char *line;			/* Assumes line points to start of macro
				   defn.  I.e. leading white space is gone. */
    char *eqPtr;		/* Pts. to '=' sign. */
  {
    struct MacroRec *recPtr;
    char *strPtr, *str1Ptr;
    char *p = eqPtr - 1, *p1 = eqPtr + 1, *p2 = line + strlen(line) - 1;

    recPtr = (struct MacroRec *) malloc(sizeof(struct MacroRec));
    while ((*p == ' ') || (*p == '\t'))
	p--;
    strPtr = malloc(p+1 - line + 1);
    while ((*p1 == ' ') || (*p1 == '\t'))
	p1++;
    while ((*p2 == '\n') && (*p2 == ' ') && (*p2 == '\t'))
	p2--;
    str1Ptr = malloc(p2+1 - p1 + 1);
    if ((recPtr == NULL) || (strPtr == NULL) || (str1Ptr == NULL))
      {
	fprintf(stderr, "Ran out of memory!\n");
	exit(1);
      }
    recPtr->macName = strPtr;
    recPtr->macDefn = str1Ptr;
    while (line <= p)
	*strPtr++ = *line++;
    *strPtr = '\0';
    while (p1 <= p2)
	*str1Ptr++ = *p1++;
    *str1Ptr = '\0';
    recPtr->next = Macros;
    Macros = recPtr;
  }


Defined(line)
    char *line;
  {
    char *ptr, *ptr1, buf[MaxLine];
    struct MacroRec *p = Macros;

    strcpy(buf, line);
    ptr = substr(buf, "#ifdef") + 6;
    while ((*ptr == ' ') || (*ptr == '\t'))
	ptr++;
    ptr1 = ptr;
    while ((*ptr1 != ' ') && (*ptr1 != '\t') && (*ptr1 != '\n') &&
    		(*ptr1 != '\0'))
	ptr1++;
    *ptr1 = '\0';
    while (p != NULL)
      {
	if (strcmp(p->macName, ptr) == 0)
	    return(1);
	p = p->next;
      }
    return(0);
  }


ProcessStatement(line)
    char *line;
  {
        switch (LineType(line))
	  {
	    case IfdefType:
		ProcessIfdef(line);
		break;
	    case ElseType:
	    case EndifType:
		ErrorExit("Unexpected #else of #endif encountered");
	    case IncludeType:
	        ProcessInclude(line);
		break;
	    case BlankType:
	    case CommentType:
	    case MacroDefnType:	/* Note: Macro defn. is automatically put in
				   local table. */
	    default:
		printf("%s", line);
		break;
	  }
  }


ProcessIfdef(line)
    char *line;
  {
    int inclFlag, state = 0;

    inclFlag = Defined(line);
    line = GetLine();
    while (line != NULL)
      {
	switch (LineType(line))
	  {
	    case ElseType:
		if (state != 0)
		  {
		    ErrorExit("Unexpected #else encountered");
		  }
		state = 1;
		inclFlag = !inclFlag;
		break;
	    case EndifType:
		return;
	    default:
		if (inclFlag)
		    ProcessStatement(line);
		break;
	  }
	line = GetLine();
      }
    ErrorExit("Missing #endif");
  }


ProcessInclude(line)
    char *line;
  {
    char *includeFileName, *ptr;

    CurrentBf++;
    if (CurrentBf == MaxBf)
      {
	ErrorExit("Include files too deeply");
      }

    /* Find name of include file. */
    includeFileName = substr(line, "#include");
    includeFileName += 8;
    while ((*includeFileName == ' ') || (*includeFileName == '\t'))
      {
	includeFileName++;
      }
    /* Delimit the include file name. */
    ptr = includeFileName;
    while ((*ptr != ' ') && (*ptr != '\t') && (*ptr != '\n'))
      {
	ptr++;
      }
    *ptr = '\0';

    Bf[CurrentBf] = fopen(includeFileName, "r");
    if (Bf[CurrentBf] == NULL)
      {	
        perror( includeFileName );
	exit( errno );
      }

    line = GetLine();
    while (line != NULL)
      {
        ProcessStatement(line);
	line = GetLine();
      }

    fclose( Bf[CurrentBf] );
    CurrentBf--;
  }


char *GetLine()
  {
    char *ptr;

    LineNo++;
    ptr = fgets(Line, MaxLine, Bf[CurrentBf]);
    return(ptr);
  }


char *substr(s, t)
    char *s, *t;
  {
    int i, j, k;

    for (i = 0; s[i] != '\0'; i++)
      {
	for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++)
	    ;
	if (t[k] == '\0')
	    return(&s[i]);
      }
    return(NULL);
  }


ErrorExit(s)
    char *s;
  {
    fprintf(stderr, "ERROR on line %d.  %s.\n", LineNo, s);
    unlink(TmpMakeFileName);
    exit(errno);
  }
926!Funky!Stuff!