#!/usr/bin/perl -w # Copyright (C) 2011, 2012, 2013, 2014 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Filters the output of build-webkit into a more human-readable format. use strict; use warnings; use CGI qw(escapeHTML); use File::Basename; use FindBin; use lib $FindBin::Bin; use Getopt::Long; use VCSUtils; use constant { STYLE_PLAIN => 0, STYLE_HEADER => 1, STYLE_SUCCESS => 2, STYLE_ALERT => 3, HTML_HEADER =>< Build Log HTMLHEADER HTML_FOOTER =>< HTMLFOOTER }; sub printLine($$); sub setLogfileOption($$); sub setOutputFormatOption($$); sub shouldIgnoreLine($$); sub usageAndExit(); # Defined in VCSUtils. sub possiblyColored($$); # Global variables used only in global scope. my $outputPath = "&STDOUT"; my $platform = "mac"; my $showHelp; # Global variables used in global and subroutine scope. our $logUnfilteredOutput; our $outputFormat = "text"; our $unfilteredOutputPath = "build.log"; our $useColor = -t STDOUT; sub usageAndExit() { print STDERR <<__END__; Usage: @{[ basename($0) ]} [options] buildlog1 [buildlog2 ...] build-webkit | @{[ basename($0) ]} [options] -h|--help Show this help message -p|--platform Logfile type (default: $platform) Output Options: -o|--output Path for output (default: STDOUT) -f|--format Output format (default: $outputFormat) text: Plain text html: Standalone HTML document --[no-]color ANSI color output for text (default: on, if -o is STDOUT) Unfiltered Logging Options: -l|--log Save unfiltered output to file (see --log-file) --logfile Path to save unfiltered output (implies --log, default: $unfilteredOutputPath) __END__ exit 1; } my $getOptionsResult = GetOptions( 'h|help' => \$showHelp, 'o|output=s' => \$outputPath, 'p|platform=s' => \$platform, 'f|format=s' => \&setOutputFormatOption, 'color!' => \$useColor, 'l|log' => \$logUnfilteredOutput, 'logfile=s' => \&setLogfileOption, ); if (-t STDIN || $showHelp || !$getOptionsResult) { usageAndExit(); } open(OUTPUT_HANDLE, ">$outputPath") or die "Failed to open $outputPath : $!"; if ($logUnfilteredOutput) { open(UNFILTERED_OUTPUT_HANDLE, ">$unfilteredOutputPath") or die "Failed to open $unfilteredOutputPath : $!"; } print OUTPUT_HANDLE HTML_HEADER if ($outputFormat eq "html"); my $buildFinished; my $buildFailed = 0; for (my $previousLine = "", my $line = <>; $line; $previousLine = $line, $line = <>) { print UNFILTERED_OUTPUT_HANDLE $line if $logUnfilteredOutput; chomp($line); next if shouldIgnoreLine($previousLine, $line); if ($line =~ /^={10}/) { printLine($line, STYLE_SUCCESS); $buildFinished = 1; } elsif ($line =~ /^===/) { printLine($line, STYLE_HEADER); } elsif ($line =~ /Checking Dependencies|Check dependencies|Create product structure|Write auxiliary files/) { printLine($line, STYLE_PLAIN); } elsif ($line =~ /\*\* BUILD SUCCEEDED \*\*/) { printLine("Build Succeeded", STYLE_SUCCESS); } elsif ($line =~ /^(\e\[1m)?(PhaseScriptExecution|ClCompile|CompileC|Distributed-CompileC|Ld|PBXCp|CpResource|CopyPNGFile|CopyTiffFile|CpHeader|Preprocess|Processing|ProcessInfoPlistFile|ProcessPCH|ProcessPCH\+\+|Touch|Libtool|CopyStringsFile|Mig|CreateUniversalBinary|Analyze|AnalyzeShallow|ProcessProductPackaging|CodeSign|SymLink|Updating|CompileDTraceScript|CompileXIB|StripNIB|CopyPlistFile|GenerateDSYMFile|ExternalBuildToolExecution)(\e\[0m)? ("[^"]+"|(\\|(?<=\\)\s|\S)+)?/) { my ($command, $path) = ($2, basename($4)); $path =~ s/("|\\|\.[ah]$)//g; printLine("$command $path", STYLE_PLAIN); } elsif ($line =~ /^\S+mkdir .*?(\S+)$/) { my $path = basename($1); printLine("mkdir $path", STYLE_PLAIN); } elsif ($line =~ /^cp (\S+)/) { my $path = basename($1); printLine("cp $path", STYLE_PLAIN); } elsif ($line =~ /python (\S+\.py) (\S+)/) { my ($command, $path) = (basename($1), basename($2)); printLine("python $command $path", STYLE_PLAIN); } elsif ($line =~ /^\/\S+?(strip|WebCoreExportFileGenerator) .*?(\/|\> )(\S+)/) { my ($command, $path) = (basename($1), basename($3)); printLine("$command $path", STYLE_PLAIN); } elsif ($line =~ /^offlineasm\: /) { printLine($line, STYLE_PLAIN); } elsif ($line =~ /^Generating (\S+) from (\S+)/) { printLine($line, STYLE_PLAIN); } elsif ($line =~ /^Generating bindings for the (\S+) builtin\./) { printLine("Generating $1 builtin", STYLE_PLAIN); } elsif ($line =~ /^Generating (bindings|messages? (header|receiver|dispatcher)|derived source) for (\S+)\.\.\./) { my ($command, $path) = ($1, basename($3)); printLine("Generating $command $path", STYLE_PLAIN); } elsif ($line =~ /^Pre-processing (\S+) sandbox profile/) { printLine($line, STYLE_PLAIN); } elsif ($line =~ /^(\S+\/cc).*?(\S+)\.(out|exp)/) { my ($command, $path) = (basename($1), basename($2)); printLine("$command $path", STYLE_PLAIN); } else { # This only gets hit if stderr is redirected to stdout. if (($line =~ /\*\* BUILD FAILED \*\*/) || ($line =~ /^Build FAILED./)) { $buildFailed = 1; } printLine($line, $buildFinished ? STYLE_SUCCESS : STYLE_ALERT); } } print OUTPUT_HANDLE HTML_FOOTER if ($outputFormat eq "html"); close(OUTPUT_HANDLE); close(UNFILTERED_OUTPUT_HANDLE) if ($logUnfilteredOutput); exit $buildFailed; sub printLine($$) { my ($line, $style) = @_; if ($outputFormat eq "html") { $line = escapeHTML($line); if ($style == STYLE_HEADER) { print OUTPUT_HANDLE "

