Skip to content

Commit df03ba4

Browse files
authored
Add 'config_file = filename' RC file option - to enable recursive inclusion of config files. (#407)
See discussion in PR #404 for a discussion of the feature and some potential use models. Signed-off-by: Henry Cox <[email protected]>
1 parent 9b27df1 commit df03ba4

File tree

7 files changed

+161
-78
lines changed

7 files changed

+161
-78
lines changed

lcovrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
# Note that this example script does not include all configuration options
88
# see 'man lcovrc(5) for a complete list and usage description.
99

10+
# include some other config file
11+
# e.g, user-specific options. Note the environment variable expansion
12+
# config_file = $ENV{HOME}/.user_lcovrc
13+
# or project specific - hard-coded from environment variable
14+
# config_file = /path/to/myproject/.lcovrc
15+
# or in the current run directory
16+
# config_file = $ENV{PWD}/.lcovrc
17+
1018
# Specify an external style sheet file (same as --css-file option of genhtml)
1119
#genhtml_css_file = gcov.css
1220

lib/lcovutil.pm

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,57 +1009,6 @@ sub cleanup_callbacks
10091009
}
10101010
}
10111011

1012-
#
1013-
# apply_config(REF, ref)
1014-
#
1015-
# REF is a reference to a hash containing the following mapping:
1016-
#
1017-
# key_string => var_ref
1018-
#
1019-
# where KEY_STRING is a keyword and VAR_REF is a reference to an associated
1020-
# variable. If the global configuration hashes CONFIG or OPT_RC contain a value
1021-
# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
1022-
#
1023-
# Return 1 if we set something
1024-
1025-
sub _set_config($$$)
1026-
{
1027-
my ($ref, $key, $value) = @_;
1028-
my $r = $ref->{$key};
1029-
my $t = ref($r);
1030-
if ('ARRAY' eq $t) {
1031-
info(2, " append $value to list $key\n");
1032-
if ('ARRAY' eq ref($value)) {
1033-
push(@$r, @$value);
1034-
} else {
1035-
push(@$r, $value);
1036-
}
1037-
} else {
1038-
# opt is a scalar or not defined
1039-
# only way for $value to NOT be an array is if there is a bug in
1040-
# the caller such that a scalar ref was passed where a prior call
1041-
# had passed a list ref for the same RC option name
1042-
die("unexpected ARRAY for $key value")
1043-
if ('ARRAY' eq ref($value));
1044-
$$r = $value;
1045-
info(2, " assign $$r to $key\n");
1046-
}
1047-
}
1048-
1049-
sub apply_config($$)
1050-
{
1051-
my ($ref, $config) = @_;
1052-
my $set_value = 0;
1053-
foreach (keys(%{$ref})) {
1054-
# if sufficiently verbose, could mention that key is ignored
1055-
next unless exists($config->{$_});
1056-
my $v = $config->{$_};
1057-
$set_value = 1;
1058-
_set_config($ref, $_, $v); # write into options
1059-
}
1060-
return $set_value;
1061-
}
1062-
10631012
# use these list values from the RC file unless the option is
10641013
# passed on the command line
10651014
my (@rc_filter, @rc_ignore, @rc_exclude_patterns,
@@ -1284,27 +1233,66 @@ sub warnDeprecated
12841233
return $opt_used;
12851234
}
12861235

1236+
sub _set_config($$$)
1237+
{
1238+
# write an RC configuration value - array or scalar
1239+
my ($ref, $key, $value) = @_;
1240+
my $r = $ref->{$key};
1241+
my $t = ref($r);
1242+
if ('ARRAY' eq $t) {
1243+
info(2, " append $value to list $key\n");
1244+
if ('ARRAY' eq ref($value)) {
1245+
push(@$r, @$value);
1246+
} else {
1247+
push(@$r, $value);
1248+
}
1249+
} else {
1250+
# opt is a scalar or not defined
1251+
# only way for $value to NOT be an array is if there is a bug in
1252+
# the caller such that a scalar ref was passed where a prior call
1253+
# had passed a list ref for the same RC option name
1254+
die("unexpected ARRAY for $key value")
1255+
if ('ARRAY' eq ref($value));
1256+
$$r = $value;
1257+
info(2, " assign $$r to $key\n");
1258+
}
1259+
}
1260+
12871261
#
1288-
# read_config(filename)
1289-
#
1290-
# Read configuration file FILENAME and return a reference to a hash containing
1291-
# all valid key=value pairs found.
1262+
# read_config(filename, $optionsHash)
12921263
#
1264+
# Read configuration file FILENAME and write supported key/values into
1265+
# RC options hash
1266+
# Return: 1 if some config value was set, 0 if not (used for error messaging)
12931267

