#! /usr/perl5/bin/perl -w
#
# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
#
require 5.005; # minimal Perl version required
use strict; #
use diagnostics; #
use File::Spec; # pathname manipulation routines
use File::stat; # Named results from stat() function
use English qw( -nomatchvars );
my $default_logfile = 'buildit-XW';
# Log file from new pkg(5) build (in same directory as $default_logfile)
my $default_ips_pkglogfile = 'make-pkgs';
# Log file from old SVR4 package build:
my $default_logpath = 'log/' . $default_logfile;
my $default_svr4_pkglogpath = 'proto-packages/logs/package_build';
my $logfile;
my $pkglog;
my $pkglog_type; # 'ips' or 'svr4'
my $pkgvalidlog;
my $pkgfailed;
my $summary_only = 0;
if (defined $ARGV[0] && $ARGV[0] eq '-s') {
$summary_only = 1;
shift @ARGV;
}
if (defined $ARGV[0]) {
if (-d $ARGV[0]) {
$logfile = $ARGV[0] . '/' . $default_logpath;
} elsif ($ARGV[0] =~ m{/make-pkgs$}ms) {
$pkglog = $ARGV[0];
$pkglog_type = 'ips';
} elsif ($ARGV[0] =~ m{/check-pkgs$}ms) {
$pkglog = $ARGV[0];
$pkglog_type = 'validate';
} elsif ($ARGV[0] =~ m{/package_build$}ms) {
$pkglog = $ARGV[0];
$pkglog_type = 'svr4';
} else {
$logfile = $ARGV[0];
}
} elsif ( -f $default_logfile ) {
$logfile = $default_logfile;
} elsif ( -f $default_logpath ) {
$logfile = $default_logpath;
} else {
my @dirtree = File::Spec->splitdir(
File::Spec->rel2abs(File::Spec->curdir()));
# climb the tree, removing one parent at a time to find the logfile
while (scalar(@dirtree) > 0) {
$logfile = File::Spec->catfile( @dirtree, $default_logpath);
last if ( -f $logfile);
# print "$logfile not found\n";
pop @dirtree;
}
if (scalar(@dirtree) == 0) {
die "$default_logfile not found, please specify path to log\n";
}
}
if (defined $logfile) {
my $make_errors = 0;
my $LOGFILE = check_make_log($logfile, \$make_errors);
print "Build errors: ", $make_errors, "\n";
my $printme = 0;
# end of file stuff
while (my $l = <$LOGFILE>) {
if ($l =~ m{^Runtime: }) {
print $l;
next;
}
# Look for package build results
if ($l =~ m{^result log is in (\S*)$}ims) {
if ($1 =~ m{/package_build$}ms) {
$pkglog = $1;
$pkglog_type = 'svr4';
} else {
$pkglog_type = 'ips';
$printme = 1;
print "\n";
}
} elsif ($l =~ m{^Packages built:}ms) {
print $l;
} elsif ($l =~ m{^Package.* failed:\s+(\d+)}ms) {
$pkgfailed = $1;
print $l;
}
# print lines where messages about COPYING file errors appear
# between "Copying package descriptions" & "Building packages"
elsif ($l =~ m{Copying package descriptions}) {
$printme = 1;
} elsif ($l =~ m{Building packages}) {
$printme = 0;
}
# Log for IPS packaging checks
elsif ($l =~ m{Checking built packages}) {
next;
} elsif ($l =~ m{Package check log is in (\S*)$}ims) {
$pkgvalidlog = $1;
}
# Print selected lines
elsif ($printme == 1) {
print $l;
}
}
print "\n";
close($LOGFILE);
}
# Input: string containing name of logfile
# Prints errors from make output log
# Returns open log file pointer for post-processing
sub check_make_log {
my ($logfile, $error_count_ref) = @_;
open my $LOGFILE, '<', $logfile
or die "Can't open '$logfile': $OS_ERROR";
print "Scanning $logfile for error messages...\n\n";
my @steplines;
my $found_error = 0;
my $error_count = 0;
while (my $l = <$LOGFILE>) {
# Finished if we see the end line
last if $l =~ m{Finished building the X Window System Consolidation}ms;
# Clear saved lines for each new module/subdir
if (($l =~ m{^\#\# making \S+ in \S+\.\.\.$}ms) || # open-src pattern
($l =~ m{^dmake: Warning: Target `subdirs' not remade because of errors}ms) ||
($l =~ m{^\#\# [[:upper:]][[:lower:]]+ing }ms) || # pkg pattern
($l =~ m{^\S+ing( \S+)* in \S+\.\.\.$}ms)) { # xc pattern
@steplines = ();
$found_error = 0;
}
# If we already hit an error, skip the rest of this module
next if ($found_error != 0);
# Add this line to the saved output, combine with previous if previous
# ended with an \
if (($#steplines >= 0) && ($steplines[$#steplines] =~ m{\\\Z}ms)) {
$steplines[$#steplines] .= $l;
} else {
push @steplines, $l;
}
# Skip ahead to next line if this line ends with \
next if ($l =~ m{\\\Z}ms);
# Found a new error?
if (($l =~ m{\*\*\* }ms) || ($l =~ m{^(d)?make: Fatal error}ms)) {
$found_error = 1;
$error_count++;
next if ($summary_only);
# Print section header
print $steplines[0], "\n";
my $lastmake;
my $lastcommand = 1;
my $lastplus = 0;
# scan back to figure out how far back to print
for my $ln (1..($#steplines - 1)) {
my $sl = $steplines[$ln];
# print "lastmake: $lastmake, lastcom: $lastcommand, lastplus: $lastplus, line #$ln: $sl\n";
if ($sl =~ m{\b(make|dmake|gmake)\b}ms) {
$lastmake = $ln;
}
if ($sl =~ m{\breturned\b}ms) {
# don't treat this as a command
} elsif ($sl =~ m{\b(cc|gcc|CC|g\+\+|ld|gpatch|libtool|GEN|CCLD)\s+}ms) {
if ($sl !~ m{usage:}) {
$lastcommand = $ln;
}
} elsif ($sl =~ m{^\+ }ms) {
# print from start of shell's set -x output, not end
if ($lastplus != ($ln - 1)) {
$lastcommand = $ln;
}
$lastplus = $ln;
} elsif ($lastplus == ($ln - 1)) {
$lastcommand = $ln;
}
}
# print "lastmake: $lastmake, lastcommand: $lastcommand\n";
if ($lastmake && ($lastmake < $lastcommand)) {
print $steplines[$lastmake];
}
for my $ln ($lastcommand..$#steplines) {
print $steplines[$ln];
}
print "\n", '-'x78, "\n";
}
}
if (defined $error_count_ref) {
${$error_count_ref} = $error_count;
}
return $LOGFILE;
}
sub check_pkglog {
my ($pl) = @_;
if ( -f $pl ) {
my $logfile_sb = stat($logfile);
my $pkglog_sb = stat($pl);
if ($logfile_sb->mtime > $pkglog_sb->mtime) {
# Haven't rebuilt packages since last build, so no point reporting errors
undef $pl;
}
} else {
undef $pl;
}
return $pl;
}
# No packaging log found in build log, try to guess where it is
if (!defined($pkglog)) {
my $path_to_check = $logfile;
$path_to_check =~ s{$default_logfile}{$default_ips_pkglogfile}ms;
$pkglog = check_pkglog($path_to_check);
if (defined($pkglog)) {
$pkglog_type = 'ips';
} else {
$pkglog_type = 'svr4';
$path_to_check = $logfile;
$path_to_check =~ s{$default_logpath}{$default_svr4_pkglogpath}ms;
$pkglog = check_pkglog($path_to_check);
if (!defined($pkglog)) {
$path_to_check = $logfile;
$path_to_check =~ s{($default_logpath).*$}{$default_svr4_pkglogpath}ms;
$pkglog = check_pkglog($path_to_check);
}
}
}
if ((!defined($pkgfailed) || ($pkgfailed > 0)) && defined($pkglog)) {
check_pkg_log($pkglog, $pkglog_type);
}
if (defined($pkgvalidlog)) {
print "\nScanning $pkgvalidlog for package checking issues...\n\n";
check_pkg_log($pkgvalidlog, 'validate');
}
sub check_pkg_log {
my ($logfile, $logtype) = @_;
if ($logtype eq 'svr4') {
open my $PKGLOG, '<', $logfile
or die "Can't open '$logfile': $OS_ERROR";
my @pkglines;
while (my $l = <$PKGLOG>) {
# Clear saved lines for each new package
if ($l =~ m{^[*]+ Making the \S+ package [*]+$}ms) {
@pkglines = ();
}
# Warnings we can ignore
next if $l =~ m{^WARNING: parameter \<PSTAMP\> set}ms;
next if $l =~ m{^WARNING: parameter \<CLASSES\> set to "none"}ms;
push @pkglines, $l;
if (($l =~ m{(Packaging was not successful.|was not found ; skipping)}ms)
|| ($l =~ m{^WARNING: }ms)) {
print join('', @pkglines);
@pkglines = ();
}
}
close($PKGLOG);
} elsif ($logtype eq 'ips') {
my $ips_count_errors = 0;
my $PKGLOG = check_make_log($logfile, \$ips_count_errors);
seek($PKGLOG, 0, 0); # reset to start reading from beginning of file
my $ips_count_published = 0;
while (my $l = <$PKGLOG>) {
if ($l =~ m{Publishing .* to proto repository}) {
$ips_count_published++;
}
}
print "Packages published: $ips_count_published\n";
print "Package build errors: $ips_count_errors\n";
} elsif ($logtype eq 'validate') { # validate_pkg
open my $PKGLOG, '<', $logfile
or die "Can't open '$logfile': $OS_ERROR";
my @pkglines;
my $issue_count = 0;
my $issue_type = "";
my $publisher;
while (my $l = <$PKGLOG>) {
chomp($l);
if ($l =~ m{^\#\# Running pkglint on the (\S+) repository}) {
$publisher = $1;
}
# Make pkgfmt issues better match the other patterns
if ($issue_type =~ m{pkgfmt}) {
$l =~ s{^pkgfmt:}{};
}
# Clear saved lines for each new class of issue
if (($l =~ m{^\S+ .*:+$}ms) || ($l =~ m{^\#\# })) {
$issue_type = $l;
$issue_count = 0;
@pkglines = ();
next;
}
# These issues print across two lines
if ($issue_type eq
'Entries that differ between manifests and proto area:')
{
if ($l =~ m{^\s+manifests }) {
my $l2 = <$PKGLOG>;
chomp($l2);
# strip off prefixes for comparisons
my $compare1 = $l;
$compare1 =~ s{^\s+manifests }{};
my $compare2 = $l2;
$compare2 =~ s{^\s+proto area }{};
# Warnings we can ignore
if ($compare1 =~ m{^hardlink }) {
my $hl1 = $compare1;
my $hl2 = $compare2;
# validate_pkg doesn't like hardlinks to isaexec from our pkgs
$hl1 =~ s{ target=usr/lib/isaexec}{};
$hl2 =~ s{ target=\d+}{};
next if ($hl1 eq $hl2);
} elsif ($compare1 =~ m{^file }) {
my $f1 = $compare1;
my $f2 = $compare2;
$f1 =~ s{^file NOHASH }{ };
$f2 =~ s{^file \S+ }{ };
# We don't expect files in proto area to be chowned/chgrped
$f1 =~ s{ owner=root}{};
$f2 =~ s{ owner=owner}{};
$f1 =~ s{ group=\S+}{};
$f2 =~ s{ group=group}{};
# We don't expect files in proto area to be setuid/setgid
if ($f1 =~ m{mode=[24]555}) {
$f1 =~ s{ mode=[24]555}{};
$f2 =~ s{ mode=0555}{};
}
next if ($f1 eq $f2);
}
$l .= "\n" . $l2 . "\n";
}
}
# Skip warnings we can't do anything about (workaround for
# https://defect.opensolaris.org/bz/show_bug.cgi?id=18346 )
if ($l =~ m{^WARNING pkglint.manifest004}) {
next unless $l =~ m{pkg://$publisher/};
}
# Skip informational messages from pkglint
next if ($l =~ m{^INFO pkglint});
# Group pkglint errors by type
if ($l =~ m{^([A-Z]+ (?:pkglint|opensolaris)\S+)\s+(.*)$}) {
my ($new_issue, $details) = ($1, $2);
if ($issue_type eq $new_issue) {
push @pkglines, $details;
$issue_count++;
} else {
print_valid_issue($issue_type, $issue_count, \@pkglines);
$issue_type = $new_issue;
$issue_count = 1;
@pkglines = ($details);
}
}
elsif ($l =~ m{^$}) { # Blank lines separate validate_pkg sections
print_valid_issue($issue_type, $issue_count, \@pkglines);
$issue_type = "";
$issue_count = 0;
@pkglines = ();
} else {
push @pkglines, $l;
$issue_count++;
}
}
# print any final issues that weren't followed by a blank line
print_valid_issue($issue_type, $issue_count, \@pkglines);
close($PKGLOG);
}
}
sub print_valid_issue
{
my ($issue_type, $issue_count, $pkglines) = @_;
if ($issue_count > 0) {
if ($issue_type !~ m{:$}) {
$issue_type .= ':';
}
if ($summary_only) {
print $issue_type, ' ', $issue_count, "\n";
} else {
print join("\n", $issue_type, '', @{$pkglines}), "\n\n";
}
}
}