$line

"; } elsif ($style == STYLE_SUCCESS) { print OUTPUT_HANDLE "

$line

"; } elsif ($style == STYLE_ALERT) { print OUTPUT_HANDLE "

$line

"; } else { print OUTPUT_HANDLE "

$line

"; } } else { if ($useColor) { my $colors = "reset"; if ($style == STYLE_HEADER) { $colors = "blue"; } if ($style == STYLE_SUCCESS) { $colors = "green"; } if ($style == STYLE_ALERT) { $colors = "red"; } print OUTPUT_HANDLE possiblyColored($colors, $line); } else { print OUTPUT_HANDLE $line; } } print OUTPUT_HANDLE "\n"; } sub setLogfileOption($$) { my ($opt, $value) = @_; $unfilteredOutputPath = $value; $logUnfilteredOutput = 1; } sub setOutputFormatOption($$) { my ($opt, $value) = @_; $value = lc($value); if ($value ne "html" && $value ne "text") { die "The $opt option must be either \"html\" or \"text\""; } $outputFormat = $value; } sub shouldIgnoreLine($$) { my ($previousLine, $line) = @_; return 1 if $line =~ /^\s*$/; return 1 if $line =~ /^Build settings from command line:/; return 1 if $line =~ /make: Nothing to be done for `all'\./; return 1 if $line =~ /^JavaScriptCore\/create_hash_table/; return 1 if $line =~ /JavaScriptCore.framework\/PrivateHeaders\/create_hash_table/; return 1 if $line =~ /^JavaScriptCore\/pcre\/dftables/; return 1 if $line =~ /^Creating hashtable for /; return 1 if $line =~ /^Wrote output to /; return 1 if $line =~ /^UNDOCUMENTED: /; return 1 if $line =~ /libtool.*has no symbols/; return 1 if $line =~ /^# Lower case all the values, as CSS values are case-insensitive$/; return 1 if $line =~ /^if sort /; return 1 if $line =~ /set-webkit-configuration/; return 1 if $line =~ /^building file list/; return 1 if $line =~ /^\.\/$/; return 1 if $line =~ /^\S+\.h$/; return 1 if $line =~ /^\S+\/$/; return 1 if $line =~ /^sent \d+ bytes/; return 1 if $line =~ /^total size is/; return 1 if $line =~ /^\( (xcodebuild|if) /; return 1 if $line =~ /^warning\: detected internal install, passing entitlements to simulator anyway\./; return 1 if $line =~ /may not function in the Simulator because Ad Hoc/; return 1 if $line =~ /\/usr\/bin\/clang .*? \> \S+.sb/; return 1 if $line =~ / xcodebuild\[[0-9]+:[0-9a-f]+\]\s+DVTAssertions: Warning in .*XCClangResultsPostprocessor.m/; return 1 if $line =~ /^(Details|Object|Method|Function|Thread):/; return 1 if $line =~ /^Please file a bug at /; return 1 if $line =~ /created by an unsupported XCDependencyGraph build$/; if ($platform eq "win") { return 1 if $line =~ /^\s*(touch|perl|cat|rm -f|bison|del|flex|python|\/usr\/bin\/g\+\+|gperf|echo|sed|if \[ \-f|WebCore\/generate-export-file) /; return 1 if $line =~ /^\s*(if not exist \"|if errorlevel 1)/; return 1 if $line =~ /(^\s*|MSB3073:\s+)(set |REM |cmd \/c)/; return 1 if $line =~ /^\s*[cC]:\\[pP]rogram [fF]iles.*\\.*\\(CL|midl)\.exe /; return 1 if $line =~ /^\s*Processing .*\.(acf|h|idl)\s*$/; return 1 if $line =~ /^\s*printf /; return 1 if $line =~ /^\s*\/usr\/bin\/bash\s*/; return 1 if $line =~ /^\s*offlineasm: Nothing changed/; return 1 if $line =~ / \d+ File\(s\) copied/; return 1 if $line =~ /^\s*File not found - \*\.h/; return 1 if $line =~ /mkdir\s+\"/; return 1 if $line =~ /xcopy \/y \/d \"/; return 1 if $line =~ /\.obj\"\s*$/; return 1 if $line =~ /:\s+(cmd \/c|set)\s+/; return 1 if $line =~ /MSB3073:\s+$/; return 1 if $line =~ /MSB3073:\s+if not exist/; return 1 if $line =~ /which.exe bash/; } else { return 1 if $line =~ /^(touch|perl|cat|rm -f|bison|flex|python|\/usr\/bin\/g\+\+|\/bin\/ln|gperf|echo|sed|if \[ \-f|WebCore\/generate-export-file|write-file|chmod) /; return 1 if $line =~ /^ / && $previousLine !~ /referenced from:$/; return 1 if $line =~ /^printf /; return 1 if $line =~ /^offlineasm: Nothing changed/; } return 1 if $line =~ /^Showing first/; return 0; }