make/tests/scripts/variables/append
Dmitry Goncharov 07187db947 [SV 64822, SV 36486] Fix appending to a pattern specific variable
Appending to a pattern specific variable produces an incorrect value
in the presence of a command line definition or an env override of
the variable.  Also, fix pattern/target-specific appending to a
variable with origin override.

* At parse time record_target_var sets the value of a pattern
  specific variable to the value defined on command line or to the
  value of the env override.
* Later, at build time, recursively_expand_for_file appends this
  value of the variable (set in record_target_var) to the command
  line value again, regardless of the origin of the variable.

This patch modifies recursively_expand_for_file to avoid appending,
unless the origin of the variable beats or equals the origin of one
of the parent definitions of this variable.

Reported by Rob <robw9739@gmail.com>,
Brian Vandenberg <phantall@gmail.com>,
Markus Oberhumer <markus@oberhumer.com>.

* NEWS: Note the change.
* src/variable.c (do_variable_definition): Avoid merging a
pattern-specific variable with the parent definition when a command
line or env override is present.
* src/expand.c (recursively_expand_for_file): Avoid appending to a
pattern-specific variable, unless the origin of this pattern-specific
variable beats or equals the origin of one of the parent definitions
of this variable.
* doc/make.texi (Override Directive): Add missing cross-reference.
* tests/scripts/variables/append: Add tests.
2024-02-04 18:26:21 -05:00

281 lines
8.4 KiB
Perl

