aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/EnvironmentGroup.java
blob: 07da227daf2ebbced4478cb36a02d17b0b5be78f (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
// Copyright 2015 Google Inc. 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.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.syntax.Label;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Model for the "environment_group' rule: the piece of Bazel's rule constraint system that binds
 * thematically related environments together and determines which environments a rule supports
 * by default. See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics}
 * for precise semantic details of how this information is used.
 *
 * <p>Note that "environment_group" is implemented as a loading-time function, not a rule. This is
 * to support proper discovery of defaults: Say rule A has no explicit constraints and depends
 * on rule B, which is explicitly constrained to environment ":bar". Since A declares nothing
 * explicitly, it's implicitly constrained to DEFAULTS (whatever that is). Therefore, the
 * dependency is only allowed if DEFAULTS doesn't include environments beyond ":bar". To figure
 * that out, we need to be able to look up the environment group for ":bar", which is what this
 * class provides.
 *
 * <p>If we implemented this as a rule, we'd have to provide that lookup via rule dependencies,
 * e.g. something like:
 *
 * <code>
 *   environment(
 *       name = 'bar',
 *       group = [':sample_environments'],
 *       is_default = 1
 *   )
 * </code>
 *
 * <p>But this won't work. This would let us find the environment group for ":bar", but the only way
 * to determine what other environments belong to the group is to have the group somehow reference
 * them. That would produce circular dependencies in the build graph, which is no good.
 */
@Immutable
public class EnvironmentGroup implements Target {
  private final Label label;
  private final Location location;
  private final Package containingPackage;
  private final Set<Label> environments;
  private final Set<Label> defaults;

  /**
   * Predicate that matches labels from a different package than the initialized package.
   */
  private static final class DifferentPackage implements Predicate<Label> {
    private final Package containingPackage;

    private DifferentPackage(Package containingPackage) {
      this.containingPackage = containingPackage;
    }

    @Override
    public boolean apply(Label environment) {
      return !environment.getPackageName().equals(containingPackage.getName());
    }
  }

  /**
   * Instantiates a new group without verifying the soundness of its contents. See the validation
   * methods below for appropriate checks.
   *
   * @param label the build label identifying this group
   * @param pkg the package this group belongs to
   * @param environments the set of environments that belong to this group
   * @param defaults the environments a rule implicitly supports unless otherwise specified
   * @param location location in the BUILD file of this group
   */
  EnvironmentGroup(Label label, Package pkg, final List<Label> environments, List<Label> defaults,
      Location location) {
    this.label = label;
    this.location = location;
    this.containingPackage = pkg;
    this.environments = ImmutableSet.copyOf(environments);
    this.defaults = ImmutableSet.copyOf(defaults);
  }

  /**
   * Checks that all environments declared by this group are in the same package as the group (so
   * we can perform an environment --> environment_group lookup and know the package is available)
   * and checks that all defaults are legitimate members of the group.
   *
   * <p>Does <b>not</b> check that the referenced environments exist (see
   * {@link #checkEnvironmentsExist).
   *
   * @return a list of validation errors that occurred
   */
  List<Event> validateMembership() {
    List<Event> events = new ArrayList<>();

    // All environments should belong to the same package as this group.
    for (Label environment :
        Iterables.filter(environments, new DifferentPackage(containingPackage))) {
      events.add(Event.error(location,
          environment + " is not in the same package as group " + label));
    }

    // The defaults must be a subset of the member environments.
    for (Label unknownDefault : Sets.difference(defaults, environments)) {
      events.add(Event.error(location, "default " + unknownDefault + " is not a "
          + "declared environment for group " + getLabel()));
    }

    return events;
  }

  /**
   * Given the set of targets in this group's package, checks that all of the group's declared
   * environments are part of that set (i.e. the group doesn't reference non-existant labels).
   *
   * @param pkgTargets mapping from label name to target instance for this group's package
   * @return a list of validation errors that occurred
   */
  List<Event> checkEnvironmentsExist(Map<String, Target> pkgTargets) {
    List<Event> events = new ArrayList<>();
    for (Label envName : environments) {
      Target env =  pkgTargets.get(envName.getName());
      if (env == null) {
        events.add(Event.error(location, "environment " + envName + " does not exist"));
      } else if (!env.getTargetKind().equals("environment rule")) {
        events.add(Event.error(location, env.getLabel() + " is not a valid environment"));
      }
    }
    return events;
  }

  /**
   * Returns the environments that belong to this group.
   */
  public Set<Label> getEnvironments() {
    return environments;
  }

  /**
   * Returns the environments a rule supports by default, i.e. if it has no explicit references to
   * environments in this group.
   */
  public Set<Label> getDefaults() {
    return defaults;
  }

  /**
   * Determines whether or not an environment is a default. Returns false if the environment
   * doesn't belong to this group.
   */
  public boolean isDefault(Label environment) {
    return defaults.contains(environment);
  }

  @Override
  public Label getLabel() {
    return label;
  }

  @Override
  public String getName() {
    return label.getName();
  }

  @Override
  public Package getPackage() {
    return containingPackage;
  }

  @Override
  public String getTargetKind() {
    return targetKind();
  }

  @Override
  public Rule getAssociatedRule() {
    return null;
  }

  @Override
  public License getLicense() {
    return License.NO_LICENSE;
  }

  @Override
  public Location getLocation() {
    return location;
  }

  @Override
  public String toString() {
   return targetKind() + " " + getLabel();
  }

  @Override
  public Set<License.DistributionType> getDistributions() {
    return Collections.emptySet();
  }

  @Override
  public RuleVisibility getVisibility() {
    return ConstantRuleVisibility.PRIVATE; // No rule should be referencing an environment_group.
  }

  public static String targetKind() {
    return "environment group";
  }

  @Override
  public boolean equals(Object o) {
    // In a distributed implementation these may not be the same object.
    if (o == this) {
      return true;
    } else if (!(o instanceof EnvironmentGroup)) {
      return false;
    } else {
      return ((EnvironmentGroup) o).getLabel().equals(getLabel());
    }
  }
}