// Generate Compiler Status HTML from jam regression test output -----------// // (C) Copyright Beman Dawes 2002. Permission to copy, // use, modify, sell and distribute this software is granted provided this // copyright notice appears in all copies. This software is provided "as is" // without express or implied warranty, and with no claim as to its // suitability for any purpose. /******************************************************************************* This program was designed to work unchanged on all platforms and configurations. All output which is platform or configuration dependent is obtained from external sources such as the status/Jamfile, the residue from jam execution, the tools/build/xxx-tools.jam files, or the output of the config_info tests. Please avoid adding platform or configuration dependencies during program maintenance. *******************************************************************************/ #include "boost/filesystem/operations.hpp" #include "boost/filesystem/fstream.hpp" #include "tiny_xml.hpp" namespace fs = boost::filesystem; namespace xml = boost::tiny_xml; #include // for abort #include #include #include #include #include #include #include using std::string; const string pass_msg( "Pass" ); const string warn_msg( "Warn" ); const string fail_msg( "" "Fail" "" ); const string missing_residue_msg( "Missing" ); const std::size_t max_compile_msg_size = 10000; namespace { fs::path boost_root_dir; bool ignore_pass; bool no_warn; bool no_links; fs::directory_iterator end_itr; // It's immportant for reliability that we find the same compilers for each // test, and that they match the column header. So save the names at the // time column headings are generated. std::vector toolsets; fs::ifstream jamfile; fs::ofstream report; fs::ofstream links_file; string links_name; string specific_compiler; // if running on one toolset only const string empty_string; // convert backslashes to forward slashes -----------------------------------// void convert_backslashes( string & s ) { for ( string::iterator itr = s.begin(); itr != s.end(); ++itr ) if ( *itr == '\\' ) *itr = '/'; } // extra information from target directory string ---------------------------// string extract_test_name( const string & s ) { string t( s ); string::size_type pos = t.find( "/bin/" ); if ( pos != string::npos ) pos += 5; else return ""; return t.substr( pos, t.find( ".", pos ) - pos ); } // find_file ---------------------------------------------------------------// // given a directory to recursively search bool find_file( const fs::path & dir_path, const string & name, fs::path & path_found, const string & ignore_dir_named="" ) { if ( !fs::exists( dir_path ) ) return false; for ( fs::directory_iterator itr( dir_path ); itr != end_itr; ++itr ) if ( fs::is_directory( *itr ) && itr->leaf() != ignore_dir_named ) { if ( find_file( *itr, name, path_found ) ) return true; } else if ( itr->leaf() == name ) { path_found = *itr; return true; } return false; } // platform_desc -----------------------------------------------------------// // from boost-root/status/bin/config_info.test/xxx/.../config_info.output string platform_desc( const fs::path & boost_root_dir ) { string result; fs::path dot_output_path; // the gcc config_info "Detected Platform" sometimes reports "cygwin", so // prefer any of the other compilers. if ( find_file( boost_root_dir << "status/bin/config_info.test", "config_info.output", dot_output_path, "gcc" ) || find_file( boost_root_dir << "status/bin/config_info.test", "config_info.output", dot_output_path ) ) { fs::ifstream file( dot_output_path ); if ( file ) { while( std::getline( file, result ) ) { if ( result.find( "Detected Platform: " ) == 0 ) { result.erase( 0, 19 ); return result; } } result.clear(); } } return result; } // version_desc ------------------------------------------------------------// // from boost-root/status/bin/config_info.test/xxx/.../config_info.output string version_desc( const fs::path & boost_root_dir, const string & compiler_name ) { string result; fs::path dot_output_path; if ( find_file( boost_root_dir << "status/bin/config_info.test" << compiler_name, "config_info.output", dot_output_path ) ) { fs::ifstream file( dot_output_path ); if ( file ) { if( std::getline( file, result ) ) { string::size_type pos = result.find( "version " ); if ( pos != string::npos ) { result.erase( 0, pos+8 ); } else result.clear(); } } } return result; } // compiler_desc -----------------------------------------------------------// // from boost-root/tools/build/xxx-tools.jam string compiler_desc( const fs::path & boost_root_dir, const string & compiler_name ) { string result; fs::path tools_path( boost_root_dir << "tools/build" << compiler_name + "-tools.jam" ); fs::ifstream file( tools_path ); if ( file ) { while( std::getline( file, result ) ) { if ( result.substr( 0, 3 ) == "#//" ) { result.erase( 0, 3 ); return result; } } result.clear(); } return result; } // test_type_desc ----------------------------------------------------------// // from boost-root/status/Jamfile string test_type_desc( const string & test_name ) { // adding "/" and ".c" eliminates a couple of corner cases. // ".c" rather than ".cpp" because regex library includes some .c tests string search_name1( "/" + test_name + ".c" ); string search_name2( " " + test_name + " " ); string result; if ( jamfile.is_open() ) { jamfile.clear(); jamfile.seekg(0); string line; while( std::getline( jamfile, line ) ) { if ( line.find( ".c" ) != string::npos ) { if ( line.find( "run " ) != string::npos ) result = "run"; else if ( line.find( "run-fail " ) != string::npos ) result = "run-fail"; else if ( line.find( "link " ) != string::npos ) result = "link"; else if ( line.find( "link-fail " ) != string::npos ) result = "link-fail"; else if ( line.find( "compile " ) != string::npos ) result = "compile"; else if ( line.find( "compile-fail " ) != string::npos ) result = "compile-fail"; } if ( result.size() && ( line.find( search_name1 ) != string::npos || line.find( search_name2 ) != string::npos ) ) { if ( line.find( "# compiler_status" ) != string::npos ) result.insert( 0, 1, '*' ); return result; } } result.clear(); } return result; } // target_directory --------------------------------------------------------// // this amounts to a request to find a unique leaf directory fs::path target_directory( const fs::path & root ) { if ( !fs::exists( root ) ) return fs::path("no-such-path"); fs::path child; for ( fs::directory_iterator itr( root ); itr != end_itr; ++itr ) { if ( fs::is_directory( *itr ) ) { if ( child.is_null() ) child = *itr; else throw std::runtime_error( string( "two target possibilities found: \"" ) + child.generic_path() + "\" and \"" + (*itr).generic_path() + "\"" ); } } if ( child.is_null() ) return root; // this dir has no children return target_directory( child ); } // element_content ---------------------------------------------------------// const string & element_content( const xml::element_ptr & root, const string & name ) { static string empty_string; xml::element_list::iterator itr; for ( itr = root->elements.begin(); itr != root->elements.end() && (*itr)->name != name; ++itr ) {} return itr != root->elements.end() ? (*itr)->content : empty_string; } // generate_report ---------------------------------------------------------// // return 0 if nothing generated, 1 otherwise, except 2 if compiler msgs int generate_report( const xml::element_ptr & db, const string & test_name, const string & toolset, bool pass, bool always_show_run_output = false ) { // compile msgs sometimes modified, so make a local copy string compile( (pass && no_warn) ? empty_string : element_content( db, "compile" ) ); const string & link( pass ? empty_string : element_content( db, "link" ) ); const string & run( (pass && !always_show_run_output) ? empty_string : element_content( db, "run" ) ); string lib( pass ? empty_string : element_content( db, "lib" ) ); // some compilers output the filename even if there are no errors or // warnings; detect this if one line of output and it contains no space. string::size_type pos = compile.find( '\n', 1 ); if ( pos != string::npos && compile.size()-pos <= 2 && compile.find( ' ' ) == string::npos ) compile.clear(); if ( lib.empty() && compile.empty() && link.empty() && run.empty() ) return 0; int result = 1; // some kind of msg for sure // limit compile message length if ( compile.size() > max_compile_msg_size ) { compile.erase( max_compile_msg_size ); compile += "...\n (remainder deleted because of excessive size)\n"; } links_file << "

