/*
 *
 * MakeMake - a program to construct a makefile from C source code
 *
 *   Usage: makemake file1.c file2.c file3.c ... filen.c
 *
 * MakeMake reads the C source files and creates
 * a file called 'makefile' in the current directory. For each
 * source file entered on the command line, makemake adds an entry 
 * of the form:
 * 
 *   filex.o :	filex.c ... header file list ...
 *   	cc $(OPT) filex.c
 * 
 * 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 ("). System header files enclosed
 * in angle brackets (< and >) are not examined.
 * 
 * The target of the makefile is assumed to be a program called 'main', 
 * with dependencies as shown:
 * 
 * main :  file1.o file2.o file3.o ... filek.o
 * main :  filek+1.o ... filen.o
 * # ------- construction command goes here ---------
 * 
 * At this point, the programmer should edit the resulting makefile
 * to add the statement sequence which builds the program from the 
 * compiled object code by replacing the '#' comment line.
 * 
 * The makefile has a hook for specifying compilation options through
 * symbol OPT. 
 * 
 * Written and placed in the public domain by Tim McGrath 4/2/87
 * 
 */
#include <stdio.h>
#define LINEMAX 80
#define MAINPGM "main"

long malloc();
char *strcat(),*strcpy();

FILE *makefile;

main(argc,argv)
    char **argv; 
    int argc;
{ 
  int dependent_count;
  char **dependents;
  int i;
  
  if ((makefile = fopen("makefile","w")) == 0L) {
    puts("Can't create makefile"); exit(1);
  }
  fprintf(makefile,"OPT = \n\n");
  depend_file(argc,argv,MAINPGM,"  ",".o");
  for (i = 1; i < argc; i++) {
    get_dependents(argv[i],&dependents,&dependent_count);
    depend_file(dependent_count,dependents,argv[i],".o","");
    free_space(dependents,dependent_count);
  }
  fclose(makefile); exit(0);
}

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--;
  }
  free(dp);
}

char *
file_exten(pgm_name,xtension,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,*xtension,*bufout;
{
  int i = 0;
  
  while (*pgm_name) {
    bufout[i++] = *pgm_name;
    if (*pgm_name++ == '.' && xtension[0] != '\0') {
      bufout[i++] = xtension[1]; 
      break;
    }
  }
  bufout[i] = '\0'; return(bufout);
}

depend_file(ct,flist,pgm_name,pgmx,filex)
/* 
 * 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
 *	    pgmx - extension for pgm_name file (or "" if none)
 *	    filex - extension for dependent file names (or "" if none)
 */
    char **flist,*pgm_name,*pgmx,*filex;
    int ct;
{
  int i;
  char buf[LINEMAX], add_name[LINEMAX], pname[LINEMAX];
  
  start_line(file_exten(pgm_name,pgmx,pname),buf);
  if (strcmp(pgm_name,MAINPGM)) strcat(strcat(buf," "),pgm_name);
  for (i = 1; i < ct; i++) {
    file_exten(flist[i],filex,add_name);
    if (columns(buf)+strlen(add_name)+1 >= LINEMAX - 1) {
      fputs(buf,makefile); fputc('\n',makefile);
      start_line(file_exten(pgm_name,pgmx,pname),buf);
    }
    strcat(strcat(buf," "),add_name);
  }
  fputs(buf,makefile); fputc('\n',makefile);
  if (strcmp(pgm_name,MAINPGM)) fprintf(makefile,"\tcc $(OPT) %s\n",pgm_name);
  else { 
    fputs("# ------- construction command goes here ---------",makefile);
    fputc('\n',makefile);
  }
}


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,"\t");
  if (columns(buf) < 16) strcat(buf,"\t");
  if (columns(buf) < 24) strcat(buf,"\t");
  strcat(buf,":");
}

columns(s)
/* 
 * Purpose: count the number of columns a line spans
 * Inputs:  s - the characters in a line
 * Returns: the number of columns (including tab expansion)
 */
    char *s;
{
  int col = 0;
  
  while (*s) {
    if (*s++ == '\t') while (++col & 7);
    else ++col;
  }
  return(col);
}

get_dependents(fn,depv,depc)
/* 
 * Purpose: return a list of files depending on a C source file
 * Inputs:  fn - name of the c source file
 * Outputs: depv - list of dependents (an array of pointers to filenames)
 *	    depc - number of dependents
 */
     char *fn,***depv;
     int *depc;
{
  char **lst;
  int i;

  lst = (char **) malloc(1024*sizeof(char *)); move_name(&lst[0],fn);
  fputs(fn,stdout); fputc('\n',stdout); i = 0;
  
  scan_file(lst,&i,fn); *depv = lst; *depc = i+1;
}

move_name(p,s)
/* 
 * Purpose: Allocate space for a new filename and copy it
 * Inputs:  p - location for new pointer to filename
 *	    s - pointer to file name
 */
     char **p,*s;
{
  *p = (char *) malloc(strlen(s)+1); strcpy(*p,s);
}

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

  fp = fopen(fn,"r");
  if (!fp) { fprintf(stdout,"Couldn't open file %s\n",fn); return; }

  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';
      if (add_name(file_name_list, last_list_used, ifn))
        scan_file(file_name_list, last_list_used, ifn);
    }
  }
  fclose(fp); return;
}

add_name(file_name_list, last_list_used, fn)
/* 
 * Purpose: Add a file name to the list if it's not there already
 * Inputs:  file_name_list - pointer to array of pointers to file names
 *	    last_list_used - last element in array with a filename
 *	    fn - name of file
 * Returns: 1 if file name added, 0 otherwise
 */
  char **file_name_list, *fn;
  int *last_list_used;
{
  int i;
  for (i = 0; i <= *last_list_used; i++)
    if (!strcmp(file_name_list[i],fn)) return(0);
  *last_list_used += 1;
  move_name(&file_name_list[*last_list_used],fn);
  return(1);
}
