automake-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

FYI: diagnose target clashes (for PR/344)


From: Alexandre Duret-Lutz
Subject: FYI: diagnose target clashes (for PR/344)
Date: 01 Jan 2000 01:22:30 +0100
User-agent: Gnus/5.0808 (Gnus v5.8.8) Emacs/20.7

Here is the final patch I'm committing.

I've fixed my previous change of msg_target, and restricted the
variable_defined check to user-targets.

2002-09-13  Alexandre Duret-Lutz  <address@hidden>

        Diagnose target clashes, for PR automake/344:
        * automake.in (%targets): Record conditionals for definitions.
        (%target_conditional): Remove (obsoleted by %targets).
        (%target_source, %target_owner): New hashes.
        (TARGET_AUTOMAKE, TARGET_USER): New constants.
        (initialize_per_input): Adjust to reset new variables.
        (err_cond_target, msg_cond_target): New functions.
        (msg_target): Adjust usage of %targets.
        (conditional_ambiguous_p): Take a list of conditional to check
        as a third parameter, so this can be used for other things that
        variables.
        (handle_lib_objects_cond): Adjust conditional_ambiguous_p usage.
        (variable_defined): Restrict the target-with-same-name check
        to user targets.
        (rule_define): Add the $SOURCE argument, and take $OWNER instead
        of $IS_AM.  Diagnose target clashes (including ambugious
        conditionals).  Return a list of conditions where the rule should
        be defined instead of a boolean.  Fill %target_source and
        %target_owner.
        (target_define): Use `exists', not `defined'.
        (read_am_file): Adjust the call to rule_define.
        (file_contents_internal): Add more FIXMEs.  Simplify my moving
        and documenting the "define rules in undefined conditions" to
        rule_define.
        * tests/Makefile.am (XFAIL_TESTS): Add specflags7.test and
        specflags8.test.

Index: automake.in
===================================================================
RCS file: /cvs/automake/automake/automake.in,v
retrieving revision 1.1347
diff -u -r1.1347 automake.in
--- automake.in 10 Sep 2002 20:45:57 -0000      1.1347
+++ automake.in 13 Sep 2002 16:33:04 -0000
@@ -517,11 +517,19 @@
 my %content_seen;
 
 # This holds the names which are targets.  These also appear in
-# %contents.
+# %contents.  $targets{TARGET}{COND} is the location of the definition
+# of TARGET for condition COND.
 my %targets;
 
-# Same as %VAR_VALUE, but for targets.
-my %target_conditional;
+# $target_source{TARGET}{COND} is the filename where TARGET
+# were defined for condition COND.  Note this must be a
+# filename, *without* any line number.
+my %target_source;
+
+# $target_owner{TARGET}{COND} the owner of TARGET in condition COND.
+my %target_owner;
+use constant TARGET_AUTOMAKE => 0; # Target defined by Automake.
+use constant TARGET_USER => 1; # Target defined in the user's Makefile.am.
 
 # This is the conditional stack.
 my @cond_stack;
@@ -721,8 +729,8 @@
     %content_seen = ();
 
     %targets = ();
-
-    %target_conditional = ();
+    %target_source = ();
+    %target_owner = ();
 
     @cond_stack = ();
 
@@ -1186,6 +1194,14 @@
   msg_target ('error', @_);
 }
 
+# err_cond_target ($COND, $TARGETNAME, $MESSAGE, [%OPTIONS])
+# ----------------------------------------------------------
+# Uncategorized errors about conditional targets.
+sub err_cond_target ($$$;%)
+{
+  msg_cond_target ('error', @_);
+}
+
 # err_am ($MESSAGE, [%OPTIONS])
 # -----------------------------
 # Uncategorized errors about the current Makefile.am.
@@ -1211,13 +1227,24 @@
   msg $channel, $var_location{$macro}, $msg, %opts;
 }
 
