/*---------------------------------------------------------------------------*/
/* mklib.c                                                                   */
/*                                                                           */
/* Copyright (c) 2011-2017 Texas Instruments Incorporated                    */
/* http://www.ti.com/                                                        */
/*                                                                           */
/*  Redistribution and  use in source  and binary forms, with  or without    */
/*  modification,  are permitted provided  that the  following conditions    */
/*  are met:                                                                 */
/*                                                                           */
/*     Redistributions  of source  code must  retain the  above copyright    */
/*     notice, this list of conditions and the following disclaimer.         */
/*                                                                           */
/*     Redistributions in binary form  must reproduce the above copyright    */
/*     notice, this  list of conditions  and the following  disclaimer in    */
/*     the  documentation  and/or   other  materials  provided  with  the    */
/*     distribution.                                                         */
/*                                                                           */
/*     Neither the  name of Texas Instruments Incorporated  nor the names    */
/*     of its  contributors may  be used to  endorse or  promote products    */
/*     derived  from   this  software  without   specific  prior  written    */
/*     permission.                                                           */
/*                                                                           */
/*  THIS SOFTWARE  IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS    */
/*  "AS IS"  AND ANY  EXPRESS OR IMPLIED  WARRANTIES, INCLUDING,  BUT NOT    */
/*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR    */
/*  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT    */
/*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,    */
/*  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL  DAMAGES  (INCLUDING, BUT  NOT    */
/*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,    */
/*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY    */
/*  THEORY OF  LIABILITY, WHETHER IN CONTRACT, STRICT  LIABILITY, OR TORT    */
/*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE    */
/*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.     */
/*                                                                           */
/*---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
 * This is mklib version  for PRU compiler  
 * run-time support library                                                  
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
 * This source code is automatically generated.  You should not need to      
 * modify it if you have not added files to the compiler RTS library.        
 *---------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------- 
 * This source code is specific to compiler RTS library version .  
 * It uses a Makefile which understands which files belong in each version of
 * the library, and what extra options are needed for each variant of the RTS.
 * The Makefile uses 'make' variables to activate major feature clusters.
 * This source code and the Makefile share knowledge of these groups.  The
 * format of the Makefile, as well as the implementation of this source code,
 * is subject to change without notice.
 *---------------------------------------------------------------------------*/ 

#include <stdio.h>
#include <assert.h>
#include <string>
#include <sstream>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <time.h>

using namespace std;

const char *isa = "PRU";
const char *version = "";
int         buildmodel = 2011;
const char *c_dir_name = "PRU_C_DIR";

#define MAX_PREDICATE 4

struct library_t { 
    const char *name;
    const char *predicates[MAX_PREDICATE+1];
};

typedef struct library_t library_t;

library_t LIBRARIES[] = {
    { "rtspruv1_le.lib", { "V1","LITTLE_ENDIAN" } },
    { "rtspruv1_le_eh.lib", { "EXCEPTIONS","FULL_PORTABLE_EH","V1","LITTLE_ENDIAN" } },
    { "rtspruv1_be.lib", { "V1","BIG_ENDIAN" } },
    { "rtspruv3_be_eh.lib", { "V3","BIG_ENDIAN","EXCEPTIONS","FULL_PORTABLE_EH" } },
    { "rtspruv3_be.lib", { "BIG_ENDIAN","V3" } },
    { "rtspruv1_be_eh.lib", { "FULL_PORTABLE_EH","BIG_ENDIAN","EXCEPTIONS","V1" } },
    { "rtspruv2_be_eh.lib", { "V2","EXCEPTIONS","BIG_ENDIAN","FULL_PORTABLE_EH" } },
    { "rtspruv2_be.lib", { "V2","BIG_ENDIAN" } },
    { "rtspruv2_le.lib", { "V2","LITTLE_ENDIAN" } },
    { "rtspruv3_le_eh.lib", { "V3","LITTLE_ENDIAN","EXCEPTIONS","FULL_PORTABLE_EH" } },
    { "rtspruv3_le.lib", { "V3","LITTLE_ENDIAN" } },
    { "rtspruv2_le_eh.lib", { "LITTLE_ENDIAN","V2","EXCEPTIONS","FULL_PORTABLE_EH" } },
};

const char *DEFAULT_OPTIONS = " -O --embed_icode --keep_asm --diag_warning=225 --quiet";

library_t *find_library(const string &pattern)
{
    for (size_t i = 0; i < (sizeof LIBRARIES / sizeof *LIBRARIES); i++)
        if (!pattern.compare(LIBRARIES[i].name)) return &LIBRARIES[i];

    return NULL;
}

string pattern;
string index_library_path;

string name;
bool all = false;

string options;
string extra_options;
string internal_options;

string install_to;
string compiler_bin_dir;

FILE *logfile;
string logfile_name;
string tmpdir;
bool user_defined_tmpdir = false;

string gmake = "gmake";
int parallel;

bool quiet = false;
bool dryrun = false;
bool verbose = false;
bool keep_scratch_dir = false;

void list_libraries()
{
    for (size_t i = 0; i < (sizeof LIBRARIES / sizeof *LIBRARIES); i++)
        printf("%s ", LIBRARIES[i].name);
    printf("\n");
}

