/* $XConsortium: imake.c /main/cde1_maint/2 1995/10/10 18:12:45 drk $ */

/*****************************************************************************\
 *                                                                           *
 *                                Porting Note                               *
 *                                                                           *
 * Add the value of BOOTSTRAPCFLAGS to the cpp_argv table so that it will be *
 * passed to the template file.                                              *
 *                                                                           *
\*****************************************************************************/

/*
 * 
 * Copyright 1985, 1986, 1987 by the Massachusetts Institute of Technology
 * 
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 * 
 * Original Author:
 *  Todd Brunhoff
 *  Tektronix, inc.
 *  While a guest engineer at Project Athena, MIT
 *
 * imake: the include-make program.
 *
 * Usage: imake [-Idir] [-Ddefine] [-T] [-f imakefile ] [-s] [-e] [-v] [make flags]
 *
 * Imake takes a template makefile (Imake.tmpl) and runs cpp on it
 * producing a temporary makefile in /tmp.  It then runs make on
 * this pre-processed makefile.
 * Options:
 *      -D  define.  Same as cpp -D argument.
 *      -I  Include directory.  Same as cpp -I argument.
 *      -T  template.  Designate a template other
 *          than Imake.tmpl
 *      -s[F]   show.  Show the produced makefile on the standard
 *          output.  Make is not run is this case.  If a file
 *          argument is provided, the output is placed there.
 *              -e[F]   execute instead of show; optionally name Makefile F
 *      -v  verbose.  Show the make command line executed.
 *
 * Environment variables:
 *      
 *      IMAKEINCLUDE    Include directory to use in addition to "."
 *      IMAKECPP    Cpp to use instead of /lib/cpp
 *      IMAKEMAKE   make program to use other than what is
 *              found by searching the $PATH variable.
 * Other features:
 *  imake reads the entire cpp output into memory and then scans it
 *  for occurences of "@@".  If it encounters them, it replaces it with
 *  a newline.  It also trims any trailing white space on output lines
 *  (because make gets upset at them).  This helps when cpp expands
 *  multi-line macros but you want them to appear on multiple lines.
 *
 *  The macros MAKEFILE and MAKE are provided as macros
 *  to make.  MAKEFILE is set to imake's makefile (not the constructed,
 *  preprocessed one) and MAKE is set to argv[0], i.e. the name of
 *  the imake program.
 *
 * Theory of operation:
 *   1. Determine the name of the imakefile from the command line (-f)
 *  or from the content of the current directory (Imakefile or imakefile).
 *  Call this <imakefile>.  This gets added to the arguments for
 *  make as MAKEFILE=<imakefile>.
 *   2. Determine the name of the template from the command line (-T)
 *  or the default, Imake.tmpl.  Call this <template>
 *   3. Start up cpp an provide it with three lines of input:
 *      #define IMAKE_TEMPLATE      " <template> "
 *      #define INCLUDE_IMAKEFILE   < <imakefile> >
 *      #include IMAKE_TEMPLATE
 *  Note that the define for INCLUDE_IMAKEFILE is intended for
 *  use in the template file.  This implies that the imake is
 *  useless unless the template file contains at least the line
 *      #include INCLUDE_IMAKEFILE
 *   4. Gather the output from cpp, and clean it up, expanding @@ to
 *  newlines, stripping trailing white space, cpp control lines,
 *  and extra blank lines.  This cleaned output is placed in a
 *  temporary file.  Call this <makefile>.
 *   5. Start up make specifying <makefile> as its input.
 *
 * The design of the template makefile should therefore be:
 *  <set global macros like CFLAGS, etc.>
 *  <include machine dependent additions>
 *  #include INCLUDE_IMAKEFILE
 *  <add any global targets like 'clean' and long dependencies>
 */

/* The following extra configuration file has been added:
   OSVersion.tmpl
   This file, if present, is included before anything else and sets
   OSMajorVersion and OSMinorVersion.
*/
/* The following defines are used in any directory containing an
   Imakefile to allow local users to override global defines and macros.
   This program will include the following files if they are present.
   localdefines is included just after OSVersion.tmpl and should be used to 
   set defines.
   localmacros is included after everything else and should be used to
   set make macro values.
   admindefines is included after localdefines and should be used by the
   build administrator to changed defines in a directory.
   adminmacros is included just before localmacros and should be used by
   the build administrator to set make macro values.
 */
