#!/usr/local/bin/perl use strict; use File::Basename; =pod =head1 wbw.pl - What Breaks What =head2 Purpose What Breaks What processes a make file (e.g., libdeps.mk). It recursively examines the items in each target : dependencies line and constructs a table of what breaks what. The left column lists the items. The right column lists what items will break if the item in the left column is not present for some reason. =head2 Modifications Date Author E-mail Adr Modification ---- ------ ---------- ------------ 2001/03/09 D. Ritchie ritchie@fnal.gov Original 2001/03/19 D. Ritchie ritchie@fnal.gov Deal with circular target:components =cut # # debug settings # debug = 0 -> no invalid format or other error messages, just what breaks what output # debug = 1 -> invalid format messages and what breaks what output # debug = 2 -> debugging info # debug = 3 -> even more debugging info $main::debug = 0; @main::AnalysisSummary = (); my $wait; # # internal constants # Something that can not be a target # (because anything that begins with tab is ignored) # (and target names are not allowed to include white space) my $IsTarget = "\tTARGET"; # # process arguments # input and output files with no defaults my $infile; my $outfile; # # format my $format = 'h'; my $NewLine = "
\n"; my $BegLine = ""; my $RedOnFont = ''; my $RedOffFont = ''; # # Target Special Action my %TargetSpecialActions; # # if the number of arguments is less than 4, then error if ($#ARGV+1 < 4) { &Usage; exit; } # # at least four arguments # (i.e., an input switch, an input file, an output switch, an output file) while (@ARGV) { # get the switch and its value my $SwitchParam = shift @ARGV; my $ValueParam = shift @ARGV; # # if switch param does not match minus sign plus character) if ($SwitchParam !~ m/^\-(.)/) { &Usage; exit; } my $Switch = $1; SWITCH: { if ($Switch eq 'i') { $infile = $ValueParam; last SWITCH; } if ($Switch eq 'o') { $outfile = $ValueParam; last SWITCH; } if ($Switch eq 'f') { $format = $ValueParam; last SWITCH; } if ($Switch eq 'd') { $main::debug = $ValueParam; last SWITCH; } if ($Switch eq 'u') { $TargetSpecialActions{$ValueParam} = 'u'; last SWITCH; } if ($Switch eq 'h') { &Usage; exit; } # # unrecognized switch &Usage; exit; } } # # check switch settings and issue message if not ok # # debug switch if ($main::debug !~ m/\d/) { &Usage; exit; } if ($main::debug < 0 or $main::debug > 3) { &Usage; exit; } # # format switch if ($format eq 'h') { $NewLine = "
\n"; $BegLine = ""; } elsif ($format eq 't') { $NewLine = "\n"; $BegLine = "# "; } else { &Usage; exit; } # # warn about no target special actions if (scalar keys %TargetSpecialActions == 0 ) { # # no target special actions print STDERR "Warning - no target special actions specified.\n", ".PHONY's will be included!\n"; } # # input and output files if (! $infile or ! $outfile) { &Usage; exit; } if (-e $outfile) { die "Output file $outfile exists";} open INFILE, $infile or die "Can't open $infile: $!"; open OUTFILE, ">$outfile" or die "Can't open $outfile: $!"; # # set title my $Title = "What Breaks What"; # # put out header print OUTFILE "\n" if ($format eq 'h'); print OUTFILE "\n" if ($format eq 'h'); print OUTFILE "\n" if ($format eq 'h'); print OUTFILE "$Title\n" if ($format eq 'h'); print OUTFILE "# <<$Title>>\n" if ($format eq 't'); print OUTFILE "# \n"if ($format eq 't'); print OUTFILE "\n" if ($format eq 'h'); print OUTFILE "\n" if ($format eq 'h'); print OUTFILE "\n" if ($format eq 'h'); my $linecount = 0; my $item = 0; my @Targets; my %ListOfWhatIBreak; # # while there are lines from the libdeps.mk file... while () { chomp; # # if format is... if ( m/^\s*$/) { # # ...empty line, then skip # ...(e.g., begin of line, zero or more white space, end of line) if ($main::debug >= 2) { # # print out error message with vertical bar as end of text marker print OUTFILE $BegLine, "Line Count: $linecount - begin of line, white space, end of line:", $NewLine; print OUTFILE $BegLine, substr ($_, 0, 2000),'|', $NewLine; } # $linecount++; next; } elsif ( m/^\t.*$/ ) { # # ...command, then skip # ...(e.g.,begin of line, tab) if ($main::debug >= 2) { # # print out error message with vertical bar as end of text marker print OUTFILE $BegLine, "Line Count: $linecount - begin of line, tab:", $NewLine; print OUTFILE $BegLine, substr ($_, 0, 2000),'|', $NewLine; } # $linecount++; next; } elsif ( m/^(\s*)([-.\w]+\s+)+[-.\w]+\s*:\s*(\s+[-.\w]+|[-.\w]+)*\s*$/ ) { # # ...multiple targets into a single set of dependencies, skip # ...(begin of line, multiple targets : multiple dependencies) if ($main::debug >= 2) { # # print out error message with vertical bar as end of text marker print OUTFILE $BegLine, "Line Count: $linecount - begin of line, multiple targets:", $NewLine; print OUTFILE $BegLine, substr ($_, 0, 2000),'|', $NewLine; } # $linecount++; next; } elsif ( ! m/^(\s*)([-.\w]+)\s*:\s*(\s+[-.\w]+|[-.\w]+)*\s*$/ ) { # # ...not a single target : a set of dependencies, give error if ($main::debug >= 1) { # # print out error message with vertical bar as end of text marker print OUTFILE $BegLine, "Line Count: $linecount - incorrect format:", $NewLine; print OUTFILE $BegLine, substr ($_, 0, 2000),'|', $NewLine; } $linecount++; next; } else { # # ...that of a target : set of dependencies line # e.g.: # <0 or more chars of white space> # <1 or more chars of period or word> # <0 or more chars of white space> # # <0 or more chars of white space> # <0 or more dependency items> # (where a dependency item = one or more word char # followed by one or more white space) # <0 or more chars of word> # <0 or more white space chars> # # # show what we are seeing if debug if ($main::debug >= 3) { print $BegLine, "first char: ", $1, $NewLine; print $BegLine, "target: ", $2, $NewLine; print $BegLine, "line:", $NewLine; print $BegLine, $_; $wait = ; } # # process target line # break apart my $ThisTarget; my @TheseComponents; ($ThisTarget, @TheseComponents) = split /\s*:\s*|\s+/ ; # # if the target is... if (defined $TargetSpecialActions{$ThisTarget} and $TargetSpecialActions{$ThisTarget} eq 'u') { # # ...an undesired target # # if debug is... if ($main::debug >=1 ) { print OUTFILE $BegLine, "Line Count: $linecount - ThisTarget: $ThisTarget", $NewLine; # # print out error message with vertical bar as end of text marker print OUTFILE $BegLine, "Line Count: $linecount - undesired target:", $NewLine; print OUTFILE $BegLine, substr ($_, 0, 30),'|', $NewLine; } $linecount++; next; } # # remember the target $Targets[$item] = $ThisTarget; print OUTFILE $BegLine, "Target: $ThisTarget Components: @TheseComponents", $NewLine if ($main::debug >= 3); # # assume a target can at least break itself push @{$ListOfWhatIBreak{$ThisTarget}}, $IsTarget; # see if $ThisTarget is null if ($ThisTarget =~ m/^ *$/ ) { print STDERR $BegLine, "ThisTarget is null! Pushing ($IsTarget)", $NewLine; } # # if any of the components on the target : component line break, then it will break the target. # so add the name of target to the ListOfWhatIBreak. my $ThisComponent; foreach $ThisComponent (@TheseComponents) { push @{ $ListOfWhatIBreak{$ThisComponent} }, $ThisTarget; # # see if $ThisTarget is null if ($ThisComponent =~ m/^ *$/ ) { print STDERR $BegLine, "ThisComponent is null! Pushing $ThisTarget", $NewLine; } } # # next item $item++; # # next line $linecount++; next; } } # end of while there are still lines # # summarize... print OUTFILE $BegLine, "Number of Lines Read: ", $linecount - 1, $NewLine if ($main::debug >= 1); my $NumberOfItemsThatCanBreakThings = (scalar keys %ListOfWhatIBreak) + 1; if ($format eq 'h') { print OUTFILE "