void usage()
{
    printf("Usage: mklib [--pattern=rts6200.lib --index=libc.a ..]\n\n");
    printf("This is mklib version %s for %s compiler run-time support libraries.\n\n", version, isa);

    printf("mklib is used to build a compiler RTS library from source; it is specific to\n"
           "compiler RTS library version %s.\n\n", version);

    printf("    --index=FILENAME\n\n"
           "                        The index library (libc.a) for this release.  Used to\n"
           "                        find a template library for custom builds, and to find\n"
           "                        the source files (located in the subdirectory \"src\").\n"
           "                        REQUIRED.\n\n");

    printf("    --pattern=FILENAME\n\n"
           "                        Pattern for building a library.  If neither\n"
           "                        --extra_options nor --options are specified, the\n"
           "                        library will be the standard library with the standard\n"
           "                        options for that library.  If either --extra_options\n"
           "                        or --options are specified, the library is a custom\n"
           "                        library with custom options.  REQUIRED unless --all is\n"
           "                        used.\n\n");

    printf("    --all\n\n"
           "                        Build all standard libraries at once.\n\n");

    printf("    --install_to=DIRECTORY\n\n"
           "                        The directory into which to write the library.  For a\n"
           "                        standard library, this defaults to the same directory\n"
           "                        as the index library (libc.a).  For a custom library,\n"
           "                        this option is REQUIRED.\n\n");

    printf("    --compiler_bin_dir=DIRECTORY\n\n"
           "                        The directory where the compiler executables are.\n"
           "                        When invoking mklib directly, the executables should\n"
           "                        be in the PATH, but if they are not, this option must\n"
           "                        be used to tell mklib where they are.  This option is\n"
           "                        primarily for use when mklib is invoked by the linker.\n\n");

    printf("    --name=FILENAME\n\n"
           "                        File name for the library with no directory part.\n"
           "                        Only useful for custom libraries.\n\n");

    printf("    --options='STR'\n\n"
           "                        Options to use when building the library.  The default\n"
           "                        options (see below) are REPLACED by this string.  If\n"
           "                        this option is used, the library will be a custom\n"
           "                        library.\n\n");

    printf("    --extra_options='STR'\n\n"
           "                        Options to use when building the library.  The default\n"
           "                        options (see below) are also used.  If this option is\n"
           "                        used, the library will be a custom library.\n\n");

    printf("    --list_libraries\n\n"
           "                        List the libraries this script is capable of building\n"
           "                        and exit.  ordinary system-specific directory.\n\n");

    printf("    --log=FILENAME\n\n"
           "                        Save the build log as FILENAME\n\n");

    printf("    --tmpdir=DIRECTORY\n\n"
           "                        Use DIRECTORY for scratch space instead of the\n"
           "                        ordinary system-specific directory.\n\n");

    printf("    --keep_scratch\n\n"
           "                        Don't delete scratch directory when finished\n\n");

    printf("    --gmake=FILENAME    Gmake-compatible program to invoke instead of \"gmake\"\n"
           "    --parallel=N        Compile N files at once (\"gmake -j N\")\n\n");

    printf("    --query=FILENAME    Does this script know how to build FILENAME?\n\n");

    printf("    --help|h            Display this help\n"
           "    --quiet|q           Operate silently\n"
           "    --verbose|v         Extra information to debug this executable\n");

    printf("Examples:\n\n");

    printf("    To build all standard libraries and place them in the compiler's\n"
           "    library directory:\n\n");

    printf("    mklib --all --index=$C_DIR/lib\n\n");

    printf("    To build one standard library and place it in the compiler's library\n"
           "    directory:\n\n");

    printf("    mklib --pattern=rts6200.lib --index=$C_DIR/lib\n\n");

    printf("    To build a custom library that is just like rts6200.lib, but has symbolic\n"
           "    debugging support enabled:\n\n");

    printf("    mklib --pattern=rts6200.lib --extra_options=\"-g\" --index=$C_DIR/lib --install_to=$Project/Debug --name=rts6200_debug.lib\n\n");

    printf("Default options: [%s]\n\n", DEFAULT_OPTIONS);

    printf("Standard library names: "); 
    list_libraries();

    exit(1);
}

/*===========================================================================*/
/* System-specific functions                                                 */
/*===========================================================================*/

#include <getopt.h>

#if __APPLE__ || unix || __unix || __hpux /* 'unix' DEFINED BY MOST COMPILERS  */
#include <unistd.h>
#include <dirent.h>

string directory_part(const string &pathname)
{
    string::size_type pos = pathname.find_last_of('/');
    if (pos == string::npos) return ".";
    else return pathname.substr(0, pos);
}

const string splice(const string &dir, const string &base)
{
    return dir + '/' + base;
}

const string path_delim(void) 
{
    return ":";
}

bool path_is_absolute(const string &pathname)
{
    return !pathname.empty() && pathname[0] == '/';
}

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

bool make_directory(const string &pathname)
{
    return mkdir(pathname.c_str(), 0775) == 0;
}

bool set_working_directory(const string &pathname)
{
    return chdir(pathname.c_str()) == 0;
}

void discover_tmpdir(void)
{
    char *dir = getenv("TMPDIR"); /* POSIX */
    tmpdir = dir ? dir : "/tmp";
}

const string getcwd_or_die(void);

const string absolute_path(const string &pathname)
{
    if (path_is_absolute(pathname)) return pathname;
    const string pwd = getcwd_or_die();
    if (pathname.compare(".") == 0) return pwd;
    else return splice(pwd, pathname);
}

const string unquote(const string &in) { return in; }

bool directory_exists(const string &path)
{
    DIR *dp = opendir(path.c_str());
    if (dp == NULL) return false;
    closedir(dp);
    return true;
}
  
void remove_directory_recursive(const string &path) 
{
    const char *cpath = path.c_str();

    DIR *dp = opendir(cpath);

    if (dp == NULL)
    {
        if (errno == ENOTDIR)
        {
            if (remove(cpath))
            {
                const char *problem = strerror(errno);
                if (logfile) fprintf(logfile, ">> ERROR: mklib: could not remove %s: %s\n", cpath, problem);
                fprintf(stderr, ">> ERROR: mklib: could not remove %s: %s\n", cpath, problem);
                exit(1);
            }
            return;
        }
        else
        {
            const char *problem = strerror(errno);
            if (logfile) fprintf(logfile, ">> ERROR: mklib: could not open directory %s for deleting: %s\n", cpath, problem);
            fprintf(stderr, ">> ERROR: mklib: could not open directory %s for deleting: %s\n", cpath, problem);
            exit(1);
        }
    }
 
    struct dirent *entry;

    while((entry = readdir(dp)))
    {
        if (strcmp(entry->d_name, ".") &&
            strcmp(entry->d_name, ".."))
        {
            remove_directory_recursive(splice(path, entry->d_name));
        }
    }

    if (rmdir(cpath))
    {
        const char *problem = strerror(errno);
        if (logfile) fprintf(logfile, ">> ERROR: mklib: could not remove %s: %s\n", cpath, problem);
        fprintf(stderr, ">> ERROR: mklib: could not remove %s: %s\n", cpath, problem);
        exit(1);
    }
 
    closedir(dp);
}    

#elif _WIN32

#include <windows.h> // For GetFullPathName, GetShortPathName
#include <direct.h>

#define getcwd _getcwd
#define chdir  _chdir
#define mkdir  _mkdir

#define PATH_MAX MAX_PATH

string directory_part(const string &pathname)
{
    char drive[MAX_PATH];
    char dir  [MAX_PATH];
    char fname[MAX_PATH];
    char ext  [MAX_PATH];

    char outdir[MAX_PATH];

    _splitpath(pathname.c_str(), drive, dir, fname, ext);
    _makepath(outdir, drive, dir, "", "");

    if (strlen(outdir) == 0) return ".";
    return outdir;
}

bool path_is_absolute(const string &pathname)
{
    return directory_part(pathname) != ".";
}

const string splice(const string &dir, const string &base)
{
    return dir + '\\' + base;
}

const string path_delim(void) 
{
    return ";";
}

bool make_directory(const string &pathname)
{
    return mkdir(pathname.c_str()) == 0;
}

bool set_working_directory(const string &pathname)
{
    return chdir(pathname.c_str()) == 0;
}

void discover_tmpdir(void)
{
    char *dir = getenv("TEMP"); /* Windows standard */
    if (!dir) dir = getenv("TMP"); /* Old DOS variant */

    if (!dir) 
    {
        fprintf(stderr, ">> ERROR: mklib: environment variable TEMP not set\n");
        exit(1);
    }

    tmpdir = dir;
}

#include <cctype>
#include <algorithm>

const string downcase(const string uppercase)
{
    string result(uppercase);
    std::transform(result.begin(), result.end(), result.begin(),
                   ::tolower);
    return result;
}

const string upcase(const string lowercase)
{
    string result(lowercase);
    std::transform(result.begin(), result.end(), result.begin(),
                   ::toupper);
    return result;
}

