| 1 | //  process jam regression test output into XML  -----------------------------// | 
|---|
| 2 |  | 
|---|
| 3 | //  Copyright Beman Dawes 2002.  Distributed under the Boost | 
|---|
| 4 | //  Software License, Version 1.0. (See accompanying file | 
|---|
| 5 | //  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | 
|---|
| 6 |  | 
|---|
| 7 | //  See http://www.boost.org/tools/regression for documentation. | 
|---|
| 8 |  | 
|---|
| 9 | #include "detail/tiny_xml.hpp" | 
|---|
| 10 | #include "boost/filesystem/operations.hpp" | 
|---|
| 11 | #include "boost/filesystem/fstream.hpp" | 
|---|
| 12 | #include "boost/filesystem/exception.hpp" | 
|---|
| 13 | #include "boost/filesystem/convenience.hpp" | 
|---|
| 14 |  | 
|---|
| 15 | #include <iostream> | 
|---|
| 16 | #include <string> | 
|---|
| 17 | #include <cstring> | 
|---|
| 18 | #include <map> | 
|---|
| 19 | #include <utility> // for make_pair | 
|---|
| 20 | #include <ctime> | 
|---|
| 21 | #include <cctype>   // for tolower | 
|---|
| 22 |  | 
|---|
| 23 | using std::string; | 
|---|
| 24 | namespace xml = boost::tiny_xml; | 
|---|
| 25 | namespace fs = boost::filesystem; | 
|---|
| 26 |  | 
|---|
| 27 | #define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE | 
|---|
| 28 | #include <boost/test/included/prg_exec_monitor.hpp> | 
|---|
| 29 |  | 
|---|
| 30 | // options  | 
|---|
| 31 |  | 
|---|
| 32 | static bool echo = false; | 
|---|
| 33 | static bool create_dirs = false; | 
|---|
| 34 | static bool boost_build_v2 = false; | 
|---|
| 35 |  | 
|---|
| 36 | namespace | 
|---|
| 37 | { | 
|---|
| 38 |   struct test_info | 
|---|
| 39 |   { | 
|---|
| 40 |     string      file_path; // relative boost-root | 
|---|
| 41 |     string      type; | 
|---|
| 42 |     bool        always_show_run_output; | 
|---|
| 43 |   }; | 
|---|
| 44 |   typedef std::map< string, test_info > test2info_map;  // key is test-name | 
|---|
| 45 |   test2info_map test2info; | 
|---|
| 46 |  | 
|---|
| 47 |   fs::path boost_root; | 
|---|
| 48 |   fs::path locate_root; // ALL_LOCATE_TARGET (or boost_root if none) | 
|---|
| 49 |  | 
|---|
| 50 | //  append_html  -------------------------------------------------------------// | 
|---|
| 51 |  | 
|---|
| 52 |   void append_html( const string & src, string & target ) | 
|---|
| 53 |   { | 
|---|
| 54 |     // there are a few lines we want to ignore | 
|---|
| 55 |     if ( src.find( "th target..." ) != string::npos | 
|---|
| 56 |       || src.find( "cc1plus.exe: warning: changing search order for system directory" ) != string::npos | 
|---|
| 57 |       || src.find( "cc1plus.exe: warning:   as it has already been specified as a non-system directory" ) != string::npos | 
|---|
| 58 |       ) return; | 
|---|
| 59 |  | 
|---|
| 60 |     // on some platforms (e.g. tru64cxx) the following line is a real performance boost | 
|---|
| 61 |     target.reserve(src.size() * 2 + target.size()); | 
|---|
| 62 |  | 
|---|
| 63 |     for ( string::size_type pos = 0; pos < src.size(); ++pos ) | 
|---|
| 64 |     { | 
|---|
| 65 |       if ( src[pos] == '<' ) target += "<"; | 
|---|
| 66 |       else if ( src[pos] == '>' ) target += ">"; | 
|---|
| 67 |       else if ( src[pos] == '&' ) target += "&"; | 
|---|
| 68 |       else target += src[pos]; | 
|---|
| 69 |     } | 
|---|
| 70 |   } | 
|---|
| 71 |  | 
|---|
| 72 |  //  timestamp  ---------------------------------------------------------------// | 
|---|
| 73 |  | 
|---|
| 74 |   string timestamp() | 
|---|
| 75 |   { | 
|---|
| 76 |     char run_date[128]; | 
|---|
| 77 |     std::time_t tod; | 
|---|
| 78 |     std::time( &tod ); | 
|---|
| 79 |     std::strftime( run_date, sizeof(run_date), | 
|---|
| 80 |       "%Y-%m-%d %X UTC", std::gmtime( &tod ) ); | 
|---|
| 81 |     return string( run_date ); | 
|---|
| 82 |   } | 
|---|
| 83 |  | 
|---|
| 84 | //  convert path separators to forward slashes  ------------------------------// | 
|---|
| 85 |  | 
|---|
| 86 |   void convert_path_separators( string & s ) | 
|---|
| 87 |   { | 
|---|
| 88 |     for ( string::iterator itr = s.begin(); itr != s.end(); ++itr ) | 
|---|
| 89 |       if ( *itr == '\\' || *itr == '!' ) *itr = '/'; | 
|---|
| 90 |   } | 
|---|
| 91 |  | 
|---|
| 92 | //  extract a target directory path from a jam target string  ----------------// | 
|---|
| 93 | //  s may be relative to the initial_path: | 
|---|
| 94 | //    ..\..\..\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj | 
|---|
| 95 | //  s may be absolute: | 
|---|
| 96 | //    d:\myboost\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj | 
|---|
| 97 | //  return path is always relative to the boost directory tree: | 
|---|
| 98 | //    libs/foo/build/bin/libfs.lib/vc7/debug/runtime-link-dynamic | 
|---|
| 99 |  | 
|---|
| 100 |   string target_directory( const string & s ) | 
|---|
| 101 |   { | 
|---|
| 102 |     string temp( s ); | 
|---|
| 103 |     convert_path_separators( temp ); | 
|---|
| 104 |     temp.erase( temp.find_last_of( "/" ) ); // remove leaf | 
|---|
| 105 |     string::size_type pos = temp.find_last_of( " " ); // remove leading spaces | 
|---|
| 106 |     if ( pos != string::npos ) temp.erase( 0, pos+1 ); | 
|---|
| 107 |     if ( temp[0] == '.' ) temp.erase( 0, temp.find_first_not_of( "./" ) );  | 
|---|
| 108 |     else temp.erase( 0, locate_root.string().size()+1 ); | 
|---|
| 109 | //std::cout << "\"" << s << "\", \"" << temp << "\"" << std::endl; | 
|---|
| 110 |     return temp; | 
|---|
| 111 |   } | 
|---|
| 112 |  | 
|---|
| 113 |   string::size_type target_name_end( const string & s ) | 
|---|
| 114 |   { | 
|---|
| 115 |     string::size_type pos = s.find( ".test/" ); | 
|---|
| 116 |     if ( pos == string::npos ) pos = s.find( ".dll/" ); | 
|---|
| 117 |     if ( pos == string::npos ) pos = s.find( ".so/" ); | 
|---|
| 118 |     if ( pos == string::npos ) pos = s.find( ".lib/" ); | 
|---|
| 119 |     if ( pos == string::npos ) pos = s.find( ".pyd/" ); | 
|---|
| 120 |     if ( pos == string::npos ) pos = s.find( ".a/" ); | 
|---|
| 121 |     return pos; | 
|---|
| 122 |   } | 
|---|
| 123 |  | 
|---|
| 124 |   string toolset( const string & s ) | 
|---|
| 125 |   { | 
|---|
| 126 |     string::size_type pos = target_name_end( s ); | 
|---|
| 127 |     if ( pos == string::npos ) return ""; | 
|---|
| 128 |     pos = s.find( "/", pos ) + 1; | 
|---|
| 129 |     return s.substr( pos, s.find( "/", pos ) - pos ); | 
|---|
| 130 |   } | 
|---|
| 131 |  | 
|---|
| 132 |   string test_name( const string & s ) | 
|---|
| 133 |   { | 
|---|
| 134 |     string::size_type pos = target_name_end( s ); | 
|---|
| 135 |     if ( pos == string::npos ) return ""; | 
|---|
| 136 |     string::size_type pos_start = s.rfind( '/', pos ) + 1; | 
|---|
| 137 |     return s.substr( pos_start, | 
|---|
| 138 |       (s.find( ".test/" ) != string::npos | 
|---|
| 139 |         ? pos : s.find( "/", pos )) - pos_start ); | 
|---|
| 140 |   } | 
|---|
| 141 |  | 
|---|
| 142 |   // Take a path to a target directory of test, and | 
|---|
| 143 |   // returns library name corresponding to that path. | 
|---|
| 144 |   string test_path_to_library_name( string const& path ) | 
|---|
| 145 |   { | 
|---|
| 146 |     std::string result; | 
|---|
| 147 |     string::size_type start_pos( path.find( "libs/" ) ); | 
|---|
| 148 |     if ( start_pos != string::npos ) | 
|---|
| 149 |     { | 
|---|
| 150 |       // The path format is ...libs/functional/hash/test/something.test/....       | 
|---|
| 151 |       // So, the part between "libs" and "test/something.test" can be considered | 
|---|
| 152 |       // as library name. But, for some libraries tests are located too deep, | 
|---|
| 153 |       // say numeric/ublas/test/test1 directory, and some libraries have tests | 
|---|
| 154 |       // in several subdirectories (regex/example and regex/test). So, nested | 
|---|
| 155 |       // directory may belong to several libraries. | 
|---|
| 156 |  | 
|---|
| 157 |       // To disambituate, it's possible to place a 'sublibs' file in | 
|---|
| 158 |       // a directory. It means that child directories are separate libraries. | 
|---|
| 159 |       // It's still possible to have tests in the directory that has 'sublibs' | 
|---|
| 160 |       // file. | 
|---|
| 161 |  | 
|---|
| 162 |       std::string interesting; | 
|---|
| 163 |       start_pos += 5; | 
|---|
| 164 |       string::size_type end_pos( path.find( ".test/", start_pos ) ); | 
|---|
| 165 |       end_pos = path.rfind('/', end_pos); | 
|---|
| 166 |       if (path.substr(end_pos - 5, 5) == "/test") | 
|---|
| 167 |         interesting = path.substr( start_pos, end_pos - 5 - start_pos ); | 
|---|
| 168 |       else | 
|---|
| 169 |         interesting = path.substr( start_pos, end_pos - start_pos ); | 
|---|
| 170 |  | 
|---|
| 171 |       // Take slash separate elements until we have corresponding 'sublibs'. | 
|---|
| 172 |       end_pos = 0; | 
|---|
| 173 |       for(;;) | 
|---|
| 174 |       { | 
|---|
| 175 |         end_pos = interesting.find('/', end_pos); | 
|---|
| 176 |         if (end_pos == string::npos) { | 
|---|
| 177 |           result = interesting; | 
|---|
| 178 |           break; | 
|---|
| 179 |         } | 
|---|
| 180 |         result = interesting.substr(0, end_pos); | 
|---|
| 181 |  | 
|---|
| 182 |         if ( fs::exists( ( boost_root / "libs" ) / result / "sublibs" ) ) | 
|---|
| 183 |         { | 
|---|
| 184 |           end_pos = end_pos + 1; | 
|---|
| 185 |         } | 
|---|
| 186 |         else | 
|---|
| 187 |           break; | 
|---|
| 188 |       } | 
|---|
| 189 |     } | 
|---|
| 190 |  | 
|---|
| 191 |     return result; | 
|---|
| 192 |   } | 
|---|
| 193 |  | 
|---|
| 194 |   // Tries to find target name in the string 'msg', starting from  | 
|---|
| 195 |   // position start. | 
|---|
| 196 |   // If found, extract the directory name from the target name and | 
|---|
| 197 |   // stores it in 'dir', and return the position after the target name. | 
|---|
| 198 |   // Otherwise, returns string::npos. | 
|---|
| 199 |   string::size_type parse_skipped_msg_aux(const string& msg, | 
|---|
| 200 |                                           string::size_type start, | 
|---|
| 201 |                                           string& dir) | 
|---|
| 202 |   { | 
|---|
| 203 |     dir.clear(); | 
|---|
| 204 |     string::size_type start_pos = msg.find( '<', start ); | 
|---|
| 205 |     if ( start_pos == string::npos ) return string::npos; | 
|---|
| 206 |     ++start_pos; | 
|---|
| 207 |     string::size_type end_pos = msg.find( '>', start_pos ); | 
|---|
| 208 |     dir += msg.substr( start_pos, end_pos - start_pos ); | 
|---|
| 209 |     if ( boost_build_v2 ) | 
|---|
| 210 |     { | 
|---|
| 211 |         // The first letter is a magic value indicating | 
|---|
| 212 |         // the type of grist. | 
|---|
| 213 |         convert_path_separators( dir ); | 
|---|
| 214 |         dir.erase( 0, 1 ); | 
|---|
| 215 |         // We need path from root, not from 'status' dir. | 
|---|
| 216 |         if (dir.find("../") == 0) | 
|---|
| 217 |           dir.erase(0,3); | 
|---|
| 218 |     } | 
|---|
| 219 |     else | 
|---|
| 220 |     { | 
|---|
| 221 |       if ( dir[0] == '@' ) | 
|---|
| 222 |       { | 
|---|
| 223 |         // new style build path, rooted build tree | 
|---|
| 224 |         convert_path_separators( dir ); | 
|---|
| 225 |         dir.replace( 0, 1, "bin/" ); | 
|---|
| 226 |       } | 
|---|
| 227 |       else | 
|---|
| 228 |       { | 
|---|
| 229 |         // old style build path, integrated build tree | 
|---|
| 230 |         start_pos = dir.rfind( '!' ); | 
|---|
| 231 |         convert_path_separators( dir ); | 
|---|
| 232 |         dir.insert( dir.find( '/', start_pos + 1), "/bin" ); | 
|---|
| 233 |       } | 
|---|
| 234 |     } | 
|---|
| 235 |     return end_pos; | 
|---|
| 236 |   } | 
|---|
| 237 |    | 
|---|
| 238 |   // the format of paths is really kinky, so convert to normal form | 
|---|
| 239 |   //   first path is missing the leading "..\". | 
|---|
| 240 |   //   first path is missing "\bin" after "status". | 
|---|
| 241 |   //   second path is missing the leading "..\". | 
|---|
| 242 |   //   second path is missing "\bin" after "build". | 
|---|
| 243 |   //   second path uses "!" for some separators. | 
|---|
| 244 |   void parse_skipped_msg( const string & msg, | 
|---|
| 245 |     string & first_dir, string & second_dir ) | 
|---|
| 246 |   { | 
|---|
| 247 |     string::size_type pos = parse_skipped_msg_aux(msg, 0, first_dir); | 
|---|
| 248 |     if (pos == string::npos) | 
|---|
| 249 |       return; | 
|---|
| 250 |     parse_skipped_msg_aux(msg, pos, second_dir); | 
|---|
| 251 |   } | 
|---|
| 252 |  | 
|---|
| 253 | //  test_log hides database details  -----------------------------------------// | 
|---|
| 254 |  | 
|---|
| 255 |   class test_log | 
|---|
| 256 |     : boost::noncopyable | 
|---|
| 257 |   { | 
|---|
| 258 |     const string & m_target_directory; | 
|---|
| 259 |     xml::element_ptr m_root; | 
|---|
| 260 |   public: | 
|---|
| 261 |     test_log( const string & target_directory, | 
|---|
| 262 |               const string & test_name, | 
|---|
| 263 |               const string & toolset, | 
|---|
| 264 |               bool force_new_file ) | 
|---|
| 265 |       : m_target_directory( target_directory ) | 
|---|
| 266 |     { | 
|---|
| 267 |       if ( !force_new_file ) | 
|---|
| 268 |       { | 
|---|
| 269 |         fs::path pth( locate_root / target_directory / "test_log.xml" ); | 
|---|
| 270 |         fs::ifstream file( pth  ); | 
|---|
| 271 |         if ( file )   // existing file | 
|---|
| 272 |         { | 
|---|
| 273 |           try | 
|---|
| 274 |           { | 
|---|
| 275 |             m_root = xml::parse( file, pth.string() ); | 
|---|
| 276 |             return; | 
|---|
| 277 |           } | 
|---|
| 278 |           catch(...) | 
|---|
| 279 |           { | 
|---|
| 280 |             // unable to parse existing XML file, fall through | 
|---|
| 281 |           } | 
|---|
| 282 |         } | 
|---|
| 283 |       } | 
|---|
| 284 |  | 
|---|
| 285 |       string library_name( test_path_to_library_name( target_directory ) ); | 
|---|
| 286 |  | 
|---|
| 287 |       test_info info; | 
|---|
| 288 |       test2info_map::iterator itr( test2info.find( library_name + "/" + test_name ) ); | 
|---|
| 289 |       if ( itr != test2info.end() ) | 
|---|
| 290 |         info = itr->second; | 
|---|
| 291 |        | 
|---|
| 292 |       if ( !info.file_path.empty() ) | 
|---|
| 293 |         library_name = test_path_to_library_name( info.file_path ); | 
|---|
| 294 |        | 
|---|
| 295 |       if ( info.type.empty() ) | 
|---|
| 296 |       { | 
|---|
| 297 |         if ( target_directory.find( ".lib/" ) != string::npos | 
|---|
| 298 |           || target_directory.find( ".dll/" ) != string::npos  | 
|---|
| 299 |           || target_directory.find( ".so/" ) != string::npos  | 
|---|
| 300 |           || target_directory.find( ".dylib/" ) != string::npos  | 
|---|
| 301 |           ) | 
|---|
| 302 |         { | 
|---|
| 303 |           info.type = "lib"; | 
|---|
| 304 |         } | 
|---|
| 305 |         else if ( target_directory.find( ".pyd/" ) != string::npos ) | 
|---|
| 306 |           info.type = "pyd"; | 
|---|
| 307 |       } | 
|---|
| 308 |    | 
|---|
| 309 |       m_root.reset( new xml::element( "test-log" ) ); | 
|---|
| 310 |       m_root->attributes.push_back( | 
|---|
| 311 |         xml::attribute( "library", library_name ) ); | 
|---|
| 312 |       m_root->attributes.push_back( | 
|---|
| 313 |         xml::attribute( "test-name", test_name ) ); | 
|---|
| 314 |       m_root->attributes.push_back( | 
|---|
| 315 |         xml::attribute( "test-type", info.type ) ); | 
|---|
| 316 |       m_root->attributes.push_back( | 
|---|
| 317 |         xml::attribute( "test-program", info.file_path ) ); | 
|---|
| 318 |       m_root->attributes.push_back( | 
|---|
| 319 |         xml::attribute( "target-directory", target_directory ) ); | 
|---|
| 320 |       m_root->attributes.push_back( | 
|---|
| 321 |         xml::attribute( "toolset", toolset ) ); | 
|---|
| 322 |       m_root->attributes.push_back( | 
|---|
| 323 |         xml::attribute( "show-run-output", | 
|---|
| 324 |           info.always_show_run_output ? "true" : "false" ) ); | 
|---|
| 325 |     } | 
|---|
| 326 |  | 
|---|
| 327 |     ~test_log() | 
|---|
| 328 |     { | 
|---|
| 329 |       fs::path pth( locate_root / m_target_directory / "test_log.xml" ); | 
|---|
| 330 |       if ( create_dirs && !fs::exists( pth.branch_path() ) ) | 
|---|
| 331 |           fs::create_directories( pth.branch_path() ); | 
|---|
| 332 |       fs::ofstream file( pth ); | 
|---|
| 333 |       if ( !file ) | 
|---|
| 334 |       { | 
|---|
| 335 |         std::cout << "*****Warning - can't open output file: " | 
|---|
| 336 |           << pth.string() << "\n"; | 
|---|
| 337 |       } | 
|---|
| 338 |       else xml::write( *m_root, file ); | 
|---|
| 339 |     } | 
|---|
| 340 |  | 
|---|
| 341 |     const string & target_directory() const { return m_target_directory; } | 
|---|
| 342 |  | 
|---|
| 343 |     void remove_action( const string & action_name ) | 
|---|
| 344 |     // no effect if action_name not found | 
|---|
| 345 |     { | 
|---|
| 346 |       xml::element_list::iterator itr; | 
|---|
| 347 |       for ( itr = m_root->elements.begin(); | 
|---|
| 348 |             itr != m_root->elements.end() && (*itr)->name != action_name; | 
|---|
| 349 |             ++itr ) {} | 
|---|
| 350 |       if ( itr != m_root->elements.end() ) m_root->elements.erase( itr ); | 
|---|
| 351 |     } | 
|---|
| 352 |  | 
|---|
| 353 |     void add_action( const string & action_name, | 
|---|
| 354 |                      const string & result, | 
|---|
| 355 |                      const string & timestamp, | 
|---|
| 356 |                      const string & content ) | 
|---|
| 357 |     { | 
|---|
| 358 |       remove_action( action_name ); | 
|---|
| 359 |       xml::element_ptr action( new xml::element(action_name) ); | 
|---|
| 360 |       m_root->elements.push_back( action ); | 
|---|
| 361 |       action->attributes.push_back( xml::attribute( "result", result ) ); | 
|---|
| 362 |       action->attributes.push_back( xml::attribute( "timestamp", timestamp ) ); | 
|---|
| 363 |       action->content = content; | 
|---|
| 364 |     } | 
|---|
| 365 |   }; | 
|---|
| 366 |  | 
|---|
| 367 | //  message_manager maps input messages into test_log actions  ---------------// | 
|---|
| 368 |  | 
|---|
| 369 |   class message_manager | 
|---|
| 370 |     : boost::noncopyable | 
|---|
| 371 |   { | 
|---|
| 372 |     string  m_action_name;  // !empty() implies action pending | 
|---|
| 373 |                             // IOW, a start_message awaits stop_message | 
|---|
| 374 |     string  m_target_directory; | 
|---|
| 375 |     string  m_test_name; | 
|---|
| 376 |     string  m_toolset; | 
|---|
| 377 |  | 
|---|
| 378 |     bool    m_note;  // if true, run result set to "note" | 
|---|
| 379 |                      // set false by start_message() | 
|---|
| 380 |  | 
|---|
| 381 |     // data needed to stop further compile action after a compile failure | 
|---|
| 382 |     // detected in the same target directory | 
|---|
| 383 |     string  m_previous_target_directory; | 
|---|
| 384 |     bool    m_compile_failed; | 
|---|
| 385 |  | 
|---|
| 386 |   public: | 
|---|
| 387 |     message_manager() : m_note(false) {} | 
|---|
| 388 |     ~message_manager() { /*assert( m_action_name.empty() );*/ } | 
|---|
| 389 |  | 
|---|
| 390 |     bool note() const { return m_note; } | 
|---|
| 391 |     void note( bool value ) { m_note = value; } | 
|---|
| 392 |  | 
|---|
| 393 |     void start_message( const string & action_name, | 
|---|
| 394 |                       const string & target_directory, | 
|---|
| 395 |                       const string & test_name, | 
|---|
| 396 |                       const string & toolset, | 
|---|
| 397 |                       const string & prior_content ) | 
|---|
| 398 |     { | 
|---|
| 399 |       if ( !m_action_name.empty() ) stop_message( prior_content ); | 
|---|
| 400 |       m_action_name = action_name; | 
|---|
| 401 |       m_target_directory = target_directory; | 
|---|
| 402 |       m_test_name = test_name; | 
|---|
| 403 |       m_toolset = toolset; | 
|---|
| 404 |       m_note = false; | 
|---|
| 405 |       if ( m_previous_target_directory != target_directory ) | 
|---|
| 406 |       { | 
|---|
| 407 |         m_previous_target_directory = target_directory; | 
|---|
| 408 |         m_compile_failed = false; | 
|---|
| 409 |       } | 
|---|
| 410 |     } | 
|---|
| 411 |  | 
|---|
| 412 |     void stop_message( const string & content ) | 
|---|
| 413 |     { | 
|---|
| 414 |       if ( m_action_name.empty() ) return; | 
|---|
| 415 |       stop_message( m_action_name, m_target_directory, | 
|---|
| 416 |         "succeed", timestamp(), content ); | 
|---|
| 417 |     } | 
|---|
| 418 |  | 
|---|
| 419 |     void stop_message( const string & action_name, | 
|---|
| 420 |                      const string & target_directory, | 
|---|
| 421 |                      const string & result, | 
|---|
| 422 |                      const string & timestamp, | 
|---|
| 423 |                      const string & content ) | 
|---|
| 424 |     // the only valid action_names are "compile", "link", "run", "lib" | 
|---|
| 425 |     { | 
|---|
| 426 |       // My understanding of the jam output is that there should never be | 
|---|
| 427 |       // a stop_message that was not preceeded by a matching start_message. | 
|---|
| 428 |       // That understanding is built into message_manager code. | 
|---|
| 429 |       assert( m_action_name == action_name ); | 
|---|
| 430 |       assert( m_target_directory == target_directory ); | 
|---|
| 431 |       assert( result == "succeed" || result == "fail" ); | 
|---|
| 432 |  | 
|---|
| 433 |       // if test_log.xml entry needed | 
|---|
| 434 |       if ( !m_compile_failed | 
|---|
| 435 |         || action_name != "compile" | 
|---|
| 436 |         || m_previous_target_directory != target_directory ) | 
|---|
| 437 |       { | 
|---|
| 438 |         if ( action_name == "compile" | 
|---|
| 439 |           && result == "fail" ) m_compile_failed = true; | 
|---|
| 440 |  | 
|---|
| 441 |         test_log tl( target_directory, | 
|---|
| 442 |           m_test_name, m_toolset, action_name == "compile" ); | 
|---|
| 443 |         tl.remove_action( "lib" ); // always clear out lib residue | 
|---|
| 444 |  | 
|---|
| 445 |         // dependency removal | 
|---|
| 446 |         if ( action_name == "lib" ) | 
|---|
| 447 |         { | 
|---|
| 448 |           tl.remove_action( "compile" ); | 
|---|
| 449 |           tl.remove_action( "link" ); | 
|---|
| 450 |           tl.remove_action( "run" ); | 
|---|
| 451 |         } | 
|---|
| 452 |         else if ( action_name == "compile" ) | 
|---|
| 453 |         { | 
|---|
| 454 |           tl.remove_action( "link" ); | 
|---|
| 455 |           tl.remove_action( "run" ); | 
|---|
| 456 |           if ( result == "fail" ) m_compile_failed = true; | 
|---|
| 457 |         } | 
|---|
| 458 |         else if ( action_name == "link" ) { tl.remove_action( "run" ); } | 
|---|
| 459 |  | 
|---|
| 460 |         // dependency removal won't work right with random names, so assert | 
|---|
| 461 |         else { assert( action_name == "run" ); } | 
|---|
| 462 |  | 
|---|
| 463 |         // add the "run" stop_message action | 
|---|
| 464 |         tl.add_action( action_name, | 
|---|
| 465 |           result == "succeed" && note() ? "note" : result, | 
|---|
| 466 |           timestamp, content ); | 
|---|
| 467 |       } | 
|---|
| 468 |  | 
|---|
| 469 |       m_action_name = ""; // signal no pending action | 
|---|
| 470 |       m_previous_target_directory = target_directory; | 
|---|
| 471 |     } | 
|---|
| 472 |   }; | 
|---|
| 473 | } | 
|---|
| 474 |  | 
|---|
| 475 |  | 
|---|
| 476 | //  main  --------------------------------------------------------------------// | 
|---|
| 477 |  | 
|---|
| 478 |  | 
|---|
| 479 | int cpp_main( int argc, char ** argv ) | 
|---|
| 480 | { | 
|---|
| 481 |   // Turn off synchronization with corresponding C standard library files. This | 
|---|
| 482 |   // gives a significant speed improvement on platforms where the standard C++ | 
|---|
| 483 |   // streams are implemented using standard C files. | 
|---|
| 484 |   std::ios::sync_with_stdio(false); | 
|---|
| 485 |  | 
|---|
| 486 |   if ( argc <= 1 ) | 
|---|
| 487 |     std::cout << "Usage: bjam [bjam-args] | process_jam_log [--echo] [--create-directories] [--v2] [locate-root]\n" | 
|---|
| 488 |                  "locate-root         - the same as the bjam ALL_LOCATE_TARGET\n" | 
|---|
| 489 |                  "                      parameter, if any. Default is boost-root.\n" | 
|---|
| 490 |                  "create-directories  - if the directory for xml file doesn't exists - creates it.\n" | 
|---|
| 491 |                  "                      usually used for processing logfile on different machine\n"; | 
|---|
| 492 |  | 
|---|
| 493 |   boost_root = fs::initial_path(); | 
|---|
| 494 |  | 
|---|
| 495 |   while ( !boost_root.empty() | 
|---|
| 496 |     && !fs::exists( boost_root / "libs" ) ) | 
|---|
| 497 |   { | 
|---|
| 498 |     boost_root /=  ".."; | 
|---|
| 499 |   } | 
|---|
| 500 |  | 
|---|
| 501 |   if ( boost_root.empty() ) | 
|---|
| 502 |   { | 
|---|
| 503 |     std::cout << "must be run from within the boost-root directory tree\n"; | 
|---|
| 504 |     return 1; | 
|---|
| 505 |   } | 
|---|
| 506 |  | 
|---|
| 507 |  | 
|---|
| 508 |   if ( argc > 1 && std::strcmp( argv[1], "--echo" ) == 0 ) | 
|---|
| 509 |   { | 
|---|
| 510 |     echo = true; | 
|---|
| 511 |     --argc; ++argv; | 
|---|
| 512 |   } | 
|---|
| 513 |  | 
|---|
| 514 |  | 
|---|
| 515 |   if (argc > 1 && std::strcmp( argv[1], "--create-directories" ) == 0 ) | 
|---|
| 516 |   { | 
|---|
| 517 |       create_dirs = true; | 
|---|
| 518 |       --argc; ++argv; | 
|---|
| 519 |   }  | 
|---|
| 520 |  | 
|---|
| 521 |   if ( argc > 1 && std::strcmp( argv[1], "--v2" ) == 0 ) | 
|---|
| 522 |   { | 
|---|
| 523 |     boost_build_v2 = true; | 
|---|
| 524 |     --argc; ++argv; | 
|---|
| 525 |   } | 
|---|
| 526 |  | 
|---|
| 527 |  | 
|---|
| 528 |   if (argc > 1) | 
|---|
| 529 |   { | 
|---|
| 530 |       locate_root = fs::path( argv[1], fs::native ); | 
|---|
| 531 |       --argc; ++argv; | 
|---|
| 532 |   }  | 
|---|
| 533 |   else | 
|---|
| 534 |   { | 
|---|
| 535 |       locate_root = boost_root; | 
|---|
| 536 |   } | 
|---|
| 537 |  | 
|---|
| 538 |   std::cout << "boost_root: " << boost_root.string() << '\n' | 
|---|
| 539 |             << "locate_root: " << locate_root.string() << '\n'; | 
|---|
| 540 |  | 
|---|
| 541 |   message_manager mgr; | 
|---|
| 542 |  | 
|---|
| 543 |   string line; | 
|---|
| 544 |   string content; | 
|---|
| 545 |   bool capture_lines = false; | 
|---|
| 546 |  | 
|---|
| 547 |   std::istream* input; | 
|---|
| 548 |   if (argc > 1) | 
|---|
| 549 |   { | 
|---|
| 550 |       input = new std::ifstream(argv[1]); | 
|---|
| 551 |   } | 
|---|
| 552 |   else | 
|---|
| 553 |   { | 
|---|
| 554 |       input = &std::cin; | 
|---|
| 555 |   } | 
|---|
| 556 |  | 
|---|
| 557 |   // This loop looks at lines for certain signatures, and accordingly: | 
|---|
| 558 |   //   * Calls start_message() to start capturing lines. (start_message() will | 
|---|
| 559 |   //     automatically call stop_message() if needed.) | 
|---|
| 560 |   //   * Calls stop_message() to stop capturing lines. | 
|---|
| 561 |   //   * Capture lines if line capture on. | 
|---|
| 562 |  | 
|---|
| 563 |   while ( std::getline( *input, line ) ) | 
|---|
| 564 |   { | 
|---|
| 565 |     if ( echo ) std::cout << line << "\n"; | 
|---|
| 566 |  | 
|---|
| 567 |     // create map of test-name to test-info | 
|---|
| 568 |     if ( line.find( "boost-test(" ) == 0 ) | 
|---|
| 569 |     { | 
|---|
| 570 |       string::size_type pos = line.find( '"' ); | 
|---|
| 571 |       string test_name( line.substr( pos+1, line.find( '"', pos+1)-pos-1 ) ); | 
|---|
| 572 |       test_info info; | 
|---|
| 573 |       info.always_show_run_output | 
|---|
| 574 |         = line.find( "\"always_show_run_output\"" ) != string::npos; | 
|---|
| 575 |       info.type = line.substr( 11, line.find( ')' )-11 ); | 
|---|
| 576 |       for (unsigned int i = 0; i!=info.type.size(); ++i ) | 
|---|
| 577 |         { info.type[i] = std::tolower( info.type[i] ); } | 
|---|
| 578 |       pos = line.find( ':' ); | 
|---|
| 579 |       // the rest of line is missing if bjam didn't know how to make target | 
|---|
| 580 |       if ( pos + 1 != line.size() ) | 
|---|
| 581 |       { | 
|---|
| 582 |         info.file_path = line.substr( pos+3, | 
|---|
| 583 |           line.find( "\"", pos+3 )-pos-3 ); | 
|---|
| 584 |         convert_path_separators( info.file_path ); | 
|---|
| 585 |         if ( info.file_path.find( "libs/libs/" ) == 0 ) info.file_path.erase( 0, 5 ); | 
|---|
| 586 |         if ( test_name.find( "/" ) == string::npos ) | 
|---|
| 587 |             test_name = "/" + test_name; | 
|---|
| 588 |         test2info.insert( std::make_pair( test_name, info ) ); | 
|---|
| 589 |   //      std::cout << test_name << ", " << info.type << ", " << info.file_path << "\n"; | 
|---|
| 590 |       } | 
|---|
| 591 |       else | 
|---|
| 592 |       { | 
|---|
| 593 |         std::cout << "*****Warning - missing test path: " << line << "\n" | 
|---|
| 594 |           << "  (Usually occurs when bjam doesn't know how to make a target)\n"; | 
|---|
| 595 |       } | 
|---|
| 596 |       continue; | 
|---|
| 597 |     } | 
|---|
| 598 |  | 
|---|
| 599 |     // these actions represent both the start of a new action | 
|---|
| 600 |     // and the end of a failed action | 
|---|
| 601 |     else if ( line.find( "C++-action " ) != string::npos | 
|---|
| 602 |       || line.find( "vc-C++ " ) != string::npos | 
|---|
| 603 |       || line.find( "C-action " ) != string::npos | 
|---|
| 604 |       || line.find( "Cc-action " ) != string::npos | 
|---|
| 605 |       || line.find( "vc-Cc " ) != string::npos | 
|---|
| 606 |       || line.find( "Link-action " ) != string::npos | 
|---|
| 607 |       // archive can fail too | 
|---|
| 608 |       || line.find( "Archive-action " ) != string::npos | 
|---|
| 609 |       || line.find( "vc-Link " ) != string::npos  | 
|---|
| 610 |       || line.find( ".compile.") != string::npos | 
|---|
| 611 |       || ( line.find( ".link") != string::npos && | 
|---|
| 612 |            // .linkonce is present in gcc linker messages about | 
|---|
| 613 |            // unresolved symbols. We don't have to parse those | 
|---|
| 614 |            line.find( ".linkonce" ) == string::npos ) | 
|---|
| 615 |     ) | 
|---|
| 616 |     { | 
|---|
| 617 |       string action( ( line.find( "Link-action " ) != string::npos | 
|---|
| 618 |         || line.find( "vc-Link " ) != string::npos  | 
|---|
| 619 |         || line.find( ".link") != string::npos | 
|---|
| 620 |         || line.find( "Archive-action ") != string::npos ) | 
|---|
| 621 |         ? "link" : "compile" ); | 
|---|
| 622 |       if ( line.find( "...failed " ) != string::npos ) | 
|---|
| 623 |         mgr.stop_message( action, target_directory( line ), | 
|---|
| 624 |           "fail", timestamp(), content ); | 
|---|
| 625 |       else | 
|---|
| 626 |       { | 
|---|
| 627 |         string target_dir( target_directory( line ) ); | 
|---|
| 628 |         mgr.start_message( action, target_dir, | 
|---|
| 629 |           test_name( target_dir ), toolset( target_dir ), content ); | 
|---|
| 630 |       } | 
|---|
| 631 |       content = "\n"; | 
|---|
| 632 |       capture_lines = true; | 
|---|
| 633 |     } | 
|---|
| 634 |  | 
|---|
| 635 |     // these actions are only used to stop the previous action | 
|---|
| 636 |     else if ( line.find( "-Archive" ) != string::npos | 
|---|
| 637 |       || line.find( "MkDir" ) == 0 ) | 
|---|
| 638 |     { | 
|---|
| 639 |       mgr.stop_message( content ); | 
|---|
| 640 |       content.clear(); | 
|---|
| 641 |       capture_lines = false; | 
|---|
| 642 |     } | 
|---|
| 643 |  | 
|---|
| 644 |     else if ( line.find( "execute-test" ) != string::npos  | 
|---|
| 645 |              || line.find( "capture-output" ) != string::npos ) | 
|---|
| 646 |     { | 
|---|
| 647 |       if ( line.find( "...failed " ) != string::npos ) | 
|---|
| 648 |       { | 
|---|
| 649 |         mgr.stop_message( "run", target_directory( line ), | 
|---|
| 650 |           "fail", timestamp(), content ); | 
|---|
| 651 |         content = "\n"; | 
|---|
| 652 |         capture_lines = true; | 
|---|
| 653 |       } | 
|---|
| 654 |       else | 
|---|
| 655 |       { | 
|---|
| 656 |         string target_dir( target_directory( line ) ); | 
|---|
| 657 |         mgr.start_message( "run", target_dir, | 
|---|
| 658 |           test_name( target_dir ), toolset( target_dir ), content ); | 
|---|
| 659 |  | 
|---|
| 660 |         // contents of .output file for content | 
|---|
| 661 |         capture_lines = false; | 
|---|
| 662 |         content = "\n"; | 
|---|
| 663 |         fs::ifstream file( locate_root / target_dir | 
|---|
| 664 |           / (test_name(target_dir) + ".output") ); | 
|---|
| 665 |         if ( file ) | 
|---|
| 666 |         { | 
|---|
| 667 |           string ln; | 
|---|
| 668 |           while ( std::getline( file, ln ) ) | 
|---|
| 669 |           { | 
|---|
| 670 |             if ( ln.find( "<note>" ) != string::npos ) mgr.note( true ); | 
|---|
| 671 |             append_html( ln, content ); | 
|---|
| 672 |             content += "\n"; | 
|---|
| 673 |           } | 
|---|
| 674 |         } | 
|---|
| 675 |       } | 
|---|
| 676 |     } | 
|---|
| 677 |  | 
|---|
| 678 |     // bjam indicates some prior dependency failed by a "...skipped" message | 
|---|
| 679 |     else if ( line.find( "...skipped <" ) != string::npos && line.find( "<directory-grist>" ) == string::npos) | 
|---|
| 680 |     { | 
|---|
| 681 |       mgr.stop_message( content ); | 
|---|
| 682 |       content.clear(); | 
|---|
| 683 |       capture_lines = false; | 
|---|
| 684 |  | 
|---|
| 685 |       if ( line.find( " for lack of " ) != string::npos ) | 
|---|
| 686 |       { | 
|---|
| 687 |         capture_lines = ( line.find( ".run for lack of " ) == string::npos ); | 
|---|
| 688 |  | 
|---|
| 689 |         string target_dir; | 
|---|
| 690 |         string lib_dir; | 
|---|
| 691 |  | 
|---|
| 692 |         parse_skipped_msg( line, target_dir, lib_dir ); | 
|---|
| 693 |  | 
|---|
| 694 |         if ( target_dir != lib_dir ) // it's a lib problem | 
|---|
| 695 |         { | 
|---|
| 696 |           mgr.start_message( "lib", target_dir,  | 
|---|
| 697 |             test_name( target_dir ), toolset( target_dir ), content ); | 
|---|
| 698 |           content = lib_dir; | 
|---|
| 699 |           mgr.stop_message( "lib", target_dir, "fail", timestamp(), content ); | 
|---|
| 700 |           content = "\n"; | 
|---|
| 701 |         } | 
|---|
| 702 |       } | 
|---|
| 703 |  | 
|---|
| 704 |     } | 
|---|
| 705 |  | 
|---|
| 706 |     else if ( line.find( "**passed**" ) != string::npos | 
|---|
| 707 |       || line.find( "failed-test-file " ) != string::npos | 
|---|
| 708 |       || line.find( "command-file-dump" ) != string::npos ) | 
|---|
| 709 |     { | 
|---|
| 710 |       mgr.stop_message( content ); | 
|---|
| 711 |       content = "\n"; | 
|---|
| 712 |       capture_lines = true; | 
|---|
| 713 |     } | 
|---|
| 714 |  | 
|---|
| 715 |     else if ( capture_lines ) // hang onto lines for possible later use | 
|---|
| 716 |     { | 
|---|
| 717 |       append_html( line, content );; | 
|---|
| 718 |       content += "\n"; | 
|---|
| 719 |     } | 
|---|
| 720 |   } | 
|---|
| 721 |  | 
|---|
| 722 |   mgr.stop_message( content ); | 
|---|
| 723 |   if (input != &std::cin) | 
|---|
| 724 |       delete input; | 
|---|
| 725 |   return 0; | 
|---|
| 726 | } | 
|---|