diff --git a/utils/makehelp.pl b/utils/makehelp.pl index aa742a7ea..15ef1a2b1 100755 --- a/utils/makehelp.pl +++ b/utils/makehelp.pl @@ -4,22 +4,35 @@ use strict; use warnings; my %msg = (); -my @start = (); my %reorder = (help=>-1); +# We do not want every Makefile syntax error to break help completely, +# so we just disable the kind of features where the syntax error occurs +# In that case we store also the error message here +my %brokenFeatures = (); + +my @blocks = (); +my @plainCode = (); + READ: while(<>) { + my $lnum = $.; if(m/^# HELP HEADER START/) { + my @start = (); while(<>) { - next READ if m/^# HELP HEADER END/; + do { + push @blocks, { kind=>'start', text=>\@start, line=>$lnum }; + next READ + } if m/^# HELP HEADER END/; if(m/# (.*)/) { push @start, $1 } else { die "$0: Invalid HELP HEADER section in Makefile!\n"; } } + push @blocks, { kind=>'start', text=>\@start, line=>$lnum }; next READ } - if(m/# HELP((?:\([^\(\)]+\))?):\s*(.*)/) { + if(m/^# HELP((?:\((?:[^\(\)]|\([^\(\)]+\))+\))?):\s*(.*)/) { my ($target, $message) = ($1, $2); if($target=~m/\((.*)\)/) { $target = $1; @@ -29,13 +42,125 @@ READ: while(<>) { $line=~m/^([^:]+):/ or die "$0: HELP marker expects target but no target found!\n"; $target = $1 } - $msg{$target} .= $message + push @blocks, { kind=>'target', target=>$target, message=>$message, line=>$lnum }; + next READ + } + if(m/^# HELPVAR\((.*)\)/) { + push @blocks, { kind=>'helpvar', content=>$1, line=>$lnum }; + next READ; + } + push @plainCode, [$lnum, $_] +} + +my %kindBlocks = (); +for(@blocks) { + push @{$kindBlocks{$_->{kind}}}, $_ +} + +my @matchies = (); +HELPVARS: { + for my $hv(@{$kindBlocks{helpvar}}) { + my $reVar = qr((?:\$\((?[A-Z]+)(?:,(?[^\(\)]+))?\))); + my $reVarNoGroups = qr((?:\$\((?:[A-Z]+)(?:,(?:[^\(\)]+))?\))); + my @parts = split /($reVarNoGroups)/, $hv->{content}; + my %setvars = (); + my @reParts = (); + for my $p(@parts) { + if($p=~m#^$reVar$#) { + my $name = $+{name}; + my $re = $+{re}; + if(defined $re) { + if(exists $setvars{$name}) { + $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Multiple definitions of variable $name found!"; + last HELPVARS + } + $setvars{$name} = $re; + push @reParts, [1,qr((?<$name>$re))]; + } else { + if(not exists $setvars{$name}) { + $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Variable $name used without definition!"; + last HELPVARS + } + push @reParts, [0,$name]; #qr((?:\k<$name>)); -- problem: cannot reference variable when not defined + } + } else { + push @reParts, [1,qr((?:\Q$p\E))]; + } + } + my $reCol = qr((?:)); + for my $r(@reParts) { + my ($isRe, $reg) = @$r; + if($isRe) { + $reCol = qr((?:$reCol$reg)); + } else { + eval { + $reCol = qr((?:$reCol(?:\k<$reg>))); + }; + if($@) { + $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Variable $reg caused an internal error ($@)!"; + last HELPVARS + } + } + } + push @matchies, $reCol } } -print "$_\n" for @start; -print "\n" if @start; -for my $tar(sort {($reorder{$a}||0) <=> ($reorder{$b}||0) || $a cmp $b } keys %msg) { - print "$tar\n $msg{$tar}\n\n" +my %helpvars = (); + +for my $rowr(@plainCode) { + my ($lnum, $row) = @$rowr; + my @matches = (); + for my $re(@matchies) { + if($row=~m#^$re$#) { + my %h = %+; + push @matches, \%h + } + } + if(@matches>1) { + $brokenFeatures{helpvars} = "Makefile line $lnum: Multiple variable definitions match on row '$row'\n"; + } + if(1==@matches) { + my %h = %{$matches[0]}; + for my $k(keys %h) { + push @{$helpvars{$k}}, $h{$k} + } + } +} + +my @start = (); +@start = @{$kindBlocks{start}} if exists $kindBlocks{start}; + +for my $tg(@{$kindBlocks{target}}) { + my $tgv = $tg->{target}; + my @build = (); + $tgv=~s#\$\(([A-Z]+)(?:,[^\(\)]*)?\)#push @build, {name=>$1,elm=>$helpvars{$1}}; $1#ge; + unshift @build, $tg->{message}; + $msg{$tgv} = \@build; +} + +sub msgprinter { + my $el = shift; + if('HASH' eq ref $el) { + print " Where $el->{name} is one of\n"; + print " $_\n" for @{$el->{elm}}; + return + } + $el =~ s#(.{40,76})\s+#$1\n #g; + print " $el\n"; +} + +for(@start) { + print "$_\n" for @{$_->{text}} +} +print "\n" if @start; +for my $tar(sort {($reorder{$a}||0) <=> ($reorder{$b}||0) || $a cmp $b } keys %msg) { + print "$tar\n"; + msgprinter($_) for @{$msg{$tar}}; + print "\n"; +} + +for my $k(keys %brokenFeatures) { + warn "\n!!!!!\nFailed to complete construct the help message from the Makefile.\nFeature '$k' disabled, because: $brokenFeatures{$k}\n\n"; }