/****************************************************************************
 * tools/mkdeps.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#define _FILE_OFFSET_BITS 64

#include <sys/stat.h>

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <libgen.h>
#include <errno.h>
#include <dirent.h>

#ifdef HOST_CYGWIN
#  include <sys/cygwin.h>
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define MAX_BUFFER  (16384)
#define MAX_EXPAND  (16384)
#define MAX_SHQUOTE (16384)

/* MAX_PATH might be defined in stdlib.h */

#if !defined(MAX_PATH)
#  define MAX_PATH  (512)
#endif

/* NAME_MAX is typically defined in limits.h */

#if !defined(NAME_MAX)

  /* FILENAME_MAX might be defined in stdio.h */

#  if defined(FILENAME_MAX)
#    define NAME_MAX FILENAME_MAX
#  else

  /* MAXNAMELEN might be defined in dirent.h */

#    if defined(MAXNAMLEN)
#      define NAME_MAX MAXNAMLEN
#    else

  /* Lets not let a silly think like this stop us... just make something up */

#      define NAME_MAX 256
#    endif
#  endif
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

typedef enum
{
  COMPILER_GNU      = 0,
  COMPILER_TASKING  = 1,
  COMPILER_NUM      = 2
} compiler_t;

/****************************************************************************
 * Private Data
 ****************************************************************************/

static char *g_cc        = NULL;
static char *g_cflags    = NULL;
static char *g_files     = NULL;
static char *g_altpath   = NULL;
static char *g_objpath   = NULL;
static char *g_suffix    = ".o";
static int   g_debug     = 0;
static bool  g_winnative = false;
#ifdef HOST_CYGWIN
static bool  g_winpath   = false;
#endif

static char g_command[MAX_BUFFER];
static char g_path[MAX_PATH];
#ifdef HOST_CYGWIN
static char g_expand[MAX_EXPAND];
static char g_dequoted[MAX_PATH];
static char g_posixpath[MAX_PATH];
#endif
#ifndef CONFIG_WINDOWS_NATIVE
static char g_shquote[MAX_SHQUOTE];
#endif

