/* ************************************************************************ *\
 *                                                                          *
 * File:        md.c                                                        *
 *                                                                          *
 *      Updates makefiles from the .n dependency files generated by the     *
 *      -MD option to "cc" (and "cpp").                                     *
 *                                                                          *
 * Abstract:                                                                *
 *                                                                          *
 *      Basically, "md" does two things:                                    *
 *      1) It processes the raw dependency files produced by the cpp -MD     *
 *         option.  There is one line in the file for every #include        *
 *         encountered, but there are repeats and patterns like             *
 *         .../dir1/../dir2 appear which should reduce to .../dir2          *
 *         Md canonicalizes and flushes repeats from the dependency         *
 *         list.  It also sorts the file names and "fills" them to a 78     *
 *         character line.                                                  *
 *      2) Md also updates the makefile directly with the dependency        *
 *         information, so the .d file can be thrown away (-- -d option)    *
 *         This is done to save space.  Md assumes that dependency          *
 *         information in the makefile is sorted by .o file name and it     *
 *         procedes to merge in (add/or replace [as appropriate])  the new  *
 *         dependency lines that it has generated.  For time effeciency,    *
 *         Md assumes that any .d files it is given that were created       *
 *         before the creation date of the "makefile" were processed        *
 *         already.  It ignores them unless the force flag (-f) is given.   *
 *                                                                          *
 * Arguments:                                                               *
 *                                                                          *
 *      -d      delete the .d file after it is processed                    *
 *      -f      force an update of the dependencies in the makefile         *
 *              even though the makefile is more recent than the .n file    *
 *              (This implies that md has been run already.)                *
 *      -m      specify the makefile to be upgraded.  The defaults are      *
 *              "makefile" and then "Makefile".                             *
 *      -u      like -m above, but the file will be created if necessary    *
 *      -o      specify an output file for the dependencies other than a    *
 *              makefile                                                    *
 *      -v      set the verbose flag                                        *
 *      -x      expunge old dependency info from makefile                   *
 *      -D      subswitch for debugging.  can be followed by any of         *
 *              "c", "d", "m", "o", "t", "D" meaning:                       *
 *              c       show file contents                                  *
 *              d       show new dependency crunching                       *
 *              m       show generation of makefile                         *
 *              o       show files being opened                             *
 *              t       show time comparisons                               *
 *              D       show very low level debugging                       *
 *                                                                          *
 * Author:      Robert V. Baron                                             *
 *              Copyright (c) 1986 by Robert V. Baron                       *
 *                                                                          *
 * HISTORY                                                                  *
 * 29-Apr-87  Robert Baron (rvb) at Carnegie-Mellon University
 *      If specified -u file does not exist, assume it is empty and
 *      generate one.  As a sanity check, it must be possible to create
 *      the output file.
 *      Also, generalized fix below to handle any case of . as a
 *      file name.
 *
 * 25-Mar-87  Mary Thompson (mrt) at Carnegie Mellon
 *      Fixed up pathnamecanonicalization to recognize .// and
 *      drop the second / as well. mmax cpp generates this form.
 *
 *  6-Jan-87  Robert Baron (rvb) at Carnegie-Mellon University
 *      Fixed up pathname canonicalization to that ../../, etc would be
 *      handled correctly.
 *      Also made "force" on by default.
 *
 * 16-Mar-86  Robert Baron (rvb) at Carnegie-Mellon University
 *              Created 4/16/86                                             *
 *                                                                          *
\* ************************************************************************ */


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

#define LINESIZE 65536  //  NeXT_MOD

#define OUTLINELEN 79
#define IObuffer 50000
#define SALUTATION "# Dependencies for File:"
#define SALUTATIONLEN (sizeof SALUTATION - 1)
#define OLDSALUTATION "# DO NOT DELETE THIS LINE"
#define OLDSALUTATIONLEN (sizeof OLDSALUTATION - 1)

char file_array[IObuffer];      /* read file and store crunched names */
char dep_line[LINESIZE];        /* line being processed */
char dot_o[LINESIZE];           /* <foo.o>: prefix */
char *path_component[100];      /* stores components for a path while being
                                   crunched */