/* The following extra configuration file has been added:
   treedefines
   This file, if present, is included before Imake.tmpl
   and sets cpp  macros for a whole tree. This is for local use.
   Do not check this file in.
*/
#ifdef __hpux
#include "OSVersion.tmpl"
#endif

#include <stdio.h>
#if (defined(SVR4) || defined(_IBMR2) || defined(SYSV386)) && defined(__STDC__)
FILE * fdopen();
#endif
#include <ctype.h>
#include "Xosdefs.h"
#ifndef X_NOT_POSIX
#define _POSIX_SOURCE
#endif
#include <sys/types.h>
#include <fcntl.h>
#ifdef X_NOT_POSIX
#include <sys/file.h>
#else
#include <unistd.h>
#endif
#if defined(X_NOT_POSIX) || defined(_POSIX_SOURCE)
#include <signal.h>
#else
#define _POSIX_SOURCE
#include <signal.h>
#undef _POSIX_SOURCE
#endif
#include <sys/stat.h>
#ifndef X_NOT_POSIX
#ifdef _POSIX_SOURCE
#include <sys/wait.h>
#else
#define _POSIX_SOURCE
#include <sys/wait.h>
#undef _POSIX_SOURCE
#endif
#define waitCode(w) WEXITSTATUS(w)
#define waitSig(w)  WTERMSIG(w)
typedef int     waitType;
#else /* X_NOT_POSIX */
#ifdef SYSV
#define waitCode(w) (((w) >> 8) & 0x7f)
#define waitSig(w)  ((w) & 0xff)
typedef int     waitType;
#else /* SYSV */
#include <sys/wait.h>
#define waitCode(w) ((w).w_T.w_Retcode)
#define waitSig(w)  ((w).w_T.w_Termsig)
typedef union wait  waitType;
#endif
#ifndef WIFSIGNALED
#define WIFSIGNALED(w) waitSig(w)
#endif
#ifndef WIFEXITED
#define WIFEXITED(w) waitCode(w)
#endif
#endif /* X_NOT_POSIX */
#ifndef X_NOT_STDC_ENV
#include <stdlib.h>
#else
char *malloc(), *realloc();
void exit();
#endif
#if defined(macII) && !defined(__STDC__)  /* stdlib.h fails to define these */
char *malloc(), *realloc();
#endif /* macII */
#ifdef X_NOT_STDC_ENV
extern char *getenv();
#endif
#include <errno.h>
extern int  errno;
#include "imakemdep.h"


#define TRUE        1
#define FALSE       0

#ifdef FIXUP_CPP_WHITESPACE
int InRule = FALSE;
#endif

/*
 * Some versions of cpp reduce all tabs in macro expansion to a single
 * space.  In addition, the escaped newline may be replaced with a
 * space instead of being deleted.  Blech.
 */
#ifdef FIXUP_CPP_WHITESPACE
void KludgeOutputLine(), KludgeResetRule();
#else
#define KludgeOutputLine(arg)
#define KludgeResetRule()
#endif

typedef unsigned char   boolean;

#ifndef DEFAULT_CPP
# ifdef USE_CC_E
#   if (defined(__hpux) && ( OSMajorVersion >= 10 ))
#     define DEFAULT_CPP "/opt/ansic/bin/cc"
#   else
#     define DEFAULT_CPP "/bin/cc"
#   endif
# else
#   ifdef CPP_PROGRAM
#    define DEFAULT_CPP CPP_PROGRAM
#   else
#    define DEFAULT_CPP "/lib/cpp"
#   endif
# endif
#endif


char *cpp = DEFAULT_CPP;

char    *tmpMakefile    = "/tmp/Imf.XXXXXX";
char    *tmpImakefile    = "/tmp/IIf.XXXXXX";
char    *make_argv[ ARGUMENTS ] = { "make" };

