// 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.rules.config; import static com.google.devtools.build.lib.packages.Attribute.attr; import static com.google.devtools.build.lib.packages.BuildType.LABEL_KEYED_STRING_DICT; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.syntax.Type.STRING; import static com.google.devtools.build.lib.syntax.Type.STRING_DICT; import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.analysis.BaseRuleClasses; import com.google.devtools.build.lib.analysis.PlatformConfiguration; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.RuleDefinition; import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.syntax.Type; /** * Definitions for rule classes that specify or manipulate configuration settings. * *

These are not "traditional" rule classes in that they can't be requested as top-level * targets and don't translate input artifacts into output artifacts. Instead, they affect * how *other* rules work. See individual class comments for details. */ public class ConfigRuleClasses { private static final String NONCONFIGURABLE_ATTRIBUTE_REASON = "part of a rule class that *triggers* configurable behavior"; /** * Common settings for all configurability rules. */ public static final class ConfigBaseRule implements RuleDefinition { @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { return builder .override(attr("tags", Type.STRING_LIST) // No need to show up in ":all", etc. target patterns. .value(ImmutableList.of("manual")) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) .exemptFromConstraintChecking( "these rules don't include content that gets built into their dependers") .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("$config_base_rule") .type(RuleClass.Builder.RuleClassType.ABSTRACT) .ancestors(BaseRuleClasses.BaseRule.class) .build(); } } /** * A named "partial configuration setting" that specifies a set of command-line "flag=value" * bindings. * *

For example: * *

   *   config_setting(
   *       name = 'foo',
   *       values = {
   *           'flag1': 'aValue'
   *           'flag2': 'bValue'
   *       })
   * 
* *

declares a setting that binds command-line flag * *

flag1
* * to value * *
aValue
* * and * *
flag2
* * to * *
bValue
* * . * *

This is used by configurable attributes to determine which branch to follow based on which * *

config_setting
* * instance matches all its flag values in the configurable attribute owner's configuration. * *

This rule isn't accessed through the standard {@link RuleContext#getPrerequisites} * interface. This is because Bazel constructs a rule's configured attribute map *before* its * {@link RuleContext} is created (in fact, the map is an input to the context's constructor). And * the config_settings referenced by the rule's configurable attributes are themselves inputs to * that map. So Bazel has special logic to read and properly apply config_setting instances. See * {@link com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction#getConfigConditions} for * details. */ public static final class ConfigSettingRule implements RuleDefinition { /** * The name of this rule. */ public static final String RULE_NAME = "config_setting"; /** The name of the attribute that declares flag bindings. */ public static final String SETTINGS_ATTRIBUTE = "values"; /** The name of the attribute that declares "--define foo=bar" flag bindings.*/ public static final String DEFINE_SETTINGS_ATTRIBUTE = "define_values"; /** The name of the attribute that declares user-defined flag bindings. */ public static final String FLAG_SETTINGS_ATTRIBUTE = "flag_values"; /** The name of the attribute that declares constraint_values. */ public static final String CONSTRAINT_VALUES_ATTRIBUTE = "constraint_values"; @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { return builder .requiresConfigurationFragments(PlatformConfiguration.class) /* The set of configuration values that match this rule (expressed as Bazel flags) (Dictionary mapping flags to expected values, both expressed as strings; mandatory)

This rule inherits the configuration of the configured target that references it in a select statement. It is considered to "match" a Bazel invocation if, for every entry in the dictionary, its configuration matches the entry's expected value. For example values = {"compilation_mode": "opt"} matches the invocations bazel build --compilation_mode=opt ... and bazel build -c opt ... on target-configured rules.

For convenience's sake, configuration values are specified as Bazel flags (without the preceding "--"). But keep in mind that the two are not the same. This is because targets can be built in multiple configurations within the same build. For example, a host configuration's "cpu" matches the value of --host_cpu, not --cpu. So different instances of the same config_setting may match the same invocation differently depending on the configuration of the rule using them.

If a flag is not explicitly set at the command line, its default value is used. If a key appears multiple times in the dictionary, only the last instance is used. If a key references a flag that can be set multiple times on the command line (e.g. bazel build --copt=foo --copt=bar --copt=baz ...), a match occurs if any of those settings match.

This and define_values cannot both be empty.

*/ .add( attr(SETTINGS_ATTRIBUTE, STRING_DICT) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) /* The same as values but specifically for the --define flag.

--define is special for two reasons:

  1. It's the primary interface Bazel has today for declaring user-definable settings.
  2. Its syntax (--define KEY=VAL) means KEY=VAL is a value from a Bazel flag perspective.

That means:

            config_setting(
                name = "a_and_b",
                values = {
                    "define": "a=1",
                    "define": "b=2",
                })
          

doesn't work because the same key (define) appears twice in the dictionary. This attribute solves that problem:

            config_setting(
                name = "a_and_b",
                define_values = {
                    "a": "1",
                    "b": "2",
                })
          

corrrectly matches bazel build //foo --define a=1 --define b=2.

--define can still appear in values with normal flag syntax, and can be mixed freely with this attribute as long as dictionary keys remain distinct. */ .add( attr(DEFINE_SETTINGS_ATTRIBUTE, STRING_DICT) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) .add( attr(FLAG_SETTINGS_ATTRIBUTE, LABEL_KEYED_STRING_DICT) .undocumented("the feature flag feature has not yet been launched") .allowedFileTypes() .mandatoryProviders(ImmutableList.of(ConfigFeatureFlagProvider.id())) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) /* The set of constraint_values that match this rule.

A constraint_value is composed of a name and a corresponding constraint_setting which classifies the value. A platform consists of a collection of constraint_value labels which describes target itself and/or how its environment.

            constraint_setting(name = "rock_type")
            constraint_value(name = metamorphic, constraint_setting = "rock_type")
            platform(
              name = "my_platform_rocks",
              constraint_values = [":metamorphic"]
            )
          

As mentioned above, this rule inherits the configuration of the configured target that references it in a select statement. This constraint_values attribute is considered to "match" a Bazel invocation if it includes each constraint_value specified in the configuration's target platform which is set with the command line flag --experimental_platforms. If it contains extra constraint_values not included in the target platform, it is still considered a match. In this example, both slate and marble would be considered matches for a bazel invocation which uses --experimental_platforms=my_platform_rocks. Multiple matches like this may lead to ambiguous select resolves and are not allowed.

            constraint_setting(name = "color")
            constraint_value(name = "white", constraint_setting = "color")

            config_setting(
              name = "slate",
              constraint_values = [":metamorphic"]
            )

            config_setting(
              name = "marble",
              constraint_values = [
                ":metamorphic",
                ":white"
              ]
            )
          
*/ .add( attr(CONSTRAINT_VALUES_ATTRIBUTE, LABEL_LIST) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON) .allowedFileTypes()) .setIsConfigMatcherForConfigSettingOnly() .setOptionReferenceFunctionForConfigSettingOnly( rule -> NonconfigurableAttributeMapper.of(rule) .get(SETTINGS_ATTRIBUTE, Type.STRING_DICT) .keySet()) .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name(RULE_NAME) .type(RuleClass.Builder.RuleClassType.NORMAL) .ancestors(ConfigBaseRule.class) .factoryClass(ConfigSetting.class) .build(); } } /*

Matches an expected configuration state (expressed as Bazel flags) for the purpose of triggering configurable attributes. See select for how to consume this rule and Configurable attributes for an overview of the general feature.

Examples

The following matches any Bazel invocation that specifies --compilation_mode=opt or -c opt (either explicitly at the command line or implicitly from .blazerc files, etc.), when applied to a target configuration rule:

  config_setting(
      name = "simple",
      values = {"compilation_mode": "opt"}
  )
  

The following matches any Bazel invocation that builds for ARM and applies a custom define (e.g. bazel build --cpu=armeabi --define FOO=bar ...), when applied to a target configuration rule:

  config_setting(
      name = "two_conditions",
      values = {
          "cpu": "armeabi",
          "define": "FOO=bar"
      }
  )
  

The following config_setting matches any Bazel invocation that builds a platform which contains exactly the same or a subset of its constraint_values (like the example below).

  config_setting(
      name = "marble",
      constraint_values = [
          "white",
          "metamorphic",
      ]
  )

  platform(
      name = "marble_platform",
      constraint_values = [
          "white",
          "metamorphic"
      ]
  )
  

Notes

See select for policies on what happens depending on how many rules match an invocation.

For flags that support shorthand forms (e.g. --compilation_mode vs. -c), values definitions must use the full form. These automatically match invocations using either form.

The currently endorsed method for creating custom conditions that can't be expressed through dedicated build flags is through the --define flag. Use this flag with caution: it's not ideal and only endorsed for lack of a currently better workaround. See the Configurable attributes section for more discussion.

Try to consolidate config_setting definitions as much as possible. In other words, define //common/conditions:foo in one common package instead of repeating separate instances in //project1:foo, //project2:foo, etc. that all mean the same thing.

values, define_values, and constraint_values can be used in any combination in the same config_setting but at least one must be set for any given config_setting.

*/ /** Rule definition for Android's config_feature_flag rule. */ public static final class ConfigFeatureFlagRule implements RuleDefinition { public static final String RULE_NAME = "config_feature_flag"; @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented(/* the feature flag feature has not yet been launched */) .requiresConfigurationFragments(ConfigFeatureFlagConfiguration.class) .add( attr("allowed_values", STRING_LIST) .mandatory() .nonEmpty() .orderIndependent() .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) .add( attr("default_value", STRING) .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) .add(ConfigFeatureFlag.getWhitelistAttribute(env)) .removeAttribute(BaseRuleClasses.TAGGED_TRIMMING_ATTR) .build(); } @Override public RuleDefinition.Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name(RULE_NAME) .ancestors(ConfigBaseRule.class) .factoryClass(ConfigFeatureFlag.class) .build(); } } }