struct dep {                    /* stores paths that a file depends on */
        int len;
        char *str;
} dep_files[1000];
int dep_file_index;

qsort_strcmp(a, b)
struct dep *a, *b;
{
extern int strcmp();
        return strcmp(a->str, b->str);
}

char *outfile = (char *) 0;     /* generate dependency file */
FILE *out;

char *makefile = (char *) 0;    /* user supplied makefile name */
char *real_mak_name;            /* actual makefile name (if not supplied) */
char shadow_mak_name[LINESIZE]; /* changes done here then renamed */
FILE *mak;                      /* for reading makefile */
FILE *makout;                   /* for writing shadow */
char makbuf[LINESIZE];          /* one line buffer for makefile */
struct stat makstat;            /* stat of makefile for time comparisons */
int mak_eof = 0;                        /* eof seen on makefile */
FILE *find_mak(), *temp_mak();

int delete = 0;                 /* -d delete dependency file */
int debug = 0;
int     D_contents = 0;         /* print file contents */
int     D_depend = 0;           /* print dependency processing info */
int     D_make = 0;             /* print makefile processing info */
int     D_open = 0;             /* print after succesful open */
int     D_time = 0;             /* print time comparison info */
int force = 1;                  /* always update dependency info */
int update = 0;                 /* it's ok if the -m file does not exist */
int verbose = 0;                /* tell me something */
int expunge = 0;                /* first flush dependency stuff from makefile */


char *name;

main(argc,argv)
register char **argv;
{
int size;

        name = *argv;
        {register char *cp =name;
                while (*cp) if (*cp++ == '/') name = cp;
        }

        for ( argv++ ; --argc ; argv++ ) { register char *token = *argv;
                if (*token++ != '-' || !*token)
                        break;
                else { register int flag;
                        for ( ; flag = *token++ ; ) {
                                switch (flag) {
                                case 'd':
                                        delete++;
                                        break;
                                case 'f':
                                        force++;
                                        break;
                                case 'u':
                                        update++;
                                case 'm':
                                        makefile = *++argv;
                                        if (--argc < 0) goto usage;
                                        break;
                                case 'o':
                                        outfile = *++argv;
                                        if (--argc < 0) goto usage;
                                        break;
                                case 'v':
                                        verbose++;
                                        break;
                                case 'x':
                                        expunge++;
                                        break;
                                case 'D':
                                        for ( ; flag = *token++ ; )
                                                switch (flag) {
                                                case 'c':
                                                        D_contents++;
                                                        break;
                                                case 'd':
                                                        D_depend++;
                                                        break;
                                                case 'm':
                                                        D_make++;
                                                        break;
                                                case 'o':
                                                        D_open++;
                                                        break;
                                                case 't':
                                                        D_time++;
                                                        break;
                                                case 'D':
                                                        debug++;
                                                        break;
                                                default:
                                                        goto letters;
                                                }
                                        goto newtoken;
                                default:
                                        goto usage;
                                }
letters: ;
                        }
                }
newtoken: ;
        }

        if (!expunge && argc < 1) goto usage;
        if ((int) outfile && (int) makefile)    /* not both */
                goto usage;

        if ((int) outfile) {
                /*
                 * NeXT_MOD, For SGS stuff, in case still linked to master version
                 */
                unlink(outfile);

                if ((out = fopen(outfile, "w")) == NULL) {
                        fprintf(stderr, "%s: outfile = \"%s\" ", name, outfile);
                        perror("fopen");
                        fflush(stdout), fflush(stderr);
                        exit(1);
                } else if (D_open)
                        printf("%s: opened outfile \"%s\"\n", name, outfile);
        } else if (mak = find_mak(makefile)) {
                makout = temp_mak();
                out = makout;
                if (expunge)
                        expunge_mak(mak, makout);
                else
                        skip_mak(mak, makout);
        } else if (mak_eof &&  /* non existent file == mt file */
                   (int)(makout = temp_mak())) { /* but we need to be able */
                out = makout;                    /* to write here */
        } else if (makefile) {
                fprintf(stderr, "%s: makefile \"%s\" can not be opened or stat'ed\n",
                        name, makefile);
                exit(2);
        }

        for (; argc--; argv++) {
                dep_file_index = 0;

                if (size = read_dep(*argv)) {

                        save_dot_o();
                        if (D_depend) printf("%s: dot_o = \"%s\"\n", name, dot_o);

                        parse_dep();
                        if (mak) scan_mak(mak, makout, dot_o);
                        output_dep(out);

                        if (delete)
                                unlink(*argv);
                }
        }

        if (mak) finish_mak(mak, makout);
        rename(shadow_mak_name, real_mak_name);
        exit(0);
usage:
        fprintf(stderr, "usage: md -f -Dcdmot -m makefile -o outputfile -v <file1> ... <filen>\n");
        exit(1);
}


