Skip to content

Commit

Permalink
Expose CoverageOutputGenerator on a Fragment (#14997)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
fmeum authored Mar 8, 2022
1 parent 785c7ec commit 5e79972
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 13 deletions.
3 changes: 3 additions & 0 deletions src/main/java/com/google/devtools/build/lib/analysis/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ java_library(
"test/AnalysisTestActionBuilder.java",
"test/BaselineCoverageAction.java",
"test/CoverageCommon.java",
"test/CoverageConfiguration.java",
"test/InstrumentedFileManifestAction.java",
"test/InstrumentedFilesCollector.java",
"test/TestActionBuilder.java",
Expand Down Expand Up @@ -287,6 +288,7 @@ java_library(
":config/build_options",
":config/config_conditions",
":config/config_matching_provider",
":config/core_option_converters",
":config/core_options",
":config/execution_transition_factory",
":config/fragment",
Expand Down Expand Up @@ -379,6 +381,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/actions:package_roots",
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
"//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
"//src/main/java/com/google/devtools/build/lib/analysis/starlark/annotations",
"//src/main/java/com/google/devtools/build/lib/analysis/stringtemplate",
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.google.devtools.build.lib.analysis.config.RunUnder;
import com.google.devtools.build.lib.analysis.constraints.ConstraintConstants;
import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
import com.google.devtools.build.lib.analysis.test.CoverageConfiguration;
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
Expand Down Expand Up @@ -139,9 +140,6 @@ public static LabelLateBoundDefault<TestConfiguration> coverageSupportAttribute(
public static final String DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE =
"//tools/test:coverage_report_generator";

private static final String DEFAULT_COVERAGE_OUTPUT_GENERATOR_VALUE =
"@bazel_tools//tools/test:lcov_merger";

@SerializationConstant @AutoCodec.VisibleForSerialization
static final Resolver<TestConfiguration, Label> COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER =
(rule, attributes, configuration) -> configuration.getCoverageReportGenerator();
Expand All @@ -152,20 +150,14 @@ public static LabelLateBoundDefault<TestConfiguration> coverageReportGeneratorAt
TestConfiguration.class, defaultValue, COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER);
}

public static LabelLateBoundDefault<BuildConfiguration> getCoverageOutputGeneratorLabel() {
public static LabelLateBoundDefault<CoverageConfiguration> getCoverageOutputGeneratorLabel() {
return LabelLateBoundDefault.fromTargetConfiguration(
BuildConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER);
CoverageConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER);
}

@SerializationConstant @AutoCodec.VisibleForSerialization
static final Resolver<BuildConfiguration, Label> COVERAGE_OUTPUT_GENERATOR_RESOLVER =
(rule, attributes, configuration) -> {
if (configuration.isCodeCoverageEnabled()) {
return Label.parseAbsoluteUnchecked(DEFAULT_COVERAGE_OUTPUT_GENERATOR_VALUE);
} else {
return null;
}
};
static final Resolver<CoverageConfiguration, Label> COVERAGE_OUTPUT_GENERATOR_RESOLVER =
(rule, attributes, configuration) -> configuration.outputGenerator();

// TODO(b/65746853): provide a way to do this without passing the entire configuration
/** Implementation for the :run_under attribute. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.analysis.test;

import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.LabelConverter;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.analysis.starlark.annotations.StarlarkConfigurationField;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.starlarkbuildapi.test.CoverageConfigurationApi;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import javax.annotation.Nullable;

/** The coverage configuration fragment. */
@Immutable
@RequiresOptions(options = {CoreOptions.class, CoverageConfiguration.CoverageOptions.class})
public class CoverageConfiguration extends Fragment implements CoverageConfigurationApi {

/** Command-line options. */
public static class CoverageOptions extends FragmentOptions {

@Option(
name = "coverage_output_generator",
converter = LabelConverter.class,
defaultValue = "@bazel_tools//tools/test:lcov_merger",
documentationCategory = OptionDocumentationCategory.TOOLCHAIN,
effectTags = {
OptionEffectTag.CHANGES_INPUTS,
OptionEffectTag.AFFECTS_OUTPUTS,
OptionEffectTag.LOADING_AND_ANALYSIS
},
help =
"Location of the binary that is used to postprocess raw coverage reports. This must "
+ "currently be a filegroup that contains a single file, the binary. Defaults to "
+ "'//tools/test:lcov_merger'.")
public Label coverageOutputGenerator;
}

private final CoverageOptions coverageOptions;

public CoverageConfiguration(BuildOptions buildOptions) {
if (!buildOptions.get(CoreOptions.class).collectCodeCoverage) {
this.coverageOptions = null;
return;
}
this.coverageOptions = buildOptions.get(CoverageOptions.class);
}

@Override
@StarlarkConfigurationField(
name = "output_generator",
doc = "Label for the coverage output generator.")
@Nullable
public Label outputGenerator() {
if (coverageOptions == null) {
return null;
}
return coverageOptions.coverageOutputGenerator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.RuleSet;
import com.google.devtools.build.lib.analysis.test.CoverageConfiguration;
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.analysis.test.TestTrimmingTransitionFactory;

Expand All @@ -33,6 +34,7 @@ public void init(ConfiguredRuleClassProvider.Builder builder) {
builder.setShouldInvalidateCacheForOptionDiff(
TestConfiguration.SHOULD_INVALIDATE_FOR_OPTION_DIFF);
builder.addConfigurationFragment(TestConfiguration.class);
builder.addConfigurationFragment(CoverageConfiguration.class);
builder.addTrimmingTransitionFactory(new TestTrimmingTransitionFactory());
builder.addRuleDefinition(new BaseRuleClasses.NativeBuildRule());
builder.addRuleDefinition(new BaseRuleClasses.NativeActionCreatingRule());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ java_library(
"//src/main/java/net/starlark/java/annot",
"//src/main/java/net/starlark/java/eval",
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
"//third_party:jsr305",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.starlarkbuildapi.test;

import com.google.devtools.build.docgen.annot.DocCategory;
import com.google.devtools.build.lib.cmdline.Label;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.StarlarkValue;

/** Coverage configuration fragment API. */
@StarlarkBuiltin(
name = "coverage",
category = DocCategory.CONFIGURATION_FRAGMENT,
doc = "A configuration fragment representing the coverage configuration.")
public interface CoverageConfigurationApi extends StarlarkValue {

@StarlarkMethod(
name = "output_generator",
allowReturnNones = true,
structField = true,
doc =
"Returns the label pointed to by the"
+ " <a href=\"../../user-manual.html#flag--coverage_output_generator\">"
+ "<code>--coverage_output_generator</code></a> option if coverage collection is"
+ " enabled, otherwise returns <code>None</code>. Can be accessed with"
+ " <a href=\"globals.html#configuration_field\"><code>configuration_field"
+ "</code></a>:<br/>"
+ "<pre>attr.label(<br/>"
+ " default = configuration_field(<br/>"
+ " fragment = \"coverage\",<br/>"
+ " name = \"output_generator\"<br/>"
+ " )<br/>"
+ ")</pre>")
@Nullable
Label outputGenerator();
}
86 changes: 86 additions & 0 deletions src/test/shell/bazel/bazel_coverage_starlark_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,90 @@ EOF
|| fail "Coverage report did not contain evidence of custom lcov_merger."
}

function test_starlark_rule_with_configuration_field_lcov_merger_coverage_enabled() {

cat <<EOF > lcov_merger.sh
for var in "\$@"
do
if [[ "\$var" == "--output_file="* ]]; then
path="\${var##--output_file=}"
mkdir -p "\$(dirname \$path)"
echo lcov_merger_called >> \$path
exit 0
fi
done
EOF
chmod +x lcov_merger.sh

cat <<EOF > rules.bzl
def _impl(ctx):
output = ctx.actions.declare_file(ctx.attr.name)
ctx.actions.write(output, "", is_executable = True)
return [DefaultInfo(executable=output)]
custom_test = rule(
implementation = _impl,
test = True,
attrs = {
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = "exec"
),
},
fragments = ["coverage"],
)
EOF

cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
sh_binary(
name = "lcov_merger",
srcs = ["lcov_merger.sh"],
)
custom_test(name = "foo_test")
EOF

bazel coverage --test_output=all //:foo_test --combined_report=lcov --coverage_output_generator=//:lcov_merger > $TEST_log \
|| fail "Coverage run failed but should have succeeded."

local coverage_file_path="$( get_coverage_file_path_from_test_log )"
cat $coverage_file_path
grep "lcov_merger_called" "$coverage_file_path" \
|| fail "Coverage report did not contain evidence of custom lcov_merger."
}

function test_starlark_rule_with_configuration_field_lcov_merger_coverage_disabled() {

cat <<EOF > rules.bzl
def _impl(ctx):
if ctx.attr._lcov_merger:
fail("Expected _lcov_merger to be None if coverage is not collected")
output = ctx.actions.declare_file(ctx.attr.name)
ctx.actions.write(output, "", is_executable = True)
return [DefaultInfo(executable=output)]
custom_test = rule(
implementation = _impl,
test = True,
attrs = {
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = "exec"
),
},
fragments = ["coverage"],
)
EOF

cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
custom_test(name = "foo_test")
EOF

bazel test --test_output=all //:foo_test > $TEST_log \
|| fail "Test run failed but should have succeeded."
}

run_suite "test tests"

0 comments on commit 5e79972

Please sign in to comment.