int make_argindex;
int cpp_argindex;
char    *make = NULL;
char    *Imakefile = NULL;
char    *Makefile = "Makefile";
char    *Template = "Imake.tmpl";
char    *Localdefs = "localdefines";
char    *Localmacros = "localmacros";
char    *Admindefs = "admindefines";
char    *Adminmacros = "adminmacros";
/* The following is used to get the correct path for included files
 * such as OSVersion.tmpl or BuildTree.name.
 * You must grab the TOPDIR passed to imake and append the appropriate
 * path.
 */
char    *ConfigDir = "/config/";
char     TopConfigPath[256];
char     OSVersionFilePath[256];
char     TreeTemplatePath[256];
char     BuildTreeNameFilePath[256];
char     *GlobalDefinesFile;
char     *GlobalMacrosFile;
char    *BuildTreeNameFile = "BuildTree.name";
char    *OSVersionFile = "OSVersion.tmpl";
char    *TreeTemplateFile = "treedefines";
/* The following set the macros for X11R4,R5,etc */
char    *R3macro = "#define Release3 3";
char    *R4macro = "#define Release4 4";
char    *R5macro = "#define Release5 5";
char    *R6macro = "#define Release6 6";

/* The following macros are for determining which HP architecture
 * You are running imake on.
 * This will allow you to ifdef for architectures inside Imakefiles,
 * config template files, etc.
 */
#ifdef __hp9000s700
   char    *HPSystemMacro = "#define __hp9000s700";
#else 
#  ifdef __hp9000s300
     char    *HPSystemMacro = "#define __hp9000s300";
#  else
#    ifdef __hp9000s800
       char    *HPSystemMacro = "#define __hp9000s800";
#    endif
#  endif
#endif

char    *program;
char    *FindImakefile();
char    *ReadLine();
char    *CleanCppInput();
char    *Strdup();
char    *Emalloc();

boolean verbose = FALSE;
boolean show = TRUE;

main(argc, argv)
    int argc;
    char    **argv;
{
    FILE    *tmpfd;
    char    makeMacro[ BUFSIZ ];
    char    makefileMacro[ BUFSIZ ];
    program = argv[0];
    init();
    SetOpts(argc, argv);
#ifdef USE_CC_E
    AddCppArg("-");
#endif

    Imakefile = FindImakefile(Imakefile);
    if (Makefile)
        tmpMakefile = Makefile;
    else {
        tmpMakefile = Strdup(tmpMakefile);
        (void) mktemp(tmpMakefile);
    }
    AddMakeArg("-f");
    AddMakeArg( tmpMakefile );
    sprintf(makeMacro, "MAKE=%s", program);
    AddMakeArg( makeMacro );
    sprintf(makefileMacro, "MAKEFILE=%s", Imakefile);
    AddMakeArg( makefileMacro );

    if ((tmpfd = fopen(tmpMakefile, "w+")) == NULL)
        LogFatal("Cannot create temporary file %s.", tmpMakefile);

    cppit(Imakefile, Template, tmpfd, tmpMakefile);

    if (show) {
        if (Makefile == NULL)
            showit(tmpfd);
    } else
        makeit();
    wrapup();
    exit(0);
}

showit(fd)
    FILE    *fd;
{
    char    buf[ BUFSIZ ];
    int red;

    fseek(fd, 0, 0);
    while ((red = fread(buf, 1, BUFSIZ, fd)) > 0)
        fwrite(buf, red, 1, stdout);
    if (red < 0)
        LogFatal("Cannot write stdout.", "");
}

wrapup()
{
    if (tmpMakefile != Makefile)
        unlink(tmpMakefile);
    unlink(tmpImakefile);
}

#ifdef SIGNALRETURNSINT
int
#else
void
#endif
catch(sig)
    int sig;
{
    errno = 0;
    LogFatalI("Signal %d.", sig);
}

/*
 * Initialize some variables.
 */
init()
{
    char    *p;

    make_argindex=0;
    while (make_argv[ make_argindex ] != NULL)
        make_argindex++;
    cpp_argindex = 0;
    while (cpp_argv[ cpp_argindex ] != NULL)
        cpp_argindex++;

    /*
     * See if the standard include directory is different than
     * the default.  Or if cpp is not the default.  Or if the make
     * found by the PATH variable is not the default.
     */
    if (p = getenv("IMAKEINCLUDE")) {
        if (*p != '-' || *(p+1) != 'I')
            LogFatal("Environment var IMAKEINCLUDE %s\n",
                "must begin with -I");
        AddCppArg(p);
        for (; *p; p++)
            if (*p == ' ') {
                *p++ = '\0';
                AddCppArg(p);
            }
    }
    if (p = getenv("IMAKECPP"))
        cpp = p;
    if (p = getenv("IMAKEMAKE"))
        make = p;

    if (signal(SIGINT, SIG_IGN) != SIG_IGN)
        signal(SIGINT, catch);
}