/*---------------------------------------------------------------------------*/
/* If the string is quoted, and the quotes match, remove the outermost       */
/* quotes.                                                                   */
/*---------------------------------------------------------------------------*/
const string unquote(const string &in)
{
    size_t len = in.length();

    if (len >= 2)
    {
        char first = in[0];
        char last  = in[len-1];

        if (first == last &&
            (first == '"' || first == '\''))
        {
            return in.substr(1,len-2);
        }
    }

    return in;
}

/*---------------------------------------------------------------------------*/
/* Return the absolute path to a file.  If the file is not already an        */
/* absolute path, it will be considered relative to the current directory.   */
/* In addition, this will cook the path so that it is palatable to gmake     */
/*---------------------------------------------------------------------------*/
const string absolute_path(const string &pathname)
{
    /*-----------------------------------------------------------------------*/
    /* The user can mistakenly introduce quotes into environment variables.  */
    /* Quotes in TEMP (for example), are treated as literal quotes (part of  */
    /* the actual filename).                                                 */
    /*                                                                       */
    /* For example, this will add literal quotes to TEMP:                    */
    /*                                                                       */
    /*    set TEMP="C:\where ever\tmp"                                       */
    /*                                                                       */
    /* The following work, and do not embed literal quotes:                  */
    /*                                                                       */
    /*    set TEMP=C:\where ever\tmp                                         */
    /*    set "TEMP=C:\where ever\tmp"                                       */
    /*                                                                       */
    /* When presented with a pathname, remove the quotes.  This assumes the  */
    /* user merely mistakenly added the quotes, rather than having a real    */
    /* file name with embedded quotes (possible, but we just hope they don't */
    /* use such a name for a PATH, TEMP, or compiler installation ...)       */
    /*-----------------------------------------------------------------------*/
    const string unquoted_pathname = unquote(pathname);

    /*-----------------------------------------------------------------------*/
    /* Get the absolute path name.                                           */
    /*-----------------------------------------------------------------------*/
    const char *orig = unquoted_pathname.c_str();

    char full_path[MAX_PATH];
    size_t full_sz = GetFullPathName(orig, MAX_PATH, full_path, NULL);

    if (full_sz == 0 || full_sz > MAX_PATH)
    {
        fprintf(stderr, ">> ERROR: mklib: could not get a full pathname for %s\n", orig);
        exit(1);
    }

    string result;

    /*-----------------------------------------------------------------------*/
    /* Now convert it to "short" form.  We do this so that we don't have to  */
    /* worry about spaces in filenames, which are a special problem for gmake*/
    /*-----------------------------------------------------------------------*/
    /* If the value of tmpdir contains both a colon (it will if it is of the */
    /* form C:\whatever) and a space, gmake will fail with "multiple target  */
    /* patterns" because gmake uses colons to separate parts of patterns.    */
    /* Use the 8.3 "short" pathname to avoid this problem.                   */
    /*-----------------------------------------------------------------------*/
    char short_path[MAX_PATH];
    size_t short_sz = GetShortPathName(full_path, short_path, MAX_PATH);

    if (short_sz == 0 || short_sz > MAX_PATH)
    {
        /*-------------------------------------------------------------------*/
        /* If the file does not exist, GetShortPathName will fail.  This is  */
        /* not particularly a problem unless we're going to pass the         */
        /* pathname to gmake.  Make sure to create directories, files,       */
        /* etc. before calling this function.  The only one we can't do this */
        /* for is the temporary library pathname.  Don't give an error if we */
        /* can't get the short path name.                                    */
        /*-------------------------------------------------------------------*/
        result = full_path;
    }
    else result = short_path;

    /*-----------------------------------------------------------------------*/
    /* Now replace all backslashes with forward slashes.  gmake can't handle */
    /* backslashes in all contexts, but Windows API functions can handle     */
    /* forward shashes, so just use those.                                   */
    /*-----------------------------------------------------------------------*/
    for (size_t i=0; i < result.size(); i++)
       if (result[i] == '\\') result[i] = '/';

    /*-----------------------------------------------------------------------*/
    /* Downcase the file extension here because gmake 3.81 emits a bizarre   */
    /* error message (probably indicating NULL dereference) if any part of   */
    /* the filename part of the pathname has an uppercase character.  The    */
    /* case on the directory part does not matter.                           */
    /*-----------------------------------------------------------------------*/
    return downcase(result);
}

/*---------------------------------------------------------------------------*/
/* mkdtemp not provided in the cross-compiler we use (win32_g++-4.1.1)       */
/*---------------------------------------------------------------------------*/
const char *mkdtemp(char pattern[])
{
    /* The pattern MUST end with six X's */

    char *start;
    if ((start = strstr(pattern, "XXXXXX")) == NULL || *(start+6) != '\0')
    {
        errno = EINVAL;
        return NULL;
    }

    /*-----------------------------------------------------------------------*/
    /* This algorithm is weak, but better than nothing.  We don't expect     */
    /* expect much danger from collisions (fingers crossed).  Hopefully      */
    /* we'll get a real version of mkdtemp eventually.                       */
    /*-----------------------------------------------------------------------*/
    srand(time(NULL));

    for (int attempt = 0; attempt < TMP_MAX; attempt++)
    {
        for (int i=0; i < 6; i++)
        {
            const char *chars = 
                "abcdefghijklmnopqrstuvwxyz"
                "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 
                "0123456789";
            size_t nchars = strlen(chars);
            int r = rand();
            unsigned p = (long long)r * nchars / (RAND_MAX+1);
            assert(p < nchars);
            start[i] = chars[p];
        }

        if (make_directory(pattern)) return pattern;
    }

    /*-----------------------------------------------------------------------*/
    /* Could not create a unique temp directory.                             */
    /*-----------------------------------------------------------------------*/
    return NULL;
}

bool directory_exists(const string &path)
{
   DWORD ftyp = GetFileAttributes(path.c_str());
   if (ftyp == INVALID_FILE_ATTRIBUTES)
      return false;

   if (ftyp & FILE_ATTRIBUTE_DIRECTORY)
      return true;

   return false;
}

void remove_directory_recursive(const string &path)
{
   DWORD gwdRet;
   char prev_working_directory[MAX_PATH];

   // Get the current directory so we can restore the working directory
   if (GetCurrentDirectory(MAX_PATH, prev_working_directory) == 0)
   {
      fprintf(stderr, ">> ERROR: mklib: Failed to get current working directory\n");
      exit(1);
   }

   // Change to the path we're trying to delete. We do this because FindFileData
   // will contain the relative path name of subdirectories. Instead of trying
   // to reconstruct a full path, just change the working directory.
   if (!SetCurrentDirectory(path.c_str()))
   {
      fprintf(stderr, ">> ERROR: mklib: Failed to set the current directory\n");
      exit(1);
   }

   WIN32_FIND_DATA FindFileData;
   HANDLE hfind = FindFirstFile("*", &FindFileData);

   while (hfind != INVALID_HANDLE_VALUE)
   {
      if (strcmp(FindFileData.cFileName, ".") &&
          strcmp(FindFileData.cFileName, ".."))
      {
         if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            remove_directory_recursive(FindFileData.cFileName);
         else
            if (DeleteFile(FindFileData.cFileName) ==  0)
            {
               fprintf(stderr, ">> ERROR: mklib: Failed to delete file %s\n", FindFileData.cFileName);
               exit(1);
            }
      }

      if (!FindNextFile(hfind, &FindFileData))
         break;
   }
   FindClose(hfind);

   if (!SetCurrentDirectory(prev_working_directory))
   {
      fprintf(stderr, ">> ERROR: mklib: Failed to restore the current directory\n");
      exit(1);
   }

   if (RemoveDirectory(path.c_str()) == 0)
   {
      fprintf(stderr, ">> ERROR: mklib: Failed to remove the directory %s\n", path.c_str());
      exit(1);
   }
}

