| // 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.runtime; |
| |
| import static com.google.common.base.Strings.isNullOrEmpty; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.common.options.Converters; |
| import com.google.devtools.common.options.OptionsParsingException; |
| import java.util.function.DoubleBinaryOperator; |
| import java.util.function.Supplier; |
| import org.apache.commons.lang.math.NumberUtils; |
| |
| /** |
| * Converter for options that accept a value, or one of the keywords in the validKeywords map, |
| * followed by an optional operator in the form [-|*]<float>. If a keyword is passed, the converter |
| * returns the keyword's value in the validKeywords map, scaled by the operation that follows if |
| * there is one. All values, explicit and derived, are adjusted for validity. A map of valid |
| * keywords, and the functions they correspond to, is passed to the constructor. |
| */ |
| public abstract class ResourceConverter extends Converters.IntegerConverter { |
| |
| private static final ImmutableMap<String, DoubleBinaryOperator> OPERATORS = |
| ImmutableMap.<String, DoubleBinaryOperator>builder() |
| .put("-", (l, r) -> l - r) |
| .put("*", (l, r) -> l * r) |
| .build(); |
| |
| private final ImmutableMap<String, Supplier<Integer>> validKeywords; |
| |
| private final String flagSyntax; |
| |
| /** |
| * Creates a converter whose behavior is defined by the provided map of keywords to functions. A |
| * supplier is used so that the converter responds correctly if host resources are configured |
| * after it is constructed. |
| * |
| * @param keywords a map of keywords to suppliers of their values. Keywords must not be starting |
| * substrings of each other and are case sensitive. A typical keyword set would contain "auto" |
| * linked to a function that calculates a reasonable flag value based on host resources, and a |
| * keyword containing "HOST", defining the host's capacity in terms of threads, CPUs, RAM, |
| * etc. Valid functions return an integer. |
| */ |
| protected ResourceConverter(ImmutableMap<String, Supplier<Integer>> keywords) |
| throws OptionsParsingException { |
| |
| for (String keyword : keywords.keySet()) { |
| if (keyword.matches("(" + String.join("|", keywords.keySet()) + ").+")) { |
| throw new OptionsParsingException( |
| String.format( |
| "Keywords (%s) must not be starting substrings of each other.", |
| String.join(",", keywords.keySet()))); |
| } |
| } |
| |
| validKeywords = keywords; |
| flagSyntax = |
| "[" |
| + String.join("|", validKeywords.keySet()) |
| + "][" |
| + String.join("|", OPERATORS.keySet()) |
| + "]<float>"; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @param input A value or a member of validKeywords, optionally followed by [-|*]<float> |
| */ |
| @Override |
| public final Integer convert(String input) throws OptionsParsingException { |
| if (isNullOrEmpty(input)) { |
| return null; |
| } |
| if (NumberUtils.isNumber(input)) { |
| return adjustValue(super.convert(input)); |
| } |
| for (String keyword : validKeywords.keySet()) { |
| if (input.startsWith(keyword)) { |
| int resourceValue = applyOperator(input.replace(keyword, ""), validKeywords.get(keyword)); |
| return adjustValue(resourceValue); |
| } |
| } |
| // Not numeric and not valid parameter format. |
| throw new OptionsParsingException( |
| String.format( |
| "Parameter '%s' does not follow correct syntax. This flag takes %s.", |
| input, flagSyntax)); |
| } |
| |
| /** |
| * Applies function designated in functionString ([-|*]<float>) to value. Empty functionString |
| * returns value (eg. for "auto" or unscaled host value input). |
| */ |
| private Integer applyOperator(String functionString, Supplier<Integer> firstOperandSupplier) |
| throws OptionsParsingException { |
| if (isNullOrEmpty(functionString)) { |
| return firstOperandSupplier.get(); |
| } |
| for (String opString : OPERATORS.keySet()) { |
| if (functionString.startsWith(opString)) { |
| float adjustBy; |
| try { |
| adjustBy = Float.parseFloat(functionString.substring(opString.length())); |
| } catch (NumberFormatException e) { |
| throw new OptionsParsingException( |
| String.format("'%s is not a float", functionString.substring(opString.length())), e); |
| } |
| return (int) |
| Math.round( |
| OPERATORS |
| .get(opString) |
| .applyAsDouble((float) firstOperandSupplier.get(), adjustBy)); |
| } |
| } |
| throw new OptionsParsingException( |
| String.format("Parameter value '%s' does not contain a valid operator.", functionString)); |
| } |
| |
| /** |
| * Checks validity of all values, both calculated and explicitly defined, based on test condition |
| * or host capacity. Fixes value or throws an error. Default return original value. |
| */ |
| protected Integer adjustValue(int value) throws OptionsParsingException { |
| return value; |
| } |
| |
| @Override |
| public String getTypeDescription() { |
| return "\"auto\" or \"" + flagSyntax + "\" or " + super.getTypeDescription(); |
| } |
| } |