/*
 * MakeMake - a program to construct a makefile from C source code
 *
 *   Usage: makemake [ -dir ... ] file.c ...
 *
 * MakeMake reads the C source files and outputs on standard out a file
 *	of the form:
 * target.o: target.c header file list
 * where the header file list is the transitive closure of all
 * files #included in filex.c (i.e. if filex.c #includes "header.h", and
 * "header.h" #includes "subheader.h", both "header.h" and "subheader.h"
 * appear in the list of header files). MakeMake only examines header
 * files delimited by double quotes ("). It looks for those file in the
 * current directory, and in all directories given as arguments. Include
 * directories are indicated by prepending a "-" to their name. That name
 * is then prepended to the include file name from scanned files, so you'll
 * want to use something like "-/h/ -rexx:".  System header files enclosed
 * in angle brackets ("<" and ">") are not examined.
 *
 * Written and placed in the public domain by Tim McGrath 4/2/87
 *
 * modified by Mike Meyer to be usefull in with the BSD-oid makefile
 * construction technic, which replaces the end of an existing makefile
 * with a dependency list. The result is still in the public domain.
 *
 * While I was at it, I deleted the multiple statements per line (so the
 * source-level debugger would be more usefull), and all the tacky, tacky
 * passing around of pointers to int to get a return value. Also fixed
 * the bugs of not printing the first elelement in the array of file
 * names, and other oddities. I also fixed the memory allocator to
 * be less proficient at exercising malloc, and to believe in real
 * limits. The "heap" struct comes from that. My final change was adding
 * -dir to indicate include files. Strange, strange code, this was.
 *
 * Modified again by Lynn Kerby to be useful with the MANX compiler and
 * to use a slightly different convention for command line arguments. 
 * to turn on generation of the compilation rule and a more common make
 * file dependency format, use the -M option (upper or lower works fine).
 * Directories must be prefixed with a -i (again, upper or lower case) to
 * be more compatible with C compiler syntax.  Also changed it to allow
 * for NON-ANSI compilers with conditional prototypes
 */
#ifdef	LATTICE
#define _ANSI_C
#endif

#include <stdio.h>
#ifdef _ANSI_C
#include <stdlib.h>
#endif
#include <string.h>

#ifdef _ANSI_C
#define PROTO(x) x
void main(int, char **) ;
#else
#define PROTO(x) ()
#define NO_PROTOS
#endif

#define LINEMAX 	80

struct heap {
    char **h_names;
    int h_used;
    int h_incr;
};
int heap_init PROTO((struct heap * h, int size));
int heap_add PROTO((struct heap * h, int where, char *name));

#define heap_names(h)	((h) . h_names)

void free_space PROTO((char **dp, int dc));
char *file_exten PROTO((char *pgm_name, char extension, char *bufout));
char *base_name PROTO((char *name, char *bufout));
char *path_name PROTO((char *name, char *bufout));
void depend_file PROTO((int ct, char **flist, char *pgm_name));
void start_line PROTO((char *with_name, char *buf));
int get_dependents PROTO((char *name, struct heap * files,
			  struct heap * incs, int cinc));
int scan_file PROTO((struct heap * h, int list_to_use, FILE * fp,
		     struct heap * incs, int cinc));
FILE *incopen PROTO((char *file, struct heap * incs, int count, char *bufout));

static char RCSid[] = "$Header: makemake.c,v 1.5 90/04/27 15:40:40 lfk Exp $";
int	manx_output = 0;

void
main(argc, argv)
char **argv;
int argc;
{
    int i, dependent_count, include_count = 0;
    struct heap dependents, includes;

    if (!heap_init(&dependents, 10) || !heap_init(&includes, 10)) {
	fprintf(stderr, "No memory\n");
	exit(20);
    }
    for (i = 1; i < argc; i++)
	if (argv[i][0] == '-') {
	    switch(argv[i][1]) {
	      case 'i':
	      case 'I':
		heap_add(&includes, include_count, &argv[i][2]);
		include_count++;
		break;
	      case 'm':
	      case 'M':
		manx_output++;
		break;
	      default:
		fprintf(stderr,"unknown argument %s\n",argv[i]);
	    }
	} else {
	    dependent_count = get_dependents(argv[i], &dependents, &includes,
					     include_count);
	    if (dependent_count > 0)
		depend_file(dependent_count, heap_names(dependents), argv[i]);
	    free_space(heap_names(dependents), dependent_count);
	}
    exit(0);
}