+# msg_cond_target ($CHANNEL, $COND, $TARGETNAME, $MESSAGE, [%OPTIONS])
+# --------------------------------------------------------------------
+# Messages about conditional targets.
+sub msg_cond_target ($$$$;%)
+{
+  my ($channel, $cond, $target, $msg, %opts) = @_;
+  msg $channel, $targets{$target}{$cond}, $msg, %opts;
+}
+
 # msg_target ($CHANNEL, $TARGETNAME, $MESSAGE, [%OPTIONS])
 # --------------------------------------------------------
 # Messages about targets.
 sub msg_target ($$$;%)
 {
   my ($channel, $target, $msg, %opts) = @_;
-  msg $channel, $targets{$target}, $msg, %opts;
+  # Don't know which condition is concerned.  Pick any.
+  my $cond = (keys %{$targets{$target}})[0];
+  msg_cond_target ($channel, $cond, $target, $msg, %opts);
 }
 
 # msg_am ($CHANNEL, $MESSAGE, [%OPTIONS])
@@ -2913,21 +2940,22 @@
        }
     }
 
-    if ($xname ne '')
+  if ($xname ne '')
     {
-       if (conditional_ambiguous_p ($xname . '_DEPENDENCIES', $cond) ne '')
+      my $depvar = $xname . '_DEPENDENCIES';
+      if ((conditional_ambiguous_p ($depvar, $cond,
+                                   keys %{$var_value{$depvar}}))[0] ne '')
        {
-           # Note that we've examined this.
-           &examine_variable ($xname . '_DEPENDENCIES');
+         # Note that we've examined this.
+         &examine_variable ($depvar);
        }
-       else
+      else
        {
-           define_pretty_variable ($xname . '_DEPENDENCIES', $cond,
-                                   @dep_list);
+         define_pretty_variable ($depvar, $cond, @dep_list);
        }
     }
 
-    return $seen_libobjs;
+  return $seen_libobjs;
 }
 
 # Canonicalize the input parameter
@@ -6034,51 +6062,54 @@
 sub check_ambiguous_conditional ($$)
 {
   my ($var, $cond) = @_;
-  my $message = conditional_ambiguous_p ($var, $cond);
+  my ($message, $ambig_cond) =
+    conditional_ambiguous_p ($var, $cond, keys %{$var_value{$var}});
   err_var $var, "$message\n" . macro_dump ($var)
     if $message;
 }
 
-# $STRING
-# conditional_ambiguous_p ($VAR, $COND)
-# -------------------------------------
-# Check for an ambiguous conditional.  Return an error message if we
-# have one, the empty string otherwise.
-sub conditional_ambiguous_p ($$)
+# $STRING, $AMBIG_COND
+# conditional_ambiguous_p ($WHAT, $COND, @CONDS)
+# ----------------------------------------------
+# Check for an ambiguous conditional.  Return an error message and
+# the other condition involved if we have one, two empty strings otherwise.
+#   WHAT:  the thing being defined
+#   COND:  the condition under which is is being defined
+#   CONDS: the conditons under which is had already been defined
+sub conditional_ambiguous_p ($$@)
 {
-    my ($var, $cond) = @_;
-    foreach my $vcond (keys %{$var_value{$var}})
+  my ($var, $cond, @conds) = @_;
+  foreach my $vcond (@conds)
     {
-       # Note that these rules doesn't consider the following
-       # example as ambiguous.
-       #
-       #   if COND1
-       #     FOO = foo
-       #   endif
-       #   if COND2
-       #     FOO = bar
-       #   endif
-       #
-       # It's up to the user to not define COND1 and COND2
-       # simultaneously.
-       my $message;
-       if ($vcond eq $cond)
-       {
-          return "$var multiply defined in condition $cond";
-       }
-       elsif (&conditional_true_when ($vcond, $cond))
-       {
-        return ("$var was already defined in condition $vcond, "
-                . "which implies condition $cond");
-       }
-       elsif (&conditional_true_when ($cond, $vcond))
-       {
-          return ("$var was already defined in condition $vcond, "
-                  . "which is implied by condition $cond");
-       }
-   }
-
-    return '';
+      # Note that these rules doesn't consider the following
+      # example as ambiguous.
+      #
+      #   if COND1
+      #     FOO = foo
+      #   endif
+      #   if COND2
+      #     FOO = bar
+      #   endif
+      #
+      # It's up to the user to not define COND1 and COND2
+      # simultaneously.
+      my $message;
+      if ($vcond eq $cond)
+       {
+         return ("$var multiply defined in condition $cond", $vcond);
+       }
+      elsif (&conditional_true_when ($vcond, $cond))
+       {
+         return ("$var was already defined in condition $vcond, "
+                 . "which implies condition $cond", $vcond);
+       }
+      elsif (&conditional_true_when ($cond, $vcond))
+       {
+         return ("$var was already defined in condition $vcond, "
+                  . "which is implied by condition $cond", $vcond);
+       }
+    }
+  return ('', '');
 }
 
 # @MISSING_CONDS
