/*
 *      MakeDMake v0.15 -- automatically construct a makefile for DMake
 *
 *      Basically it's Tim McGrath's 'MakeMake' from Fish 74,
 *      -----------------------------------------------------
 *
 *      but it has been slightly hacked, so that it got much more
 *      enterprising and workaholic, so to say.
 *
 *
 *      HOW TO:
 *
 *      You give it the names of all the C-files that are to produce your
 *      executable (but NOT #include'd .c or .h files!), and it will
 *      automatically scan them to find all dependencies, and produce a
 *      ready to use (in many cases) DMakeFile calling DCC with options
 *      you will need for normal compilation and linking. Other things
 *      can be added if needed by editing that file. In most cases it
 *      should be enough to give MakeDMake '#? or '*' on command-line.
 *
 *      For exemple, if you call it with the command-line like this:
 *
 *      MakeDMake file1.c file2.c file3.c ... filen.c
 *
 *      you will get something like that in your DMakeFile:
 *
 *      OPT1 =
 *      OPT2 =
 *
 *      ex_file         : file1.o file2.o file3.o ... filen.o
 *          dcc $(OPT1) -o ex_file  file1.o file2.o file3.o ... filen.o
 *
 *      filex.o :   filex.c ... header file list ...
 *          dcc $(OPT1) 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). MakeDMake only
 *      examines header files delimited by double quotes ("). System header
 *      files enclosed in angle brackets (< and >) are not examined.
 *      (That last bit of beautiful native-speaker English was almost
 *      literally taken from Tim's original comment. Now, back to my own
 *      personal variant...)
 *
 *      As you probably know, if you have all properly set, 'dmake'
 *      typed in the CLI with no mparameters, will look for the
 *      file called 'DMakeFile' in current directory and use its information
 *      to update/recompile etc. your files. And it can be made even easier
 *      with aliases. With two Csh aliases like that:
 *
 *          alias mdm "MakeDMake *.c"   ## for all C files in current dir
 *          alias go "DMake"            ## not so much really
 *          ## or "alias go DMake -f *make" if you can have the makefile
 *          ## name in the form <something>.make
 *
 *      I can compile most of the C programs with no effort whatsoever,
 *      as most of the DCC I need use are already set in the ENV:DCCOPTS.
 *      You can, however, recompile with your own standard set of options.
 *      Many other things here can be easilly changed, adjusted and
 *      then recompiled; also for other compilers than DICE. Try to do
 *      that changing the #defines first!
 *
 *      The original 'MakeMake' was in Public Domain, and this stuff here
 *      also is.
 *
 *                                  Piotr Obminski, November 1992
 */

#include <stdio.h>

#define LINEMAX 80
#define CC_NAME         "dcc"           /* or e.g. "lc" for Lattice */
#define MAIN_NAME       "main"
#define OUTFILE_NAME    "DMakeFile"     /* you only type 'dmake' with this! */

/* default OPT1 & OPT2 strings */
#define O2X_OPT         NULL            /* or e.g. "-v" */
#define C2O_OPT         NULL            /* or e.g. "-v -r -//" */

/* zero if we want call it 'DMakeFile' instead of the individualized
 * (unique) name in the form '<executable>.make'
 */
#define UNIQUE_OUT_NAME 0

char main_name[ 40 ] = MAIN_NAME;

FILE *OutFile;


main( argc, argv )
    char **argv;
    int argc;
{
    int dependent_count;
    char **dependents;
    char outfile_name[ 40 ] = OUTFILE_NAME;
    int i;

    /* no input, '?', 'h', 'H' or something starting with '-' --
     * -- he needs help! (we have no other options starting with '-',
     * so why not?
     */
    if ( argc < 2 || ( *argv[ 1 ] == '?' ) || ( *argv[ 1 ] == '-' ) ||
                    ( *argv[ 1 ] == 'h' ) || ( *argv[ 1 ] == 'H' ) ) {
        puts( "›1mMakeDMake v0.15›0m  ›2mPD Utility for creating›0m ›1mDMakeFiles.›0m" );
        puts( "----------------------------------------------------------" );
        puts( "›2mUSAGE:›0m  Give me names of your ›1msource files›0m and then we'll" );
        puts( "        get interactive. ›1mNow that you know -- START AGAIN!›0m" );
        exit ( 1 );
    }

    printf( "Name for the executable? (or ›1mRETURN›0m for '%s'): ", MAIN_NAME );
    {
        char temp[ 40 ] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
        gets( temp );
        if ( isalpha( *temp ) ) {
            strcpy( main_name, temp );

#if UNIQUE_OUT_NAME
                /* change name of the generated MakeFile to
                 * <executable>.make
                 */
                strcpy( outfile_name, temp );
                strcat( outfile_name, ".make" );
#endif

        }
        else {
            puts( "You're getting the default executable name." );
        }
        puts(     "--------------------------------------------------" );
    }

    if ( ( OutFile = fopen( outfile_name, "w" ) ) == 0L ) {
        printf( "Can't create %\n", outfile_name ); exit( 1 );
    }

    fputs( "# DMakeFile generated by MakeDMake v0.15\n\n", OutFile );
    fprintf( OutFile, "OPT1 = %s\nOPT2 = %s\n\n", O2X_OPT, C2O_OPT );
    depend_file( argc, argv, main_name, "  ", ".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( OutFile ); 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 );  /* here's the offender in the original --
     * -- it makes the program to hang...
     */
}

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 ];
    char bare_name[ LINEMAX ];

    {
        register short i = 0;
        do {
            bare_name[ i ] = pgm_name[ i ];
        }
        while ( pgm_name[ i ] && ( pgm_name[ i++ ] != '.' ) );
        bare_name[ --i ] = '\0';
    }

    start_line( file_exten( pgm_name, pgmx, pname ), buf );
    if ( strcmp( pgm_name, main_name ) ) 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, OutFile ); fputs( "\\\n", OutFile );
            continue_line( file_exten( NULL, NULL, pname ), buf );
        }
        strcat( strcat( buf," " ), add_name );
    }
    fputs( buf, OutFile ); fputc( '\n', OutFile );

    if ( strcmp( pgm_name, main_name ) ) {
        fprintf( OutFile,"\t%s $(OPT2) -c %s.o %s.c\n\n", CC_NAME, bare_name, bare_name );
    }
    else {
        fprintf( OutFile, "\t%s $(OPT1) -o %s ", CC_NAME, main_name );
        output_objects( flist, ct );
        fputs( "\n\n", OutFile );
    }
}

output_objects( files, count )
    char **files;
    int count;
{
    unsigned short i, col, len;
    char arr[ 40 ], *brk1, *brk2;
    register char *p;

    col = 32;       /* more or less initial column */

    for ( i = 1; i < count; i++ ) {

        strcpy( arr, files[ i ] );

        len = strlen( arr );
        col += len;

        if ( col >= LINEMAX - 2 ) {
            brk1 = "\\\n\t\t\t";
            brk2 = "";
            col = 32 + len + 1;
        }
        else {
            brk1 = " ";
            brk2 = "";
        }

        p = (char *)strchr( arr, '.' );
        *++p = 'o';

        fprintf( OutFile, "%s%s%s", brk1, arr, brk2 );
    }
}


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, ":" );
}

continue_line( with_name, buf )
/*
 * Purpose: let the line continue after '\' and '\n'
 * 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 " );
}


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 ) {
        /* though I'm NOT using any tabs! */
        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;
    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 );
}