AddMakeArg(arg)
    char    *arg;
{
    errno = 0;
    if (make_argindex >= ARGUMENTS-1)
        LogFatal("Out of internal storage.", "");
    make_argv[ make_argindex++ ] = arg;
    make_argv[ make_argindex ] = NULL;
}

AddCppArg(arg)
    char    *arg;
{
    errno = 0;
    if (cpp_argindex >= ARGUMENTS-1)
        LogFatal("Out of internal storage.", "");
    cpp_argv[ cpp_argindex++ ] = arg;
    cpp_argv[ cpp_argindex ] = NULL;
}

SetOpts(argc, argv)
    int argc;
    char    **argv;
{
    errno = 0;
    /*
     * Now gather the arguments for make
     */
    for(argc--, argv++; argc; argc--, argv++) {
        /*
         * We intercept these flags.
         */
        if (argv[0][0] == '-') {
        if (argv[0][1] == 'D') {
                /* this is for OSVersion.tmpl,etc.  include */
        /* need to grab the top path */
            if (strncmp(&argv[0][2],"TOPDIR=",7) == 0)
                strcpy(TopConfigPath,&argv[0][9]);
            AddCppArg(argv[0]);
        } else if (argv[0][1] == 'I') {
            AddCppArg(argv[0]);
        } else if (argv[0][1] == 'f') {
            if (argv[0][2])
            Imakefile = argv[0]+2;
            else {
            argc--, argv++;
            if (! argc)
                LogFatal("No description arg after -f flag\n", "");
            Imakefile = argv[0];
            }
        } else if (argv[0][1] == 's') {
            if (argv[0][2])
            Makefile = ((argv[0][2] == '-') && !argv[0][3]) ?
                NULL : argv[0]+2;
            else {
            argc--, argv++;
            if (!argc)
                LogFatal("No description arg after -s flag\n", "");
            Makefile = ((argv[0][0] == '-') && !argv[0][1]) ?
                NULL : argv[0];
            }
            show = TRUE;
        } else if (argv[0][1] == 'e') {
           Makefile = (argv[0][2] ? argv[0]+2 : NULL);
           show = FALSE;
        } else if (argv[0][1] == 'T') {
            if (argv[0][2])
            Template = argv[0]+2;
            else {
            argc--, argv++;
            if (! argc)
                LogFatal("No description arg after -T flag\n", "");
            Template = argv[0];
            }
        } else if (argv[0][1] == 'v') {
            verbose = TRUE;
        } else
            AddMakeArg(argv[0]);
        } else
        AddMakeArg(argv[0]);
    }
}

char *FindImakefile(Imakefile)
    char    *Imakefile;
{
    int fd;

    if (Imakefile) {
        if ((fd = open(Imakefile, O_RDONLY)) < 0)
            LogFatal("Cannot open %s.", Imakefile);
    } else {
        if ((fd = open("Imakefile", O_RDONLY)) < 0)
            if ((fd = open("imakefile", O_RDONLY)) < 0)
                LogFatal("No description file.", "");
            else
                Imakefile = "imakefile";
        else
            Imakefile = "Imakefile";
    }
    close (fd);
    return(Imakefile);
}

LogFatalI(s, i)
    char *s;
    int i;
{
    /*NOSTRICT*/
    LogFatal(s, (char *)i);
}

LogFatal(x0,x1)
    char *x0, *x1;
{
    static boolean  entered = FALSE;

    if (entered)
        return;
    entered = TRUE;

    fprintf(stderr, "%s: ", program);
    if (errno)
        fprintf(stderr, "%s: ", strerror(errno));
    fprintf(stderr, x0,x1);
    fprintf(stderr, "  Stop.\n");
    wrapup();
    exit(1);
}

