# Copyright (C) 2003-2015 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . package Automake::Rule; use 5.006; use strict; use Carp; use Automake::Item; use Automake::RuleDef; use Automake::ChannelDefs; use Automake::Channels; use Automake::Options; use Automake::Condition qw (TRUE FALSE); use Automake::DisjConditions; require Exporter; use vars '@ISA', '@EXPORT', '@EXPORT_OK'; @ISA = qw/Automake::Item Exporter/; @EXPORT = qw (reset rules depend %dependencies %actions register_action reject_rule msg_rule msg_cond_rule err_rule err_cond_rule rule rrule ruledef rruledef); =head1 NAME Automake::Rule - support for rules definitions =head1 SYNOPSIS use Automake::Rule; use Automake::RuleDef; =head1 DESCRIPTION This package provides support for Makefile rule definitions. An C is a rule name associated to possibly many conditional definitions. These definitions are instances of C. Therefore obtaining the value of a rule under a given condition involves two lookups. One to look up the rule, and one to look up the conditional definition: my $rule = rule $name; if ($rule) { my $def = $rule->def ($cond); if ($def) { return $def->location; } ... } ... when it is known that the rule and the definition being looked up exist, the above can be simplified to return rule ($name)->def ($cond)->location; # do not write this. but is better written return rrule ($name)->rdef ($cond)->location; or even return rruledef ($name, $cond)->location; The I variants of the C, C, and C methods add an extra test to ensure that the lookup succeeded, and will diagnose failures as internal errors (with a message which is much more informative than Perl's warning about calling a method on a non-object). =head2 Global variables =over 4 =cut my %_rule_dict; =item C<%dependencies> Holds the dependencies of targets which dependencies are factored. Typically, C<.PHONY> will appear in plenty of F<*.am> files, but must be output once. Arguably all pure dependencies could be subject to this factoring, but it is not unpleasant to have paragraphs in Makefile: keeping related stuff altogether. =cut use vars '%dependencies'; =item <%actions> Holds the factored actions. Tied to C<%dependencies>, i.e., filled only when keys exists in C<%dependencies>. =cut use vars '%actions'; =back =head2 Error reporting functions In these functions, C<$rule> can be either a rule name, or an instance of C. =over 4 =item C Uncategorized errors about rules. =cut sub err_rule ($$;%) { msg_rule ('error', @_); } =item C Uncategorized errors about conditional rules. =cut sub err_cond_rule ($$$;%) { msg_cond_rule ('error', @_); } =item C Messages about conditional rules. =cut sub msg_cond_rule ($$$$;%) { my ($channel, $cond, $rule, $msg, %opts) = @_; my $r = ref ($rule) ? $rule : rrule ($rule); msg $channel, $r->rdef ($cond)->location, $msg, %opts; } =item C Messages about rules. =cut sub msg_rule ($$$;%) { my ($channel, $rule, $msg, %opts) = @_; my $r = ref ($rule) ? $rule : rrule ($rule); # Don't know which condition is concerned. Pick any. my $cond = $r->conditions->one_cond; msg_cond_rule ($channel, $cond, $r, $msg, %opts); } =item C<$bool = reject_rule ($rule, $error_msg)> Bail out with C<$error_msg> if a rule with name C<$rule> has been defined. Return true iff C<$rule> is defined. =cut sub reject_rule ($$) { my ($rule, $msg) = @_; if (rule ($rule)) { err_rule $rule, $msg; return 1; } return 0; } =back =head2 Administrative functions =over 4 =item C Return the list of all L instances. (I.e., all rules defined so far.) =cut sub rules () { return values %_rule_dict; } =item C Append the C<$action> to C<$actions{$target}> taking care of special cases. =cut sub register_action ($$) { my ($target, $action) = @_; if ($actions{$target}) { $actions{$target} .= "\n$action" if $action; } else { $actions{$target} = $action; } } =item C The I function. Clears all known rules and resets some other internal data. =cut sub reset() { %_rule_dict = (); %dependencies = ( # Texinfoing. 'dvi' => [], 'dvi-am' => [], 'pdf' => [], 'pdf-am' => [], 'ps' => [], 'ps-am' => [], 'info' => [], 'info-am' => [], 'html' => [], 'html-am' => [], # Installing/uninstalling. 'install-data-am' => [], 'install-exec-am' => [], 'uninstall-am' => [], 'install-man' => [], 'uninstall-man' => [], 'install-dvi' => [], 'install-dvi-am' => [], 'install-html' => [], 'install-html-am' => [], 'install-info' => [], 'install-info-am' => [], 'install-pdf' => [], 'install-pdf-am' => [], 'install-ps' => [], 'install-ps-am' => [], 'installcheck-am' => [], # Cleaning. 'clean-am' => [], 'mostlyclean-am' => [], 'maintainer-clean-am' => [], 'distclean-am' => [], 'clean' => [], 'mostlyclean' => [], 'maintainer-clean' => [], 'distclean' => [], # Tarballing. 'dist-all' => [], '.PHONY' => [], '.PRECIOUS' => [], ); %actions = (); } =item C Return the C object for the rule named C<$rulename> if defined. Return 0 otherwise. =cut sub rule ($) { my ($name) = @_; # Strip $(EXEEXT) from $name, so we can diagnose # a clash if 'ctags$(EXEEXT):' is redefined after 'ctags:'. $name =~ s,\$\(EXEEXT\)$,,; return $_rule_dict{$name} || 0; } =item C Return the C object for the rule named C<$rulename> if defined in condition C<$cond>. Return false if the condition or the rule does not exist. =cut sub ruledef ($$) { my ($name, $cond) = @_; my $rule = rule $name; return $rule && $rule->def ($cond); } =item C object for the variable named C<$rulename>. Abort with an internal error if the variable was not defined. The I in front of C stands for I. One should call C to assert the rule's existence. =cut sub rrule ($) { my ($name) = @_; my $r = rule $name; prog_error ("undefined rule $name\n" . &rules_dump) unless $r; return $r; } =item C Return the C object for the rule named C<$rulename> if defined in condition C<$cond>. Abort with an internal error if the condition or the rule does not exist. =cut sub rruledef ($$) { my ($name, $cond) = @_; return rrule ($name)->rdef ($cond); } # Create the variable if it does not exist. # This is used only by other functions in this package. sub _crule ($) { my ($name) = @_; my $r = rule $name; return $r if $r; return _new Automake::Rule $name; } sub _new ($$) { my ($class, $name) = @_; # Strip $(EXEEXT) from $name, so we can diagnose # a clash if 'ctags$(EXEEXT):' is redefined after 'ctags:'. (my $keyname = $name) =~ s,\$\(EXEEXT\)$,,; my $self = Automake::Item::new ($class, $name); $_rule_dict{$keyname} = $self; return $self; } sub _rule_defn_with_exeext_awareness ($$$) { my ($target, $cond, $where) = @_; # For now 'foo:' will override 'foo$(EXEEXT):'. This is temporary, # though, so we emit a warning. (my $noexe = $target) =~ s/\$\(EXEEXT\)$//; my $noexerule = rule $noexe; my $tdef = $noexerule ? $noexerule->def ($cond) : undef; if ($noexe ne $target && $tdef && $noexerule->name ne $target) { # The no-exeext option enables this feature. if (! option 'no-exeext') { msg ('obsolete', $tdef->location, "deprecated feature: target '$noexe' overrides " . "'$noexe\$(EXEEXT)'\n" . "change your target to read '$noexe\$(EXEEXT)'", partial => 1); msg ('obsolete', $where, "target '$target' was defined here"); } } return $tdef; } sub _maybe_warn_about_duplicated_target ($$$$$$) { my ($target, $tdef, $source, $owner, $cond, $where) = @_; my $oldowner = $tdef->owner; # Ok, it's the name target, but the name maybe different because # 'foo$(EXEEXT)' and 'foo' have the same key in our table. my $oldname = $tdef->name; # Don't mention true conditions in diagnostics. my $condmsg = $cond == TRUE ? '' : (" in condition '" . $cond->human . "'"); if ($owner == RULE_USER) { if ($oldowner == RULE_USER) { ## FIXME: Presently we can't diagnose duplicate user rules ## because we don't distinguish rules with commands ## from rules that only add dependencies. E.g., ## .PHONY: foo ## .PHONY: bar ## is legitimate. This is checked in the 'phony.sh' test. # msg ('syntax', $where, # "redefinition of '$target'$condmsg ...", partial => 1); # msg_cond_rule ('syntax', $cond, $target, # "... '$target' previously defined here"); } else { # Since we parse the user Makefile.am before reading # the Automake fragments, this condition should never happen. prog_error ("user target '$target'$condmsg seen after Automake's" . " definition\nfrom " . $tdef->source); } } else # $owner == RULE_AUTOMAKE { if ($oldowner == RULE_USER) { # -am targets listed in %dependencies support a -local # variant. If the user tries to override TARGET or # TARGET-am for which there exists a -local variant, # just tell the user to use it. my $hint = 0; my $noam = $target; $noam =~ s/-am$//; if (exists $dependencies{"$noam-am"}) { $hint = "consider using $noam-local instead of $target"; } msg_cond_rule ('override', $cond, $target, "user target '$target' defined here" . "$condmsg ...", partial => 1); msg ('override', $where, "... overrides Automake target '$oldname' defined here", partial => $hint); msg_cond_rule ('override', $cond, $target, $hint) if $hint; } else # $oldowner == RULE_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. # However 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 = $tdef->source; if (not ($source eq $oldsource && $target eq $oldname)) { msg ('syntax', $where, "redefinition of '$target'$condmsg ...", partial => 1); msg_cond_rule ('syntax', $cond, $target, "... '$oldname' previously defined here"); } } } } # Return the list of conditionals in which the rule was defined. In case # an ambiguous conditional definition is detected, return the empty list. sub _conditionals_for_rule ($$$$) { my ($rule, $owner, $cond, $where) = @_; my $target = $rule->name; my @conds; my ($message, $ambig_cond) = $rule->conditions->ambiguous_p ($target, $cond); return $cond if !$message; # No ambiguity. # Ignore possible ambiguity in '%'-style pattern rules. We'd need the # dependencies to detect duplicates, and would be overkill anyway, worth # the possibility of annoying false positives. return $cond if $target =~ /%/; if ($owner == RULE_USER) { msg 'syntax', $where, "$message ...", partial => 1; msg_cond_rule ('syntax', $ambig_cond, $target, "... '$target' previously defined here"); return (); } # FIXME: for Automake rules, we can't diagnose ambiguities yet. # The point is that Automake doesn't propagate conditions # 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 # condition. 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.sh and cond15.sh for some test cases.) @conds = $rule->not_always_defined_in_cond ($cond)->conds; # No conditions left to define the rule. # Warn, because our workaround is meaningless in this case. if (scalar @conds == 0) { msg 'syntax', $where, "$message ...", partial => 1; msg_cond_rule ('syntax', $ambig_cond, $target, "... '$target' previously defined here"); return (); } return @conds; } =item C<@conds = define ($rulename, $source, $owner, $cond, $where, [$deps])> Define a new rule. C<$rulename> is the list of targets. C<$source> is the filename the rule comes from. C<$owner> is the owner of the rule (C or C). C<$cond> is the C under which the rule is defined. C<$where> is the C where the rule is defined. C<$deps> is a withespace-separated list of dependencies (currently unused). Returns a (possibly empty) list of Cs where the rule's definition should be output. =cut sub define ($$$$$;$) { my ($target, $source, $owner, $cond, $where, $deps) = @_; $deps = '' unless defined $deps; prog_error "$where is not a reference" unless ref $where; prog_error "$cond is not a reference" unless ref $cond; # Don't even think about defining a rule in condition FALSE. return () if $cond == FALSE; my $tdef = _rule_defn_with_exeext_awareness ($target, $cond, $where); # See whether this is a duplicated target declaration. # Ignore '%'-style pattern rules. We'd need to scan the dependencies # to detect duplicates, and doing so would be overkill anyway, not # worth the possibility of annoying false positives. if ($tdef && $target !~ /%/) { # Diagnose invalid target redefinitions, if any. Note that some # target redefinitions are valid (e.g., for multiple-targets # pattern rules). _maybe_warn_about_duplicated_target ($target, $tdef, $source, $owner, $cond, $where); # Return so we don't redefine the rule in our tables, don't check # for ambiguous condition, etc. The rule will be output anyway # because '&read_am_file' ignores the return code. return (); } my $rule = _crule $target; # Conditions for which the rule should be defined. Due to some # complications in the automake internals, this aspect is not as # obvious as it might be, and in come cases this list must contain # other entries in addition to '$cond'. See the comments in # '_conditionals_for_rule' for a rationale. my @conds = _conditionals_for_rule ($rule, $owner, $cond, $where); # Stop if we had ambiguous conditional definitions. return unless @conds; # Finally define this rule. for my $c (@conds) { my $def = new Automake::RuleDef ($target, $where->clone, $owner, $source); $rule->set ($c, $def); } return @conds; } =item C Adds C<@deps> to the dependencies of target C<$target>. This should be used only with factored targets (those appearing in C<%dependees>). =cut sub depend ($@) { my ($category, @dependees) = @_; push (@{$dependencies{$category}}, @dependees); } =back =head1 SEE ALSO L, L, L, L. =cut 1; ### Setup "GNU" style for perl-mode and cperl-mode. ## Local Variables: ## perl-indent-level: 2 ## perl-continued-statement-offset: 2 ## perl-continued-brace-offset: 0 ## perl-brace-offset: 0 ## perl-brace-imaginary-offset: 0 ## perl-label-offset: -2 ## cperl-indent-level: 2 ## cperl-brace-offset: 0 ## cperl-continued-brace-offset: 0 ## cperl-label-offset: -2 ## cperl-extra-newline-before-brace: t ## cperl-merge-trailing-else: nil ## cperl-continued-statement-offset: 2 ## End: