| // 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.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.Types.STRING_LIST; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; | |
| import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; | |
| import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory; | |
| import com.google.devtools.build.lib.analysis.config.RunUnder.LabelRunUnder; | |
| import com.google.devtools.build.lib.analysis.config.transitions.NoConfigTransition; | |
| 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; | |
| 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.TargetUtils; | |
| import com.google.devtools.build.lib.packages.TestSize; | |
| import com.google.devtools.build.lib.packages.Type; | |
| import com.google.devtools.build.lib.packages.Types; | |
| import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; | |
| import com.google.devtools.build.lib.util.FileTypeSet; | |
| import javax.annotation.Nullable; | |
| /** Rule class definitions used by (almost) every rule. */ | |
| public class BaseRuleClasses { | |
| private BaseRuleClasses() {} | |
| @SerializationConstant @VisibleForSerialization | |
| static final Attribute.ComputedDefault testonlyDefault = | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| return rule.getPackageArgs().defaultTestOnly(); | |
| } | |
| }; | |
| @SerializationConstant @VisibleForSerialization | |
| static final Attribute.ComputedDefault deprecationDefault = | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| return rule.getPackageArgs().defaultDeprecation(); | |
| } | |
| }; | |
| @SerializationConstant @VisibleForSerialization | |
| public static final Attribute.ComputedDefault TIMEOUT_DEFAULT = | |
| 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"; | |
| } | |
| }; | |
| @SerializationConstant @VisibleForSerialization | |
| public static final Attribute.ComputedDefault packageMetadataDefault = | |
| new Attribute.ComputedDefault() { | |
| @Override | |
| public Object getDefault(AttributeMap rule) { | |
| return rule.getPackageArgs().defaultPackageMetadata(); | |
| } | |
| }; | |
| // 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). | |
| */ | |
| @SerializationConstant @VisibleForSerialization @VisibleForTesting | |
| static final LabelListLateBoundDefault<?> ACTION_LISTENER = | |
| LabelListLateBoundDefault.fromTargetConfiguration( | |
| BuildConfigurationValue.class, | |
| (rule, attributes, configuration) -> configuration.getActionListeners()); | |
| public static final String DEFAULT_COVERAGE_SUPPORT_VALUE = "//tools/test:coverage_support"; | |
| @SerializationConstant @VisibleForSerialization | |
| 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"; | |
| @SerializationConstant @VisibleForSerialization | |
| static final Resolver<CoverageConfiguration, Label> | |
| COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER = | |
| (rule, attributes, configuration) -> configuration.reportGenerator(); | |
| public static LabelLateBoundDefault<CoverageConfiguration> coverageReportGeneratorAttribute( | |
| Label defaultValue) { | |
| return LabelLateBoundDefault.fromTargetConfiguration( | |
| CoverageConfiguration.class, | |
| defaultValue, | |
| COVERAGE_REPORT_GENERATOR_CONFIGURATION_RESOLVER); | |
| } | |
| // TODO(b/65746853): provide a way to do this without passing the entire configuration | |
| /** | |
| * Resolves the latebound exec-configured :run_under label. This only exists if --run_under is set | |
| * to a label and --incompatible_bazel_test_exec_run_under is true. Else it's null. | |
| * | |
| * <p>{@link RUN_UNDER_EXEC_CONFIG} and {@link RUN_UNDER_TARGET_CONFIG} cannot both be non-null in | |
| * a build: if {@code --run_under} is set it must connect to a single dependency. | |
| */ | |
| @SerializationConstant @VisibleForSerialization | |
| public static final LabelLateBoundDefault<?> RUN_UNDER_EXEC_CONFIG = | |
| LabelLateBoundDefault.fromTargetConfiguration( | |
| BuildConfigurationValue.class, | |
| null, | |
| (rule, attributes, config) -> { | |
| if (config.isExecConfiguration() | |
| // This is the opposite of RUN_UNDER_TARGET_CONFIG, so both can't be non-null. | |
| || !config.runUnderExecConfigForTests() | |
| || config.getRunUnder() == null) { | |
| return null; | |
| } | |
| return config.getRunUnder() instanceof LabelRunUnder runUnder ? runUnder.label() : null; | |
| }); | |
| // TODO(b/65746853): provide a way to do this without passing the entire configuration | |
| /** | |
| * Resolves the latebound target-configured :run_under label. This only exists if --run_under is | |
| * set to a label and --incompatible_bazel_test_exec_run_under is false. Else it's null. | |
| * | |
| * <p>{@link RUN_UNDER_EXEC_CONFIG} and {@link RUN_UNDER_TARGET_CONFIG} cannot both be non-null in | |
| * a build: if {@code --run_under} is set it must connect to a single dependency. | |
| */ | |
| public static final LabelLateBoundDefault<?> RUN_UNDER_TARGET_CONFIG = | |
| LabelLateBoundDefault.fromTargetConfiguration( | |
| BuildConfigurationValue.class, | |
| null, | |
| (rule, attributes, config) -> { | |
| if (config.isExecConfiguration() | |
| // This is the opposite of RUN_UNDER_EXEC_CONFIG, so both can't be non-null. | |
| || config.runUnderExecConfigForTests() | |
| || config.getRunUnder() == null) { | |
| return null; | |
| } | |
| return config.getRunUnder() instanceof LabelRunUnder runUnder ? runUnder.label() : null; | |
| }); | |
| 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( | |
| RuleDefinitionEnvironment env) { | |
| if (testRuntimeLabelList == null) { | |
| testRuntimeLabelList = | |
| ImmutableList.of( | |
| Label.parseCanonicalUnchecked( | |
| 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. | |
| * | |
| * <p>A special value of "//command_line_option/fragments:test" instructs | |
| * TestTrimmingTransitionFactory to skip trimming for this rule. | |
| */ | |
| public static final String TAGGED_TRIMMING_ATTR = "transitive_configs"; | |
| /** Share common attributes across both base and Starlark base rules. */ | |
| // TODO(bazel-team): replace this with a common RuleDefinition ancestor of NativeBuildRule | |
| // and StarlarkRuleClassFunctions.baseRule. This requires refactoring StarlarkRuleClassFunctions | |
| // to instantiate its RuleClasses through RuleDefinition. | |
| public static RuleClass.Builder commonCoreAndStarlarkAttributes(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(ExecutionTransitionFactory.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(ExecutionTransitionFactory.createFactory()) | |
| .value(ACTION_LISTENER)) | |
| .add( | |
| attr(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST) | |
| .allowedRuleClasses(ConstraintConstants.ENVIRONMENT_RULE) | |
| .cfg(NoConfigTransition.getFactory()) | |
| .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(NoConfigTransition.getFactory()) | |
| .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_METADATA_ATTR, LABEL_LIST) | |
| .value(packageMetadataDefault) | |
| .cfg(NoConfigTransition.getFactory()) | |
| .allowedFileTypes(FileTypeSet.NO_FILE) | |
| .dontCheckConstraints() | |
| .nonconfigurable("applicable_metadata is not configurable")) | |
| .add(attr(RuleClass.ASPECT_HINTS_ATTR, LABEL_LIST).allowedFileTypes(FileTypeSet.NO_FILE)); | |
| } | |
| /** | |
| * Ancestor of every native rule in BUILD files (not WORKSPACE files). | |
| * | |
| * <p>This includes: | |
| * | |
| * <ul> | |
| * <li>rules that create actions ({@link NativeActionCreatingRule}) | |
| * <li>rules that encapsulate toolchain and build environment context | |
| * <li>rules that aggregate other rules (like file groups, test suites, or aliases) | |
| * </ul> | |
| */ | |
| public static final class NativeBuildRule implements RuleDefinition { | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| return commonCoreAndStarlarkAttributes(builder) | |
| .add( | |
| attr("licenses", LICENSE) | |
| .nonconfigurable("Used in core loading phase logic with no access to configs")) | |
| .add( | |
| // TODO: b/148549967 - Remove for Bazel 9.0 | |
| attr("distribs", STRING_LIST).nonconfigurable("deprecated - no op")) | |
| // Any rule that provides its own meaning for the "target_compatible_with" attribute | |
| // has to be excluded in `IncompatibleTargetChecker`. | |
| .add( | |
| attr(RuleClass.TARGET_COMPATIBLE_WITH_ATTR, LABEL_LIST) | |
| .mandatoryBuiltinProviders(ImmutableList.of(ConfigMatchingProvider.class)) | |
| .mandatoryProviders(ConstraintValueInfo.PROVIDER.id()) | |
| // This should be configurable to allow for complex types of restrictions. | |
| .tool( | |
| "target_compatible_with exists for constraint checking, not to create an" | |
| + " actual dependency") | |
| .allowedFileTypes(FileTypeSet.NO_FILE)) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$native_build_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .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(); | |
| } | |
| } | |
| /** | |
| * Ancestor of every native BUILD rule that creates actions. | |
| * | |
| * <p>This is a subset of all BUILD rules. Filegroups and aliases, for example, simply encapsulate | |
| * other rules. Toolchain rules provide metadata for actions of other rules. See {@link | |
| * NativeBuildRule} for these. | |
| */ | |
| public static final class NativeActionCreatingRule 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_ATTR, Types.STRING_DICT).value(ImmutableMap.of())) | |
| .add( | |
| attr(RuleClass.EXEC_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST) | |
| .allowedFileTypes() | |
| .nonconfigurable("Used in toolchain resolution") | |
| .tool( | |
| "exec_compatible_with exists for constraint checking, not to create an" | |
| + " actual dependency") | |
| .value(ImmutableList.of())) | |
| .add( | |
| attr(RuleClass.EXEC_GROUP_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST_DICT) | |
| .allowedFileTypes() | |
| .nonconfigurable("Used in toolchain resolution") | |
| .tool( | |
| "exec_group_compatible_with exists for constraint checking, not to create an" | |
| + " actual dependency") | |
| .value(ImmutableMap.of())) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| return RuleDefinition.Metadata.builder() | |
| .name("$native_buildable_rule") | |
| .type(RuleClassType.ABSTRACT) | |
| .ancestors(BaseRuleClasses.NativeBuildRule.class) | |
| .build(); | |
| } | |
| } | |
| /** | |
| * An empty rule that exists for the sole purpose to completely remove a native rule while it's | |
| * still defined as a Starlark rule in builtins. | |
| * | |
| * <p>Use it like <code>builder.addRuleDefinition(new BaseRuleClasses.EmptyRule("name") {}); | |
| * </code>. The <code>{}</code> create a new class for each rule. That's needed because {@link | |
| * ConfiguredRuleClassProvider.Builder} assumes each rule class has a different Java class. | |
| */ | |
| public abstract static class EmptyRule implements RuleDefinition { | |
| private final String name; | |
| @Nullable private final String bzlLoadFile; | |
| public EmptyRule(String name) { | |
| this(name, null); | |
| } | |
| public EmptyRule(String name, @Nullable String bzlLoadLabel) { | |
| this.name = name; | |
| this.bzlLoadFile = bzlLoadLabel; | |
| } | |
| @Override | |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { | |
| if (builder.getType() == RuleClassType.TEST) { | |
| builder | |
| .add( | |
| attr("size", STRING) | |
| .nonconfigurable("policy decision: should be consistent across configurations")) | |
| .add( | |
| attr("timeout", STRING) | |
| .nonconfigurable("policy decision: should be consistent across configurations")) | |
| .add(attr("flaky", BOOLEAN)) | |
| .add(attr("shard_count", INTEGER)) | |
| .add(attr("local", BOOLEAN)); | |
| } | |
| return builder | |
| .removeAttribute("deps") | |
| .removeAttribute("data") | |
| .addAttribute(attr("$bzl_load_label", STRING).value(this.bzlLoadFile).build()) | |
| .build(); | |
| } | |
| @Override | |
| public Metadata getMetadata() { | |
| Metadata.Builder metadata = | |
| Metadata.builder() | |
| .name(name) | |
| .type(TargetUtils.isTestRuleName(name) ? RuleClassType.TEST : RuleClassType.NORMAL) | |
| .ancestors(BaseRuleClasses.NativeActionCreatingRule.class) | |
| .factoryClass(EmptyRuleConfiguredTargetFactory.class); | |
| return metadata.build(); | |
| } | |
| } | |
| /** | |
| * Factory used by rules' definitions that exist for the sole purpose of providing documentation. | |
| * For most of these rules, the actual rule is implemented in Starlark but the documentation | |
| * generation mechanism does not work yet for Starlark rules. TODO(bazel-team): Delete once | |
| * documentation tools work for Starlark. | |
| */ | |
| public static class EmptyRuleConfiguredTargetFactory implements RuleConfiguredTargetFactory { | |
| @Override | |
| @Nullable | |
| public ConfiguredTarget create(RuleContext ruleContext) { | |
| String ruleName = ruleContext.getRule().getRuleClass(); | |
| String bzlLoadLabel = ruleContext.attributes().getOrDefault("$bzl_load_label", STRING, null); | |
| if (bzlLoadLabel != null) { | |
| ruleContext.ruleError( | |
| """ | |
| The %s rule has been removed, add the following to your BUILD/bzl file: | |
| load("%s", "%s") | |
| """ | |
| .formatted(ruleName, bzlLoadLabel, ruleName)); | |
| } else { | |
| ruleContext.ruleError("Rule '" + ruleName + "' is unimplemented."); | |
| } | |
| return null; | |
| } | |
| } | |
| } |