//----------------------------------------------------------- // Copyright Christian Arnault LAL-Orsay CNRS // arnault@lal.in2p3.fr // See the complete license in cmt_license.txt "http://www.cecill.info". //----------------------------------------------------------- #include "cmt_deps_builder.h" #include "cmt_system.h" #include "cmt_use.h" #include "cmt_include.h" #include "cmt_symbol.h" #include "cmt_log.h" // // While parsing a C++ file, these are the possible usefull // states we can reach. // Each state correspond to a specific state action function. // enum state_def { at_start, // beginning of the file in_line, // along a line in_string, // inside a quoted string in_char, // inside a quoted char in_comment, // inside a multi-line comment in_string_comment, // inside a quoted string in a comment in_char_comment, // inside a quoted char in a comment in_line_comment // inside a single-line comment }; //-------------------------------------------------- static int locate (const cmt_string& name, const cmt_string& dir_name, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, cmt_string& full_name); //-------------------------------------------------- static void build_deps_text (char* text, const cmt_string& dir_name, int current_path_index, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, const DepsBuilder::HeaderFilters& header_filters, CmtSystem::cmt_string_vector& all_deps, CmtSystem::cmt_string_vector& deps); //-------------------------------------------------- static void header_file_action (const char* header_file, const cmt_string& dir_name, int current_path_index, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, const DepsBuilder::HeaderFilters& header_filters, CmtSystem::cmt_string_vector& all_deps, CmtSystem::cmt_string_vector& deps) { Log; for (int i = 0; i < all_deps.size (); i++) { const cmt_string& n = all_deps[i]; log << "CMT> check old header file name=" << n << " against " << header_file << log_endl; if (n == header_file) return; } all_deps.push_back (header_file); cmt_string header_file_path; int path_index = locate (header_file, dir_name, include_paths, substitutions, header_file_path); if (path_index < 0) return; static int header_filters_size = header_filters.size (); if (header_filters_size > 0) { // check the filter macros from // find all macros named _header_file_filter // for all those macros, get the regexp and check the matches against the header file name // if any match, select the stamp file instead of the header file // the stamp file might be either a conventional name // /cmt/cmt_all_headers.stamp // or given in a macro eg _header_stamp_file_name // for (int i = header_filters_size - 1; i >= 0; i--) { cmt_string stamp_file; if (header_filters[i].use_stamp (header_file_path, stamp_file)) { bool included = false; for (int i = deps.size () - 1; i >= 0; i--) { if (deps[i] == stamp_file) { included = true; break; } } if (!included) deps.push_back (stamp_file); return; } } } log << "CMT> parsing new header file name=" << header_file << log_endl; /////////////////////////////////////////////////////////////////////////////////////////////////////// cmt_string text; text.read (header_file_path); char* ptr = &text[0]; cmt_string new_dir; CmtSystem::dirname (header_file_path, new_dir); if (path_index == 0 || path_index == 1) build_deps_text (ptr, new_dir, current_path_index, include_paths, substitutions, header_filters, all_deps, deps); else build_deps_text (ptr, new_dir, path_index, include_paths, substitutions, header_filters, all_deps, deps); /////////////////////////////////////////////////////////////////////////////////////////////////////// //path_index >= 0 cmt_string full_name; if (path_index == 1) { full_name = dir_name; full_name += CmtSystem::file_separator (); if (current_path_index >= 2) { full_name.replace (include_paths[current_path_index - 2], substitutions[current_path_index - 2]); } } else if (path_index > 1) { full_name = substitutions[path_index - 2]; full_name += CmtSystem::file_separator (); } full_name += header_file; deps.push_back (full_name); } //-------------------------------------------------- static char* at_start_action (char* ptr, state_def& state, const cmt_string& dir_name, int current_path_index, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, const DepsBuilder::HeaderFilters& header_filters, CmtSystem::cmt_string_vector& all_deps, CmtSystem::cmt_string_vector& deps) { char term = 0; // To ignore leading spaces and tabs while ( (*ptr == ' ') || (*ptr == '\t')) { ptr++; } if (*ptr == '#') { ptr++; // skip spaces while ((*ptr == ' ') || (*ptr == '\t')) ptr++; if (!strncmp (ptr, "include", 7)) { // We have a #include statement ptr += 7; while ((*ptr == ' ') || (*ptr == '\t')) ptr++; if (*ptr == '<') { term = '>'; ptr++; } else if (*ptr == '"') { term = '"'; ptr++; } else { // empty #include statement?? state = in_line; ptr += strlen (ptr); return (ptr); } } else { // ignore other pre-processor statements state = in_line; ptr += strlen (ptr); return (ptr); } } else if (!strncmp (ptr, " include", 13)) { // fortran include statement ptr += 13; while ((*ptr == ' ') || (*ptr == '\t')) ptr++; if (*ptr == '\'') { term = '\''; ptr++; } else { state = in_line; return (ptr); } } else if (!strncmp (ptr, "\tinclude", 8)) { // fortran include statement ptr += 8; while ((*ptr == ' ') || (*ptr == '\t')) ptr++; if (*ptr == '\'') { term = '\''; ptr++; } else { state = in_line; return (ptr); } } else { state = in_line; return (ptr); } // At this point we do have to include a header file. char* end; end = strchr (ptr, term); if (end != 0) { *end = 0; } const char* header_file = ptr; header_file_action (header_file, dir_name, current_path_index, include_paths, substitutions, header_filters, all_deps, deps); if (end != 0) { *end = term; } state = in_line; ptr += strlen (ptr); return (ptr); } /** Any line with no header inclusion step through comments and strings */ static char* in_line_action (char* ptr, state_def& state) { char* pattern = ptr + strlen (ptr); int length = 0; /* search for the first occurrence of {single-quote double-quote open-comment open-line-comment} Must exclude escaped quotes \' and \" */ char* pos = strchr (ptr, '"'); if ((pos != 0) && (pos < pattern) && (pos > ptr) && (*(pos-1) != '\\')) { state = in_string; pattern = pos; length = 1; } pos = strchr (ptr, '\''); if ((pos != 0) && (pos < pattern) && (pos > ptr) && (*(pos-1) != '\\')) { state = in_char; pattern = pos; length = 1; } pos = strstr (ptr, "/*"); //*/ if ((pos != 0) && (pos < pattern)) { state = in_comment; pattern = pos; length = 2; } pos = strstr (ptr, "//"); if ((pos != 0) && (pos < pattern)) { state = in_line_comment; pattern = pos; length = 2; } ptr = pattern + length; return (ptr); } //-------------------------------------------------- static char* in_string_action (char* ptr, state_def& state) { // we exclusively look for a double quote char* pos = strchr (ptr, '"'); if (pos == 0) { // This string is not finished till the end of the line.. // we expect it to continue to the next line... // thus we leave the state as it is ptr += strlen (ptr); } else { if ((pos > ptr) && (*(pos - 1) == '\\')) { ptr = pos + 1; } else { ptr = pos + 1; state = in_line; } } return (ptr); } //-------------------------------------------------- static char* in_char_action (char* ptr, state_def& state) { // we exclusively look for a single quote char* pos = strchr (ptr, '\''); if (pos == 0) { // This string is not finished till the end of the line.. // we expect it continues to the nex line... // thus we leave the state as it is ptr += strlen (ptr); } else { if ((pos > ptr) && (*(pos - 1) == '\\')) { ptr = pos + 1; } else { ptr = pos + 1; state = in_line; } } return (ptr); } //-------------------------------------------------- static char* in_comment_action (char* ptr, state_def& state) { char* pattern = ptr + strlen (ptr); int length = 0; char* pos; /* Even if we are inside a comment, we must detect strings since comment markers may be written inside them. pos = strchr (ptr, '"'); if ((pos != 0) && (pos < pattern) && (pos > ptr) && (*(pos-1) != '\\')) { state = in_string_comment; pattern = pos; length = 1; } pos = strchr (ptr, '\''); if ((pos != 0) && (pos < pattern) && (pos > ptr) && (*(pos-1) != '\\')) { state = in_char_comment; pattern = pos; length = 1; } */ pos = strstr (ptr, "*/"); if ((pos != 0) && (pos < pattern)) { state = in_line; pattern = pos; length = 2; } ptr = pattern + length; return (ptr); } //-------------------------------------------------- static char* in_string_comment_action (char* ptr, state_def& state) { char* pos = strchr (ptr, '"'); if (pos == 0) { // This string is not finished till the end of the line.. // we expect it continues to the nex line... ptr += strlen (ptr); } else { if ((pos > ptr) && (*(pos - 1) == '\\')) { ptr = pos + 1; } else { ptr = pos + 1; state = in_comment; } } return (ptr); } //-------------------------------------------------- static char* in_char_comment_action (char* ptr, state_def& state) { char* pos = strchr (ptr, '\''); if (pos == 0) { // This string is not finished till the end of the line.. // we expect it continues to the nex line... ptr += strlen (ptr); } else { if ((pos > ptr) && (*(pos - 1) == '\\')) { ptr = pos + 1; } else { ptr = pos + 1; state = in_comment; } pos--; } return (ptr); } //-------------------------------------------------- static char* in_line_comment_action (char* ptr, state_def& state) { char * pos = strchr (ptr, '\\'); /* Extend this part to deal with continuation character */ if ( (pos == NULL) || ( (ptr + strlen(ptr)-1)!=pos )) { state = in_line; } ptr += strlen (ptr); return (ptr); } //-------------------------------------------------- static void build_deps_text (char* text, const cmt_string& dir_name, int current_path_index, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, const DepsBuilder::HeaderFilters& header_filters, CmtSystem::cmt_string_vector& all_deps, CmtSystem::cmt_string_vector& deps) { Log; int pos; int max_pos; int line_number = 1; log << "CMT> build_deps_text dir_name=" << dir_name << log_endl; // erase of continuation character pos = 0; max_pos = strlen (text); char* current = text; char* last = text + max_pos; while (current < last) { // bscrnl -> BackSlash Carriage Return New Line // bsnl -> BackSlash New Line char* bscrnl = strstr (current, "\\\r\n"); char* bsnl = strstr (current, "\\\n"); if ( (bscrnl==0) && (bsnl ==0)) break; int length = 0; char * ptr = 0; if (bsnl==0) //bscrnl > 0 { length = 3; ptr = bscrnl; } else if (bscrnl==0) //bsnl > 0 { length = 2; ptr = bsnl; } else if (bscrnl < bsnl) { length = 3; ptr = bscrnl; } else // (bscrnl > bsnl) { length = 2; ptr = bsnl; } strcpy (ptr, ptr+length); current = ptr; last -= length; } pos = 0; max_pos = strlen (text); current = text; last = text + max_pos; state_def state = at_start; while (current < last) { char marker; char* marker_pos = 0; // crnl -> Carriage Return New Line // nl -> New Line char* crnl = strstr (current, "\r\n"); char* nl = strchr (current, '\n'); char* first = nl; int length = 1; char* ptr = 0; if (crnl != 0) { // cr+nl has been found if (nl == 0) { // cr but no nl ?? first = crnl; length = 2; } else { // both cr+nl and nl found first = (nl < crnl) ? nl : crnl; length = (nl < crnl) ? 1 : 2; } } else { // no cr+nl but nl alone found first = nl; length = 1; } ptr = current; if (first == 0) { // neither nl nor cr+nl found => this is the last line marker_pos = 0; } else { marker_pos = first; marker = *marker_pos; *marker_pos = 0; } log << "CMT> build_deps_text2 line=[" << current << "]" << log_endl; while (strlen (ptr) > 0) { switch (state) { case at_start: ptr = at_start_action (ptr, state, dir_name, current_path_index, include_paths, substitutions, header_filters, all_deps, deps); break; case in_line: ptr = in_line_action (ptr, state); break; case in_string: ptr = in_string_action (ptr, state); break; case in_char: ptr = in_char_action (ptr, state); break; case in_comment: ptr = in_comment_action (ptr, state); break; case in_string_comment: ptr = in_string_comment_action (ptr, state); break; case in_char_comment: ptr = in_char_comment_action (ptr, state); break; case in_line_comment: ptr = in_line_comment_action (ptr, state); break; } } if (state == in_line) state = at_start; line_number++; if (marker_pos != 0) { *marker_pos = marker; current = marker_pos + length; } else { break; } } } //-------------------------------------------------- static int build_deps (const cmt_string& name, const cmt_string& dir_name, int current_path_index, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, const DepsBuilder::HeaderFilters& header_filters, CmtSystem::cmt_string_vector& all_deps, CmtSystem::cmt_string_vector& deps) { Log; log << "CMT> build_deps name=" << name << " dir_name=" << dir_name << log_endl; cmt_string full_name; int path_index = locate (name, dir_name, include_paths, substitutions, full_name); if (path_index >= 0) { cmt_string text; text.read (full_name); char* ptr = &text[0]; cmt_string new_dir; CmtSystem::dirname (full_name, new_dir); if (path_index == 0 || path_index == 1) build_deps_text (ptr, new_dir, current_path_index, include_paths, substitutions, header_filters, all_deps, deps); else build_deps_text (ptr, new_dir, path_index, include_paths, substitutions, header_filters, all_deps, deps); } if (path_index < 0) { log << "CMT> build_deps3" << log_endl; } return path_index; } //-------------------------------------------------- static int locate (const cmt_string& name, const cmt_string& dir_name, const CmtSystem::cmt_string_vector& include_paths, const CmtSystem::cmt_string_vector& substitutions, cmt_string& full_name) { Log; log << "CMT> locate name=" << name << " dir_name=" << dir_name << log_endl; // // Return 0 when the file is found in the current directory // if (CmtSystem::test_file (name)) { full_name = name; return (0); } cmt_string p; // // Return 1 when the file is found in the directory of the // upper level source file // p = dir_name; p += CmtSystem::file_separator (); p += name; if (CmtSystem::test_file (p)) { full_name = p; return (1); } // // Return [path_index + 2] when the include file is found at one of // the include_paths // for (int path_index = 0; path_index < include_paths.size (); path_index++) { p = include_paths[path_index]; p += CmtSystem::file_separator (); p += name; log << "CMT> locate2 full_name=" << p << log_endl; if (CmtSystem::test_file (p)) { full_name = p; return (path_index + 2); } } log << "CMT> locate3" << log_endl; return (-1); } //-------------------------------------------------------------------------- void DepsBuilder::clear () { m_include_paths.clear (); m_substitutions.clear (); for (int i = m_header_filters.size () - 1; i >= 0; i--) { m_header_filters[i].clear (); } m_header_filters.clear (); } //-------------------------------------------------------------------------- void DepsBuilder::add (const cmt_string& path, const cmt_string& substitution) { if (path.size () == 0) { return; } if (path[path.size () - 1] == CmtSystem::file_separator ()) { cmt_string p = path; p.erase (path.size () - 1); m_include_paths.push_back (p); } else { m_include_paths.push_back (path); } m_substitutions.push_back (substitution); } //-------------------------------------------------------------------------- void DepsBuilder::add_includes (const Use& use) { Log; const Include::IncludeVector& includes = use.includes; int include_number; for (include_number = 0; include_number < includes.size (); include_number++) { const Include& include = includes[include_number]; cmt_string temp = include.name; cmt_string pattern; cmt_string name; char end_pattern; int start = 0; for (;;) { int begin; begin = temp.find (start, "${"); if (begin != cmt_string::npos) { end_pattern = '}'; } else { begin = temp.find (start, "$("); if (begin != cmt_string::npos) { end_pattern = ')'; } else { break; } } start = begin + 2; int end; end = temp.find (start, end_pattern); if (end == cmt_string::npos) break; if (end < begin) break; start = end + 1; temp.substr (begin, end - begin + 1, pattern); temp.substr (begin + 2, end - begin - 2, name); Symbol* macro = Symbol::find (name); if (macro != 0) { cmt_string value = macro->resolve_macro_value (); value += CmtSystem::file_separator (); temp.replace_all (pattern, value); } else { cmt_string value = CmtSystem::getenv (name); value += CmtSystem::file_separator (); temp.replace_all (pattern, value); } } log << "include = " << temp << log_endl; add (temp, include.name); } } //-------------------------------------------------------------------------- CmtSystem::cmt_string_vector& DepsBuilder::run (const cmt_string& file_name) { Log; log << "Starting deps builder on " << file_name << log_endl; m_deps.clear (); m_all_deps.clear (); cmt_string preprocessor; Symbol* macro = Symbol::find ("preprocessor_command"); if (macro != 0) { preprocessor = macro->resolve_macro_value (); } if (preprocessor == "") { // // Since no preprocessor command is defined, // we use the internal mechanism provided here. // cmt_string new_dir; CmtSystem::dirname (file_name, new_dir); build_deps (file_name, new_dir, 0, m_include_paths, m_substitutions, m_header_filters, m_all_deps, m_deps); } else { // // An external preprocessor command is defined. We expect it // to follow a "standard" syntax for its output, ie: // o It starts with: // .o: ... // o There may be many lines with trailing back-slashes // o All entries are space-separated // o One of the entries is the source file name itself // // The preprocessor command expects the list of -I options // (resolved from the "includes" macro) and the list of // -D/-U options (resolved from the "*_pp_*flags" macros) // // // Building the complete command (still the pp_*flags are // missing) // preprocessor += " "; macro = Symbol::find ("includes"); preprocessor += macro->resolve_macro_value (); preprocessor += " "; preprocessor += file_name; cmt_string output; CmtSystem::execute (preprocessor, output); // // Make the output as one single big line. // output.replace_all ("\n", " "); output.replace_all ("\\ ", " "); CmtSystem::cmt_string_vector files; CmtSystem::split (output, " \t", files); // // Analyze each entry // for (int i = 1; i < files.size (); i++) { const cmt_string& file = files[i]; if (file == file_name) continue; cmt_string dir; cmt_string name; cmt_string full_name; CmtSystem::dirname (file, dir); // // Only declared include_paths will be taken into account // Others are considered as system include paths. // for (int j = 0; j < m_include_paths.size (); j++) { const cmt_string& p = m_include_paths[j]; if (dir == p) { CmtSystem::basename (file, name); full_name = m_substitutions[j]; full_name += name; // // We add in the "m_deps" list the symbolic form // of the path rather that the expanded one. // m_deps.push_back (full_name); break; } } } } return (m_deps); } //-------------------------------------------------------------------------- void DepsBuilder::add_header_filter (const Use* use, const cmt_regexp* filter, const cmt_string& stamp) { add_header_filter (HeaderFilter (use, filter, stamp)); } //-------------------------------------------------------------------------- void DepsBuilder::add_header_filter (const HeaderFilter& hf) { m_header_filters.push_back (hf); }