#endif

/*===========================================================================*/
/* Not-quite-so-system-specific functions                                    */
/*===========================================================================*/

const string absolute_directory_part(const string &pathname)
{
    return directory_part(absolute_path(pathname));
}

const string getcwd_or_die(void)
{
    char pwd[PATH_MAX];

    if (getcwd(pwd, PATH_MAX) == NULL)
    {
        const char *problem = strerror(errno);
        fprintf(stderr, ">> ERROR: mklib: could not discover current directory: %s\n", problem);
        exit(1);
    }

    return pwd;
}

void make_directory_or_die(const string &pathname)
{
    if (!make_directory(pathname) && !directory_exists(pathname))
    {
        const char *problem = strerror(errno);
        if (logfile) fprintf(logfile, ">> ERROR: mklib: Could not create directory %s: %s\n", pathname.c_str(), problem);
        fprintf(stderr, ">> ERROR: mklib: Could not create directory %s: %s\n", pathname.c_str(), problem);
        exit(1);
    }
}

void set_working_directory_or_die(const string &pathname)
{
    if (!set_working_directory(pathname))
    {
        const char *problem = strerror(errno);
        if (logfile) fprintf(logfile, ">> ERROR: mklib: Could not set working directory %s: %s\n", pathname.c_str(), problem);
        fprintf(stderr, ">> ERROR: mklib: Could not set working directory directory %s: %s\n", pathname.c_str(), problem);
        exit(1);
    }
}

string filename_extension(const string &pathname)
{
    string::size_type pos = pathname.find_last_of('.');
    if (pos == string::npos) return "";
    else return pathname.substr(pos+1);
}

bool file_exists(const string &pathname)
{
    FILE *f = fopen(pathname.c_str(), "r");
    if (f) { fclose(f); return true; } 
    else return false;
}

FILE* fopen_or_die(const string& filename, char* mode)
{
   FILE* f = fopen(filename.c_str(), mode);
   if (!f)
   {
      const char *problem = strerror(errno);
      if (logfile) fprintf(logfile, ">> ERROR: mklib: could not open %s with mode %s: %s\n", filename.c_str(), mode, problem);
      fprintf(stderr, ">> ERROR: mklib: could not open %s with mode %s: %s\n", filename.c_str(), mode, problem);
      exit(1);
   }

   return f;
}

string make_temp_directory_or_die(const string &libname)
{
    if (user_defined_tmpdir)
    {
        if (!directory_exists(tmpdir))
           make_directory_or_die(tmpdir);
        string scratch_dir(splice(tmpdir, libname)); 
        if (!directory_exists(scratch_dir))
           make_directory_or_die(scratch_dir);
        return scratch_dir;
    }
    else
    {
        string scratch_dir_pattern(splice(tmpdir, "TI_MKLIBXXXXXX"));
        char *buf = new char[scratch_dir_pattern.length() + 1];
        strcpy(buf, scratch_dir_pattern.c_str());
        if (mkdtemp(buf))
        {
            string result(buf);
            delete [] buf;
            return result;
        }
        else
        {
            const char *problem = strerror(errno);
            if (logfile) fprintf(logfile, ">> ERROR: mklib: Could not create temp directory in %s: %s\n", tmpdir.c_str(), problem);
            fprintf(stderr, ">> ERROR: mklib: Could not create temp directory in %s: %s\n", tmpdir.c_str(), problem);
            exit(1);
        }
    }
}
/*===========================================================================*/
/*===========================================================================*/

/*---------------------------------------------------------------------------*/
/* Split a PATH, PATHEXT, or C_DIR on ';' or ':' as appropriate              */
/*---------------------------------------------------------------------------*/
#include <vector>
const vector<string> tokenize(const string &input, const string &delim)
{
    vector<string> result;

    vector<string>::size_type begin = 0;
    vector<string>::size_type end = input.find_first_of(delim, begin);

    while (end != string::npos || begin != string::npos)
    {
        result.push_back(input.substr(begin, end - begin));
        begin = input.find_first_not_of(delim, end);
        end = input.find_first_of(delim, begin);
    }
    
    return result;
}

/*---------------------------------------------------------------------------*/
/* Double quote a string which will be passed through system() in case the   */
/* string has space characters.  On Windows, we always use the "short"       */
/* pathname, which never has spaces, but there could still be spaces in the  */
/* pathname on Unix.                                                         */
/*---------------------------------------------------------------------------*/
const string quote(const string &quotee)
{
    return string("\"") + quotee + "\"";
}

/*---------------------------------------------------------------------------*/
/* Move or copy the file to a new location.                                  */
/*---------------------------------------------------------------------------*/
void copy_or_die(const string &src_name, const string &dst_name)
{
    const char *src = src_name.c_str();
    const char *dst = dst_name.c_str();

    if (rename(src, dst))
    {
        /*-------------------------------------------------------------------*/
        /* If the rename fails (probably due to cross-filesystem move), the  */
        /* original file is untouched, as well as the original destination,  */
        /* if it exists.  Copy it instead.                                   */
        /*-------------------------------------------------------------------*/
        FILE *in = fopen_or_die(src_name, "rb");
        FILE *out = fopen_or_die(dst_name, "wb");

        char buf[BUFSIZ];

        size_t nread, nwritten;

        while (!feof(in) && !ferror(in))
        {
            nread = fread(buf, 1, sizeof(buf), in); 

            if (nread < sizeof(buf) && ferror(in))
            {
                const char *problem = strerror(errno);
                if (logfile) fprintf(logfile, ">> ERROR: mklib: fread error on %s: %s\n", src, problem);
                fprintf(stderr, ">> ERROR: mklib: fread error on %s: %s\n", src, problem);
                exit(1);
            }

            if (nread)
            {
                nwritten = fwrite(buf, 1, nread, out);

                if (nwritten < nread && ferror(out))
                {
                    const char *problem = strerror(errno);
                    if (logfile) fprintf(logfile, ">> ERROR: mklib: fwrite error on %s: %s\n", dst, problem);
                    fprintf(stderr, ">> ERROR: mklib: fwrite error on %s: %s\n", dst, problem);
                    exit(1);
                }
            }
        }

        fclose(in);
        fclose(out);
    }
}

/*===========================================================================*/
/* Helper program search                                                     */
/*===========================================================================*/

