--- /dev/null
+/*
+ *
+ * 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.
+ *
+ * $Header$
+ * $Locker$
+ *
+ * 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] [-v] [make flags]
+ *
+ * Imake takes a template makefile (Imake.template) and runs cpp on it
+ * producing a temporary makefile in /usr/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.template
+ * -s 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.
+ * -v verbose. Show the make command line executed.
+ *
+ * Environment variables:
+ *
+ * IMAKEINCLUDE Include directory to use in addition to
+ * "." and "/usr/lib/local/imake.include".
+ * 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.template. 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>
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#ifdef SYSV
+#include <fcntl.h>
+#else /* !SYSV */
+#include <sys/wait.h>
+#endif /* !SYSV */
+#include <sys/signal.h>
+#include <sys/stat.h>
+
+#ifdef SYSV
+#define dup2(fd1,fd2) ((fd1 == fd2) ? fd1 : \
+ (close(fd2), fcntl(fd1, F_DUPFD, fd2)))
+#endif /* SYSV */
+#define TRUE 1
+#define FALSE 0
+#define ARGUMENTS 50
+
+#if defined(sun) || defined(hpux)
+#define REDUCED_TO_ASCII_SPACE
+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.
+ */
+#ifndef REDUCED_TO_ASCII_SPACE
+#define KludgeOutputLine(arg)
+#define KludgeResetRule()
+#endif
+
+typedef u_char boolean;
+
+#ifndef apollo
+char *cpp = "/lib/cpp";
+#else apollo
+char *cpp = "/usr/lib/cpp";
+#endif /* apollo */
+
+char *tmpMakefile = "/usr/tmp/tmp-make.XXXXXX";
+char *tmpImakefile = "/usr/tmp/tmp-imake.XXXXXX";
+char *make_argv[ ARGUMENTS ] = { "make" };
+char *cpp_argv[ ARGUMENTS ] = {
+ "cpp",
+ "-I.",
+#ifdef unix
+ "-Uunix",
+#endif /* unix */
+#ifdef pegasus
+ "-Dpegasus",
+#endif /* pegasus */
+};
+int make_argindex;
+int cpp_argindex;
+char *make = NULL;
+char *Imakefile = NULL;
+char *Makefile = NULL;
+char *Template = "Imake.template";
+char *program;
+char *FindImakefile();
+char *ReadLine();
+char *CleanCppInput();
+char *strdup();
+
+boolean verbose = FALSE;
+boolean show = FALSE;
+extern int errno;
+extern char *Emalloc();
+extern char *realloc();
+extern char *getenv();
+extern char *mktemp();
+
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *tmpfd;
+ char makeMacro[ BUFSIZ ];
+ char makefileMacro[ BUFSIZ ];
+
+ init();
+ SetOpts(argc, argv);
+
+ AddCppArg("-I/usr/lib/local/imake.includes");
+
+ Imakefile = FindImakefile(Imakefile);
+ if (Makefile)
+ tmpMakefile = Makefile;
+ else
+ tmpMakefile = mktemp(strdup(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);
+
+ 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);
+}
+
+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
+ */
+ program = argv[0];
+ for(argc--, argv++; argc; argc--, argv++) {
+ /*
+ * We intercept these flags.
+ */
+ if (argv[0][0] == '-') {
+ if (argv[0][1] == 'D') {
+ 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;
+ else if (argc > 1 && argv[1][0] != '-') {
+ argc--, argv++;
+ Makefile = argv[0];
+ }
+ show = TRUE;
+ } 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;
+{
+ extern char *sys_errlist[];
+ static boolean entered = FALSE;
+
+ if (entered)
+ return;
+ entered = TRUE;
+
+ fprintf(stderr, "%s: ", program);
+ if (errno)
+ fprintf(stderr, "%s: ", sys_errlist[ 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)
+ char *Imakefile;
+ char *template;
+ FILE *outfd;
+{
+ FILE *pipeFile;
+ int pid, pipefd[2];
+#ifdef SYSV
+ int status;
+#else /* !SYSV */
+ union wait status;
+#endif /* !SYSV */
+ char *cleanedImakefile;
+
+ /*
+ * Get a pipe.
+ */
+ if (pipe(pipefd) < 0)
+ LogFatal("Cannot make a pipe.", "");
+
+ /*
+ * Fork and exec cpp
+ */
+ pid = vfork();
+ 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]);
+ fprintf(pipeFile, "#define IMAKE_TEMPLATE\t\"%s\"\n",
+ template);
+ fprintf(pipeFile, "#define INCLUDE_IMAKEFILE\t\"%s\"\n",
+ cleanedImakefile);
+ fprintf(pipeFile, "#include IMAKE_TEMPLATE\n");
+ fclose(pipeFile);
+ while (wait(&status) > 0) {
+ errno = 0;
+#ifdef SYSV
+ if ((status >> 8) & 0xff)
+ LogFatalI("Signal %d.", (status >> 8) & 0xff);
+ if (status & 0xff)
+ LogFatalI("Exit code %d.", status & 0xff);
+#else /* !SYSV */
+ if (status.w_termsig)
+ LogFatalI("Signal %d.", status.w_termsig);
+ if (status.w_retcode)
+ LogFatalI("Exit code %d.", status.w_retcode);
+#endif /* !SYSV */
+ }
+ CleanCppOutput(outfd);
+ } 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;
+#ifdef SYSV
+ int status;
+#else /* !SYSV */
+ union wait status;
+#endif /* !SYSV */
+
+ /*
+ * Fork and exec make
+ */
+ pid = vfork();
+ if (pid < 0)
+ LogFatal("Cannot fork.", "");
+ if (pid) { /* parent... simply wait */
+ while (wait(&status) > 0) {
+ errno = 0;
+#ifdef SYSV
+ if ((status >> 8) & 0xff)
+ LogFatalI("Signal %d.", (status >> 8) & 0xff);
+ if (status & 0xff)
+ LogFatalI("Exit code %d.", status & 0xff);
+#else /* !SYSV */
+ if (status.w_termsig)
+ LogFatalI("Signal %d.", status.w_termsig);
+ if (status.w_retcode)
+ LogFatalI("Exit code %d.", status.w_retcode);
+#endif /* !SYSV */
+ }
+ } 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.", cpp);
+ }
+}
+
+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, "endif")
+ && strcmp(ptoken, "if")) {
+ if (outFile == NULL) {
+ tmpImakefile = mktemp(strdup(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)
+ FILE *tmpfd;
+{
+ char *input;
+ int blankline = 0;
+
+ while(input = ReadLine(tmpfd)) {
+ if (isempty(input)) {
+ if (blankline++)
+ continue;
+ KludgeResetRule();
+ } else {
+ blankline = 0;
+ KludgeOutputLine(&input);
+ fputs(input, tmpfd);
+ }
+ putc('\n', tmpfd);
+ }
+ fflush(tmpfd);
+}
+
+/*
+ * 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');
+}
+
+char *ReadLine(tmpfd)
+ FILE *tmpfd;
+{
+ 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);
+ ftruncate(fileno(tmpfd), 0);
+ initialized = TRUE;
+#ifdef REDUCED_TO_ASCII_SPACE
+ fprintf(tmpfd, "#\n");
+ fprintf(tmpfd, "# Warning: the cpp used on this machine replaces\n");
+ fprintf(tmpfd, "# all newlines and multiple tabs/spaces in a macro\n");
+ fprintf(tmpfd, "# expansion with a single space. Imake tries to\n");
+ fprintf(tmpfd, "# compensate for this, but is not always\n");
+ fprintf(tmpfd, "# successful.\n");
+ fprintf(tmpfd, "#\n");
+#endif /* REDUCED_TO_ASCII_SPACE */
+ }
+
+ 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, *malloc();
+
+ if ((p = malloc(size)) == NULL)
+ LogFatalI("Cannot allocate %d bytes\n", size);
+ return(p);
+}
+
+#ifdef REDUCED_TO_ASCII_SPACE
+KludgeOutputLine(pline)
+ char **pline;
+{
+ char *p = *pline;
+
+ switch (*p) {
+ case '#': /*Comment - ignore*/
+ break;
+ case '\t': /*Already tabbed - ignore it*/
+ break;
+ case ' ': /*May need a tab*/
+ default:
+ while (*p) if (*p++ == ':') {
+ if (**pline == ' ')
+ (*pline)++;
+ InRule = TRUE;
+ break;
+ }
+ if (InRule && **pline == ' ')
+ **pline = '\t';
+ break;
+ }
+}
+
+KludgeResetRule()
+{
+ InRule = FALSE;
+}
+#endif /* REDUCED_TO_ASCII_SPACE */
+
+char *strdup(cp)
+ register char *cp;
+{
+ register char *new = Emalloc(strlen(cp) + 1);
+
+ strcpy(new, cp);
+ return new;
+}