read_dep(file)
register char *file;
{
register int fd;
register int size;
struct stat statbuf;

        if ((fd = open(file, 0)) < 0) {
                fprintf(stderr, "%s: file = \"%s\" ", name, file);
                perror("open");
                fflush(stdout), fflush(stderr);
                return 0;
        }
        if (D_open)
                printf("%s: opened dependency file \"%s\"\n", name, file);
        
        if (fstat(fd, &statbuf) < 0) {
                fprintf(stderr, "%s: file = \"%s\" ", name, file);
                perror("stat");
                fflush(stdout), fflush(stderr);
                goto out;
        }
        switch(statbuf.st_mode & S_IFMT) {
        case S_IFREG:
                if (D_time)
                        printf("%s: file time = %d\n", name, statbuf.st_mtime);

                if (statbuf.st_size > IObuffer) {
                        fprintf(stderr, "%s: file \"%s\" tooo big for IObuffer\n",
                                name, file);
                        goto out;
                } else if (force)
                        break;
                else if ((int) mak && statbuf.st_mtime < makstat.st_mtime) {
                        if (verbose || D_time)
                                fprintf(stderr, "%s: skipping \"%s\" %d < %d \"%s\"\n",
                                        name, file, statbuf.st_mtime, makstat.st_mtime,
                                        real_mak_name);
                        goto out;
                } else /* >=   =>ok */
                        break;
        case S_IFDIR:
        case S_IFLNK:
        case S_IFCHR:
        case S_IFBLK:
        case S_IFSOCK:
        default:
                fprintf(stderr, "%s: bad mode: 0%o on \"%s%s\"\n",
                        name, statbuf.st_mode, file);
                fflush(stdout), fflush(stderr);
                goto out;
        }

        if ((size = read(fd, file_array, sizeof (file_array))) < 0) {
                fprintf(stderr, "%s: file = \"%s\" ", name, file);
                perror("read");
                fflush(stdout), fflush(stderr);
                goto out;
        }
        file_array[size] = 0;
        
        if (close(fd) < 0) {
                fprintf(stderr, "%s: file = \"%s\" ", name, file);
                perror("close");
                fflush(stdout), fflush(stderr);
                return 0;
        }

        if (D_depend && D_contents)
                printf("file_array: \"%s\"\n", file_array);
        return size;
out: ;
        close(fd);
        return 0;
}

save_dot_o()
{
register char *cp = file_array;
register char *svp = dot_o;
register int c;

        while ((*svp++ = (c = *cp++)) && c != ':');
        *svp = 0;
}

