summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2019-05-11 18:35:03 -0400
committerPaul Smith <psmith@gnu.org>2019-05-12 16:29:20 -0400
commit8c888d95f61814dd698bf76126a9079234080d77 (patch)
tree8020c57690e970a97f6142e61277301b8dc20ac6
parent1710573272f0063bc5e0109203c467bf9c44d944 (diff)
downloadmake-git-8c888d95f61814dd698bf76126a9079234080d77.tar.gz
[SV 8297] Implement "grouped targets" for explicit rules.
This patch allows "grouped targets" using the &: syntax: tgt1 tgt2 ... tgtn &: pre1 pre2 ... recipe When the &: separator is used (in single or double colon forms), all the targets are understood to be built by a single invocation of the recipe. This is accomplished by piggy-backing on the already-existing pattern rule feature, using the file's "also_make" list. * NEWS: Add information about grouped targets. * doc/make.texi (Multiple Targets): Add information on grouped targets. (Pattern Intro): Refer to the new section to discuss multiple patterns. * src/main.c (main): Add "grouped-targets" to .FEATURES * src/read.c (make_word_type): Add new types for &: and &::. (eval): Recognize the &: and &:: separator and remember when used. (record_files): Accept an indicator of whether the rule is grouped. If so, update also_make for each file to depend on the other files. (get_next_mword): Recognize the &: and &:: word types. * tests/scripts/features/grouped_targets: New test script. * AUTHORS: Add Kaz Kylheku
-rw-r--r--AUTHORS1
-rw-r--r--NEWS8
-rw-r--r--doc/make.texi104
-rw-r--r--src/main.c1
-rw-r--r--src/makeint.h1
-rw-r--r--src/read.c144
-rw-r--r--tests/scripts/features/grouped_targets133
7 files changed, 335 insertions, 57 deletions
diff --git a/AUTHORS b/AUTHORS
index 685aede8..e80bad91 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -67,6 +67,7 @@ Other contributors:
David A. Wheeler <dwheeler@dwheeler.com>
David Boyce <dsb@boyski.com>
Frank Heckenbach <f.heckenbach@fh-soft.de>
+ Kaz Kylheku <kaz@kylheku.com>
With suggestions/comments/bug reports from a cast of ... well ...
hundreds, anyway :)
diff --git a/NEWS b/NEWS
index eb25119c..40127c36 100644
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,14 @@ http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=108&set
treated BOTH as simple targets AND as pattern rules. Behavior now matches
the documentation, and pattern rules are no longer created in this case.
+* New feature: Grouped explicit targets
+ Pattern rules have always had the ability to generate multiple targets with
+ a single invocation of the recipe. It's now possible to declare that an
+ explicit rule generates multiple targets with a single invocation. To use
+ this, replace the ":" token with "&:" in the rule. To detect this feature
+ search for 'grouped-target' in the .FEATURES special variable.
+ Implementation contributed by Kaz Kylheku <kaz@kylheku.com>
+
* Makefiles can now specify the '-j' option in their MAKEFLAGS variable and
this will cause make to enable that parallelism mode.
diff --git a/doc/make.texi b/doc/make.texi
index 25a37c00..30229274 100644
--- a/doc/make.texi
+++ b/doc/make.texi
@@ -3012,13 +3012,22 @@ both pieces to the suffix list. In practice, suffixes normally begin with
@cindex targets, multiple
@cindex rule, with multiple targets
-A rule with multiple targets is equivalent to writing many rules, each with
-one target, and all identical aside from that. The same recipe applies to
-all the targets, but its effect may vary because you can substitute the
-actual target name into the recipe using @samp{$@@}. The rule contributes
-the same prerequisites to all the targets also.
+When an explicit rule has multiple targets they can be treated in one
+of two possible ways: as independent targets or as grouped targets.
+The manner in which they are treated is determined by the separator that
+appears after the list of targets.
-This is useful in two cases.
+@subsubheading Rules with Independent Targets
+@cindex independent targets
+@cindex targets, independent
+
+Rules that use the standard target separator, @code{:}, define
+independent targets. This is equivalent to writing the same rule once
+for each target, with duplicated prerequisites and recipes. Typically,
+the recipe would use automatic variables such as @samp{$@@} to specify
+which target is being built.
+
+Rules with independent targets are useful in two cases:
@itemize @bullet
@item
@@ -3030,13 +3039,18 @@ kbd.o command.o files.o: command.h
@noindent
gives an additional prerequisite to each of the three object files
-mentioned.
+mentioned. It is equivalent to writing:
+
+@example
+kbd.o: command.h
+command.o: command.h
+files.o: command.h
+@end example
@item
-Similar recipes work for all the targets. The recipes do not need
-to be absolutely identical, since the automatic variable @samp{$@@}
-can be used to substitute the particular target to be remade into the
-commands (@pxref{Automatic Variables}). For example:
+Similar recipes work for all the targets. The automatic variable
+@samp{$@@} can be used to substitute the particular target to be
+remade into the commands (@pxref{Automatic Variables}). For example:
@example
@group
@@ -3070,6 +3084,57 @@ You cannot do this with multiple targets in an ordinary rule, but you
can do it with a @dfn{static pattern rule}. @xref{Static Pattern,
,Static Pattern Rules}.
+@subsubheading Rules with Grouped Targets
+@cindex grouped targets
+@cindex targets, grouped
+
+If instead of independent targets you have a recipe that generates
+multiple files from a single invocation, you can express that
+relationship by declaring your rule to use @emph{grouped targets}. A
+grouped target rule uses the separator @code{&:} (the @samp{&} here is
+used to imply ``all'').
+
+When @code{make} builds any one of the grouped targets, it understands
+that all the other targets in the group are also created as a result
+of the invocation of the recipe. Furthermore, if only some of the
+grouped targets are out of date or missing @code{make} will realize
+that running the recipe will update all of the targets.
+
+As an example, this rule defines a grouped target:
+
+@example
+@group
+foo bar biz &: baz boz
+ echo $^ > foo
+ echo $^ > bar
+ echo $^ > biz
+@end group
+@end example
+
+During the execution of a grouped target's recipe, the automatic
+variable @samp{$@@} is set to the name of the particular target in the
+group which triggered the rule. Caution must be used if relying on
+this variable in the recipe of a grouped target rule.
+
+Unlike independent targets, a grouped target rule @emph{must} include
+a recipe. However, targets that are members of a grouped target may
+also appear in independent target rule definitions that do not have
+recipes.
+
+Each target may have only one recipe associated with it. If a grouped
+target appears in either an independent target rule or in another
+grouped target rule with a recipe, you will get a warning and the
+latter recipe will replace the former recipe. Additionally the target
+will be removed from the previous group and appear only in the new
+group.
+
+If you would like a target to appear in multiple groups, then you must
+use the double-colon grouped target separator, @code{&::} when
+declaring all of the groups containing that target. Grouped
+double-colon targets are each considered independently, and each
+grouped double-colon rule's recipe is executed at most once, if at
+least one of its multiple targets requires updating.
+
@node Multiple Rules, Static Pattern, Multiple Targets, Rules
@section Multiple Rules for One Target
@cindex multiple rules for one target
@@ -9772,20 +9837,13 @@ More than one pattern rule may match a target. In this case
@code{make} will choose the ``best fit'' rule. @xref{Pattern Match,
,How Patterns Match}.
-@c !!! The end of of this paragraph should be rewritten. --bob
-Pattern rules may have more than one target. Unlike normal rules,
-this does not act as many different rules with the same prerequisites
-and recipe. If a pattern rule has multiple targets, @code{make} knows
-that the rule's recipe is responsible for making all of the targets.
-The recipe is executed only once to make all the targets. When
-searching for a pattern rule to match a target, the target patterns of
-a rule other than the one that matches the target in need of a rule
-are incidental: @code{make} worries only about giving a recipe and
-prerequisites to the file presently in question. However, when this
-file's recipe is run, the other targets are marked as having been
-updated themselves.
@cindex multiple targets, in pattern rule
@cindex target, multiple in pattern rule
+Pattern rules may have more than one target; however, every target
+must contain a @code{%} character. Pattern rules are always treated
+as grouped targets (@pxref{Multiple Targets, , Multiple Targets in a
+Rule}) regardless of whether they use the @code{:} or @code{&:}
+separator.
@node Pattern Examples, Automatic Variables, Pattern Intro, Pattern Rules
@subsection Pattern Rule Examples
diff --git a/src/main.c b/src/main.c
index bcaeab76..57e58982 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1315,6 +1315,7 @@ main (int argc, char **argv, char **envp)
{
const char *features = "target-specific order-only second-expansion"
" else-if shortest-stem undefine oneshell nocomment"
+ " grouped-target"
#ifndef NO_ARCHIVES
" archives"
#endif
diff --git a/src/makeint.h b/src/makeint.h
index 315500fc..668d07ee 100644
--- a/src/makeint.h
+++ b/src/makeint.h
@@ -67,6 +67,7 @@ char *alloca ();
# define __NO_STRING_INLINES
#endif
+#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
diff --git a/src/read.c b/src/read.c
index 45f3e35b..fd357ab8 100644
--- a/src/read.c
+++ b/src/read.c
@@ -72,7 +72,7 @@ struct vmodifiers
enum make_word_type
{
w_bogus, w_eol, w_static, w_variable, w_colon, w_dcolon, w_semicolon,
- w_varassign
+ w_varassign, w_ampcolon, w_ampdcolon
};
@@ -142,7 +142,8 @@ static void do_undefine (char *name, enum variable_origin origin,
static struct variable *do_define (char *name, enum variable_origin origin,
struct ebuffer *ebuf);
static int conditional_line (char *line, size_t len, const floc *flocp);
-static void record_files (struct nameseq *filenames, const char *pattern,
+static void record_files (struct nameseq *filenames, int are_also_makes,
+ const char *pattern,
const char *pattern_percent, char *depstr,
unsigned int cmds_started, char *commands,
size_t commands_idx, int two_colon,
@@ -151,7 +152,7 @@ static void record_target_var (struct nameseq *filenames, char *defn,
enum variable_origin origin,
struct vmodifiers *vmod,
const floc *flocp);
-static enum make_word_type get_next_mword (char *buffer, char *delim,
+static enum make_word_type get_next_mword (char *buffer,
char **startp, size_t *length);
static void remove_comments (char *line);
static char *find_map_unquote (char *string, int map);
@@ -574,6 +575,7 @@ eval (struct ebuffer *ebuf, int set_default)
unsigned int cmds_started, tgts_started;
int ignoring = 0, in_ignored_define = 0;
int no_targets = 0; /* Set when reading a rule without targets. */
+ int also_make_targets = 0; /* Set when reading grouped targets. */
struct nameseq *filenames = 0;
char *depstr = 0;
long nlines = 0;
@@ -591,7 +593,8 @@ eval (struct ebuffer *ebuf, int set_default)
{ \
fi.lineno = tgts_started; \
fi.offset = 0; \
- record_files (filenames, pattern, pattern_percent, depstr, \
+ record_files (filenames, also_make_targets, pattern, \
+ pattern_percent, depstr, \
cmds_started, commands, commands_idx, two_colon, \
prefix, &fi); \
filenames = 0; \
@@ -599,6 +602,7 @@ eval (struct ebuffer *ebuf, int set_default)
commands_idx = 0; \
no_targets = 0; \
pattern = 0; \
+ also_make_targets = 0; \
} while (0)
pattern_percent = 0;
@@ -1023,7 +1027,7 @@ eval (struct ebuffer *ebuf, int set_default)
variable we don't want to expand it. So, walk from the
beginning, expanding as we go, and looking for "interesting"
chars. The first word is always expandable. */
- wtype = get_next_mword (line, NULL, &lb_next, &wlen);
+ wtype = get_next_mword (line, &lb_next, &wlen);
switch (wtype)
{
case w_eol:
@@ -1035,6 +1039,8 @@ eval (struct ebuffer *ebuf, int set_default)
case w_colon:
case w_dcolon:
+ case w_ampcolon:
+ case w_ampdcolon:
/* We accept and ignore rules without targets for
compatibility with SunOS 4 make. */
no_targets = 1;
@@ -1080,20 +1086,29 @@ eval (struct ebuffer *ebuf, int set_default)
}
colonp = find_char_unquote (p2, ':');
+
#ifdef HAVE_DOS_PATHS
- /* The drive spec brain-damage strikes again... */
- /* Note that the only separators of targets in this context
- are whitespace and a left paren. If others are possible,
- they should be added to the string in the call to index. */
- while (colonp && (colonp[1] == '/' || colonp[1] == '\\') &&
- colonp > p2 && isalpha ((unsigned char)colonp[-1]) &&
- (colonp == p2 + 1 || strchr (" \t(", colonp[-2]) != 0))
- colonp = find_char_unquote (colonp + 1, ':');
+ if (colonp > p2)
+ /* The drive spec brain-damage strikes again...
+ Note that the only separators of targets in this context are
+ whitespace and a left paren. If others are possible, add them
+ to the string in the call to strchr. */
+ while (colonp && (colonp[1] == '/' || colonp[1] == '\\') &&
+ isalpha ((unsigned char) colonp[-1]) &&
+ (colonp == p2 + 1 || strchr (" \t(", colonp[-2]) != 0))
+ colonp = find_char_unquote (colonp + 1, ':');
#endif
- if (colonp != 0)
- break;
- wtype = get_next_mword (lb_next, NULL, &lb_next, &wlen);
+ if (colonp)
+ {
+ /* If the previous character is '&', back up before '&:' */
+ if (colonp > p2 && colonp[-1] == '&')
+ --colonp;
+
+ break;
+ }
+
+ wtype = get_next_mword (lb_next, &lb_next, &wlen);
if (wtype == w_eol)
break;
@@ -1123,12 +1138,21 @@ eval (struct ebuffer *ebuf, int set_default)
O (fatal, fstart, _("missing separator"));
}
- /* Make the colon the end-of-string so we know where to stop
- looking for targets. Start there again once we're done. */
- *colonp = '\0';
- filenames = PARSE_SIMPLE_SEQ (&p2, struct nameseq);
- *colonp = ':';
- p2 = colonp;
+ {
+ char save = *colonp;
+
+ /* If we have &:, it specifies that the targets are understood to be
+ updated/created together by a single invocation of the recipe. */
+ if (save == '&')
+ also_make_targets = 1;
+
+ /* Make the colon the end-of-string so we know where to stop
+ looking for targets. Start there again once we're done. */
+ *colonp = '\0';
+ filenames = PARSE_SIMPLE_SEQ (&p2, struct nameseq);
+ *colonp = save;
+ p2 = colonp + (save == '&');
+ }
if (!filenames)
{
@@ -1930,7 +1954,8 @@ record_target_var (struct nameseq *filenames, char *defn,
that are not incorporated into other data structures. */
static void
-record_files (struct nameseq *filenames, const char *pattern,
+record_files (struct nameseq *filenames, int are_also_makes,
+ const char *pattern,
const char *pattern_percent, char *depstr,
unsigned int cmds_started, char *commands,
size_t commands_idx, int two_colon,
@@ -1938,6 +1963,7 @@ record_files (struct nameseq *filenames, const char *pattern,
{
struct commands *cmds;
struct dep *deps;
+ struct dep *also_make = NULL;
const char *implicit_percent;
const char *name;
@@ -1963,8 +1989,10 @@ record_files (struct nameseq *filenames, const char *pattern,
cmds->command_lines = 0;
cmds->recipe_prefix = prefix;
}
+ else if (are_also_makes)
+ O (fatal, flocp, _("grouped targets must provide a recipe"));
else
- cmds = 0;
+ cmds = NULL;
/* If there's a prereq string then parse it--unless it's eligible for 2nd
expansion: if so, snap_deps() will do it. */
@@ -2159,6 +2187,15 @@ record_files (struct nameseq *filenames, const char *pattern,
f->cmds = cmds;
}
+ if (are_also_makes)
+ {
+ struct dep *also = alloc_dep();
+ also->name = f->name;
+ also->file = f;
+ also->next = also_make;
+ also_make = also;
+ }
+
f->is_target = 1;
/* If this is a static pattern rule, set the stem to the part of its
@@ -2223,6 +2260,29 @@ record_files (struct nameseq *filenames, const char *pattern,
O (error, flocp,
_("*** mixed implicit and normal rules: deprecated syntax"));
}
+
+ /* If there are also-makes, then populate a copy of the also-make list into
+ each one. For the last file, we take our original also_make list instead
+ wastefully copying it one more time and freeing it. */
+ {
+ struct dep *i;
+
+ for (i = also_make; i != NULL; i = i->next)
+ {
+ struct file *f = i->file;
+ struct dep *cpy = i->next ? copy_dep_chain (also_make) : also_make;
+
+ if (f->also_make)
+ {
+ OS (error, &cmds->fileinfo,
+ _("warning: overriding group membership for target '%s'"),
+ f->name);
+ free_dep_chain (f->also_make);
+ }
+
+ f->also_make = cpy;
+ }
+ }
}
/* Search STRING for an unquoted STOPMAP.
@@ -2660,6 +2720,8 @@ readline (struct ebuffer *ebuf)
w_variable A word containing one or more variables/functions
w_colon A colon
w_dcolon A double-colon
+ w_ampcolon An ampersand-colon (&:) token
+ w_ampdcolon An ampersand-double-colon (&::) token
w_semicolon A semicolon
w_varassign A variable assignment operator (=, :=, ::=, +=, ?=, or !=)
@@ -2668,7 +2730,7 @@ readline (struct ebuffer *ebuf)
in a command list, etc.) */
static enum make_word_type
-get_next_mword (char *buffer, char *delim, char **startp, size_t *length)
+get_next_mword (char *buffer, char **startp, size_t *length)
{
enum make_word_type wtype;
char *p = buffer, *beg;
@@ -2717,6 +2779,21 @@ get_next_mword (char *buffer, char *delim, char **startp, size_t *length)
wtype = w_colon;
goto done;
+ case '&':
+ if (*p == ':')
+ {
+ ++p;
+ if (*p != ':')
+ wtype = w_ampcolon; /* &: */
+ else
+ {
+ ++p;
+ wtype = w_ampdcolon; /* &:: */
+ }
+ goto done;
+ }
+ break;
+
case '+':
case '?':
case '!':
@@ -2726,19 +2803,15 @@ get_next_mword (char *buffer, char *delim, char **startp, size_t *length)
wtype = w_varassign; /* += or ?= or != */
goto done;
}
- /* FALLTHROUGH */
+ break;
default:
- if (delim && strchr (delim, c))
- {
- wtype = w_static;
- goto done;
- }
+ break;
}
/* This is some non-operator word. A word consists of the longest
string of characters that doesn't contain whitespace, one of [:=#],
- or [?+!]=, or one of the chars in the DELIM string. */
+ or [?+!]=, or &:. */
/* We start out assuming a static word; if we see a variable we'll
adjust our assumptions then. */
@@ -2818,10 +2891,13 @@ get_next_mword (char *buffer, char *delim, char **startp, size_t *length)
}
break;
- default:
- if (delim && strchr (delim, c))
+ case '&':
+ if (*p == ':')
goto done_word;
break;
+
+ default:
+ break;
}
c = *(p++);
diff --git a/tests/scripts/features/grouped_targets b/tests/scripts/features/grouped_targets
new file mode 100644
index 00000000..ef9366b7
--- /dev/null
+++ b/tests/scripts/features/grouped_targets
@@ -0,0 +1,133 @@
+# -*-perl-*-
+
+$description = "This test is about grouped multiple targets indicated by &:";
+$details = "Here we test for requirements like\n"
+ ."- if multiple such targets are updated, the recipe is run once\n"
+ ."- parsing issues related to the &: syntax itself\n";
+
+# Parsing: &: allowed without any targets.
+run_make_test(q{
+.PHONY: all
+&:;
+all: ;@echo -n
+},
+'', "");
+
+# Parsing: &: works not preceded by whitespace.
+run_make_test(q{
+foo&:;@echo foo
+},
+'foo', "foo");
+
+# Ordinary rule runs recipe four times for t1 t2 t3 t4.
+# Grouped target rule runs recipe once; others are considered updated.
+run_make_test(q{
+.PHONY: t1 t2 t3 t4 g1 g2 g3 g4
+t1 t2 t3 t4: ; @echo $@
+g1 g2 g3 g4 &: ; @echo $@
+},
+'t1 t2 t3 t4 g1 g2 g3 g4',
+"t1\n"
+."t2\n"
+."t3\n"
+."t4\n"
+."g1\n"
+."#MAKE#: Nothing to be done for 'g2'.\n"
+."#MAKE#: Nothing to be done for 'g3'.\n"
+."#MAKE#: Nothing to be done for 'g4'.");
+
+# Similar to previous test, but targets come from m1 phony
+# rather than from the command line. We don't see "Nothing to
+# be done for" messages. Also, note reversed order g4 g3 ...
+# Thus the auto variable $@ is "g4" when that rule fires.
+run_make_test(q{
+.PHONY: m1 t1 t2 t3 t4 g1 g2 g3 g4
+m1: t1 t2 t3 t4 g4 g3 g2 g1
+t1 t2 t3 t4: ; @echo $@
+g1 g2 g3 g4&: ; @echo $@
+},
+'',
+"t1\nt2\nt3\nt4\ng4");
+
+# Set a grouped target recipe for existing targets
+run_make_test(q{
+.PHONY: M a b
+M: a b
+a:
+a b&: ; @echo Y
+b:
+},
+'',
+"Y");
+
+# grouped targets require a recipe
+run_make_test(q{
+.PHONY: M a b
+M: a b
+a b&:
+},
+'',
+"#MAKEFILE#:4: *** grouped targets must provide a recipe. Stop.", 512);
+
+# Pattern rules use grouped targets anyway so it's a no-op
+run_make_test(q{
+.PHONY: M
+M: a.q b.q
+a.% b.%&: ; @echo Y
+},
+'',
+"Y");
+
+# Double-colon grouped target rules.
+run_make_test(q{
+.PHONY: M a b c d e f g h
+M: a b
+a b c&:: ; @echo X
+c d e&:: ; @echo Y
+f g h&:: ; @echo Z
+},
+'',
+"X");
+
+run_make_test(q{
+.PHONY: M a b c d e f g h
+M: c
+a b c&:: ; @echo X
+c d e&:: ; @echo Y
+f g h&:: ; @echo Z
+},
+'',
+"X\nY");
+
+run_make_test(q{
+.PHONY: M a b c d e f g h
+M: a b c d e
+a b c&:: ; @echo X
+c d e&:: ; @echo Y
+f g h&:: ; @echo Z
+},
+'',
+"X\nY");
+
+run_make_test(q{
+.PHONY: M a b c d e f g h
+M: d e
+a b c&:: ; @echo X
+c d e&:: ; @echo Y
+f g h&:: ; @echo Z
+},
+'',
+"Y");
+
+run_make_test(q{
+.PHONY: M a b c d e f g h
+M: f g h
+a b c&:: ; @echo X
+c d e&:: ; @echo Y
+f g h&:: ; @echo Z
+},
+'',
+"Z");
+
+# This tells the test driver that the perl test script executed properly.
+1;