| // 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.base.Verify; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| 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 java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| 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; |
| |
| /** |
| * Maps a member environment to the set of environments that directly fulfill it. Note that |
| * we can't populate this map until all Target instances for member environments have been |
| * initialized, which may occur after group instantiation (this makes the class mutable). |
| */ |
| private final Map<Label, NestedSet<Label>> fulfillersMap = new HashMap<>(); |
| |
| /** |
| * 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 #processMemberEnvironments}). |
| * |
| * @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; |
| } |
| |
| /** |
| * Checks that the group's declared environments are legitimate same-package environment |
| * rules and prepares the "fulfills" relationships between these environments to support |
| * {@link #getFulfillers}. |
| * |
| * @param pkgTargets mapping from label name to target instance for this group's package |
| * @return a list of validation errors that occurred |
| */ |
| List<Event> processMemberEnvironments(Map<String, Target> pkgTargets) { |
| List<Event> events = new ArrayList<>(); |
| // Maps an environment to the environments that directly fulfill it. |
| Multimap<Label, Label> directFulfillers = HashMultimap.create(); |
| |
| for (Label envName : environments) { |
| Target env = pkgTargets.get(envName.getName()); |
| if (isValidEnvironment(env, envName, "", events)) { |
| AttributeMap attr = NonconfigurableAttributeMapper.of((Rule) env); |
| for (Label fulfilledEnv : attr.get("fulfills", BuildType.LABEL_LIST)) { |
| if (isValidEnvironment(pkgTargets.get(fulfilledEnv.getName()), fulfilledEnv, |
| "in \"fulfills\" attribute of " + envName + ": ", events)) { |
| directFulfillers.put(fulfilledEnv, envName); |
| } |
| } |
| } |
| } |
| |
| // Now that we know which environments directly fulfill each other, compute which environments |
| // transitively fulfill each other. We could alternatively compute this on-demand, but since |
| // we don't expect these chains to be very large we opt toward computing them once at package |
| // load time. |
| Verify.verify(fulfillersMap.isEmpty()); |
| for (Label envName : environments) { |
| setTransitiveFulfillers(envName, directFulfillers, fulfillersMap); |
| } |
| |
| return events; |
| } |
| |
| /** |
| * Given an environment and set of environments that directly fulfill it, computes a nested |
| * set of environments that <i>transitively</i> fulfill it, places it into transitiveFulfillers, |
| * and returns that set. |
| */ |
| private static NestedSet<Label> setTransitiveFulfillers(Label env, |
| Multimap<Label, Label> directFulfillers, Map<Label, NestedSet<Label>> transitiveFulfillers) { |
| if (transitiveFulfillers.containsKey(env)) { |
| return transitiveFulfillers.get(env); |
| } else if (!directFulfillers.containsKey(env)) { |
| // Nobody fulfills this environment. |
| NestedSet<Label> emptySet = NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| transitiveFulfillers.put(env, emptySet); |
| return emptySet; |
| } else { |
| NestedSetBuilder<Label> set = NestedSetBuilder.stableOrder(); |
| for (Label fulfillingEnv : directFulfillers.get(env)) { |
| set.add(fulfillingEnv); |
| set.addTransitive( |
| setTransitiveFulfillers(fulfillingEnv, directFulfillers, transitiveFulfillers)); |
| } |
| NestedSet<Label> builtSet = set.build(); |
| transitiveFulfillers.put(env, builtSet); |
| return builtSet; |
| } |
| } |
| |
| private boolean isValidEnvironment(Target env, Label envName, String prefix, List<Event> events) { |
| if (env == null) { |
| events.add(Event.error(location, prefix + "environment " + envName + " does not exist")); |
| return false; |
| } else if (!env.getTargetKind().equals("environment rule")) { |
| events.add(Event.error(location, prefix + env.getLabel() + " is not a valid environment")); |
| return false; |
| } else if (!environments.contains(env.getLabel())) { |
| events.add(Event.error(location, prefix + env.getLabel() + " is not a member of this group")); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * Returns the set of environments that transitively fulfill the specified environment. |
| * The environment must be a valid member of this group. |
| * |
| * <p>>For example, if the input is <code>":foo"</code> and <code>":bar"</code> fulfills |
| * <code>":foo"</code> and <code>":baz"</code> fulfills <code>":bar"</code>, this returns |
| * <code>[":foo", ":bar", ":baz"]</code>. |
| * |
| * <p>If no environments fulfill the input, returns an empty set. |
| */ |
| public Iterable<Label> getFulfillers(Label environment) { |
| return Verify.verifyNotNull(fulfillersMap.get(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()); |
| } |
| } |
| } |