#!/usr/bin/perl

# noddy script to generate html summary page of logs

use strict;
use warnings;
use diagnostics;
use POSIX qw(strftime);
use Getopt::Long;

use vars qw($package $version $arch $date $datestring $buildstate $message $summarystate 
       $today $todayreadable $now %loglist $logname @packages $buildok $unneeded $builddeps $sourcefail $buildfail $packagecount 
       %pkgfilter $pkgfilter $pkg $pkgstatus $logregex $buildtime);

# allow specifying a (reprepro) package filter 
GetOptions( "pkgfilter=s" => \$pkgfilter); # --pkgfilter=filename

if ($pkgfilter && -r $pkgfilter) {
    open (FILTER,"<", "$pkgfilter") or die "Can't open $pkgfilter: $!";
    while (<FILTER>) {
	chomp;
	($pkg, $pkgstatus)=split;
	$pkgfilter{$pkg}=1;
    }
    close FILTER;
}


#my ($day, $month, $year) = (localtime)[3,4,5];
#my $today = sprintf("%04d%02d%02d", $year+1900, $month+1, $day);
my $today = strftime( "%Y%m%d", localtime);
my $todayreadable = readabledate($today);

# regex to cope with rebuildd's annoying log-file naming scheme with 
# dash separators and dashes in version strings and distronames.
# dashes in version are always followed by a digit
# reponames always start with a letter 
my $logregex = qr/^  # start 
      ([\S]+)       # package ($1)
      _                  # package_ver separator
      (                  # version number ($2)
	  (
	   [:\w\d\.\+~]  # chars in version strings
	   |             # or 
	   -(?=\d)       # a dash with a digit after it ($3, annoyingly)
	  )+             # at least one character
      )     
      -                  # dash separator
      ([-\w]+)           # repo name (including dashes) $4
      -                  # dash separator
      (\w+)              # architecture $5
      -                  # dash separator
      (\d{8})            # date (20120522) $6
      -                  # dash separator
      (\d\d)(\d\d).*     # hours and minutes $7 $8
      $                  # end 
      /x ;              # (allowing multiline comments)


print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\"http://www.w3.org/TR/html4/loose.dtd\"";
print "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"><title>Multiarch Cross-buildd logs</title></head><body>\n";
print "<h2>Build logs by packagename</h2><p>Generated $todayreadable</p>\n";
if ($pkgfilter) {print "<p>Package list filtered by $pkgfilter.</p>";}
print "<p><a href=\"#summary\">Summary count</a></p>\n";
print "<div id=\"table\"><table border=\"1\">";
print "<tr><th>Package</th><th>Version</th><th>Architecture</th><th>Date</th><th>Summary</th><th>Message</th></tr>\n";



# parse all the logfiles, noting one per package
my @files =<*.log>;
foreach $logname (@files) {
    $package=$version=$arch=$date=$datestring="";
    if ($logname =~ m/$logregex/) {
        $package=$1;
        $version=$2;
        $arch=$5;
        $date="$6";
        $datestring="$6&nbsp;$7:$8";
    }

    $loglist{$package} = $logname unless ($pkgfilter && ! exists $pkgfilter{$package});
}

my $packagecount=$buildok=$unneeded=$builddeps=$sourcefail=0;
# parse noted logs and print into page
@packages = sort (keys %loglist);

foreach $package (@packages) {
    $logname = $loglist{$package};
    $packagecount++;
    $package=$version=$arch=$date=$datestring="";
    if ($logname =~ m/$logregex/) {
        $package=$1;
        $version=$2;
        $arch=$5;
        $date="$6";
        $datestring="$6&nbsp;$7:$8";
    }
    
    ($buildstate,$message,$buildtime) = parselog($logname);

    $summarystate=$buildstate;
    if ($summarystate eq "BUILD OK") {$buildok++} 
    if ($summarystate eq "NOT ATTEMPTED") {$unneeded++} 
    if ($summarystate eq "BUILD DEPS FAILED") {$builddeps++} 
    if ($summarystate eq "SOURCE FAILED") {$sourcefail++} 
    print "<tr><td><a href=\"$logname\">$package</a></td><td>$version</td><td>$arch</td><td>".readabledate($date)."</td><td ".cellcolour($summarystate).">$summarystate</td><td>$message</td></tr>\n"; 
}

$buildfail=$packagecount-($buildok+$unneeded+$builddeps+$sourcefail);
print "</table></div>\n<a name=\"summary\"><p>Of $packagecount source packages: Build OK: $buildok, Build Failed: $buildfail, Deps failed: $builddeps, Not needed(Arch:all): $unneeded, No Source: $sourcefail</p>";
print "<p><ul><li>Build OK: The source package cross-built</li><li>Build Failed: A cross-build was attempted but did not succeed</li><li>Deps Failed: The cross-build dependencies could not be installed, so no build attempted</li><li>Source Failed: the correct version of source was not available (usually a transient issue)</li></ul></p>\n</body></html>";


sub cellcolour {
    my $state=$_[0];
    my $col="#FFFFFF";
    if ($state eq "BUILD DEPS FAILED") {$col="#FF6600";} # orange
    if ($state eq "BUILD OK") {$col="#00FF33";} # green
    if ($state eq "NOT ATTEMPTED") {$col="#CCFF33";} # light green
    if ($state eq "BUILD FAILED") {$col="#CC0000";} # red
    if ($state eq "SOURCE FAILED") {$col="#FFFF66";} # yellow
    return ("bgcolor=\"$col\"");
}