void
free_space(dp, dc)
/*
 * Purpose: free up list of file names
 * Inputs:  dp - points to list of pointers to strings
 *	    dc - number of pointers in the list
 */
char **dp;
int dc;
{
    while (dc > 0) {
	free(*dp++);
	dc--;
    }
}

char *
file_exten(pgm_name, extension, bufout)
/*
 * Purpose: append new extension onto file name
 * Inputs:  pgm_name - pointer to name of file
 *	    xtension - pointer to new file name extension (2 chars only)
 * Outputs: bufout - points to area for new file name
 * Returns: bufout
 */
char *pgm_name, extension, *bufout;
{
    int i = 0;

    while (*pgm_name) {
	bufout[i++] = *pgm_name;
	if (*pgm_name++ == '.' && extension) {
	    bufout[i++] = extension;
	    bufout[i] = '\0';
	    return (bufout);
	}
    }
    if (extension) {
	bufout[i++] = '.';
	bufout[i++] = extension;
    }
    bufout[i] = '\0';
    return (bufout);
}

char *
base_name(name, bufout)
/*
 * Purpose: strip any extensions and path off of a file name.
 * Inputs:  name - pointer to file name
 * Outputs: bufout - area for new file name
 * Returns: bufout
 */
char *name, *bufout;
{
    char *p;

    p = strrchr(name, '/');
    if (p == NULL)
	p = strrchr(name, ':');
    if (p == NULL)
	p = name;
    else
	p += 1;
    strcpy(bufout, p);
    p = strrchr(bufout, '.');
    if (p != NULL)
	*p = '\0';
    return bufout;
}

char *
path_name(name, bufout)
/*
 * Purpose: strip the filename off the path 
 * Inputs:  name - pointer to file name
 * Outputs: bufout - containing the filename portion of the path
 * Returns: bufout
 */
char *name, *bufout;
{
    char *p;

    strcpy(bufout, name);
    p = strrchr(bufout, '/');
    if (p == NULL)
	p = strrchr(bufout, ':');
    if (p == NULL)
	p = bufout;
    else
	p += 1;
    *p = '\0';
    return bufout;
}

void
depend_file(ct, flist, pgm_name)
/*
 * Purpose: print file name and list of dependents
 * Inputs:  ct - number of dependents in the list
 *	    flist - pointer to a list of pointers to dependent names
 *	    pgm_name - name of file whose dependents are being printed
 */
char **flist, *pgm_name;
int ct;
{
    int i;
    char buf[LINEMAX], add_name[LINEMAX], pname[LINEMAX];
    
    start_line(file_exten(base_name(pgm_name, add_name), 'o', pname), buf);
    for (i = 0; i < ct; i++) {
	file_exten(flist[i], 0, add_name);
	if (manx_output) {
	    if (strlen(buf) + strlen(add_name) + 1 >= LINEMAX - 3) {
		strcat(buf," \\");
		puts(buf);
		strcpy(buf, "       ");
	    }
	} else {
	    if (strlen(buf) + strlen(add_name) + 1 >= LINEMAX - 1) {
		puts(buf);
		start_line(pname, buf);
	    }
	}
	strcat(strcat(buf," "),add_name);
    }
    puts(buf);
    if (manx_output) {
	sprintf(buf,"\t$(CC) $(CFLAGS) -o $@ %s$*.c\n",
		path_name(pgm_name,add_name));
	puts(buf);
    }
}

void
start_line(with_name, buf)
/*
 * Purpose: give each line a standard indentation
 * Inputs:  with_name - name of root file on each line
 *	    buf - place to put indented line
 */
char *with_name, *buf;
{
    strcpy(buf, with_name);
    strcat(buf, ":");
}

int
get_dependents(name, files, incs, cinc)
/*
 * Purpose: get heap of dependent files for input file
 * Inputs:  name - file name to scan for dependents.
 *	    incs - heap of include directories to look at
 *	    cinc - cout of above.
 * Outputs: files - heap of dependent file names.
 * Returns: number of files in files.
 */