showargs(argv)
    char    **argv;
{
    for (; *argv; argv++)
        fprintf(stderr, "%s ", *argv);
    fprintf(stderr, "\n");
}

cppit(Imakefile, template, outfd, outfname)
    char    *Imakefile;
    char    *template;
    FILE    *outfd;
    char    *outfname;
{
    FILE    *pipeFile;
    int pid, pipefd[2];
    waitType    status;
    char    *cleanedImakefile;

    /*
     * Get a pipe.
     */
    if (pipe(pipefd) < 0)
        LogFatal("Cannot make a pipe.", "");

    /*
     * Fork and exec cpp
     */
    pid = fork();
    if (pid < 0)
        LogFatal("Cannot fork.", "");
    if (pid) {  /* parent */
        close(pipefd[0]);
        cleanedImakefile = CleanCppInput(Imakefile);
        if ((pipeFile = fdopen(pipefd[1], "w")) == NULL)
            LogFatalI("Cannot fdopen fd %d for output.", pipefd[1]);
        /* place the macros for r4, r5 etc into the Makefile */ 
        fprintf(pipeFile, "%s\n",R3macro);
        fprintf(pipeFile, "%s\n",R4macro);
        fprintf(pipeFile, "%s\n",R5macro);
        fprintf(pipeFile, "%s\n",R6macro);
	    /* place the system macro for hp */
#ifdef __hpux
        fprintf(pipeFile, "%s\n",HPSystemMacro);
#endif
            /* Build the Build Tree name file Path */
        strcpy(BuildTreeNameFilePath,TopConfigPath);
    strcat(BuildTreeNameFilePath,ConfigDir);
    strcat(BuildTreeNameFilePath,BuildTreeNameFile);
            /* include BuildTree.name if present */
        if (!access(BuildTreeNameFilePath,R_OK))
        {
            fprintf(pipeFile, "#define BUILDTREENAMEFILE\t\"%s\"\n",
                BuildTreeNameFile);
            fprintf(pipeFile, "#include BUILDTREENAMEFILE\n");
        }
            /* include OSVersion.tmpl if present */
        strcpy(OSVersionFilePath,TopConfigPath);
        strcat(OSVersionFilePath,ConfigDir);
    strcat(OSVersionFilePath,OSVersionFile);
            /* include OSVersion.tmpl if present */
        if (!access(OSVersionFilePath,R_OK))
        {
            fprintf(pipeFile, "#define OSVERSIONFILE\t\"%s\"\n",
                OSVersionFile);
            fprintf(pipeFile, "#include OSVERSIONFILE\n");
        }
        else /* warn user that he should set OSVersion.tmpl */
        {
            printf("\nWarning! OSMajorVersion and OSMinorVersion defaulting to some\n\tUnknown default.  These should be set on a per-tree basis in \n\tconfig/OSVersion.tmpl. If you do not want to set \n\tOSMajorVersion and OSMinorVersion, create an empty \n\tOSVersion.tmpl in config.\n");
        }
            
    /* stuff for local defines */
        if (!access(Localdefs,R_OK))
        {
            printf("\nWarning! You are using a local file, %s, \n",Localdefs);
            printf("         to override global cpp macros.\n");
            fprintf(pipeFile, "#define LOCALDEFS\t\"%s\"\n",
                Localdefs);
            fprintf(pipeFile, "#include LOCALDEFS\n");
        }
        if (GlobalDefinesFile = getenv("IMAKE_DEFINES_FILE"))
        {
            if (!access(GlobalDefinesFile,R_OK)) {
                printf("\nWarning! The environment variable IMAKE_DEFINES_FILE is set:\n");
                printf("\tIncluding the file %s.\n",GlobalDefinesFile);
                fprintf(pipeFile, "#define GLOBALDEFSFILE\t\"%s\"\n",
                    GlobalDefinesFile);
                fprintf(pipeFile, "#include GLOBALDEFSFILE\n");
            }
        }
        if (!access(Admindefs,R_OK))
        {
            printf("\nWarning! There exists a file overwriting global cpp macros, %s.\n",Admindefs);
            fprintf(pipeFile, "#define ADMINDEFS\t\"%s\"\n",
                Admindefs);
            fprintf(pipeFile, "#include ADMINDEFS\n");
        }
            /* include treedefines if present */
        strcpy(TreeTemplatePath,TopConfigPath);
        strcat(TreeTemplatePath,ConfigDir);
        strcat(TreeTemplatePath,TreeTemplateFile);
        if (!access(TreeTemplatePath,R_OK))
        {
            fprintf(pipeFile, "#define TREETEMPLATEFILE\t\"%s\"\n",
                TreeTemplateFile);
            fprintf(pipeFile, "#include TREETEMPLATEFILE\n");
        }
        fprintf(pipeFile, "#define IMAKE_TEMPLATE\t\"%s\"\n",
            template);
        fprintf(pipeFile, "#define INCLUDE_IMAKEFILE\t<%s>\n",
            cleanedImakefile);
        fprintf(pipeFile, "#include IMAKE_TEMPLATE\n");
        if (!access(Adminmacros,R_OK))
        {
            printf("\nWarning! There exists a file overwriting global make macros, %s.\n",Adminmacros);
            fprintf(pipeFile, "#define ADMINMACROS\t\"%s\"\n",
                Adminmacros);
            fprintf(pipeFile, "#include ADMINMACROS\n");
        }
        if (GlobalMacrosFile = getenv("IMAKE_MACROS_FILE"))
        {
            if (!access(GlobalMacrosFile,R_OK)) {
                printf("\nWarning! The environment variable IMAKE_MACROS_FILE is set:\n");
                printf("\tIncluding the file %s.\n",GlobalMacrosFile);
                fprintf(pipeFile, "#define GLOBALMACROSFILE\t\"%s\"\n",
                    GlobalMacrosFile);
                fprintf(pipeFile, "#include GLOBALMACROSFILE\n");
            }
        }
        if (!access(Localmacros,R_OK))
        {
            printf("\nWarning! You are using a local file, %s, \n",Localmacros);
            printf("         to override global make macros.\n\n");
            fprintf(pipeFile, "#define LOCALMACROS\t\"%s\"\n",
                Localmacros);
            fprintf(pipeFile, "#include LOCALMACROS\n");
        }
        fclose(pipeFile);
        while (wait(&status) > 0) {
            errno = 0;
            if (WIFSIGNALED(status))
                LogFatalI("Signal %d.", waitSig(status));
            if (WIFEXITED(status) && waitCode(status))
                LogFatalI("Exit code %d.", waitCode(status));
        }
        CleanCppOutput(outfd, outfname);
    } else {    /* child... dup and exec cpp */
        if (verbose)
            showargs(cpp_argv);
        dup2(pipefd[0], 0);
        dup2(fileno(outfd), 1);
        close(pipefd[1]);
        execv(cpp, cpp_argv);
        LogFatal("Cannot exec %s.", cpp);
    }
}