" << test_name << " / " << toolset << "

\n"; if ( !compile.empty() ) { ++result; links_file << "

Compiler output:

"
        << compile << "
\n"; } if ( !link.empty() ) links_file << "

Linker output:

" << link << "
\n"; if ( !run.empty() ) links_file << "

Run output:

" << run << "
\n"; static std::set< string > failed_lib_target_dirs; if ( !lib.empty() ) { if ( lib[0] == '\n' ) lib.erase( 0, 1 ); string lib_test_name( extract_test_name( lib ) ); links_file << "

Library build failure:

\n" "See " << lib_test_name << " / " << toolset << ""; if ( failed_lib_target_dirs.find( lib ) == failed_lib_target_dirs.end() ) { failed_lib_target_dirs.insert( lib ); fs::ifstream file( boost_root_dir << lib << "test_log.xml" ); if ( file ) { xml::element_ptr db = xml::parse( file ); generate_report( db, lib_test_name, toolset, false ); } else { links_file << "

" << lib_test_name << " / " << toolset << "

\n" "test_log.xml not found\n"; } } } return result; } // do_cell -----------------------------------------------------------------// bool do_cell( const fs::path & test_dir, const string & test_name, const string & toolset, string & target, bool always_show_run_output ) // return true if any results except pass_msg { fs::path target_dir( target_directory( test_dir << toolset ) ); bool pass = false; // missing jam residue if ( fs::exists( target_dir << (test_name + ".success") ) ) pass = true; else if ( !fs::exists( target_dir << (test_name + ".failure") ) && !fs::exists( target_dir << "test_log.xml" ) ) { target += "" + missing_residue_msg + ""; return true; } int anything_generated = 0; if ( !no_links ) { fs::ifstream file( target_dir << "test_log.xml" ); if ( !file ) // missing jam_log.xml { std::cerr << "Missing jam_log.xml in target \"" << target_dir.generic_path() << "\"\n"; target += ""; target += pass ? pass_msg : fail_msg; target += ""; return pass; } xml::element_ptr db = xml::parse( file ); // generate bookmarked report of results, and link to it anything_generated = generate_report( db, test_name, toolset, pass, always_show_run_output ); } target += ""; if ( anything_generated != 0 ) { target += ""; target += pass ? (anything_generated < 2 ? pass_msg : warn_msg) : fail_msg; target += ""; } else target += pass ? pass_msg : fail_msg; target += ""; return (anything_generated != 0) || !pass; } // do_row ------------------------------------------------------------------// void do_row( const fs::path & boost_root_dir, const fs::path & test_dir, // "c:/boost_root/status/bin/any_test.test" const string & test_name, // "any_test" string & target ) { // get the path to the test program from the .test file contents string test_path( test_name ); // test_name is default if missing .test fs::path file_path; if ( find_file( test_dir, test_name + ".test", file_path ) ) { fs::ifstream file( file_path ); if ( file ) { std::getline( file, test_path ); if ( test_path.size() ) { if ( test_path[0] == '\"' ) // added for non-Win32 systems { test_path = test_path.erase( 0, 1 ); // strip " test_path.erase( test_path.find( "\"" ) ); } // test_path is now a disk path, so convert to URL style path convert_backslashes( test_path ); string::size_type pos = test_path.find( "/libs/" ); if ( pos != string::npos ) test_path.replace( 0, pos, ".." ); } } } // extract the library name from the test_path string lib_name( test_path ); string::size_type pos = lib_name.find( "/libs/" ); if ( pos != string::npos ) { lib_name.erase( 0, pos+6 ); pos = lib_name.find( "/" ); if ( pos != string::npos ) lib_name.erase( pos ); } else lib_name.clear(); // find the library documentation path string lib_docs_path( "../libs/" + lib_name + "/" ); fs::path cur_lib_docs_path( boost_root_dir << "libs" << lib_name ); if ( fs::exists( cur_lib_docs_path << "index.htm" ) ) lib_docs_path += "index.htm"; else if ( fs::exists( cur_lib_docs_path << "index.html" ) ) lib_docs_path += "index.html"; else if ( fs::exists( cur_lib_docs_path << "doc/index.htm" ) ) lib_docs_path += "doc/index.htm"; else if ( fs::exists( cur_lib_docs_path << "doc/index.html" ) ) lib_docs_path += "doc/index.html"; else if ( fs::exists( cur_lib_docs_path << (lib_name + ".htm") ) ) lib_docs_path += lib_name + ".htm"; else if ( fs::exists( cur_lib_docs_path << (lib_name + ".html") ) ) lib_docs_path += lib_name + ".html"; else if ( fs::exists( cur_lib_docs_path << "doc" << (lib_name + ".htm") ) ) lib_docs_path += lib_name + ".htm"; else if ( fs::exists( cur_lib_docs_path << "doc" << (lib_name + ".html") ) ) lib_docs_path += lib_name + ".html"; // generate the library name, test name, and test type table data string::size_type row_start_pos = target.size(); target += "" + lib_name + ""; target += "" + test_name + ""; string test_type = test_type_desc( test_name ); bool always_show_run_output = false; if ( !test_type.empty() && test_type[0] == '*' ) { always_show_run_output = true; test_type.erase( 0, 1 ); } target += "" + test_type + ""; bool no_warn_save = no_warn; if ( test_type.find( "fail" ) != string::npos ) no_warn = true; // for each compiler, generate ... html bool anything_to_report = false; for ( std::vector::const_iterator itr=toolsets.begin(); itr != toolsets.end(); ++itr ) { anything_to_report |= do_cell( test_dir, test_name, *itr, target, always_show_run_output ); } target += ""; if ( ignore_pass && !anything_to_report ) target.erase( row_start_pos ); no_warn = no_warn_save; } // do_table_body -----------------------------------------------------------// void do_table_body( const fs::path & boost_root_dir, const fs::path & build_dir ) { // rows are held in a vector so they can be sorted, if desired. std::vector results; // each test directory for ( fs::directory_iterator itr( build_dir ); itr != end_itr; ++itr ) { if ( fs::is_directory( *itr ) ) { results.push_back( std::string() ); // no sort required, but leave code // in place in case that changes do_row( boost_root_dir, *itr, itr->leaf().substr( 0, itr->leaf().size()-5 ), results[results.size()-1] ); } } std::sort( results.begin(), results.end() ); for ( std::vector::iterator v(results.begin()); v != results.end(); ++v ) { report << *v << "\n"; } } // do_table ----------------------------------------------------------------// void do_table( const fs::path & boost_root_dir ) { fs::path build_dir( boost_root_dir << "status" << "bin" ); report << "\n"; // generate the column headings report << "\n" "\n"; fs::directory_iterator itr( build_dir ); if ( itr != end_itr ) { fs::directory_iterator compiler_itr( *itr ); if ( specific_compiler.empty() ) std::clog << "Using " << itr->generic_path() << " to determine compilers\n"; for (; compiler_itr != end_itr; ++compiler_itr ) { if ( fs::is_directory( *compiler_itr ) // check just to be sure && compiler_itr->leaf() != "test" ) // avoid strange directory (Jamfile bug?) { if ( specific_compiler.size() != 0 && specific_compiler != compiler_itr->leaf() ) continue; toolsets.push_back( compiler_itr->leaf() ); string desc( compiler_desc( boost_root_dir, compiler_itr->leaf() ) ); string vers( version_desc( boost_root_dir, compiler_itr->leaf() ) ); report << "\n"; } } } report << "\n"; // now the rest of the table body do_table_body( boost_root_dir, build_dir ); report << "
LibraryTest NameTest Type" << (desc.size() ? desc : compiler_itr->leaf()) << (vers.size() ? (string( "
" ) + vers ) : string( "" )) << "
\n"; } } // unnamed namespace // main --------------------------------------------------------------------// #define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE #include int cpp_main( int argc, char * argv[] ) // note name! { while ( argc > 1 && *argv[1] == '-' ) { if ( argc > 2 && std::strcmp( argv[1], "--compiler" ) == 0 ) { specific_compiler = argv[2]; --argc; ++argv; } else if ( std::strcmp( argv[1], "--ignore-pass" ) == 0 ) ignore_pass = true; else if ( std::strcmp( argv[1], "--no-warn" ) == 0 ) no_warn = true; else { std::cerr << "Unknown option: " << argv[1] << "\n"; argc = 1; } --argc; ++argv; } if ( argc != 3 && argc != 4 ) { std::cerr << "usage: compiler_status [options...] boost-root-dir status-file [links-file]\n" " options: --compiler name Run for named compiler only\n" " --ignore-pass Do not report tests which pass all compilers\n" " --no-warn Warnings not reported if test passes\n" "example: compiler_status --compiler gcc \\boost-root cs.html cs-links.html\n"; return 1; } boost_root_dir = fs::path( argv[1], fs::system_specific ); jamfile.open( boost_root_dir << "status/Jamfile" ); // may fail; that's OK report.open( argv[2] ); if ( !report ) { std::cerr << "Could not open report output file: " << argv[2] << std::endl; return 1; } if ( argc == 4 ) { links_name = argv[3]; links_file.open( links_name ); if ( !links_file ) { std::cerr << "Could not open links output file: " << links_name << std::endl; return 1; } } else no_links = true; char run_date[128]; std::time_t tod; std::time( &tod ); std::strftime( run_date, sizeof(run_date), "%X UTC, %A %d %B %Y", std::gmtime( &tod ) ); report << "\n" "\n" "Boost Compiler Status Automatic Test\n" "\n" "\n" "\n" "\n" "\n" "\n
\n" "

Compiler Status: " + platform_desc( boost_root_dir ) + "

\n" "Run Date: " << run_date << "\n
\n
\n" ; do_table( boost_root_dir ); report << "\n" "\n" ; return 0; }