Skip to content

Commit

Permalink
Add the additional options to support manual trimming of feature flags.
Browse files Browse the repository at this point in the history
There are effectively three different states a flag's value could be in:
1. Value is known to be non-default
2. Value is known to be default
3. Value is unknown (has been trimmed)

In addition to flagValues (which covers the first state), there are now
two additional sets covering the other two states. Neither of these sets
are used when manual trimming is disabled or when the entire set of flags
is known, in which case state 1 is represented by labels in the map,
state 2 is represented by labels not in the map, and state 3 doesn't exist.

This also adds the flag which controls whether manual trimming is active,
but it currently has no effect.

RELNOTES: None.
PiperOrigin-RevId: 193964624
  • Loading branch information
mstaib authored and Copybara-Service committed Apr 23, 2018
1 parent 981f65b commit a7c6786
Show file tree
Hide file tree
Showing 2 changed files with 515 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;
Expand All @@ -24,6 +25,8 @@
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)
Expand All @@ -42,30 +45,236 @@ public String getTypeDescription() {
}
}

/** 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.UNKNOWN},
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();

/** Retrieves the set of flag-value pairs. */
/**
* 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.
* 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();
}
}

0 comments on commit a7c6786

Please sign in to comment.