makeit()
{
    int pid;
    waitType    status;

    /*
     * Fork and exec make
     */
    pid = fork();
    if (pid < 0)
        LogFatal("Cannot fork.", "");
    if (pid) {  /* parent... simply wait */
        while (wait(&status) > 0) {
            errno = 0;
            if (WIFSIGNALED(status))
                LogFatalI("Signal %d.", waitSig(status));
            if (WIFEXITED(status) && waitCode(status))
                LogFatalI("Exit code %d.", waitCode(status));
        }
    } else {    /* child... dup and exec cpp */
        if (verbose)
            showargs(make_argv);
        if (make)
            execv(make, make_argv);
        else
            execvp("make", make_argv);
        LogFatal("Cannot exec %s.", make);
    }
}

char *CleanCppInput(Imakefile)
    char    *Imakefile;
{
    FILE    *outFile = NULL;
    int infd;
    char    *buf,       /* buffer for file content */
        *pbuf,      /* walking pointer to buf */
        *punwritten,    /* pointer to unwritten portion of buf */
        *cleanedImakefile = Imakefile,  /* return value */
        *ptoken,    /* pointer to # token */
        *pend,      /* pointer to end of # token */
        savec;      /* temporary character holder */
    struct stat st;

    /*
     * grab the entire file.
     */
    if ((infd = open(Imakefile, O_RDONLY)) < 0)
        LogFatal("Cannot open %s for input.", Imakefile);
    fstat(infd, &st);
    buf = Emalloc(st.st_size+1);
    if (read(infd, buf, st.st_size) != st.st_size)
        LogFatal("Cannot read all of %s:", Imakefile);
    close(infd);
    buf[ st.st_size ] = '\0';

    punwritten = pbuf = buf;
    while (*pbuf) {
        /* pad make comments for cpp */
        if (*pbuf == '#' && (pbuf == buf || pbuf[-1] == '\n')) {

        ptoken = pbuf+1;
        while (*ptoken == ' ' || *ptoken == '\t')
            ptoken++;
        pend = ptoken;
        while (*pend && *pend != ' ' && *pend != '\t' && *pend != '\n')
            pend++;
        savec = *pend;
        *pend = '\0';
        if (strcmp(ptoken, "include")
         && strcmp(ptoken, "define")
         && strcmp(ptoken, "undef")
         && strcmp(ptoken, "ifdef")
         && strcmp(ptoken, "ifndef")
         && strcmp(ptoken, "else")
	 && strcmp(ptoken, "elif")
         && strcmp(ptoken, "endif")
         && strcmp(ptoken, "if")) {
            if (outFile == NULL) {
            tmpImakefile = Strdup(tmpImakefile);
            (void) mktemp(tmpImakefile);
            cleanedImakefile = tmpImakefile;
            outFile = fopen(tmpImakefile, "w");
            if (outFile == NULL)
                LogFatal("Cannot open %s for write.\n",
                tmpImakefile);
            }
            fwrite(punwritten, sizeof(char), pbuf-punwritten, outFile);
            fputs("/**/", outFile);
            punwritten = pbuf;
        }
        *pend = savec;
        }
        pbuf++;
    }
    if (outFile) {
        fwrite(punwritten, sizeof(char), pbuf-punwritten, outFile);
        fclose(outFile); /* also closes the pipe */
    }

    return(cleanedImakefile);
}