/*---------------------------------------------------------------------------*/
/* When invoked from CCS, we don't need the compiler tools in the path,      */
/* because CCS invokes the compiler (e.g. cl6x) with an absolute path.  When */
/* executed this way, the compiler executes the linker with an absolute path */
/* (if the linker exists in the same directory as the compiler shell).  The  */
/* linker knows the absolute path to mklib, because it was found through     */
/* <TRG>_C_DIR.  The linker invokes mklib using an absolute path, passing it */
/* the absolute path to the compiler as a parameter.  Now, mklib requires    */
/* unzip (InfoZIP unzip 5.51 or later), gmake, and sh; these might not be in */
/* the user's path, so we need to try to find them.  We will use a heuristic */
/* to try to get them from CCS.                                              */
/*---------------------------------------------------------------------------*/
/* We will not modify PATH; instead, we will invoke the tools with an        */
/* absolute pathname.                                                        */
/*---------------------------------------------------------------------------*/
/* For CCS 5.1 and later, the environment variable CCS_UTILS_DIR points to a */
/* subdirectory in the installed CCS product.  It contains two directories:  */
/*                                                                           */
/*  %CCS_UTILS_DIR%\cygwin (contains unzip.exe and sh.exe)                   */
/*  %CCS_UTILS_DIR%\bin (contains gmake.exe)                                 */
/*---------------------------------------------------------------------------*/
/* For CCS 4.x and 5.0, if the XDC tools are installed, and the              */
/* environment variable XDCTOOLS is set, it points to the installed XDC      */
/* product.  It contains two directories:                                    */
/*                                                                           */
/*  %XDCTOOLS% (contains gmake.exe)                                          */
/*  %XDCTOOLS%\bin (contains unzip.exe and sh.exe)                           */
/*---------------------------------------------------------------------------*/

const string do_nothing_args(const string name)
{
    if (name.compare("unzip") == 0) return "-v";
    if (name.compare("gmake") == 0) return "--version";
    if (name.compare("sh") == 0) return "-c true";
    assert(0 && "Invalid argument to do_nothing_args");
}

#if _WIN32
/*---------------------------------------------------------------------------*/
/* Return the absolute pathname of a program.  If directory is given, search */
/* that directory; otherwise, search the user's PATH.                        */
/*---------------------------------------------------------------------------*/
bool find_program_search_path(const char *directory,
                              const string program, 
                              string &fullpath)
{
    /*-----------------------------------------------------------------------*/
    /* The Windows function SearchPath searches for an executable in the     */
    /* given diretctory, or if the given directory is NULL, in the user's    */
    /* PATH.  However, it is quite cantanerkous, and doesn't seem to match   */
    /* up with its documentation.  It doesn't seem to iterate over PATHEXT.  */
    /* For some reason it doesn't write to the output buffer if it is of     */
    /* exactly the right size.  In fact, for one test case, the buffer had   */
    /* to be at least 9 bytes larger than needed before SearchPath actually  */
    /* wrote to it.  I don't know how to tell from the return value whether  */
    /* it successfully wrote or not, and I don't know if I can rely on       */
    /* GetError or that the output buffer is no longer an empty string.  For */
    /* that reason, only give it one chance, with MAX_PATH.                  */
    /*-----------------------------------------------------------------------*/
    static const char *PATHEXT = getenv("PATHEXT");

    if (!PATHEXT) PATHEXT = ".EXE";

    vector<string> exts(tokenize(PATHEXT, path_delim()));

    char realname[MAX_PATH];

    for (size_t i = 0; i < exts.size(); i++)
    {
        const string &ext = exts[i];
    
        size_t sz = SearchPath(directory, program.c_str(), ext.c_str(),
                               MAX_PATH, realname, NULL);
        
        if (sz > 0 && sz < MAX_PATH)
        {
            assert(strlen(realname) == sz);
            fullpath = absolute_path(realname);
            return true;
        }
    }
    return false;
}
#else
bool find_program_search_path(const char *directory,
                              const string program,
                              string &fullpath)
{
    if (directory)
    {
        string pathname(splice(directory, program));
        if (file_exists(pathname)) 
        {
            fullpath = pathname;
            return true;
        }
    }
    else
    {
        static const char *PATH = getenv("PATH");
        if (PATH)
        {
            vector<string> paths(tokenize(PATH, path_delim()));
            for (size_t i = 0; i < paths.size(); i++)
                if (find_program_search_path(paths[i].c_str(),
                                             program,
                                             fullpath))
                    return true;
        }
    }
    return false;
}
#endif

/*---------------------------------------------------------------------------*/
/* Find the program and return the absolute pathname.                        */
/*---------------------------------------------------------------------------*/
bool find_program(const string program, string &abs_path)
{
    static const char *CCS_UTILS_DIR = getenv("CCS_UTILS_DIR");
    static const char *XDCROOT       = getenv("XDCROOT");

    /*=======================================================================*/
    /* If the program is already an absolute pathname (the user specified an */
    /* absolute pathname with --gmake), just use that.                       */
    /*=======================================================================*/
    if (path_is_absolute(program))
    {
        abs_path = absolute_path(program);
        return true;
    }

    /*=======================================================================*/
    /* Try certain pre-defined CCS-installed directories.                    */
    /*=======================================================================*/
    for (int pass = 0; pass < 4; pass++)
    {
        string root;

        switch(pass)
        {
            /*---------------------------------------------------------------*/
            /* In CCS 5.1 or later, the environment variable CCS_UTILS_DIR   */
            /* indicates the appropriate executables have been installed as  */
            /* part of core CCS.                                             */
            /*---------------------------------------------------------------*/
            case 0: 
            {
                if (!CCS_UTILS_DIR) continue;
                root = splice(unquote(CCS_UTILS_DIR), "cygwin");
                break;
            }
            case 1: 
            {
                if (!CCS_UTILS_DIR) continue;
                root = splice(unquote(CCS_UTILS_DIR), "bin");
                break;
            }
            /*---------------------------------------------------------------*/
            /* In CCS 4.x, if the environment variable XDCROOT is set, the   */
            /* user chose to install XDC as part of the CCS installation.    */
            /*---------------------------------------------------------------*/
            case 2: 
            {
                if (!XDCROOT) continue;
                root = unquote(XDCROOT);
                break;
            }
            case 3: 
            {
                if (!XDCROOT) continue;
                root = splice(unquote(XDCROOT), "bin");
                break;
            }
        }

        if (find_program_search_path(root.c_str(), program, abs_path))
            return true;
    }

    /*=======================================================================*/
    /* We give CCS_UTILS_DIR and XDCROOT precedence over the user's PATH. If */
    /* the program has still not been found, then look in the user's PATH.   */
    /* The user must have a viable version of the program available (e.g.    */
    /* InfoZIP 5.51 or better).                                              */
    /*=======================================================================*/
    if (find_program_search_path(NULL, program, abs_path)) return true;

    return false;
}

