/* $Id$ */ /* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** * @file depend/depend.cpp Custom implementation of Makedepend. * * We previously used makedepend, but that could not handle the amount of * files we have and does not handle conditional includes in a sane manner. * This caused many link problems because not enough files were recompiled. * This has lead to the development of our own dependency generator. It is * meant to be a substitute to the (relatively slow) dependency generation * via gcc. It thus helps speeding up compilation. It will also ignore * system headers making it less error prone when system headers are moved * or renamed. */ #include #include #include #include #include #include #include #include #include #include /** * Return the length of an fixed size array. * Unlike sizeof this function returns the number of elements * of the given type. * * @param x The pointer to the first element of the array * @return The number of elements */ #define lengthof(x) (sizeof(x) / sizeof(x[0])) /** * Get the last element of an fixed size array. * * @param x The pointer to the first element of the array * @return The pointer to the last element of the array */ #define lastof(x) (&x[lengthof(x) - 1]) /** * Copies characters from one buffer to another. * * Copies the source string to the destination buffer with respect of the * terminating null-character and the last pointer to the last element in * the destination buffer. If the last pointer is set to nullptr no boundary * check is performed. * * @note usage: strecpy(dst, src, lastof(dst)); * @note lastof() applies only to fixed size arrays * * @param dst The destination buffer * @param src The buffer containing the string to copy * @param last The pointer to the last element of the destination buffer * @return The pointer to the terminating null-character in the destination buffer */ char *strecpy(char *dst, const char *src, const char *last) { assert(dst <= last); while (dst != last && *src != '\0') { *dst++ = *src++; } *dst = '\0'; if (dst == last && *src != '\0') { fprintf(stderr, "String too long for destination buffer\n"); exit(-3); } return dst; } /** * Appends characters from one string to another. * * Appends the source string to the destination string with respect of the * terminating null-character and and the last pointer to the last element * in the destination buffer. If the last pointer is set to nullptr no * boundary check is performed. * * @note usage: strecat(dst, src, lastof(dst)); * @note lastof() applies only to fixed size arrays * * @param dst The buffer containing the target string * @param src The buffer containing the string to append * @param last The pointer to the last element of the destination buffer * @return The pointer to the terminating null-character in the destination buffer */ static char *strecat(char *dst, const char *src, const char *last) { assert(dst <= last); while (*dst != '\0') { if (dst == last) return dst; dst++; } return strecpy(dst, src, last); } /** * Version of the standard free that accepts const pointers. * @param ptr The data to free. */ static inline void free(const void *ptr) { free(const_cast(ptr)); } #ifndef PATH_MAX /** The maximum length of paths, if we don't know it. */ # define PATH_MAX 260 #endif /** Simple string comparator using strcmp as implementation */ struct StringCompare { /** * Compare a to b using strcmp. * @param a string to compare. * @param b string to compare. * @return whether a is less than b. */ bool operator () (const char *a, const char *b) const { return strcmp(a, b) < 0; } }; /** Set of C-style strings. */ typedef std::set StringSet; /** Mapping of C-style string to a set of C-style strings. */ typedef std::map StringMap; /** Pair of C-style string and a set of C-style strings. */ typedef std::pair StringMapItem; /** Include directory to search in. */ static StringSet _include_dirs; /** Files that have been parsed/handled with their dependencies. */ static StringMap _files; /** Dependencies of headers. */ static StringMap _headers; /** The current 'active' defines. */ static StringSet _defines; /** * Helper class to read a file. */ class File { public: /** * Create the helper by opening the given file. * @param filename the file to open * @post the file is open; otherwise the application is killed. */ File(const char *filename) { this->fp = fopen(filename, "r"); if (this->fp == nullptr) { fprintf(stdout, "Could not open %s for reading\n", filename); exit(1); } this->dirname = strdup(filename); char *last = strrchr(this->dirname, '/'); if (last != nullptr) { *last = '\0'; } else { *this->dirname = '\0'; } } /** Free everything we have allocated. */ ~File() { fclose(this->fp); free(this->dirname); } /** * Get a single character from the file. * If we are reading beyond the end of the file '\0' is returned. * @return the read character. */ char GetChar() const { int c = fgetc(this->fp); return (c == EOF) ? '\0' : c; } /** * Get the directory name of the file. * @return the directory name. */ const char *GetDirname() const { return this->dirname; } private: FILE *fp; ///< The currently opened file. char *dirname; ///< The directory of the file. }; /** A token returned by the tokenizer. */ enum Token { TOKEN_UNKNOWN, ///< Unknown token TOKEN_END, ///< End of document TOKEN_EOL, ///< End of line TOKEN_SHARP, ///< # character, usually telling something important comes. TOKEN_LOCAL, ///< Read a local include TOKEN_GLOBAL, ///< Read a global include TOKEN_IDENTIFIER, ///< Identifier within the data. TOKEN_DEFINE, ///< \c \#define in code TOKEN_IF, ///< \c \#if in code TOKEN_IFDEF, ///< \c \#ifdef in code TOKEN_IFNDEF, ///< \c \#ifndef in code TOKEN_ELIF, ///< \c \#elif in code TOKEN_ELSE, ///< \c \#else in code TOKEN_ENDIF, ///< \c \#endif in code TOKEN_UNDEF, ///< \c \#undef in code TOKEN_OR, ///< '||' within \c \#if expression TOKEN_AND, ///< '&&' within \c \#if expression TOKEN_DEFINED, ///< 'defined' within \c \#if expression TOKEN_OPEN, ///< '(' within \c \#if expression TOKEN_CLOSE, ///< ')' within \c \#if expression TOKEN_NOT, ///< '!' within \c \#if expression TOKEN_ZERO, ///< '0' within \c \#if expression TOKEN_INCLUDE, ///< \c \#include in code }; /** Mapping from a C-style keyword representation to a Token. */ typedef std::map KeywordList; /** * Lexer of a file. */ class Lexer { public: /** * Create the lexer and fill the keywords table. * @param file the file to read from. */ Lexer(const File *file) : file(file), current_char('\0'), string(nullptr), token(TOKEN_UNKNOWN) { this->keywords["define"] = TOKEN_DEFINE; this->keywords["defined"] = TOKEN_DEFINED; this->keywords["if"] = TOKEN_IF; this->keywords["ifdef"] = TOKEN_IFDEF; this->keywords["ifndef"] = TOKEN_IFNDEF; this->keywords["include"] = TOKEN_INCLUDE; this->keywords["elif"] = TOKEN_ELIF; this->keywords["else"] = TOKEN_ELSE; this->keywords["endif"] = TOKEN_ENDIF; this->keywords["undef"] = TOKEN_UNDEF; /* Initialise currently read character. */ this->Next(); /* Allocate the buffer. */ this->buf_len = 32; this->buf = (char*)malloc(sizeof(*this->buf) * this->buf_len); } /** Free everything */ ~Lexer() { free(this->buf); } /** * Read the next character into 'current_char'. */ void Next() { this->current_char = this->file->GetChar(); } /** * Get the current token. * @return the token. */ Token GetToken() const { return this->token; } /** * Read the currently processed string. * @return the string, can be nullptr. */ const char *GetString() const { return this->string; } /** * Perform the lexing/tokenizing of the file till we can return something * that must be parsed. */ void Lex() { for (;;) { free(this->string); this->string = nullptr; this->token = TOKEN_UNKNOWN; switch (this->current_char) { /* '\0' means End-Of-File */ case '\0': this->token = TOKEN_END; return; /* Skip some chars, as they don't do anything */ case '\t': this->Next(); break; case '\r': this->Next(); break; case ' ': this->Next(); break; case '\\': this->Next(); if (this->current_char == '\n') this->Next(); break; case '\n': this->token = TOKEN_EOL; this->Next(); return; case '#': this->token = TOKEN_SHARP; this->Next(); return; case '"': this->ReadString('"', TOKEN_LOCAL); this->Next(); return; case '<': this->ReadString('>', TOKEN_GLOBAL); this->Next(); return; case '&': this->Next(); if (this->current_char == '&') { this->Next(); this->token = TOKEN_AND; return; } break; case '|': this->Next(); if (this->current_char == '|') { this->Next(); this->token = TOKEN_OR; return; } break; case '(': this->Next(); this->token = TOKEN_OPEN; return; case ')': this->Next(); this->token = TOKEN_CLOSE; return; case '!': this->Next(); if (this->current_char != '=') { this->token = TOKEN_NOT; return; } break; /* Possible begin of comment */ case '/': this->Next(); switch (this->current_char) { case '*': { this->Next(); char previous_char = '\0'; while ((this->current_char != '/' || previous_char != '*') && this->current_char != '\0') { previous_char = this->current_char; this->Next(); } this->Next(); break; } case '/': while (this->current_char != '\n' && this->current_char != '\0') this->Next(); break; default: break; } break; default: if (isalpha(this->current_char) || this->current_char == '_') { /* If the name starts with a letter, it is an identifier */ this->ReadIdentifier(); return; } if (isdigit(this->current_char)) { bool zero = this->current_char == '0'; this->Next(); if (this->current_char == 'x' || this->current_char == 'X') Next(); while (isdigit(this->current_char) || this->current_char == '.' || (this->current_char >= 'a' && this->current_char <= 'f') || (this->current_char >= 'A' && this->current_char <= 'F')) { zero &= this->current_char == '0'; this->Next(); } if (zero) this->token = TOKEN_ZERO; return; } this->Next(); break; } } } private: /** * The token based on keyword with a given name. * @param name the actual keyword. * @return the token of the keyword. */ Token FindKeyword(const char *name) const { KeywordList::const_iterator it = this->keywords.find(name); if (it == this->keywords.end()) return TOKEN_IDENTIFIER; return (*it).second; } /** * Read an identifier. */ void ReadIdentifier() { size_t count = 0; /* Read the rest of the identifier */ do { this->buf[count++] = this->current_char; this->Next(); if (count >= buf_len) { /* Scale the buffer if required */ this->buf_len *= 2; this->buf = (char *)realloc(this->buf, sizeof(*this->buf) * this->buf_len); } } while ((isalpha(this->current_char) || this->current_char == '_' || isdigit(this->current_char))); this->buf[count] = '\0'; free(this->string); this->string = strdup(this->buf); this->token = FindKeyword(this->string); } /** * Read a string up to a given character, then set the given token. * @param end the 'marker' for the end of the string. * @param token the token to set after returning. */ void ReadString(char end, Token token) { size_t count = 0; this->Next(); while (this->current_char != end && this->current_char != ')' && this->current_char != '\n' && this->current_char != '\0') { this->buf[count++] = this->current_char; this->Next(); if (count >= this->buf_len) { /* Scale the buffer if required */ this->buf_len *= 2; this->buf = (char *)realloc(this->buf, sizeof(*this->buf) * this->buf_len); } } this->buf[count] = '\0'; free(this->string); this->string = strdup(this->buf); this->token = token; } const File *file; ///< The file to read from. char current_char; ///< The current character to process. char *string; ///< Currently processed string. Token token; ///< The current token to process. char *buf; ///< Temporary buffer. size_t buf_len; ///< Length of the temporary buffer. KeywordList keywords; ///< All keywords we know of. }; /** * Generate a path from a directory name and a relative filename. * If the file is not local the include directory names will be used instead * of the passed parameter with directory name. If the file is local both will * be queried where the parameter takes precedence. * @param dirname the directory to look in. * @param filename the file to look for. * @param local whether to look locally (in dirname) for the file. * @return the absolute path, or nullptr if the file doesn't exist. */ const char *GeneratePath(const char *dirname, const char *filename, bool local) { /* Ignore C++ standard library headers. */ if (strchr(filename, '.') == nullptr) return nullptr; if (local) { if (access(filename, R_OK) == 0) return strdup(filename); char path[PATH_MAX]; strecpy(path, dirname, lastof(path)); const char *p = filename; /* Remove '..' from the begin of the filename. */ while (*p == '.') { if (*(++p) == '.') { char *s = strrchr(path, '/'); if (s != nullptr) *s = '\0'; p += 2; } } strecat(path, "/", lastof(path)); strecat(path, p, lastof(path)); if (access(path, R_OK) == 0) return strdup(path); } for (StringSet::iterator it = _include_dirs.begin(); it != _include_dirs.end(); it++) { char path[PATH_MAX]; strecpy(path, *it, lastof(path)); const char *p = filename; /* Remove '..' from the begin of the filename. */ while (*p == '.') { if (*(++p) == '.') { char *s = strrchr(path, '/'); if (s != nullptr) *s = '\0'; p += 2; } } strecat(path, "/", lastof(path)); strecat(path, p, lastof(path)); if (access(path, R_OK) == 0) return strdup(path); } return nullptr; } /** * Try to parse a 'defined(expr)' expression. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionDefined(Lexer *lexer, StringSet *defines, bool verbose); /** * Try to parse a 'expr || expr' expression. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionOr(Lexer *lexer, StringSet *defines, bool verbose); /** * Try to parse a '!expr' expression. Also parses the '(expr)', '0' and * identifiers. Finally it also consumes any unknown tokens. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionNot(Lexer *lexer, StringSet *defines, bool verbose) { if (lexer->GetToken() == TOKEN_NOT) { if (verbose) fprintf(stderr, "!"); lexer->Lex(); bool value = !ExpressionDefined(lexer, defines, verbose); if (verbose) fprintf(stderr, "[%d]", value); return value; } if (lexer->GetToken() == TOKEN_OPEN) { if (verbose) fprintf(stderr, "("); lexer->Lex(); bool value = ExpressionOr(lexer, defines, verbose); if (verbose) fprintf(stderr, ")[%d]", value); lexer->Lex(); return value; } if (lexer->GetToken() == TOKEN_ZERO) { if (verbose) fprintf(stderr, "0"); lexer->Lex(); if (verbose) fprintf(stderr, "[0]"); return false; } bool first = true; while (lexer->GetToken() == TOKEN_UNKNOWN || lexer->GetToken() == TOKEN_IDENTIFIER) { if (verbose && first) fprintf(stderr, ""); first = false; lexer->Lex(); } return true; } /** * Try to parse a 'defined(expr)' expression. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionDefined(Lexer *lexer, StringSet *defines, bool verbose) { bool value = ExpressionNot(lexer, defines, verbose); if (lexer->GetToken() != TOKEN_DEFINED) return value; lexer->Lex(); if (verbose) fprintf(stderr, "defined"); bool open = (lexer->GetToken() == TOKEN_OPEN); if (open) lexer->Lex(); if (verbose) fprintf(stderr, open ? "(" : " "); if (lexer->GetToken() == TOKEN_IDENTIFIER) { if (verbose) fprintf(stderr, "%s", lexer->GetString()); value = defines->find(lexer->GetString()) != defines->end(); } if (open) { if (verbose) fprintf(stderr, ")"); lexer->Lex(); } lexer->Lex(); if (verbose) fprintf(stderr, "[%d]", value); return value; } /** * Try to parse a 'expr && expr' expression. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionAnd(Lexer *lexer, StringSet *defines, bool verbose) { bool value = ExpressionDefined(lexer, defines, verbose); for (;;) { if (lexer->GetToken() != TOKEN_AND) return value; if (verbose) fprintf(stderr, " && "); lexer->Lex(); value = value && ExpressionDefined(lexer, defines, verbose); } } /** * Try to parse a 'expr || expr' expression. * @param lexer the lexer to get tokens from. * @param defines the set of known defines. * @param verbose whether to give verbose debugging information. * @return the value of the expression. */ bool ExpressionOr(Lexer *lexer, StringSet *defines, bool verbose) { bool value = ExpressionAnd(lexer, defines, verbose); for (;;) { if (lexer->GetToken() != TOKEN_OR) return value; if (verbose) fprintf(stderr, " || "); lexer->Lex(); value = value || ExpressionAnd(lexer, defines, verbose); } } /** Enumerator to tell how long to ignore 'stuff'. */ enum Ignore { NOT_IGNORE, ///< No ignoring. IGNORE_UNTIL_ELSE, ///< Ignore till a \c \#else is reached. IGNORE_UNTIL_ENDIF, ///< Ignore till a \c \#endif is reached. }; /** * Scan a file for includes, defines and the lot. * @param filename the name of the file to scan. * @param ext the extension of the filename. * @param header whether the file is a header or not. * @param verbose whether to give verbose debugging information. */ void ScanFile(const char *filename, const char *ext, bool header, bool verbose) { static StringSet defines; static std::stack ignore; /* Copy in the default defines (parameters of depend) */ if (!header) { for (StringSet::iterator it = _defines.begin(); it != _defines.end(); it++) { defines.insert(strdup(*it)); } } File file(filename); Lexer lexer(&file); /* Start the lexing! */ lexer.Lex(); while (lexer.GetToken() != TOKEN_END) { switch (lexer.GetToken()) { /* We reached the end of the file... yay, we're done! */ case TOKEN_END: break; /* The line started with a # (minus whitespace) */ case TOKEN_SHARP: lexer.Lex(); switch (lexer.GetToken()) { case TOKEN_INCLUDE: if (verbose) fprintf(stderr, "%s #include ", filename); lexer.Lex(); switch (lexer.GetToken()) { case TOKEN_LOCAL: case TOKEN_GLOBAL: { if (verbose) fprintf(stderr, "%s", lexer.GetString()); if (!ignore.empty() && ignore.top() != NOT_IGNORE) { if (verbose) fprintf(stderr, " (ignored)"); break; } const char *h = GeneratePath(file.GetDirname(), lexer.GetString(), lexer.GetToken() == TOKEN_LOCAL); if (h != nullptr) { StringMap::iterator it = _headers.find(h); if (it == _headers.end()) { it = (_headers.insert(StringMapItem(strdup(h), new StringSet()))).first; if (verbose) fprintf(stderr, "\n"); ScanFile(h, ext, true, verbose); } StringMap::iterator curfile; if (header) { curfile = _headers.find(filename); } else { /* Replace the extension with the provided extension of '.o'. */ char path[PATH_MAX]; strecpy(path, filename, lastof(path)); *(strrchr(path, '.')) = '\0'; strecat(path, ext != nullptr ? ext : ".o", lastof(path)); curfile = _files.find(path); if (curfile == _files.end()) { curfile = (_files.insert(StringMapItem(strdup(path), new StringSet()))).first; } } if (it != _headers.end()) { for (StringSet::iterator header = it->second->begin(); header != it->second->end(); header++) { if (curfile->second->find(*header) == curfile->second->end()) curfile->second->insert(strdup(*header)); } } if (curfile->second->find(h) == curfile->second->end()) curfile->second->insert(strdup(h)); free(h); } } /* FALL THROUGH */ default: break; } break; case TOKEN_DEFINE: if (verbose) fprintf(stderr, "%s #define ", filename); lexer.Lex(); if (lexer.GetToken() == TOKEN_IDENTIFIER) { if (verbose) fprintf(stderr, "%s", lexer.GetString()); if (!ignore.empty() && ignore.top() != NOT_IGNORE) { if (verbose) fprintf(stderr, " (ignored)"); break; } if (defines.find(lexer.GetString()) == defines.end()) defines.insert(strdup(lexer.GetString())); lexer.Lex(); } break; case TOKEN_UNDEF: if (verbose) fprintf(stderr, "%s #undef ", filename); lexer.Lex(); if (lexer.GetToken() == TOKEN_IDENTIFIER) { if (verbose) fprintf(stderr, "%s", lexer.GetString()); if (!ignore.empty() && ignore.top() != NOT_IGNORE) { if (verbose) fprintf(stderr, " (ignored)"); break; } StringSet::iterator it = defines.find(lexer.GetString()); if (it != defines.end()) { free(*it); defines.erase(it); } lexer.Lex(); } break; case TOKEN_ENDIF: if (verbose) fprintf(stderr, "%s #endif", filename); lexer.Lex(); if (!ignore.empty()) ignore.pop(); if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; case TOKEN_ELSE: { if (verbose) fprintf(stderr, "%s #else", filename); lexer.Lex(); Ignore last = ignore.empty() ? NOT_IGNORE : ignore.top(); if (!ignore.empty()) ignore.pop(); if (ignore.empty() || ignore.top() == NOT_IGNORE) { ignore.push(last == IGNORE_UNTIL_ELSE ? NOT_IGNORE : IGNORE_UNTIL_ENDIF); } else { ignore.push(IGNORE_UNTIL_ENDIF); } if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; } case TOKEN_ELIF: { if (verbose) fprintf(stderr, "%s #elif ", filename); lexer.Lex(); Ignore last = ignore.empty() ? NOT_IGNORE : ignore.top(); if (!ignore.empty()) ignore.pop(); if (ignore.empty() || ignore.top() == NOT_IGNORE) { bool value = ExpressionOr(&lexer, &defines, verbose); ignore.push(last == IGNORE_UNTIL_ELSE ? (value ? NOT_IGNORE : IGNORE_UNTIL_ELSE) : IGNORE_UNTIL_ENDIF); } else { ignore.push(IGNORE_UNTIL_ENDIF); } if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; } case TOKEN_IF: { if (verbose) fprintf(stderr, "%s #if ", filename); lexer.Lex(); if (ignore.empty() || ignore.top() == NOT_IGNORE) { bool value = ExpressionOr(&lexer, &defines, verbose); ignore.push(value ? NOT_IGNORE : IGNORE_UNTIL_ELSE); } else { ignore.push(IGNORE_UNTIL_ENDIF); } if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; } case TOKEN_IFDEF: if (verbose) fprintf(stderr, "%s #ifdef ", filename); lexer.Lex(); if (lexer.GetToken() == TOKEN_IDENTIFIER) { bool value = defines.find(lexer.GetString()) != defines.end(); if (verbose) fprintf(stderr, "%s[%d]", lexer.GetString(), value); if (ignore.empty() || ignore.top() == NOT_IGNORE) { ignore.push(value ? NOT_IGNORE : IGNORE_UNTIL_ELSE); } else { ignore.push(IGNORE_UNTIL_ENDIF); } } if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; case TOKEN_IFNDEF: if (verbose) fprintf(stderr, "%s #ifndef ", filename); lexer.Lex(); if (lexer.GetToken() == TOKEN_IDENTIFIER) { bool value = defines.find(lexer.GetString()) != defines.end(); if (verbose) fprintf(stderr, "%s[%d]", lexer.GetString(), value); if (ignore.empty() || ignore.top() == NOT_IGNORE) { ignore.push(!value ? NOT_IGNORE : IGNORE_UNTIL_ELSE); } else { ignore.push(IGNORE_UNTIL_ENDIF); } } if (verbose) fprintf(stderr, " -> %signore", (!ignore.empty() && ignore.top() != NOT_IGNORE) ? "" : "not "); break; default: if (verbose) fprintf(stderr, "%s #", filename); lexer.Lex(); break; } if (verbose) fprintf(stderr, "\n"); /* FALL THROUGH */ default: /* Ignore the rest of the garbage on this line */ while (lexer.GetToken() != TOKEN_EOL && lexer.GetToken() != TOKEN_END) lexer.Lex(); lexer.Lex(); break; } } if (!header) { for (StringSet::iterator it = defines.begin(); it != defines.end(); it++) { free(*it); } defines.clear(); while (!ignore.empty()) ignore.pop(); } } /** * Entry point. Arguably the most common function in all applications. * @param argc the number of arguments. * @param argv the actual arguments. * @return return value for the caller to tell we succeed or not. */ int main(int argc, char *argv[]) { bool ignorenext = true; char *filename = nullptr; char *ext = nullptr; char *delimiter = nullptr; bool append = false; bool verbose = false; for (int i = 0; i < argc; i++) { if (ignorenext) { ignorenext = false; continue; } if (argv[i][0] == '-') { /* Append */ if (strncmp(argv[i], "-a", 2) == 0) append = true; /* Include dir */ if (strncmp(argv[i], "-I", 2) == 0) { if (argv[i][2] == '\0') { i++; _include_dirs.insert(strdup(argv[i])); } else { _include_dirs.insert(strdup(&argv[i][2])); } continue; } /* Define */ if (strncmp(argv[i], "-D", 2) == 0) { char *p = strchr(argv[i], '='); if (p != nullptr) *p = '\0'; _defines.insert(strdup(&argv[i][2])); continue; } /* Output file */ if (strncmp(argv[i], "-f", 2) == 0) { if (filename != nullptr) continue; filename = strdup(&argv[i][2]); continue; } /* Object file extension */ if (strncmp(argv[i], "-o", 2) == 0) { if (ext != nullptr) continue; ext = strdup(&argv[i][2]); continue; } /* Starting string delimiter */ if (strncmp(argv[i], "-s", 2) == 0) { if (delimiter != nullptr) continue; delimiter = strdup(&argv[i][2]); continue; } /* Verbose */ if (strncmp(argv[i], "-v", 2) == 0) verbose = true; continue; } ScanFile(argv[i], ext, false, verbose); } /* Default output file is Makefile */ if (filename == nullptr) filename = strdup("Makefile"); /* Default delimiter string */ if (delimiter == nullptr) delimiter = strdup("# DO NOT DELETE"); char backup[PATH_MAX]; strecpy(backup, filename, lastof(backup)); strecat(backup, ".bak", lastof(backup)); char *content = nullptr; long size = 0; /* Read in the current file; so we can overwrite everything from the * end of non-depend data marker down till the end. */ FILE *src = fopen(filename, "rb"); if (src != nullptr) { fseek(src, 0, SEEK_END); if ((size = ftell(src)) < 0) { fprintf(stderr, "Could not read %s\n", filename); exit(-2); } rewind(src); content = (char*)malloc(size * sizeof(*content)); if (fread(content, 1, size, src) != (size_t)size) { fprintf(stderr, "Could not read %s\n", filename); exit(-2); } fclose(src); } FILE *dst = fopen(filename, "w"); bool found_delimiter = false; if (size != 0) { src = fopen(backup, "wb"); if (fwrite(content, 1, size, src) != (size_t)size) { fprintf(stderr, "Could not write %s\n", filename); exit(-2); } fclose(src); /* Then append it to the real file. */ src = fopen(backup, "r"); while (fgets(content, size, src) != nullptr) { fputs(content, dst); if (!strncmp(content, delimiter, strlen(delimiter))) found_delimiter = true; if (!append && found_delimiter) break; } fclose(src); } if (!found_delimiter) fprintf(dst, "\n%s\n", delimiter); for (StringMap::iterator it = _files.begin(); it != _files.end(); it++) { for (StringSet::iterator h = it->second->begin(); h != it->second->end(); h++) { fprintf(dst, "%s: %s\n", it->first, *h); } } /* Clean up our mess. */ fclose(dst); free(delimiter); free(filename); free(ext); free(content); for (StringMap::iterator it = _files.begin(); it != _files.end(); it++) { for (StringSet::iterator h = it->second->begin(); h != it->second->end(); h++) { free(*h); } it->second->clear(); delete it->second; free(it->first); } _files.clear(); for (StringMap::iterator it = _headers.begin(); it != _headers.end(); it++) { for (StringSet::iterator h = it->second->begin(); h != it->second->end(); h++) { free(*h); } it->second->clear(); delete it->second; free(it->first); } _headers.clear(); for (StringSet::iterator it = _defines.begin(); it != _defines.end(); it++) { free(*it); } _defines.clear(); for (StringSet::iterator it = _include_dirs.begin(); it != _include_dirs.end(); it++) { free(*it); } _include_dirs.clear(); return 0; }