| // 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.analysis.platform; |
| |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.common.base.Functions; |
| import com.google.common.base.Objects; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.events.Location; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; |
| import com.google.devtools.build.lib.skylarkbuildapi.platform.ConstraintCollectionApi; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.EvalUtils; |
| import com.google.devtools.build.lib.syntax.Printer; |
| import com.google.devtools.build.lib.syntax.SkylarkList; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import java.util.function.Function; |
| import javax.annotation.Nullable; |
| |
| /** A collection of constraint values. */ |
| @Immutable |
| @AutoCodec |
| public final class ConstraintCollection |
| implements ConstraintCollectionApi<ConstraintSettingInfo, ConstraintValueInfo> { |
| @Nullable private final ConstraintCollection parent; |
| private final ImmutableMap<ConstraintSettingInfo, ConstraintValueInfo> constraints; |
| |
| /** Creates a new {@link ConstraintCollection} with the given constraint values. */ |
| public ConstraintCollection(ImmutableList<ConstraintValueInfo> constraints) { |
| this(null, constraints); |
| } |
| |
| /** |
| * Creates a new {@link ConstraintCollection} which inherits constraint values from {@code |
| * parent}. |
| * |
| * <p>Any constraints passed in {@code constraints} will override values from {@code parent}. |
| */ |
| public ConstraintCollection( |
| @Nullable ConstraintCollection parent, ImmutableList<ConstraintValueInfo> constraints) { |
| this( |
| parent, |
| constraints.stream() |
| .collect( |
| ImmutableMap.toImmutableMap(ConstraintValueInfo::constraint, Function.identity()))); |
| } |
| |
| @AutoCodec.Instantiator |
| @VisibleForSerialization |
| ConstraintCollection( |
| @Nullable ConstraintCollection parent, |
| ImmutableMap<ConstraintSettingInfo, ConstraintValueInfo> constraints) { |
| this.parent = parent; |
| this.constraints = constraints; |
| } |
| |
| /** |
| * Returns {@code true} if this {@link ConstraintCollection} contains every {@link |
| * ConstraintValueInfo} in {@code expected}, or if the expected constraint value is the default |
| * for its setting. |
| */ |
| public boolean containsAll(Iterable<ConstraintValueInfo> expected) { |
| return findMissing(expected).isEmpty(); |
| } |
| |
| /** |
| * Returns the set of {@link ConstraintValueInfo constraints} from {@code expected} that are not |
| * present in this {@link ConstraintCollection}, either directly, or by being the default for |
| * their {@link ConstraintSettingInfo}. |
| */ |
| public ImmutableList<ConstraintValueInfo> findMissing(Iterable<ConstraintValueInfo> expected) { |
| ImmutableList.Builder<ConstraintValueInfo> missing = new ImmutableList.Builder<>(); |
| // For every constraint check if it is (1) non-null and (2) set correctly. |
| for (ConstraintValueInfo constraint : expected) { |
| ConstraintSettingInfo setting = constraint.constraint(); |
| ConstraintValueInfo targetValue = get(setting); |
| if (targetValue == null || !constraint.equals(targetValue)) { |
| missing.add(constraint); |
| } |
| } |
| return missing.build(); |
| } |
| |
| /** |
| * Returns the set of {@link ConstraintSettingInfo settings} where this {@link |
| * ConstraintCollection} and {@code other} have different {@link ConstraintValueInfo values}. |
| */ |
| public ImmutableSet<ConstraintSettingInfo> diff(ConstraintCollection other) { |
| ImmutableSet<ConstraintSettingInfo> constraintsToCheck = |
| new ImmutableSet.Builder<ConstraintSettingInfo>() |
| .addAll(this.constraintSettings()) |
| .addAll(other.constraintSettings()) |
| .build(); |
| ImmutableSet.Builder<ConstraintSettingInfo> mismatchSettings = new ImmutableSet.Builder<>(); |
| for (ConstraintSettingInfo constraintSetting : constraintsToCheck) { |
| ConstraintValueInfo thisConstraint = this.get(constraintSetting); |
| ConstraintValueInfo otherConstraint = other.get(constraintSetting); |
| |
| if (thisConstraint != null && !thisConstraint.equals(otherConstraint)) { |
| mismatchSettings.add(constraintSetting); |
| } |
| } |
| |
| return mismatchSettings.build(); |
| } |
| |
| private ConstraintSettingInfo convertKey(Object key, Location loc) throws EvalException { |
| if (!(key instanceof ConstraintSettingInfo)) { |
| throw new EvalException( |
| loc, |
| String.format( |
| "Constraint names must be platform_common.ConstraintSettingInfo, got %s instead", |
| EvalUtils.getDataTypeName(key))); |
| } |
| |
| return (ConstraintSettingInfo) key; |
| } |
| |
| @Override |
| public boolean has(ConstraintSettingInfo constraint) { |
| // First, check locally. |
| if (constraints.containsKey(constraint)) { |
| return true; |
| } |
| |
| // Then, check the parent, directly to ignore defaults. |
| if (parent != null) { |
| return parent.has(constraint); |
| } |
| |
| return constraint.hasDefaultConstraintValue(); |
| } |
| |
| /** |
| * Returns the {@link ConstraintValueInfo} for the given {@link ConstraintSettingInfo}, or {@code |
| * null} if none exists. |
| */ |
| @Nullable |
| @Override |
| public ConstraintValueInfo get(ConstraintSettingInfo constraint) { |
| // First, check locally. |
| if (constraints.containsKey(constraint)) { |
| return constraints.get(constraint); |
| } |
| |
| // Then, check the parent, directly to ignore defaults. |
| if (parent != null) { |
| return parent.get(constraint); |
| } |
| |
| // Finally, Since this constraint isn't set, fall back to the default. |
| return constraint.defaultConstraintValue(); |
| } |
| |
| @Override |
| public SkylarkList<ConstraintSettingInfo> constraintSettings() { |
| return SkylarkList.createImmutable(constraints.keySet()); |
| } |
| |
| @Override |
| public Object getIndex(Object key, Location loc) throws EvalException { |
| ConstraintSettingInfo constraint = convertKey(key, loc); |
| return get(constraint); |
| } |
| |
| @Override |
| public boolean containsKey(Object key, Location loc) throws EvalException { |
| ConstraintSettingInfo constraint = convertKey(key, loc); |
| return has(constraint); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| |
| if (!(other instanceof ConstraintCollection)) { |
| return false; |
| } |
| ConstraintCollection pc = (ConstraintCollection) other; |
| return Objects.equal(this.constraints, pc.constraints); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(constraints); |
| } |
| |
| @Override |
| public String toString() { |
| return Printer.str(this); |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| printer.append("<"); |
| printer.append( |
| constraints.values().stream() |
| .map(ConstraintValueInfo::label) |
| .map(Functions.toStringFunction()) |
| .collect(joining(", "))); |
| printer.append(">"); |
| } |
| |
| /** |
| * Adds information to the {@link Fingerprint} to uniquely identify this collection of |
| * constraints. |
| */ |
| public void addToFingerprint(Fingerprint fp) { |
| fp.addInt(constraints.size()); |
| constraints.values().forEach(constraintValue -> constraintValue.addTo(fp)); |
| } |
| } |