[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
This commit is contained in:
Kaz Kylheku 2019-05-11 18:35:03 -04:00 committed by Paul Smith
parent 1710573272
commit 8c888d95f6
7 changed files with 337 additions and 59 deletions

View File

@ -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 :)

8
NEWS
View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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, ':');
#endif
if (colonp != 0)
break;
wtype = get_next_mword (lb_next, NULL, &lb_next, &wlen);
#ifdef HAVE_DOS_PATHS
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)
{
/* 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++);

View File

@ -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;