@@ -6458,21 +6489,47 @@
 {
     my ($var, $cond) = @_;
 
-    # Unfortunately we can't just check for $var_value{VAR}{COND}
-    # as this would make perl create $condition{VAR}, which we
-    # don't want.
-    if (!exists $var_value{$var})
+    if (! exists $var_value{$var}
+       && (! defined $cond || ! exists $var_value{$var}{$cond}))
       {
-       err_target $var, "`$var' is a target; expected a variable"
-         if defined $targets{$var};
-       # The variable is not defined
+       # VAR is not defined.
+
+       # Check there is no target defined with the name of the
+       # variable we check.
+
+       # adl> I'm wondering if this error still makes any sense today. I
+       # adl> guess it was because targets and variables used to share
+       # adl> the same namespace in older versions of Automake?
+       # tom> While what you say is definitely part of it, I think it
+       # tom> might also have been due to someone making a "spelling error"
+       # tom> -- writing "foo:..." instead of "foo = ...".
+       # tom> I'm not sure whether it is really worth diagnosing
+       # tom> this sort of problem.  In the old days I used to add warnings
+       # tom> and errors like this pretty randomly, based on bug reports I
+       # tom> got.  But there's a plausible argument that I was trying
+       # tom> too hard to prevent people from making mistakes.
+       if (exists $targets{$var}
+           && (! defined $cond || exists $targets{$var}{$cond}))
+         {
+           for my $tcond ($cond || keys %{$targets{$var}})
+             {
+               prog_error ("\$targets{$var}{$tcond} exists but "
+                           . "\$target_owner doesn't")
+                 unless exists $target_owner{$var}{$tcond};
+               # Diagnose the first user target encountered, if any.
+               # Restricting this test to user targets allows Automake
+               # to create rules for things like `bin_PROGRAMS = LDADD'.
+               if ($target_owner{$var}{$tcond} == TARGET_USER)
+                 {
+                   err_cond_target ($tcond, $var, "`$var' is a target; "
+                                    . "expected a variable");
+                   return 0;
+                 }
+             }
+         }
        return 0;
       }
 
-    # The variable is not defined for the given condition.
-    return 0
-      if $cond && !exists $var_value{$var}{$cond};
-
     # Even a var_value examination is good enough for us.  FIXME:
     # really should maintain examined status on a per-condition basis.
     $content_seen{$var} = 1;
@@ -7287,22 +7344,27 @@
     }
 }
 
-# $BOOL
-# rule_define ($TARGET, $IS_AM, $COND, $WHERE)
-# --------------------------------------------
-# Define a new rule.  $TARGET is the rule name.  $IS_AM is a boolean
-# which is true if the new rule is defined by the user.  $COND is the
-# condition under which the rule is defined.  $WHERE is where the rule
-# is defined (file name or line number).  Returns true if it is ok to
-# define the rule, false otherwise.
-sub rule_define ($$$$)
+# @CONDS
+# rule_define ($TARGET, $SOURCE, $OWNER, $COND, $WHERE)
+# -----------------------------------------------------
+# Define a new rule.  $TARGET is the rule name.  $SOURCE
+# si the filename the rule comes from.  $OWNER is the
+# owener of the rule (TARGET_AUTOMAKE or TARGET_USER).
+# $COND is the condition string under which the rule is defined.
+# $WHERE is where the rule is defined (file name and/or line number).
+# Returns a (possibly empty) list of conditions where the rule
+# should be defined.
+sub rule_define ($$$$$)
 {
-  my ($target, $rule_is_am, $cond, $where) = @_;
+  my ($target, $source, $owner, $cond, $where) = @_;
+
+  # Don't even think about defining a rule in condition FALSE.
+  return () if $cond eq 'FALSE';
 
   # For now `foo:' will override `foo$(EXEEXT):'.  This is temporary,
   # though, so we emit a warning.
   (my $noexe = $target) =~ s,\$\(EXEEXT\)$,,;
-  if ($noexe ne $target && defined $targets{$noexe})
+  if ($noexe ne $target && exists $targets{$noexe}{$cond})
     {
       # The no-exeext option enables this feature.
       if (! defined $options{'no-exeext'})
@@ -7312,29 +7374,141 @@
               . "change your target to read `$noexe\$(EXEEXT)'");
        }
       # Don't define.