static const char * const g_moptions[COMPILER_NUM][2] =
{
  /* GNU C/C++ Compiler */

  {
    " -M ",
    " -MT "
  },

  /* Tasking C/C++ Compiler */

  {
    " -Em ",
    " --pass-c=--make-target="
  }
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static compiler_t get_compiler(char *ccname)
{
  if (strstr(ccname, "ctc") != NULL)
    {
      return COMPILER_TASKING;
    }

  return COMPILER_GNU;
}

/* MinGW does not seem to provide strtok_r */

#ifndef HAVE_STRTOK_R
static char *my_strtok_r(char *str, const char *delim, char **saveptr)
{
  char *pbegin;
  char *pend = NULL;

  /* Decide if we are starting a new string or continuing from
   * the point we left off.
   */

  if (str)
    {
      pbegin = str;
    }
  else if (saveptr && *saveptr)
    {
      pbegin = *saveptr;
    }
  else
    {
      return NULL;
    }

  /* Find the beginning of the next token */

  for (;
       *pbegin && strchr(delim, *pbegin) != NULL;
       pbegin++);

  /* If we are at the end of the string with nothing
   * but delimiters found, then return NULL.
   */

  if (!*pbegin)
    {
      return NULL;
    }

  /* Find the end of the token */

  for (pend = pbegin + 1;
       *pend && strchr(delim, *pend) == NULL;
       pend++);

  /* pend either points to the end of the string or to
   * the first delimiter after the string.
   */

  if (*pend)
    {
      /* Turn the delimiter into a null terminator */

      *pend++ = '\0';
    }

  /* Save the pointer where we left off and return the
   * beginning of the token.
   */

  if (saveptr)
    {
      *saveptr = pend;
    }

  return pbegin;
}

#undef strtok_r
#define strtok_r my_strtok_r
#endif

static void append(char **base, const char *str)
{
  char *oldbase;
  char *newbase;
  int alloclen;

  oldbase = *base;
  if (!oldbase)
    {
      newbase = strdup(str);
      if (!newbase)
        {
          fprintf(stderr, "ERROR: Failed to strdup %s\n", str);
          exit(EXIT_FAILURE);
        }
    }
  else
    {
      alloclen = strlen(oldbase) + strlen(str) +
        sizeof((char) ' ') + sizeof((char) '\0');
      newbase = malloc(alloclen);
      if (!newbase)
        {
          fprintf(stderr, "ERROR: Failed to allocate %d bytes\n", alloclen);
          exit(EXIT_FAILURE);
        }

      snprintf(newbase, alloclen, "%s %s", oldbase, str);
      free(oldbase);
    }

  *base = newbase;
}

static void show_usage(const char *progname, const char *msg, int exitcode)
{
  if (msg)
    {
      fprintf(stderr, "\n");
      fprintf(stderr, "%s:\n", msg);
    }

  fprintf(stderr, "\n");
  fprintf(stderr, "%s  [OPTIONS] CC -- CFLAGS -- file [file [file...]]\n",
          progname);
  fprintf(stderr, "\n");
  fprintf(stderr, "Where:\n");
  fprintf(stderr, "  CC\n");
  fprintf(stderr, "    A variable number of arguments that define how to\n");
  fprintf(stderr, "    execute the compiler\n");
  fprintf(stderr, "  CFLAGS\n");
  fprintf(stderr, "    The compiler compilation flags\n");
  fprintf(stderr, "  file\n");
  fprintf(stderr, "    One or more C files whose dependencies will be\n");
  fprintf(stderr, "    checked.  Each file is expected\n");
  fprintf(stderr, "    to reside in the current directory unless\n");
  fprintf(stderr, "     --dep-path is provided on the command line\n");
  fprintf(stderr, "\n");
  fprintf(stderr, "And [OPTIONS] include:\n");
  fprintf(stderr, "  --dep-debug\n");
  fprintf(stderr, "    Enable script debug\n");
  fprintf(stderr, "  --dep-path <path>\n");
  fprintf(stderr, "    Do not look in the current directory for the\n");
  fprintf(stderr, "    file. Instead, look in <path> to see\n");
  fprintf(stderr, "    if the file resides there.  --dep-path may be\n");
  fprintf(stderr, "    used multiple times to specify\n");
  fprintf(stderr, "    multiple alternative location\n");
  fprintf(stderr, "  --obj-path <path>\n");
  fprintf(stderr, "    The final objects will not reside in this path\n");
  fprintf(stderr, "    but, rather, at the path provided by\n");
  fprintf(stderr, "    <path>.  if provided multiple time, only the last\n");
  fprintf(stderr, "     --obj-path will be used.\n");
  fprintf(stderr, "  --obj-suffix <suffix>\n");
  fprintf(stderr, "    If an object path is provided, then the extension\n");
  fprintf(stderr, "    will be assumed to be .o.  This\n");
  fprintf(stderr, "    default suffix can be overridden with this\n");
  fprintf(stderr, "    command line option.\n");
  fprintf(stderr, "  --winnative\n");
  fprintf(stderr, "    By default, a POSIX-style environment is assumed\n");
  fprintf(stderr, "    (e.g., Linux, Cygwin, etc.)  This option is\n");
  fprintf(stderr, "    inform the tool that is working in a pure Windows\n");
  fprintf(stderr, "    native environment.\n");
#ifdef HOST_CYGWIN
  fprintf(stderr, "  --winpaths\n");
  fprintf(stderr, "    This option is useful when using a Windows native\n");
  fprintf(stderr, "    toolchain in a POSIX environment (such such as\n");
  fprintf(stderr, "    Cygwin).  In this case, will CC\n");
  fprintf(stderr, "    generates dependency lists using Windows paths\n");
  fprintf(stderr, "    (e.g., C:\\blablah\\blabla).\n");
#endif
  fprintf(stderr, "  --help\n");
  fprintf(stderr, "    Shows this message and exits\n");
  exit(exitcode);
}

/****************************************************************************
 * Name: do_shquote
 *
 * Description:
 *    Escape the given string for use with the shell.
 *
 *    The idea was taken from:
 *    https://netbsd.gw.com/cgi-bin/man-cgi?shquote++NetBSD-current
 *    However, this implementation doesn't try to elide extraneous quotes.
 ****************************************************************************/
#ifndef CONFIG_WINDOWS_NATIVE
static const char *do_shquote(const char *argument)
{
  const char *src;
  char *dest;
  int len;

  src  = argument;
  dest = g_shquote;
  len  = 0;

  if (len < sizeof(g_shquote))
    {
      *dest++ = '\'';
      len++;
    }

  while (*src && len < sizeof(g_shquote))
    {
      if (*src == '\'')
        {
          /* Expand single quote to '\'' */

          if (len + 4 > sizeof(g_shquote))
            {
              break;
            }

          src++;
          memcpy(dest, "\'\\\'\'", 4);
          dest += 4;
          len += 4;
        }
      else
        {
          *dest++ = *src++;
          len++;
        }
    }

  if (*src || len + 2 > sizeof(g_shquote))
    {
      fprintf(stderr,
              "ERROR: Truncated during shquote string is too long"
              "[%zu/%zu]\n", strlen(argument), sizeof(g_shquote));
      exit(EXIT_FAILURE);
    }

  *dest++ = '\'';
  *dest = '\0';
  return g_shquote;
}
#endif

static void parse_args(int argc, char **argv)
{
  char *args = NULL;
  int argidx;
  int group = 0;

  /* Always look in the current directory */

  g_altpath = strdup(".");

  /* Accumulate CFLAGS up to "--" */

  for (argidx = 1; argidx < argc; argidx++)
    {
      if (strcmp(argv[argidx], "--") == 0)
        {
          g_cc = g_cflags;
          g_cflags = args;
          args = NULL;
          group++;
        }
      else if (strcmp(argv[argidx], "--dep-debug") == 0)
        {
          g_debug++;
        }
      else if (strcmp(argv[argidx], "--dep-path") == 0)
        {
          argidx++;
          if (argidx >= argc)
            {
              show_usage(argv[0], "ERROR: Missing argument to --dep-path",
                         EXIT_FAILURE);
            }

          if (args)
            {
              append(&args, argv[argidx]);
            }
          else
            {
              append(&g_altpath, argv[argidx]);
            }
        }
      else if (strcmp(argv[argidx], "--obj-path") == 0)
        {
          argidx++;
          if (argidx >= argc)
            {
              show_usage(argv[0], "ERROR: Missing argument to --obj-path",
                         EXIT_FAILURE);
            }

          g_objpath = argv[argidx];
        }
      else if (strcmp(argv[argidx], "--obj-suffix") == 0)
        {
          argidx++;
          if (argidx >= argc)
            {
              show_usage(argv[0], "ERROR: Missing argument to --obj-suffix",
                         EXIT_FAILURE);
            }

          g_suffix = argv[argidx];
        }
      else if (strcmp(argv[argidx], "--winnative") == 0)
        {
          g_winnative = true;
        }
#ifdef HOST_CYGWIN
      else if (strcmp(argv[argidx], "--winpath") == 0)
        {
          g_winpath = true;
        }
#endif
      else if (strcmp(argv[argidx], "--help") == 0)
        {
          show_usage(argv[0], NULL, EXIT_SUCCESS);
        }
      else
        {
          const char *arg = argv[argidx];

          /* This condition means "perform shquote for
           * g_cflags, but not g_cc or g_files".
           *
           * It isn't safe to escape g_cc because, for some reasons,
           * Makefile passes it as a single argument like:
           *
           *    $(MKDEP) $(DEPPATH) "$(CC)" -- $(CFLAGS) -- $(SRCS)
           *
           * It isn't safe to escape g_files because
           * do_dependency() uses them as bare filenames as well.
           * (In addition to passing them to system().)
           */
#ifndef CONFIG_WINDOWS_NATIVE
          if (group == 1)
            {
               arg = do_shquote(arg);
            }

#endif
          append(&args, arg);
        }
    }

  /* The final thing accumulated is the list of files */

  g_files = args;

  if (g_debug)
    {
      fprintf(stderr, "SELECTIONS\n");
      fprintf(stderr, "  CC             : [%s]\n",
              g_cc ? g_cc : "(None)");
      fprintf(stderr, "  CFLAGS         : [%s]\n",
              g_cflags ? g_cflags : "(None)");
      fprintf(stderr, "  FILES          : [%s]\n",
              g_files ? g_files : "(None)");
      fprintf(stderr, "  PATHS          : [%s]\n",
              g_altpath ? g_altpath : "(None)");
      if (g_objpath)
        {
          fprintf(stderr, "  OBJDIR         : [%s]\n", g_objpath);
          fprintf(stderr, "  SUFFIX         : [%s]\n", g_suffix);
        }
      else
        {
          fprintf(stderr, "  OBJDIR         : (None)\n");
        }

#ifdef HOST_CYGWIN
      fprintf(stderr, "  Windows Paths  : [%s]\n",
              g_winpath ? "TRUE" : "FALSE");
#endif
      fprintf(stderr, "  Windows Native : [%s]\n",
              g_winnative ? "TRUE" : "FALSE");
    }

  /* Check for required parameters */

  if (!g_cc)
    {
      show_usage(argv[0], "ERROR: No compiler specified", EXIT_FAILURE);
    }

  if (!g_files)
    {
      /* Don't report an error --
       * this happens normally in some configurations
       */

      printf("# No files specified for dependency generation\n");
      exit(EXIT_SUCCESS);
    }

#ifdef HOST_CYGWIN
  if (g_winnative && g_winpath)
    {
      show_usage(argv[0],
                 "ERROR: Both --winnative and --winpath makes no sense",
                 EXIT_FAILURE);
    }
#endif
}

static const char *do_expand(const char *argument)
{
#ifdef HOST_CYGWIN
  if (g_winpath)
    {
      const char *src;
      char *dest;
      int len;

      src  = argument;
      dest = g_expand;
      len  = 0;

      while (*src && len < MAX_EXPAND)
        {
          if (*src == '\\')
            {
              /* Copy backslash */

              *dest++ = *src++;
              if (++len >= MAX_EXPAND)
                {
                  break;
                }

              /* Already expanded? */

              if (*src == '\\')
                {
                  /* Yes... just copy all consecutive backslashes */

                  do
                    {
                      *dest++ = *src++;
                      if (++len >= MAX_EXPAND)
                        {
                          break;
                        }
                    }
                  while (*src == '\\');
                }
              else
                {
                  /* No.. expeand */

                  *dest++ = '\\';
                  if (++len >= MAX_EXPAND)
                    {
                      break;
                    }
                }
            }
          else
            {
              *dest++ = *src++;
              len++;
            }
        }

      if (*src)
        {
          fprintf(stderr,
                  "ERROR: Truncated during expansion string is too long"
                  "[%zu/%u]\n", strlen(argument), MAX_EXPAND);
          exit(EXIT_FAILURE);
        }

      *dest = '\0';
      return g_expand;
    }
  else
#endif
    {
      return argument;
    }
}

#ifdef HOST_CYGWIN
static bool dequote_path(const char *winpath)
{
  char *dest = g_dequoted;
  const char *src = winpath;
  int len = 0;
  bool quoted = false;

  while (*src && len < MAX_PATH)
    {
      if (*src != '\\' || (src[1] != ' ' && src[1] != '(' && src[1] != ')'))
        {
          *dest++ = *src;
          len++;
        }
      else
        {
          quoted = true;
        }

      src++;
    }

  if (*src || len >= MAX_PATH)
    {
      fprintf(stderr, "# ERROR: Path truncated\n");
      exit(EXIT_FAILURE);
    }

  *dest = '\0';
  return quoted;
}
#endif

static const char *convert_path(const char *path)
{
#ifdef HOST_CYGWIN
  if (g_winpath)
    {
      const char *retptr;
      ssize_t size;
      ssize_t ret;
      bool quoted;

      quoted = dequote_path(path);
      if (quoted)
        {
          retptr = g_posixpath;
        }
      else
        {
          retptr = &g_posixpath[1];
        }

      size = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_RELATIVE, g_dequoted,
                             NULL, 0);
      if (size > (MAX_PATH - 3))
        {
          fprintf(stderr, "# ERROR: POSIX path too long: %zd\n", size);
          exit(EXIT_FAILURE);
        }

      ret = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_RELATIVE, g_dequoted,
                             &g_posixpath[1], MAX_PATH - 3);
      if (ret < 0)
        {
          fprintf(stderr, "# ERROR: cygwin_conv_path '%s' failed: %s\n",
                  g_dequoted, strerror(errno));
          exit(EXIT_FAILURE);
        }

      if (quoted)
        {
          size++;
          g_posixpath[0] = '"';
          g_posixpath[size] = '"';
        }

      g_posixpath[size + 1] = '\0';
      return retptr;
    }
  else