parse_dep()
{
register char *lp = file_array;
register int c;

        while (*lp) {register char *tlp = lp;
                     register char *cp = dep_line;
                     register int i = 0;
                     int abspath = 0;
                     char oldc;
                     char *oldcp;

                        /* get a line to process */
                while ((c = *lp++) && c != '\n')
                  {
                    if (c == '\\')
                      lp++;             /* skip backslash newline */
                    else
                      *cp++ = c;
                  }
                  if (!c)
                    break;
                *cp = 0;
                cp = dep_line;
                lp[-1] = 0;
                        /* skip .o file name */
                while ((c = *cp++) && c != ':'); if (!c) continue;
next_filename:
                i = 0;
                abspath = 0;
                while ((c = *cp) && (c == ' ' || c == '\t')) cp++; if (!c) continue;

                        /* canonicalization processing */

                                        /* initial / is remembered */
                if (c == '/')
                        abspath++;

                while (c && c != ' ' && c != '\t') {
                        if (D_depend) printf("i = %d going \"%s\"\n", i, cp);
                                        /* kill \'s */
                        while ((c = *cp) && c == '/') cp++; if (!c) break;
                        path_component[i] = cp;
                                        /* swallow chars till next / or null */
                        while ((c = *cp++) && c != '/' && c != ' ' && c != '\t');
                        if (c) cp[-1]=0;/* end component C style */

                                        /* ignore . */;
                        if (!strcmp(path_component[i], "."))
                                ;       /* if "component" != .. */
                        else            /* don't reduce /component/.. to nothing */
                                i++;    /* there could be symbolic links! */
                }
                        /* reassemble components */
                oldc = c;               /* save c */
                oldcp = cp;             /* save cp */
                cp = tlp;               /* overwrite line in buffer */
                if (abspath)
                        *cp++ = '/';
                for (c=0; c<i; c++) {register char *ccp = path_component[c];
                        while (*cp++ = *ccp++);
                        *--cp = '/';
                        cp++;
                }
                *--cp = 0;

                c=dep_file_index++;
                dep_files[c].str = tlp;
                dep_files[c].len = cp - tlp;
                if (D_depend)
                        printf("%s: dep_file[%d] = \"%s\" Len %d\n",
                                name, dep_file_index - 1, tlp, cp - tlp);
                tlp = cp + 1;
                if (oldc)
                  {
                     cp = oldcp;
                     goto next_filename;
                  }
        }
}

output_dep(out)
FILE *out;
{
register int j;
register int size = 1000;
register int dot_o_len = strlen(dot_o);
register struct dep *dp = dep_files;
int written = 0;

        if (D_depend && debug)
                for(j = 0; j < dep_file_index; j++) {
                        printf("dep_files[%d] = %s\n", j, dep_files[j].str);
                }

        qsort(dep_files, dep_file_index, sizeof (struct dep), qsort_strcmp);

        if (D_depend && debug)
                for(j = 0; j < dep_file_index; j++) {
                        printf("dep_files[%d] = %s\n", j, dep_files[j].str);
                }

        fprintf(out, "%s %s", SALUTATION, dot_o);
        for(j = 0; j < dep_file_index; j++, dp++)
                                        {register int len = dp->len;
                                         register char *str = dp->str;
                if (j && len == (dp-1)->len && !strcmp(str, (dp-1)->str))
                        continue;
                written++;
                if (size + len + 1 > OUTLINELEN) {
                        fprintf(out, "\n%s %s", dot_o, str);
                        size = dot_o_len + len + 1;
                } else {
                        fprintf(out, " %s", str);
                        size += len + 1;
                }
        }
        fprintf(out, "\n");
        if (verbose)
                fprintf(stdout, "%s: \"%s\" %d => %d\n", name, dot_o, dep_file_index, written);
}

                /* process makefile */
FILE *
find_mak(file)
char *file;
{
FILE *mak;

        if ((int) file) {
                if ((mak = fopen(file, "r")) != NULL) {
                        real_mak_name = file;
                } else if (update) {
                        mak_eof = 1;
                        real_mak_name = file;
                        return NULL;
                } else {
                        fprintf(stderr, "%s: file = \"%s\" ", name, file);
                        perror("fopen");
                        fflush(stdout), fflush(stderr);
                        return NULL;
                }
        } else {
                if ((mak = fopen("makefile", "r")) != NULL) {
                        real_mak_name = "makefile";
                } else if ((mak = fopen("Makefile", "r")) != NULL) {
                        real_mak_name = "Makefile";
                } else return NULL;
        }

        if (fstat(fileno(mak), &makstat) < 0) {
                fprintf(stderr, "%s: file = \"%s\" ", name, real_mak_name);
                perror("stat");
                fflush(stdout), fflush(stderr);
                return NULL;
        }
        if (D_open)
                printf("%s: opened makefile \"%s\"\n", name, real_mak_name);
        if (D_time)
                printf("%s: makefile time = %d\n", name, makstat.st_mtime);

        return mak;
}

