Support the .EXTRA_PREREQS special variable

Initial implementation by Christof Warlich <cwarlich@gmx.de>

* NEWS: Announce the new feature.
* doc/make.texi (Other Special Variables): Document .EXTRA_PREREQS.
* src/dep.h (struct dep): New flag to note extra prereq deps.
* src/filedef.h (expand_extra_prereqs): Declare a function to expand
the value of .EXTRA_PREREQS.
* src/file.c (expand_extra_prereqs): Given a struct variable lookup
of .EXTRA_PREREQS, convert it into a list of deps and for each one
make sure it has a struct file and has the new flag set.
(snap_file): A new function invoked by hash_map that will perform
per-file operations: set up second expansion, intermediate, and also
.EXTRA_PREREQS.  Manage circular dependencies by ignoring them.
(snap_deps): Defer per-file operations until the end.  Look up the
global .EXTRA_PREREQS and pass it along to snap_file for each file.
* src/implicit.c (struct patdeps): Remember the extra prereqs flag.
(pattern_search): Transfer extra prereqs flag settings into the
matched pattern rule.
* src/rule.h (snap_implicit_rules): Rename count_implicit_rules to
snap_implicit_rules since we now do more than count.
* src/rule.c (snap_implicit_rules): As we walk through all the pattern
rules, add in any global .EXTRA_PREREQS to the dep list.  Ensure we
take them into account for the max number of prereqs and name length.
* src/main.c (main): Add extra-prereqs to .FEATURES.
Call the renamed snap_implicit_rules.
* tests/scripts/variables/EXTRA_PREREQS: Add tests.
This commit is contained in:
Paul Smith 2020-01-02 05:08:06 -05:00
parent e56243fe57
commit 4e12a5fa45
12 changed files with 340 additions and 52 deletions

View File

@ -68,6 +68,7 @@ Other contributors:
David Boyce <dsb@boyski.com>
Frank Heckenbach <f.heckenbach@fh-soft.de>
Kaz Kylheku <kaz@kylheku.com>
Christof Warlich <cwarlich@gmx.de>
With suggestions/comments/bug reports from a cast of ... well ...
hundreds, anyway :)

7
NEWS
View File

@ -48,6 +48,13 @@ http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=108&set
search for 'grouped-target' in the .FEATURES special variable.
Implementation contributed by Kaz Kylheku <kaz@kylheku.com>
* New feature: .EXTRA_PREREQS variable
Words in this variable are considered prerequisites of targets but they are
not added to any of the automatic variable values when expanding the
recipe. This variable can either be global (applies to all targets) or
a target-specific variable. To detect this feature search for 'extra-prereqs'
in the .FEATURES special variable.
* Makefiles can now specify the '-j' option in their MAKEFLAGS variable and
this will cause make to enable that parallelism mode.

View File

@ -6631,6 +6631,57 @@ Supports dynamically loadable objects for creating custom extensions.
Expands to a list of directories that @code{make} searches for
included makefiles (@pxref{Include, , Including Other Makefiles}).
@vindex .EXTRA_PREREQS @r{(prerequisites not added to automatic variables)}
@item .EXTRA_PREREQS
Each word in this variable is a new prerequisite which is added to
targets for which it is set. These prerequisites differ from normal
prerequisites in that they do not appear in any of the automatic
variables (@pxref{Automatic Variables}). This allows prerequisites to
be defined which do not impact the recipe.
Consider a rule to link a program:
@example
myprog: myprog.o file1.o file2.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@@ $^ $(LDLIBS)
@end example
Now suppose you want to enhance this makefile to ensure that updates
to the compiler cause the program to be re-linked. You can add the
compiler as a prerequisite, but you must ensure that it's not passed
as an argument to link command. You'll need something like this:
@example
myprog: myprog.o file1.o file2.o $(CC)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@@ $(filter-out $(CC),$^) $(LDLIBS)
@end example
Then consider having multiple extra prerequisites: they would all have
to be filtered out. Using @code{.EXTRA_PREREQS} and target-specific
variables provides a simpler solution:
@example
myprog: myprog.o file1.o file2.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@@ $^ $(LDLIBS)
myprog: .EXTRA_PREREQS = $(CC)
@end example
This feature can also be useful if you want to add prerequisites to a
makefile you cannot easily modify: you can create a new file such as
@file{extra.mk}:
@example
myprog: .EXTRA_PREREQS = $(CC)
@end example
then invoke @code{make -f extra.mk -f Makefile}.
Setting @code{.EXTRA_PREREQS} globally will cause those prerequisites
to be added to all targets (which did not themselves override it with
a target-specific value). Note @code{make} is smart enough not to add
a prerequisite listed in @code{.EXTRA_PREREQS} as a prerequisite to
itself.
@end table
@node Conditionals, Functions, Using Variables, Top

