// 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.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.devtools.build.lib.actions.ArtifactOwner; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment; import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; import com.google.devtools.build.lib.analysis.config.FragmentOptions; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.common.options.Converter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsParser.OptionUsageRestrictions; import java.util.Map; import java.util.SortedMap; import javax.annotation.Nullable; /** * Configuration fragment for Android's config_feature_flag, flags which can be defined in BUILD * files. */ public final class ConfigFeatureFlagConfiguration extends BuildConfiguration.Fragment { /** 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)"; } } /** The options fragment which defines {@link ConfigFeatureFlagConfiguration}. */ public static final class Options extends FragmentOptions { /** The mapping from config_feature_flag rules to their values. */ @Option( name = "config_feature_flag values (private)", optionUsageRestrictions = OptionUsageRestrictions.INTERNAL, converter = EmptyImmutableSortedMapConverter.class, defaultValue = "{}" ) public ImmutableSortedMap<Label, String> flagValues = ImmutableSortedMap.of(); /** Retrieves the set of flag-value pairs. */ public ImmutableSortedMap<Label, String> getFlagValues() { return this.flagValues; } /** * 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. */ public void replaceFlagValues(Map<Label, String> flagValues) { this.flagValues = ImmutableSortedMap.copyOf(flagValues); } } /** * A configuration fragment loader able to create instances of {@link * ConfigFeatureFlagConfiguration} from {@link ConfigFeatureFlagConfiguration.Options}. */ public static final class Loader implements ConfigurationFragmentFactory { @Override public BuildConfiguration.Fragment create( ConfigurationEnvironment env, BuildOptions buildOptions) { return new ConfigFeatureFlagConfiguration(buildOptions.get(Options.class)); } @Override public Class<? extends BuildConfiguration.Fragment> creates() { return ConfigFeatureFlagConfiguration.class; } @Override public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() { return ImmutableSet.<Class<? extends FragmentOptions>>of(Options.class); } } private final ImmutableSortedMap<Label, String> flagValues; @Nullable private final String flagHash; /** Creates a new configuration fragment from the given {@link Options} fragment. */ public ConfigFeatureFlagConfiguration(Options options) { this.flagValues = options.getFlagValues(); this.flagHash = this.flagValues.isEmpty() ? null : hashFlags(this.flagValues); } /** Converts the given flag values into a string hash for use as an output directory fragment. */ private static String hashFlags(SortedMap<Label, String> flagValues) { // This hash function is relatively fast and stable between JVM invocations. Hasher hasher = Hashing.murmur3_128().newHasher(); for (Map.Entry<Label, String> flag : flagValues.entrySet()) { hasher.putUnencodedChars(flag.getKey().toString()); hasher.putByte((byte) 0); hasher.putUnencodedChars(flag.getValue()); hasher.putByte((byte) 0); } return hasher.hash().toString(); } /** * Retrieves the value of a configuration flag. * * <p>If the flag is not set in the current configuration, then the returned value will be absent. * * <p>This method should only be used by the rule whose label is passed here. Other rules should * depend on that rule and read a provider exported by it. To encourage callers of this method to * do the right thing, this class takes {@link ArtifactOwner} instead of {@link Label}; to get the * ArtifactOwner for a rule, call {@code ruleContext.getOwner()}. */ public Optional<String> getFeatureFlagValue(ArtifactOwner owner) { return Optional.fromNullable(flagValues.get(owner.getLabel())); } /** * Returns a fragment of the output directory name for this configuration, based on the set of * flags and their values. It will be {@code null} if no flags are set. */ @Nullable @Override public String getOutputDirectoryName() { return flagHash; } }