434 lines
17 KiB
Java
434 lines
17 KiB
Java
|
// 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.common.options;
|
||
|
|
||
|
import com.google.common.base.Preconditions;
|
||
|
import com.google.common.collect.ArrayListMultimap;
|
||
|
import com.google.common.collect.ImmutableList;
|
||
|
import com.google.common.collect.ListMultimap;
|
||
|
import com.google.devtools.common.options.OptionsParser.ConstructionException;
|
||
|
import java.util.Collection;
|
||
|
import java.util.Comparator;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
import java.util.stream.Collectors;
|
||
|
import javax.annotation.Nullable;
|
||
|
|
||
|
/**
|
||
|
* The value of an option.
|
||
|
*
|
||
|
* <p>This takes care of tracking the final value as multiple instances of an option are parsed.
|
||
|
*/
|
||
|
public abstract class OptionValueDescription {
|
||
|
|
||
|
protected final OptionDefinition optionDefinition;
|
||
|
|
||
|
public OptionValueDescription(OptionDefinition optionDefinition) {
|
||
|
this.optionDefinition = optionDefinition;
|
||
|
}
|
||
|
|
||
|
public OptionDefinition getOptionDefinition() {
|
||
|
return optionDefinition;
|
||
|
}
|
||
|
|
||
|
/** Returns the current or final value of this option. */
|
||
|
public abstract Object getValue();
|
||
|
|
||
|
/** Returns the source(s) of this option, if there were multiple, duplicates are removed. */
|
||
|
public abstract String getSourceString();
|
||
|
|
||
|
/**
|
||
|
* Add an instance of the option to this value. The various types of options are in charge of
|
||
|
* making sure that the value is correctly stored, with proper tracking of its priority and
|
||
|
* placement amongst other options.
|
||
|
*
|
||
|
* @return a bundle containing arguments that need to be parsed further.
|
||
|
*/
|
||
|
abstract ExpansionBundle addOptionInstance(
|
||
|
ParsedOptionDescription parsedOption, List<String> warnings) throws OptionsParsingException;
|
||
|
|
||
|
/**
|
||
|
* Grouping of convenience for the options that expand to other options, to attach an
|
||
|
* option-appropriate source string along with the options that need to be parsed.
|
||
|
*/
|
||
|
public static class ExpansionBundle {
|
||
|
List<String> expansionArgs;
|
||
|
String sourceOfExpansionArgs;
|
||
|
|
||
|
public ExpansionBundle(List<String> args, String source) {
|
||
|
expansionArgs = args;
|
||
|
sourceOfExpansionArgs = source;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the canonical instances of this option - the instances that affect the current value.
|
||
|
*
|
||
|
* <p>For options that do not have values in their own right, this should be the empty list. In
|
||
|
* contrast, the DefaultOptionValue does not have a canonical form at all, since it was never set,
|
||
|
* and is null.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public abstract List<ParsedOptionDescription> getCanonicalInstances();
|
||
|
|
||
|
/**
|
||
|
* For the given option, returns the correct type of OptionValueDescription, to which unparsed
|
||
|
* values can be added.
|
||
|
*
|
||
|
* <p>The categories of option types are non-overlapping, an invariant checked by the
|
||
|
* OptionProcessor at compile time.
|
||
|
*/
|
||
|
public static OptionValueDescription createOptionValueDescription(
|
||
|
OptionDefinition option, OptionsData optionsData) {
|
||
|
if (option.isExpansionOption()) {
|
||
|
return new ExpansionOptionValueDescription(option, optionsData);
|
||
|
} else if (option.allowsMultiple()) {
|
||
|
return new RepeatableOptionValueDescription(option);
|
||
|
} else if (option.hasImplicitRequirements()) {
|
||
|
return new OptionWithImplicitRequirementsValueDescription(option);
|
||
|
} else {
|
||
|
return new SingleOptionValueDescription(option);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* For options that have not been set, this will return a correct OptionValueDescription for the
|
||
|
* default value.
|
||
|
*/
|
||
|
public static OptionValueDescription getDefaultOptionValue(OptionDefinition option) {
|
||
|
return new DefaultOptionValueDescription(option);
|
||
|
}
|
||
|
|
||
|
private static class DefaultOptionValueDescription extends OptionValueDescription {
|
||
|
|
||
|
private DefaultOptionValueDescription(OptionDefinition optionDefinition) {
|
||
|
super(optionDefinition);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Object getValue() {
|
||
|
return optionDefinition.getDefaultValue();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getSourceString() {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) {
|
||
|
throw new IllegalStateException(
|
||
|
"Cannot add values to the default option value. Create a modifiable "
|
||
|
+ "OptionValueDescription using createOptionValueDescription() instead.");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The form of a value for a default type of flag, one that does not accumulate multiple values
|
||
|
* and has no expansion.
|
||
|
*/
|
||
|
private static class SingleOptionValueDescription extends OptionValueDescription {
|
||
|
private ParsedOptionDescription effectiveOptionInstance;
|
||
|
private Object effectiveValue;
|
||
|
|
||
|
private SingleOptionValueDescription(OptionDefinition optionDefinition) {
|
||
|
super(optionDefinition);
|
||
|
if (optionDefinition.allowsMultiple()) {
|
||
|
throw new ConstructionException("Can't have a single value for an allowMultiple option.");
|
||
|
}
|
||
|
if (optionDefinition.isExpansionOption()) {
|
||
|
throw new ConstructionException("Can't have a single value for an expansion option.");
|
||
|
}
|
||
|
effectiveOptionInstance = null;
|
||
|
effectiveValue = null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Object getValue() {
|
||
|
return effectiveValue;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getSourceString() {
|
||
|
return effectiveOptionInstance.getSource();
|
||
|
}
|
||
|
|
||
|
// Warnings should not end with a '.' because the internal reporter adds one automatically.
|
||
|
@Override
|
||
|
ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)
|
||
|
throws OptionsParsingException {
|
||
|
// This might be the first value, in that case, just store it!
|
||
|
if (effectiveOptionInstance == null) {
|
||
|
effectiveOptionInstance = parsedOption;
|
||
|
effectiveValue = effectiveOptionInstance.getConvertedValue();
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// If there was another value, check whether the new one will override it, and if so,
|
||
|
// log warnings describing the change.
|
||
|
if (parsedOption.getPriority().compareTo(effectiveOptionInstance.getPriority()) >= 0) {
|
||
|
// Identify the option that might have led to the current and new value of this option.
|
||
|
ParsedOptionDescription implicitDependent = parsedOption.getImplicitDependent();
|
||
|
ParsedOptionDescription expandedFrom = parsedOption.getExpandedFrom();
|
||
|
ParsedOptionDescription optionThatDependsOnEffectiveValue =
|
||
|
effectiveOptionInstance.getImplicitDependent();
|
||
|
ParsedOptionDescription optionThatExpandedToEffectiveValue =
|
||
|
effectiveOptionInstance.getExpandedFrom();
|
||
|
|
||
|
Object newValue = parsedOption.getConvertedValue();
|
||
|
// Output warnings if there is conflicting options set different values in a way that might
|
||
|
// not have been obvious to the user, such as through expansions and implicit requirements.
|
||
|
if (!effectiveValue.equals(newValue)) {
|
||
|
boolean samePriorityCategory =
|
||
|
parsedOption
|
||
|
.getPriority()
|
||
|
.getPriorityCategory()
|
||
|
.equals(effectiveOptionInstance.getPriority().getPriorityCategory());
|
||
|
if ((implicitDependent != null) && (optionThatDependsOnEffectiveValue != null)) {
|
||
|
if (!implicitDependent.equals(optionThatDependsOnEffectiveValue)) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s is implicitly defined by both %s and %s",
|
||
|
optionDefinition, optionThatDependsOnEffectiveValue, implicitDependent));
|
||
|
}
|
||
|
} else if ((implicitDependent != null) && samePriorityCategory) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s is implicitly defined by %s; the implicitly set value "
|
||
|
+ "overrides the previous one",
|
||
|
optionDefinition, implicitDependent));
|
||
|
} else if (optionThatDependsOnEffectiveValue != null) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"A new value for %s overrides a previous implicit setting of that "
|
||
|
+ "option by %s",
|
||
|
optionDefinition, optionThatDependsOnEffectiveValue));
|
||
|
} else if (samePriorityCategory
|
||
|
&& ((optionThatExpandedToEffectiveValue == null) && (expandedFrom != null))) {
|
||
|
// Create a warning if an expansion option overrides an explicit option:
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s was expanded and now overrides the explicit option %s with %s",
|
||
|
expandedFrom,
|
||
|
effectiveOptionInstance.getCommandLineForm(),
|
||
|
parsedOption.getCommandLineForm()));
|
||
|
} else if ((optionThatExpandedToEffectiveValue != null) && (expandedFrom != null)) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s was expanded to from both %s and %s",
|
||
|
optionDefinition, optionThatExpandedToEffectiveValue, expandedFrom));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Record the new value:
|
||
|
effectiveOptionInstance = parsedOption;
|
||
|
effectiveValue = newValue;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
|
||
|
// If the current option is an implicit requirement, we don't need to list this value since
|
||
|
// the parent implies it. In this case, it is sufficient to not list this value at all.
|
||
|
if (effectiveOptionInstance.getImplicitDependent() == null) {
|
||
|
return ImmutableList.of(effectiveOptionInstance);
|
||
|
}
|
||
|
return ImmutableList.of();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** The form of a value for an option that accumulates multiple values on the command line. */
|
||
|
private static class RepeatableOptionValueDescription extends OptionValueDescription {
|
||
|
ListMultimap<OptionPriority, ParsedOptionDescription> parsedOptions;
|
||
|
ListMultimap<OptionPriority, Object> optionValues;
|
||
|
|
||
|
private RepeatableOptionValueDescription(OptionDefinition optionDefinition) {
|
||
|
super(optionDefinition);
|
||
|
if (!optionDefinition.allowsMultiple()) {
|
||
|
throw new ConstructionException(
|
||
|
"Can't have a repeated value for a non-allowMultiple option.");
|
||
|
}
|
||
|
parsedOptions = ArrayListMultimap.create();
|
||
|
optionValues = ArrayListMultimap.create();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getSourceString() {
|
||
|
return parsedOptions
|
||
|
.asMap()
|
||
|
.entrySet()
|
||
|
.stream()
|
||
|
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||
|
.map(Map.Entry::getValue)
|
||
|
.flatMap(Collection::stream)
|
||
|
.map(ParsedOptionDescription::getSource)
|
||
|
.distinct()
|
||
|
.collect(Collectors.joining(", "));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public List<Object> getValue() {
|
||
|
// Sort the results by option priority and return them in a new list. The generic type of
|
||
|
// the list is not known at runtime, so we can't use it here.
|
||
|
return optionValues
|
||
|
.asMap()
|
||
|
.entrySet()
|
||
|
.stream()
|
||
|
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||
|
.map(Map.Entry::getValue)
|
||
|
.flatMap(Collection::stream)
|
||
|
.collect(Collectors.toList());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)
|
||
|
throws OptionsParsingException {
|
||
|
// For repeatable options, we allow flags that take both single values and multiple values,
|
||
|
// potentially collapsing them down.
|
||
|
Object convertedValue = parsedOption.getConvertedValue();
|
||
|
OptionPriority priority = parsedOption.getPriority();
|
||
|
parsedOptions.put(priority, parsedOption);
|
||
|
if (convertedValue instanceof List<?>) {
|
||
|
optionValues.putAll(priority, (List<?>) convertedValue);
|
||
|
} else {
|
||
|
optionValues.put(priority, convertedValue);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
|
||
|
return parsedOptions
|
||
|
.asMap()
|
||
|
.entrySet()
|
||
|
.stream()
|
||
|
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||
|
.map(Map.Entry::getValue)
|
||
|
.flatMap(Collection::stream)
|
||
|
// Only provide the options that aren't implied elsewhere.
|
||
|
.filter(optionDesc -> optionDesc.getImplicitDependent() == null)
|
||
|
.collect(ImmutableList.toImmutableList());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The form of a value for an expansion option, one that does not have its own value but expands
|
||
|
* in place to other options. This should be used for both flags with a static expansion defined
|
||
|
* in {@link Option#expansion()} and flags with an {@link Option#expansionFunction()}.
|
||
|
*/
|
||
|
private static class ExpansionOptionValueDescription extends OptionValueDescription {
|
||
|
private final List<String> expansion;
|
||
|
|
||
|
private ExpansionOptionValueDescription(
|
||
|
OptionDefinition optionDefinition, OptionsData optionsData) {
|
||
|
super(optionDefinition);
|
||
|
this.expansion = optionsData.getEvaluatedExpansion(optionDefinition);
|
||
|
if (!optionDefinition.isExpansionOption()) {
|
||
|
throw new ConstructionException(
|
||
|
"Options without expansions can't be tracked using ExpansionOptionValueDescription");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Object getValue() {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getSourceString() {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) {
|
||
|
if (parsedOption.getUnconvertedValue() != null
|
||
|
&& !parsedOption.getUnconvertedValue().isEmpty()) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s is an expansion option. It does not accept values, and does not change its "
|
||
|
+ "expansion based on the value provided. Value '%s' will be ignored.",
|
||
|
optionDefinition, parsedOption.getUnconvertedValue()));
|
||
|
}
|
||
|
|
||
|
return new ExpansionBundle(
|
||
|
expansion,
|
||
|
(parsedOption.getSource() == null)
|
||
|
? String.format("expanded from %s", optionDefinition)
|
||
|
: String.format(
|
||
|
"expanded from %s (source %s)", optionDefinition, parsedOption.getSource()));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
|
||
|
// The options this expands to are incorporated in their own right - this option does
|
||
|
// not have a canonical form.
|
||
|
return ImmutableList.of();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** The form of a value for a flag with implicit requirements. */
|
||
|
private static class OptionWithImplicitRequirementsValueDescription
|
||
|
extends SingleOptionValueDescription {
|
||
|
|
||
|
private OptionWithImplicitRequirementsValueDescription(OptionDefinition optionDefinition) {
|
||
|
super(optionDefinition);
|
||
|
if (!optionDefinition.hasImplicitRequirements()) {
|
||
|
throw new ConstructionException(
|
||
|
"Options without implicit requirements can't be tracked using "
|
||
|
+ "OptionWithImplicitRequirementsValueDescription");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)
|
||
|
throws OptionsParsingException {
|
||
|
// This is a valued flag, its value is handled the same way as a normal
|
||
|
// SingleOptionValueDescription. (We check at compile time that these flags aren't
|
||
|
// "allowMultiple")
|
||
|
ExpansionBundle superExpansion = super.addOptionInstance(parsedOption, warnings);
|
||
|
Preconditions.checkArgument(
|
||
|
superExpansion == null, "SingleOptionValueDescription should not expand to anything.");
|
||
|
if (parsedOption.getConvertedValue().equals(optionDefinition.getDefaultValue())) {
|
||
|
warnings.add(
|
||
|
String.format(
|
||
|
"%s sets %s to its default value. Since this option has implicit requirements that "
|
||
|
+ "are set whenever the option is explicitly provided, regardless of the "
|
||
|
+ "value, this will behave differently than letting a default be a default. "
|
||
|
+ "Specifically, this options expands to {%s}.",
|
||
|
parsedOption.getCommandLineForm(),
|
||
|
optionDefinition,
|
||
|
String.join(" ", optionDefinition.getImplicitRequirements())));
|
||
|
}
|
||
|
|
||
|
// Now deal with the implicit requirements.
|
||
|
return new ExpansionBundle(
|
||
|
ImmutableList.copyOf(optionDefinition.getImplicitRequirements()),
|
||
|
(parsedOption.getSource() == null)
|
||
|
? String.format("implicit requirement of %s", optionDefinition)
|
||
|
: String.format(
|
||
|
"implicit requirement of %s (source %s)",
|
||
|
optionDefinition, parsedOption.getSource()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|