blob: dda852b30decf779aadeb65e0aef3e5cda526d77 [file] [log] [blame]
// Copyright 2017 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.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionMetadataTag;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/** The options fragment which defines {@link ConfigFeatureFlagConfiguration}. */
@AutoCodec(strategy = AutoCodec.Strategy.PUBLIC_FIELDS)
public final class ConfigFeatureFlagOptions extends FragmentOptions {
/** A converter used by the flag options which always returns an empty map, ignoring input. */
public static final class EmptyImmutableSortedMapConverter
implements Converter<ImmutableSortedMap<Label, String>> {
@Override
public ImmutableSortedMap<Label, String> convert(String input) {
return ImmutableSortedMap.<Label, String>of();
}
@Override
public String getTypeDescription() {
return "n/a (do not set this on the command line)";
}
}
/** A converter used by the flag options which always returns an empty set, ignoring input. */
public static final class EmptyImmutableSortedSetConverter
implements Converter<ImmutableSortedSet<Label>> {
@Override
public ImmutableSortedSet<Label> convert(String input) {
return ImmutableSortedSet.of();
}
@Override
public String getTypeDescription() {
return "n/a (do not set this on the command line)";
}
}
/**
* Whether to perform user-guided trimming of feature flags based on the tagging in the
* transitive_configs attribute.
*
* <p>Currently a no-op.
*/
@Option(
name = "enforce_transitive_configs_for_config_feature_flag",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {
OptionEffectTag.LOSES_INCREMENTAL_STATE,
OptionEffectTag.AFFECTS_OUTPUTS,
OptionEffectTag.BUILD_FILE_SEMANTICS,
OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION,
OptionEffectTag.LOADING_AND_ANALYSIS
},
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
defaultValue = "false"
)
public boolean enforceTransitiveConfigsForConfigFeatureFlag = false;
/** The mapping from config_feature_flag rules to their values. */
@Option(
name = "config_feature_flag values (private)",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {
OptionEffectTag.AFFECTS_OUTPUTS,
OptionEffectTag.BUILD_FILE_SEMANTICS,
OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION,
OptionEffectTag.LOADING_AND_ANALYSIS
},
metadataTags = {OptionMetadataTag.INTERNAL},
converter = EmptyImmutableSortedMapConverter.class,
defaultValue = "{}"
)
public ImmutableSortedMap<Label, String> flagValues = ImmutableSortedMap.of();
/**
* The set of feature flags which are definitely set to their default values.
*
* <p>If the set is non-null, the current configuration is trimmed, and this set contains the
* labels of feature flags whose values are known to be default in the current configuration.
* In this case:
*
* <ul>
* <li>Keys present in flagValues are known to have non-default values. The value of such a
* feature flag is the value in flagValues.
* <li>Keys present in this set are known to have default values. The value of such a feature
* flag is its default value.
* <li>Keys missing from both flagValues and this set have unknown values - they may be unset
* and have their default value, or they may be set to a non-default value which has been
* trimmed out. Attempting to access the value of such a feature flag is an error.
* </ul>
*
* <p>If the set is null, the current configuration is untrimmed, and flagValues contains the
* mapping of ALL feature flags with non-default values. In this case:
*
* <ul>
* <li>Keys present in flagValues are known to have non-default values. The value of such a
* feature flag is the value in flagValues.
* <li>Keys missing from flagValues are known to have default values. The value of such a
* feature flag is its default value.
* </ul>
*/
@Option(
name = "config_feature_flag known default values (private)",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {
OptionEffectTag.AFFECTS_OUTPUTS,
OptionEffectTag.BUILD_FILE_SEMANTICS,
OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION,
OptionEffectTag.LOADING_AND_ANALYSIS
},
metadataTags = {OptionMetadataTag.INTERNAL},
converter = EmptyImmutableSortedSetConverter.class,
defaultValue = "null"
)
public ImmutableSortedSet<Label> knownDefaultFlags = null;
/**
* The set of feature flags which were requested but whose values are not known. If this value
* is ever set non-empty, the configuration loader fails.
*/
@Option(
name = "config_feature_flag unknown values (private)",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {
OptionEffectTag.AFFECTS_OUTPUTS,
OptionEffectTag.BUILD_FILE_SEMANTICS,
OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION,
OptionEffectTag.LOADING_AND_ANALYSIS
},
metadataTags = {OptionMetadataTag.INTERNAL},
converter = EmptyImmutableSortedSetConverter.class,
defaultValue = "{}"
)
public ImmutableSortedSet<Label> unknownFlags = ImmutableSortedSet.of();
/**
* Retrieves the map of flag-value pairs for flags which are definitely set to some non-default
* value.
*/
public ImmutableSortedMap<Label, String> getFlagValues() {
return this.flagValues;
}
/**
* Retrieves the set of flags which are definitely set to their default values.
*
* <p>The returned Optional will be empty if {@link isTrimmed} is false. In this case, all flags
* not in {@link getFlagValues} should be considered set to their default values.
*/
public Optional<ImmutableSortedSet<Label>> getKnownDefaultFlags() {
return Optional.ofNullable(this.knownDefaultFlags);
}
/**
* Returns whether this configuration has been trimmed, meaning that not all feature flags' values
* are known.
*/
public boolean isTrimmed() {
return this.knownDefaultFlags != null;
}
/**
* Retrieves the set of flags whose values were requested while trimming, but whose values are not
* known.
*
* <p>If this set is non-empty, this configuration is in error; a target requested a flag which
* was not requested by earlier trimmings.
*/
public ImmutableSortedSet<Label> getUnknownFlags() {
return this.unknownFlags;
}
/**
* Replaces the set of flag-value pairs with the given mapping of flag-value pairs.
*
* <p>Flags not present in the new {@code flagValues} will return to being unset! To set flags
* while still retaining the values already set, call {@link #getFlagValues()} and build a map
* containing both the old values and the new ones. Note that when {@link #isTrimmed()} is true,
* it's not possible to know the values of ALL flags.
*
* <p>Because this method replaces the entire set of flag values, all flag values for this
* configuration are known, and thus knownValues is set to null, and unknownFlags is cleared.
* After this method is called, isTrimmed will return false.
*/
public void replaceFlagValues(Map<Label, String> flagValues) {
this.flagValues = ImmutableSortedMap.copyOf(flagValues);
this.knownDefaultFlags = null;
this.unknownFlags = ImmutableSortedSet.of();
}
/**
* Trims the set of known flag-value pairs to the given set.
*
* <p>Each target which participates in manual trimming will call this method (via
* ConfigFeatureFlagTaggedTrimmingTransitionFactory) with its set of requested flags. This set
* typically comes straight from the user via the transitive_configs attribute. For feature
* flags themselves, this will be a singleton set containing the feature flag's own label.
*
* <p>At the top level, or when there is also a transition which calls replaceFlagValues (e.g.,
* ConfigFeatureFlagValuesTransition, created by ConfigFeatureFlagTransitionFactory and used by
* android_binary among others), the configuration will start off untrimmed (knownDefaultFlags is
* null). In this case:
*
* <ul>
* <li>Any map entries from flagValues whose keys are in requiredFlags will be retained in
* flagValues; all other entries of flagValues will be discarded.</li>
* <li>All other elements of requiredFlags will be put into knownDefaultFlags.</li>
* <li>unknownFlags will always be set to the empty set; its old value will be discarded.</li>
* </ul>
*
* <p>At any place other than the top level and the aforementioned replaceFlagValues transitions,
* the source configuration is already trimmed (knownDefaultFlags is not null). In this case:
*
* <ul>
* <li>Any map entries from flagValues which have keys that are in requiredFlags will be
* retained in flagValues; all other entries of flagValues will be discarded.</li>
* <li>Any elements of knownDefaultFlags which are also in requiredFlags will be retained in
* knownDefaultFlags; all other elements of knownDefaultFlags will be discarded.</li>
* <li>unknownFlags will be set to contain all other elements of requiredFlags; its old value
* will be discarded.</li>
* </ul>
*
* <p>If requiredFlags is empty, then flagValues, knownDefaultFlags, and unknownFlags will all be
* set to empty values.
*
* <p>After this method is called, regardless of circumstances:
*
* <ul>
* <li>knownDefaultValues will be non-null, and thus isTrimmed will return true, indicating that
* the configuration is trimmed.</li>
* <li>If unknownFlags is set non-empty, this indicates that the target this configuration is
* for has been reached via a path which mistakenly trimmed out one or more of the flags it
* needs, and thus there isn't enough information to evaluate it.</li>
* </ul>
*/
public void trimFlagValues(Set<Label> requiredFlags) {
ImmutableSortedMap.Builder<Label, String> flagValuesBuilder =
ImmutableSortedMap.naturalOrder();
ImmutableSortedSet.Builder<Label> knownDefaultFlagsBuilder = ImmutableSortedSet.naturalOrder();
ImmutableSortedSet.Builder<Label> unknownFlagsBuilder = ImmutableSortedSet.naturalOrder();
for (Label label : requiredFlags) {
if (this.flagValues.containsKey(label)) {
flagValuesBuilder.put(label, flagValues.get(label));
} else if (!this.isTrimmed() || this.knownDefaultFlags.contains(label)) {
knownDefaultFlagsBuilder.add(label);
} else {
unknownFlagsBuilder.add(label);
}
}
this.flagValues = flagValuesBuilder.build();
this.knownDefaultFlags = knownDefaultFlagsBuilder.build();
this.unknownFlags = unknownFlagsBuilder.build();
}
}