char *name;
struct heap *files, *incs;
int cinc;
{
    FILE *fp;
    int result;

    if ((fp = fopen(name, "r")) == NULL) {
	fprintf(stderr, "Couldn't open file %s\n", name);
	return 0;
    }
    heap_add(files, 0, name);
    result = scan_file(files, 1, fp, incs, cinc);
    fclose(fp);
    return result;
}

int
scan_file(file_name_heap, next_to_use, fp, inc, cinc)
/*
 * Purpose: search a C source file file #includes, and search the #includes
 *	    for nested #includes
 * Inputs:  fn - name of file to scan
 *	    next_to_use - last used filename position in file_name_list
 * Outputs: file_name_list - list of included files
 * Returns: new value for last_list_used.
 */
struct heap *file_name_heap, *inc;
FILE *fp;
int next_to_use, cinc;
{
    FILE *ifp;
    char buf[1024], ifn[LINEMAX];
    int j, k;


    while (fgets(buf, 1024, fp)) {
	if (strncmp(buf, "#include", 8) == 0) {
	    j = 8;
	    while (buf[j] == ' ' || buf[j] == '\t')
		j++;
	    if (buf[j++] != '"')
		continue;
	    k = 0;
	    while (buf[j]) {
		if (buf[j] == '"' || buf[j] == '\n')
		    break;
		else
		    ifn[k++] = buf[j++];
	    }
	    ifn[k] = '\0';

	    ifp = incopen(ifn, inc, cinc, buf);
	    if (!ifp)
		fprintf(stdout, "Couldn't open include file %s\n", ifn);
	    else if (heap_add(file_name_heap, next_to_use, buf))
		next_to_use = scan_file(file_name_heap, next_to_use + 1, ifp,
					inc, cinc);
	    fclose(ifp);
	}
    }
    return next_to_use;
}

FILE *
incopen(file, incs, count, bufout)
/*
 * Purpose: Open an include file, figuring out where it is along the way.
 * Inputs:  file - name of the file
 *	    incs - heap of include files to check
 *	    count - number of include files in the heap
 * Outputs: bufout - name of the file we actually opened.
 * Returns: open file, or NULL if we didn't open the file
 */
char *file, *bufout;
struct heap *incs;
int count;
{
    FILE *out;
    char *pp;
    register int i = 0;

    strcpy(bufout, file);
    out = fopen(bufout, "r");

    while (out == NULL && i < count) {
	pp=heap_names(*incs)[i++];
	if (*(pp + strlen(pp) - 1) != '/' && *(pp + strlen(pp) - 1) != ':')
	    sprintf(bufout, "%s/%s", pp, file);
	else
	    sprintf(bufout, "%s%s", pp, file);
	out = fopen(bufout, "r");
    }
    return out;
}

heap_init(h, size)
/*
 * Purpose: Initalize a heap of file names.
 * Inputs:  h - the heap
 *	    size - the initial size and growth increment.
 *
 * Outputs: h, correctly initialized.
 * Returns: TRUE if all went well, FALSE otherwise.
 */
struct heap *h;
int size;
{
    if ((h->h_names = (char **)malloc(size * sizeof(char *))) == NULL)
	return 0;
    h->h_incr = h->h_used = size;
    return 1;
}

heap_add(h, where, fn)
/*
 * Purpose: Add a file name to the heap if it's not there already
 * Inputs:  h - the heap of names
 *	    where - where to add the name
 *	    fn - name of file
 * Returns: 1 if file name added, 0 otherwise
 */
struct heap *h;
int where;
char *fn;
{
    int i;
    char *p;

    for (i = 0; i < where; i++)
	if (!strcmp(h->h_names[i], fn))
	    return (0);
    if (where >= h->h_used)
	h->h_names = (char **) realloc(h->h_names,
				 (h->h_used += h->h_incr) * sizeof(char *));
    p = strdup(fn);
    if (p == NULL || h->h_names == NULL) {
	fprintf(stderr, "Out of memory");
	exit(20);
    }
    h->h_names[where] = p;
    return (1);
}
/*EOF MARKER*/

