mirror of
https://github.com/boostorg/regression.git
synced 2026-01-19 04:42:10 +00:00
to its own subdir. Move man index docs to root. Fix main build file to build release versions of libs for performance.
1137 lines
37 KiB
C++
1137 lines
37 KiB
C++
// Generate Compiler Status HTML from jam regression test output -----------//
|
|
|
|
// Copyright Beman Dawes 2002.
|
|
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// See http://www.boost.org/LICENSE_1_0.txt
|
|
|
|
// See http://www.boost.org/tools/regression/ for documentation.
|
|
|
|
/*******************************************************************************
|
|
|
|
Please contact the maintainer, bdawes <at> acm <dot> org, before making
|
|
any non-trivial changes.
|
|
|
|
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/config/warning_disable.hpp>
|
|
|
|
#include <boost/config.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/filesystem/convenience.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
#include "tiny_xml.hpp"
|
|
#include "common.hpp"
|
|
namespace fs = boost::filesystem;
|
|
namespace xml = boost::tiny_xml;
|
|
|
|
#include <cstdlib> // for abort, exit
|
|
#include <cctype> // for toupper, isdigit
|
|
#include <string>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <ctime>
|
|
#include <stdexcept>
|
|
#include <cassert>
|
|
|
|
#include <stdio.h> // for popen, pclose
|
|
#if defined(_MSC_VER)
|
|
# define POPEN _popen
|
|
# define PCLOSE _pclose
|
|
#else
|
|
# define POPEN popen
|
|
# define PCLOSE pclose
|
|
#endif
|
|
|
|
#include <boost/detail/lightweight_main.hpp>
|
|
|
|
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 note_msg( "<sup>*</sup>" );
|
|
const string missing_residue_msg( "<i>Missing</i>" );
|
|
|
|
const std::size_t max_compile_msg_size = 10000;
|
|
|
|
namespace
|
|
{
|
|
fs::path boost_root; // boost-root complete path
|
|
fs::path locate_root; // locate-root (AKA ALL_LOCATE_TARGET) complete path
|
|
|
|
bool compile_time;
|
|
bool run_time;
|
|
bool ignore_pass;
|
|
bool no_warn;
|
|
bool no_links;
|
|
bool boost_build_v2 = true;
|
|
|
|
fs::path jamfile_path;
|
|
|
|
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<string> toolsets;
|
|
|
|
fs::ifstream jamfile;
|
|
fs::ofstream report;
|
|
fs::ofstream links_file;
|
|
string links_name;
|
|
|
|
fs::path notes_path;
|
|
string notes_html;
|
|
|
|
fs::path notes_map_path;
|
|
typedef std::multimap< string, string > notes_map; // key is test_name-toolset,
|
|
// value is note bookmark
|
|
notes_map notes;
|
|
|
|
string specific_compiler; // if running on one toolset only
|
|
|
|
const string empty_string;
|
|
|
|
std::vector<int> error_count;
|
|
|
|
// prefix for library and test hyperlink prefix
|
|
string svn_root ( "http://svn.boost.org/trac/boost/browser/trunk/" );
|
|
string url_prefix_dir_view( svn_root );
|
|
string url_prefix_checkout_view( svn_root );
|
|
string url_suffix_text_view( "" );
|
|
|
|
// build notes_bookmarks from notes HTML -----------------------------------//
|
|
|
|
void build_notes_bookmarks()
|
|
{
|
|
if ( notes_map_path.empty() ) return;
|
|
fs::ifstream notes_map_file( notes_map_path );
|
|
if ( !notes_map_file )
|
|
{
|
|
std::cerr << "Could not open --notes-map input file: " << notes_map_path.string() << std::endl;
|
|
std::exit( 1 );
|
|
}
|
|
string line;
|
|
while( std::getline( notes_map_file, line ) )
|
|
{
|
|
string::size_type pos = 0;
|
|
if ( (pos = line.find( ',', pos )) == string::npos ) continue;
|
|
string key(line.substr( 0, pos ) );
|
|
string bookmark( line.substr( pos+1 ) );
|
|
|
|
// std::cout << "inserting \"" << key << "\",\"" << bookmark << "\"\n";
|
|
notes.insert( notes_map::value_type( key, bookmark ) );
|
|
}
|
|
}
|
|
|
|
// load_notes_html ---------------------------------------------------------//
|
|
|
|
bool load_notes_html()
|
|
{
|
|
if ( notes_path.empty() ) return false;
|
|
fs::ifstream notes_file( notes_path );
|
|
if ( !notes_file )
|
|
{
|
|
std::cerr << "Could not open --notes input file: " << notes_path.string() << std::endl;
|
|
std::exit( 1 );
|
|
}
|
|
string line;
|
|
bool in_body( false );
|
|
while( std::getline( notes_file, line ) )
|
|
{
|
|
if ( in_body && line.find( "</body>" ) != string::npos ) in_body = false;
|
|
if ( in_body ) notes_html += line;
|
|
else if ( line.find( "<body>" ) ) in_body = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// relative path between two paths -----------------------------------------//
|
|
|
|
void relative_path( const fs::path & from, const fs::path & to,
|
|
fs::path & target )
|
|
{
|
|
if ( from.string().size() <= to.string().size() ) return;
|
|
target /= "..";
|
|
relative_path( from.branch_path(), to, target );
|
|
return;
|
|
}
|
|
|
|
// 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_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->path().filename() != ignore_dir_named )
|
|
{
|
|
if ( find_file( *itr, name, path_found ) ) return true;
|
|
}
|
|
else if ( itr->path().filename() == name )
|
|
{
|
|
path_found = *itr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// platform_desc -----------------------------------------------------------//
|
|
|
|
string platform_desc()
|
|
{
|
|
string result = BOOST_PLATFORM;
|
|
result[0] = std::toupper( result[0] );
|
|
return result;
|
|
}
|
|
|
|
// version_desc ------------------------------------------------------------//
|
|
// from locate-root/status/bin/config_info.test/xxx/.../config_info.output
|
|
|
|
string version_desc( const string & compiler_name )
|
|
{
|
|
string result;
|
|
fs::path dot_output_path;
|
|
if ( find_file( locate_root / "bin/boost/status/config_info.test"
|
|
/ compiler_name, "config_info.output", dot_output_path )
|
|
|| find_file( locate_root / "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 string & compiler_name )
|
|
{
|
|
string result;
|
|
fs::path tools_path( boost_root / "tools/build/v1" / (compiler_name
|
|
+ "-tools.jam") );
|
|
if ( !fs::exists( tools_path ) )
|
|
tools_path = boost_root / "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;
|
|
}
|
|
|
|
// 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 ) )
|
|
{
|
|
// SunCC creates an internal subdirectory everywhere it writes
|
|
// object files. This confuses the target_directory() algorithm.
|
|
// This patch ignores the SunCC internal directory. Jens Maurer
|
|
if ( itr->path().filename() == "SunWS_cache" ) continue;
|
|
// SGI does something similar for template instantiations. Jens Maurer
|
|
if( itr->path().filename() == "ii_files" ) continue;
|
|
|
|
if ( child.empty() ) child = *itr;
|
|
else
|
|
{
|
|
std::cout << "Warning: only first of two target possibilities will be reported for: \n "
|
|
<< root.string() << ": " << child.filename()
|
|
<< " and " << itr->path().filename() << "\n";
|
|
}
|
|
}
|
|
}
|
|
if ( child.empty() ) return root; // this dir has no children
|
|
return target_directory( child );
|
|
}
|
|
|
|
// element_content ---------------------------------------------------------//
|
|
|
|
const string & element_content(
|
|
const xml::element & root, const string & name )
|
|
{
|
|
static string empty_string;
|
|
xml::element_list::const_iterator itr;
|
|
for ( itr = root.elements.begin();
|
|
itr != root.elements.end() && (*itr)->name != name;
|
|
++itr ) {}
|
|
return itr != root.elements.end() ? (*itr)->content : empty_string;
|
|
}
|
|
|
|
// find_element ------------------------------------------------------------//
|
|
|
|
const xml::element empty_element;
|
|
|
|
const xml::element & find_element(
|
|
const xml::element & root, const string & name )
|
|
{
|
|
xml::element_list::const_iterator itr;
|
|
for ( itr = root.elements.begin();
|
|
itr != root.elements.end() && (*itr)->name != name;
|
|
++itr ) {}
|
|
if (itr == root.elements.end())
|
|
std::cout << "did not find element " << name << std::endl;
|
|
return itr != root.elements.end() ? *((*itr).get()) : empty_element;
|
|
}
|
|
|
|
// attribute_value ----------------------------------------------------------//
|
|
|
|
const string & attribute_value( const xml::element & element,
|
|
const string & attribute_name )
|
|
{
|
|
static const string empty_string;
|
|
xml::attribute_list::const_iterator atr;
|
|
for ( atr = element.attributes.begin();
|
|
atr != element.attributes.end() && atr->name != attribute_name;
|
|
++atr ) {}
|
|
if (atr == element.attributes.end())
|
|
std::cout << "did not find attribute " << attribute_name << std::endl;
|
|
return atr == element.attributes.end() ? empty_string : atr->value;
|
|
}
|
|
|
|
// find_bin_path -----------------------------------------------------------//
|
|
|
|
// Takes a relative path from boost root to a Jamfile.
|
|
// Returns the directory where the build targets from
|
|
// that Jamfile are located. If not found, emits a warning
|
|
// and returns empty path.
|
|
const fs::path find_bin_path(const string& relative)
|
|
{
|
|
fs::path bin_path;
|
|
if (boost_build_v2)
|
|
{
|
|
if ( relative == "status" )
|
|
bin_path = locate_root / "bin.v2" / "libs";
|
|
else
|
|
{
|
|
bin_path = locate_root / "bin.v2" / relative;
|
|
if (!fs::exists(bin_path))
|
|
bin_path = locate_root / "bin" / relative;
|
|
}
|
|
if (!fs::exists(bin_path))
|
|
{
|
|
std::cerr << "warning: could not find build results for '"
|
|
<< relative << "'.\n";
|
|
std::cerr << "warning: tried directory "
|
|
<< bin_path.string() << "\n";
|
|
bin_path = "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bin_path = locate_root / "bin/boost" / relative;
|
|
if (!fs::exists(bin_path))
|
|
{
|
|
bin_path = locate_root / "bin" / relative / "bin";
|
|
if (!fs::exists(bin_path))
|
|
{
|
|
bin_path = fs::path( locate_root / relative / "bin" );
|
|
if (!fs::exists(bin_path))
|
|
{
|
|
bin_path = fs::path( locate_root / "bin/boost/libs" /
|
|
relative.substr( relative.find( '/' )+1 ) );
|
|
}
|
|
}
|
|
}
|
|
if (!fs::exists(bin_path))
|
|
{
|
|
std::cerr << "warning: could not find build results for '"
|
|
<< relative << "'.\n";
|
|
bin_path = "";
|
|
}
|
|
}
|
|
return bin_path;
|
|
}
|
|
|
|
|
|
// generate_report ---------------------------------------------------------//
|
|
|
|
// return 0 if nothing generated, 1 otherwise, except 2 if compiler msgs
|
|
int generate_report( const xml::element & db,
|
|
const string & source_library_name,
|
|
const string & test_type,
|
|
const string & test_name, // possibly object library 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" )) );
|
|
|
|
string::size_type pos;
|
|
if ( (pos = compile.find("30 DAY EVALUATION LICENSE")) != string::npos )
|
|
{
|
|
compile.erase(pos, 25);
|
|
while ( compile[0] == '\n' || compile[0] == '\r' ) compile.erase(0,1);
|
|
}
|
|
|
|
// 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.
|
|
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";
|
|
}
|
|
|
|
links_file << "<h2><a name=\""
|
|
<< source_library_name << "-" << test_name << "-" << toolset << "\">"
|
|
<< source_library_name << " - " << test_name << " - " << toolset << "</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 << "-" << toolset << "\">"
|
|
<< source_library_name << " - "
|
|
<< object_library_name << " - " << toolset << "</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, object_library_name, toolset, false );
|
|
}
|
|
else
|
|
{
|
|
links_file << "<h2><a name=\""
|
|
<< object_library_name << "-" << toolset << "\">"
|
|
<< object_library_name << " - " << toolset << "</a></h2>\n"
|
|
"test_log.xml not found\n";
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// add_notes --------------------------------------------------------------//
|
|
|
|
void add_notes( const string & key, bool fail, string & sep, string & target )
|
|
{
|
|
notes_map::const_iterator itr = notes.lower_bound( key );
|
|
if ( itr != notes.end() && itr->first == key )
|
|
{
|
|
for ( ; itr != notes.end() && itr->first == key; ++itr )
|
|
{
|
|
string note_desc( itr->second[0] == '-'
|
|
? itr->second.substr( 1 ) : itr->second );
|
|
if ( fail || itr->second[0] == '-' )
|
|
{
|
|
target += sep;
|
|
sep = ",";
|
|
target += "<a href=\"";
|
|
target += "#";
|
|
target += note_desc;
|
|
target += "\">";
|
|
target += note_desc;
|
|
target += "</a>";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get_notes -------------------------------------------------------------//
|
|
|
|
string get_notes( const string & toolset,
|
|
const string & library, const string & test, bool fail )
|
|
{
|
|
string sep;
|
|
string target( "<sup>" );
|
|
add_notes( toolset + "/" + library + "/" + test, fail, sep, target );
|
|
add_notes( "*/" + library + "/" + test, fail, sep, target );
|
|
add_notes( toolset + "/" + library + "/*", fail, sep, target );
|
|
add_notes( "*/" + library + "/*", fail, sep, target );
|
|
if ( target == "<sup>" ) target.clear();
|
|
else target += "</sup>";
|
|
return target;
|
|
}
|
|
|
|
// do_cell ---------------------------------------------------------------//
|
|
|
|
bool do_cell(
|
|
int compiler,
|
|
const string & lib_name,
|
|
const fs::path & test_dir,
|
|
const string & test_type,
|
|
const string & test_name,
|
|
const string & toolset,
|
|
string & target,
|
|
bool always_show_run_output )
|
|
// return true if any results except simple pass_msg
|
|
{
|
|
fs::path target_dir( target_directory( test_dir / toolset ) );
|
|
bool pass = false;
|
|
|
|
if ( !fs::exists( target_dir / "test_log.xml" ) )
|
|
{
|
|
std::cerr << "Missing test_log.xml in target:\n "
|
|
<< target_dir.string() << "\n";
|
|
target += "<td>" + missing_residue_msg + "</td>";
|
|
return true;
|
|
}
|
|
|
|
int anything_generated = 0;
|
|
bool note = false;
|
|
|
|
fs::path pth( target_dir / "test_log.xml" );
|
|
fs::ifstream file( pth );
|
|
if ( !file )
|
|
{
|
|
std::cerr << "Can't open test_log.xml in target:\n "
|
|
<< target_dir.string() << "\n";
|
|
target += "<td>" + missing_residue_msg + "</td>";
|
|
return false;
|
|
}
|
|
|
|
xml::element_ptr dbp = xml::parse( file, pth.string() );
|
|
const xml::element & db( *dbp );
|
|
|
|
std::string test_type_base( test_type );
|
|
if ( test_type_base == "run_pyd" ) test_type_base = "run";
|
|
else if ( test_type_base.size() > 5 )
|
|
{
|
|
const string::size_type trailer = test_type_base.size() - 5;
|
|
if ( test_type_base.substr( trailer ) == "_fail" )
|
|
{
|
|
test_type_base.erase( trailer );
|
|
}
|
|
}
|
|
const xml::element & test_type_element( find_element( db, test_type_base ) );
|
|
|
|
pass = !test_type_element.name.empty()
|
|
&& attribute_value( test_type_element, "result" ) != "fail";
|
|
|
|
std::string revision = attribute_value(db, "revision");
|
|
|
|
if ( !no_links )
|
|
{
|
|
note = attribute_value( test_type_element, "result" ) == "note";
|
|
|
|
// generate bookmarked report of results, and link to it
|
|
anything_generated
|
|
= generate_report( db, lib_name, test_type, test_name, toolset, pass,
|
|
always_show_run_output || note );
|
|
}
|
|
|
|
target += "<td>";
|
|
|
|
// generate the status table cell pass/warn/fail HTML
|
|
if ( anything_generated != 0 )
|
|
{
|
|
target += "<a href=\"";
|
|
target += links_name;
|
|
target += "#";
|
|
target += lib_name;
|
|
target += "-";
|
|
target += test_name;
|
|
target += "-";
|
|
target += toolset;
|
|
target += "\">";
|
|
target += pass
|
|
? (anything_generated < 2 ? pass_msg : warn_msg)
|
|
: fail_msg;
|
|
target += "</a>";
|
|
if ( pass && note ) target += note_msg;
|
|
}
|
|
else target += pass ? pass_msg : fail_msg;
|
|
|
|
if (!revision.empty())
|
|
{
|
|
target += "<br><font size=\"2\">";
|
|
target += revision;
|
|
target += "</font>";
|
|
}
|
|
|
|
// if notes, generate the superscript HTML
|
|
if ( !notes.empty() )
|
|
target += get_notes( toolset, lib_name, test_name, !pass );
|
|
|
|
// generate compile-time if requested
|
|
if ( compile_time )
|
|
{
|
|
const xml::element & compile_element( find_element( db, "compile" ) );
|
|
|
|
if ( !compile_element.name.empty() )
|
|
{
|
|
string times = attribute_value( compile_element, "timings" );
|
|
if ( !times.empty() )
|
|
{
|
|
target += "<br>";
|
|
target += times.substr( 0, times.find( " " ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// generate run-time if requested
|
|
if ( run_time )
|
|
{
|
|
const xml::element & run_element( find_element( db, "run" ) );
|
|
|
|
if ( !run_element.name.empty() )
|
|
{
|
|
string times = attribute_value( run_element, "timings" );
|
|
if ( !times.empty() )
|
|
{
|
|
target += "<br>";
|
|
target += times.substr( 0, times.find( " " ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pass ) ++error_count[compiler];
|
|
|
|
target += "</td>";
|
|
return (anything_generated != 0) || !pass;
|
|
}
|
|
|
|
// do_row ------------------------------------------------------------------//
|
|
|
|
void do_row(
|
|
const fs::path & test_dir, // locate_root / "status/bin/any_test.test"
|
|
const string & test_name, // "any_test"
|
|
string & target )
|
|
{
|
|
// get library name, test-type, test-program path, etc., from the .xml file
|
|
string lib_name;
|
|
string test_path( test_name ); // test_name is default if missing .test
|
|
string test_type( "unknown" );
|
|
bool always_show_run_output( false );
|
|
fs::path xml_file_path;
|
|
if ( find_file( test_dir, "test_log.xml", xml_file_path ) )
|
|
{
|
|
fs::ifstream file( xml_file_path );
|
|
if ( file )
|
|
{
|
|
xml::element_ptr dbp = xml::parse( file, xml_file_path.string() );
|
|
const xml::element & db( *dbp );
|
|
test_path = attribute_value( db, "test-program" );
|
|
lib_name = attribute_value( db, "library" );
|
|
test_type = attribute_value( db, "test-type" );
|
|
always_show_run_output
|
|
= attribute_value( db, "show-run-output" ) == "true";
|
|
}
|
|
}
|
|
|
|
// generate the library name, test name, and test type table data
|
|
string::size_type row_start_pos = target.size();
|
|
target += "<tr><td><a href=\"" + url_prefix_dir_view + "/libs/" + lib_name
|
|
+ "\">" + lib_name + "</a></td>";
|
|
target += "<td><a href=\"" + url_prefix_checkout_view + "/" + test_path
|
|
+ url_suffix_text_view + "\">" + test_name + "</a>";
|
|
|
|
if ( compile_time ) target += "<br> Compile time:";
|
|
if ( run_time ) target += "<br> Run time:";
|
|
|
|
target += "</td>";
|
|
target += "<td>" + test_type + "</td>";
|
|
|
|
bool no_warn_save = no_warn;
|
|
//if ( test_type.find( "fail" ) != string::npos ) no_warn = true;
|
|
|
|
// for each compiler, generate <td>...</td> html
|
|
bool anything_to_report = false;
|
|
int compiler = 0;
|
|
for ( std::vector<string>::const_iterator itr=toolsets.begin();
|
|
itr != toolsets.end(); ++itr, ++compiler )
|
|
{
|
|
anything_to_report |= do_cell( compiler, lib_name, test_dir, test_type, test_name, *itr, target,
|
|
always_show_run_output );
|
|
}
|
|
|
|
target += "</tr>";
|
|
if ( ignore_pass && !anything_to_report ) target.erase( row_start_pos );
|
|
no_warn = no_warn_save;
|
|
}
|
|
|
|
// do_rows_for_sub_tree ----------------------------------------------------//
|
|
|
|
void do_rows_for_sub_tree(
|
|
const fs::path & bin_dir, std::vector<string> & results )
|
|
{
|
|
for ( fs::directory_iterator itr( bin_dir ); itr != end_itr; ++itr )
|
|
{
|
|
if ( fs::is_directory( *itr )
|
|
&& itr->path().string().find( ".test" ) == (itr->path().string().size()-5) )
|
|
{
|
|
results.push_back( std::string() );
|
|
do_row( *itr,
|
|
itr->path().filename().string().substr( 0,
|
|
itr->path().filename().string().size()-5 ),
|
|
results[results.size()-1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// find_compilers ------------------------------------------------------------//
|
|
|
|
void find_compilers(const fs::path & bin_dir)
|
|
{
|
|
fs::directory_iterator compiler_itr( bin_dir );
|
|
if ( specific_compiler.empty() )
|
|
std::clog << "Using " << bin_dir.string() << " to determine compilers\n";
|
|
for (; compiler_itr != end_itr; ++compiler_itr )
|
|
{
|
|
if ( fs::is_directory( *compiler_itr ) // check just to be sure
|
|
&& compiler_itr->path().filename() != "test" ) // avoid strange directory (Jamfile bug?)
|
|
{
|
|
if ( specific_compiler.size() != 0
|
|
&& specific_compiler != compiler_itr->path().filename() ) continue;
|
|
toolsets.push_back( compiler_itr->path().filename().string() );
|
|
string desc( compiler_desc( compiler_itr->path().filename().string() ) );
|
|
string vers( version_desc( compiler_itr->path().filename().string() ) );
|
|
report << "<td>"
|
|
<< (desc.size() ? desc : compiler_itr->path().filename().string())
|
|
<< (vers.size() ? (string( "<br>" ) + vers ) : string( "" ))
|
|
<< "</td>\n";
|
|
error_count.push_back( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// do_table_body -----------------------------------------------------------//
|
|
|
|
void do_table_body( const fs::path & bin_dir )
|
|
{
|
|
// rows are held in a vector so they can be sorted, if desired.
|
|
std::vector<string> results;
|
|
|
|
// do primary bin directory
|
|
do_rows_for_sub_tree( bin_dir, results );
|
|
|
|
// do subinclude bin directories
|
|
jamfile.clear();
|
|
jamfile.seekg(0);
|
|
string line;
|
|
bool run_tests = false;
|
|
|
|
while( std::getline( jamfile, line ) )
|
|
{
|
|
bool v2(false);
|
|
string::size_type sub_pos( line.find( "subinclude" ) );
|
|
if ( sub_pos == string::npos ) {
|
|
sub_pos = line.find( "build-project" );
|
|
v2 = true;
|
|
}
|
|
if ( sub_pos != string::npos
|
|
&& line.find( '#' ) > sub_pos )
|
|
{
|
|
if (v2)
|
|
sub_pos = line.find_first_not_of( " \t./", sub_pos+13 );
|
|
else
|
|
sub_pos = line.find_first_not_of( " \t./", sub_pos+10 );
|
|
|
|
if ( sub_pos == string::npos ) continue;
|
|
string subinclude_bin_dir(
|
|
line.substr( sub_pos, line.find_first_of( " \t", sub_pos )-sub_pos ) );
|
|
|
|
fs::path bin_path = find_bin_path(subinclude_bin_dir);
|
|
if (!bin_path.empty())
|
|
do_rows_for_sub_tree( bin_path, results );
|
|
}
|
|
if ( ! run_tests )
|
|
{
|
|
string::size_type run_pos = line.find("run-tests");
|
|
if ( run_pos != string::npos && line.find_first_not_of(" \t") == run_pos )
|
|
run_tests = true;
|
|
}
|
|
else
|
|
{
|
|
if ( line.find(";") != string::npos )
|
|
run_tests = false;
|
|
else
|
|
{
|
|
string::size_type pos = line.find_first_not_of( " \t" );
|
|
if ( pos != string::npos && line[pos] != '#' )
|
|
{
|
|
string::size_type end_pos = line.find_first_of(" \t#", pos);
|
|
string::iterator end = end_pos != string::npos ? line.begin() + end_pos : line.end();
|
|
string run_tests_bin_dir(line.begin() + pos, end);
|
|
fs::path bin_path = find_bin_path("libs/" + run_tests_bin_dir);
|
|
if (!bin_path.empty())
|
|
do_rows_for_sub_tree( bin_path, results );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::sort( results.begin(), results.end() );
|
|
|
|
for ( std::vector<string>::iterator v(results.begin());
|
|
v != results.end(); ++v )
|
|
{ report << *v << "\n"; }
|
|
}
|
|
|
|
// do_table ----------------------------------------------------------------//
|
|
|
|
void do_table()
|
|
{
|
|
// Find test result locations, trying:
|
|
// - Boost.Build V1 location with ALL_LOCATE_TARGET
|
|
// - Boost.Build V2 location with top-level "build-dir"
|
|
// - Boost.Build V1 location without ALL_LOCATE_TARGET
|
|
string relative( fs::initial_path().string() );
|
|
|
|
#ifdef BOOST_WINDOWS_API
|
|
if (relative.size() > 1 && relative[1] == ':') relative[0] = std::tolower(relative[0]);
|
|
#endif
|
|
|
|
if ( relative.find(boost_root.string()) != string::npos )
|
|
relative.erase( 0, boost_root.string().size()+1 );
|
|
else if ( relative.find(locate_root.string()) != string::npos )
|
|
relative.erase( 0, locate_root.string().size()+1 );
|
|
fs::path bin_path = find_bin_path(relative);
|
|
|
|
report << "<table border=\"1\" cellspacing=\"0\" cellpadding=\"5\">\n";
|
|
|
|
// generate the column headings
|
|
|
|
report << "<tr><td>Library</td><td>Test Name</td>\n"
|
|
"<td><a href=\"compiler_status.html#test-type\">Test Type</a></td>\n";
|
|
|
|
if ( relative == "status" )
|
|
{
|
|
fs::recursive_directory_iterator ritr( bin_path );
|
|
fs::recursive_directory_iterator end_ritr;
|
|
while ( ritr != end_ritr
|
|
&& ((ritr->path().string().find( ".test" ) != (ritr->path().string().size()-5))
|
|
|| !fs::is_directory( *ritr )))
|
|
++ritr; // bypass chaff
|
|
if ( ritr != end_ritr )
|
|
{
|
|
find_compilers( *ritr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fs::directory_iterator itr( bin_path );
|
|
while ( itr != end_itr
|
|
&& ((itr->path().string().find( ".test" ) != (itr->path().string().size()-5))
|
|
|| !fs::is_directory( *itr )))
|
|
++itr; // bypass chaff
|
|
if ( itr != end_itr )
|
|
{
|
|
find_compilers( *itr );
|
|
}
|
|
}
|
|
|
|
report << "</tr>\n";
|
|
|
|
// now the rest of the table body
|
|
|
|
do_table_body( bin_path );
|
|
|
|
// error total row
|
|
|
|
report << "<tr> <td> </td><td>Number of Failures</td><td> </td>\n";
|
|
|
|
// for each compiler, generate <td>...</td> html
|
|
int compiler = 0;
|
|
for ( std::vector<string>::const_iterator itr=toolsets.begin();
|
|
itr != toolsets.end(); ++itr, ++compiler )
|
|
{
|
|
report << "<td align=\"center\">" << error_count[compiler] << "</td>\n";
|
|
}
|
|
|
|
report << "</tr>\n</table>\n";
|
|
}
|
|
|
|
} // unnamed namespace
|
|
|
|
// main --------------------------------------------------------------------//
|
|
|
|
int cpp_main( int argc, char * argv[] ) // note name!
|
|
{
|
|
fs::initial_path();
|
|
fs::path comment_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 ( argc > 2 && std::strcmp( argv[1], "--comment" ) == 0 )
|
|
{ comment_path = fs::path( argv[2] ); --argc; ++argv; }
|
|
else if ( argc > 2 && std::strcmp( argv[1], "--notes" ) == 0 )
|
|
{ notes_path = fs::path( argv[2] ); --argc; ++argv; }
|
|
else if ( argc > 2 && std::strcmp( argv[1], "--notes-map" ) == 0 )
|
|
{ notes_map_path = 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], "--v1" ) == 0 ) boost_build_v2 = false;
|
|
else if ( std::strcmp( argv[1], "--v2" ) == 0 ) boost_build_v2 = true;
|
|
else if ( argc > 2 && std::strcmp( argv[1], "--jamfile" ) == 0)
|
|
{ jamfile_path = fs::path( argv[2] ); --argc; ++argv; }
|
|
else if ( std::strcmp( argv[1], "--compile-time" ) == 0 ) compile_time = true;
|
|
else if ( std::strcmp( argv[1], "--run-time" ) == 0 ) run_time = 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-tree status-file [links-file]\n"
|
|
" boost-tree is a path within a boost directory tree; may be a period.\n"
|
|
" status-file and links-file are paths to the output files.\n"
|
|
"Must be run from directory containing Jamfile\n"
|
|
" options: --compiler name Run for named compiler only\n"
|
|
" --locate-root path Path to ALL_LOCATE_TARGET for bjam;\n"
|
|
" default boost-root.\n"
|
|
" --comment path Path to file containing HTML\n"
|
|
" to be copied into status-file.\n"
|
|
" --notes path Path to file containing HTML\n"
|
|
" to be copied into status-file.\n"
|
|
" --notes-map path Path to file of toolset/test,n lines, where\n"
|
|
" n is number of note bookmark in --notes file.\n"
|
|
" --jamfile path Path to Jamfile. By default \"Jamfile\".\n"
|
|
" --v1 Assume Boost.Build version 1.\n"
|
|
" --v2 Assume Boost.Build version 2. (default)\n"
|
|
" --ignore-pass Ignore passing tests.\n"
|
|
" --no-warn Do not report warnings.\n"
|
|
" --compile-time Show compile time.\n"
|
|
" --run-time Show run time.\n"
|
|
"Example: compiler_status --compiler gcc . cs.html cs-links.html\n"
|
|
"Note: Only the leaf of the links-file path and --notes file string are\n"
|
|
"used in status-file HTML links. Thus for browsing, status-file,\n"
|
|
"links-file, and --notes file must all be in the same directory.\n"
|
|
;
|
|
return 1;
|
|
}
|
|
|
|
boost_root = boost::regression_tools::boost_root_path( fs::absolute( argv[1] ));
|
|
#ifdef BOOST_WINDOWS_API
|
|
// normalize drive letter to lowercase so later string compares are case independent
|
|
std::string root_string = boost_root.string();
|
|
if (root_string.size() > 1 && root_string[1] == ':')
|
|
{
|
|
root_string[0] = std::tolower(root_string[0]);
|
|
boost_root = root_string;
|
|
}
|
|
#endif
|
|
std::cout << "boost-root: " << boost_root << std::endl;
|
|
|
|
if ( boost_root.empty() )
|
|
{
|
|
std::cerr << "error: boost-root argument \"" << argv[1]
|
|
<< "\" is not within a boost directory tree\n";
|
|
return 1;
|
|
}
|
|
|
|
if ( locate_root.empty() ) locate_root = boost_root;
|
|
|
|
if (jamfile_path.empty())
|
|
if (boost_build_v2)
|
|
jamfile_path = "Jamfile.v2";
|
|
else
|
|
jamfile_path = "Jamfile";
|
|
jamfile_path = fs::absolute( jamfile_path, fs::initial_path() );
|
|
jamfile.open( jamfile_path );
|
|
if ( !jamfile )
|
|
{
|
|
std::cerr << "Could not open Jamfile: " << jamfile_path.string() << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
report.open( fs::path( argv[2] ) );
|
|
if ( !report )
|
|
{
|
|
std::cerr << "Could not open report output file: " << argv[2] << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if ( argc == 4 )
|
|
{
|
|
fs::path links_path( argv[3] );
|
|
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;
|
|
|
|
build_notes_bookmarks();
|
|
|
|
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 ) );
|
|
|
|
// std::string rev = revision( boost_root );
|
|
|
|
report << "<html>\n"
|
|
"<head>\n"
|
|
"<title>Boost Test Results</title>\n"
|
|
"</head>\n"
|
|
"<body bgcolor=\"#ffffff\" text=\"#000000\">\n"
|
|
"<table border=\"0\">\n"
|
|
"<tr>\n"
|
|
"<td><img border=\"0\" src=\"http://www.boost.org/boost.png\" width=\"277\" "
|
|
"height=\"86\"></td>\n"
|
|
"<td>\n"
|
|
"<h1>Boost Test Results - " + platform_desc() + "</h1>\n"
|
|
"<b>Run</b> "
|
|
<< run_date;
|
|
//if ( !rev.empty() ) report << ", <b>Revision</b> " << rev;
|
|
report << "\n";
|
|
|
|
|
|
if ( compile_time )
|
|
report << "<p>Times reported are elapsed wall clock time in seconds.</p>\n";
|
|
|
|
|
|
if ( !comment_path.empty() )
|
|
{
|
|
fs::ifstream comment_file( comment_path );
|
|
if ( !comment_file )
|
|
{
|
|
std::cerr << "Could not open \"--comment\" input file: " << comment_path.string() << std::endl;
|
|
return 1;
|
|
}
|
|
char c;
|
|
while ( comment_file.get( c ) ) { report.put( c ); }
|
|
}
|
|
|
|
report << "</td>\n</table>\n<br>\n";
|
|
|
|
if ( !no_links )
|
|
{
|
|
links_file
|
|
<< "<html>\n"
|
|
"<head>\n"
|
|
"<title>Boost Test Details</title>\n"
|
|
"</head>\n"
|
|
"<body bgcolor=\"#ffffff\" text=\"#000000\">\n"
|
|
"<table border=\"0\">\n"
|
|
"<tr>\n"
|
|
"<td><img border=\"0\" src=\"http://www.boost.org/boost.png\" width=\"277\" "
|
|
"height=\"86\"></td>\n"
|
|
"<td>\n"
|
|
"<h1>Boost Test Details - " + platform_desc() + "</h1>\n"
|
|
"<b>Run Date:</b> "
|
|
<< run_date;
|
|
//if ( !rev.empty() ) links_file << ", <b>Revision</b> " << rev;
|
|
links_file << "\n</td>\n</table>\n<br>\n";
|
|
}
|
|
|
|
do_table();
|
|
|
|
if ( load_notes_html() ) report << notes_html << "\n";
|
|
|
|
report << "</body>\n"
|
|
"</html>\n"
|
|
;
|
|
|
|
if ( !no_links )
|
|
{
|
|
links_file
|
|
<< "</body>\n"
|
|
"</html>\n"
|
|
;
|
|
}
|
|
return 0;
|
|
}
|