$Title

\n"; print OUTFILE < The left column lists the items. The right column lists what items will break if the item in the left column is not present for some reason. In parentheses next to each item in the right column is shown the "break level"-- how many levels deep the item appears. Within break levels, the items are sorted alphabetically

If a circular reference is detected, the item is shown in $RedOnFont red $RedOffFont and an error message is printed.

EOF print OUTFILE '

ritchie@fnal.gov'; print OUTFILE "

Summary

\n"; print OUTFILE "Input file: $infile", $NewLine; print OUTFILE "Output file: $outfile", $NewLine; my @Specials = keys %TargetSpecialActions; print OUTFILE "Undesired targets: @Specials

\n"; print OUTFILE $BegLine, "Number of Targets: ", $item - 1, " Number of Items That Can Break Things: $NumberOfItemsThatCanBreakThings", $NewLine; print OUTFILE $BegLine, '',"Items Making Circular References (if any)", $NewLine; if ($main::debug >= 3) { my $i; for ($i = 0; $i < $item ; $i++) { print OUTFILE $BegLine, "Target: $i Target: $Targets[$i]", $NewLine; } } print OUTFILE "

Details

\n"; print OUTFILE "\n"; print OUTFILE "\n"; } elsif ($format eq 't') { print OUTFILE "# $Title\n# \n"; print OUTFILE <<'EOF'; # What Breaks What processes a make file (e.g., libdeps.mk). # It recursively examines the items in each target : dependencies # line and constructs a table of what breaks what. # The left column lists the items. The right column lists # what items will break if the item in the left column is not # present for some reason. In parentheses next to each # item in the right column is shown the "break level"-- # how many levels deep the item appears. Within break levels, the # items are sorted alphabetically. # # If a circular reference is detected, the item is shown in # surrounded by asterisks and an error message is printed. # # --ritchie@fnal.gov EOF print OUTFILE "# \n# Summary\n# \n"; print OUTFILE "# Input file: $infile\n"; print OUTFILE "# Output file: $outfile\n"; my @Specials = keys %TargetSpecialActions; print OUTFILE "# Undesired targets: @Specials\n# \n"; print OUTFILE "# Number of Targets: ", $item - 1, " Number of Items That Can Break Things: $NumberOfItemsThatCanBreakThings\n"; print OUTFILE "# See the list of items making circular references at the end of this list.\n"; if ($main::debug >= 3) { my $i; for ($i = 0; $i < $item ; $i++) { print OUTFILE "# Target: $i Target: $Targets[$i]\n"; } } print OUTFILE "# \n# Details\n# \n"; print OUTFILE "# A Bug in this...will break all of these...\n"; print OUTFILE "# ------------------------------------------\n"; } else { print STDERR "Illegal value for $format\n"; } # # for each component that breaks something, make a combined list of what it breaks and what # that thing breaks and so on. my $MaximumNumberOfComponents = ($main::debug >= 3 ) ? 10 : -1 ; my $CountOfComponents = 0; my $ThisComponent; foreach $ThisComponent (sort keys %ListOfWhatIBreak) { $CountOfComponents++; if ($MaximumNumberOfComponents > 0 and $CountOfComponents > $MaximumNumberOfComponents) { print OUTFILE $BegLine, "Number Of Components: $CountOfComponents exceeded $MaximumNumberOfComponents", $NewLine if ($main::debug >= 2); exit; } if (!$ThisComponent) { print STDERR "Component ($ThisComponent) Null\n"; } print OUTFILE $BegLine, "Working on $ThisComponent", $NewLine if ($main::debug >= 2); # # if the hash for this component key does not have a reference (to an array) # as its value, then something is wrong. if (! ref $ListOfWhatIBreak{$ThisComponent} ) { die "Error: Component $ThisComponent is not part of anything"; } # # initialize the break hash for this component %main::BreakHash = (); $main::BreakLevel = 1; $main::BreakStartingComponent = $ThisComponent; AnalyzeComponent($ThisComponent); my $ComponentLineNumber = 0; if ($format eq 'h') { print OUTFILE ""; } elsif ($format eq 't') { print OUTFILE "$ThisComponent->"; } else { print STDERR "Illegal value for $format\n"; } my $BreakItemsSeen = 0; my $BreakItem; my $NumberItemsOnLine = 4; my $ItemsPutOnLine = 0; # # define a subroutine to assist in sorting by break level sub ByBreakLevel { $main::BreakHash{$a} <=> $main::BreakHash{$b} or $a cmp $b; } foreach $BreakItem ( sort ByBreakLevel keys %main::BreakHash ) { $BreakItemsSeen = 1; if ($ItemsPutOnLine == 0) { if ($ComponentLineNumber != 0) { if ($format eq 'h') { print OUTFILE ""; } elsif ($format eq 't') { print OUTFILE "------>"; } else { print STDERR "Illegal value for $format\n"; } } } if ($format eq 'h') { print OUTFILE "" if ($main::BreakHash{$BreakItem} < 0); print OUTFILE "" if ($main::BreakHash{$BreakItem} > 0); } elsif ($format eq 't') { print OUTFILE "***$BreakItem (", -$main::BreakHash{$BreakItem}, ")*** " if ($main::BreakHash{$BreakItem} < 0); print OUTFILE "$BreakItem ($main::BreakHash{$BreakItem}) " if ($main::BreakHash{$BreakItem} > 0); } $ItemsPutOnLine++; if ($ItemsPutOnLine == $NumberItemsOnLine ) { if ($format eq 'h') { print OUTFILE "\n"; } elsif ($format eq 't') { print OUTFILE "\n"; } else { print STDERR "Illegal value for $format\n"; } $ComponentLineNumber++; $ItemsPutOnLine = 0; } } my $ItemsLeftToPut = $NumberItemsOnLine - $ItemsPutOnLine; if ($ItemsLeftToPut >= 1 and $ItemsLeftToPut <= 3 or $BreakItemsSeen == 0) { my $ItemSlot; for ($ItemSlot = 1 ; $ItemSlot <= $ItemsLeftToPut; $ItemSlot++) { if ($ItemSlot == 0) { if ($ComponentLineNumber != 0) { if ($format eq 'h') { print OUTFILE ""; } elsif ($format eq 't') { print OUTFILE "------>"; } else { print STDERR "Illegal value for $format\n"; } } } if ($format eq 'h') { print OUTFILE ""; } elsif ($format eq 't') { print OUTFILE " "; } else { print STDERR "Illegal value for $format\n"; } $ItemsPutOnLine++; } $ComponentLineNumber++; if ($format eq 'h') { print OUTFILE "\n"; } elsif ($format eq 't') { print OUTFILE "\n"; } else { print STDERR "Illegal value for $format\n"; } } } if ($format eq 'h') { print OUTFILE "
A Bug in this...Will break all of these...
$ThisComponent
 $RedOnFont$BreakItem (", -$main::BreakHash{$BreakItem}, ") $RedOffFont$BreakItem ($main::BreakHash{$BreakItem})
  