sub readabledate {
    my $date=$_[0];
    if ( length($date)==8 ) { return substr($date, 0,4)."-".substr($date, 4,2)."-".substr($date, 6,2); };
    }

# parse a build log for build-stage info and errors
# takes a filehandle
sub parselog {
    my ($enddate,$endtime,$block,$diskspace,$buildtime,$buildstate,$misseddeps,$message,$buildprofile) = ("","","","","","","","","");
    open (LOG,"<", "$logname") or die "Can't open $logname: $!";
    while (<LOG>) {
        my $line= $_; 
        # print("parsing line:".$_);
	if ( $line =~ /^│ (Changes|Package contents|Update chroot|Fetch source files|Install core build dependencies|Install cross build-dependencies|Build environment|Build|Summary|Cleanup) .*$/) 
            { $block = $1 };

        SWITCH: {

            $block eq "Fetch source files" && do {
                if ( $line =~ /^Can't find source for (.*)$/ ) { $buildstate="SOURCE FAILED"; $message="$line"  };
                last SWITCH;
            };
            (($block eq "Install cross build-dependencies") || ( $block eq "Install core build dependencies" )) && do {
                # if ( $line =~ /^$/ ) {  };
                if ( $line =~ /APT::Build-Profile=(\w+)/ ) { $buildprofile="Build Profile: $1. " };
                if ( $line =~ /^ (.*) but it is not (installable|going to be installed)$/ ) { $buildstate="BUILD DEPS FAILED"; $message="$line" };
                if ( $line =~ /dpkg: error processing ([\w\.-]+) (--configure):/ ) { $buildstate="BUILD DEPS FAILED"; $message="Build-deps failed to install. Possibly: $1" };
                # should only use this if above one failed to get a message - how to do?
                if ( $line =~ /^E: Failed to process build dependencies$/  && $message eq "" ) { $buildstate="BUILD DEPS FAILED"; $message="Build-deps failed to install" };
                # if ( $line =~ /^ (.*) but it is not going to be installed$/ ) { $buildstate="BUILD DEPS FAILED"; $message="$line" };
                if ( $line =~ /^E: Build-Depends dependency for ([\w\.-]+) cannot be satisfied because the package ([\w\.-]+) cannot be found$/ ) 
                    { $buildstate="BUILD DEPS FAILED"; $message="package $2 cannot be found" };
                last SWITCH;
            };
            $block eq "Build" && do {
                if ( $line =~ /^dpkg-checkbuilddeps: Unmet build dependencies: (.*)$/ ) { $buildstate="BUILD DEPS FAILED"; $message="dpkg-checkbuildeps missing: $1" };
                if ( $line =~ /^strip: Unable to recognise the format of the input file/ ) { $buildstate="BUILD FAILED";  $message="Wrong-architecture strip for binary" };
                if ( $line =~ /binary-is-wrong-architecture/ ) { $buildstate="BUILD FAILED"; $message="Wrong-architecture binaries found" };
                if (( $line =~ /^E: Build failure \(dpkg-buildpackage died\)$/ ) && $message eq "") { $buildstate="BUILD FAILED"; $message="dpkg-buildpackage failed" };
                if ( $line =~ /^I: Built successfully/ ) { $buildstate="BUILD OK" };
                last SWITCH;
            };
            $block eq "Cleanup" && do {
	        if ( $line =~ /^E: .*: ([\w-]+) not in arch list or does not match any arch wildcards: (.*) -- skipping$/ ) { $buildstate="NOT ATTEMPTED"; $message="$1 not in arch list for this package"  };
                last SWITCH;
            };
            $block eq "Summary" && do {
 	        # if ( $line =~ /^Build started at ([\d-]+) ([\d:]+).\d*$/ ) { ($startday,$starttime) = ($1,$2) };
	        if ( $line =~ /^Build needed ([\d:]+), (\d+)k disc space$/ ) { ($buildtime, $diskspace) = ($1,$2) };
	        if ( $line =~ /^Finished at (\d{8})-(\d{4})$/ ) { ($enddate, $endtime) = ($1,$2) };
                last SWITCH;
            };
            # Not in sbuild block so probably a manual build log
            $block eq "" && do {
                if ( $line =~ /^dpkg-buildpackage: binary only upload/ ) {  $buildstate="BUILD OK"; $message="Manual build"  };  # for manual builds done outside sbuild
                if ( $line =~ /^I: Built successfully/ ) { $buildstate="BUILD OK"; $message="Manual build" };   # for manual builds done outside sbuild
                if ( $line =~ /^Build Profile: (\w+)$/ ) { $buildprofile="Build Profile: $1. " };
                if ( $line =~ /DEB_BUILD_PROFILE=(\w+)/ ) { $buildprofile="Build Profile: $1. " };
                last SWITCH;
            };
        }
       if ( $line =~ /^E: ABORT: Received INT signal \(requesting cleanup and shutdown\)$/ ) { $buildstate="ABORTED";  $message="build interrupted" };
    }
    close (LOG);
    if ( $buildstate eq "" ) { $buildstate="BUILD FAILED"; $message="State not determined - probably failed or aborted" };
    if ( $buildprofile ) { $message=$buildprofile . $message }; 
    return ($buildstate, $message, $buildtime)
}