1294-
sub read_config($)
1268+
sub read_config($$); # forward decl, to make perl happy about recursive call
1269+
my %included_config_files;
1270+
my @include_stack;
1271+
1272+
sub read_config($$)
12951273
{
1296-
my $filename = shift;
1297-
my %result;
1274+
my ($filename, $opts) = @_;
12981275
my $key;
12991276
my $value;
13001277
local *HANDLE;
13011278

1279+
my $set_value = 0;
13021280
info(1, "read_config: $filename\n");
1281+
if (exists($included_config_files{abs_path($filename)})) {
1282+
lcovutil::ignorable_error($lcovutil::ERROR_USAGE,
1283+
'config file inclusion loop detected: "' .
1284+
join('" -> "', @include_stack) .
1285+
'" -> "' . $filename . '"');
1286+
return 0;
1287+
}
1288+
13031289
if (!open(HANDLE, "<", $filename)) {
13041290
lcovutil::ignorable_error($lcovutil::ERROR_USAGE,
13051291
"cannot read configuration file '$filename': $!");
1306-
return undef;
1292+
return 0; # didn't set anything
13071293
}
1294+
$included_config_files{abs_path($filename)} = 1;
1295+
push(@include_stack, $filename);
13081296
VAR: while (<HANDLE>) {
13091297
chomp;
13101298
# Skip comments
@@ -1337,15 +1325,15 @@ sub read_config($)
13371325
}
13381326
if (defined($key) && defined($value)) {
13391327
info(2, " set: $key = $value\n");
1340-
if (exists($result{$key})) {
1341-
if ('ARRAY' eq ref($result{$key})) {
1342-
push(@{$result{$key}}, $value);
1343-
} else {
1344-
$result{$key} = [$result{$key}, $value];
1345-
}
1346-
} else {
1347-
$result{$key} = $value;
1328+
# special case: read included file
1329+
if ($key eq 'config_file') {
1330+
$set_value |= read_config($value, $opts);
1331+
next;
13481332
}
1333+
# skip if application doesn't use this setting
1334+
next unless exists($opts->{$key});
1335+
_set_config($opts, $key, $value);
1336+
$set_value = 1;
13491337
} else {
13501338
my $context = MessageContext::context();
13511339
push(
@@ -1357,7 +1345,9 @@ sub read_config($)
13571345
}
13581346
}
13591347
close(HANDLE) or die("unable to close $filename: $!\n");
1360-
return \%result;
1348+
delete $included_config_files{abs_path($filename)};
1349+
pop(@include_stack);
1350+
return $set_value;
13611351
}
13621352

13631353
# common utility used by genhtml, geninfo, lcov to clean up RC options,
@@ -1387,17 +1377,14 @@ sub apply_rc_params($)
13871377

13881378
if (0 != scalar(@opt_config_files)) {
13891379
foreach my $f (@opt_config_files) {
1390-
my $cfg = read_config($f);
1391-
$set_value |= apply_config(\%rcHash, $cfg);
1380+
$set_value |= read_config($f, \%rcHash);
13921381
}
13931382
} else {
13941383
foreach my $v (['HOME', '.lcovrc'], ['LCOV_HOME', 'etc', 'lcovrc']) {
13951384
next unless exists($ENV{$v->[0]});
13961385
my $f = File::Spec->catfile($ENV{$v->[0]}, splice(@$v, 1));
13971386
if (-r $f) {
1398-
my $config = read_config($f);
1399-
# Copy configuration file and --rc values to variables
1400-
$set_value |= apply_config(\%rcHash, $config);
1387+
$set_value |= read_config($f, \%rcHash);
14011388
last;
14021389
}
14031390
}

man/genhtml.1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3231,7 +3231,10 @@ for more details.
32313231
Specify a configuration file to use.
32323232
See man
32333233
.B lcovrc(5)
3234-
for details of the file format and options.
3234+
for details of the file format and options. Also see the
3235+
.I config_file
3236+
entry in the same man page for details on how to include one config file into
3237+
another.
32353238

32363239
When this option is specified, neither the system\-wide configuration file
32373240
/etc/lcovrc, nor the per\-user configuration file ~/.lcovrc is read.

man/geninfo.1

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,13 @@ libtool, disable this option to prevent problems when capturing coverage data.
572572
.br
573573
.RS
574574
Specify a configuration file to use.
575-
See the lcovrc man page for details of the file format and options.
575+
See man
576+
.B lcovrc(5)
577+
for details of the file format and options.
578+
Also see the
579+
.I config_file
580+
entry in the same man page for details on how to include one config file into
581+
another.
576582

577583
When this option is specified, neither the system\-wide configuration file
578584
/etc/lcovrc, nor the per\-user configuration file ~/.lcovrc is read.

man/lcov.1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,10 @@ libtool, disable this option to prevent problems when capturing coverage data.
671671
Specify a configuration file to use.
672672
See man
673673
.B lcovrc(5)
674-
for details of the file format and options.
674+
for details of the file format and options. Also see the
675+
.I config_file
676+
entry in the same man page for details on how to include one config file into
677+
another.
675678

676679
When this option is specified, neither the system\-wide configuration file
677680
/etc/lcovrc, nor the per\-user configuration file ~/.lcovrc is read.

man/lcovrc.5

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,21 @@ See the OPTIONS section below for details.
148148
.br
149149
#
150150
.br
151-
151+
# include some other config file
152+
.br
153+
# e.g, user-specific options. Note the environment variable expansion
154+
.br
155+
# config_file = $ENV{HOME}/.user_lcovrc
156+
.br
157+
# or project specific - hard-coded from environment variable
158+
.br
159+
# config_file = /path/to/myproject/.lcovrc
160+
.br
161+
# or in the current run directory
162+
.br
163+
# config_file = $ENV{PWD}/.lcovrc
164+
.br
165+
.br
152166
# External style sheet file
153167
.br
154168
#genhtml_css_file = gcov.css
@@ -727,6 +741,44 @@ maximum during parallel processing.
727741

728742
.SH OPTIONS
729743

744+
.BR config_file " ="
745+
.I filename
746+
.IP
747+
748+
Include another config file.
749+
750+
Inclusion is equivalent to inserting the text from
751+
.I filename
752+
at this point in the current file. As a result, settings from the included
753+
file are processed after earlier settings in the current file, but before later settings from the current file.
754+
As a result:
755+
756+
.BR "Scalar options"
757+
set earlier in the current file are overridden by settings from the included file, and scalar options from the included file are overridden by later setting in the current file.
758+
759+
.BR "Array options"
760+
from earlier in the current file appear before setting from the included file, and array options from later in the current file appear after.
761+
762+
Config file inclusion is recursive: an included config file may include another file - and so on.
763+
Inclusion loops are not supported and will result in a
764+
.I usage
765+
error.
766+
767+
The most common usecase for config file inclusion is so that a site-wide or project-wide options file can include a user-specific or module-specific options file - for example, as
768+
769+
.RS 3
770+
.IP
771+
...
772+
.br
773+
config_file = $ENV{HOME}/.lcovrc_user
774+
.br
775+
...
776+
.PP
777+
.RE
778+
779+
780+
.PP
781+
730782
.BR genhtml_css_file " ="
731783
.I filename
732784
.IP

tests/gendiffcov/errs/msgtest.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set +x
33

44
source ../../common.tst
55

6-
rm -f test.cpp *.gcno *.gcda a.out *.info *.log *.json diff.txt
6+
rm -f test.cpp *.gcno *.gcda a.out *.info *.log *.json diff.txt loop*.rc markers.err*
77
rm -rf select criteria annotate empty unused_src scriptErr scriptFixed epoch inconsistent highlight etc mycache cacheFail expect subset context labels
88

99
clean_cover
@@ -203,6 +203,30 @@ if [ 0 != $? ] ; then
203203
fi
204204
fi
205205
206+
# loop in config file inclusion
207+
echo "config_file = loop1.rc" > loop1.rc
208+
echo lcov $LCOV_OPTS --summary initial.info --config-file loop1.rc --ignore usage
209+
$COVER $LCOV_TOOL $LCOV_OPTS --summary initial.info --config-file loop1.rc --ignore usage 2>&1 | tee err_selfloop.log
210+
grep "config file inclusion loop" err_selfloop.log
211+
if [ 0 != $? ] ; then
212+
echo "ERROR: missing config file message"
213+
if [ 0 == $KEEP_GOING ] ; then
214+
exit 1
215+
fi
216+
fi
217+
218+
echo "config_file = loop3.rc" > loop2.rc
219+
echo 'config_file = $ENV{PWD}/loop2.rc' > loop3.rc
220+
echo lcov $LCOV_OPTS --summary initial.info --config-file loop2.rc --ignore usage
221+
$COVER $LCOV_TOOL $LCOV_OPTS --summary initial.info --config-file loop2.rc --ignore usage 2>&1 | tee err_loop.log
222+
grep "config file inclusion loop" err_loop.log
223+
if [ 0 != $? ] ; then
224+
echo "ERROR: missing config file message"
225+
if [ 0 == $KEEP_GOING ] ; then
226+
exit 1
227+
fi
228+
fi
229+
206230
207231
echo lcov $LCOV_OPTS --capture -d . -o build.info --build-dir $LCOV_HOME
208232
$COVER $LCOV_TOOL $LCOV_OPTS --capture -d . -o build.info --build-dir $LCOV_HOME 2>&1 | tee build_dir_unused.log

0 commit comments

Comments
 (0)