Skip to content

Commit 5e79972

Browse files
authored
Expose CoverageOutputGenerator on a Fragment (#14997)
This allows Starlark rules to define _lcov_merger as a late-bound Starlark configuration_field in order not to pay the cost of building it when coverage isn't enabled. Fixes #8736 Fixes #10642 Closes #14724. RELNOTES: Add coverage configuration fragment, used to expose output_generator label. PiperOrigin-RevId: 433156089
1 parent 785c7ec commit 5e79972

File tree

7 files changed

+225
-13
lines changed

7 files changed

+225
-13
lines changed

src/main/java/com/google/devtools/build/lib/analysis/BUILD

+3
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ java_library(
254254
"test/AnalysisTestActionBuilder.java",
255255
"test/BaselineCoverageAction.java",
256256
"test/CoverageCommon.java",
257+
"test/CoverageConfiguration.java",
257258
"test/InstrumentedFileManifestAction.java",
258259
"test/InstrumentedFilesCollector.java",
259260
"test/TestActionBuilder.java",
@@ -287,6 +288,7 @@ java_library(
287288
":config/build_options",
288289
":config/config_conditions",
289290
":config/config_matching_provider",
291+
":config/core_option_converters",
290292
":config/core_options",
291293
":config/execution_transition_factory",
292294
":config/fragment",
@@ -379,6 +381,7 @@ java_library(
379381
"//src/main/java/com/google/devtools/build/lib/actions:package_roots",
380382
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
381383
"//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
384+
"//src/main/java/com/google/devtools/build/lib/analysis/starlark/annotations",
382385
"//src/main/java/com/google/devtools/build/lib/analysis/stringtemplate",
383386
"//src/main/java/com/google/devtools/build/lib/bugreport",
384387
"//src/main/java/com/google/devtools/build/lib/buildeventstream",

src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java

+5-13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.devtools.build.lib.analysis.config.RunUnder;
3535
import com.google.devtools.build.lib.analysis.constraints.ConstraintConstants;
3636
import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
37+
import com.google.devtools.build.lib.analysis.test.CoverageConfiguration;
3738
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
3839
import com.google.devtools.build.lib.cmdline.Label;
3940
import com.google.devtools.build.lib.packages.Attribute;
@@ -139,9 +140,6 @@ public static LabelLateBoundDefault<TestConfiguration> coverageSupportAttribute(
139140
public static final String DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE =
140141
"//tools/test:coverage_report_generator";
141142

142-
private static final String DEFAULT_COVERAGE_OUTPUT_GENERATOR_VALUE =
143-
"@bazel_tools//tools/test:lcov_merger";
144-
145143
@SerializationConstant @AutoCodec.VisibleForSerialization
146144
static final Resolver<TestConfiguration, Label> COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER =
147145
(rule, attributes, configuration) -> configuration.getCoverageReportGenerator();
@@ -152,20 +150,14 @@ public static LabelLateBoundDefault<TestConfiguration> coverageReportGeneratorAt
152150
TestConfiguration.class, defaultValue, COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER);
153151
}
154152

155-
public static LabelLateBoundDefault<BuildConfiguration> getCoverageOutputGeneratorLabel() {
153+
public static LabelLateBoundDefault<CoverageConfiguration> getCoverageOutputGeneratorLabel() {
156154
return LabelLateBoundDefault.fromTargetConfiguration(
157-
BuildConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER);
155+
CoverageConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER);
158156
}
159157

160158
@SerializationConstant @AutoCodec.VisibleForSerialization
161-
static final Resolver<BuildConfiguration, Label> COVERAGE_OUTPUT_GENERATOR_RESOLVER =
162-
(rule, attributes, configuration) -> {
163-
if (configuration.isCodeCoverageEnabled()) {
164-
return Label.parseAbsoluteUnchecked(DEFAULT_COVERAGE_OUTPUT_GENERATOR_VALUE);
165-
} else {
166-
return null;
167-
}
168-
};
159+
static final Resolver<CoverageConfiguration, Label> COVERAGE_OUTPUT_GENERATOR_RESOLVER =
160+
(rule, attributes, configuration) -> configuration.outputGenerator();
169161

170162
// TODO(b/65746853): provide a way to do this without passing the entire configuration
171163
/** Implementation for the :run_under attribute. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.analysis.test;
16+
17+
import com.google.devtools.build.lib.analysis.config.BuildOptions;
18+
import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.LabelConverter;
19+
import com.google.devtools.build.lib.analysis.config.CoreOptions;
20+
import com.google.devtools.build.lib.analysis.config.Fragment;
21+
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
22+
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
23+
import com.google.devtools.build.lib.analysis.starlark.annotations.StarlarkConfigurationField;
24+
import com.google.devtools.build.lib.cmdline.Label;
25+
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
26+
import com.google.devtools.build.lib.starlarkbuildapi.test.CoverageConfigurationApi;
27+
import com.google.devtools.common.options.Option;
28+
import com.google.devtools.common.options.OptionDocumentationCategory;
29+
import com.google.devtools.common.options.OptionEffectTag;
30+
import javax.annotation.Nullable;
31+
32+
/** The coverage configuration fragment. */
33+
@Immutable
34+
@RequiresOptions(options = {CoreOptions.class, CoverageConfiguration.CoverageOptions.class})
35+
public class CoverageConfiguration extends Fragment implements CoverageConfigurationApi {
36+
37+
/** Command-line options. */
38+
public static class CoverageOptions extends FragmentOptions {
39+
40+
@Option(
41+
name = "coverage_output_generator",
42+
converter = LabelConverter.class,
43+
defaultValue = "@bazel_tools//tools/test:lcov_merger",
44+
documentationCategory = OptionDocumentationCategory.TOOLCHAIN,
45+
effectTags = {
46+
OptionEffectTag.CHANGES_INPUTS,
47+
OptionEffectTag.AFFECTS_OUTPUTS,
48+
OptionEffectTag.LOADING_AND_ANALYSIS
49+
},
50+
help =
51+
"Location of the binary that is used to postprocess raw coverage reports. This must "
52+
+ "currently be a filegroup that contains a single file, the binary. Defaults to "
53+
+ "'//tools/test:lcov_merger'.")
54+
public Label coverageOutputGenerator;
55+
}
56+
57+
private final CoverageOptions coverageOptions;
58+
59+
public CoverageConfiguration(BuildOptions buildOptions) {
60+
if (!buildOptions.get(CoreOptions.class).collectCodeCoverage) {
61+
this.coverageOptions = null;
62+
return;
63+
}
64+
this.coverageOptions = buildOptions.get(CoverageOptions.class);
65+
}
66+
67+
@Override
68+
@StarlarkConfigurationField(
69+
name = "output_generator",
70+
doc = "Label for the coverage output generator.")
71+
@Nullable
72+
public Label outputGenerator() {
73+
if (coverageOptions == null) {
74+
return null;
75+
}
76+
return coverageOptions.coverageOutputGenerator;
77+
}
78+
}

src/main/java/com/google/devtools/build/lib/rules/core/CoreRules.java

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
1818
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
1919
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.RuleSet;
20+
import com.google.devtools.build.lib.analysis.test.CoverageConfiguration;
2021
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
2122
import com.google.devtools.build.lib.analysis.test.TestTrimmingTransitionFactory;
2223

@@ -33,6 +34,7 @@ public void init(ConfiguredRuleClassProvider.Builder builder) {
3334
builder.setShouldInvalidateCacheForOptionDiff(
3435
TestConfiguration.SHOULD_INVALIDATE_FOR_OPTION_DIFF);
3536
builder.addConfigurationFragment(TestConfiguration.class);
37+
builder.addConfigurationFragment(CoverageConfiguration.class);
3638
builder.addTrimmingTransitionFactory(new TestTrimmingTransitionFactory());
3739
builder.addRuleDefinition(new BaseRuleClasses.NativeBuildRule());
3840
builder.addRuleDefinition(new BaseRuleClasses.NativeActionCreatingRule());

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ java_library(
3030
"//src/main/java/net/starlark/java/annot",
3131
"//src/main/java/net/starlark/java/eval",
3232
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
33+
"//third_party:jsr305",
3334
],
3435
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.starlarkbuildapi.test;
16+
17+
import com.google.devtools.build.docgen.annot.DocCategory;
18+
import com.google.devtools.build.lib.cmdline.Label;
19+
import javax.annotation.Nullable;
20+
import net.starlark.java.annot.StarlarkBuiltin;
21+
import net.starlark.java.annot.StarlarkMethod;
22+
import net.starlark.java.eval.StarlarkValue;
23+
24+
/** Coverage configuration fragment API. */
25+
@StarlarkBuiltin(
26+
name = "coverage",
27+
category = DocCategory.CONFIGURATION_FRAGMENT,
28+
doc = "A configuration fragment representing the coverage configuration.")
29+
public interface CoverageConfigurationApi extends StarlarkValue {
30+
31+
@StarlarkMethod(
32+
name = "output_generator",
33+
allowReturnNones = true,
34+
structField = true,
35+
doc =
36+
"Returns the label pointed to by the"
37+
+ " <a href=\"../../user-manual.html#flag--coverage_output_generator\">"
38+
+ "<code>--coverage_output_generator</code></a> option if coverage collection is"
39+
+ " enabled, otherwise returns <code>None</code>. Can be accessed with"
40+
+ " <a href=\"globals.html#configuration_field\"><code>configuration_field"
41+
+ "</code></a>:<br/>"
42+
+ "<pre>attr.label(<br/>"
43+
+ " default = configuration_field(<br/>"
44+
+ " fragment = \"coverage\",<br/>"
45+
+ " name = \"output_generator\"<br/>"
46+
+ " )<br/>"
47+
+ ")</pre>")
48+
@Nullable
49+
Label outputGenerator();
50+
}

src/test/shell/bazel/bazel_coverage_starlark_test.sh

+86
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,90 @@ EOF
134134
|| fail "Coverage report did not contain evidence of custom lcov_merger."
135135
}
136136

137+
function test_starlark_rule_with_configuration_field_lcov_merger_coverage_enabled() {
138+
139+
cat <<EOF > lcov_merger.sh
140+
for var in "\$@"
141+
do
142+
if [[ "\$var" == "--output_file="* ]]; then
143+
path="\${var##--output_file=}"
144+
mkdir -p "\$(dirname \$path)"
145+
echo lcov_merger_called >> \$path
146+
exit 0
147+
fi
148+
done
149+
EOF
150+
chmod +x lcov_merger.sh
151+
152+
cat <<EOF > rules.bzl
153+
def _impl(ctx):
154+
output = ctx.actions.declare_file(ctx.attr.name)
155+
ctx.actions.write(output, "", is_executable = True)
156+
return [DefaultInfo(executable=output)]
157+
158+
custom_test = rule(
159+
implementation = _impl,
160+
test = True,
161+
attrs = {
162+
"_lcov_merger": attr.label(
163+
default = configuration_field(fragment = "coverage", name = "output_generator"),
164+
cfg = "exec"
165+
),
166+
},
167+
fragments = ["coverage"],
168+
)
169+
EOF
170+
171+
cat <<EOF > BUILD
172+
load(":rules.bzl", "custom_test")
173+
174+
sh_binary(
175+
name = "lcov_merger",
176+
srcs = ["lcov_merger.sh"],
177+
)
178+
custom_test(name = "foo_test")
179+
EOF
180+
181+
bazel coverage --test_output=all //:foo_test --combined_report=lcov --coverage_output_generator=//:lcov_merger > $TEST_log \
182+
|| fail "Coverage run failed but should have succeeded."
183+
184+
local coverage_file_path="$( get_coverage_file_path_from_test_log )"
185+
cat $coverage_file_path
186+
grep "lcov_merger_called" "$coverage_file_path" \
187+
|| fail "Coverage report did not contain evidence of custom lcov_merger."
188+
}
189+
190+
function test_starlark_rule_with_configuration_field_lcov_merger_coverage_disabled() {
191+
192+
cat <<EOF > rules.bzl
193+
def _impl(ctx):
194+
if ctx.attr._lcov_merger:
195+
fail("Expected _lcov_merger to be None if coverage is not collected")
196+
output = ctx.actions.declare_file(ctx.attr.name)
197+
ctx.actions.write(output, "", is_executable = True)
198+
return [DefaultInfo(executable=output)]
199+
200+
custom_test = rule(
201+
implementation = _impl,
202+
test = True,
203+
attrs = {
204+
"_lcov_merger": attr.label(
205+
default = configuration_field(fragment = "coverage", name = "output_generator"),
206+
cfg = "exec"
207+
),
208+
},
209+
fragments = ["coverage"],
210+
)
211+
EOF
212+
213+
cat <<EOF > BUILD
214+
load(":rules.bzl", "custom_test")
215+
216+
custom_test(name = "foo_test")
217+
EOF
218+
219+
bazel test --test_output=all //:foo_test > $TEST_log \
220+
|| fail "Test run failed but should have succeeded."
221+
}
222+
137223
run_suite "test tests"

0 commit comments

Comments
 (0)