aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/RequiredProviders.java
blob: 2f026c84a214fb648f2f049d230a6204131bec4f (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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
// Copyright 2016 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.packages;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;

/**
 * Represents a constraint on a set of providers required by a dependency (of a rule or an aspect).
 *
 * <p>Currently we support three kinds of constraints:
 *
 * <ul>
 *   <li>accept any dependency.
 *   <li>accept no dependency (used for aspects-on-aspects to indicate that an aspect never wants to
 *       see any other aspect applied to a target.
 *   <li>accept a dependency that provides all providers from one of several sets of providers. It
 *       just so happens that in all current usages these sets are either all native providers or
 *       all Skylark providers, so this is the only use case this class currently supports.
 * </ul>
 */
@Immutable
@AutoCodec
public final class RequiredProviders {
  /** A constraint: either ANY, NONE, or RESTRICTED */
  private final Constraint constraint;
  /**
   * Sets of native providers.
   * If non-empty, {@link #constraint} is {@link Constraint#RESTRICTED}
   */
  private final ImmutableList<ImmutableSet<Class<?>>> nativeProviders;
  /**
   * Sets of native providers.
   * If non-empty, {@link #constraint} is {@link Constraint#RESTRICTED}
   */
  private final ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> skylarkProviders;

  public String getDescription() {
    return constraint.getDescription(this);
  }

  @Override
  public String toString() {
    return getDescription();
  }

  /** Represents one of the constraints as desctibed in {@link RequiredProviders} */
  @VisibleForSerialization
  enum Constraint {
    /** Accept any dependency */
    ANY {
      @Override
      public boolean satisfies(
          AdvertisedProviderSet advertisedProviderSet,
          RequiredProviders requiredProviders,
          Builder missing) {
        return true;
      }

      @Override
      public boolean satisfies(
          Predicate<Class<?>> hasNativeProvider,
          Predicate<SkylarkProviderIdentifier> hasSkylarkProvider,
          RequiredProviders requiredProviders,
          Builder missingProviders) {
        return true;
      }

      @Override
      Builder copyAsBuilder(RequiredProviders providers) {
        return acceptAnyBuilder();
      }

      @Override
      public String getDescription(RequiredProviders providers) {
        return "no providers required";
      }
    },
    /** Accept no dependency */
    NONE {
      @Override
      public boolean satisfies(
          AdvertisedProviderSet advertisedProviderSet,
          RequiredProviders requiredProviders,
          Builder missing) {
        return false;
      }

      @Override
      public boolean satisfies(
          Predicate<Class<?>> hasNativeProvider,
          Predicate<SkylarkProviderIdentifier> hasSkylarkProvider,
          RequiredProviders requiredProviders,
          Builder missingProviders) {
        return false;
      }

      @Override
      Builder copyAsBuilder(RequiredProviders providers) {
        return acceptNoneBuilder();
      }

      @Override
      public String getDescription(RequiredProviders providers) {
        return "no providers accepted";
      }
    },

    /** Accept a dependency that has all providers from one of the sets. */
    RESTRICTED {
      @Override
      public boolean satisfies(
          final AdvertisedProviderSet advertisedProviderSet,
          RequiredProviders requiredProviders,
          Builder missing) {
        if (advertisedProviderSet.canHaveAnyProvider()) {
          return true;
        }
        return satisfies(
            advertisedProviderSet.getNativeProviders()::contains,
            advertisedProviderSet.getSkylarkProviders()::contains,
            requiredProviders,
            missing);
      }

      @Override
      public boolean satisfies(
          Predicate<Class<?>> hasNativeProvider,
          Predicate<SkylarkProviderIdentifier> hasSkylarkProvider,
          RequiredProviders requiredProviders,
          Builder missingProviders) {
        for (ImmutableSet<Class<?>> nativeProviderSet : requiredProviders.nativeProviders) {
          if (nativeProviderSet.stream().allMatch(hasNativeProvider)) {
            return true;
          }

          // Collect missing providers
          if (missingProviders != null) {
            missingProviders.addNativeSet(
                nativeProviderSet
                    .stream()
                    .filter(hasNativeProvider.negate())
                    .collect(ImmutableSet.toImmutableSet()));
          }
        }

        for (ImmutableSet<SkylarkProviderIdentifier> skylarkProviderSet
            : requiredProviders.skylarkProviders) {
          if (skylarkProviderSet.stream().allMatch(hasSkylarkProvider)) {
            return true;
          }
          // Collect missing providers
          if (missingProviders != null) {
            missingProviders.addSkylarkSet(
                skylarkProviderSet
                    .stream()
                    .filter(hasSkylarkProvider.negate())
                    .collect(ImmutableSet.toImmutableSet()));
          }
        }
        return false;
      }

      @Override
      Builder copyAsBuilder(RequiredProviders providers) {
        Builder result = acceptAnyBuilder();
        for (ImmutableSet<Class<?>> nativeProviderSet : providers.nativeProviders) {
          result.addNativeSet(nativeProviderSet);
        }
        for (ImmutableSet<SkylarkProviderIdentifier> skylarkProviderSet :
            providers.skylarkProviders) {
          result.addSkylarkSet(skylarkProviderSet);
        }
        return result;
      }

      @Override
      public String getDescription(RequiredProviders providers) {
        StringBuilder result = new StringBuilder();
        describe(result, providers.nativeProviders, Class::getSimpleName);
        describe(result, providers.skylarkProviders, id -> "'" + id.toString() + "'");
        return result.toString();
      }
    };

    /** Checks if {@code advertisedProviderSet} satisfies these {@code RequiredProviders} */
    public abstract boolean satisfies(
        AdvertisedProviderSet advertisedProviderSet,
        RequiredProviders requiredProviders,
        Builder missing);

    /**
     * Checks if a set of providers encoded by predicates {@code hasNativeProviders} and {@code
     * hasSkylarkProvider} satisfies these {@code RequiredProviders}
     */
    abstract boolean satisfies(
        Predicate<Class<?>> hasNativeProvider,
        Predicate<SkylarkProviderIdentifier> hasSkylarkProvider,
        RequiredProviders requiredProviders,
        @Nullable Builder missingProviders);

    abstract Builder copyAsBuilder(RequiredProviders providers);

    /** Returns a string describing the providers that can be presented to the user. */
    abstract String getDescription(RequiredProviders providers);
  }

  /** Checks if {@code advertisedProviderSet} satisfies this {@code RequiredProviders} instance. */
  public boolean isSatisfiedBy(AdvertisedProviderSet advertisedProviderSet) {
    return constraint.satisfies(advertisedProviderSet, this, null);
  }

  /**
   * Checks if a set of providers encoded by predicates {@code hasNativeProviders}
   * and {@code hasSkylarkProvider} satisfies this {@code RequiredProviders} instance.
   */
  public boolean isSatisfiedBy(
      Predicate<Class<?>> hasNativeProvider,
      Predicate<SkylarkProviderIdentifier> hasSkylarkProvider) {
    return constraint.satisfies(hasNativeProvider, hasSkylarkProvider, this, null);
  }

  /**
   * Returns providers that are missing. If none are missing, returns {@code RequiredProviders} that
   * accept anything.
   */
  public RequiredProviders getMissing(
      Predicate<Class<?>> hasNativeProvider,
      Predicate<SkylarkProviderIdentifier> hasSkylarkProvider) {
    Builder builder = acceptAnyBuilder();
    if (constraint.satisfies(hasNativeProvider, hasSkylarkProvider, this, builder)) {
      // Ignore all collected missing providers.
      return acceptAnyBuilder().build();
    }
    return builder.build();
  }

  /**
   * Returns providers that are missing. If none are missing, returns {@code RequiredProviders} that
   * accept anything.
   */
  public RequiredProviders getMissing(AdvertisedProviderSet set) {
    Builder builder = acceptAnyBuilder();
    if (constraint.satisfies(set, this, builder)) {
      // Ignore all collected missing providers.
      return acceptAnyBuilder().build();
    }
    return builder.build();
  }

  /** Returns true if this {@code RequiredProviders} instance accept any set of providers. */
  public boolean acceptsAny() {
    return constraint.equals(Constraint.ANY);
  }

  @VisibleForSerialization
  RequiredProviders(
      Constraint constraint,
      ImmutableList<ImmutableSet<Class<?>>> nativeProviders,
      ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> skylarkProviders) {
    this.constraint = constraint;

    Preconditions.checkState(constraint.equals(Constraint.RESTRICTED)
        || (nativeProviders.isEmpty() && skylarkProviders.isEmpty())
    );

    this.nativeProviders = nativeProviders;
    this.skylarkProviders = skylarkProviders;
  }

  /** Helper method to describe lists of sets of things. */
  private static <T> void describe(
      StringBuilder result,
      ImmutableList<ImmutableSet<T>> listOfSets,
      Function<T, String> describeOne) {
    Joiner joiner = Joiner.on(", ");
    for (ImmutableSet<T> ids : listOfSets) {
      if (result.length() > 0) {
        result.append(" or ");
      }
      result.append((ids.size() > 1) ? "[" : "");
      joiner.appendTo(result, ids.stream().map(describeOne).iterator());
      result.append((ids.size() > 1) ? "]" : "");
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    RequiredProviders that = (RequiredProviders) o;
    return constraint == that.constraint
        && Objects.equals(nativeProviders, that.nativeProviders)
        && Objects.equals(skylarkProviders, that.skylarkProviders);
  }

  @Override
  public int hashCode() {
    return Objects.hash(constraint, nativeProviders, skylarkProviders);
  }

  /**
   * A builder for {@link RequiredProviders} that accepts any dependency
   * unless restriction provider sets are added.
   */
  public static Builder acceptAnyBuilder() {
    return new Builder(false);
  }

  /**
   * A builder for {@link RequiredProviders} that accepts no dependency
   * unless restriction provider sets are added.
   */
  public static Builder acceptNoneBuilder() {
    return new Builder(true);
  }

  /** Returns a Builder initialized to the same value as this {@code RequiredProvider} */
  public Builder copyAsBuilder() {
    return constraint.copyAsBuilder(this);
  }

  /** A builder for {@link RequiredProviders} */
  public static class Builder {
    private final ImmutableList.Builder<ImmutableSet<Class<?>>> nativeProviders;
    private final ImmutableList.Builder<ImmutableSet<SkylarkProviderIdentifier>> skylarkProviders;
    private Constraint constraint;

    private Builder(boolean acceptNone) {
      constraint = acceptNone ? Constraint.NONE : Constraint.ANY;
      nativeProviders = ImmutableList.builder();
      skylarkProviders = ImmutableList.builder();
    }

    /**
     * Add an alternative set of Skylark providers.
     *
     * If all of these providers are present in the dependency, the dependency satisfies
     * {@link RequiredProviders}.
     */
    public Builder addSkylarkSet(ImmutableSet<SkylarkProviderIdentifier> skylarkProviderSet) {
      constraint = Constraint.RESTRICTED;
      Preconditions.checkState(!skylarkProviderSet.isEmpty());
      this.skylarkProviders.add(skylarkProviderSet);
      return this;
    }

    /**
     * Add an alternative set of native providers.
     *
     * If all of these providers are present in the dependency, the dependency satisfies
     * {@link RequiredProviders}.
     */
    public Builder addNativeSet(ImmutableSet<Class<?>> nativeProviderSet) {
      constraint = Constraint.RESTRICTED;
      Preconditions.checkState(!nativeProviderSet.isEmpty());
      this.nativeProviders.add(nativeProviderSet);
      return this;
    }

    public RequiredProviders build() {
      return new RequiredProviders(constraint, nativeProviders.build(), skylarkProviders.build());
    }
  }
}