| // Copyright 2018 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.MoreObjects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| 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 java.util.Collection; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * Parts of an {@link EnvironmentGroup} that are needed for analysis. Since {@link EnvironmentGroup} |
| * keeps a reference to a {@link Package} object, it is too heavyweight to store in analysis. |
| * |
| * <p>Constructor should only be called by {@link EnvironmentGroup}, and this object must never be |
| * accessed externally until after {@link EnvironmentGroup#processMemberEnvironments} is called. The |
| * mutability of fulfillersMap means that we must take care to wait until it is set before doing |
| * anything with this class. |
| */ |
| public final class EnvironmentLabels { |
| final Label label; |
| final ImmutableSet<Label> environments; |
| final ImmutableSet<Label> defaults; |
| /** |
| * Maps a member environment to the set of environments that directly fulfill it. Note that we |
| * can't set this map until all Target instances for member environments have been initialized, |
| * which occurs after group instantiation (this makes the class mutable). |
| */ |
| private Map<Label, NestedSet<Label>> fulfillersMap; |
| |
| EnvironmentLabels(Label label, Collection<Label> environments, Collection<Label> defaults) { |
| this(label, environments, defaults, null); |
| } |
| |
| /** |
| * Only for use by serialization: the mutable fulfillersMap object is not properly initialized |
| * otherwise during deserialization. |
| */ |
| private EnvironmentLabels( |
| Label label, |
| Collection<Label> environments, |
| Collection<Label> defaults, |
| Map<Label, NestedSet<Label>> fulfillersMap) { |
| this.label = label; |
| this.environments = ImmutableSet.copyOf(environments); |
| this.defaults = ImmutableSet.copyOf(defaults); |
| this.fulfillersMap = fulfillersMap; |
| } |
| |
| void assertNotInitialized() { |
| Preconditions.checkState(fulfillersMap == null, this); |
| } |
| |
| void checkInitialized() { |
| Preconditions.checkNotNull(fulfillersMap, this); |
| } |
| |
| void setFulfillersMap(Map<Label, NestedSet<Label>> fulfillersMap) { |
| Preconditions.checkState(this.fulfillersMap == null, this); |
| this.fulfillersMap = Collections.unmodifiableMap(fulfillersMap); |
| } |
| |
| public Set<Label> getEnvironments() { |
| checkInitialized(); |
| return environments; |
| } |
| |
| public Set<Label> getDefaults() { |
| checkInitialized(); |
| 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) { |
| checkInitialized(); |
| 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 NestedSet<Label> getFulfillers(Label environment) { |
| checkInitialized(); |
| NestedSet<Label> ans = fulfillersMap.get(environment); |
| return ans == null ? NestedSetBuilder.emptySet(Order.STABLE_ORDER) : ans; |
| } |
| |
| public Label getLabel() { |
| checkInitialized(); |
| return label; |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("label", label) |
| .add("sizes", environments.size() + ", " + defaults.size() + ", " + fulfillersMap.size()) |
| .add("environments", environments) |
| .add("defaults", defaults) |
| .add("fulfillersMap", fulfillersMap) |
| .toString(); |
| } |
| |
| @Override |
| public int hashCode() { |
| checkInitialized(); |
| return Objects.hash(label, environments, defaults, fulfillersMap.keySet()); |
| } |
| |
| /** |
| * Compares {@code map1} and {@code map2} using deep equality for their values. Should be feasible |
| * because comparison will usually only happen between == objects, so this is hit rarely. If |
| * objects are equal, but have been deserialized separately so not ==, this should still be ok |
| * because these nested sets are not particularly big, and there are very few EnvironmentGroups |
| * (and therefore EnvironmentLabels) in any given build. |
| * |
| * <p>This will have to be revisited if it turns out to be noticeably expensive. It should be |
| * sound to not compare the values of the fulfillerMaps at all, since they are determined from the |
| * package each EnvironmentLabel is associated with, and so as long as EnvironmentLabels from |
| * different source states but the same package are not compared, the values shouldn't be |
| * necessary. |
| */ |
| private static boolean fulfillerMapsEqual( |
| Map<Label, NestedSet<Label>> map1, Map<Label, NestedSet<Label>> map2) { |
| if (map1 == map2) { |
| return true; |
| } |
| if (map1.size() != map2.size()) { |
| return false; |
| } |
| for (Map.Entry<Label, NestedSet<Label>> entry : map1.entrySet()) { |
| NestedSet<Label> secondValue = map2.get(entry.getKey()); |
| // Do shallowEquals check first for speed. |
| if (secondValue == null |
| || (!entry.getValue().shallowEquals(secondValue) |
| && !entry.getValue().toList().equals(secondValue.toList()))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| checkInitialized(); |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| EnvironmentLabels that = (EnvironmentLabels) o; |
| that.checkInitialized(); |
| return label.equals(that.label) |
| && environments.equals(that.environments) |
| && defaults.equals(that.defaults) |
| && fulfillerMapsEqual(this.fulfillersMap, that.fulfillersMap); |
| } |
| } |