-      return 0;
+      return ();
     }
 
-  reject_target ($target,
-                "$target defined both conditionally and unconditionally")
-    if ($cond
-       ? ! exists $target_conditional{$target}
-       : exists $target_conditional{$target});
+  $target = $noexe;
+
+  # Diagnose target redefinitions.
+  if (exists $target_source{$target}{$cond})
+    {
+      # Sanity checks.
+      prog_error ("\$target_source{$target}{$cond} exists, but \$target_owner"
+                 . " doesn't.")
+       unless exists $target_owner{$target}{$cond};
+      prog_error ("\$target_source{$target}{$cond} exists, but \$targets"
+                 . " doesn't.")
+       unless exists $targets{$target}{$cond};
+
+      my $oldowner  = $target_owner{$target}{$cond};
+
+      # Don't mention true conditions in diagnostics.
+      my $condmsg = $cond ne 'TRUE' ? " in condition `$cond'" : '';
+
+      if ($owner == TARGET_USER)
+       {
+         if ($oldowner eq TARGET_USER)
+           {
+             err ($where, "redefinition of `$target'$condmsg...");
+             err_cond_target ($cond, $target,
+                              "... `$target' previously defined here.");
+             return ();
+           }
+         else
+           {
+             # Since we parse the user Makefile.am before reading
+             # the Automake fragments, this condition should never happen.
+             prog_error ("user target `$target' seen after Automake's "
+                         . "definition\nfrom `$targets{$target}$condmsg'");
+           }
+       }
+      else # $owner == TARGET_AUTOMAKE
+       {
+         if ($oldowner == TARGET_USER)
+           {
+             # Don't overwrite the user definition of TARGET.
+             return ();
+           }
+         else # $oldowner == TARGET_AUTOMAKE
+           {
+             # Automake should ignore redefinitions of its own
+             # rules if they came from the same file.  This makes
+             # it easier to process a Makefile fragment several times.
+             # Hower it's an error if the target is defined in many
+             # files.  E.g., the user might be using bin_PROGRAMS = ctags
+             # which clashes with our `ctags' rule.
+             # (It would be more accurate if we had a way to compare
+             # the *content* of both rules.  Then $targets_source would
+             # be useless.)
+             my $oldsource = $target_source{$target}{$cond};
+             return () if $source eq $oldsource;
+
+             err ($where, "redefinition of `$target'$condmsg...");
+             err_cond_target ($cond, $target,
+                              "... `$target' previously defined here.");
+             return ();
+           }
+       }
+      # Never reached.
+      prog_error ("Unreachable place reached.");
+    }
 
   # A GNU make-style pattern rule has a single "%" in the target name.
   msg ('portability', $where,
        "`%'-style pattern rules are a GNU make extension")
     if $target =~ /^[^%]*%[^%]*$/;
 