View File

@ -133,7 +133,7 @@ set_file_variables (struct file *file)
/* $< is the first not order-only dependency. */
less = "";
for (d = file->deps; d != 0; d = d->next)
if (!d->ignore_mtime)
if (!d->ignore_mtime && !d->ignore_automatic_vars)
{
if (!d->need_2nd_expansion)
less = dep_name (d);
@ -178,7 +178,7 @@ set_file_variables (struct file *file)
bar_len = 0;
for (d = file->deps; d != 0; d = d->next)
{
if (!d->need_2nd_expansion)
if (!d->need_2nd_expansion && !d->ignore_automatic_vars)
{
if (d->ignore_mtime)
bar_len += strlen (dep_name (d)) + 1;
@ -200,7 +200,7 @@ set_file_variables (struct file *file)
qmark_len = plus_len + 1; /* Will be this or less. */
for (d = file->deps; d != 0; d = d->next)
if (! d->ignore_mtime && ! d->need_2nd_expansion)
if (! d->ignore_mtime && ! d->need_2nd_expansion && ! d->ignore_automatic_vars)
{
const char *c = dep_name (d);
@ -247,7 +247,7 @@ set_file_variables (struct file *file)
for (d = file->deps; d != 0; d = d->next)
{
if (d->need_2nd_expansion)
if (d->need_2nd_expansion || d->ignore_automatic_vars)
continue;
slot = hash_find_slot (&dep_hash, d);
@ -269,7 +269,7 @@ set_file_variables (struct file *file)
{
const char *c;
if (d->need_2nd_expansion || hash_find_item (&dep_hash, d) != d)
if (d->need_2nd_expansion || d->ignore_automatic_vars || hash_find_item (&dep_hash, d) != d)
continue;
c = dep_name (d);

View File

@ -48,7 +48,8 @@ struct nameseq
unsigned int changed : 1; \
unsigned int ignore_mtime : 1; \
unsigned int staticpattern : 1; \
unsigned int need_2nd_expansion : 1
unsigned int need_2nd_expansion : 1; \
unsigned int ignore_automatic_vars : 1
struct dep
{

View File

@ -550,15 +550,6 @@ enter_prereqs (struct dep *deps, const char *stem)
return deps;
}
/* Set the intermediate flag. */
static void
set_intermediate (const void *item)
{
struct file *f = (struct file *) item;
f->intermediate = 1;
}
/* Expand and parse each dependency line. */
static void
expand_deps (struct file *f)
@ -644,13 +635,69 @@ expand_deps (struct file *f)
}
}
/* Reset the updating flag. */
/* Add extra prereqs to the file in question. */
struct dep *
expand_extra_prereqs (const struct variable *extra)
{
struct dep *prereqs = extra ? split_prereqs (variable_expand (extra->value)) : NULL;
for (struct dep *d = prereqs; d; d = d->next)
{
d->file = lookup_file (d->name);
if (!d->file)
d->file = enter_file (d->name);
d->name = NULL;
d->ignore_automatic_vars = 1;
}
return prereqs;
}
/* Perform per-file snap operations. */
static void
reset_updating (const void *item)
snap_file (const void *item, void *arg)
{
struct file *f = (struct file *) item;
f->updating = 0;
struct file *f = (struct file*)item;
struct dep *prereqs = NULL;
/* If we're not doing second expansion then reset updating. */
if (!second_expansion)
f->updating = 0;
/* If .SECONDARY is set with no deps, mark all targets as intermediate. */
if (all_secondary)
f->intermediate = 1;
/* If .EXTRA_PREREQS is set, add them as ignored by automatic variables. */
if (f->variables)
prereqs = expand_extra_prereqs (lookup_variable_in_set (STRING_SIZE_TUPLE(".EXTRA_PREREQS"), f->variables->set));
else if (f->is_target)
prereqs = copy_dep_chain (arg);
if (prereqs)
{
struct dep *d;
for (d = prereqs; d; d = d->next)
if (streq (f->name, dep_name (d)))
/* Skip circular dependencies. */
break;
if (d)
/* We broke early: must have found a circular dependency. */
free_dep_chain (prereqs);
else if (!f->deps)
f->deps = prereqs;
else
{
d = f->deps;
while (d->next)
d = d->next;
d->next = prereqs;
}
}
}
/* For each dependency of each file, make the 'struct dep' point
@ -700,9 +747,6 @@ snap_deps (void)
expand_deps (f);
free (file_slot_0);
}
else
/* We're not doing second expansion, so reset updating. */
hash_map (&files, reset_updating);
/* Now manage all the special targets. */
@ -744,10 +788,7 @@ snap_deps (void)
f2->intermediate = f2->secondary = 1;
/* .SECONDARY with no deps listed marks *all* files that way. */
else
{
all_secondary = 1;
hash_map (&files, set_intermediate);
}
all_secondary = 1;
f = lookup_file (".EXPORT_ALL_VARIABLES");
if (f != 0 && f->is_target)
@ -779,6 +820,15 @@ snap_deps (void)
if (f != 0 && f->is_target)
not_parallel = 1;
{
struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS")));
/* Perform per-file snap operations. */
hash_map_arg(&files, snap_file, prereqs);
free_dep_chain (prereqs);
}
#ifndef NO_MINUS_C_MINUS_O
/* If .POSIX was defined, remove OUTPUT_OPTION to comply. */
/* This needs more work: what if the user sets this in the makefile?

View File

@ -21,6 +21,11 @@ this program. If not, see <http://www.gnu.org/licenses/>. */
#include "hash.h"
struct commands;
struct dep;
struct variable;
struct variable_set_list;
struct file
{
const char *name;
@ -110,6 +115,7 @@ struct file *lookup_file (const char *name);
struct file *enter_file (const char *name);
struct dep *split_prereqs (char *prereqstr);
struct dep *enter_prereqs (struct dep *prereqs, const char *stem);
struct dep *expand_extra_prereqs (const struct variable *extra);
void remove_intermediates (int sig);
void snap_deps (void);
void rename_file (struct file *file, const char *name);

View File

@ -152,6 +152,7 @@ struct patdeps
const char *pattern;
struct file *file;
unsigned int ignore_mtime : 1;
unsigned int ignore_automatic_vars : 1;
};
/* This structure stores information about pattern rules that we need
@ -564,6 +565,7 @@ pattern_search (struct file *file, int archive,
{
++deps_found;
d->ignore_mtime = dep->ignore_mtime;
d->ignore_automatic_vars = dep->ignore_automatic_vars;
}
/* We've used up this dep, so next time get a new one. */
@ -723,6 +725,7 @@ pattern_search (struct file *file, int archive,
memset (pat, '\0', sizeof (struct patdeps));
pat->ignore_mtime = d->ignore_mtime;
pat->ignore_automatic_vars = d->ignore_automatic_vars;
DBS (DB_IMPLICIT,
(is_rule
@ -913,6 +916,7 @@ pattern_search (struct file *file, int archive,
dep = alloc_dep ();
dep->ignore_mtime = pat->ignore_mtime;
dep->ignore_automatic_vars = pat->ignore_automatic_vars;
s = strcache_add (pat->name);
if (recursions)
dep->name = s;

View File

@ -1313,7 +1313,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"
" grouped-target extra-prereqs"
#ifndef NO_ARCHIVES
" archives"
#endif
@ -2134,9 +2134,9 @@ main (int argc, char **argv, char **envp)
install_default_implicit_rules ();
/* Compute implicit rule limits. */
/* Compute implicit rule limits and do magic for pattern rules. */
count_implicit_rule_limits ();
snap_implicit_rules ();
/* Construct the listings of directories in VPATH lists. */

View File

@ -60,36 +60,43 @@ struct file *suffix_file;
static size_t maxsuffix;
/* Compute the maximum dependency length and maximum number of
dependencies of all implicit rules. Also sets the subdir
flag for a rule when appropriate, possibly removing the rule
completely when appropriate. */
/* Compute the maximum dependency length and maximum number of dependencies of
all implicit rules. Also sets the subdir flag for a rule when appropriate,
possibly removing the rule completely when appropriate.
Add any global EXTRA_PREREQS here as well. */
void
count_implicit_rule_limits (void)
snap_implicit_rules (void)
{
char *name;
size_t namelen;
struct rule *rule;
char *name = NULL;
size_t namelen = 0;
struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS")));
unsigned int pre_deps = 0;
num_pattern_rules = max_pattern_targets = max_pattern_deps = 0;
max_pattern_dep_length = 0;
name = 0;
namelen = 0;
rule = pattern_rules;
while (rule != 0)
for (struct dep *d = prereqs; d; d = d->next)
{
unsigned int ndeps = 0;
struct dep *dep;
struct rule *next = rule->next;
size_t l = strlen (dep_name (d));
if (l > max_pattern_dep_length)
max_pattern_dep_length = l;
++pre_deps;
}
num_pattern_rules = max_pattern_targets = max_pattern_deps = 0;
for (struct rule *rule = pattern_rules; rule; rule = rule->next)
{
unsigned int ndeps = pre_deps;
struct dep *lastdep = NULL;
++num_pattern_rules;
if (rule->num > max_pattern_targets)
max_pattern_targets = rule->num;
for (dep = rule->deps; dep != 0; dep = dep->next)
for (struct dep *dep = rule->deps; dep != 0; dep = dep->next)
{
const char *dname = dep_name (dep);
size_t len = strlen (dname);
@ -99,17 +106,20 @@ count_implicit_rule_limits (void)
const char *p2;
if (p == 0)
p = strrchr (dname, ':');
p2 = p != 0 ? strchr (dname, '%') : 0;
p2 = p ? strchr (p, '%') : 0;
#else
const char *p = strrchr (dname, '/');
const char *p2 = p != 0 ? strchr (dname, '%') : 0;
const char *p2 = p ? strchr (p, '%') : 0;
#endif
ndeps++;
if (len > max_pattern_dep_length)
max_pattern_dep_length = len;
if (p != 0 && p2 > p)
if (!dep->next)
lastdep = dep;
if (p2)
{
/* There is a slash before the % in the dep name.
Extract the directory name. */
@ -134,13 +144,20 @@ count_implicit_rule_limits (void)
dep->changed = 0;
}
if (prereqs)
{
if (lastdep)
lastdep->next = copy_dep_chain (prereqs);
else
rule->deps = copy_dep_chain (prereqs);
}
if (ndeps > max_pattern_deps)
max_pattern_deps = ndeps;
rule = next;
}
free (name);
free_dep_chain (prereqs);
}
/* Create a pattern rule from a suffix rule.

View File

@ -48,7 +48,7 @@ extern size_t max_pattern_dep_length;
extern struct file *suffix_file;
void count_implicit_rule_limits (void);
void snap_implicit_rules (void);
void convert_to_pattern (void);
void install_pattern_rule (struct pspec *p, int terminal);
void create_pattern_rule (const char **targets, const char **target_percents,

View File

@ -0,0 +1,151 @@
# -*-perl-*-
$description = "Test the .EXTRA_PREREQS special variable.";
$details = "";
# Simple global .EXTRA_PREREQS and automatic variable settings
run_make_test('
.EXTRA_PREREQS = tick tack
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS}/$@/$</$^/$?/$+/$|/$*/
tick tack: ; @echo $@
',
'', "tick\ntack\ntick tack/all///////\n");
# Global .EXTRA_PREREQS and pattern rules
run_make_test('
.EXTRA_PREREQS = tick tack
a%: ; @echo ${.EXTRA_PREREQS}/$@/$</$^/$?/$+/$|/$*/
tick tack: ; @echo $@
',
'all', "tick\ntack\ntick tack/all//////ll/\n");
# Simple target-specific .EXTRA_PREREQS and automatic variable settings
run_make_test('
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS}/$@/$</$^/$?/$+/$|/$*/
all: .EXTRA_PREREQS = tick tack
tick tack: ; @echo $@
',
'', "tick\ntack\ntick tack/all///////\n");
# Simple pattern-specific .EXTRA_PREREQS and automatic variable settings
# This is not currently supported :-/
if (0) {
run_make_test('
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS}/$@/$</$^/$?/$+/$|/$*/
a%: .EXTRA_PREREQS = tick tack
tick tack: ; @echo $@
',
'', "tick\ntack\ntick tack/all///////\n");
}
touch('hi');
# Basic test target specific .EXTRA_PREREQS:
run_make_test('
DEPENDENCY_ONLY_PREREQUISITES = ho hey
OTHER_PREREQUISITES := foo bar baz
target: .EXTRA_PREREQS := hi ${DEPENDENCY_ONLY_PREREQUISITES}
target: ${OTHER_PREREQUISITES} ; @echo ${.EXTRA_PREREQS} $^
.PHONY: target ${DEPENDENCY_ONLY_PREREQUISITES} ${OTHER_PREREQUISITES}
${DEPENDENCY_ONLY_PREREQUISITES} ${OTHER_PREREQUISITES}: ; @echo $@
',
'', "foo\nbar\nbaz\nho\nhey\nhi ho hey foo bar baz\n");
# Test target specific .EXTRA_PREREQS and pattern rules:
run_make_test('
all: target.dst
DEPENDENCY_ONLY_PREREQUISITES = ho hey
target.dst: .EXTRA_PREREQS := hi ${DEPENDENCY_ONLY_PREREQUISITES}
%.dst: %.src ; @echo ${.EXTRA_PREREQS} $^
.PHONY: ${DEPENDENCY_ONLY_PREREQUISITES} target.src
${DEPENDENCY_ONLY_PREREQUISITES} target.src: ; @echo $@
',
'', "target.src\nho\nhey\nhi ho hey target.src\n");
# Test that global .EXTRA_PREREQS are built first:
run_make_test('
.EXTRA_PREREQS = hi ho hey
OTHER_PREREQUISITES := foo bar baz
target: ${OTHER_PREREQUISITES} ; @echo ${.EXTRA_PREREQS} $^
.PHONY: target ${.EXTRA_PREREQS} ${OTHER_PREREQUISITES}
${.EXTRA_PREREQS} ${OTHER_PREREQUISITES}: ; @echo $@
',
'', "hi\nho\nhey\nfoo\nbar\nbaz\nhi ho hey foo bar baz\n");
# Test that target specific .EXTRA_PREREQS override global .EXTRA_PREREQS:
run_make_test('
.EXTRA_PREREQS = tick tack
DEPENDENCY_ONLY_PREREQUISITES = ho hey
OTHER_PREREQUISITES := foo bar baz
target: .EXTRA_PREREQS := hi ${DEPENDENCY_ONLY_PREREQUISITES}
target: ${OTHER_PREREQUISITES} ; @echo ${.EXTRA_PREREQS} $^
.PHONY: target ${DEPENDENCY_ONLY_PREREQUISITES} ${OTHER_PREREQUISITES} ${.EXTRA_PREREQS}
${DEPENDENCY_ONLY_PREREQUISITES} ${OTHER_PREREQUISITES} ${.EXTRA_PREREQS}: ; @echo $@
',
'', "tick\ntack\nfoo\nbar\nbaz\nho\nhey\nhi ho hey foo bar baz\n");
# Cleanup:
unlink('hi');
# Test error reporting of global .EXTRA_PREREQS:
run_make_test('
.EXTRA_PREREQS = tick tack
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS} $^
',
'', "#MAKE#: *** No rule to make target 'tick', needed by 'all'. Stop.", 512);
# Test error reporting of global .EXTRA_PREREQS and keep-going:
run_make_test('
.EXTRA_PREREQS = tick tack
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS} $^
',
'-k', "#MAKE#: *** No rule to make target 'tick', needed by 'all'.\n#MAKE#: *** No rule to make target 'tack', needed by 'all'.\n#MAKE#: Target 'all' not remade because of errors.", 512);
# Test error reporting of target specific .EXTRA_PREREQS and keep-going:
run_make_test('
all: .EXTRA_PREREQS = tick tack
.PHONY: all
all: ; @echo ${.EXTRA_PREREQS} $^
',
'-k',
"#MAKE#: *** No rule to make target 'tick', needed by 'all'.
#MAKE#: *** No rule to make target 'tack', needed by 'all'.
#MAKE#: Target 'all' not remade because of errors.\n", 512);
# Test wildcard
touch('tick', 'tack');
run_make_test('
.EXTRA_PREREQS = *ck
.PHONY: all tick tack
all: ; @echo ${.EXTRA_PREREQS} $^
tick tack: ; @echo $@
',
'', "tack\ntick\ntack tick\n");
run_make_test('
.PHONY: all tick tack
all: ; @echo ${.EXTRA_PREREQS} $^
all: .EXTRA_PREREQS = *ck
tick tack: ; @echo $@
',
'', "tack\ntick\ntack tick\n");
run_make_test('
.PHONY: tick tack
a%: ; @echo ${.EXTRA_PREREQS} $^
.EXTRA_PREREQS = *ck
tick tack: ; @echo $@
',
'all', "tack\ntick\ntack tick\n");
unlink('tick', 'tack');
# This tells the test driver that the perl test script executed properly.
1;