chiark / gitweb /
Warnings: Track variable references in &-expansions
[subdirmk.git] / generate
index 84572edc2ef842e0ceffacd884aa306851518831..eb99456144302fe5e20ef5b09f22dade5702dbef 100755 (executable)
--- a/generate
+++ b/generate
@@ -7,7 +7,7 @@
 # $(srcdir)/subdirmk/generate [--srcdir=SRCDIR] [--] SUBDIR...
 #
 # generates in each subdirectory
-#     Subdir.mk.tmp
+#     Dir.mk.tmp
 #     Makefile
 # and in toplevel
 #     main.mk.tmp
@@ -26,8 +26,11 @@ our $srcdir='.';
 #         err "message";
 #
 # Other input or usage errors:
-#         die "$0: $file:$lno: problem\n";
-#         die "$0: some problem not locatable in that way\n";
+#         die "subdirmk: $file:$lno: problem\n";
+#         die "subdirmk: some problem not locatable in that way\n";
+#
+# Usage error:
+#         die "subdirmk $0: explanation of problem\n";
 #
 # System call error (not ENOENT) accessing input/output files:
 #         die "description of problem eg maybe erbing noun: $!\n";
@@ -41,7 +44,7 @@ while (@ARGV && $ARGV[0] =~ m/^-/) {
     if (s/^--srcdir=//) {
        $srcdir=$';
     } else {
-       die "$0: unknown option \`$_'\n";
+       die "subdirmk $0: unknown option \`$_'\n";
     }
 }
 our @subdirs = @ARGV;
@@ -86,7 +89,7 @@ sub close_any_output_file() {
 }
 
 sub oraw {
-    die unless defined $writing_output;
+    die 'internal error' unless defined $writing_output;
     print O @_ or die "error writing $writing_output.tmp: $!\n";
 }
 