# -*-perl-*-
use warnings;
my $description = "Test appending to variables of various origins.";
# sv 64822, sv 36486.
# Test various combinations of appends in the presence and absence of command
# line definitions, env overrides and makefile overrides.
# Test the following statements.
# A target or pattern specific definition or append is available at build time
# only.
# A definition with a makefile override can only be appended to with another
# override.
# A definition with a makefile override can only be redefined with another
# override.
# A target or pattern specific definition takes precedence over a global
# definition for that target.
# A global variable is immune to a target or pattern specific definition or
# append.
# A target or pattern specific definition is immune to another target or pattern
# specific definition or append.
# A prerequisite inherits a target or pattern specific value from its target.
# "phobos" inherits the value of "hello" from "mars".
my @goals = ('phobos', 'mars');
my @specificities = ('', 's: ', '%: ');
my @inits = ('', 'hello=file', 'hello:=file', 'override hello=file');
my @global_append = ('', 'hello+=red');
my @blue_override = ('', 'override ');
my @yellow_override = ('', 'override ');
my @cmdline = ('', 'hello=cmd', 'hello:=cmd hello+=cmd2');
my @env_ovr = ('', '-e');
my @envs = ('', 'env');
for my $goal (@goals) {
for my $spec (@specificities) {
for my $init (@inits) {
for my $ga (@global_append) {
for my $bovr (@blue_override) {
for my $yovr (@yellow_override) {
for my $dashe (@env_ovr) {
for my $env (@envs) {
for my $cmd (@cmdline) {
if ($env) {
$ENV{'hello'} = 'env';
} else {
delete $ENV{'hello'};
}
my $parse_time_answer = '';
my $build_time_answer = '';
# For goal "phobos" target is "", "phobos: " and "phobo%: ".
# For goal "mars" target is "", "mars: " and "mar%: ".
my $target = '';
if ($spec) {
$target = $goal;
chop($target);
$target .= $spec;
}
if ($init =~ 'override') {
$build_time_answer = 'file';
} elsif ($cmd) {
$build_time_answer = $cmd =~ 'cmd2' ? 'cmd cmd2' : 'cmd';
} elsif ($dashe and $env) {
$build_time_answer = 'env';
} elsif ($init and !$target and $ga) {
$build_time_answer = 'file red';
} elsif ($init) {
$build_time_answer = 'file';
} elsif ($env and $ga) {
$build_time_answer = 'env red';
} elsif ($env) {
$build_time_answer = 'env';
} elsif ($ga) {
$build_time_answer = 'red';
}
if ($bovr and $yovr) {
$build_time_answer .= ' ' if ($build_time_answer);
$build_time_answer .= 'blue yellow';
} elsif ($bovr) {
$build_time_answer .= ' ' if ($build_time_answer);
$build_time_answer .= 'blue';
} elsif ($yovr and !($init =~ 'override') and !$cmd and !($dashe and $env)) {
$build_time_answer .= ' ' if ($build_time_answer);
$build_time_answer .= 'blue yellow';
} elsif ($yovr) {
$build_time_answer .= ' ' if ($build_time_answer);
$build_time_answer .= 'yellow';
} elsif ($init =~ 'override') {
} elsif ($cmd) {
} elsif ($dashe and $env) {
} else {
$build_time_answer .= ' ' if ($build_time_answer);
$build_time_answer .= 'blue yellow';
}
if ($cmd and $target) {
$parse_time_answer = $cmd =~ 'cmd2' ? 'cmd cmd2' : 'cmd';
} elsif ($env and !$dashe and $target and $ga) {
$parse_time_answer = 'env red';
} elsif ($env and $target) {
$parse_time_answer = 'env';
} elsif ($target and $ga) {
$parse_time_answer = 'red';
} elsif ($target) {
$parse_time_answer = '';
} else {
$parse_time_answer = $build_time_answer;
}
my $i = $init ? "$target$init" : '';
# moon and satur% specific settings test that target and pattern
# settings specific to one target do not affect another target.
my $answer = $goal eq "mars" ?
"$parse_time_answer\n$build_time_answer\n" # From parse time and from "phobos" recipe.
. "$build_time_answer\n#MAKE#: 'mars' is up to date.\n" : # From "mars" recipe.
"$parse_time_answer\n$build_time_answer\n#MAKE#: 'phobos' is up to date.\n";
run_make_test("
# goal = $goal
# init = $init
# target = $target
# ga = $ga
# bovr = $bovr
# yovr = $yovr
# cmd = $cmd
# env = $env
# dashe = $dashe
moon: hello=1
moon: override hello+=2
$i
$ga
$target${bovr}hello+=blue
$target${yovr}hello+=yellow
satur%: override hello:=3
satur%: hello+=4
\$(info \$(hello))
phobos:; \$(info \$(hello))
mars: phobos; \$(info \$(hello))
saturn:; \$(info \$(hello))
", "$dashe $cmd $goal", "$answer");
}
}
}
}
}
}
}
}
}
# Preferably, we would want to run the tests below for all the combinations,
# generated by the loop above. However, that causes the test to take a lot of
# time.
# The fix for sv 64822 went to recursively_expand_for_file.
# There are three code paths that lead to recursively_expand_for_file.
# - export of a variable to target environment.
# - expansion of a substitution reference.
# - other expansions of a variable that was appended to.
# Test target env, substitution reference in parse and build modes.;
# Also test a mix of pattern and target specific definitions and appends.
run_make_test(q!
al%: hello=file
al%: hello+=one
all: hello+=two
$(info $(hello))
all:; $(info $(hello))
!, "", "\nfile one two\n#MAKE#: 'all' is up to date.\n");
run_make_test(q!
hello=file
al%: hello+=one
all: hello+=two
$(info $(hello:X=Y))
all:; $(info $(hello:X=Y))
!, "", "file\nfile one two\n#MAKE#: 'all' is up to date.\n");
run_make_test(q!
hello=file
al%: hello+=one
all: hello+=two
export hello
$(info $(shell echo $$hello))
all:; $(info $(shell echo $$hello))
!, "", "file\nfile one two\n#MAKE#: 'all' is up to date.\n");
# "phobos" inherits the value of "hello" from "mars". On top of that there are
# also "phobos" specific appends.
for my $goal (@goals) {
my $answer = $goal eq "mars" ?
"\nminit mone mtwo pone ptwo\n" # From parse time and from "phobos" recipe.
. "minit mone mtwo\n#MAKE#: 'mars' is up to date.\n" : # From "mars" recipe.
"\npone ptwo\n#MAKE#: 'phobos' is up to date.\n"; # From parse time and from "phobos" recipe.
run_make_test("
mar%: hello=minit
mar%: hello+=mone
mars: hello+=mtwo
phobo%: hello+=pone
phobos: hello+=ptwo
\$(info \$(hello))
phobos:; \$(info \$(hello))
mars: phobos; \$(info \$(hello))
", "$goal", "$answer");
}
# This test is similar to the one above. The difference is that here there is a
# pattern-specific definition of "hello" that matches "phobos".
run_make_test(q!
mar%: hello:=minit
mar%: hello+=mone
mars: hello+=mtwo
phobo%: hello:=pinit
phobo%: hello+=pone
phobos: hello+=ptwo
$(info $(hello))
phobos:; $(info $(hello))
mars: phobos; $(info $(hello))
!, 'phobos', "\npinit pone ptwo\n#MAKE#: 'phobos' is up to date.\n");
# Test pattern and target specific appends to a global variable that has origin override.
# sv 36486.
my @ops = ('=', '+=', ':=');
@inits = ('', 'override ', 'al%: override ');
@specificities = ('', 'all: ', 'al%: ');
for my $init (@inits) {
for my $spec (@specificities) {
for my $op (@ops) {
my $build_time_answer = '';
if ($init =~ ':' and $op eq '+=' and !$spec) {
# This is the case where global variable obtains value 'one two three' at
# parse time and later at build time a pattern or target specific
# 'hello+=file' appends 'file'.
# e.g.
# al%: override hello+=file
# hello+=one
# hello+=two
# hello+=three
$build_time_answer = 'one two three file';
} elsif ($init =~ 'override') {
$build_time_answer = 'file';
} else {
$build_time_answer = 'file one two three';
}
my $parse_time_answer = $init =~ ':' ? '' : 'file';
if (!$spec and ($init ne 'override ')) {
$parse_time_answer .= ' ' if $parse_time_answer;
$parse_time_answer .= 'one two three';
}
run_make_test("
${init}hello${op}file
${spec}hello+=one
${spec}hello+=two
${spec}hello+=three
\$(info \$(hello))
all:; \$(info \$(hello))
", "", "$parse_time_answer\n$build_time_answer\n#MAKE#: 'all' is up to date.\n");
}
}
}
1;