FILE *
temp_mak()
{
FILE *mak;

        strcpy(shadow_mak_name, real_mak_name);
        strcat(shadow_mak_name, ".md");

        /*
         * For SGS stuff, in case still linked to master version
         */
        unlink(shadow_mak_name);
        if ((mak = fopen(shadow_mak_name, "w")) == NULL) {
                fprintf(stderr, "%s: file = \"%s\" ", name, shadow_mak_name);
                perror("fopen");
                fflush(stdout), fflush(stderr);
                return NULL;
        }
        if (D_open)
                printf("%s: opened makefile.md \"%s\"\n", name, shadow_mak_name);

        return mak;
}

skip_mak(makin, makout)
register FILE *makin, *makout;
{
register int len = SALUTATIONLEN;

        if (D_make)
                printf("skipping in \"%s\"  ", real_mak_name);

        while (fgets(makbuf, LINESIZE, makin) != NULL) {
                if (D_make && D_contents)
                        printf("%s: \"%s\"\n", real_mak_name, makbuf);
                if (strncmp(makbuf, SALUTATION, len)) {
                        fputs(makbuf, makout);
                } else
                        break;
        }
        mak_eof = feof(makin);
        if (mak_eof)
                fclose(makin);
        if (D_make)
                printf("eof = %d str = \"%s\"", mak_eof, makbuf);
}

expunge_mak(makin, makout)
register FILE *makin, *makout;
{
register int len = SALUTATIONLEN;
register int oldlen = OLDSALUTATIONLEN;

        if (D_make)
                printf("expunging in \"%s\"  ", real_mak_name);

        while (fgets(makbuf, LINESIZE, makin) != NULL) {
                if (D_make && D_contents)
                        printf("%s: \"%s\"\n", real_mak_name, makbuf);
                if (! strncmp(makbuf, SALUTATION, len) ||
                    ! strncmp(makbuf, OLDSALUTATION, oldlen))
                        break;
                else
                        fputs(makbuf, makout);
        }
        mak_eof = 1;
        if (mak_eof)
                fclose(makin);
        if (D_make)
                printf("eof = %d str = \"%s\"", mak_eof, makbuf);
}

scan_mak(makin, makout, file)
register FILE *makin, *makout;
char *file;
{
register char *cp = &makbuf[SALUTATIONLEN+1];
register int len = strlen(file);
register int ret;

        if (D_make)
                printf("scanning in \"%s\" for \"%s\"\n", real_mak_name, file);

        do {
                if (mak_eof)            /* don't scan any more */
                        return;

                ret = strncmp(cp, file, len);
                if (D_make)
                        printf("saw \"%s\" ret = %d\n", cp, ret);

                if (ret < 0) {          /* skip forward till match or greater */
                        fputs(makbuf, makout);          /* line we're looking at */
                        while (fgets(makbuf, LINESIZE, makin) != NULL) {
                                if (strncmp(makbuf, SALUTATION, SALUTATIONLEN)) {
                                        fputs(makbuf, makout);
                                } else
                                        break;
                        }
                        mak_eof = feof(makin);
                        if (mak_eof)
                                fclose(makin);
                        continue;
                } else if (ret == 0) {  /* flush match */
                        while (fgets(makbuf, LINESIZE, makin) != NULL) {
                                if (strncmp(makbuf, SALUTATION, SALUTATIONLEN)) {
                                        ;       /* flush old stuff */
                                } else
                                        break;
                        }
                        mak_eof = feof(makin);
                        if (mak_eof)
                                fclose(makin);
                        break;
                } else {                /* no luck this time */
                        break;
                }
        } while (1);
}

finish_mak(makin, makout)
register FILE *makin, *makout;
{
        if (mak_eof)            /* don't scan any more */
                return;

        if (D_make)
                printf("finishing in \"%s\"\n", real_mak_name);

        fputs(makbuf, makout);          /* line we're looking at */
        while (fgets(makbuf, LINESIZE, makin) != NULL) {
                fputs(makbuf, makout);
        }
}