\n"; } elsif ($format eq 't') { print OUTFILE "\n"; } # # put out analysis summary if ($format eq 'h') { print OUTFILE '

Circular Analysis Summary

', "\n"; if (scalar @main::AnalysisSummary != 0) { print OUTFILE "The following items were found to have circular references:
\n"; foreach (@main::AnalysisSummary) { print OUTFILE $_,"
\n"; } } else { print OUTFILE "No items were found to have circular references.
\n"; } print OUTFILE "\n"; print OUTFILE "\n"; } elsif ($format eq 't') { print OUTFILE "# <>", "\n"; if (scalar @main::AnalysisSummary != 0) { print OUTFILE "# The following items were found to have circular references:\n"; foreach (@main::AnalysisSummary) { print OUTFILE "# ", $_,"\n"; } } else { print OUTFILE "# No items were found to have circular references.\n"; } } close OUTFILE or die "Can't close $outfile: $!"; close INFILE or die "Can't close $infile: $!"; $wait = if ($main::debug >= 2); exit; sub Usage { my @suffixlist = ('.pl', ''); my $name; my $path; my $suffix; ($name, $path, $suffix) = fileparse($0, @suffixlist); print "Usage: $name$suffix -i inputfile -o outputfile\n", "where inputfile = libdeps.mk\n", "and outputfile = list of what breaks what\n", "Optional switches:\n", "-u target to specify an undesired target, such as .PHONY\n", "-f t or -f h (default) for text or html format\n", "-d 0, -d 1, -d 2, -d 3 for little to alot debug output\n", "-h gives usage.\n"; return; } sub AnalyzeComponent{ my $ThisComponent = shift; print OUTFILE $BegLine, "AnalyzeComponent - Beginning to do: $ThisComponent ($main::BreakLevel)", $NewLine if ($main::debug >= 2); my $wait; $wait = if ($main::debug >= 2); my $ItemBrokenAtThisLevelByThisComponent; # # for each item that is broken at this level by this component foreach $ItemBrokenAtThisLevelByThisComponent ( @{$ListOfWhatIBreak{$ThisComponent}} ) { # # if this item is simply a record of the fact that it breaks itself then skip print OUTFILE $BegLine, "AnalyzeComponent - $ItemBrokenAtThisLevelByThisComponent is broken by $ThisComponent", $NewLine if ($main::debug >= 2); if ($ItemBrokenAtThisLevelByThisComponent eq $IsTarget) { print OUTFILE $BegLine, "AnalyzeComponent - next since $ItemBrokenAtThisLevelByThisComponent indicates that entry for $ThisComponent is $IsTarget", $NewLine if ($main::debug >= 2); next; } # # if this item is... if ( $ItemBrokenAtThisLevelByThisComponent eq $main::BreakStartingComponent) { # # ...our starting point # we have gotten back to where we started. push @main::AnalysisSummary, "Circular Reference: $ItemBrokenAtThisLevelByThisComponent ($main::BreakLevel)"; print STDERR "Component $ItemBrokenAtThisLevelByThisComponent is same as starting component: $main::BreakStartingComponent\n"; print STDERR "Circular reference - further analysis discontinued at Level $main::BreakLevel\n"; $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} = -$main::BreakLevel; next; } # # if the break hash for this item has... if ( exists $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} and defined $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} and $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} ) { print OUTFILE $BegLine, "AnalyzeComponent - next since break level $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} shows that $ItemBrokenAtThisLevelByThisComponent has already been seen for $ThisComponent beginning from $main::BreakStartingComponent", $NewLine if ($main::debug >= 2); next; } # # record that this item breaks at this level $main::BreakHash{$ItemBrokenAtThisLevelByThisComponent} = $main::BreakLevel; # # go to next level and analyze this item and what it breaks $main::BreakLevel++; AnalyzeComponent($ItemBrokenAtThisLevelByThisComponent); $main::BreakLevel--; } return; }