@@ -115,7 +118,8 @@ sub od { # maybe $-doubled
 sub start_output_file ($) {
     close_any_output_file();
     ($writing_output) = @_;
-    die "$writing_output ?" if $output_files{$writing_output}++;
+    die "internal error ($writing_output?)"
+       if $output_files{$writing_output}++;
     my $tmp = "$writing_output.tmp";
     open O, ">", $tmp or die "create $tmp: $!\n";
     oraw "# autogenerated - do not edit\n";
@@ -148,6 +152,8 @@ Makefile run-main.mk:
 END
 }
 
+our %varref;
+
 our ($dir_prefix, $dir_suffix, $dir_name,
      $var_prefix, $var_prefix_name);
 
@@ -169,7 +175,12 @@ our $err_file;
 
 sub err ($) {
     my ($m) = @_;
-    die "$0: ${err_file}:$.: $m\n";
+    die "subdirmk: ${err_file}:$.: $m\n";
+}
+
+sub wrn ($) {
+    my ($m) = @_;
+    print STDERR "subdirmk: warning: ${err_file}:$.: $m\n";
 }
 
 sub ddbl_only ($) {
@@ -194,7 +205,7 @@ sub process_input_mk ($$$$) {
 
     my $input = new IO::File $f, '<';
     if (!$input) {
-       die "open $f: $!\n" unless $!==ENOENT && $enoent_ok;
+       err "open $f: $!" unless $!==ENOENT && $enoent_ok;
        return;
     }
     $input_files{$f}++;
@@ -212,49 +223,75 @@ sub process_input_mk ($$$$) {
 
     local $ddbl;
     my @nest = (['']);
+    my $evalcall_brackets;
 
     my $push_nest = sub {
-       my ($nk, $nndbl) = @_;
-       unshift @nest, [ $nk, $ddbl ];
+       my ($nk, $nndbl, $what) = @_;
+       unshift @nest, [ $nk, $ddbl, $what, $. ];
        $ddbl = $nndbl;
     };
     my $pop_nest = sub {
        my ($nk) = @_;
-       die unless $nest[0][0] eq $nk;
+       err "unexpectedly closed $nk in middle of $nest[0][0] ($nest[0][2])"
+           unless $nest[0][0] eq $nk;
        $ddbl = (shift @nest)[1];
     };
 
+    # Our detection of variable settings does not have to be completely
+    # accurate, since it is only going to be used for advice to the user.
+    my $note_varref = sub {
+       my ($vn,$amp) = @_;
+       $varref{$vn}{$amp}{"$f:$."} = 1;
+    };
+
     while (<$input>) {
+       if (m#^\s*($esc)?(\w+)\s*(?:=|\+=|\?=|:=)# ||
+           m#^\s*(?:$esc\:macro|define)\s+($esc)?(\S+)\s#) {
+           $note_varref->($2,!!$1);
+       }
        if (s#^\s*$esc\:changequote\s+(\S+)\s+$##) {
            $$esclitr = $1;
            $set_esc->();
            next;
        } elsif (s#^\s*$esc\:endm\s+$##) {
-           $pop_nest->('Macro');
+           $pop_nest->('macro');
            od "endef\n";
            next;
        } elsif (s#^\s*$esc\:(?=(-?)include|macro)##) {
            $buffering_output='';
        } elsif (m#^\s*$esc\:([a-z][-0-9a-z_]*)#) {
-           die "unknown directive $1";
+           err "unknown directive &:$1 or bad argumnt syntax";
        } elsif (s{^\s*${esc}TARGETS(?:_([0-9a-zA-Z_]+))?(?=\W)}{}) {
            my $t = $1 // 'all';
-           od target_varname($var_prefix, $t);
+           my $vn = target_varname($var_prefix, $t);
+           $note_varref->($vn,1);
+           od $vn;
            $targets->{$t} //= [ ];
        }
        for (;;) {
-           die if $ddbl && defined $buffering_output;
-           unless ($nest[0][0] eq 'Eval'
-                   ? s{^(.*?)($esc|[{}])}{}
-                   : s{^(.*?)($esc)}{}) { od $_; last; }
+           err 'cannot $-double &-processed RHS of directive'
+               if $ddbl && defined $buffering_output;
+           unless ($nest[0][0] eq 'eval'
+                   ? s{^(.*?)($esc|\$|[{}])}{}
+                   : s{^(.*?)($esc|\$)}{}) { od $_; last; }
            od $1;
            if ($2 eq '{') {
-               $ddbl++;
+               od $2;
+               $evalcall_brackets++;
                next;
            } elsif ($2 eq '}') {
-               next if --$ddbl;
-               $pop_nest->('Eval');
-               od '}}';
+               od $2;
+               next if --$evalcall_brackets;
+               $pop_nest->('eval');
+               od '}';
+               next;
+           } elsif ($2 eq '$') {
+               od $2;
+               if (s{^\$}{}) { od $&; }
+               elsif (m{^\(($esc)?([^()\$]+)\)} ||
+                      m{^\{($esc)?([^{}\$]+)\}}) {
+                   $note_varref->($2,!!$1);
+               }
                next;
            }
            if (s{^\\$esc}{}) { od "$$esclitr" }
@@ -262,7 +299,10 @@ sub process_input_mk ($$$$) {
            elsif (s{^\\\s+$}{}) { }
            elsif (s{^$esc}{}) { od "$$esclitr$$esclitr" }
            elsif (m{^(?=$caps_re)}) { od $var_prefix }
-           elsif (s{^\$([A-Za-z]\w+)}{}) { od "\$(${var_prefix}$1)" }
+           elsif (s{^\$([A-Za-z]\w+)}{}) {
+               $note_varref->($1,1);
+               od "\$(${var_prefix}$1)";
+           }
            elsif (s{^([~^]?)(?=$lc_re)}{}) { od $pfxmap{$1} }
            elsif (s{^_}{}) { od $var_prefix }
            elsif (s{^=}{}) { od $var_prefix_name }
@@ -271,14 +311,19 @@ sub process_input_mk ($$$$) {
            elsif (s{^([~^])\.}{}) { od $srcdirmap{$1} }
            elsif (s{^\$\-}{}) { $ddbl=undef; }
            elsif (s{^\$\+}{}) { $ddbl=1; }
-           elsif (s{^\$\(}{}) { die unless $ddbl; oud "\$("; }
-           elsif (s{^\$(\d+)}{}) { die unless $ddbl; oud "\$($1)"; }
+           elsif (s{^\$\(}{}) {
+               ddbl_only($&); oud "\$(";
+               $note_varref->($2,!!$1) if m{^($esc)?([^()\$]+\))};
+           }
+           elsif (s{^\$(\d+)}{}) { ddbl_only($&); oud "\$($1)"; }
            elsif (s{^\$\{}{}) {
-               die if $ddbl;
+               err 'macro invocation cannot be re-$-doubled' if $ddbl;
                od '${eval ${call ';
-               $push_nest->('Eval',1);
+               $evalcall_brackets = 1;
+               $push_nest->('eval',1, '&${...}');
+               $note_varref->($2,!!$1) if m{^\s*($esc)?([^,{}\$]+)};
            } elsif (s{^([~^]?)(?=[ \t])}{}) {
-               my $prefix = $pfxmap{$1} // die;
+               my $prefix = $pfxmap{$1} // die "internal error ($1?)";
                my $after='';
                if (m{([ \t])$esc}) { ($_,$after) = ($`, $1.$'); }
                s{(?<=[ \t])(?=\S)(?!\\\s*$)}{$prefix}g;
@@ -290,7 +335,8 @@ sub process_input_mk ($$$$) {
                od $_;
                $_ = '';
            } else {
-               die "bad escape $$esclitr$_ ";
+               m{^.{0,5}};
+               err "bad &-escape \`$$esclitr$&'";
            }
        }
        if (defined $buffering_output) {
@@ -302,13 +348,14 @@ sub process_input_mk ($$$$) {
                od "\n";
            } elsif (m#^macro\s+(\S+)\s+$#) {
                od "define $1\n";
-               $push_nest->('Macro', 1);
+               $push_nest->('macro', 1, '&:macro');
            } else {
-               die "internal error buffering directive $_ ";
+               err "bad directive argument syntax";
            }
        }
     }
-    die "unclosed $nest[0][0]" if $nest[0][0];
+    die "subdirmk: $f:$nest[0][3]: unclosed $nest[0][0] ($nest[0][2])\n"
+       if $nest[0][0];
     $input->error and die "read $f: $!\n";
     close $input or die "close $f: $!\n";
 }
@@ -325,9 +372,9 @@ sub filter_subdir_mk ($) {
        my ($f, $enoentok) = @_;
        process_input_mk($targets, "${srcdir}/$f", \$esclit, $enoentok);
     };
-    $pi->("Prefix.sd.mk",              1);
-    $pi->("${dir_prefix}Subdir.sd.mk", 0);
-    $pi->("Suffix.sd.mk",              1);
+    $pi->("Prefix.sd.mk",           1);
+    $pi->("${dir_prefix}Dir.sd.mk", 0);
+    $pi->("Suffix.sd.mk",           1);
 }
 
 sub process_subtree ($$);
@@ -343,30 +390,31 @@ sub process_subtree ($$) {
     # ^ this is the only var which we need before we come back from
     #   the recursion.
 
-    push @output_makefiles, "${dir_prefix}Subdir.mk";
+    push @output_makefiles, "${dir_prefix}Dir.mk";
     write_makefile($dir_prefix, scalar @$path);
 
     my %targets = (all => []);
     foreach my $child (@{ $node->[1] }) {
        my @childpath = (@$path, $child->[0]);
        my $child_subdir = join '/', @childpath;
-       mkdir $child_subdir or $!==EEXIST or die "mkdir $child_subdir: $!";
+       mkdir $child_subdir or $!==EEXIST or die "mkdir $child_subdir: $!\n";
        push @{ $targets{$_} }, $child_subdir foreach
            process_subtree($child, \@childpath);
     }
 
     set_dir_vars($path);
-    start_output_file("${dir_prefix}Subdir.mk.tmp");
+    start_output_file("${dir_prefix}Dir.mk.tmp");
 
     if ($node->[2]) {
        filter_subdir_mk(\%targets);
     } else {
-       my $sdmk = "${dir_prefix}Subdir.sd.mk";
+       my $sdmk = "${dir_prefix}Dir.sd.mk";
        if (stat $sdmk) {
-           die "$sdmk unexpectedly exists (${dir_prefix} not mentioned)";
+           die
+ "subdirmk: $sdmk unexpectedly exists (${dir_prefix} not mentioned on subdirmk/generate command line, maybe directory is missing from SUBDIRMK_SUBDIRS)";
        } elsif ($!==ENOENT) {
        } else {
-           die "stat $sdmk: $!";
+           die "stat $sdmk: $!\n";
        }
     }
 
@@ -375,16 +423,16 @@ sub process_subtree ($$) {
     my @targets = sort keys %targets;
     foreach my $target (@targets) {
        my $target_varname = target_varname($var_prefix, $target);
-       print O "${dir_prefix}${target}:: \$($target_varname)";
+       oraw "${dir_prefix}${target}:: \$($target_varname)";
        foreach my $child_subdir (@{ $targets{$target} }) {
-           print O " $child_subdir/$target";
+           oraw " $child_subdir/$target";
        }
-       print O "\n";
+       oraw "\n";
     }
     if (@targets) {
-       print O ".PHONY:";
-       print O " ${dir_prefix}${_}" foreach @targets;
-       print O "\n";
+       oraw ".PHONY:";
+       oraw " ${dir_prefix}${_}" foreach @targets;
+       oraw "\n";
     }
 
     return @targets;
@@ -400,7 +448,8 @@ sub process_final ($) {
     process_input_mk(\%ntargets, "${srcdir}/Final.sd.mk", \$esclit, 1);
     delete $ntargets{$_} foreach @$otargets;
     my @ntargets = sort keys %ntargets;
-    die "late new targets @ntargets" if @ntargets;
+    die "subdirmk: Final.sd.mk may not introduce new top-level targets".
+       " (@ntargets)\n" if @ntargets;
 }
 
 sub process_tree() {
@@ -421,6 +470,20 @@ sub process_tree() {
     oraw "include \$(SUBDIRMK_MAKEFILES)\n";
 }
 
+sub print_varref_warnings () {
+    foreach my $vn (sort keys %varref) {
+       my $vv = $varref{$vn};
+       next unless $vv->{''} && $vv->{1};
+       print STDERR "subdirmk: warning: saw both $vn and &$vn\n";
+       foreach my $amp ('', 1) {
+           printf STDERR " saw %s%s at %s\n",
+               ($amp ? '&' : ''), $vn, $_
+               foreach sort keys %{ $vv->{$amp} };
+       }
+    }
+}
+
 build_tree();
 process_tree();
+print_varref_warnings();
 install_output_files();