diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD index ab553b460b52a1..4cfeb659323e79 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD +++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD @@ -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", @@ -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", @@ -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", diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java index 861206b86c550e..6b6402f01ae4f9 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java @@ -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; @@ -139,9 +140,6 @@ public static LabelLateBoundDefault 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 COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER = (rule, attributes, configuration) -> configuration.getCoverageReportGenerator(); @@ -152,20 +150,14 @@ public static LabelLateBoundDefault coverageReportGeneratorAt TestConfiguration.class, defaultValue, COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER); } - public static LabelLateBoundDefault getCoverageOutputGeneratorLabel() { + public static LabelLateBoundDefault getCoverageOutputGeneratorLabel() { return LabelLateBoundDefault.fromTargetConfiguration( - BuildConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER); + CoverageConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER); } @SerializationConstant @AutoCodec.VisibleForSerialization - static final Resolver COVERAGE_OUTPUT_GENERATOR_RESOLVER = - (rule, attributes, configuration) -> { - if (configuration.isCodeCoverageEnabled()) { - return Label.parseAbsoluteUnchecked(DEFAULT_COVERAGE_OUTPUT_GENERATOR_VALUE); - } else { - return null; - } - }; + static final Resolver 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. */ diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageConfiguration.java new file mode 100644 index 00000000000000..7aaccfa4d4eec2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageConfiguration.java @@ -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; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/core/CoreRules.java b/src/main/java/com/google/devtools/build/lib/rules/core/CoreRules.java index d2b6c6523b7c59..2908893d33c661 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/core/CoreRules.java +++ b/src/main/java/com/google/devtools/build/lib/rules/core/CoreRules.java @@ -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; @@ -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()); diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/BUILD b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/BUILD index 7ea3da44281863..a295e6e29c4a4a 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/BUILD +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/BUILD @@ -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", ], ) diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/CoverageConfigurationApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/CoverageConfigurationApi.java new file mode 100644 index 00000000000000..cb342e938313ea --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/CoverageConfigurationApi.java @@ -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" + + " " + + "--coverage_output_generator option if coverage collection is" + + " enabled, otherwise returns None. Can be accessed with" + + " configuration_field" + + ":
" + + "
attr.label(
" + + " default = configuration_field(
" + + " fragment = \"coverage\",
" + + " name = \"output_generator\"
" + + " )
" + + ")
") + @Nullable + Label outputGenerator(); +} diff --git a/src/test/shell/bazel/bazel_coverage_starlark_test.sh b/src/test/shell/bazel/bazel_coverage_starlark_test.sh index 0ea7e032ef5dad..e97c1500c5e14c 100755 --- a/src/test/shell/bazel/bazel_coverage_starlark_test.sh +++ b/src/test/shell/bazel/bazel_coverage_starlark_test.sh @@ -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 < 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 < 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 < 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 < 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 < 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"