From 8c888d95f61814dd698bf76126a9079234080d77 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Sat, 11 May 2019 18:35:03 -0400 Subject: [PATCH] [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 --- AUTHORS | 1 + NEWS | 8 ++ doc/make.texi | 104 +++++++++++++---- src/main.c | 1 + src/makeint.h | 1 + src/read.c | 148 +++++++++++++++++++------ tests/scripts/features/grouped_targets | 133 ++++++++++++++++++++++ 7 files changed, 337 insertions(+), 59 deletions(-) create mode 100644 tests/scripts/features/grouped_targets diff --git a/AUTHORS b/AUTHORS index 685aede8..e80bad91 100644 --- a/AUTHORS +++ b/AUTHORS @@ -67,6 +67,7 @@ Other contributors: David A. Wheeler David Boyce Frank Heckenbach + Kaz Kylheku 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 + * 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 #include #include #include 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, ':'); -#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++); 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;