blob: f9a759afd87e5a4f8744ccd02f1bf4a885459eb8 [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.rules.config;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.cmdline.Label;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Marker interface for detecting feature flags in the Starlark setting map. */
interface FeatureFlagValue {
/** Returns the value of this flag, or null if it's set default. */
@Nullable
String getValue();
/** A feature flag value for a flag known to be set to a particular value. */
@AutoValue
abstract class SetValue implements FeatureFlagValue {
static SetValue of(String value) {
return new AutoValue_FeatureFlagValue_SetValue(value);
}
@Override
public abstract String getValue();
@Override
public final String toString() {
return String.format("FeatureFlagValue.SetValue{%s}", getValue());
}
}
/** A feature flag value for a flag known to be set to its default value. */
enum DefaultValue implements FeatureFlagValue {
INSTANCE;
@Override
public String getValue() {
return null;
}
@Override
public String toString() {
return "FeatureFlagValue.DefaultValue{}";
}
}
/** A feature flag value for a flag which was requested but which value was already trimmed. */
enum UnknownValue implements FeatureFlagValue {
INSTANCE;
@Override
public String getValue() {
throw new IllegalStateException();
}
@Override
public String toString() {
return "FeatureFlagValue.UnknownValue{}";
}
}
/** Returns a new BuildOptions with a new map of feature flag values. */
static BuildOptions replaceFlagValues(BuildOptions original, Map<Label, String> newValues) {
BuildOptions.Builder result = original.toBuilder();
for (Map.Entry<Label, Object> entry : original.getStarlarkOptions().entrySet()) {
if (entry.getValue() instanceof FeatureFlagValue) {
result.removeStarlarkOption(entry.getKey());
}
}
ImmutableMap.Builder<Label, Object> newValueObjects = new ImmutableMap.Builder<>();
for (Map.Entry<Label, String> entry : newValues.entrySet()) {
newValueObjects.put(entry.getKey(), SetValue.of(entry.getValue()));
}
result.addStarlarkOptions(newValueObjects.build());
BuildOptions builtResult = result.build();
if (builtResult.contains(ConfigFeatureFlagOptions.class)) {
builtResult.get(ConfigFeatureFlagOptions.class).allFeatureFlagValuesArePresent = true;
}
return builtResult;
}
/** Returns a new BuildOptions with the feature flag values trimmed down to the given flags. */
static BuildOptions trimFlagValues(BuildOptions original, Set<Label> availableFlags) {
BuildOptions.Builder result = original.toBuilder();
ImmutableSet.Builder<Label> seenFlagsBuilder = new ImmutableSet.Builder<>();
for (Map.Entry<Label, Object> entry : original.getStarlarkOptions().entrySet()) {
if (entry.getValue() instanceof FeatureFlagValue) {
seenFlagsBuilder.add(entry.getKey());
}
}
ImmutableSet<Label> seenFlags = seenFlagsBuilder.build();
for (Label trimmedFlag : Sets.difference(seenFlags, availableFlags)) {
result.removeStarlarkOption(trimmedFlag);
}
FeatureFlagValue unknownFlagValue =
(original.contains(ConfigFeatureFlagOptions.class)
&& original.get(ConfigFeatureFlagOptions.class).allFeatureFlagValuesArePresent)
? DefaultValue.INSTANCE
: UnknownValue.INSTANCE;
for (Label unknownFlag : Sets.difference(availableFlags, seenFlags)) {
result.addStarlarkOption(unknownFlag, unknownFlagValue);
}
BuildOptions builtResult = result.build();
if (builtResult.contains(ConfigFeatureFlagOptions.class)) {
builtResult.get(ConfigFeatureFlagOptions.class).allFeatureFlagValuesArePresent = false;
}
return builtResult;
}
/**
* Returns the map of known non-default flag values. Throws UnknownValueException when a flag is
* set to UNKNOWN_VALUE (due to an earlier trimming gone wrong).
*/
static ImmutableSortedMap<Label, String> getFlagValues(BuildOptions options)
throws UnknownValueException {
ImmutableSortedMap.Builder<Label, String> knownValues = ImmutableSortedMap.naturalOrder();
ImmutableList.Builder<Label> unknownFlagsBuilder = new ImmutableList.Builder<>();
for (Map.Entry<Label, Object> entry : options.getStarlarkOptions().entrySet()) {
if (entry.getValue().equals(UnknownValue.INSTANCE)) {
unknownFlagsBuilder.add(entry.getKey());
} else if (entry.getValue() instanceof FeatureFlagValue) {
String value = ((FeatureFlagValue) entry.getValue()).getValue();
if (value != null) {
knownValues.put(entry.getKey(), value);
}
}
}
ImmutableList<Label> unknownFlags = unknownFlagsBuilder.build();
if (!unknownFlags.isEmpty()) {
throw new UnknownValueException(unknownFlags);
}
return knownValues.build();
}
/** Exception class for when getFlagValues runs into UNKNOWN_VALUE. */
static final class UnknownValueException extends InvalidConfigurationException {
private static final String ERROR_TEMPLATE =
"Feature flag %1$s was accessed in a configuration it is not present in. All "
+ "targets which depend on %1$s directly or indirectly must name it in their "
+ "transitive_configs attribute.";
private final ImmutableList<Label> unknownFlags;
UnknownValueException(ImmutableList<Label> unknownFlags) {
super(
"Some feature flags were incorrectly specified:\n"
+ unknownFlags.stream()
.map((missingLabel) -> String.format(ERROR_TEMPLATE, missingLabel))
.collect(Collectors.joining("\n")));
this.unknownFlags = unknownFlags;
}
ImmutableList<Label> getUnknownFlags() {
return unknownFlags;
}
}
}