aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/AndroidFeatureFlagSetProvider.java
blob: dae015df42c8c7458ed200cc427687d1c4f9931b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright 2017 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.android;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.BuiltinProvider;
import com.google.devtools.build.lib.packages.NativeInfo;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.config.ConfigFeatureFlag;
import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidFeatureFlagSetProviderApi;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import java.util.Map;

/**
 * Provider for checking the set of feature flags used by an android_binary.
 *
 * <p>Because the feature flags are completely replaced by android_binary, android_test uses this
 * provider to ensure that the test sets the same flags as the binary. Otherwise, the dependencies
 * of the android_test will be compiled with different flags from the android_binary code which runs
 * in the same Android virtual machine, which may cause compatibility issues at runtime.
 */
@Immutable
public final class AndroidFeatureFlagSetProvider extends NativeInfo
    implements AndroidFeatureFlagSetProviderApi {

  public static final String PROVIDER_NAME = "AndroidFeatureFlagSetProvider";
  public static final Provider PROVIDER = new Provider();

  /** The name of the attribute used by Android rules to set config_feature_flags. */
  public static final String FEATURE_FLAG_ATTR = "feature_flags";

  private final Optional<ImmutableMap<Label, String>> flags;

  AndroidFeatureFlagSetProvider(Optional<? extends Map<Label, String>> flags) {
    super(PROVIDER);
    this.flags = flags.transform(ImmutableMap::copyOf);
  }

  public static AndroidFeatureFlagSetProvider create(Optional<? extends Map<Label, String>> flags) {
    return new AndroidFeatureFlagSetProvider(flags.transform(ImmutableMap::copyOf));
  }

  /**
   * Constructs a definition for the attribute used to restrict access to feature flags. The
   * whitelist will only be reached if the feature_flags attribute is explicitly set.
   */
  public static Attribute.Builder<Label> getWhitelistAttribute(RuleDefinitionEnvironment env) {
    return ConfigFeatureFlag.getWhitelistAttribute(env, FEATURE_FLAG_ATTR);
  }

  /**
   * Builds a map which can be used with create, confirming that the desired flag values were
   * actually received, and producing an error if they were not (because aliases were used).
   *
   * <p>If the attribute which defines feature flags was not specified, an empty {@link Optional}
   * instance is returned.
   */
  public static Optional<ImmutableMap<Label, String>> getAndValidateFlagMapFromRuleContext(
      RuleContext ruleContext) throws RuleErrorException {
    NonconfigurableAttributeMapper attrs = NonconfigurableAttributeMapper.of(ruleContext.getRule());

    if (!attrs.isAttributeValueExplicitlySpecified(FEATURE_FLAG_ATTR)) {
      return Optional.absent();
    }

    Map<Label, String> expectedValues =
        attrs.get(FEATURE_FLAG_ATTR, BuildType.LABEL_KEYED_STRING_DICT);
    if (expectedValues.isEmpty()) {
      return Optional.of(ImmutableMap.of());
    }

    if (!ConfigFeatureFlag.isAvailable(ruleContext)) {
      ruleContext.attributeError(
          FEATURE_FLAG_ATTR,
          String.format(
              "the %s attribute is not available in package '%s'",
              FEATURE_FLAG_ATTR, ruleContext.getLabel().getPackageIdentifier()));
      throw new RuleErrorException();
    }

    Iterable<? extends TransitiveInfoCollection> actualTargets =
        ruleContext.getPrerequisites(FEATURE_FLAG_ATTR, Mode.TARGET);
    boolean aliasFound = false;
    for (TransitiveInfoCollection target : actualTargets) {
      Label label = AliasProvider.getDependencyLabel(target);
      if (!label.equals(target.getLabel())) {
        ruleContext.attributeError(
            FEATURE_FLAG_ATTR,
            String.format(
                "Feature flags must be named directly, not through aliases; use '%s', not '%s'",
                target.getLabel(), label));
        aliasFound = true;
      }
    }
    if (aliasFound) {
      throw new RuleErrorException();
    }
    return Optional.of(ImmutableMap.copyOf(expectedValues));
  }

  /**
   * Returns whether it is acceptable to have a dependency with flags {@code depFlags} if the target
   * has flags {@code targetFlags}.
   */
  public static boolean isValidDependency(
      Optional<? extends Map<Label, String>> targetFlags,
      Optional<? extends Map<Label, String>> depFlags) {
    return !depFlags.isPresent()
        || (targetFlags.isPresent() && targetFlags.get().equals(depFlags.get()));
  }

  public Optional<ImmutableMap<Label, String>> getFlags() {
    return flags;
  }

  @Override
  public ImmutableMap<Label, String> getFlagMap() {
    return flags.or(ImmutableMap.of());
  }

  /** Provider class for {@link AndroidFeatureFlagSetProvider} objects. */
  public static class Provider extends BuiltinProvider<AndroidFeatureFlagSetProvider>
      implements AndroidFeatureFlagSetProviderApi.Provider {
    private Provider() {
      super(PROVIDER_NAME, AndroidFeatureFlagSetProvider.class);
    }

    public String getName() {
      return PROVIDER_NAME;
    }

    @Override
    public AndroidFeatureFlagSetProvider create(SkylarkDict<Label, String> flags)
        throws EvalException {
      return new AndroidFeatureFlagSetProvider(
          Optional.of(
              SkylarkDict.castSkylarkDictOrNoneToDict(flags, Label.class, String.class, "flags")));
    }
  }
}