/* * MakeDeps, NMAKE companion * * Fredrik Roubert , 2003 * http://www.df.lth.se/~roubert/ * * $Id: makedeps.cpp,v 1.6 2003/08/24 13:42:12 roubert Exp $ * */ #pragma comment(lib, "shlwapi.lib") #define VC_EXTRALEAN #include #include #include #include #include #include #include #include #include #include #if _UNICODE # include # define tmemcpy(dst, src, count) wmemcpy(dst, src, count) #else # define tmemcpy(dst, src, count) memcpy(dst, src, count) #endif #if _MSC_VER < 1300 # pragma warning(disable:4100) // Unreferenced formal parameter # pragma warning(disable:4245) // Signed/unsigned mismatch # pragma warning(disable:4510) // Default constructor could not be generated # pragma warning(disable:4610) // User-defined constructor required # pragma warning(disable:4786) // Identifier was truncated # include #else # include #endif #include static const TCHAR depext[] = _T(".d"); static const TCHAR objext[] = _T(".obj"); static const TCHAR errfmt[] = _T("%s: %i: %s(): "); static const TCHAR failed[] = _T("%s: %i: %s(): Failed.\n"); #define WERROR(function) \ weprintf(stderr, GetLastError(), errfmt, \ _T(__FILE__), __LINE__, _T(#function)) #define CERROR(function) \ ceprintf(stderr, errno, errfmt, \ _T(__FILE__), __LINE__, _T(#function)) #define FERROR(function) \ _ftprintf(stderr, failed, \ _T(__FILE__), __LINE__, _T(#function)) class CCString { protected: LPSTR string; int *count; public: CCString(LPCSTR src) { size_t l = 1 + strlen(src); string = new CHAR[l]; count = new int(1); memcpy(string, src, l); } CCString(const CCString& src) : string(src.string), count(src.count) { (*count) ++; } ~CCString() { if (-- (*count) == 0) { delete[] string; delete count; } } inline operator LPCSTR () const throw () { return string; } #if _MSC_VER < 1300 inline bool operator < (const CCString& x) const throw () { return strcmp(string, x.string) < 0; } #endif }; #if _UNICODE class CTString { protected: LPWSTR string; int *count; public: CTString(LPCWSTR src) { size_t l = 1 + wcslen(src); string = new WCHAR[l]; count = new int(1); wmemcpy(string, src, l); } CTString(const CTString& src) : string(src.string), count(src.count) { (*count) ++; } ~CTString() { if (-- (*count) == 0) { delete[] string; delete count; } } inline operator LPCWSTR () const throw () { return string; } }; #else typedef CCString CTString; #endif #if _MSC_VER < 1300 typedef std::set CHash; #else class CCStringHash { public: enum { bucket_size = 4, min_buckets = 8 }; size_t operator () (LPCSTR str) const throw () { size_t i, j; for (i = 0, j = 0; str[i] != '\0'; i ++) { j *= 7; j += str[i]; } return j; } bool operator () (LPCSTR a, LPCSTR b) const throw () { return strcmp(a, b) < 0; } }; typedef std::hash_set CHash; #endif typedef std::list CList; static inline bool MakeDeps(LPCTSTR srcpath, LPCTSTR dstpath, LPCTSTR opts); static inline LPTSTR MakeOptLine(CList& opts); static inline LPTSTR MakeCmdLine(LPCTSTR path, LPCTSTR opts); static inline HANDLE PreProcess(LPTSTR cmdline); static inline bool Parse(HANDLE hCompiler, CHash& hash); static inline bool ParseLine(LPSTR line, CHash& hash); static inline bool WriteDeps(LPCTSTR srcpath, LPCTSTR dstpath, CHash& hash); static inline bool MakePaths(LPCTSTR src, LPCTSTR dst, LPTSTR cwd, LPTSTR dep); static inline LPTSTR ConvertPath(LPCSTR src); static inline LPTSTR MultiByteToTChar(LPCSTR str); static inline bool MakeLists(LPCTSTR file, LPCTSTR dir, LPCTSTR pat[], int n); static inline bool MatchPattern(LPCTSTR pat, CList& list); static inline bool WriteList(FILE *stream, LPCTSTR name, LPCTSTR dir, LPCTSTR ext, CList& list); static void weprintf(FILE *stream, LONG status, LPCTSTR fmt = NULL, ...); static void ceprintf(FILE *stream, int errnum, LPCTSTR fmt = NULL, ...); int __cdecl _tmain(int argc, LPCTSTR argv[]) { CList opts, args; CList::iterator it; LPTSTR optline; LPCTSTR dstpath; size_t l; int i; if (argc < 2) { _fputts ( _T("\n") _T("MakeDeps (Fredrik Roubert 2003)\n") _T("\n") _T("usage: makedeps [ option... ] filename... [ destination ]\n") _T("usage: makedeps pattern... destination filename\n"), stderr ); goto error; } if (argc >= 4) for (i = 1; i < argc - 2; i ++) for (l = 0; argv[i][l] != _T('\0'); l ++) if (argv[i][l] == _T('*') || argv[i][l] == _T('?')) goto makelists; dstpath = NULL; for (i = 1; i < argc; i ++) if (argv[i][0] == _T('/') || argv[i][0] == _T('-')) opts.push_back(argv[i]); else { if (i == argc - 1) { l = _tcslen(argv[i]); if (l > 0 && argv[i][l - 1] == _T('\\')) { dstpath = argv[i]; break; } } args.push_back(argv[i]); } if (args.empty()) { _fputts(_T("makedeps: Missing source filename.\n"), stderr); goto error; } if ((optline = MakeOptLine(opts)) == NULL) goto error; for (it = args.begin(); it != args.end(); it ++) if (! MakeDeps(*it, dstpath, optline)) _ftprintf(stderr, _T("%s: Could not process file.\n"), *it); free(optline); return EXIT_SUCCESS; makelists: if (! MakeLists(argv[argc - 1], argv[argc - 2], argv + 1, argc - 3)) goto error; return EXIT_SUCCESS; error: return EXIT_FAILURE; } static inline bool MakeDeps(LPCTSTR srcpath, LPCTSTR dstpath, LPCTSTR opts) { HANDLE hSource; LPTSTR cmdline; CHash hash; if ((cmdline = MakeCmdLine(srcpath, opts)) == NULL) goto error; if ((hSource = PreProcess(cmdline)) == INVALID_HANDLE_VALUE) goto error_free; if (! Parse(hSource, hash)) goto error_close; if (! CloseHandle(hSource)) { WERROR(CloseHandle); goto error_free; } free(cmdline); if (! WriteDeps(srcpath, dstpath, hash)) goto error; return true; error_close: CloseHandle(hSource); error_free: free(cmdline); error: return false; } static inline LPTSTR MakeOptLine(CList& opts) { CList::iterator it; LPTSTR tmp; size_t i, j, l; if (opts.empty()) l = 1; else for (l = 1, it = opts.begin(); it != opts.end(); it ++) l += 3 + _tcslen(*it); if ((tmp = (LPTSTR)malloc(l * sizeof (TCHAR))) == NULL) { FERROR(malloc); goto error; } if (l > 1) for (i = 0, it = opts.begin(); it != opts.end(); it ++, i += j) if ((j = _sntprintf(tmp + i, l - i, _T("\"%s\" "), *it)) < 0) { CERROR(_sntprintf); goto error_free; } tmp[l - 1] = _T('\0'); return tmp; error_free: free(tmp); error: return NULL; } static inline LPTSTR MakeCmdLine(LPCTSTR path, LPCTSTR opts) { LPTSTR tmp; size_t l; l = 17 + _tcslen(path) + _tcslen(opts); if ((tmp = (LPTSTR)malloc(l * sizeof (TCHAR))) == NULL) { FERROR(malloc); goto error; } if (_sntprintf(tmp, l, _T("cl /nologo %s/E \"%s\""), opts, path) < 0) { CERROR(_sntprintf); goto error_free; } return tmp; error_free: free(tmp); error: return NULL; } static inline HANDLE PreProcess(LPTSTR cmdline) { HANDLE hPipeRead, hPipeWrite, hPipeReadDup, hStdout; SECURITY_ATTRIBUTES saAttr; PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; saAttr.nLength = sizeof (SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if ((hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE) { WERROR(GetStdHandle); goto error; } if (! CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) { WERROR(CreatePipe); goto error; } if (! DuplicateHandle(GetCurrentProcess(), hPipeRead, GetCurrentProcess(), &hPipeReadDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { WERROR(DuplicateHandle); CloseHandle(hPipeRead); goto error_closewrite; } if (! CloseHandle(hPipeRead)) { WERROR(CloseHandle); goto error_closeread; } if (! SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite)) { WERROR(SetStdHandle); goto error_closeread; } ZeroMemory(&piProcInfo, sizeof (PROCESS_INFORMATION)); ZeroMemory(&siStartInfo, sizeof (STARTUPINFO)); siStartInfo.cb = sizeof (STARTUPINFO); if (! CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { WERROR(CreateProcess); goto error_setstdout; } if (! CloseHandle(piProcInfo.hProcess)) { WERROR(CloseHandle); goto error_setstdout; } if (! CloseHandle(piProcInfo.hThread)) { WERROR(CloseHandle); goto error_setstdout; } if (! SetStdHandle(STD_OUTPUT_HANDLE, hStdout)) { WERROR(SetStdHandle); goto error_closeread; } if (! CloseHandle(hPipeWrite)) { WERROR(CloseHandle); CloseHandle(hPipeReadDup); goto error; } return hPipeReadDup; error_setstdout: SetStdHandle(STD_OUTPUT_HANDLE, hStdout); error_closeread: CloseHandle(hPipeReadDup); error_closewrite: CloseHandle(hPipeWrite); error: return INVALID_HANDLE_VALUE; } static inline bool Parse(HANDLE hCompiler, CHash& hash) { LPSTR buf, tmp; DWORD i, j, k, l, size, read; size = 64; if ((buf = (LPSTR)malloc(size * sizeof (CHAR))) == NULL) { FERROR(malloc); goto error; } for (i = 0;;) { if (! ReadFile(hCompiler, buf + i, size - i, &read, NULL)) { if (GetLastError() != ERROR_BROKEN_PIPE) { WERROR(ReadFile); goto error_free; } else break; } for (j = i, k = 0, l = read + i; j < l; j ++) if (buf[j] == '\r' || buf[j] == '\n') { if (j > k) { buf[j] = '\0'; if (! ParseLine(buf + k, hash)) goto error_free; } if (j + 1 < l && buf[j + 1] == '\n') j ++; k = j + 1; } if (k < l) { if (k == 0) { if (l == size) { size <<= 1; if ((tmp = (LPSTR)realloc(buf, size)) == NULL) { FERROR(realloc); goto error_free; } buf = tmp; } i = l; } else { i = l - k; memmove(buf, buf + k, i); } } else i = 0; } free(buf); return true; error_free: free(buf); error: return false; } static inline bool ParseLine(LPSTR line, CHash& hash) { static const CHAR prefix[] = "#line "; static const size_t prefixl = sizeof (prefix) - 1; size_t i, j; if (strncmp(line, prefix, prefixl) != 0) return true; for (i = prefixl; line[i] != '"' && line[i] != '\0'; i ++) ; for (j = strlen(line); j > i && line[j] != '"'; j --) ; if (++ i >= j) goto error; line[j] = '\0'; line += i; hash.insert(line); return true; error: return false; } static inline bool WriteDeps(LPCTSTR srcpath, LPCTSTR dstpath, CHash& hash) { CHash::iterator it; FILE *stream; LPTSTR file; TCHAR cwd[MAX_PATH], dep[MAX_PATH]; size_t cwdl, l; if (! MakePaths(srcpath, dstpath, cwd, dep)) goto error; cwdl = _tcslen(cwd); if ((stream = _tfopen(dep, _T("wtS"))) == NULL) { CERROR(_tfopen); goto error; } _ftprintf(stream, _T("%s:"), srcpath); for (it = hash.begin(); it != hash.end(); it ++) { if ((file = ConvertPath(*it)) == NULL) goto error_close; if (_tcsicmp(file, srcpath) == 0) ; else if (_tcsncicmp(file, cwd, cwdl) != 0) ; else if (_tcsicmp(file + cwdl, srcpath) == 0) ; else if (file[cwdl] != _T('\0')) { for (l = cwdl; file[l] != _T('\0'); l ++) if (file[l] == _T(' ')) break; if (file[l] == _T('\0')) _ftprintf(stream, _T(" %s"), file + cwdl); else _ftprintf(stream, _T(" \"%s\""), file + cwdl); } free(file); } _ftprintf(stream, _T("\n")); if (fclose(stream) == EOF) { CERROR(fclose); goto error; } return true; error_close: fclose(stream); error: return false; } static inline bool MakePaths(LPCTSTR src, LPCTSTR dst, LPTSTR cwd, LPTSTR dep) { TCHAR tmp[MAX_PATH]; LPTSTR name, a, b; if (_tgetcwd(cwd, MAX_PATH) == NULL) { CERROR(_tgetcwd); goto error; } PathAddBackslash(cwd); a = PathFindFileName(src); b = PathFindExtension(a); name = dst == NULL ? dep : tmp; tmemcpy(name, a, b - a); name[b - a] = _T('\0'); if (! PathAddExtension(name, depext)) { FERROR(PathAddExtension); goto error; } if (dst != NULL && PathCombine(dep, dst, name) == NULL) { FERROR(PathCombine); goto error; } return true; error: return false; } static inline LPTSTR ConvertPath(LPCSTR src) { LPTSTR dst, tmp; size_t i, j, l; if ((tmp = MultiByteToTChar(src)) == NULL) goto error; l = 1 + _tcslen(tmp); if ((dst = (LPTSTR)malloc(l * sizeof (TCHAR))) == NULL) { FERROR(malloc); goto error_free; } for (i = 0, j = 0; i < l; i ++, j ++) { dst[j] = tmp[i]; if (i < l - 1 && tmp[i] == _T('\\') && tmp[i + 1] == _T('\\')) i ++; } free(tmp); return dst; error_free: free(tmp); error: return NULL; } static inline LPTSTR MultiByteToTChar(LPCSTR str) { #if _UNICODE UINT codepage; LPWSTR tmp; int l; codepage = GetACP(); if ((l = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str, -1, NULL, 0)) == 0) { WERROR(MultiByteToWideChar); goto error; } if ((tmp = (LPWSTR)malloc(l * sizeof (WCHAR))) == NULL) { WERROR(malloc); goto error; } if (MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str, -1, tmp, l) == 0) { WERROR(MultiByteToWideChar); goto error_free; } return tmp; error_free: free(tmp); error: return NULL; #else LPSTR tmp; if ((tmp = _strdup(str)) == NULL) FERROR(_strdup); return tmp; #endif } static inline bool MakeLists(LPCTSTR file, LPCTSTR dir, LPCTSTR pat[], int n) { CList list; FILE *stream; int i; for (i = 0; i < n; i ++) if (! MatchPattern(pat[i], list)) _ftprintf(stderr, _T("%s: Could not process pattern.\n"), pat[i]); if ((stream = _tfopen(file, _T("wtS"))) == NULL) { CERROR(_tfopen); goto error; } if (! WriteList(stream, _T("DEPS"), dir, depext, list)) goto error_close; if (! WriteList(stream, _T("OBJS"), dir, objext, list)) goto error_close; if (fclose(stream) == EOF) { CERROR(fclose); goto error; } return true; error_close: fclose(stream); error: return false; } static inline bool MatchPattern(LPCTSTR pat, CList& list) { HANDLE hFind; WIN32_FIND_DATA data; if ((hFind = FindFirstFile(pat, &data)) == INVALID_HANDLE_VALUE) { WERROR(FindFirstFile); goto error; } for (;;) { list.push_back(data.cFileName); if (! FindNextFile(hFind, &data)) { if (GetLastError() != ERROR_NO_MORE_FILES) { WERROR(FindNextFile); goto error_close; } else break; } } if (! FindClose(hFind)) { WERROR(FindClose); goto error; } return true; error_close: FindClose(hFind); error: return false; } static inline bool WriteList(FILE *stream, LPCTSTR name, LPCTSTR dir, LPCTSTR ext, CList& list) { CList::iterator it; TCHAR tmp[MAX_PATH]; _fputts(name, stream); _fputts(_T(" ="), stream); for (it = list.begin(); it != list.end(); it ++) { if (PathCombine(tmp, dir, *it) == NULL) { FERROR(PathCombine); goto error; } if (! PathRenameExtension(tmp, ext)) { FERROR(PathRenameExtension); goto error; } PathQuoteSpaces(tmp); _fputtc(_T(' '), stream); _fputts(tmp, stream); } _fputtc(_T('\n'), stream); return true; error: return false; } static void weprintf(FILE *stream, LONG status, LPCTSTR fmt, ...) { va_list args; LPTSTR tmp; if (fmt != NULL) { va_start(args, fmt); _vftprintf(stream, fmt, args); va_end(args); } if (FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL) == 0) _ftprintf(stream, _T("Error #%li.\n"), status); else { _fputts(tmp, stream); LocalFree(tmp); } } static void ceprintf(FILE *stream, int errnum, LPCTSTR fmt, ...) { va_list args; if (fmt != NULL) { va_start(args, fmt); _vftprintf(stream, fmt, args); va_end(args); } #if _MSC_VER < 1300 fputs(strerror(errnum), stream); #else _fputts(_tcserror(errnum), stream); #endif _fputts(_T(".\n"), stream); }