Allow expansion flags to have values.
This lets us change what it expands based on the argument passed to the flag.
RELNOTES: Allows flags that expand to take values.
PiperOrigin-RevId: 160298412
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansion.java b/src/main/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansion.java
index 3f7a193..9d28331 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansion.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansion.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.ExpansionContext;
import com.google.devtools.common.options.ExpansionFunction;
import com.google.devtools.common.options.IsolatedOptionsData;
import com.google.devtools.common.options.Option;
@@ -153,11 +154,11 @@
}
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
// Grab all registered options that are identified as incompatible changes by either name or
// by category. Ensure they satisfy our requirements.
ArrayList<String> incompatibleChanges = new ArrayList<>();
- for (Map.Entry<String, Field> entry : optionsData.getAllNamedFields()) {
+ for (Map.Entry<String, Field> entry : context.getOptionsData().getAllNamedFields()) {
Field field = entry.getValue();
Option annotation = field.getAnnotation(Option.class);
if (annotation.name().startsWith(INCOMPATIBLE_NAME_PREFIX)
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
index 18455b1..53540e6 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
@@ -165,8 +165,8 @@
// Print out the canonical invocation policy if requested.
if (canonicalizeOptions.canonicalizePolicy) {
- List<FlagPolicy> effectiveFlagPolicies =
- InvocationPolicyEnforcer.getEffectivePolicy(policy, parser, commandName);
+ ImmutableList<FlagPolicy> effectiveFlagPolicies =
+ InvocationPolicyEnforcer.getEffectivePolicies(policy, parser, commandName);
InvocationPolicy effectivePolicy =
InvocationPolicy.newBuilder().addAllFlagPolicies(effectiveFlagPolicies).build();
env.getReporter().getOutErr().printOutLn(effectivePolicy.toString());
diff --git a/src/main/java/com/google/devtools/common/options/ExpansionContext.java b/src/main/java/com/google/devtools/common/options/ExpansionContext.java
new file mode 100644
index 0000000..74dac97
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/ExpansionContext.java
@@ -0,0 +1,54 @@
+// 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 java.lang.reflect.Field;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Encapsulates the data given to {@link ExpansionFunction} objects. This lets {@link
+ * ExpansionFunction} objects change how it expands flags based on the arguments given to the {@link
+ * OptionsParser}.
+ */
+@ThreadSafe
+public final class ExpansionContext {
+ private final IsolatedOptionsData optionsData;
+ private final Field field;
+ @Nullable private final String unparsedValue;
+
+ public ExpansionContext(
+ IsolatedOptionsData optionsData, Field field, @Nullable String unparsedValue) {
+ this.optionsData = optionsData;
+ this.field = field;
+ this.unparsedValue = unparsedValue;
+ }
+
+ /** Metadata for the option that is being expanded. */
+ public IsolatedOptionsData getOptionsData() {
+ return optionsData;
+ }
+
+ /** {@link Field} object for option that is being expanded. */
+ public Field getField() {
+ return field;
+ }
+
+ /** Argument given to this flag during options parsing. Will be null if no argument was given. */
+ @Nullable
+ public String getUnparsedValue() {
+ return unparsedValue;
+ }
+}
diff --git a/src/main/java/com/google/devtools/common/options/ExpansionFunction.java b/src/main/java/com/google/devtools/common/options/ExpansionFunction.java
index be4773e..1031125 100644
--- a/src/main/java/com/google/devtools/common/options/ExpansionFunction.java
+++ b/src/main/java/com/google/devtools/common/options/ExpansionFunction.java
@@ -27,7 +27,7 @@
*
* @param optionsData the parser's indexed information about its own options, before expansion
* information is computed
- * @return An expansion to use for all occurrences of this option in this parser
+ * @return An expansion to use on an empty list
*/
- ImmutableList<String> getExpansion(IsolatedOptionsData optionsData);
+ ImmutableList<String> getExpansion(ExpansionContext context) throws OptionsParsingException;
}
diff --git a/src/main/java/com/google/devtools/common/options/ExpansionNeedsValueException.java b/src/main/java/com/google/devtools/common/options/ExpansionNeedsValueException.java
new file mode 100644
index 0000000..d63b988
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/ExpansionNeedsValueException.java
@@ -0,0 +1,25 @@
+// 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;
+
+/**
+ * Exception specific to evaluating {@link ExpansionFunction} objects. Used when expansion isn't
+ * possible because of a missing input.
+ */
+public final class ExpansionNeedsValueException extends OptionsParsingException {
+
+ public ExpansionNeedsValueException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java b/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
index 651cd78..97be190 100644
--- a/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
+++ b/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
@@ -99,9 +99,9 @@
// The effective policy returned is expanded, filtered for applicable commands, and cleaned of
// redundancies and conflicts.
- List<FlagPolicy> effectivePolicy = getEffectivePolicy(invocationPolicy, parser, command);
+ List<FlagPolicy> effectivePolicies = getEffectivePolicies(invocationPolicy, parser, command);
- for (FlagPolicy flagPolicy : effectivePolicy) {
+ for (FlagPolicy flagPolicy : effectivePolicies) {
String flagName = flagPolicy.getFlagName();
OptionValueDescription valueDescription;
@@ -190,7 +190,7 @@
*
* <p>Expands any policies on expansion flags.
*/
- public static List<FlagPolicy> getEffectivePolicy(
+ public static ImmutableList<FlagPolicy> getEffectivePolicies(
InvocationPolicy invocationPolicy, OptionsParser parser, String command)
throws OptionsParsingException {
if (invocationPolicy == null) {
@@ -220,7 +220,71 @@
effectivePolicy.put(flagName, expandedPolicy);
}
- return new ArrayList<>(effectivePolicy.values());
+ return ImmutableList.copyOf(effectivePolicy.values());
+ }
+
+ private static void throwAllowValuesOnExpansionFlagException(String flagName)
+ throws OptionsParsingException {
+ throw new OptionsParsingException(
+ String.format("Allow_Values on expansion flags like %s is not allowed.", flagName));
+ }
+
+ private static void throwDisallowValuesOnExpansionFlagException(String flagName)
+ throws OptionsParsingException {
+ throw new OptionsParsingException(
+ String.format("Disallow_Values on expansion flags like %s is not allowed.", flagName));
+ }
+
+ private static ImmutableList<OptionValueDescription> getExpansionsFromFlagPolicy(
+ FlagPolicy expansionPolicy, OptionDescription optionDescription, OptionsParser parser)
+ throws OptionsParsingException {
+ if (!optionDescription.isExpansion()) {
+ return ImmutableList.of();
+ }
+
+ String expansionFlagName = expansionPolicy.getFlagName();
+
+ ImmutableList.Builder<OptionValueDescription> resultsBuilder =
+ ImmutableList.<OptionValueDescription>builder();
+ switch (expansionPolicy.getOperationCase()) {
+ case SET_VALUE:
+ {
+ SetValue setValue = expansionPolicy.getSetValue();
+ if (setValue.getFlagValueCount() > 0) {
+ for (String value : setValue.getFlagValueList()) {
+ resultsBuilder.addAll(
+ parser.getExpansionOptionValueDescriptions(expansionFlagName, value));
+ }
+ } else {
+ resultsBuilder.addAll(
+ parser.getExpansionOptionValueDescriptions(expansionFlagName, null));
+ }
+ }
+ break;
+ case USE_DEFAULT:
+ resultsBuilder.addAll(parser.getExpansionOptionValueDescriptions(expansionFlagName, null));
+ break;
+ case ALLOW_VALUES:
+ // All expansions originally given to the parser have been expanded by now, so these two
+ // cases aren't necessary (the values given in the flag policy shouldn't need to be
+ // checked). If you care about blocking specific flag values you should block the behavior
+ // on the specific ones, not the expansion that contains them.
+ throwAllowValuesOnExpansionFlagException(expansionPolicy.getFlagName());
+ break;
+ case DISALLOW_VALUES:
+ throwDisallowValuesOnExpansionFlagException(expansionPolicy.getFlagName());
+ break;
+ case OPERATION_NOT_SET:
+ throw new PolicyOperationNotSetException(expansionPolicy.getFlagName());
+ default:
+ log.warning(
+ String.format(
+ "Unknown operation '%s' from invocation policy for flag '%s'",
+ expansionPolicy.getOperationCase(), expansionFlagName));
+ break;
+ }
+
+ return resultsBuilder.build();
}
/**
@@ -234,21 +298,24 @@
FlagPolicy originalPolicy,
OptionsParser parser)
throws OptionsParsingException {
- List<FlagPolicy> expandedPolicy = new ArrayList<>();
+ List<FlagPolicy> expandedPolicies = new ArrayList<>();
- OptionDescription originalDesc = parser.getOptionDescription(originalPolicy.getFlagName());
- if (originalDesc == null) {
+ OptionDescription originalOptionDescription =
+ parser.getOptionDescription(originalPolicy.getFlagName());
+ if (originalOptionDescription == null) {
// InvocationPolicy ignores policy on non-existing flags by design, for version compatibility.
- return expandedPolicy;
+ return expandedPolicies;
}
+ ImmutableList<OptionValueDescription> expansions =
+ getExpansionsFromFlagPolicy(originalPolicy, originalOptionDescription, parser);
ImmutableList.Builder<OptionValueDescription> subflagBuilder = new ImmutableList.Builder<>();
ImmutableList<OptionValueDescription> subflags =
subflagBuilder
- .addAll(originalDesc.getImplicitRequirements())
- .addAll(originalDesc.getExpansions())
+ .addAll(originalOptionDescription.getImplicitRequirements())
+ .addAll(expansions)
.build();
- boolean isExpansion = !originalDesc.getExpansions().isEmpty();
+ boolean isExpansion = originalOptionDescription.isExpansion();
if (!subflags.isEmpty() && log.isLoggable(Level.FINE)) {
// Log the expansion. Since this is logged regardless of user provided command line, it is
@@ -283,10 +350,10 @@
&& originalPolicy.getOperationCase().equals(OperationCase.SET_VALUE)) {
repeatableSubflagsInSetValues.put(currentSubflag.getName(), currentSubflag);
} else {
- FlagPolicy subflagAsPolicy = getSubflagAsPolicy(
- currentSubflag, originalPolicy, originalDesc);
+ FlagPolicy subflagAsPolicy =
+ getSubflagAsPolicy(currentSubflag, originalPolicy, isExpansion);
// In case any of the expanded flags are themselves expansions, recurse.
- expandedPolicy.addAll(expandPolicy(subflagAsPolicy, parser));
+ expandedPolicies.addAll(expandPolicy(subflagAsPolicy, parser));
}
}
@@ -299,22 +366,18 @@
for (OptionValueDescription setValue : repeatableSubflagsInSetValues.get(repeatableFlag)) {
newValues.add(setValue.getOriginalValueString());
}
- expandedPolicy.add(
+ expandedPolicies.add(
getSetValueSubflagAsPolicy(
- repeatableFlag,
- newValues,
- /* allowMultiple */ true,
- originalPolicy));
-
+ repeatableFlag, newValues, /* allowMultiple */ true, originalPolicy));
}
// Don't add the original policy if it was an expansion flag, which have no value, but do add
// it if there was either no expansion or if it was a valued flag with implicit requirements.
if (!isExpansion) {
- expandedPolicy.add(originalPolicy);
+ expandedPolicies.add(originalPolicy);
}
- return expandedPolicy;
+ return expandedPolicies;
}
/**
@@ -365,19 +428,18 @@
* corresponding policy.
*/
private static FlagPolicy getSubflagAsPolicy(
- OptionValueDescription currentSubflag,
- FlagPolicy originalPolicy,
- OptionDescription originalDesc) throws OptionsParsingException {
- boolean isExpansion = !originalDesc.getExpansions().isEmpty();
+ OptionValueDescription currentSubflag, FlagPolicy originalPolicy, boolean isExpansion)
+ throws OptionsParsingException {
FlagPolicy subflagAsPolicy = null;
switch (originalPolicy.getOperationCase()) {
case SET_VALUE:
- assert(!currentSubflag.getAllowMultiple());
- subflagAsPolicy = getSetValueSubflagAsPolicy(
- currentSubflag.getName(),
- ImmutableList.of(currentSubflag.getOriginalValueString()),
- /* allowMultiple */ false,
- originalPolicy);
+ assert (!currentSubflag.getAllowMultiple());
+ subflagAsPolicy =
+ getSetValueSubflagAsPolicy(
+ currentSubflag.getName(),
+ ImmutableList.of(currentSubflag.getOriginalValueString()),
+ /*allowMultiple=*/ false,
+ originalPolicy);
break;
case USE_DEFAULT:
@@ -394,10 +456,7 @@
case ALLOW_VALUES:
if (isExpansion) {
- throw new OptionsParsingException(
- String.format(
- "Allow_Values on expansion flags like %s is not allowed.",
- originalPolicy.getFlagName()));
+ throwAllowValuesOnExpansionFlagException(originalPolicy.getFlagName());
}
// If this flag is an implicitRequirement, and some values for the parent flag are
// allowed, nothing needs to happen on the implicitRequirement that is set for all
@@ -406,10 +465,7 @@
case DISALLOW_VALUES:
if (isExpansion) {
- throw new OptionsParsingException(
- String.format(
- "Disallow_Values on expansion flags like %s is not allowed.",
- originalPolicy.getFlagName()));
+ throwDisallowValuesOnExpansionFlagException(originalPolicy.getFlagName());
}
// If this flag is an implicitRequirement, and some values for the parent flag are
// disallowed, that implies that all others are allowed, so nothing needs to happen
@@ -684,7 +740,7 @@
OptionDescription optionDescription,
Set<Object> convertedPolicyValues)
throws OptionsParsingException {
-
+
if (optionDescription.getAllowMultiple()) {
// allowMultiple requires that the type of the option be List<T>, so cast from Object
// to List<?>.
diff --git a/src/main/java/com/google/devtools/common/options/OptionsData.java b/src/main/java/com/google/devtools/common/options/OptionsData.java
index ba53172..48f47ff 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsData.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsData.java
@@ -14,14 +14,15 @@
package com.google.devtools.common.options;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
+import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
@@ -32,38 +33,100 @@
@Immutable
final class OptionsData extends IsolatedOptionsData {
- /** Mapping from each Option-annotated field to expansion strings, if it has any. */
- private final ImmutableMap<Field, ImmutableList<String>> evaluatedExpansions;
+ /**
+ * Keeps track of all the information needed to calculate expansion flags, whether they come from
+ * a static list or a @{link ExpansionFunction} object.
+ */
+ static class ExpansionData {
+ private final ImmutableList<String> staticExpansion;
+ @Nullable private final ExpansionFunction dynamicExpansions;
+
+ ExpansionData(ImmutableList<String> staticExpansion) {
+ Preconditions.checkArgument(staticExpansion != null);
+ this.staticExpansion = staticExpansion;
+ this.dynamicExpansions = null;
+ }
+
+ ExpansionData(ExpansionFunction dynamicExpansions) {
+ Preconditions.checkArgument(dynamicExpansions != null);
+ this.staticExpansion = EMPTY_EXPANSION;
+ this.dynamicExpansions = dynamicExpansions;
+ }
+
+ ImmutableList<String> getExpansion(ExpansionContext context) throws OptionsParsingException {
+ Preconditions.checkArgument(context != null);
+ if (dynamicExpansions != null) {
+ ImmutableList<String> result = dynamicExpansions.getExpansion(context);
+ if (result == null) {
+ String valueString =
+ context.getUnparsedValue() != null ? context.getUnparsedValue() : "(null)";
+ String name = context.getField().getAnnotation(Option.class).name();
+ throw new OptionsParsingException(
+ "Error expanding option '"
+ + name
+ + "': no expansions defined for value: "
+ + valueString,
+ name);
+ }
+ return result;
+ } else {
+ return staticExpansion;
+ }
+ }
+
+ boolean isEmpty() {
+ return staticExpansion.isEmpty() && (dynamicExpansions == null);
+ }
+ }
+
+ /**
+ * Mapping from each Option-annotated field with expansion information to the {@link
+ * ExpansionData} needed to caclulate it.
+ */
+ private final ImmutableMap<Field, ExpansionData> expansionDataForFields;
/** Construct {@link OptionsData} by extending an {@link IsolatedOptionsData} with new info. */
- private OptionsData(
- IsolatedOptionsData base, Map<Field, ImmutableList<String>> evaluatedExpansions) {
+ private OptionsData(IsolatedOptionsData base, Map<Field, ExpansionData> expansionDataForFields) {
super(base);
- this.evaluatedExpansions = ImmutableMap.copyOf(evaluatedExpansions);
+ this.expansionDataForFields = ImmutableMap.copyOf(expansionDataForFields);
}
private static final ImmutableList<String> EMPTY_EXPANSION = ImmutableList.<String>of();
+ private static final ExpansionData EMPTY_EXPANSION_DATA = new ExpansionData(EMPTY_EXPANSION);
/**
* Returns the expansion of an options field, regardless of whether it was defined using {@link
* Option#expansion} or {@link Option#expansionFunction}. If the field is not an expansion option,
* returns an empty array.
*/
- public ImmutableList<String> getEvaluatedExpansion(Field field) {
- ImmutableList<String> result = evaluatedExpansions.get(field);
- return result != null ? result : EMPTY_EXPANSION;
+ public ImmutableList<String> getEvaluatedExpansion(Field field, @Nullable String unparsedValue)
+ throws OptionsParsingException {
+ ExpansionData expansionData = expansionDataForFields.get(field);
+ if (expansionData == null) {
+ return EMPTY_EXPANSION;
+ }
+
+ return expansionData.getExpansion(new ExpansionContext(this, field, unparsedValue));
+ }
+
+ ExpansionData getExpansionDataForField(Field field) {
+ ExpansionData result = expansionDataForFields.get(field);
+ return result != null ? result : EMPTY_EXPANSION_DATA;
}
/**
* Constructs an {@link OptionsData} object for a parser that knows about the given {@link
* OptionsBase} classes. In addition to the work done to construct the {@link
- * IsolatedOptionsData}, this also computes expansion information.
+ * IsolatedOptionsData}, this also computes expansion information. If an option has static
+ * expansions or uses an expansion function that takes a Void object, try to precalculate the
+ * expansion here.
*/
- public static OptionsData from(Collection<Class<? extends OptionsBase>> classes) {
+ static OptionsData from(Collection<Class<? extends OptionsBase>> classes) {
IsolatedOptionsData isolatedData = IsolatedOptionsData.from(classes);
// All that's left is to compute expansions.
- Map<Field, ImmutableList<String>> evaluatedExpansionsBuilder = Maps.newHashMap();
+ ImmutableMap.Builder<Field, ExpansionData> expansionDataBuilder =
+ ImmutableMap.<Field, ExpansionData>builder();
for (Map.Entry<String, Field> entry : isolatedData.getAllNamedFields()) {
Field field = entry.getValue();
Option annotation = field.getAnnotation(Option.class);
@@ -74,7 +137,7 @@
throw new AssertionError(
"Cannot set both expansion and expansionFunction for option --" + annotation.name());
} else if (constExpansion.length > 0) {
- evaluatedExpansionsBuilder.put(field, ImmutableList.copyOf(constExpansion));
+ expansionDataBuilder.put(field, new ExpansionData(ImmutableList.copyOf(constExpansion)));
} else if (usesExpansionFunction(annotation)) {
if (Modifier.isAbstract(expansionFunctionClass.getModifiers())) {
throw new AssertionError(
@@ -90,11 +153,25 @@
// time it is used.
throw new AssertionError(e);
}
- ImmutableList<String> expansion = instance.getExpansion(isolatedData);
- evaluatedExpansionsBuilder.put(field, expansion);
+
+ ImmutableList<String> staticExpansion = null;
+ try {
+ staticExpansion = instance.getExpansion(new ExpansionContext(isolatedData, field, null));
+ Preconditions.checkState(
+ staticExpansion != null,
+ "Error calling expansion function for option: %s",
+ annotation.name());
+ expansionDataBuilder.put(field, new ExpansionData(staticExpansion));
+ } catch (ExpansionNeedsValueException e) {
+ // This expansion function needs data that isn't available yet. Save the instance and call
+ // it later.
+ expansionDataBuilder.put(field, new ExpansionData(instance));
+ } catch (OptionsParsingException e) {
+ throw new IllegalStateException("Error expanding void expansion function: ", e);
+ }
}
}
- return new OptionsData(isolatedData, evaluatedExpansionsBuilder);
+ return new OptionsData(isolatedData, expansionDataBuilder.build());
}
}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index 728c490..c007db8 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -239,7 +239,7 @@
private final Converter<?> converter;
private final boolean allowMultiple;
- private final ImmutableList<OptionValueDescription> expansions;
+ private final OptionsData.ExpansionData expansionData;
private final ImmutableList<OptionValueDescription> implicitRequirements;
OptionDescription(
@@ -247,13 +247,13 @@
Object defaultValue,
Converter<?> converter,
boolean allowMultiple,
- ImmutableList<OptionValueDescription> expansions,
+ OptionsData.ExpansionData expansionData,
ImmutableList<OptionValueDescription> implicitRequirements) {
this.name = name;
this.defaultValue = defaultValue;
this.converter = converter;
this.allowMultiple = allowMultiple;
- this.expansions = expansions;
+ this.expansionData = expansionData;
this.implicitRequirements = implicitRequirements;
}
@@ -277,8 +277,14 @@
return implicitRequirements;
}
- public ImmutableList<OptionValueDescription> getExpansions() {
- return expansions;
+ public boolean isExpansion() {
+ return !expansionData.isEmpty();
+ }
+
+ /** Return a list of flags that this option expands to. */
+ public ImmutableList<String> getExpansion(ExpansionContext context)
+ throws OptionsParsingException {
+ return expansionData.getExpansion(context);
}
}
@@ -658,21 +664,33 @@
* @return The {@link OptionDescription} for the option, or null if there is no option by the
* given name.
*/
- public OptionDescription getOptionDescription(String name) throws OptionsParsingException {
+ OptionDescription getOptionDescription(String name) throws OptionsParsingException {
return impl.getOptionDescription(name);
}
/**
- * Returns a description of the option value set by the last previous call to
- * {@link #parse(OptionPriority, String, List)} that successfully set the given
- * option. If the option is of type {@link List}, the description will
- * correspond to any one of the calls, but not necessarily the last.
+ * Returns a description of the options values that get expanded from this flag with the given
+ * flag value.
+ *
+ * @return The {@link ImmutableList<OptionValueDescription>} for the option, or null if there is
+ * no option by the given name.
+ */
+ ImmutableList<OptionValueDescription> getExpansionOptionValueDescriptions(
+ String flagName, @Nullable String flagValue) throws OptionsParsingException {
+ return impl.getExpansionOptionValueDescriptions(flagName, flagValue);
+ }
+
+ /**
+ * Returns a description of the option value set by the last previous call to {@link
+ * #parse(OptionPriority, String, List)} that successfully set the given option. If the option is
+ * of type {@link List}, the description will correspond to any one of the calls, but not
+ * necessarily the last.
*
* @return The {@link OptionValueDescription} for the option, or null if the value has not been
- * set.
+ * set.
* @throws IllegalArgumentException if there is no option by the given name.
*/
- public OptionValueDescription getOptionValueDescription(String name) {
+ OptionValueDescription getOptionValueDescription(String name) {
return impl.getOptionValueDescription(name);
}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
index e0fc062..f599cea 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -41,6 +41,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import javax.annotation.Nullable;
/**
* The implementation of the options parser. This is intentionally package
@@ -350,41 +351,66 @@
optionsData.getDefaultValue(field),
optionsData.getConverter(field),
optionsData.getAllowMultiple(field),
- getExpansionDescriptions(
- optionsData.getEvaluatedExpansion(field),
- /* expandedFrom */ name,
- /* implicitDependant */ null),
- getExpansionDescriptions(
- ImmutableList.copyOf(optionAnnotation.implicitRequirements()),
- /* expandedFrom */ null,
- /* implicitDependant */ name));
+ optionsData.getExpansionDataForField(field),
+ getImplicitDependantDescriptions(
+ ImmutableList.copyOf(optionAnnotation.implicitRequirements()), name));
}
/**
- * @return A list of the descriptions corresponding to the list of unparsed flags passed in. These
- * descriptions are are divorced from the command line - there is no correct priority or
+ * @return A list of the descriptions corresponding to the implicit dependant flags passed in.
+ * These descriptions are are divorced from the command line - there is no correct priority or
* source for these, as they are not actually set values. The value itself is also a string,
* no conversion has taken place.
*/
- private ImmutableList<OptionValueDescription> getExpansionDescriptions(
- ImmutableList<String> optionStrings, String expandedFrom, String implicitDependant)
- throws OptionsParsingException {
+ private ImmutableList<OptionValueDescription> getImplicitDependantDescriptions(
+ ImmutableList<String> options, String implicitDependant) throws OptionsParsingException {
ImmutableList.Builder<OptionValueDescription> builder = ImmutableList.builder();
- ImmutableList<String> options = ImmutableList.copyOf(optionStrings);
Iterator<String> optionsIterator = options.iterator();
while (optionsIterator.hasNext()) {
String unparsedFlagExpression = optionsIterator.next();
ParseOptionResult parseResult = parseOption(unparsedFlagExpression, optionsIterator);
- builder.add(new OptionValueDescription(
- parseResult.option.name(),
- parseResult.value,
- /* value */ null,
- /* priority */ null,
- /* source */null,
- implicitDependant,
- expandedFrom,
- optionsData.getAllowMultiple(parseResult.field)));
+ builder.add(
+ new OptionValueDescription(
+ parseResult.option.name(),
+ parseResult.value,
+ /* value */ null,
+ /* priority */ null,
+ /* source */ null,
+ implicitDependant,
+ /* expendedFrom */ null,
+ optionsData.getAllowMultiple(parseResult.field)));
+ }
+ return builder.build();
+ }
+
+ /**
+ * @return A list of the descriptions corresponding to options expanded from the flag for the
+ * given value. These descriptions are are divorced from the command line - there is no
+ * correct priority or source for these, as they are not actually set values. The value itself
+ * is also a string, no conversion has taken place.
+ */
+ ImmutableList<OptionValueDescription> getExpansionOptionValueDescriptions(
+ String flagName, @Nullable String flagValue) throws OptionsParsingException {
+ ImmutableList.Builder<OptionValueDescription> builder = ImmutableList.builder();
+ Field field = optionsData.getFieldFromName(flagName);
+
+ ImmutableList<String> options = optionsData.getEvaluatedExpansion(field, flagValue);
+ Iterator<String> optionsIterator = options.iterator();
+
+ while (optionsIterator.hasNext()) {
+ String unparsedFlagExpression = optionsIterator.next();
+ ParseOptionResult parseResult = parseOption(unparsedFlagExpression, optionsIterator);
+ builder.add(
+ new OptionValueDescription(
+ parseResult.option.name(),
+ parseResult.value,
+ /* value */ null,
+ /* priority */ null,
+ /* source */ null,
+ /* implicitDependant */ null,
+ flagName,
+ optionsData.getAllowMultiple(parseResult.field)));
}
return builder.build();
}
@@ -444,7 +470,7 @@
ParseOptionResult parseOptionResult = parseOption(arg, argsIterator);
Field field = parseOptionResult.field;
Option option = parseOptionResult.option;
- String value = parseOptionResult.value;
+ @Nullable String value = parseOptionResult.value;
final String originalName = option.name();
@@ -498,8 +524,9 @@
}
// Handle expansion options.
- ImmutableList<String> expansion = optionsData.getEvaluatedExpansion(field);
- if (!expansion.isEmpty()) {
+ if (OptionsData.isExpansionOption(field.getAnnotation(Option.class))) {
+ ImmutableList<String> expansion = optionsData.getEvaluatedExpansion(field, value);
+
Function<Object, String> expansionSourceFunction =
Functions.constant(
"expanded from option --"
@@ -580,9 +607,9 @@
private static final class ParseOptionResult {
final Field field;
final Option option;
- final String value;
+ @Nullable final String value;
- ParseOptionResult(Field field, Option option, String value) {
+ ParseOptionResult(Field field, Option option, @Nullable String value) {
this.field = field;
this.option = option;
this.value = value;
diff --git a/src/main/java/com/google/devtools/common/options/OptionsUsage.java b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
index 742e9f9..5f7c48a 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsUsage.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
@@ -81,15 +81,22 @@
/**
* Returns the expansion for an option, to the extent known. Precisely, if an {@link OptionsData}
- * object is supplied, the expansion is read from that. Otherwise, the annotation is inspected: If
- * the annotation uses {@link Option#expansion} it is returned, and if it uses {@link
- * Option#expansionFunction} null is returned, indicating a lack of definite information. In all
- * cases, when the option is not an expansion option, an empty list is returned.
+ * object is supplied, the expansion is read from that if the expansion function doesn't take an
+ * argument. Otherwise, the annotation is inspected: If the annotation uses {@link
+ * Option#expansion} it is returned, and if it uses {@link Option#expansionFunction} null is
+ * returned, indicating a lack of definite information. In all cases, when the option is not an
+ * expansion option, an empty list is returned.
*/
private static @Nullable ImmutableList<String> getExpansionIfKnown(
Field optionField, Option annotation, @Nullable OptionsData optionsData) {
if (optionsData != null) {
- return optionsData.getEvaluatedExpansion(optionField);
+ try {
+ return optionsData.getEvaluatedExpansion(optionField, null);
+ } catch (ExpansionNeedsValueException e) {
+ return null;
+ } catch (OptionsParsingException e) {
+ throw new IllegalStateException("Error expanding void expansion function: ", e);
+ }
} else {
if (OptionsData.usesExpansionFunction(annotation)) {
return null;