-  # Value here doesn't matter; for targets we only note existence.
-  $targets{$target} = $where;
-  if ($cond)
-    {
-      if ($target_conditional{$target})
+  # Conditions for which the rule should be defined.
+  my @conds = $cond;
+
+  # Check ambiguous conditional definitions.
+  my ($message, $ambig_cond) =
+    conditional_ambiguous_p ($target, $cond, keys %{$targets{$target}});
+  if ($message)                        # We have an ambiguty.
+    {
+      if ($owner == TARGET_USER)
+       {
+         # For user rules, just diagnose the ambiguity.
+         err $where, "$message ...";
+         err_cond_target ($ambig_cond, $target,
+                          "... `$target' previously defined here.");
+         return ();
+       }
+      else
        {
-         &check_ambiguous_conditional ($target, $cond);
+         # FIXME: for Automake rules, we can't diagnose ambiguities yet.
+         # The point is that Automake doesn't propagate conditionals
+         # everywhere.  For instance &handle_PROGRAMS doesn't care if
+         # bin_PROGRAMS was defined conditionally or not.
+         # On the following input
+         #   if COND1
+         #   foo:
+         #           ...
+         #   else
+         #   bin_PROGRAMS = foo
+         #   endif
+         # &handle_PROGRAMS will attempt to define a `foo:' rule
+         # in condition TRUE (which conflicts with COND1).  Fixing
+         # this in &handle_PROGRAMS and siblings seems hard: you'd
+         # have to explain &file_contents what to do with a
+         # conditional.  So for now we do our best *here*.  If `foo:'
+         # was already defined in condition COND1 and we want to define
+         # it in condition TRUE, then define it only in condition !COND1.
+         # (See cond14.test and cond15.test for some test cases.)
+         my @defined_conds = keys %{$targets{$target}};
+         @conds = ();
+         for my $undefined_cond (invert_conditions(@defined_conds))
+           {
+             push @conds, make_condition ($cond, $undefined_cond);
+           }
+         # No conditions left to define the rule.
+         # Warn, because our workaround is meaningless in this case.
+         if (scalar @conds == 0)
+           {
+             err $where, "$message ...";
+             err_cond_target ($ambig_cond, $target,
+                              "... `$target' previously defined here.");
+             return ();
+           }
        }
-      $target_conditional{$target}{$cond} = $where;
+    }
+
+  # Finally define this rule.
+  for my $c (@conds)
+    {
+      $targets{$target}{$c} = $where;
+      $target_source{$target}{$c} = $source;
+      $target_owner{$target}{$c} = $owner;
     }
 
   # Check the rule for being a suffix rule. If so, store in a hash.
@@ -7350,7 +7524,10 @@
       register_suffix_rule ($where, $1, $2);
     }
 
-  return 1;
+  # Return "" instead of TRUE so it can be used with make_paragraphs
+  # directly.
+  return "" if 1 == @conds && $conds[0] eq 'TRUE';
+  return @conds;
 }
 
 
@@ -7358,7 +7535,7 @@
 sub target_defined
 {
     my ($target) = @_;
-    return defined $targets{$target};
+    return exists $targets{$target};
 }
 
 
@@ -7538,7 +7715,11 @@
            # Found a rule.
            $prev_state = IN_RULE_DEF;
 
-           rule_define ($1, 0, $cond, $here);
+           # For TARGET_USER rules, rule_define won't reject a rule
+           # without diagnosic an error.  So we go on and ignore the
+           # return value.
+           rule_define ($1, $amfile, TARGET_USER, $cond || 'TRUE', $here);
+
            check_variable_expansions ($_, $here);
 
            $output_trailer .= $comment . $spacing;