/*---------------------------------------------------------------------------*/
/* Find the program and return the absolute pathname.                        */
/*---------------------------------------------------------------------------*/
const string find_program_or_die(const string program)
{
    string fullpath;

    if (find_program(program, fullpath)) return fullpath;

    /*-----------------------------------------------------------------------*/
    /* If the program is "gmake", and it wasn't found, look for "make" and   */
    /* hope that it is GNU make.                                             */
    /*-----------------------------------------------------------------------*/
    if (program == "gmake" && find_program("make", fullpath)) return fullpath;

    /*-----------------------------------------------------------------------*/
    /* Nowhere to be found.                                                  */
    /*-----------------------------------------------------------------------*/
    const char *msg = ">> ERROR: mklib: could not find program \"%s\", required for building libraries.  Modify the PATH environment variable to contain a directory containing this program.\n";
    if (logfile) fprintf(logfile, msg, program.c_str());
    fprintf(stderr, msg, program.c_str());
    exit(1);
}

/*===========================================================================*/
/* Library building functions                                                */
/*===========================================================================*/

/*---------------------------------------------------------------------------*/
/* Return a string with a series of make variable assignments representing   */
/* the "true" predicates for a particular library (e.g. "C6XABI=1 C6000=1    */
/* NOT_VENC_ASM=1 EABI=1").                                                  */
/*---------------------------------------------------------------------------*/
const string predicate_string(const string &pattern)
{
    library_t *lib = find_library(pattern);

    assert(lib);

    string result;

    for (int i = 0; lib->predicates[i]; i++)
        result += string(lib->predicates[i]) + "=1\n";

    return result;
}

/*---------------------------------------------------------------------------*/
/* Format an int as a string.                                                */
/*---------------------------------------------------------------------------*/
const string itoa(int ncpus)
{
    stringstream result;
    result << ncpus;
    return result.str();
}

/*---------------------------------------------------------------------------*/
/* Build just one library with the specified name in the specified directory */
/*---------------------------------------------------------------------------*/
void build_one_library(const string &index_library_dir,
                       const string &pattern, 
                       const string &name, 
                       const string &dst_dir)
{
    if (find_library(pattern) == NULL)
    {
        if (logfile) fprintf(logfile, ">> ERROR: mklib: Unrecognized standard library name %s\n", pattern.c_str());
        fprintf(stderr, ">> ERROR: mklib: Unrecognized standard library name %s\n", pattern.c_str());
        exit(1);
    }

    if (verbose || dryrun) printf("Building %s from pattern %s\n",
                                  splice(dst_dir.c_str(), name.c_str()).c_str(), pattern.c_str());
    if (logfile) fprintf(logfile, "Building %s from pattern %s\n",
                                  splice(dst_dir.c_str(), name.c_str()).c_str(), pattern.c_str());

    /*-----------------------------------------------------------------------*/
    /* Check if the destination library already exists.  If so, error        */
    /*-----------------------------------------------------------------------*/
    string library_pathname = splice(dst_dir, name);

    if (file_exists(library_pathname))
    {
        if (logfile) fprintf(logfile, ">> ERROR: mklib: destination library %s already exists\n", library_pathname.c_str());
        fprintf(stderr, ">> ERROR: mklib: destination library %s already exists\n", library_pathname.c_str());
        exit(1);
    }

    /*-----------------------------------------------------------------------*/
    /* Before the expensive build starts, check whether we can write to the  */
    /* destination directory.                                                */
    /*-----------------------------------------------------------------------*/
    FILE *tst = fopen_or_die(library_pathname, "w");
    fclose(tst);
    remove(library_pathname.c_str());

    /*-----------------------------------------------------------------------*/
    /* Create the scratch area and subdirectories We will build the library  */
    /* in a scratch area and only move it to the final directory when the    */
    /* library is completely built.                                          */
    /*-----------------------------------------------------------------------*/
    string scratch_dir;

    if (dryrun)
    {
        scratch_dir = splice(tmpdir, "TI_MKLIBXXXXXX");
    }
    else
    {
        scratch_dir = absolute_path(make_temp_directory_or_die(name));
    }

    if (verbose) printf("Work directory : %s\n", scratch_dir.c_str());

    if (verbose || dryrun) printf("mkdir %s\n", scratch_dir.c_str());
    if (logfile) fprintf(logfile, "mkdir %s\n", scratch_dir.c_str());
    
    string obj_dir = splice(scratch_dir, "OBJ");
    if (verbose || dryrun) printf("mkdir %s\n", obj_dir.c_str());
    if (logfile) fprintf(logfile, "mkdir %s\n", obj_dir.c_str());
    if (!dryrun && !directory_exists(obj_dir))
       make_directory_or_die(obj_dir);
    obj_dir = absolute_path(obj_dir);
    
    /*-----------------------------------------------------------------------*/
    /* Remember the current working directory so we can chdir out of the     */
    /* scratch directory.  We need to do this before deleting it, because    */
    /* otherwise the directory is in use and can't be deleted.               */
    /*-----------------------------------------------------------------------*/
    const string old_cwd = getcwd_or_die();

    /*-----------------------------------------------------------------------*/
    /* Change to the source directory                                        */
    /*-----------------------------------------------------------------------*/
    string src_dir = absolute_path(splice(index_library_dir, "src"));
    if (verbose || dryrun) printf("cd %s\n", src_dir.c_str());
    if (logfile) fprintf(logfile, "cd %s\n", src_dir.c_str());
    if (!dryrun) set_working_directory_or_die(src_dir.c_str());

    /*-----------------------------------------------------------------------*/
    /* Invoke the gmake with the appropriate variables set.                  */
    /*-----------------------------------------------------------------------*/
    int ncpus = parallel;

    if (!ncpus)
    {
        const char *np = getenv("NUMBER_OF_PROCESSORS");
        if (np) ncpus = strtol(np, NULL, 10);
        else    ncpus = 1;
    }

    string tmp_lib_name = absolute_path(splice(scratch_dir, name));

    /*-----------------------------------------------------------------------*/
    /* It is still possible that this program will be in a directory with a  */
    /* space in it, but Windows system() for some reason doesn't work if the */
    /* executable is quoted.  Windows can handle the space anyway, and       */
    /* there's unlikely to be a space on Unix, so we don't quote it.         */
    /*-----------------------------------------------------------------------*/
    string gmake_cmd = find_program_or_die("gmake");

    /*-----------------------------------------------------------------------*/
    /* The -r option tells gmake to not include implicit rules. This is      */
    /* needed to avoid header files with no .h extension like memory from    */
    /* being treated as executable files. By default make will try to build  */
    /* these using source files like memory.c.                               */
    /*-----------------------------------------------------------------------*/
    gmake_cmd += " -r";
    gmake_cmd += string(" -j ") + itoa(ncpus);
    gmake_cmd += " library";
    if (quiet) 
        gmake_cmd += " --quiet";

    /*-----------------------------------------------------------------------*/
    /* To prevent command line length issues, we use a command file to pass  */
    /* variables to the Makefile.                                            */
    /*-----------------------------------------------------------------------*/
    string gmake_cmd_file_name = absolute_path(splice(scratch_dir, "_Makefile.cmd"));
    FILE *gmake_cmd_file = fopen_or_die(gmake_cmd_file_name.c_str(), "w");

    /*-----------------------------------------------------------------------*/
    /* We must pass the absolute path for the following executables. On      */
    /* Windows we can't rely on them being in PATH so we must locate them    */
    /* for make. In CCS installations they will be in utils/cygwin.          */
    /*-----------------------------------------------------------------------*/
    fprintf(gmake_cmd_file, "SHELL=%s\n", find_program_or_die("sh").c_str());
    fprintf(gmake_cmd_file, "SED_CMD=%s\n", find_program_or_die("sed").c_str());
    fprintf(gmake_cmd_file, "CP_CMD=%s\n", find_program_or_die("cp").c_str());
    fprintf(gmake_cmd_file, "RM_CMD=%s\n", find_program_or_die("rm").c_str());

    fprintf(gmake_cmd_file, "EXTRA_FLAGS=");
    if (!extra_options.empty())
       fprintf(gmake_cmd_file, "%s ", extra_options.c_str());
    if (!internal_options.empty())
       fprintf(gmake_cmd_file, "%s", internal_options.c_str());
    fprintf(gmake_cmd_file, "\n");

    fprintf(gmake_cmd_file, "INC=%s\n", src_dir.c_str());
    fprintf(gmake_cmd_file, "OBJ=%s\n", obj_dir.c_str());
    fprintf(gmake_cmd_file, "LIB=%s\n", tmp_lib_name.c_str());
    fprintf(gmake_cmd_file, "%s\n", predicate_string(pattern).c_str());

    if (!options.empty())
       fprintf(gmake_cmd_file, "STANDARD_OPTIONS=%s\n", options.c_str());

    if (!compiler_bin_dir.empty()) 
       fprintf(gmake_cmd_file, "CGT_BIN=%s\n", compiler_bin_dir.c_str());

    fclose(gmake_cmd_file);

    gmake_cmd += string(" CMD_FILE=") + quote(gmake_cmd_file_name);

    if (verbose || dryrun) printf("%s\n", gmake_cmd.c_str());
    if (logfile) fprintf(logfile, "%s\n", gmake_cmd.c_str());
    if (!dryrun)
    {
        fflush(stderr);
        fflush(stdout);
        if (system(gmake_cmd.c_str()))
        {
            if (logfile) fprintf(logfile, ">> ERROR: mklib: gmake error during %s build\n", name.c_str());
            fprintf(stderr, ">> ERROR: mklib: gmake error during %s build\n", name.c_str());
            exit(1);
        }
    }

    if (!file_exists(tmp_lib_name))
    {
        if (logfile) fprintf(logfile, ">> ERROR: mklib: failed to build %s\n", tmp_lib_name.c_str());
        fprintf(stderr, ">> ERROR: mklib: failed to build %s\n", tmp_lib_name.c_str());
        exit(1);
    }
    
    /*-----------------------------------------------------------------------*/
    /* Now move it to the final destination.                                 */
    /*-----------------------------------------------------------------------*/
    string dst_name = splice(dst_dir, name);
    string cp_cmd = string("cp ") + tmp_lib_name + " " + dst_name;

    if (verbose || dryrun) printf("%s\n", cp_cmd.c_str());
    if (logfile) fprintf(logfile, "%s\n", cp_cmd.c_str());
    if (!dryrun) copy_or_die(tmp_lib_name, dst_name);

    /*-----------------------------------------------------------------------*/
    /* Clean up the temp directory.  First vacate it so we can delete it.    */
    /*-----------------------------------------------------------------------*/
    if (!dryrun && !keep_scratch_dir)
    {
       set_working_directory_or_die(old_cwd);
       remove_directory_recursive(scratch_dir.c_str());
    }
}

