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;
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansionTest.java b/src/test/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansionTest.java
index 7f91d17..a5ff150 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/AllIncompatibleChangesExpansionTest.java
@@ -21,9 +21,9 @@
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.UseDefault;
import com.google.devtools.common.options.Converters;
+import com.google.devtools.common.options.ExpansionContext;
import com.google.devtools.common.options.ExpansionFunction;
import com.google.devtools.common.options.InvocationPolicyEnforcer;
-import com.google.devtools.common.options.IsolatedOptionsData;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionsBase;
@@ -110,7 +110,7 @@
/** Dummy comment (linter suppression) */
public static class YExpansion implements ExpansionFunction {
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
return ImmutableList.of("--noY");
}
}
diff --git a/src/test/java/com/google/devtools/common/options/InvocationPolicySetValueTest.java b/src/test/java/com/google/devtools/common/options/InvocationPolicySetValueTest.java
index beb0005..4303665 100644
--- a/src/test/java/com/google/devtools/common/options/InvocationPolicySetValueTest.java
+++ b/src/test/java/com/google/devtools/common/options/InvocationPolicySetValueTest.java
@@ -201,14 +201,14 @@
TEST_STRING_POLICY_VALUE_2)
.inOrder();
}
-
+
@Test
public void testSetValueWithExpansionFlags() throws Exception {
InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
- invocationPolicyBuilder.addFlagPoliciesBuilder()
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
.setFlagName("test_expansion")
- .getSetValueBuilder()
- .addFlagValue("true"); // this value is arbitrary, the value for a Void flag is ignored
+ .getSetValueBuilder(); // This value must be empty for a Void flag.
InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
// Unrelated flag, but --test_expansion is not set
@@ -232,6 +232,80 @@
}
@Test
+ public void testSetValueWithExpansionFunctionFlags() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getSetValueBuilder()
+ .addFlagValue(TestOptions.TEST_EXPANSION_FUNCTION_ACCEPTED_VALUE);
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ // Unrelated flag, but --test_expansion_function is not set
+ parser.parse("--test_string=throwaway value");
+
+ // The flags that --test_expansion_function expands into should still be their default values
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_DEFAULT);
+
+ enforcer.enforce(parser, BUILD_COMMAND);
+
+ // After policy enforcement, the flags should be the values from
+ // --test_expansion_function=valueA
+ testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_EXPANSION_FUNCTION_VALUE);
+ }
+
+ @Test
+ public void testSetValueWithExpansionFunctionFlagsDefault() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getSetValueBuilder();
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ // Unrelated flag, but --test_expansion_function is not set
+ parser.parse("--test_string=throwaway value");
+
+ // The flags that --test_expansion_function expands into should still be their default values
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_DEFAULT);
+
+ try {
+ enforcer.enforce(parser, BUILD_COMMAND);
+ fail();
+ } catch (OptionsParsingException e) {
+ assertThat(e).hasMessage("Expansion value not set.");
+ }
+ }
+
+ @Test
+ public void testSetValueWithExpansionFunctionFlagsWrongValue() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getSetValueBuilder()
+ .addFlagValue("unknown_value");
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ // Unrelated flag, but --test_expansion_function is not set
+ parser.parse("--test_string=throwaway value");
+
+ // The flags that --test_expansion_function expands into should still be their default values
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_DEFAULT);
+
+ try {
+ enforcer.enforce(parser, BUILD_COMMAND);
+ fail();
+ } catch (OptionsParsingException e) {
+ assertThat(e).hasMessage("Unrecognized expansion value: unknown_value");
+ }
+ }
+
+ @Test
public void testOverridableSetValueWithExpansionFlags() throws Exception {
InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
invocationPolicyBuilder
@@ -265,13 +339,39 @@
}
@Test
+ public void testOverridableSetValueWithExpansionFunction() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getSetValueBuilder()
+ .addFlagValue(TestOptions.TEST_EXPANSION_FUNCTION_ACCEPTED_VALUE)
+ .setOverridable(true);
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ // Unrelated flag, but --test_expansion_function is not set
+ parser.parse("--expanded_d=value that overrides");
+
+ // The flags that --test_expansion_function expands into should still be their default values
+ // except for the explicitly marked flag.
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo("value that overrides");
+
+ enforcer.enforce(parser, "build");
+
+ // After policy enforcement, the flags should be the values from --test_expansion_function,
+ // except for the user-set value, since the expansion flag was set to overridable.
+ testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo("value that overrides");
+ }
+
+ @Test
public void testOverridableSetValueWithExpansionToRepeatingFlag() throws Exception {
InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
invocationPolicyBuilder
.addFlagPoliciesBuilder()
.setFlagName("test_expansion_to_repeatable")
- .getSetValueBuilder()
- .addFlagValue("") // this value is arbitrary, the value for a Void flag is ignored
+ .getSetValueBuilder() // This value must be empty for a Void flag.
.setOverridable(true);
InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
@@ -298,8 +398,7 @@
invocationPolicyBuilder
.addFlagPoliciesBuilder()
.setFlagName("test_expansion")
- .getSetValueBuilder()
- .addFlagValue("") // this value is arbitrary, the value for a Void flag is ignored
+ .getSetValueBuilder() // This value must be empty for a Void flag.
.setOverridable(false);
InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
@@ -327,13 +426,40 @@
}
@Test
+ public void testNonoverridableSetValueWithExpansionFlags() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getSetValueBuilder()
+ .addFlagValue(TestOptions.TEST_EXPANSION_FUNCTION_ACCEPTED_VALUE)
+ .setOverridable(false);
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ // Unrelated flag, but --test_expansion_function is not set
+ parser.parse("--expanded_d=value to override");
+
+ // The flags that --test_expansion_function expands into should still be their default values
+ // except for the explicitly marked flag.
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo("value to override");
+
+ enforcer.enforce(parser, "build");
+
+ // After policy enforcement, the flags should be the values from --test_expansion_function,
+ // including the value that the user tried to set, since the expansion flag was set
+ // non-overridably.
+ testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_EXPANSION_FUNCTION_VALUE);
+ }
+
+ @Test
public void testNonOverridableSetValueWithExpansionToRepeatingFlag() throws Exception {
InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
invocationPolicyBuilder
.addFlagPoliciesBuilder()
.setFlagName("test_expansion_to_repeatable")
- .getSetValueBuilder()
- .addFlagValue("") // this value is arbitrary, the value for a Void flag is ignored
+ .getSetValueBuilder() // This value must be empty for a Void flag.
.setOverridable(false);
InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
diff --git a/src/test/java/com/google/devtools/common/options/InvocationPolicyUseDefaultTest.java b/src/test/java/com/google/devtools/common/options/InvocationPolicyUseDefaultTest.java
index 0a046e5..975befb 100644
--- a/src/test/java/com/google/devtools/common/options/InvocationPolicyUseDefaultTest.java
+++ b/src/test/java/com/google/devtools/common/options/InvocationPolicyUseDefaultTest.java
@@ -14,6 +14,7 @@
package com.google.devtools.common.options;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import org.junit.Test;
@@ -101,6 +102,50 @@
}
@Test
+ public void testUseDefaultWithVoidExpansionFunction() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_void_expansion_function")
+ .getUseDefaultBuilder();
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ parser.parse("--expanded_d=value to override");
+
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo("value to override");
+
+ enforcer.enforce(parser, BUILD_COMMAND);
+
+ // After policy enforcement, all the flags that --test_void_expansion_function expanded into
+ // should be back to their default values.
+ testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo(TestOptions.EXPANDED_D_DEFAULT);
+ }
+
+ @Test
+ public void testUseDefaultWithExpansionFunction() throws Exception {
+ InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+ invocationPolicyBuilder
+ .addFlagPoliciesBuilder()
+ .setFlagName("test_expansion_function")
+ .getUseDefaultBuilder();
+
+ InvocationPolicyEnforcer enforcer = createOptionsPolicyEnforcer(invocationPolicyBuilder);
+ parser.parse("--expanded_d=value to override");
+
+ TestOptions testOptions = getTestOptions();
+ assertThat(testOptions.expandedD).isEqualTo("value to override");
+
+ try {
+ enforcer.enforce(parser, BUILD_COMMAND);
+ fail();
+ } catch (OptionsParsingException e) {
+ assertThat(e).hasMessage("Expansion value not set.");
+ }
+ }
+
+ @Test
public void testUseDefaultWithExpansionFlagAndLaterOverride() throws Exception {
InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
invocationPolicyBuilder
diff --git a/src/test/java/com/google/devtools/common/options/OptionsParserTest.java b/src/test/java/com/google/devtools/common/options/OptionsParserTest.java
index d2e2fae..18b6da9 100644
--- a/src/test/java/com/google/devtools/common/options/OptionsParserTest.java
+++ b/src/test/java/com/google/devtools/common/options/OptionsParserTest.java
@@ -868,7 +868,7 @@
/** ExpFunc */
public static class ExpFunc implements ExpansionFunction {
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
return ImmutableList.of("--yyy");
}
}
@@ -900,7 +900,7 @@
/** ExpFunc */
public static class ExpFunc implements ExpansionFunction {
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
return null;
}
}
@@ -917,8 +917,35 @@
newOptionsParser(NullExpansionsOptions.class);
fail("Should have failed due to null expansion function result");
} catch (OptionsParser.ConstructionException e) {
- assertThat(e).hasCauseThat().isInstanceOf(NullPointerException.class);
- assertThat(e).hasCauseThat().hasMessageThat().contains("null value in entry");
+ assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
+ }
+ }
+
+ /** NullExpansionOptions */
+ public static class NullExpansionsWithArgumentOptions extends OptionsBase {
+
+ /** ExpFunc */
+ public static class ExpFunc implements ExpansionFunction {
+ @Override
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
+ return null;
+ }
+ }
+
+ @Option(name = "badness", expansionFunction = ExpFunc.class, defaultValue = "null")
+ public String badness;
+ }
+
+ @Test
+ public void nullExpansionsWithArgument() throws Exception {
+ try {
+ // When an expansion takes a value, this exception should still happen at parse time.
+ newOptionsParser(NullExpansionsWithArgumentOptions.class);
+ fail("Should have failed due to null expansion function result");
+ } catch (OptionsParser.ConstructionException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("Error calling expansion function for option: badness");
}
}
@@ -937,7 +964,7 @@
/** ExpFunc */
public static class ExpFunc implements ExpansionFunction {
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
return ImmutableList.of("--expands");
}
}
@@ -946,6 +973,29 @@
public Void expandsByFunction;
}
+ /** ExpansionMultipleOptions */
+ public static class ExpansionMultipleOptions extends OptionsBase {
+ @Option(name = "underlying", defaultValue = "null", allowMultiple = true)
+ public List<String> underlying;
+
+ /** ExpFunc */
+ public static class ExpFunc implements ExpansionFunction {
+ @Override
+ public ImmutableList<String> getExpansion(ExpansionContext context)
+ throws OptionsParsingException {
+ String value = context.getUnparsedValue();
+ if (value == null) {
+ throw new ExpansionNeedsValueException("No value given to 'expands_by_function'");
+ }
+
+ return ImmutableList.of("--underlying=pre_" + value, "--underlying=post_" + value);
+ }
+ }
+
+ @Option(name = "expands_by_function", defaultValue = "null", expansionFunction = ExpFunc.class)
+ public Void expandsByFunction;
+ }
+
@Test
public void describeOptionsWithExpansion() throws Exception {
// We have to test this here rather than in OptionsTest because expansion functions require
@@ -976,6 +1026,19 @@
assertThat(options.underlying).isEqualTo("from_expansion");
}
+ // Makes sure the expansion options are expanded in the right order if they affect flags that
+ // allow multiples.
+ @Test
+ public void multipleExpansionOptionsWithValue() throws Exception {
+ OptionsParser parser = OptionsParser.newOptionsParser(ExpansionMultipleOptions.class);
+ parser.parse(
+ OptionPriority.COMMAND_LINE,
+ null,
+ Arrays.asList("--expands_by_function=a", "--expands_by_function=b"));
+ ExpansionMultipleOptions options = parser.getOptions(ExpansionMultipleOptions.class);
+ assertThat(options.underlying).containsExactly("pre_a", "post_a", "pre_b", "post_b").inOrder();
+ }
+
@Test
public void overrideWithHigherPriority() throws Exception {
OptionsParser parser = OptionsParser.newOptionsParser(NullTestOptions.class);
diff --git a/src/test/java/com/google/devtools/common/options/OptionsTest.java b/src/test/java/com/google/devtools/common/options/OptionsTest.java
index dbe680d..bb0a22e 100644
--- a/src/test/java/com/google/devtools/common/options/OptionsTest.java
+++ b/src/test/java/com/google/devtools/common/options/OptionsTest.java
@@ -71,9 +71,9 @@
/** SpecialExpansion */
public static class SpecialExpansion implements ExpansionFunction {
@Override
- public ImmutableList<String> getExpansion(IsolatedOptionsData optionsData) {
+ public ImmutableList<String> getExpansion(ExpansionContext context) {
TreeSet<String> flags = new TreeSet<>();
- for (Map.Entry<String, ?> entry : optionsData.getAllNamedFields()) {
+ for (Map.Entry<String, ?> entry : context.getOptionsData().getAllNamedFields()) {
if (entry.getKey().startsWith("specialexp_")) {
flags.add("--" + entry.getKey());
}
@@ -82,6 +82,23 @@
}
}
+ /** VariableExpansion */
+ public static class VariableExpansion implements ExpansionFunction {
+ @Override
+ public ImmutableList<String> getExpansion(ExpansionContext context)
+ throws OptionsParsingException {
+ String value = context.getUnparsedValue();
+ if (value == null) {
+ throw new ExpansionNeedsValueException("Expansion value not set.");
+ }
+ if (value.equals("foo_bar")) {
+ return ImmutableList.<String>of("--specialexp_foo", "--specialexp_bar");
+ }
+
+ throw new OptionsParsingException("Unexpected expansion argument: " + value);
+ }
+ }
+
@Option(name = "specialexp_foo", defaultValue = "false")
public boolean specialExpFoo;
@@ -90,6 +107,9 @@
@Option(name = "specialexp", defaultValue = "null", expansionFunction = SpecialExpansion.class)
public Void specialExp;
+
+ @Option(name = "dynamicexp", defaultValue = "null", expansionFunction = VariableExpansion.class)
+ public Void variableExpansion;
}
@Test
@@ -539,4 +559,23 @@
Options.parse(HttpOptions.class, new String[] {"--specialexp_foo", "--specialexp_bar"});
assertThat(options1.getOptions()).isEqualTo(options2.getOptions());
}
+
+ @Test
+ public void dynamicExpansionFunctionWorks() throws Exception {
+ Options<HttpOptions> options1 =
+ Options.parse(HttpOptions.class, new String[] {"--dynamicexp=foo_bar"});
+ Options<HttpOptions> options2 =
+ Options.parse(HttpOptions.class, new String[] {"--specialexp_foo", "--specialexp_bar"});
+ assertThat(options1.getOptions()).isEqualTo(options2.getOptions());
+ }
+
+ @Test
+ public void dynamicExpansionFunctionUnknowValue() throws Exception {
+ try {
+ Options.parse(HttpOptions.class, new String[] {"--dynamicexp=foo"});
+ fail("Unknown expansion argument should cause a failure.");
+ } catch (OptionsParsingException e) {
+ assertThat(e).hasMessage("Unexpected expansion argument: foo");
+ }
+ }
}
diff --git a/src/test/java/com/google/devtools/common/options/TestOptions.java b/src/test/java/com/google/devtools/common/options/TestOptions.java
index ce532bb..c9d606c 100644
--- a/src/test/java/com/google/devtools/common/options/TestOptions.java
+++ b/src/test/java/com/google/devtools/common/options/TestOptions.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.common.options;
+import com.google.common.collect.ImmutableList;
import com.google.devtools.common.options.InvocationPolicyEnforcerTestBase.ToListConverter;
import java.util.List;
@@ -174,4 +175,47 @@
implicitRequirements = {"--test_implicit_requirement=" + TEST_IMPLICIT_REQUIREMENT_REQUIRED}
)
public String testRecursiveImplicitRequirement;
+
+ public static final String TEST_EXPANSION_FUNCTION_ACCEPTED_VALUE = "valueA";
+ public static final String EXPANDED_D_EXPANSION_FUNCTION_VALUE = "expanded valueA";
+
+ /** Used for testing an expansion flag that requires a value. */
+ public static class TestExpansionFunction implements ExpansionFunction {
+ @Override
+ public ImmutableList<String> getExpansion(ExpansionContext expansionContext)
+ throws OptionsParsingException {
+ String value = expansionContext.getUnparsedValue();
+ if (value == null) {
+ throw new ExpansionNeedsValueException("Expansion value not set.");
+ } else if (value.equals(TEST_EXPANSION_FUNCTION_ACCEPTED_VALUE)) {
+ return ImmutableList.of("--expanded_d", EXPANDED_D_EXPANSION_FUNCTION_VALUE);
+ } else {
+ throw new OptionsParsingException("Unrecognized expansion value: " + value);
+ }
+ }
+ }
+
+ @Option(
+ name = "test_expansion_function",
+ defaultValue = "null",
+ expansionFunction = TestExpansionFunction.class
+ )
+ public Void testExpansionFunction;
+
+ public static final String EXPANDED_D_VOID_EXPANSION_FUNCTION_VALUE = "void expanded";
+
+ /** Used for testing an expansion flag that doesn't requires a value. */
+ public static class TestVoidExpansionFunction implements ExpansionFunction {
+ @Override
+ public ImmutableList<String> getExpansion(ExpansionContext expansionContext) {
+ return ImmutableList.of("--expanded_d", EXPANDED_D_VOID_EXPANSION_FUNCTION_VALUE);
+ }
+ }
+
+ @Option(
+ name = "test_void_expansion_function",
+ defaultValue = "null",
+ expansionFunction = TestVoidExpansionFunction.class
+ )
+ public Void testVoidExpansionFunction;
}