CleanCppOutput(tmpfd, tmpfname)
    FILE    *tmpfd;
    char    *tmpfname;
{
    char    *input;
    int blankline = 0;

    while(input = ReadLine(tmpfd, tmpfname)) {
        if (isempty(input)) {
            if (blankline++)
                continue;
            KludgeResetRule();
        } else {
            blankline = 0;
            KludgeOutputLine(&input);
            fputs(input, tmpfd);
        }
        putc('\n', tmpfd);
    }
    fflush(tmpfd);
#ifdef NFS_STDOUT_BUG
    /*
     * On some systems, NFS seems to leave a large number of nulls at
     * the end of the file.  Ralph Swick says that this kludge makes the
     * problem go away.
     */
    ftruncate (fileno(tmpfd), (off_t)ftell(tmpfd));
#endif
}

/*
 * Determine of a line has nothing in it.  As a side effect, we trim white
 * space from the end of the line.  Cpp magic cookies are also thrown away.
 */
isempty(line)
    char    *line;
{
    char    *pend;

    /*
     * Check for lines of the form
     *  # n "...
     * or
     *  # line n "...
     */
    if (*line == '#') {
        pend = line+1;
        if (*pend == ' ')
            pend++;
        if (strncmp(pend, "line ", 5) == 0)
            pend += 5;
        if (isdigit(*pend)) {
            while (isdigit(*pend))
                pend++;
            if (*pend++ == ' ' && *pend == '"')
                return(TRUE);
        }
    }

    /*
     * Find the end of the line and then walk back.
     */
    for (pend=line; *pend; pend++) ;

    pend--;
    while (pend >= line && (*pend == ' ' || *pend == '\t'))
        pend--;
    *++pend = '\0';
    return (*line == '\0');
}

