blob: 0536dc3a68515f1871f32f2a663fc2956216afce [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.packages;
16
17import com.google.common.base.Predicate;
Greg Estrend0f10dc2015-03-17 22:19:37 +000018import com.google.common.base.Verify;
19import com.google.common.collect.HashMultimap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Iterables;
Greg Estrend0f10dc2015-03-17 22:19:37 +000022import com.google.common.collect.Multimap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.Sets;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000024import com.google.devtools.build.lib.cmdline.Label;
Greg Estrend0f10dc2015-03-17 22:19:37 +000025import com.google.devtools.build.lib.collect.nestedset.NestedSet;
26import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
27import com.google.devtools.build.lib.collect.nestedset.Order;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
29import com.google.devtools.build.lib.events.Event;
30import com.google.devtools.build.lib.events.Location;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031
32import java.util.ArrayList;
33import java.util.Collections;
Greg Estrend0f10dc2015-03-17 22:19:37 +000034import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.List;
36import java.util.Map;
37import java.util.Set;
38
39/**
40 * Model for the "environment_group' rule: the piece of Bazel's rule constraint system that binds
41 * thematically related environments together and determines which environments a rule supports
42 * by default. See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics}
43 * for precise semantic details of how this information is used.
44 *
45 * <p>Note that "environment_group" is implemented as a loading-time function, not a rule. This is
46 * to support proper discovery of defaults: Say rule A has no explicit constraints and depends
47 * on rule B, which is explicitly constrained to environment ":bar". Since A declares nothing
48 * explicitly, it's implicitly constrained to DEFAULTS (whatever that is). Therefore, the
49 * dependency is only allowed if DEFAULTS doesn't include environments beyond ":bar". To figure
50 * that out, we need to be able to look up the environment group for ":bar", which is what this
51 * class provides.
52 *
53 * <p>If we implemented this as a rule, we'd have to provide that lookup via rule dependencies,
54 * e.g. something like:
55 *
56 * <code>
57 * environment(
58 * name = 'bar',
59 * group = [':sample_environments'],
60 * is_default = 1
61 * )
62 * </code>
63 *
64 * <p>But this won't work. This would let us find the environment group for ":bar", but the only way
65 * to determine what other environments belong to the group is to have the group somehow reference
66 * them. That would produce circular dependencies in the build graph, which is no good.
67 */
68@Immutable
69public class EnvironmentGroup implements Target {
70 private final Label label;
71 private final Location location;
72 private final Package containingPackage;
73 private final Set<Label> environments;
74 private final Set<Label> defaults;
75
76 /**
Greg Estrend0f10dc2015-03-17 22:19:37 +000077 * Maps a member environment to the set of environments that directly fulfill it. Note that
78 * we can't populate this map until all Target instances for member environments have been
79 * initialized, which may occur after group instantiation (this makes the class mutable).
80 */
81 private final Map<Label, NestedSet<Label>> fulfillersMap = new HashMap<>();
82
83 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084 * Predicate that matches labels from a different package than the initialized package.
85 */
86 private static final class DifferentPackage implements Predicate<Label> {
87 private final Package containingPackage;
88
89 private DifferentPackage(Package containingPackage) {
90 this.containingPackage = containingPackage;
91 }
92
93 @Override
94 public boolean apply(Label environment) {
95 return !environment.getPackageName().equals(containingPackage.getName());
96 }
97 }
98
99 /**
100 * Instantiates a new group without verifying the soundness of its contents. See the validation
101 * methods below for appropriate checks.
102 *
103 * @param label the build label identifying this group
104 * @param pkg the package this group belongs to
105 * @param environments the set of environments that belong to this group
106 * @param defaults the environments a rule implicitly supports unless otherwise specified
107 * @param location location in the BUILD file of this group
108 */
109 EnvironmentGroup(Label label, Package pkg, final List<Label> environments, List<Label> defaults,
110 Location location) {
111 this.label = label;
112 this.location = location;
113 this.containingPackage = pkg;
114 this.environments = ImmutableSet.copyOf(environments);
115 this.defaults = ImmutableSet.copyOf(defaults);
116 }
117
118 /**
119 * Checks that all environments declared by this group are in the same package as the group (so
120 * we can perform an environment --> environment_group lookup and know the package is available)
121 * and checks that all defaults are legitimate members of the group.
122 *
123 * <p>Does <b>not</b> check that the referenced environments exist (see
Greg Estrend0f10dc2015-03-17 22:19:37 +0000124 * {@link #processMemberEnvironments}).
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125 *
126 * @return a list of validation errors that occurred
127 */
128 List<Event> validateMembership() {
129 List<Event> events = new ArrayList<>();
130
131 // All environments should belong to the same package as this group.
132 for (Label environment :
133 Iterables.filter(environments, new DifferentPackage(containingPackage))) {
134 events.add(Event.error(location,
135 environment + " is not in the same package as group " + label));
136 }
137
138 // The defaults must be a subset of the member environments.
139 for (Label unknownDefault : Sets.difference(defaults, environments)) {
140 events.add(Event.error(location, "default " + unknownDefault + " is not a "
141 + "declared environment for group " + getLabel()));
142 }
143
144 return events;
145 }
146
147 /**
Greg Estrend0f10dc2015-03-17 22:19:37 +0000148 * Checks that the group's declared environments are legitimate same-package environment
149 * rules and prepares the "fulfills" relationships between these environments to support
150 * {@link #getFulfillers}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100151 *
152 * @param pkgTargets mapping from label name to target instance for this group's package
153 * @return a list of validation errors that occurred
154 */
Greg Estrend0f10dc2015-03-17 22:19:37 +0000155 List<Event> processMemberEnvironments(Map<String, Target> pkgTargets) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 List<Event> events = new ArrayList<>();
Greg Estrend0f10dc2015-03-17 22:19:37 +0000157 // Maps an environment to the environments that directly fulfill it.
158 Multimap<Label, Label> directFulfillers = HashMultimap.create();
159
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100160 for (Label envName : environments) {
Greg Estrend0f10dc2015-03-17 22:19:37 +0000161 Target env = pkgTargets.get(envName.getName());
162 if (isValidEnvironment(env, envName, "", events)) {
163 AttributeMap attr = NonconfigurableAttributeMapper.of((Rule) env);
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000164 for (Label fulfilledEnv : attr.get("fulfills", BuildType.LABEL_LIST)) {
Greg Estrend0f10dc2015-03-17 22:19:37 +0000165 if (isValidEnvironment(pkgTargets.get(fulfilledEnv.getName()), fulfilledEnv,
166 "in \"fulfills\" attribute of " + envName + ": ", events)) {
167 directFulfillers.put(fulfilledEnv, envName);
168 }
169 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100170 }
171 }
Greg Estrend0f10dc2015-03-17 22:19:37 +0000172
173 // Now that we know which environments directly fulfill each other, compute which environments
174 // transitively fulfill each other. We could alternatively compute this on-demand, but since
175 // we don't expect these chains to be very large we opt toward computing them once at package
176 // load time.
177 Verify.verify(fulfillersMap.isEmpty());
178 for (Label envName : environments) {
179 setTransitiveFulfillers(envName, directFulfillers, fulfillersMap);
180 }
181
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100182 return events;
183 }
184
185 /**
Greg Estrend0f10dc2015-03-17 22:19:37 +0000186 * Given an environment and set of environments that directly fulfill it, computes a nested
187 * set of environments that <i>transitively</i> fulfill it, places it into transitiveFulfillers,
188 * and returns that set.
189 */
190 private static NestedSet<Label> setTransitiveFulfillers(Label env,
191 Multimap<Label, Label> directFulfillers, Map<Label, NestedSet<Label>> transitiveFulfillers) {
192 if (transitiveFulfillers.containsKey(env)) {
193 return transitiveFulfillers.get(env);
194 } else if (!directFulfillers.containsKey(env)) {
195 // Nobody fulfills this environment.
196 NestedSet<Label> emptySet = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
197 transitiveFulfillers.put(env, emptySet);
198 return emptySet;
199 } else {
200 NestedSetBuilder<Label> set = NestedSetBuilder.stableOrder();
201 for (Label fulfillingEnv : directFulfillers.get(env)) {
202 set.add(fulfillingEnv);
203 set.addTransitive(
204 setTransitiveFulfillers(fulfillingEnv, directFulfillers, transitiveFulfillers));
205 }
206 NestedSet<Label> builtSet = set.build();
207 transitiveFulfillers.put(env, builtSet);
208 return builtSet;
209 }
210 }
211
212 private boolean isValidEnvironment(Target env, Label envName, String prefix, List<Event> events) {
213 if (env == null) {
214 events.add(Event.error(location, prefix + "environment " + envName + " does not exist"));
215 return false;
216 } else if (!env.getTargetKind().equals("environment rule")) {
217 events.add(Event.error(location, prefix + env.getLabel() + " is not a valid environment"));
218 return false;
219 } else if (!environments.contains(env.getLabel())) {
220 events.add(Event.error(location, prefix + env.getLabel() + " is not a member of this group"));
221 return false;
222 }
223 return true;
224 }
225
226 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100227 * Returns the environments that belong to this group.
228 */
229 public Set<Label> getEnvironments() {
230 return environments;
231 }
232
233 /**
234 * Returns the environments a rule supports by default, i.e. if it has no explicit references to
235 * environments in this group.
236 */
237 public Set<Label> getDefaults() {
238 return defaults;
239 }
240
241 /**
242 * Determines whether or not an environment is a default. Returns false if the environment
243 * doesn't belong to this group.
244 */
245 public boolean isDefault(Label environment) {
246 return defaults.contains(environment);
247 }
248
Greg Estrend0f10dc2015-03-17 22:19:37 +0000249 /**
250 * Returns the set of environments that transitively fulfill the specified environment.
251 * The environment must be a valid member of this group.
252 *
253 * <p>>For example, if the input is <code>":foo"</code> and <code>":bar"</code> fulfills
254 * <code>":foo"</code> and <code>":baz"</code> fulfills <code>":bar"</code>, this returns
255 * <code>[":foo", ":bar", ":baz"]</code>.
256 *
257 * <p>If no environments fulfill the input, returns an empty set.
258 */
259 public Iterable<Label> getFulfillers(Label environment) {
260 return Verify.verifyNotNull(fulfillersMap.get(environment));
261 }
262
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 @Override
264 public Label getLabel() {
265 return label;
266 }
267
268 @Override
269 public String getName() {
270 return label.getName();
271 }
272
273 @Override
274 public Package getPackage() {
275 return containingPackage;
276 }
277
278 @Override
279 public String getTargetKind() {
280 return targetKind();
281 }
282
283 @Override
284 public Rule getAssociatedRule() {
285 return null;
286 }
287
288 @Override
289 public License getLicense() {
290 return License.NO_LICENSE;
291 }
292
293 @Override
294 public Location getLocation() {
295 return location;
296 }
297
298 @Override
299 public String toString() {
300 return targetKind() + " " + getLabel();
301 }
302
303 @Override
304 public Set<License.DistributionType> getDistributions() {
305 return Collections.emptySet();
306 }
307
308 @Override
309 public RuleVisibility getVisibility() {
310 return ConstantRuleVisibility.PRIVATE; // No rule should be referencing an environment_group.
311 }
312
Greg Estrena6c88962015-09-28 19:35:18 +0000313 @Override
314 public boolean isConfigurable() {
315 return false;
316 }
317
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100318 public static String targetKind() {
319 return "environment group";
320 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100321}