/*---------------------------------------------------------------------------*/
/* Parse the user's command-line options.                                    */
/*---------------------------------------------------------------------------*/
void process_args(int argc, char *argv[])
{
    static struct option long_options[] = {
        { "pattern", 1, 0, 0 },
        { "index", 1, 0, 0 },

        { "name", 1, 0, 0 },
        { "all", 0, 0, 0 },

        { "options", 1, 0, 0 },
        { "extra_options", 1, 0, 0 },
        { "internal_options", 1, 0, 0 },

        { "install_to", 1, 0, 0 },
        { "compiler_bin_dir", 1, 0, 0 },

        { "list_libraries", 0, 0, 0 },

        { "log", 1, 0, 0 },
        { "tmpdir", 1, 0, 0 },
        { "keep_scratch", 0, 0, 0 },

        { "gmake", 1, 0, 0 },
        { "parallel", 1, 0, 0 },

        { "buildmodel", 0, 0, 0 },
        { "query", 1, 0, 0 },

        { "help", 0, 0, 'h' },
        { "quiet", 0, 0, 'q' },
        { "dryrun", 0, 0, 0 },
        { "verbose", 0, 0, 'v' },
        { 0, 0, 0, 0 }
    };

    while (1)
    {
        int idx = 0;
        int c = getopt_long (argc, argv, "hqv", long_options, &idx);

        if (c == -1)
        {
            break; /* done */
        }

        switch(c)
        {
            case 0: /* long option */
            {
                if (!strcmp("pattern", long_options[idx].name))
                {
                    pattern = optarg;
                }
                else if (!strcmp("index", long_options[idx].name))
                {
                    index_library_path = optarg;
                }
                else if (!strcmp("name", long_options[idx].name))
                {
                    name = optarg;
                }
                else if (!strcmp("all", long_options[idx].name))
                {
                    all = true;
                }
                else if (!strcmp("options", long_options[idx].name))
                {
                    options = optarg;
                }
                else if (!strcmp("extra_options", long_options[idx].name))
                {
                    extra_options = optarg;
                }
                else if (!strcmp("internal_options", long_options[idx].name))
                {
                    internal_options = optarg;
                }
                else if (!strcmp("install_to", long_options[idx].name))
                {
                    install_to = optarg;
                }
                else if (!strcmp("compiler_bin_dir", long_options[idx].name))
                {
                    compiler_bin_dir = optarg;
                }
                else if (!strcmp("list_libraries", long_options[idx].name))
                {
                    list_libraries();
                    exit(0);
                }
                else if (!strcmp("log", long_options[idx].name))
                {
                    logfile_name = optarg;
                }
                else if (!strcmp("tmpdir", long_options[idx].name))
                {
                    tmpdir = optarg;
                    user_defined_tmpdir = true;
                }
                else if (!strcmp("keep_scratch", long_options[idx].name))
                {
                    keep_scratch_dir = true;
                }
                else if (!strcmp("gmake", long_options[idx].name))
                {
                    gmake = optarg;
                }
                else if (!strcmp("parallel", long_options[idx].name))
                {
                    parallel = strtol(optarg, NULL, 10);
                }
                else if (!strcmp("buildmodel", long_options[idx].name))
                {
                    printf("%d\n", buildmodel);
                    exit(0);
                }
                else if (!strcmp("query", long_options[idx].name))
                {
                    exit(find_library(optarg) == NULL);
                }
                else if (!strcmp("dryrun", long_options[idx].name))
                {
                    dryrun = true;
                }
                else
                {
                    printf("panic\n");
                    exit(1);
                }
                break;
            }
                
            case '?': 
            case 'h': usage(); break;

            case 'q': quiet = true; break;
            case 'v': verbose = true; break;

            default: 
            {
                printf("Unknown return from getopt_long: %x\n", c);
            }
        }
    }
}

