nuttx/tools/mkdeps.c
patacongo 8f47445bd5 Add tools/mkdeps.bat and tools/mkdeps.c
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@5342 42af7a65-404d-4744-a932-0658087f49c3
2012-11-13 00:38:59 +00:00

600 lines
16 KiB
C

/****************************************************************************
* tools/mkdeps.c
*
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name NuttX 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <sys/stat.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MAX_COMMAND 256
#ifndef MAX_PATH
# define MAX_PATH 4096
#endif
#define MAX_BUFFER (MAX_COMMAND + MAX_PATH + 2)
/****************************************************************************
* Private Types
****************************************************************************/
enum slashmode_e
{
MODE_FSLASH = 0,
MODE_BSLASH = 1,
MODE_DBLBACK = 2
};
/****************************************************************************
* Private Data
****************************************************************************/
static char *g_cc = NULL;
static char *g_cflags = NULL;
static char *g_files = NULL;
static char *g_altpath = NULL;
static int g_debug = 0;
static bool g_winnative = false;
#ifdef HAVE_WINPATH
static bool g_winpath = false;
static char *g_topdir = NULL;
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
static void append(char **base, 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(newbase) + strlen(str) + 2;
newbase = (char *)malloc(alloclen);
if (!newbase)
{
fprintf(stderr, "ERROR: Failed to allocate %d bytes\n", alloclen);
exit(EXIT_FAILURE);
}
snprintf(newbase, alloclen, "%s %s\n", 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 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 checked. Each file is expected\n");
fprintf(stderr, " to reside in the current directory unless --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 file. Instead, look in <path> to see\n");
fprintf(stderr, " if the file resides there. --dep-path may be used multiple times to specify\n");
fprintf(stderr, " multiple alternative location\n");
fprintf(stderr, " --winnative\n");
fprintf(stderr, " By default, a POSIX-style environment is assumed (e.g., Linux, Cygwin, etc.) This option is\n");
fprintf(stderr, " inform the tool that is working in a pure Windows native environment.\n");
#ifdef HAVE_WINPATH
fprintf(stderr, " --winpaths <TOPDIR>\n");
fprintf(stderr, " This option is useful when using a Windows native toolchain in a POSIX environment (such\n");
fprintf(stderr, " such as Cygwin). In this case, will CC generates dependency lists using Windows paths\n");
fprintf(stderr, " (e.g., C:\\blablah\\blabla). This switch instructs the script to use 'cygpath' to convert\n");
fprintf(stderr, " the Windows paths to Cygwin POSIXE paths.\n");
#endif
fprintf(stderr, " --help\n");
fprintf(stderr, " Shows this message and exits\n");
exit(exitcode);
}
static void parse_args(int argc, char **argv)
{
char *args = NULL;
int argidx;
/* Accumulate CFLAGS up to "--" */
for (argidx = 1; argidx < argc; argidx++)
{
if (strcmp(argv[argidx], "--") == 0)
{
g_cc = g_cflags;
g_cflags = args;
args = NULL;
}
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], "--winnative") == 0)
{
g_winnative = true;
}
#ifdef HAVE_WINPATH
else if (strcmp(argv[argidx], "--winpath") == 0)
{
g_winpath = true;
if (g_topdir)
{
free(g_topdir);
}
argidx++;
if (argidx >= argc)
{
show_usage(argv[0], "ERROR: Missing argument to --winpath", EXIT_FAILURE);
}
g_topdir = strdup(argv[argidx]);
}
#endif
else if (strcmp(argv[argidx], "--help") == 0)
{
show_usage(argv[0], NULL, EXIT_SUCCESS);
}
else
{
append(&args, argv[argidx]);
}
}
/* The final thing accumulated is the list of files */
g_files = args;
/* If no paths were specified, then look in the current directory only */
if (!g_altpath)
{
g_altpath = strdup(".");
}
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)");
#ifdef HAVE_WINPATH
fprintf(stderr, " Windows Paths : \"%s\"\n", g_winpath ? "TRUE" : "FALSE");
if (g_winpath)
{
fprintf(stderr, " TOPDIR : \"%s\"\n", g_topdir);
}
#endif
fprintf(stderr, " Windows Native : \"%s\"\n", g_winnative ? "TRUE" : "FALSE");
}
/* Check for required paramters */
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 generataion\n");
exit(EXIT_SUCCESS);
}
#ifdef HAVE_WINPATH
if (g_winnative && g_winpath)
{
show_usage(argv[0], "ERROR: Both --winnative and --winpapth makes no sense", EXIT_FAILURE);
}
#endif
}
static void do_dependency(const char *file, char separator)
{
static const char moption[] = " -M ";
char command[MAX_BUFFER];
struct stat buf;
char *altpath;
char *path;
char *bufptr;
int cmdlen;
int pathlen;
int filelen;
int totallen;
int ret;
/* Copy the compiler into the command buffer */
cmdlen = strlen(g_cc);
if (cmdlen >= MAX_BUFFER)
{
fprintf(stderr, "ERROR: Compiler string is too long: %s\n", path);
exit(EXIT_FAILURE);
}
strcpy(command, g_cc);
/* Copy " -M " */
cmdlen += strlen(moption);
if (cmdlen >= MAX_BUFFER)
{
fprintf(stderr, "ERROR: Option string is too long: %s\n", moption);
exit(EXIT_FAILURE);
}
strcat(command, moption);
/* Copy the CFLAGS into the command buffer */
cmdlen += strlen(g_cflags);
if (cmdlen >= MAX_BUFFER)
{
fprintf(stderr, "ERROR: CFLAG string is too long: %s\n", g_cflags);
exit(EXIT_FAILURE);
}
strcat(command, g_cflags);
/* Add a space */
command[cmdlen] = ' ';
command[cmdlen+1] = '\0';
cmdlen++;
/* Try each path. This loop will continue until each path has been tried
* (failure) or until stat() finds the file
*/
altpath = g_altpath;
while ((path = strtok(altpath, " ")) != NULL)
{
/* Create a full path to the file */
pathlen = strlen(path);
totallen = cmdlen + pathlen;
if (totallen >= MAX_BUFFER)
{
fprintf(stderr, "ERROR: Path is too long: %s\n", path);
exit(EXIT_FAILURE);
}
strcpy(&command[cmdlen], path);
if (command[totallen] != '\0')
{
fprintf(stderr, "ERROR: Missing NUL terminator\n", path);
exit(EXIT_FAILURE);
}
if (command[totallen-1] != separator)
{
command[totallen] = separator;
command[totallen+1] = '\0';
pathlen++;
totallen++;
}
filelen = strlen(file);
totallen += filelen;
if (totallen >= MAX_BUFFER)
{
fprintf(stderr, "ERROR: Path+file is too long\n");
exit(EXIT_FAILURE);
}
strcat(command, file);
/* Check that a file actually exists at this path */
ret = stat(command, &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");
exit(EXIT_FAILURE);
}
/* Okay.. we have. Create the dependency */
ret = system(command);
if (ret != 0)
{
fprintf(stderr, "ERROR: ssystem(%s) failed\n");
exit(EXIT_FAILURE);
}
/* We don't really know that the command succeeded... Let's assume that it did */
return;
}
printf("# ERROR: No readable file for \"%s\" found at any location\n", file);
exit(EXIT_FAILURE);
}
/* Convert a Cygwin path to a Windows path */
#ifdef HAVE_WINPATH
static char *cywin2windows(const char *str, const char *append, enum slashmode_e mode)
{
static const char cygdrive[] = "/cydrive";
const char *src = src;
char *dest;
char *newpath;
char *allocpath = NULL;
int srclen = strlen(str);
int alloclen = 0;
int drive = 0;
int lastchar;
/* Skip any leading whitespace */
while (isspace(*str)) str++;
/* Were we asked to append something? */
if (append)
{
char *tmp;
alloclen = sizeof(str) + sizeof(append) + 1;
allocpath = (char *)malloc(alloclen);
if (!allocpath)
{
fprintf(stderr, "ERROR: Failed to allocate %d bytes\n", alloclen);
exit(EXIT_FAILURE);
}
snprintf(allocpath, alloclen, "%s/%s", str, append);
}
/* Looking for path of the form /cygdrive/c/bla/bla/bla */
if (strcasecmp(src, cygdrive) == 0)
{
int cygsize = sizeof(cygdrive);
if (src[cygsize] == '/')
{
cygsize++;
srclen -= cygsize;
src += cygsize;
if (srclen <= 0)
{
fprintf(stderr, "ERROR: Unhandled path: \"%s\"\n", str);
exit(EXIT_FAILURE);
}
drive = toupper(*src);
if (drive < 'A' || drive > 'Z')
{
fprintf(stderr, "ERROR: Drive charager: \"%s\"\n", str);
exit(EXIT_FAILURE);
}
srclen--;
src++;
alloclen = 2;
}
}
/* Determine the size of the new path */
alloclen += sizeof(src) + 1;
if (mode == MODE_DBLBACK)
{
const char *tmpptr;
for (tmpptr = src; *tmpptr; tmpptr++)
{
if (*tmpptr == '/') alloclen++;
}
}
/* Allocate memory for the new path */
newpath = (char *)malloc(alloclen);
if (!newpath)
{
fprintf(stderr, "ERROR: Failed to allocate %d bytes\n", alloclen);
exit(EXIT_FAILURE);
}
dest = newpath;
/* Copy the drive character */
if (drive)
{
*dest++ = drive;
*dest++ = ':';
}
/* Copy each character from the source, making modifications for foward slashes as required */
lastchar = '\0';
for (; *src; src++)
{
if (mode != MODE_FSLASH && *src == '/')
{
if (lastchar != '/')
{
*dest++ = '\\';
if (mode == MODE_DBLBACK)
{
*dest++ = '\\';
}
}
}
else
{
*dest++ = *src;
}
lastchar = *src;
}
*dest++ = '\0';
if (allocpath)
{
free(allocpath);
}
return dest;
}
#endif
#ifdef HAVE_WINPATH
static void do_winpath(char *file)
{
/* The file is in POSIX format. CC expects Windows format to generate the
* dependencies, but GNU make expect the resulting dependencies to be back
* in POSIX format. What a mess!
*/
char *path = cywin2windows(g_topdir, file, MODE_FSLASH);
/* Then get the dependency and perform conversions on it to make it
* palatable to the Cygwin make.
*/
#warning "Missing logic"
free(path);
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, char **argv, char **envp)
{
char *files;
char *file;
/* Parse command line parameters */
parse_args(argc, argv);
/* Then generate dependencies for each path on the command line */
files = g_files;
while ((file = strtok(files, " ")) != NULL)
{
/* Check if we need to do path conversions for a Windows-natvie tool
* being using in a POSIX/Cygwin environment.
*/
#ifdef HAVE_WINPATH
if (g_winpath)
{
do_winpath(file);
}
else
#endif
{
do_dependency(file, g_winnative ? '\\' : '/');
}
files = NULL;
}
return EXIT_SUCCESS;
}