| // Copyright 2014 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; | |
| import static com.google.devtools.build.lib.packages.Attribute.attr; | |
| import static com.google.devtools.build.lib.packages.BuildType.DISTRIBUTIONS; | |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; | |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; | |
| import static com.google.devtools.build.lib.packages.BuildType.LICENSE; | |
| import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL_LIST; | |
| import static com.google.devtools.build.lib.packages.Type.BOOLEAN; | |
| import static com.google.devtools.build.lib.packages.Type.INTEGER; | |
| import static com.google.devtools.build.lib.packages.Type.STRING; | |
| import static com.google.devtools.build.lib.packages.Type.STRING_DICT; | |
| import static com.google.devtools.build.lib.packages.Type.STRING_LIST; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; | |
| import com.google.devtools.build.lib.analysis.config.HostTransition; | |
| 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.test.TestConfiguration; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.packages.Attribute; | |
| import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault; | |
| import com.google.devtools.build.lib.packages.Attribute.LabelListLateBoundDefault; | |
| import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault.Resolver; | |
| import com.google.devtools.build.lib.packages.AttributeMap; | |
| import com.google.devtools.build.lib.packages.BuildType; | |
| import com.google.devtools.build.lib.packages.RuleClass; | |
| import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; | |
| import com.google.devtools.build.lib.packages.TestSize; | |
| import com.google.devtools.build.lib.packages.Type; | |
| import com.google.devtools.build.lib.packages.Type.ConversionException; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; | |
| import com.google.devtools.build.lib.util.FileTypeSet; | |
| /** | |
| * Rule class definitions used by (almost) every rule. | |
| */ | |
| public class BaseRuleClasses { | |
| @AutoCodec @AutoCodec.VisibleForSerialization | |
| static final Attribute.ComputedDefault testonlyDefault = | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| return rule.getPackageDefaultTestOnly(); | |
| } | |
| }; | |
| @AutoCodec @AutoCodec.VisibleForSerialization | |
| static final Attribute.ComputedDefault deprecationDefault = | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| return rule.getPackageDefaultDeprecation(); | |
| } | |
| }; | |
| // TODO(b/65746853): provide a way to do this without passing the entire configuration | |
| /** | |
| * Implementation for the :action_listener attribute. | |
| * | |
| * <p>action_listeners are special rules; they tell the build system to add extra_actions to | |
| * existing rules. As such they need an edge to every ConfiguredTarget with the limitation that | |
| * they only run on the target configuration and should not operate on action_listeners and | |
| * extra_actions themselves (to avoid cycles). | |
| */ | |
| @AutoCodec @VisibleForTesting | |
| static final LabelListLateBoundDefault<?> ACTION_LISTENER = | |
| LabelListLateBoundDefault.fromTargetConfiguration( | |
| BuildConfiguration.class, | |
| (rule, attributes, configuration) -> configuration.getActionListeners()); | |
| public static final String DEFAULT_COVERAGE_SUPPORT_VALUE = "//tools/test:coverage_support"; | |
| @AutoCodec | |
| static final Resolver<TestConfiguration, Label> COVERAGE_SUPPORT_CONFIGURATION_RESOLVER = | |
| (rule, attributes, configuration) -> configuration.getCoverageSupport(); | |
| public static LabelLateBoundDefault<TestConfiguration> coverageSupportAttribute( | |
| Label defaultValue) { | |
| return LabelLateBoundDefault.fromTargetConfiguration( | |
| TestConfiguration.class, defaultValue, COVERAGE_SUPPORT_CONFIGURATION_RESOLVER); | |
| } | |
| 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"; | |
| @AutoCodec | |
| static final Resolver<TestConfiguration, Label> COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER = | |
| (rule, attributes, configuration) -> configuration.getCoverageReportGenerator(); | |
| public static LabelLateBoundDefault<TestConfiguration> coverageReportGeneratorAttribute( | |
| Label defaultValue) { | |
| return LabelLateBoundDefault.fromTargetConfiguration( | |
| TestConfiguration.class, defaultValue, COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER); | |
| } | |
| public static LabelLateBoundDefault<BuildConfiguration> getCoverageOutputGeneratorLabel() { | |
| return LabelLateBoundDefault.fromTargetConfiguration( | |
| BuildConfiguration.class, null, COVERAGE_OUTPUT_GENERATOR_RESOLVER); | |
| } | |
| @AutoCodec | |
| 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; | |
| } | |
| }; | |
| // TODO(b/65746853): provide a way to do this without passing the entire configuration | |
| /** Implementation for the :run_under attribute. */ | |
| @AutoCodec | |
| public static final LabelLateBoundDefault<?> RUN_UNDER = | |
| LabelLateBoundDefault.fromTargetConfiguration( | |
| BuildConfiguration.class, | |
| null, | |
| (rule, attributes, configuration) -> { | |
| RunUnder runUnder = configuration.getRunUnder(); | |
| return runUnder != null ? runUnder.getLabel() : null; | |
| }); | |
| /** | |
| * A base rule for all test rules. | |
| */ | |
| public static final class TestBaseRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return builder | |
| .requiresConfigurationFragments(TestConfiguration.class) | |
| .add( | |
| attr("size", STRING) | |
| .value("medium") | |
| .taggable() | |
| .nonconfigurable("policy decision: should be consistent across configurations")) | |
| .add( | |
| attr("timeout", STRING) | |
| .taggable() | |
| .nonconfigurable("policy decision: should be consistent across configurations") | |
| .value( | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| TestSize size = TestSize.getTestSize(rule.get("size", Type.STRING)); | |
| if (size != null) { | |
| String timeout = size.getDefaultTimeout().toString(); | |
| if (timeout != null) { | |
| return timeout; | |
| } | |
| } | |
| return "illegal"; | |
| } | |
| })) | |
| .add( | |
| attr("flaky", BOOLEAN) | |
| .value(false) | |
| .taggable() | |
| .nonconfigurable("policy decision: should be consistent across configurations")) | |
| .add(attr("shard_count", INTEGER).value(-1)) | |
| .add( | |
| attr("local", BOOLEAN) | |
| .value(false) | |
| .taggable() | |
| .nonconfigurable("policy decision: should be consistent across configurations")) | |
| .add(attr("args", STRING_LIST)) | |
| // Input files for every test action | |
| .add( | |
| attr("$test_wrapper", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .singleArtifact() | |
| .value(env.getToolsLabel("//tools/test:test_wrapper"))) | |
| .add( | |
| attr("$xml_writer", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .singleArtifact() | |
| .value(env.getToolsLabel("//tools/test:xml_writer"))) | |
| .add( | |
| attr("$test_runtime", LABEL_LIST) | |
| .cfg(HostTransition.createFactory()) | |
| .value(getTestRuntimeLabelList(env))) | |
| .add( | |
| attr("$test_setup_script", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .singleArtifact() | |
| .value(env.getToolsLabel("//tools/test:test_setup"))) | |
| .add( | |
| attr("$xml_generator_script", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .singleArtifact() | |
| .value(env.getToolsLabel("//tools/test:test_xml_generator"))) | |
| .add( | |
| attr("$collect_coverage_script", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .singleArtifact() | |
| .value(env.getToolsLabel("//tools/test:collect_coverage"))) | |
| // Input files for test actions collecting code coverage | |
| .add( | |
| attr(":coverage_support", LABEL) | |
| .value( | |
| coverageSupportAttribute(env.getToolsLabel(DEFAULT_COVERAGE_SUPPORT_VALUE)))) | |
| // Used in the one-per-build coverage report generation action. | |
| .add( | |
| attr(":coverage_report_generator", LABEL) | |
| .cfg(HostTransition.createFactory()) | |
| .value( | |
| coverageReportGeneratorAttribute( | |
| env.getToolsLabel(DEFAULT_COVERAGE_REPORT_GENERATOR_VALUE)))) | |
| // The target itself and run_under both run on the same machine. | |
| .add(attr(":run_under", LABEL).value(RUN_UNDER).skipPrereqValidatorCheck()) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$test_base_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(RootRule.class, MakeVariableExpandingRule.class) | |
| .build(); | |
| } | |
| } | |
| private static final String TOOLS_TEST_RUNTIME_TARGET_PATTERN = "//tools/test:runtime"; | |
| private static ImmutableList<Label> testRuntimeLabelList = null; | |
| // Always return the same ImmutableList<Label> for every $test_runtime attribute's default value. | |
| public static synchronized ImmutableList<Label> getTestRuntimeLabelList( | |
| RuleDefinitionContext env) { | |
| if (testRuntimeLabelList == null) { | |
| testRuntimeLabelList = | |
| ImmutableList.of( | |
| Label.parseAbsoluteUnchecked( | |
| env.getToolsRepository() + TOOLS_TEST_RUNTIME_TARGET_PATTERN)); | |
| } | |
| return testRuntimeLabelList; | |
| } | |
| /** | |
| * The attribute used to list the configuration properties used by a target and its transitive | |
| * dependencies. Currently only supports config_feature_flag. | |
| */ | |
| public static final String TAGGED_TRIMMING_ATTR = "transitive_configs"; | |
| /** Share common attributes across both base and Starlark base rules. */ | |
| public static RuleClass.Builder commonCoreAndSkylarkAttributes(RuleClass.Builder builder) { | |
| return builder | |
| // The visibility attribute is special: it is a nodep label, and loading the | |
| // necessary package groups is handled by {@link LabelVisitor#visitTargetVisibility}. | |
| // Package groups always have the null configuration so that they are not duplicated | |
| // needlessly. | |
| .add( | |
| attr("visibility", NODEP_LABEL_LIST) | |
| .orderIndependent() | |
| .cfg(HostTransition.createFactory()) | |
| .nonconfigurable( | |
| "special attribute integrated more deeply into Bazel's core logic")) | |
| .add( | |
| attr(TAGGED_TRIMMING_ATTR, NODEP_LABEL_LIST) | |
| .orderIndependent() | |
| .nonconfigurable("Used in determining configuration")) | |
| .add( | |
| attr("deprecation", STRING) | |
| .value(deprecationDefault) | |
| .nonconfigurable("Used in core loading phase logic with no access to configs")) | |
| .add( | |
| attr("tags", STRING_LIST) | |
| .orderIndependent() | |
| .taggable() | |
| .nonconfigurable("low-level attribute, used in TargetUtils without configurations")) | |
| .add( | |
| attr("generator_name", STRING) | |
| .undocumented("internal") | |
| .nonconfigurable("static structure of a rule")) | |
| .add( | |
| attr("generator_function", STRING) | |
| .undocumented("internal") | |
| .nonconfigurable("static structure of a rule")) | |
| .add( | |
| attr("generator_location", STRING) | |
| .undocumented("internal") | |
| .nonconfigurable("static structure of a rule")) | |
| .add( | |
| attr("testonly", BOOLEAN) | |
| .value(testonlyDefault) | |
| .nonconfigurable("policy decision: rules testability should be consistent")) | |
| .add(attr("features", STRING_LIST).orderIndependent()) | |
| .add( | |
| attr(":action_listener", LABEL_LIST) | |
| .cfg(HostTransition.createFactory()) | |
| .value(ACTION_LISTENER)) | |
| .add( | |
| attr(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST) | |
| .allowedRuleClasses(ConstraintConstants.ENVIRONMENT_RULE) | |
| .cfg(HostTransition.createFactory()) | |
| .allowedFileTypes(FileTypeSet.NO_FILE) | |
| .dontCheckConstraints() | |
| .nonconfigurable( | |
| "special logic for constraints and select: see ConstraintSemantics")) | |
| .add( | |
| attr(RuleClass.RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST) | |
| .allowedRuleClasses(ConstraintConstants.ENVIRONMENT_RULE) | |
| .cfg(HostTransition.createFactory()) | |
| .allowedFileTypes(FileTypeSet.NO_FILE) | |
| .dontCheckConstraints() | |
| .nonconfigurable( | |
| "special logic for constraints and select: see ConstraintSemantics")) | |
| .add( | |
| attr(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE, LABEL_LIST) | |
| .nonconfigurable("stores configurability keys")) | |
| .add( | |
| attr(RuleClass.APPLICABLE_LICENSES_ATTR, LABEL_LIST) | |
| .cfg(HostTransition.createFactory()) | |
| .allowedFileTypes(FileTypeSet.NO_FILE) | |
| // TODO(b/148601291): Require provider to be "LicenseInfo". | |
| .dontCheckConstraints() | |
| .nonconfigurable("applicable_licenses is not configurable")); | |
| } | |
| public static RuleClass.Builder nameAttribute(RuleClass.Builder builder) { | |
| return builder.add(attr("name", STRING).nonconfigurable("Rule name")); | |
| } | |
| public static RuleClass.Builder execPropertiesAttribute(RuleClass.Builder builder) | |
| throws ConversionException { | |
| return builder.add( | |
| attr(RuleClass.EXEC_PROPERTIES, STRING_DICT).defaultValue(ImmutableMap.of())); | |
| } | |
| /** | |
| * Ancestor of every rule. | |
| * | |
| * <p>Adds the name attribute to every rule. | |
| */ | |
| public static final class RootRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { | |
| return nameAttribute(builder).build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$root_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .build(); | |
| } | |
| } | |
| /** | |
| * Common parts of some rules. | |
| */ | |
| public static final class BaseRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return commonCoreAndSkylarkAttributes(builder) | |
| .add( | |
| attr("licenses", LICENSE) | |
| .nonconfigurable("Used in core loading phase logic with no access to configs")) | |
| .add( | |
| attr("distribs", DISTRIBUTIONS) | |
| .nonconfigurable("Used in core loading phase logic with no access to configs")) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$base_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(RootRule.class) | |
| .build(); | |
| } | |
| } | |
| /** | |
| * A rule that contains a {@code variables=} attribute to allow referencing Make variables. | |
| */ | |
| public static final class MakeVariableExpandingRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return builder | |
| // Documented in | |
| // com/google/devtools/build/docgen/templates/attributes/common/toolchains.html. | |
| .add( | |
| attr("toolchains", LABEL_LIST) | |
| .allowedFileTypes(FileTypeSet.NO_FILE) | |
| .mandatoryProviders(ImmutableList.of(TemplateVariableInfo.PROVIDER.id())) | |
| .dontCheckConstraints()) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$make_variable_expanding_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .build(); | |
| } | |
| } | |
| /** | |
| * Common ancestor class for some rules. | |
| */ | |
| public static final class RuleBase implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return builder | |
| .add(attr("deps", LABEL_LIST).legacyAllowAnyFileType()) | |
| .add( | |
| attr("data", LABEL_LIST) | |
| .allowedFileTypes(FileTypeSet.ANY_FILE) | |
| .dontCheckConstraints()) | |
| .add(attr(RuleClass.EXEC_PROPERTIES, Type.STRING_DICT).value(ImmutableMap.of())) | |
| .add( | |
| attr(RuleClass.EXEC_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST) | |
| .allowedFileTypes() | |
| .nonconfigurable("Used in toolchain resolution") | |
| .value(ImmutableList.of())) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(BaseRule.class) | |
| .build(); | |
| } | |
| } | |
| public static final ImmutableSet<String> ALLOWED_RULE_CLASSES = | |
| ImmutableSet.of("filegroup", "genrule", "Fileset"); | |
| /** A base rule for all binary rules. */ | |
| public static final class BinaryBaseRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return builder | |
| .add(attr("args", STRING_LIST)) | |
| .add(attr("output_licenses", LICENSE)) | |
| .add( | |
| attr("$is_executable", BOOLEAN) | |
| .value(true) | |
| .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target")) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$binary_base_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(RootRule.class, MakeVariableExpandingRule.class) | |
| .build(); | |
| } | |
| } | |
| /** Rule class for rules in error. */ | |
| public static final class ErrorRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return builder.publicByDefault().build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$error_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(BaseRuleClasses.BaseRule.class) | |
| .build(); | |
| } | |
| } | |
| } |