/* ccdv.c */ #include #include #include #include #include #include #include #include #include #define SETCOLOR_SUCCESS (gANSIEscapes ? "\033\1331;32m" : "") #define SETCOLOR_FAILURE (gANSIEscapes ? "\033\1331;31m" : "") #define SETCOLOR_WARNING (gANSIEscapes ? "\033\1331;33m" : "") #define SETCOLOR_COMMAND (gANSIEscapes ? "\033\1331;35m" : "") #define SETCOLOR_ERROROUTPUT (gANSIEscapes ? "\033\1331;31m" : "") #define SETCOLOR_WARNINGOUTPUT (gANSIEscapes ? "\033\1331;39m" : "") #define SETCOLOR_NORMAL (gANSIEscapes ? "\033\1330;39m" : "") #define TEXT_BLOCK_SIZE 8192 #define INDENT 2 size_t gNBufUsed = 0, gNBufAllocated = 0; char *gBuf = NULL; int gCCPID; char gAction[64] = ""; char gTarget[64] = ""; char gAr[32] = ""; char gArLibraryTarget[64] = ""; int gDumpCmdArgs = 0; char gArgsStr[32768]; int gColumns = 80; int gANSIEscapes = 0; int gExitStatus = 95; static void DumpFormattedOutput(void) { char *cp; char spaces[8 + 1] = " "; char *saved; int curcol; int i; curcol = 0; saved = NULL; if(gDumpCmdArgs) { printf("%s%s%s", SETCOLOR_COMMAND, gArgsStr, SETCOLOR_NORMAL); for(i = 0; i < gColumns; ++i) { putchar('='); } } printf("%s%s%s", gDumpCmdArgs ? SETCOLOR_ERROROUTPUT : SETCOLOR_WARNINGOUTPUT, gBuf + strlen(gArgsStr), SETCOLOR_NORMAL); free(gBuf); } /* DumpFormattedOutput */ /* Difftime(), only for timeval structures. */ static void TimeValSubtract(struct timeval *tdiff, struct timeval *t1, struct timeval *t0) { tdiff->tv_sec = t1->tv_sec - t0->tv_sec; tdiff->tv_usec = t1->tv_usec - t0->tv_usec; if(tdiff->tv_usec < 0) { tdiff->tv_sec--; tdiff->tv_usec += 1000000; } } /* TimeValSubtract */ static void Wait(void) { int pid2, status; do { status = 0; pid2 = (int) waitpid(gCCPID, &status, 0); } while(((pid2 >= 0) && (!WIFEXITED(status))) || ((pid2 < 0) && (errno == EINTR))); if(WIFEXITED(status)) gExitStatus = WEXITSTATUS(status); } /* Wait */ static int SlurpProgress(int fd) { char s1[71]; char *newbuf; int nready; size_t ntoread; ssize_t nread; struct timeval now, tnext, tleft; fd_set ss; fd_set ss2; const char *trail = "/-\\|", *trailcp; trailcp = trail; snprintf(s1, sizeof(s1), "%s%s%s... ", gAction, gTarget[0] ? " " : "", gTarget); printf("\r%-70s%-9s", s1, ""); fflush(stdout); gettimeofday(&now, NULL); tnext = now; tnext.tv_sec++; tleft.tv_sec = 1; tleft.tv_usec = 0; FD_ZERO(&ss2); FD_SET(fd, &ss2); for(;;) { if(gNBufUsed == (gNBufAllocated - 1)) { if((newbuf = (char *) realloc(gBuf, gNBufAllocated + TEXT_BLOCK_SIZE)) == NULL) { perror("ccdv: realloc"); return (-1); } gNBufAllocated += TEXT_BLOCK_SIZE; gBuf = newbuf; } for(;;) { ss = ss2; nready = select(fd + 1, &ss, NULL, NULL, &tleft); if(nready == 1) break; if(nready < 0) { if(errno != EINTR) { perror("ccdv: select"); return (-1); } continue; } gettimeofday(&now, NULL); if((now.tv_sec > tnext.tv_sec) || ((now.tv_sec == tnext.tv_sec) && (now.tv_usec >= tnext.tv_usec))) { tnext = now; tnext.tv_sec++; tleft.tv_sec = 1; tleft.tv_usec = 0; printf("\r%-71s%c%-7s", s1, *trailcp, ""); fflush(stdout); if(*++trailcp == '\0') trailcp = trail; } else { TimeValSubtract(&tleft, &tnext, &now); } } ntoread = (gNBufAllocated - gNBufUsed - 1); nread = read(fd, gBuf + gNBufUsed, ntoread); if(nread < 0) { if(errno == EINTR) continue; perror("ccdv: read"); return (-1); } else if(nread == 0) { break; } gNBufUsed += nread; gBuf[gNBufUsed] = '\0'; } snprintf(s1, sizeof(s1), "%s%s%s: ", gAction, gTarget[0] ? " " : "", gTarget); Wait(); if(gExitStatus == 0) { printf("\r%-70s", s1); printf("[%s%s%s]", ((gNBufUsed - strlen(gArgsStr)) < 4) ? SETCOLOR_SUCCESS : SETCOLOR_WARNING, "OK", SETCOLOR_NORMAL); printf("%-5s\n", " "); } else { printf("\r%-70s", s1); printf("[%s%s%s]", SETCOLOR_FAILURE, "ERROR", SETCOLOR_NORMAL); printf("%-2s\n", " "); gDumpCmdArgs = 1; /* print cmd when there are errors */ } fflush(stdout); return (0); } /* SlurpProgress */ static int SlurpAll(int fd) { char *newbuf; size_t ntoread; ssize_t nread; printf("%s%s%s.\n", gAction, gTarget[0] ? " " : "", gTarget); fflush(stdout); for(;;) { if(gNBufUsed == (gNBufAllocated - 1)) { if((newbuf = (char *) realloc(gBuf, gNBufAllocated + TEXT_BLOCK_SIZE)) == NULL) { perror("ccdv: realloc"); return (-1); } gNBufAllocated += TEXT_BLOCK_SIZE; gBuf = newbuf; } ntoread = (gNBufAllocated - gNBufUsed - 1); nread = read(fd, gBuf + gNBufUsed, ntoread); if(nread < 0) { if(errno == EINTR) continue; perror("ccdv: read"); return (-1); } else if(nread == 0) { break; } gNBufUsed += nread; gBuf[gNBufUsed] = '\0'; } Wait(); gDumpCmdArgs = (gExitStatus != 0); /* print cmd when there are errors */ return (0); } /* SlurpAll */ static const char *Basename(const char *path) { const char *cp; cp = strrchr(path, '/'); if(cp == NULL) return (path); return (cp + 1); } /* Basename */ static const char *Extension(const char *path) { const char *cp = path; cp = strrchr(path, '.'); if(cp == NULL) return (""); return (cp); } /* Extension */ static void Usage(void) { char *sUsage = "\ Usage: ccdv /path/to/cc CFLAGS...\n\ \n\ I wrote this to reduce the deluge Make output to make finding actual problems\n\ easier. It is intended to be invoked from Makefiles, like this. Instead of:\n\ \n\ .c.o:\n\ $(CC) $(CFLAGS) $(DEFS) $(CPPFLAGS) $< -c\n\ \n\ Rewrite your rule so it looks like:\n\ \n\ .c.o:\n\ @ccdv $(CC) $(CFLAGS) $(DEFS) $(CPPFLAGS) $< -c\n\ .cpp.o:\n\ @ccdv $(CXX) $(CFLAGS) $(DEFS) $(CPPFLAGS) $< -c\n\ \n\ ccdv 1.1.0 is Free under the GNU Public License. Enjoy!\n\ -- Mike Gleason, NcFTP Software \n\ -- John F Meinel Jr, \n\ "; fprintf(stderr, sUsage); exit(96); } /* Usage */ int main(int argc, char **argv) { int pipe1[2]; int devnull; char emerg[256]; int fd; int nread; int i; int cc = 0; int yy = 0; int gcc = 0; int lemon = 0; const char *quote; if(argc < 2) Usage(); snprintf(gAction, sizeof(gAction), "Running %s", Basename(argv[1])); memset(gArgsStr, 0, sizeof(gArgsStr)); if(strcmp(gAction+8, "ar") == 0) { snprintf(gTarget, sizeof(gTarget), "%s", Basename(argv[i + 1])); } else if(strcmp(gAction+8, "cc") == 0 || strcmp(gAction+8, "ld") == 0 || strcmp(gAction+8, "gcc") == 0 || strcmp(gAction+8, "g++") == 0 || strcmp(gAction+8, "c++") == 0) { gcc = 1; } else if(strcmp(gAction+8, "lemon") == 0) { lemon = 1; } for(i = 1; i < argc; i++) { quote = (strchr(argv[i], ' ') != NULL) ? "\"" : ""; snprintf(gArgsStr + strlen(gArgsStr), sizeof(gArgsStr) - strlen(gArgsStr), "%s%s%s%s%s", (i == 1) ? "" : " ", quote, argv[i], quote, (i == (argc - 1)) ? "\n" : ""); if(gcc && (strcmp(argv[i], "-o") == 0) && ((i + 1) < argc)) { if(strcasecmp(Extension(argv[i + 1]), ".o") != 0) { strcpy(gAction, "Linking"); snprintf(gTarget, sizeof(gTarget), "%s", Basename(argv[i + 1])); } } else if(strchr("-+/", (int) argv[i][0]) != NULL) { continue; } else if(strncasecmp(Extension(argv[i]), ".c", 2) == 0) { cc++; snprintf(gTarget, sizeof(gTarget), "%s", Basename(argv[i])); } else if(strncasecmp(Extension(argv[i]), ".y", 2) == 0) { if(lemon) { strcpy(gAction, "Generating"); snprintf(gTarget, sizeof(gTarget), "%s", Basename(argv[i])); } yy++; } else if(strncasecmp(Extension(argv[i]), ".nas", 3) == 0 || strncasecmp(Extension(argv[i]), ".asm", 3) == 0 || strcasecmp(Extension(argv[i]), ".s") == 0) { strcpy(gAction, "Assembling"); snprintf(gTarget, sizeof(gTarget), "%s", Basename(argv[i])); } else if((i == 1) && (strcmp(Basename(argv[i]), "ar") == 0)) { snprintf(gAr, sizeof(gAr), "%s", Basename(argv[i])); } else if((gArLibraryTarget[0] == '\0') && (strcasecmp(Extension(argv[i]), ".a") == 0)) { snprintf(gArLibraryTarget, sizeof(gArLibraryTarget), "%s", Basename(argv[i])); } } if((gAr[0] != '\0') && (gArLibraryTarget[0] != '\0')) { strcpy(gAction, "Creating library"); snprintf(gTarget, sizeof(gTarget), "%s", gArLibraryTarget); } else if(cc > 0) { strcpy(gAction, yy == 0 ? "Compiling" : "Generating"); } if(pipe(pipe1) < 0) { perror("ccdv: pipe"); exit(97); } (void) close(0); devnull = open("/dev/null", O_RDWR, 00666); if((devnull != 0) && (dup2(devnull, 0) == 0)) close(devnull); gCCPID = (int) fork(); if(gCCPID < 0) { (void) close(pipe1[0]); (void) close(pipe1[1]); perror("ccdv: fork"); exit(98); } else if(gCCPID == 0) { /* Child */ (void) close(pipe1[0]); /* close read end */ if(pipe1[1] != 1) { /* use write end on stdout */ (void) dup2(pipe1[1], 1); (void) close(pipe1[1]); } (void) dup2(1, 2); /* use write end on stderr */ execvp(argv[1], argv + 1); perror(argv[1]); exit(99); } /* parent */ (void) close(pipe1[1]); /* close write end */ fd = pipe1[0]; /* use read end */ gColumns = (getenv("COLUMNS") != NULL) ? atoi(getenv("COLUMNS")) : 80; gANSIEscapes = (getenv("TERM") != NULL) && (strstr ("vt100:vt102:vt220:vt320:xterm:ansi:linux:scoterm:scoansi:dtterm:cons25:cygwin", getenv("TERM")) != NULL); gBuf = (char *) malloc(TEXT_BLOCK_SIZE); if(gBuf == NULL) goto panic; gNBufUsed = 0; gNBufAllocated = TEXT_BLOCK_SIZE; if(strlen(gArgsStr) < (gNBufAllocated - 1)) { strcpy(gBuf, gArgsStr); gNBufUsed = strlen(gArgsStr); } if(isatty(1)) { if(SlurpProgress(fd) < 0) goto panic; } else { if(SlurpAll(fd) < 0) goto panic; } DumpFormattedOutput(); exit(gExitStatus); panic: gDumpCmdArgs = 1; /* print cmd when there are errors */ DumpFormattedOutput(); while((nread = read(fd, emerg, (size_t) sizeof(emerg))) > 0) (void) write(2, emerg, (size_t) nread); Wait(); exit(gExitStatus); } /* main */ /* eof ccdv.c */