com.google.devtools.build.lib.analysis.config.ConfigSetting.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.analysis.config.ConfigSetting.java

Source

// Copyright 2014 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.config;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.LicensesProviderImpl;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implementation for the config_setting rule.
 *
 * <p>This is a "pseudo-rule" in that its purpose isn't to generate output artifacts
 * from input artifacts. Rather, it provides configuration context to rules that
 * depend on it.
 */
public class ConfigSetting implements RuleConfiguredTargetFactory {

    @Override
    public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException, RuleErrorException {
        // Get the required flag=value settings for this rule.
        Map<String, String> settings = NonconfigurableAttributeMapper.of(ruleContext.getRule())
                .get(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE, Type.STRING_DICT);
        if (settings.isEmpty()) {
            ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE,
                    "no settings specified");
            return null;
        }

        ConfigMatchingProvider configMatcher;
        try {
            configMatcher = new ConfigMatchingProvider(ruleContext.getLabel(), settings,
                    matchesConfig(settings, ruleContext.getConfiguration()));
        } catch (OptionsParsingException e) {
            ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE,
                    "error while parsing configuration settings: " + e.getMessage());
            return null;
        }

        return new RuleConfiguredTargetBuilder(ruleContext)
                .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
                .addProvider(FileProvider.class, FileProvider.EMPTY)
                .addProvider(FilesToRunProvider.class, FilesToRunProvider.EMPTY)
                .addProvider(LicensesProviderImpl.EMPTY).addProvider(ConfigMatchingProvider.class, configMatcher)
                .build();
    }

    /**
     * Given a list of [flagName, flagValue] pairs, returns true if flagName == flagValue for
     * every item in the list under this configuration, false otherwise.
     */
    private boolean matchesConfig(Map<String, String> expectedSettings, BuildConfiguration config)
            throws OptionsParsingException {
        // Rather than returning fast when we find a mismatch, continue looking at the other flags
        // to check that they're indeed valid flag specifications.
        boolean foundMismatch = false;

        // Since OptionsParser instantiation involves reflection, let's try to minimize that happening.
        Map<Class<? extends OptionsBase>, OptionsParser> parserCache = new HashMap<>();

        for (Map.Entry<String, String> setting : expectedSettings.entrySet()) {
            String optionName = setting.getKey();
            String expectedRawValue = setting.getValue();

            Class<? extends OptionsBase> optionClass = config.getOptionClass(optionName);
            if (optionClass == null) {
                throw new OptionsParsingException("unknown option: '" + optionName + "'");
            }

            OptionsParser parser = parserCache.get(optionClass);
            if (parser == null) {
                parser = OptionsParser.newOptionsParser(optionClass);
                parserCache.put(optionClass, parser);
            }
            parser.parse("--" + optionName + "=" + expectedRawValue);
            Object expectedParsedValue = parser.getOptions(optionClass).asMap().get(optionName);

            if (!optionMatches(config, optionName, expectedParsedValue)) {
                foundMismatch = true;
            }
        }
        return !foundMismatch;
    }

    /**
     * For single-value options, returns true iff the option's value matches the expected value.
     *
     * <p>For multi-value List options, returns true iff any of the option's values matches
     * the expected value. This means, e.g. "--tool_tag=foo --tool_tag=bar" would match the
     * expected condition { 'tool_tag': 'bar' }.
     *
     * <p>For multi-value Map options, returns true iff the last instance with the same key as the
     * expected key has the same value. This means, e.g. "--define foo=1 --define bar=2" would
     * match { 'define': 'foo=1' }, but "--define foo=1 --define bar=2 --define foo=3" would not
     * match. Note that the definition of --define states that the last instance takes precedence.
     */
    private static boolean optionMatches(BuildConfiguration config, String optionName, Object expectedValue) {
        Object actualValue = config.getOptionValue(optionName);
        if (actualValue == null) {
            return expectedValue == null;

            // Single-value case:
        } else if (!config.allowsMultipleValues(optionName)) {
            return actualValue.equals(expectedValue);
        }

        // Multi-value case:
        Preconditions.checkState(actualValue instanceof List);
        Preconditions.checkState(expectedValue instanceof List);
        List<?> actualList = (List<?>) actualValue;
        List<?> expectedList = (List<?>) expectedValue;

        if (actualList.isEmpty() || expectedList.isEmpty()) {
            return actualList.isEmpty() && expectedList.isEmpty();
        }

        // We're expecting a single value of a multi-value type: the options parser still embeds
        // that single value within a List container. Retrieve it here.
        Object expectedSingleValue = Iterables.getOnlyElement(expectedList);

        // Multi-value map:
        if (actualList.get(0) instanceof Map.Entry) {
            Map.Entry<?, ?> expectedEntry = (Map.Entry<?, ?>) expectedSingleValue;
            for (Map.Entry<?, ?> actualEntry : Lists.reverse((List<Map.Entry<?, ?>>) actualList)) {
                if (actualEntry.getKey().equals(expectedEntry.getKey())) {
                    // Found a key match!
                    return actualEntry.getValue().equals(expectedEntry.getValue());
                }
            }
            return false; // Never found any matching key.
        }

        // Multi-value list:
        return actualList.contains(expectedSingleValue);
    }
}