summaryrefslogtreecommitdiff
path: root/tools/regression/src/library_status.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/regression/src/library_status.cpp')
-rw-r--r--tools/regression/src/library_status.cpp889
1 files changed, 889 insertions, 0 deletions
diff --git a/tools/regression/src/library_status.cpp b/tools/regression/src/library_status.cpp
new file mode 100644
index 0000000000..3faa0f63c9
--- /dev/null
+++ b/tools/regression/src/library_status.cpp
@@ -0,0 +1,889 @@
+// Generate Library Status HTML from jam regression test output -----------//
+
+// Copyright Bryce Lelbach 2011
+// Copyright Beman Dawes 2002-2011.
+
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+// See http://www.boost.org/tools/regression/ for documentation.
+
+//Note: This version of the original program builds a large table
+//which includes all build variations such as build/release, static/dynamic, etc.
+
+
+/*******************************************************************************
+
+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 .xml file from
+process_jam_log 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 <boost/foreach.hpp>
+
+namespace fs = boost::filesystem;
+
+#include "detail/tiny_xml.hpp"
+namespace xml = boost::tiny_xml;
+
+#include <boost/iterator/transform_iterator.hpp>
+
+#include <cstdlib> // for abort, exit
+#include <string>
+#include <vector>
+#include <set>
+#include <utility> // for make_pair on STLPort
+#include <map>
+#include <algorithm> // max_element, find_if
+#include <iostream>
+#include <fstream>
+#include <ctime>
+#include <stdexcept>
+#include <cassert>
+#include <utility> // for pair
+
+using std::string;
+
+const string pass_msg( "Pass" );
+const string warn_msg( "<i>Warn</i>" );
+const string fail_msg( "<font color=\"#FF0000\"><i>Fail</i></font>" );
+const string missing_residue_msg( "<i>Missing</i>" );
+
+const std::size_t max_compile_msg_size = 10000;
+
+namespace
+{
+ fs::path locate_root; // locate-root (AKA ALL_LOCATE_TARGET) complete path
+ bool ignore_pass = false;
+ bool no_warn = false;
+ bool no_links = false;
+
+ // transform pathname to something html can accept
+ struct char_xlate {
+ typedef char result_type;
+ result_type operator()(char c) const{
+ if(c == '/' || c == '\\')
+ return '-';
+ return c;
+ }
+ };
+ typedef boost::transform_iterator<char_xlate, std::string::const_iterator> html_from_path;
+
+ template<class I1, class I2>
+ std::ostream & operator<<(
+ std::ostream &os,
+ std::pair<I1, I2> p
+ ){
+ while(p.first != p.second)
+ os << *p.first++;
+ return os;
+ }
+
+ struct col_node {
+ int rows, cols;
+ bool is_leaf;
+ typedef std::pair<const std::string, col_node> subcolumn;
+ typedef std::map<std::string, col_node> subcolumns_t;
+ subcolumns_t m_subcolumns;
+ bool operator<(const col_node &cn) const;
+ col_node() :
+ is_leaf(false)
+ {}
+ std::pair<int, int> get_spans();
+ };
+
+ std::pair<int, int> col_node::get_spans(){
+ rows = 1;
+ cols = 0;
+ if(is_leaf){
+ cols = 1;
+ }
+ if(! m_subcolumns.empty()){
+ BOOST_FOREACH(
+ subcolumn & s,
+ m_subcolumns
+ ){
+ std::pair<int, int> spans;
+ spans = s.second.get_spans();
+ rows = (std::max)(rows, spans.first);
+ cols += spans.second;
+ }
+ ++rows;
+ }
+ return std::make_pair(rows, cols);
+ }
+
+ void build_node_tree(const fs::path & dir_root, col_node & node){
+ bool has_directories = false;
+ bool has_files = false;
+ BOOST_FOREACH(
+ fs::directory_entry & d,
+ std::make_pair(
+ fs::directory_iterator(dir_root),
+ fs::directory_iterator()
+ )
+ ){
+ if(fs::is_directory(d)){
+ has_directories = true;
+ std::pair<col_node::subcolumns_t::iterator, bool> result
+ = node.m_subcolumns.insert(
+ std::make_pair(d.path().filename().string(), col_node())
+ );
+ build_node_tree(d, result.first->second);
+ }
+ else{
+ has_files = true;
+ }
+ }
+ if(has_directories && has_files)
+ throw std::string("invalid bin directory structure");
+ node.is_leaf = has_files;
+ }
+
+ fs::ofstream report;
+ fs::ofstream links_file;
+ string links_name;
+
+ string specific_compiler; // if running on one toolset only
+
+ const string empty_string;
+
+ // extract object library name from target directory string ----------------//
+
+ string extract_object_library_name( const string & s )
+ {
+ string t( s );
+ string::size_type pos = t.find( "/build/" );
+ if ( pos != string::npos ) pos += 7;
+ else if ( (pos = t.find( "/test/" )) != string::npos ) pos += 6;
+ else return "";
+ return t.substr( pos, t.find( "/", pos ) - pos );
+ }
+
+ // find_element ------------------------------------------------------------//
+
+ struct element_equal {
+ const string & m_name;
+ element_equal(const string & name) :
+ m_name(name)
+ {}
+ bool operator()(const xml::element_ptr & xep) const {
+ return xep.get()->name == m_name;
+ }
+ };
+
+ xml::element_list::const_iterator find_element(
+ const xml::element & root, const string & name
+ ){
+ return std::find_if(
+ root.elements.begin(),
+ root.elements.end(),
+ element_equal(name)
+ );
+ }
+
+ // element_content ---------------------------------------------------------//
+ const string & element_content(
+ const xml::element & root, const string & name
+ ){
+ xml::element_list::const_iterator itr;
+ itr = find_element(root, name);
+ if(root.elements.end() == itr)
+ return empty_string;
+ return (*itr)->content;
+ }
+
+ // attribute_value ----------------------------------------------------------//
+
+ struct attribute_equal {
+ const string & m_name;
+ attribute_equal(const string & name) :
+ m_name(name)
+ {}
+ bool operator()(const xml::attribute & a) const {
+ return a.name == m_name;
+ }
+ };
+
+ const string & attribute_value(
+ const xml::element & element,
+ const string & attribute_name
+ ){
+ xml::attribute_list::const_iterator itr;
+ itr = std::find_if(
+ element.attributes.begin(),
+ element.attributes.end(),
+ attribute_equal(attribute_name)
+ );
+ if(element.attributes.end() == itr){
+ static const string empty_string;
+ return empty_string;
+ }
+ return itr->value;
+ }
+
+ // generate_report ---------------------------------------------------------//
+
+ // return 0 if nothing generated, 1 otherwise, except 2 if compiler msgs
+ int generate_report(
+ const xml::element & db,
+ const std::string source_library_name,
+ const string & test_type,
+ const fs::path & target_dir,
+ bool pass,
+ bool always_show_run_output
+ )
+ {
+ // 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() || test_type == "compile_fail"
+ )
+ && 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";
+ }
+
+ const string target_dir_string = target_dir.string();
+
+ links_file << "<h2><a name=\"";
+ links_file << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ << "\">"
+ << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ ;
+ links_file << "</a></h2>\n";;
+
+ if ( !compile.empty() )
+ {
+ ++result;
+ links_file << "<h3>Compiler output:</h3><pre>"
+ << compile << "</pre>\n";
+ }
+ if ( !link.empty() )
+ links_file << "<h3>Linker output:</h3><pre>" << link << "</pre>\n";
+ if ( !run.empty() )
+ links_file << "<h3>Run output:</h3><pre>" << run << "</pre>\n";
+
+ // for an object library failure, generate a reference to the object
+ // library failure message, and (once only) generate the object
+ // library failure message itself
+ static std::set< string > failed_lib_target_dirs; // only generate once
+ if ( !lib.empty() )
+ {
+ if ( lib[0] == '\n' ) lib.erase( 0, 1 );
+ string object_library_name( extract_object_library_name( lib ) );
+
+ // changing the target directory naming scheme breaks
+ // extract_object_library_name()
+ assert( !object_library_name.empty() );
+ if ( object_library_name.empty() )
+ std::cerr << "Failed to extract object library name from " << lib << "\n";
+
+ links_file << "<h3>Library build failure: </h3>\n"
+ "See <a href=\"#"
+ << source_library_name << "-"
+ << object_library_name << "-"
+ << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ << source_library_name << " - "
+ << object_library_name << " - "
+ << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ << "</a>";
+ if ( failed_lib_target_dirs.find( lib ) == failed_lib_target_dirs.end() )
+ {
+ failed_lib_target_dirs.insert( lib );
+ fs::path pth( locate_root / lib / "test_log.xml" );
+ fs::ifstream file( pth );
+ if ( file )
+ {
+ xml::element_ptr db = xml::parse( file, pth.string() );
+ generate_report(
+ *db,
+ source_library_name,
+ test_type,
+ target_dir,
+ false,
+ false
+ );
+ }
+ else
+ {
+ links_file << "<h2><a name=\""
+ << object_library_name << "-"
+ << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ << "\">"
+ << object_library_name << " - "
+ << std::make_pair(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end())
+ )
+ << "</a></h2>\n"
+ << "test_log.xml not found\n";
+ }
+ }
+ }
+ return result;
+ }
+
+ struct has_fail_result {
+ //bool operator()(const boost::shared_ptr<const xml::element> e) const {
+ bool operator()(const xml::element_ptr & e) const {
+ return attribute_value(*e, "result") == "fail";
+ }
+ };
+
+ // do_cell ---------------------------------------------------------------//
+ bool do_cell(
+ const fs::path & target_dir,
+ const string & lib_name,
+ const string & test_name,
+ string & target,
+ bool profile
+ ){
+ // return true if any results except pass_msg
+ bool pass = false;
+
+ fs::path xml_file_path( target_dir / "test_log.xml" );
+ if ( !fs::exists( xml_file_path ) )
+ {
+ fs::path test_path = target_dir / (test_name + ".test");
+ target += "<td align=\"right\">";
+ target += fs::exists( test_path) ? pass_msg : fail_msg;
+ target += "</td>";
+ return true;
+ }
+
+
+ string test_type( "unknown" );
+ bool always_show_run_output( false );
+
+ fs::ifstream file( xml_file_path );
+ xml::element_ptr dbp = xml::parse( file, xml_file_path.string() );
+ const xml::element & db( *dbp );
+
+ always_show_run_output
+ = attribute_value( db, "show-run-output" ) == "true";
+
+ // if we don't find any failures
+ // mark it as a pass
+ pass = (db.elements.end() == std::find_if(
+ db.elements.begin(),
+ db.elements.end(),
+ has_fail_result()
+ ));
+
+ int anything_generated = 0;
+ if (!no_links){
+ anything_generated =
+ generate_report(
+ db,
+ lib_name,
+ test_type,
+ target_dir,
+ pass,
+ always_show_run_output
+ );
+ }
+
+ // generate the status table cell pass/warn/fail HTML
+ target += "<td align=\"right\">";
+ if ( anything_generated != 0 )
+ {
+ target += "<a href=\"";
+ target += links_name;
+ target += "#";
+ const string target_dir_string = target_dir.string();
+ std::copy(
+ html_from_path(target_dir_string.begin()),
+ html_from_path(target_dir_string.end()),
+ std::back_inserter(target)
+ );
+ target += "\">";
+ target += pass
+ ? (anything_generated < 2 ? pass_msg : warn_msg)
+ : fail_msg;
+ target += "</a>";
+ }
+ else target += pass ? pass_msg : fail_msg;
+
+ // if profiling
+ if(profile && pass){
+ // add link to profile
+ target += " <a href=\"";
+ target += (target_dir / "profile.txt").string();
+ target += "\"><i>Profile</i></a>";
+ }
+ target += "</td>";
+ return (anything_generated != 0) || !pass;
+ }
+
+ bool visit_node_tree(
+ const col_node & node,
+ fs::path dir_root,
+ const string & lib_name,
+ const string & test_name,
+ string & target,
+ bool profile
+ ){
+ bool retval = false;
+ if(node.is_leaf){
+ return do_cell(
+ dir_root,
+ lib_name,
+ test_name,
+ target,
+ profile
+ );
+ }
+ BOOST_FOREACH(
+ const col_node::subcolumn & s,
+ node.m_subcolumns
+ ){
+ fs::path subdir = dir_root / s.first;
+ retval |= visit_node_tree(
+ s.second,
+ subdir,
+ lib_name,
+ test_name,
+ target,
+ s.first == "profile"
+ );
+ }
+ return retval;
+ }
+
+ // emit results for each test
+ void do_row(
+ col_node test_node,
+ const fs::path & test_dir,
+ const string & lib_name,
+ const string & test_name,
+ string & target
+ ){
+ string::size_type row_start_pos = target.size();
+
+ target += "<tr>";
+
+ target += "<td>";
+ //target += "<a href=\"" + url_prefix_dir_view + "/libs/" + lib_name + "\">";
+ target += test_name;
+ //target += "</a>";
+ target += "</td>";
+
+ bool no_warn_save = no_warn;
+
+ // emit cells on this row
+ bool anything_to_report = visit_node_tree(
+ test_node,
+ test_dir,
+ lib_name,
+ test_name,
+ target,
+ false
+ );
+
+ target += "</tr>";
+
+ if ( ignore_pass
+ && ! anything_to_report )
+ target.erase( row_start_pos );
+
+ no_warn = no_warn_save;
+ }
+
+ // do_table_body -----------------------------------------------------------//
+
+ void do_table_body(
+ col_node root_node,
+ const string & lib_name,
+ const fs::path & test_lib_dir
+ ){
+ // rows are held in a vector so they can be sorted, if desired.
+ std::vector<string> results;
+
+ BOOST_FOREACH(
+ fs::directory_entry & d,
+ std::make_pair(
+ fs::directory_iterator(test_lib_dir),
+ fs::directory_iterator()
+ )
+ ){
+ if(! fs::is_directory(d))
+ continue;
+
+ // if the file name contains ".test"
+ if(d.path().extension() != ".test")
+ continue;
+
+ string test_name = d.path().stem().string();
+
+ results.push_back( std::string() );
+ do_row(
+ root_node, //*test_node_itr++,
+ d, // test dir
+ lib_name,
+ test_name,
+ results[results.size()-1]
+ );
+ }
+
+ std::sort( results.begin(), results.end() );
+
+ BOOST_FOREACH(string &s, results)
+ report << s << "\n";
+ }
+
+ // column header-----------------------------------------------------------//
+ int header_depth(const col_node & root){
+ int max_depth = 1;
+ BOOST_FOREACH(
+ const col_node::subcolumn &s,
+ root.m_subcolumns
+ ){
+ max_depth = (std::max)(max_depth, s.second.rows);
+ }
+ return max_depth;
+ }
+
+ void header_cell(int rows, int cols, const std::string & name){
+ // add row cells
+ report << "<td align=\"center\" " ;
+ if(1 < cols)
+ report << "colspan=\"" << cols << "\" " ;
+ if(1 < rows)
+ // span rows to the end the header
+ report << "rowspan=\"" << rows << "\" " ;
+ report << ">" ;
+ report << name;
+ report << "</td>\n";
+ }
+
+ void emit_column_headers(
+ const col_node & node,
+ int display_row,
+ int current_row,
+ int row_count
+ ){
+ if(current_row < display_row){
+ if(! node.m_subcolumns.empty()){
+ BOOST_FOREACH(
+ const col_node::subcolumn &s,
+ node.m_subcolumns
+ ){
+ emit_column_headers(
+ s.second,
+ display_row,
+ current_row + 1,
+ row_count
+ );
+ }
+ }
+ return;
+ }
+ /*
+ if(node.is_leaf && ! node.m_subcolumns.empty()){
+ header_cell(row_count - current_row, 1, std::string(""));
+ }
+ */
+ BOOST_FOREACH(col_node::subcolumn s, node.m_subcolumns){
+ if(1 == s.second.rows)
+ header_cell(row_count - current_row, s.second.cols, s.first);
+ else
+ header_cell(1, s.second.cols, s.first);
+ }
+ }
+
+ fs::path find_lib_test_dir(fs::path const& initial_path){
+ // walk up from the path were we started until we find
+ // bin or bin.v2
+
+ fs::path test_lib_dir = initial_path;
+ do{
+ if(fs::is_directory( test_lib_dir / "bin.v2")){
+ test_lib_dir /= "bin.v2";
+ break;
+ }
+ if(fs::is_directory( test_lib_dir / "bin")){
+ // v1 includes the word boost
+ test_lib_dir /= "bin";
+ if(fs::is_directory( test_lib_dir / "boost")){
+ test_lib_dir /= "boost";
+ }
+ break;
+ }
+ }while(! test_lib_dir.empty());
+
+ if(test_lib_dir.empty())
+ throw std::string("binary path not found");
+
+ return test_lib_dir;
+ }
+
+ string find_lib_name(fs::path lib_test_dir){
+ // search the path backwards for the magic name "libs"
+ fs::path::iterator e_itr = lib_test_dir.end();
+ while(lib_test_dir.begin() != e_itr){
+ if(*--e_itr == "libs")
+ break;
+ }
+
+ // if its found
+ if(lib_test_dir.begin() != e_itr){
+ // use the whole path since the "libs"
+ ++e_itr;
+ }
+ // otherwise, just use the last two components
+ else{
+ e_itr = lib_test_dir.end();
+ if(e_itr != lib_test_dir.begin()){
+ if(--e_itr != lib_test_dir.begin()){
+ --e_itr;
+ }
+ }
+ }
+
+ fs::path library_name;
+ while(lib_test_dir.end() != e_itr){
+ library_name /= *e_itr++;
+ }
+ return library_name.string();
+ }
+
+ fs::path find_boost_root(fs::path initial_path){
+ fs::path boost_root = initial_path;
+ for(;;){
+ if(fs::is_directory( boost_root / "boost")){
+ break;
+ }
+ if(boost_root.empty())
+ throw std::string("boost root not found");
+ boost_root.remove_filename();
+ }
+
+ return boost_root;
+ }
+
+ // do_table ----------------------------------------------------------------//
+ void do_table(const fs::path & lib_test_dir, const string & lib_name)
+ {
+ col_node root_node;
+
+ BOOST_FOREACH(
+ fs::directory_entry & d,
+ std::make_pair(
+ fs::directory_iterator(lib_test_dir),
+ fs::directory_iterator()
+ )
+ ){
+ if(! fs::is_directory(d))
+ continue;
+ fs::path p = d.path();
+ if(p.extension() != ".test")
+ continue;
+ build_node_tree(d, root_node);
+ }
+
+ // visit directory nodes and record nodetree
+ report << "<table border=\"1\" cellspacing=\"0\" cellpadding=\"5\">\n";
+
+ // emit
+ root_node.get_spans();
+ int row_count = header_depth(root_node);
+ report << "<tr>\n";
+ report << "<td rowspan=\"" << row_count << "\">Test Name</td>\n";
+
+ // emit column headers
+ int row_index = 0;
+ for(;;){
+ emit_column_headers(root_node, row_index, 0, row_count);
+ report << "</tr>" ;
+ if(++row_index == row_count)
+ break;
+ report << "<tr>\n";
+ }
+
+ // now the rest of the table body
+ do_table_body(root_node, lib_name, lib_test_dir);
+
+ report << "</table>\n";
+ }
+}// unnamed namespace
+
+// main --------------------------------------------------------------------//
+
+#define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE
+#include <boost/test/included/prg_exec_monitor.hpp>
+
+int cpp_main( int argc, char * argv[] ) // note name!
+{
+ fs::path initial_path = fs::initial_path();
+
+ while ( argc > 1 && *argv[1] == '-' )
+ {
+ if ( argc > 2 && std::strcmp( argv[1], "--compiler" ) == 0 )
+ { specific_compiler = argv[2]; --argc; ++argv; }
+ else if ( argc > 2 && std::strcmp( argv[1], "--locate-root" ) == 0 )
+ { locate_root = fs::path( 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 if ( std::strcmp( argv[1], "--v2" ) == 0 )
+ {--argc; ++argv ;} // skip
+ else if ( argc > 2 && std::strcmp( argv[1], "--jamfile" ) == 0)
+ {--argc; ++argv;} // skip
+ else { std::cerr << "Unknown option: " << argv[1] << "\n"; argc = 1; }
+ --argc;
+ ++argv;
+ }
+
+ if ( argc != 2 && argc != 3 )
+ {
+ std::cerr <<
+ "Usage: library_status [options...] status-file [links-file]\n"
+ " boost-root is the path to the boost tree root directory.\n"
+ " status-file and links-file are paths to the output files.\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"
+ " --locate-root path Path to ALL_LOCATE_TARGET for bjam;\n"
+ " default boost-root.\n"
+ "Example: library_status --compiler gcc /boost-root cs.html cs-links.html\n"
+ "Note: Only the leaf of the links-file path is\n"
+ "used in status-file HTML links. Thus for browsing, status-file,\n"
+ "links-file must be in the same directory.\n"
+ ;
+ return 1;
+ }
+
+ if(locate_root.empty())
+ if(! fs::exists("bin") && ! fs::exists("bin.v2"))
+ locate_root = find_boost_root(initial_path);
+
+ report.open( fs::path( argv[1] ) );
+ if ( !report )
+ {
+ std::cerr << "Could not open report output file: " << argv[2] << std::endl;
+ return 1;
+ }
+
+ if ( argc == 3 )
+ {
+ fs::path links_path( argv[2] );
+ links_name = links_path.filename().string();
+ links_file.open( links_path );
+ if ( !links_file )
+ {
+ std::cerr << "Could not open links output file: " << argv[3] << std::endl;
+ return 1;
+ }
+ }
+ else no_links = true;
+
+ const string library_name = find_lib_name(initial_path);
+
+ 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
+ << "<html>\n"
+ << "<head>\n"
+ << "<title>Boost Library Status Automatic Test</title>\n"
+ << "</head>\n"
+ << "<body bgcolor=\"#ffffff\" text=\"#000000\">\n"
+ << "<table border=\"0\">\n"
+ << "<h1>Library Status: " + library_name + "</h1>\n"
+ << "<b>Run Date:</b> "
+ << run_date
+ << "\n<br>"
+ ;
+
+ report << "</td>\n</table>\n<br>\n";
+
+ if ( !no_links )
+ {
+ links_file
+ << "<html>\n"
+ << "<head>\n"
+ << "<title>Boost Library Status Error Log</title>\n"
+ << "</head>\n"
+ << "<body bgcolor=\"#ffffff\" text=\"#000000\">\n"
+ << "<table border=\"0\">\n"
+ << "<h1>Library Status: " + library_name + "</h1>\n"
+ << "<b>Run Date:</b> "
+ << run_date
+ << "\n<br></table>\n<br>\n"
+ ;
+ }
+
+ // detect whether in a a directory which looks like
+ // bin/<library name>/test
+ // or just
+ // bin
+ fs::path library_test_directory = find_lib_test_dir(locate_root);
+ // if libs exists, drop down a couple of levels
+ if(fs::is_directory( library_test_directory / "libs")){
+ library_test_directory /= "libs";
+ library_test_directory /= library_name;
+ }
+
+ do_table(library_test_directory, library_name);
+
+ report << "</body>\n"
+ "</html>\n"
+ ;
+
+ if ( !no_links )
+ {
+ links_file
+ << "</body>\n"
+ "</html>\n"
+ ;
+ }
+ return 0;
+}