@@ -7896,7 +8077,7 @@
 
          foreach (split (' ' , $targets))
            {
-             # FIXME: We are not robust to people defining several targets
+             # FIXME: 1. We are not robust to people defining several targets
              # at once, only some of them being in %dependencies.  The
              # actions from the targets in %dependencies are usually generated
              # from the content of %actions, but if some targets in $targets
@@ -7904,13 +8085,16 @@
              # a rule for all $targets (i.e. the targets which are both
              # in %dependencies and $targets will have two rules).
 
-             # FIXME: The logic here is not able to output a
+             # FIXME: 2. The logic here is not able to output a
              # multi-paragraph rule several time (e.g. for each conditional
              # it is defined for) because it only knows the first paragraph.
 
+             # FIXME: 3. We are not robust to people defining a subset
+             # of a previously defined "multiple-target" rule.  E.g.
+             # `foo:' after `foo bar:'.
+
              # Output only if not in FALSE.
-             if (defined $dependencies{$_}
-                 && $cond ne 'FALSE')
+             if (defined $dependencies{$_} && $cond ne 'FALSE')
                {
                  &depend ($_, @deps);
                  $actions{$_} .= $actions;
@@ -7919,55 +8103,22 @@
                {
                  # Free-lance dependency.  Output the rule for all the
                  # targets instead of one by one.
-
-                 # Work out all the conditions for which the target hasn't
-                 # been defined
-                 my @undefined_conds;
-                 if (defined $target_conditional{$targets})
+                 my @undefined_conds =
+                   rule_define ($targets, $file,
+                                $is_am ? TARGET_AUTOMAKE : TARGET_USER,
+                                $cond || 'TRUE', $file);
+                 for my $undefined_cond (@undefined_conds)
                    {
-                     my @defined_conds = keys %{$target_conditional{$targets}};
-                     @undefined_conds = invert_conditions(@defined_conds);
+                     my $condparagraph = $paragraph;
+                     $condparagraph =~ s/^/$undefined_cond/gm;
+                     $result_rules .= "$spacing$comment$condparagraph\n";
                    }
-                 else
-                   {
-                     if (defined $targets{$targets})
-                       {
-                         # No conditions for which target hasn't been defined
-                         @undefined_conds = ();
-                       }
-                     else
-                       {
-                         # Target hasn't been defined for any conditions
-                         @undefined_conds = ("");
-                       }
-                   }
-
-                 if ($cond ne 'FALSE')
+                 if (scalar @undefined_conds == 0)
                    {
-                     for my $undefined_cond (@undefined_conds)
-                     {
-                         my $condparagraph = $paragraph;
-                         $condparagraph =~ s/^/make_condition (@cond_stack, 
$undefined_cond)/gme;
-                         if (rule_define ($targets, $is_am,
-                                         "$cond $undefined_cond", $file))
-                         {
-                             $result_rules .=
-                                 "$spacing$comment$condparagraph\n"
-                         }
-                         else
-                         {
-                             # Remember to discard next paragraphs
-                             # if they belong to this rule.
-                             $discard_rule = 1;
-                         }
-                     }
-                     if ($#undefined_conds == -1)
-                     {
-                         # This target has already been defined, the rule
-                         # has not been defined. Remember to discard next
-                         # paragraphs if they belong to this rule.
-                         $discard_rule = 1;
-                     }
+                     # Remember to discard next paragraphs
+                     # if they belong to this rule.
+                     # (but see also FIXME: #2 above.)
+                     $discard_rule = 1;
                    }
                  $comment = $spacing = '';
                  last;
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/automake/automake/tests/Makefile.am,v
retrieving revision 1.435
diff -u -r1.435 Makefile.am
--- tests/Makefile.am   10 Sep 2002 09:50:23 -0000      1.435
+++ tests/Makefile.am   13 Sep 2002 16:33:05 -0000
@@ -1,6 +1,7 @@
 ## Process this file with automake to create Makefile.in
 
-XFAIL_TESTS = subdir5.test auxdir2.test cond17.test
+XFAIL_TESTS = subdir5.test auxdir2.test cond17.test \
+             specflags7.test specflags8.test
 
 TESTS =        \
 acinclude.test \
Index: tests/Makefile.in
===================================================================
RCS file: /cvs/automake/automake/tests/Makefile.in,v
retrieving revision 1.560
diff -u -r1.560 Makefile.in
--- tests/Makefile.in   10 Sep 2002 09:50:23 -0000      1.560
+++ tests/Makefile.in   13 Sep 2002 16:33:06 -0000
@@ -91,7 +91,9 @@
 sysconfdir = @sysconfdir@
 target_alias = @target_alias@
 
-XFAIL_TESTS = subdir5.test auxdir2.test cond17.test
+XFAIL_TESTS = subdir5.test auxdir2.test cond17.test \
+             specflags7.test specflags8.test
+
 
 TESTS = \
 acinclude.test \
@@ -521,7 +523,7 @@
 mkinstalldirs = $(SHELL) $(top_srcdir)/lib/mkinstalldirs
 CONFIG_CLEAN_FILES = defs
 DIST_SOURCES =
-DIST_COMMON = Makefile.am Makefile.in defs.in
+DIST_COMMON = Makefile.am Makefile.in configure.in defs.in
 all: all-am
 
 .SUFFIXES:

-- 
Alexandre Duret-Lutz





reply via email to

[Prev in Thread] Current Thread [Next in Thread]