#if _WIN32
/*---------------------------------------------------------------------------*/
/* For each component in PATH, strip outermost quotes and write it back to   */
/* the environment.  CreateProcess (used by gmake) cannot handle quotes in   */
/* the PATH enviornment variable.  Quotes are not needed in PATH, even for   */
/* directories that have embedded spaces, but some users put quotes there    */
/* anyway.  CMD.EXE is able to handle the quotes, so to the user quotes in   */
/* PATH appear to work, but CreateProcess is not, so you'll get a mysterious */
/* gmake error like "CreateProcess(NULL, cl470 ...) failed.  The system      */
/* cannot find the file specified."                                          */
/*---------------------------------------------------------------------------*/
void strip_quotes_from_PATH(void)
{
    const char *PATH = getenv("PATH");

    if (PATH)
    {
        const vector<string> path_components(tokenize(PATH, path_delim()));
        string fixed_PATH;

        for (size_t i = 0; i < path_components.size(); i++)
        {
            if (i) fixed_PATH += ";";
            fixed_PATH += unquote(path_components[i]);
        }

        fixed_PATH = string("PATH=") + fixed_PATH;

        _putenv(fixed_PATH.c_str());
    }
}
#endif

/*---------------------------------------------------------------------------*/
/* Perform sanity checks on user arguments, figure out missing information,  */
/* and make all paths absolute                                               */
/*---------------------------------------------------------------------------*/
void sanity_checks(int argc, char *argv[])
{
    /*-----------------------------------------------------------------------*/
    /* We must know the directory containing mklib, libc.a, and rtssrc.zip.  */
    /*-----------------------------------------------------------------------*/
    if (index_library_path.empty())
    {
        /*-------------------------------------------------------------------*/
        /* If the user didn't specify --index, first look in C_DIR for an    */
        /* appropriate libc.a.                                               */
        /*-------------------------------------------------------------------*/
        const char *path = getenv(c_dir_name);

        if (path == NULL) path = getenv("C_DIR");

        if (path)
        {
            vector<string> compiler_path(tokenize(path, ";"));
            for (size_t i = 0; i < compiler_path.size(); i++)
            {
                string candidate = splice(compiler_path[i], "libc.a");

                if (file_exists(candidate))
                {
                    if (verbose) printf("Option --index not specified, using %s\n", candidate.c_str());
                    index_library_path = candidate;
                    break;
                }
            }
        }

        /*-------------------------------------------------------------------*/
        /* If that didn't work, attempt to use dirname($0)/libc.a            */
        /*-------------------------------------------------------------------*/
        if (index_library_path.empty())
        {
            string dir = absolute_directory_part(argv[0]);
            string candidate = splice(dir, "libc.a");

            if (file_exists(candidate))
            {
                index_library_path = candidate;
                if (verbose) printf("Option --index not specified, using %s\n", candidate.c_str());
            }

        }

        if (index_library_path.empty())
        {
            fprintf(stderr, ">> ERROR: mklib: option --index is required\n");
            exit(1);
        }
    }

    /*-----------------------------------------------------------------------*/
    /* Check for invalid option combinations                                 */
    /*-----------------------------------------------------------------------*/
    if (pattern.empty() && !all)
    {
        fprintf(stderr, ">> ERROR: mklib: option --pattern or --all is required\n");
        exit(1);
    }

    if (!all && find_library(pattern) == NULL)
    {
        if (logfile) fprintf(logfile, ">> ERROR: mklib: Unrecognized standard library name %s\n", pattern.c_str());
        fprintf(stderr, ">> ERROR: mklib: Unrecognized standard library name %s\n", pattern.c_str());
        exit(1);
    }

    bool standard = (options.empty() && extra_options.empty());

    if (!standard && all)
    {
        fprintf(stderr, ">> ERROR: mklib: cannot use --options or --extra_options with --all\n");
        exit(1);
    }

    if (!name.empty() && all)
    {
        fprintf(stderr, ">> ERROR: mklib: cannot use --name with --all\n");
        exit(1);
    }

    /*-----------------------------------------------------------------------*/
    /* The archiver will automatically add ".lib" when creating a library.   */
    /* If the user specifies a name with a file extension, this program will */
    /* get confused                                                          */
    /*-----------------------------------------------------------------------*/
    if (!name.empty())
    {
        string extension = filename_extension(name);
        if (extension.empty())
        {
            fprintf(stderr, ">> ERROR: mklib: --name must specify a library name with a file extension (e.g. .lib or .a).\n");
            exit(1);
        }
    }

    /*-----------------------------------------------------------------------*/
    /* Make sure all paths are absolute (don't have to be canonical).        */
    /*-----------------------------------------------------------------------*/
    if (!tmpdir.empty())
        tmpdir = absolute_path(tmpdir);

    if (!install_to.empty())
        install_to = absolute_path(install_to);

    if (!compiler_bin_dir.empty())
        compiler_bin_dir = absolute_path(compiler_bin_dir);

    if (!index_library_path.empty())
        index_library_path = absolute_path(index_library_path);

    /*-----------------------------------------------------------------------*/
    /* Make sure we know where to create temporary files.                    */
    /*-----------------------------------------------------------------------*/
    if (tmpdir.empty()) discover_tmpdir();

#if _WIN32
    /*-----------------------------------------------------------------------*/
    /* Ensure we have a "short form" pathname                                */
    /*-----------------------------------------------------------------------*/
    tmpdir = absolute_path(tmpdir);
#endif

#if _WIN32
    /*-----------------------------------------------------------------------*/
    /* Handle the (degenerate) case of quotes in PATH                        */
    /*-----------------------------------------------------------------------*/
    strip_quotes_from_PATH();
#endif
}

/*---------------------------------------------------------------------------*/
/* Stuff happens                                                             */
/*---------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
    process_args(argc, argv);
    sanity_checks(argc, argv);

    if (!logfile_name.empty()) 
       logfile = fopen_or_die(logfile_name.c_str(), "w");

    /*-----------------------------------------------------------------------*/
    /* By default, a standard library is installed in the same directory     */
    /* libc.a is in, but the user can override this with the --install_to    */
    /* option.                                                               */
    /*-----------------------------------------------------------------------*/
    const string index_library_dir = directory_part(index_library_path);

    int standard = (options.empty() && extra_options.empty());

    if (install_to.empty() && standard) install_to = index_library_dir;

    /*-----------------------------------------------------------------------*/
    /* For --all, the user is not allowed to specify the names of the        */
    /* libraries.  The name for each library will be the pattern name.       */
    /* Otherwise, if the user doesn't specify --name, also default to        */
    /* --pattern.                                                            */
    /*-----------------------------------------------------------------------*/
    if (all || name.empty())
    {
        name = pattern;
    }

    if (all)
    {
        for (size_t i = 0; i < (sizeof LIBRARIES / sizeof *LIBRARIES); i++)
            build_one_library(index_library_dir, 
                              LIBRARIES[i].name, 
                              LIBRARIES[i].name, 
                              install_to);
    }
    else
    {
        build_one_library(index_library_dir,
                          pattern, name, install_to);
    }
}