#endif
    {
      return path;
    }
}

static void do_dependency(const char *file)
{
  const char * const * moption;
  struct stat buf;
  char *alloc;
  char *altpath;
  char *path;
  char *lasts;
  char separator;
  int cmdlen;
  int pathlen;
  int filelen;
  int totallen;
  int ret;

  /* Initialize the separator */

#ifdef HOST_CYGWIN
  separator =  (g_winnative || g_winpath) ? '\\' : '/';
#else
  separator =  g_winnative ? '\\' : '/';
#endif

  moption = g_moptions[get_compiler(g_cc)];

  /* Copy the compiler into the command buffer */

  cmdlen = strlen(g_cc);
  if (cmdlen >= MAX_BUFFER)
    {
      fprintf(stderr, "ERROR: Compiler string is too long [%d/%d]: %s\n",
              cmdlen, MAX_BUFFER, g_cc);
      exit(EXIT_FAILURE);
    }

  strcpy(g_command, g_cc);

  /* Copy " -MT " */

  if (g_objpath)
    {
      char tmp[NAME_MAX + 6];
      char *dupname;
      char *objname;
      char *dotptr;
      const char *expanded;

      dupname = strdup(file);
      if (!dupname)
        {
          fprintf(stderr, "ERROR: Failed to dup: %s\n", file);
          exit(EXIT_FAILURE);
        }

      objname = basename(dupname);
      dotptr  = strrchr(objname, '.');
      if (dotptr)
        {
          *dotptr = '\0';
        }

      snprintf(tmp, NAME_MAX + 6, "%s%s%c%s%s ",
               moption[1], g_objpath, separator, objname, g_suffix);
      expanded = do_expand(tmp);

      cmdlen += strlen(expanded);
      if (cmdlen >= MAX_BUFFER)
        {
          fprintf(stderr, "ERROR: Option string is too long [%d/%d]: %s\n",
                  cmdlen, MAX_BUFFER, moption[0]);
          exit(EXIT_FAILURE);
        }

      strcat(g_command, expanded);
      free(dupname);
    }

  /* Copy " -M " */

  cmdlen += strlen(moption[0]);
  if (cmdlen >= MAX_BUFFER)
    {
      fprintf(stderr, "ERROR: Option string is too long [%d/%d]: %s\n",
              cmdlen, MAX_BUFFER, moption[0]);
      exit(EXIT_FAILURE);
    }

  strcat(g_command, moption[0]);

  /* Copy the CFLAGS into the command buffer */

  if (g_cflags)
    {
      const char *expanded;

      expanded = do_expand(g_cflags);
      cmdlen += strlen(expanded);

      if (cmdlen >= MAX_BUFFER)
        {
          fprintf(stderr, "ERROR: CFLAG string is too long [%d/%d]: %s\n",
                  cmdlen, MAX_BUFFER, g_cflags);
          exit(EXIT_FAILURE);
        }

      strcat(g_command, expanded);
    }

  /* Add a space */

  g_command[cmdlen] = ' ';
  cmdlen++;
  g_command[cmdlen] = '\0';

  /* Make a copy of g_altpath. We need to do this because at least the
   * version of strtok_r above does modify it.
   */

  alloc = strdup(g_altpath);
  if (!alloc)
    {
      fprintf(stderr, "ERROR: Failed to strdup paths\n");
      exit(EXIT_FAILURE);
    }

  altpath = alloc;

  /* Try each path.  This loop will continue until each path has been tried
   * (failure) or until stat() finds the file
   */

  while ((path = strtok_r(altpath, " ", &lasts)) != NULL)
    {
      const char *expanded;
      const char *converted;

      /* Create a full path to the file */

      pathlen = strlen(path);
      if (pathlen >= MAX_PATH)
        {
          fprintf(stderr, "ERROR: Path is too long [%d/%d]: %s\n",
                  pathlen, MAX_PATH, path);
          exit(EXIT_FAILURE);
        }

      strcpy(g_path, path);

      if (g_path[pathlen] != '\0')
        {
          fprintf(stderr, "ERROR: Missing NUL terminator\n");
          exit(EXIT_FAILURE);
        }

      if (g_path[pathlen - 1] != separator)
        {
          g_path[pathlen] = separator;
          g_path[pathlen + 1] = '\0';
          pathlen++;
        }

      filelen = strlen(file);
      pathlen += filelen;
      if (pathlen >= MAX_PATH)
        {
          fprintf(stderr, "ERROR: Path+file is too long [%d/%d]\n",
                  pathlen, MAX_PATH);
          exit(EXIT_FAILURE);
        }

      strcat(g_path, file);

      /* Check that a file actually exists at this path */

      if (g_debug)
        {
          fprintf(stderr, "Trying path=%s file=%s fullpath=%s\n",
                  path, file, g_path);
        }

      converted = convert_path(g_path);
      ret = stat(converted, &buf);
      if (ret < 0)
        {
          altpath = NULL;
          continue;
        }

      if (!S_ISREG(buf.st_mode))
        {
          fprintf(stderr,
                  "ERROR: File %s exists but is not a regular file\n",
                  g_path);
          exit(EXIT_FAILURE);
        }

      /* Append the expanded path to the command */

      expanded = do_expand(g_path);
      pathlen  = strlen(expanded);
      totallen = cmdlen + pathlen;

      if (totallen >= MAX_BUFFER)
        {
          fprintf(stderr, "ERROR: Path string is too long [%d/%d]: %s\n",
                  totallen, MAX_BUFFER, g_path);
          exit(EXIT_FAILURE);
        }

      strcat(g_command, expanded);

      /* Okay.. we have everything.  Create the dependency.  One a failure
       * to start the compiler, system() will return -1;  Otherwise, the
       * returned value from the compiler is in WEXITSTATUS(ret).
       */

      if (g_debug)
        {
          fprintf(stderr, "Executing: %s\n", g_command);
        }

      ret = system(g_command);
#ifdef WEXITSTATUS
      if (ret < 0 || WEXITSTATUS(ret) != 0)
        {
          if (ret < 0)
            {
              fprintf(stderr, "ERROR: system failed: %s\n", strerror(errno));
            }
          else
            {
              fprintf(stderr,
                      "ERROR: %s failed: %d\n", g_cc, WEXITSTATUS(ret));
            }

          fprintf(stderr, "       command: %s\n", g_command);
          exit(EXIT_FAILURE);
        }
#else
      if (ret < 0)
        {
          fprintf(stderr, "ERROR: system failed: %s\n", strerror(errno));
          fprintf(stderr, "       command: %s\n", g_command);
          exit(EXIT_FAILURE);
        }
#endif

      /* We don't really know that the command succeeded...
       * Let's assume that it did
       */

      free(alloc);
      return;
    }

  printf("# ERROR: File \"%s\" not found at any location\n", file);
  exit(EXIT_FAILURE);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int main(int argc, char **argv, char **envp)
{
  char *lasts;
  char *files;
  char *file;

  /* Parse command line parameters */

  parse_args(argc, argv);

  /* Then generate dependencies for each path on the command line.  NOTE
   * strtok_r will clobber the files list.  But that is okay because we are
   * only going to traverse it once.
   */

  files = g_files;
  while ((file = strtok_r(files, " ", &lasts)) != NULL)
    {
      /* Check if we need to do path conversions for a Windows-natvive tool
       * being using in a POSIX/Cygwin environment.
       */

      do_dependency(file);
      files = NULL;
    }

  return EXIT_SUCCESS;
}