/*ARGSUSED*/
char *ReadLine(tmpfd, tmpfname)
    FILE    *tmpfd;
    char    *tmpfname;
{
    static boolean  initialized = FALSE;
    static char *buf, *pline, *end;
    char    *p1, *p2;

    if (! initialized) {
        int total_red;
        struct stat st;

        /*
         * Slurp it all up.
         */
        fseek(tmpfd, 0, 0);
        fstat(fileno(tmpfd), &st);
        pline = buf = Emalloc(st.st_size+1);
        total_red = read(fileno(tmpfd), buf, st.st_size);
        if (total_red != st.st_size)
            LogFatal("cannot read %s\n", tmpMakefile);
        end = buf + st.st_size;
        *end = '\0';
        lseek(fileno(tmpfd), 0, 0);
#ifdef SYSV
        freopen(tmpfname, "w+", tmpfd);
#else   /* !SYSV */
        ftruncate(fileno(tmpfd), 0);
#endif  /* !SYSV */
        initialized = TRUE;
        fprintf (tmpfd, "# Makefile generated by imake - do not edit!\n");
        fprintf (tmpfd, "# %s\n",
        "$XConsortium: imake.c /main/cde1_maint/2 1995/10/10 18:12:45 drk $");

#ifdef FIXUP_CPP_WHITESPACE
        {
        static char *cpp_warning[] = {
"#",
"# The cpp used on this machine replaces all newlines and multiple tabs and",
"# spaces in a macro expansion with a single space.  Imake tries to compensate",
"# for this, but is not always successful.",
"#",
NULL };
        char **cpp;

        for (cpp = cpp_warning; *cpp; cpp++) {
            fprintf (tmpfd, "%s\n", *cpp);
        }
        }
#endif /* FIXUP_CPP_WHITESPACE */
    }

    for (p1 = pline; p1 < end; p1++) {
        if (*p1 == '@' && *(p1+1) == '@') { /* soft EOL */
            *p1++ = '\0';
            p1++; /* skip over second @ */
            break;
        }
        else if (*p1 == '\n') { /* real EOL */
            *p1++ = '\0';
            break;
        }
    }

    /*
     * return NULL at the end of the file.
     */
    p2 = (pline == p1 ? NULL : pline);
    pline = p1;
    return(p2);
}

writetmpfile(fd, buf, cnt)
    FILE    *fd;
    int cnt;
    char    *buf;
{
    errno = 0;
    if (fwrite(buf, cnt, 1, fd) != 1)
        LogFatal("Cannot write to %s.", tmpMakefile);
}

char *Emalloc(size)
    int size;
{
    char    *p;

    if ((p = malloc(size)) == NULL)
        LogFatalI("Cannot allocate %d bytes\n", size);
    return(p);
}



#ifdef FIXUP_CPP_WHITESPACE
void
KludgeOutputLine(pline)
    char    **pline;
{
    char    *p = *pline;
    char    quotechar = '\0';

    switch (*p) {
        case '#':   /*Comment - ignore*/
        break;
        case '\t':  /*Already tabbed - ignore it*/
            break;
        case ' ':   /*May need a tab*/
        default:
        /*
         * The following cases should not be treated as beginning of
         * rules:
         * variable := name (GNU make)
         * variable = .*:.* (':' should be allowed as value)
         *  sed 's:/a:/b:'  (: used in quoted values)
         */
        for (; *p; p++) {
            if (quotechar) {
            if (quotechar == '\\' ||
                (*p == quotechar &&
#ifdef WIN32
                 quotechar != ')' &&
#endif
                 p[-1] != '\\'))
                quotechar = '\0';
            continue;
            }
            switch (*p) {
            case '\\':
            case '"':
            case '\'':
            quotechar = *p;
            break;
            case '(':
            quotechar = ')';
            break;
            case '{':
            quotechar = '}';
            break;
            case '[':
            quotechar = ']';
            break;
            case '=':
            goto breakfor;
            case ':':
#ifdef REMOVE_CPP_LEADSPACE
            if (!InRule && **pline == ' ') {
                while (**pline == ' ')
                (*pline)++;
            }
#endif
            if (p[1] == '=')
                goto breakfor;
            while (**pline == ' ')
                (*pline)++;
            InRule = TRUE;
            return;
            }
        }
breakfor:
        if (InRule && **pline == ' ')
            **pline = '\t';
        break;
    }
}

void
KludgeResetRule()
{
    InRule = FALSE;
}
#endif /* FIXUP_CPP_WHITESPACE */

char *Strdup(cp)
    register char *cp;
{
    register char *new = Emalloc(strlen(cp) + 1);

    strcpy(new, cp);
    return new;
}
