blob: 1a4832c81ba2197923762b4181a1e82d36911b08 [file] [log] [blame]
// 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 com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.stream.Collectors.joining;
import com.google.auto.value.AutoValue;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.starlarkbuildapi.platform.ConstraintCollectionApi;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkSemantics;
/** A collection of constraint values. */
@Immutable
@AutoValue
public abstract class ConstraintCollection
implements ConstraintCollectionApi<ConstraintSettingInfo, ConstraintValueInfo> {
/** A builder class to help create instances of {@link ConstraintCollection}. */
public static final class Builder {
@Nullable private ConstraintCollection parent;
private ImmutableList.Builder<ConstraintValueInfo> constraintValues =
new ImmutableList.Builder<>();
private Builder() {}
/** Sets the parent {@link ConstraintCollection} of this instance. */
@CanIgnoreReturnValue
public Builder parent(@Nullable ConstraintCollection parent) {
this.parent = parent;
return this;
}
/** Adds the given constraints to the current collection. */
public Builder addConstraints(ConstraintValueInfo... constraints) {
return addConstraints(ImmutableList.copyOf(constraints));
}
/** Adds the given constraints to the current collection. */
@CanIgnoreReturnValue
public Builder addConstraints(Iterable<ConstraintValueInfo> constraints) {
constraintValues.addAll(constraints);
return this;
}
/** Returns the completed {@link ConstraintCollection} instance. */
public ConstraintCollection build() throws DuplicateConstraintException {
ImmutableList<ConstraintValueInfo> constraintValues = this.constraintValues.build();
validateConstraints(constraintValues);
return new AutoValue_ConstraintCollection(
this.parent,
constraintValues.stream()
.collect(toImmutableMap(ConstraintValueInfo::constraint, Function.identity())));
}
}
/** Returns a new {@link Builder} suitable for creating {@link ConstraintCollection} instances. */
public static Builder builder() {
return new Builder();
}
@Override
public boolean isImmutable() {
return true; // immutable and Starlark-hashable
}
/**
* Returns the parent {@link ConstraintCollection} for this instance, or {@code null} if none
* exists.
*/
@Nullable
abstract ConstraintCollection parent();
/** Returns the constraints supplied by this collection. */
abstract ImmutableMap<ConstraintSettingInfo, ConstraintValueInfo> 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 static ConstraintSettingInfo convertKey(Object key) throws EvalException {
if (!(key instanceof ConstraintSettingInfo)) {
throw Starlark.errorf(
"Constraint names must be platform_common.ConstraintSettingInfo, got %s instead",
Starlark.type(key));
}
return (ConstraintSettingInfo) key;
}
@Override
public boolean has(ConstraintSettingInfo constraint) {
// First, check locally.
if (constraints().containsKey(constraint)) {
return true;
}
// Then, check the parent.
if (parent() != null) {
return parent().has(constraint);
}
return constraint.hasDefaultConstraintValue();
}
public boolean hasWithoutDefault(ConstraintSettingInfo constraint) {
// First, check locally.
if (constraints().containsKey(constraint)) {
return true;
}
// Then, check the parent, directly to ignore defaults.
if (parent() != null) {
return parent().hasWithoutDefault(constraint);
}
return false;
}
@Override
public boolean hasConstraintValue(ConstraintValueInfo constraintValue) {
ConstraintValueInfo discoveredConstraintValue = this.get(constraintValue.constraint());
return Objects.equals(constraintValue, discoveredConstraintValue);
}
/**
* 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 Sequence<ConstraintSettingInfo> constraintSettings() {
return StarlarkList.immutableCopyOf(constraints().keySet());
}
@Override
public Object getIndex(StarlarkSemantics semantics, Object key) throws EvalException {
ConstraintSettingInfo constraintSettingInfo = convertKey(key);
Object result = get(constraintSettingInfo);
if (result == null) {
return Starlark.NONE;
}
return result;
}
@Override
public boolean containsKey(StarlarkSemantics semantics, Object key) throws EvalException {
return has(convertKey(key));
}
// It's easier to use the Starlark repr as a string form, not what AutoValue produces.
@Override
public final String toString() {
return Starlark.repr(this);
}
@Override
public void repr(Printer printer) {
printer.append("<");
if (parent() != null) {
printer.append("parent: ");
parent().repr(printer);
printer.append(", ");
}
printer.append("[");
printer.append(
constraints().values().stream()
.map(ConstraintValueInfo::label)
.map(Functions.toStringFunction())
.collect(joining(", ")));
printer.append("]");
printer.append(">");
}
/**
* Adds information to the {@link Fingerprint} to uniquely identify this collection of
* constraints.
*/
public void addToFingerprint(Fingerprint fp) {
// Encode whether there is a parent.
fp.addBoolean(parent() != null);
// Add the parent.
if (parent() != null) {
parent().addToFingerprint(fp);
}
// Add the actual constraints.
fp.addInt(constraints().size());
constraints().values().forEach(constraintValue -> constraintValue.addTo(fp));
}
public static void validateConstraints(Iterable<ConstraintValueInfo> constraintValues)
throws DuplicateConstraintException {
// Collect the constraints by the settings.
ImmutableListMultimap<ConstraintSettingInfo, ConstraintValueInfo> constraints =
Streams.stream(constraintValues)
.collect(
toImmutableListMultimap(ConstraintValueInfo::constraint, Functions.identity()));
// Find settings with duplicate values.
ImmutableListMultimap<ConstraintSettingInfo, ConstraintValueInfo> duplicates =
constraints.asMap().entrySet().stream()
.filter(e -> e.getValue().size() > 1)
.collect(
flatteningToImmutableListMultimap(Map.Entry::getKey, e -> e.getValue().stream()));
if (!duplicates.isEmpty()) {
throw new DuplicateConstraintException(duplicates);
}
}
/**
* Exception class used when more than one {@link ConstraintValueInfo} for the same {@link
* ConstraintSettingInfo} is added to a {@link Builder}.
*/
public static class DuplicateConstraintException extends Exception {
private final ImmutableListMultimap<ConstraintSettingInfo, ConstraintValueInfo>
duplicateConstraints;
DuplicateConstraintException(
ListMultimap<ConstraintSettingInfo, ConstraintValueInfo> duplicateConstraints) {
super(formatError(duplicateConstraints));
this.duplicateConstraints = ImmutableListMultimap.copyOf(duplicateConstraints);
}
public ImmutableListMultimap<ConstraintSettingInfo, ConstraintValueInfo>
duplicateConstraints() {
return duplicateConstraints;
}
public static String formatError(
ListMultimap<ConstraintSettingInfo, ConstraintValueInfo> duplicateConstraints) {
return String.format(
"Duplicate constraint values detected: %s",
duplicateConstraints.asMap().entrySet().stream()
.map(DuplicateConstraintException::describeSingleDuplicateConstraintSetting)
.collect(joining(", ")));
}
private static String describeSingleDuplicateConstraintSetting(
Map.Entry<ConstraintSettingInfo, Collection<ConstraintValueInfo>> duplicate) {
return String.format(
"constraint_setting %s has [%s]",
duplicate.getKey().label(),
duplicate.getValue().stream()
.map(ConstraintValueInfo::label)
.map(Label::toString)
.collect(joining(", ")));
}
}
}