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 :)
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.
@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
@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
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
@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
{
const char *features = "target-specific order-only second-expansion"
" else-if shortest-stem undefine oneshell nocomment"
+ " grouped-target"
#ifndef NO_ARCHIVES
" archives"
#endif
# define __NO_STRING_INLINES
#endif
+#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
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
};
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,
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);
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;
{ \
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; \
commands_idx = 0; \
no_targets = 0; \
pattern = 0; \
+ also_make_targets = 0; \
} while (0)
pattern_percent = 0;
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:
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;
}
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;
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)
{
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,
{
struct commands *cmds;
struct dep *deps;
+ struct dep *also_make = NULL;
const char *implicit_percent;
const char *name;
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. */
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
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;
+ }
+ }
}
\f
/* Search STRING for an unquoted STOPMAP.
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 !=)
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;
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 '!':
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. */
}
break;
- default:
- if (delim && strchr (delim, c))
+ case '&':
+ if (*p == ':')
goto done_word;
break;
+
+ default:
+ break;
}
c = *(p++);
--- /dev/null
+# -*-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;