Adds a mechanism for invocation policy. The policy is taken through the --invocation_policy startup flag and allows an application invoking Bazel to set or override flag values (whether from the command line or a bazelrc).

--
MOS_MIGRATED_REVID=104160290
diff --git a/src/main/java/BUILD b/src/main/java/BUILD
index 84cbf0b..1e11519 100644
--- a/src/main/java/BUILD
+++ b/src/main/java/BUILD
@@ -448,10 +448,12 @@
         ":util",
         ":vfs",
         "//src/main/protobuf:proto_build",
+        "//src/main/protobuf:proto_invocation_policy",
         "//src/main/protobuf:proto_test_status",
         "//third_party:guava",
         "//third_party:joda_time",
         "//third_party:jsr305",
+        "//third_party:protobuf",
     ],
 )
 
@@ -500,10 +502,12 @@
         ":util",
         ":vfs",
         "//src/main/protobuf:proto_build",
+        "//src/main/protobuf:proto_invocation_policy",
         "//src/main/protobuf:proto_test_status",
         "//third_party:guava",
         "//third_party:joda_time",
         "//third_party:jsr305",
+        "//third_party:protobuf",
     ],
 )
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index a441a39..dbca029 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -148,9 +148,10 @@
     return ExitCode.SUCCESS;
   }
 
-  private CommonCommandOptions checkOptions(OptionsParser optionsParser,
-      Command commandAnnotation, List<String> args, List<String> rcfileNotes, OutErr outErr)
+  private void parseArgsAndConfigs(OptionsParser optionsParser, Command commandAnnotation,
+      List<String> args, List<String> rcfileNotes, OutErr outErr)
           throws OptionsParsingException {
+
     Function<String, String> commandOptionSourceFunction = new Function<String, String>() {
       @Override
       public String apply(String input) {
@@ -187,8 +188,6 @@
       configsLoaded = commonOptions.configs;
       commonOptions = optionsParser.getOptions(CommonCommandOptions.class);
     }
-
-    return commonOptions;
   }
 
   /**
@@ -270,13 +269,16 @@
     }
 
     OptionsParser optionsParser;
-    CommonCommandOptions commonOptions;
     // Delay output of notes regarding the parsed rc file, so it's possible to disable this in the
     // rc file.
     List<String> rcfileNotes = new ArrayList<>();
     try {
       optionsParser = createOptionsParser(command);
-      commonOptions = checkOptions(optionsParser, commandAnnotation, args, rcfileNotes, outErr);
+      parseArgsAndConfigs(optionsParser, commandAnnotation, args, rcfileNotes, outErr);
+
+      InvocationPolicyEnforcer optionsPolicyEnforcer =
+          InvocationPolicyEnforcer.create(getRuntime().getStartupOptionsProvider());
+      optionsPolicyEnforcer.enforce(optionsParser, commandName);
     } catch (OptionsParsingException e) {
       for (String note : rcfileNotes) {
         outErr.printErrLn("INFO: " + note);
@@ -299,6 +301,7 @@
       }
     }
 
+    CommonCommandOptions commonOptions = optionsParser.getOptions(CommonCommandOptions.class);
     BlazeRuntime.setupLogging(commonOptions.verbosity);
 
     // Do this before an actual crash so we don't have to worry about
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
index 244cb70..69d69da 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
@@ -235,4 +235,12 @@
           + "changes instead of scanning every file for a change.")
   public boolean watchFS;
 
+
+  @Option(name = "invocation_policy",
+      defaultValue = "",
+      category = "undocumented",
+      help = "A base64-encoded-binary-serialized or text-formated "
+          + "invocation_policy.InvocationPolicy proto. Unlike other options, it is an error to "
+          + "specify --invocation_policy multiple times.")
+  public String invocationPolicy;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/InvocationPolicyEnforcer.java b/src/main/java/com/google/devtools/build/lib/runtime/InvocationPolicyEnforcer.java
new file mode 100644
index 0000000..463e996
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/InvocationPolicyEnforcer.java
@@ -0,0 +1,404 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Joiner;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import com.google.common.io.BaseEncoding;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.AllowValues;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.DisallowValues;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.FlagPolicy;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.SetValue;
+import com.google.devtools.common.options.OptionPriority;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParser.OptionDescription;
+import com.google.devtools.common.options.OptionsParser.OptionValueDescription;
+import com.google.devtools.common.options.OptionsParsingException;
+import com.google.devtools.common.options.OptionsProvider;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.TextFormat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * Given an OptionsParser and a InvocationPolicy proto, enforces the FlagPolicies on an
+ * OptionsParser.
+ *
+ * <p>"Flag" and "Option" are used interchangeably in this file.
+ */
+public final class InvocationPolicyEnforcer {
+
+  /**
+   * Creates an {@link InvocationPolicyEnforcer} with the invocation policy obtained from the given
+   * {@link OptionsProvider}. This uses the provider only to obtain the policy from the
+   * --invocation_policy flag and does not enforce any policy on the flags in the provider.
+   *
+   * @param startupOptionsProvider an options provider which provides a BlazeServerStartupOptions
+   *     options class
+   *
+   * @throws OptionsParsingException if the value of --invocation_policy is invalid
+   */
+  public static InvocationPolicyEnforcer create(OptionsProvider startupOptionsProvider)
+      throws OptionsParsingException {
+
+    BlazeServerStartupOptions blazeServerStartupOptions =
+        startupOptionsProvider.getOptions(BlazeServerStartupOptions.class);
+    return new InvocationPolicyEnforcer(parsePolicy(blazeServerStartupOptions.invocationPolicy));
+  }
+
+  /**
+   * Parses the given InvocationPolicy string, which may be a base64-encoded binary-serialized
+   * InvocationPolicy message, or a text formatted InvocationPolicy message. Note that the
+   * text format is not backwards compatible as the binary format is, and the option to
+   * provide a text formatted proto is provided only for debugging.
+   *
+   * @throws OptionsParsingException if the value of --invocation_policy is invalid
+   */
+  private static InvocationPolicy parsePolicy(String policy) throws OptionsParsingException {
+    if (policy == null || policy.isEmpty()) {
+      return null;
+    }
+
+    try {
+      try {
+        // First try decoding the policy as a base64 encoded binary proto.
+        return InvocationPolicy.parseFrom(
+            BaseEncoding.base64().decode(CharMatcher.WHITESPACE.removeFrom(policy)));
+      } catch (IllegalArgumentException e) {
+        // If the flag value can't be decoded from base64, try decoding the policy as a text
+        // formated proto.
+        InvocationPolicy.Builder builder = InvocationPolicy.newBuilder();
+        TextFormat.merge(policy, builder);
+        return builder.build();
+      }
+    } catch (InvalidProtocolBufferException | TextFormat.ParseException e) {
+      throw new OptionsParsingException("Malformed value of --invocation_policy: " + policy, e);
+    }
+  }
+
+  private static final Logger LOG = Logger.getLogger(InvocationPolicyEnforcer.class.getName());
+  
+  @Nullable
+  private final InvocationPolicy invocationPolicy;
+
+  public InvocationPolicyEnforcer(@Nullable InvocationPolicy invocationPolicy) {
+    this.invocationPolicy = invocationPolicy;
+  }
+
+  /**
+   * Applies this OptionsPolicyEnforcer's policy to the given OptionsParser.
+   *
+   * @param parser The OptionsParser to enforce policy on.
+   * @param command The command to which the options in the OptionsParser apply.
+   * @throws OptionsParsingException
+   */
+  public void enforce(OptionsParser parser, String command) throws OptionsParsingException {
+    if (invocationPolicy == null) {
+      return;
+    }
+
+    if (invocationPolicy.getFlagPoliciesCount() == 0) {
+      LOG.warning("InvocationPolicy contains no flag policies.");
+    }
+
+    Function<Object, String> sourceFunction = Functions.constant("Invocation policy");
+
+    for (FlagPolicy flagPolicy : invocationPolicy.getFlagPoliciesList()) {
+      String flagName = flagPolicy.getFlagName();
+
+      // Skip the flag policy if it doesn't apply to this command.
+      if (!flagPolicy.getCommandsList().isEmpty()
+          && !flagPolicy.getCommandsList().contains(command)) {
+        LOG.info(String.format("Skipping flag policy for flag '%s' because it "
+            + "applies only to commands %s and the current command is '%s'",
+            flagName, flagPolicy.getCommandsList(), command));
+        continue;
+      }
+
+      OptionValueDescription valueDescription;
+      try {
+        valueDescription = parser.getOptionValueDescription(flagName);
+      } catch (IllegalArgumentException e) {
+        // This flag doesn't exist. We are deliberately lenient if the flag policy has a flag
+        // we don't know about. This is for better future proofing so that as new flags are added,
+        // new policies can use the new flags without worrying about older versions of Bazel. 
+        LOG.info(String.format(
+            "Flag '%s' specified by invocation policy does not exist", flagName));
+        continue;
+      }
+
+      OptionDescription optionDescription = parser.getOptionDescription(flagName);
+      // getOptionDescription() will return null if the option does not exist, however
+      // getOptionValueDescription() above would have thrown an IllegalArgumentException if that
+      // were the case.
+      Verify.verifyNotNull(optionDescription);
+
+      switch (flagPolicy.getOperationCase()) {
+        case SET_VALUE:
+          applySetValueOperation(parser, sourceFunction, flagPolicy, flagName,
+              valueDescription, optionDescription);
+          break;
+
+        case USE_DEFAULT:
+          applyUseDefaultOperation(parser, flagName);
+          break;
+
+        case ALLOW_VALUES:
+          applyAllowValuesOperation(parser, sourceFunction, flagPolicy,
+              flagName, valueDescription, optionDescription);
+          break;
+
+        case DISALLOW_VALUES:
+          applyDisallowValuesOperation(parser, sourceFunction, flagPolicy,
+              flagName, valueDescription, optionDescription);
+          break;
+
+        case OPERATION_NOT_SET:
+          throw new OptionsParsingException(String.format("Flag policy for flag '%s' does not "
+              + "have an operation", flagName));
+
+        default:
+          LOG.warning(String.format("Unknown operation '%s' from invocation policy for flag '%s'",
+              flagPolicy.getOperationCase(), flagName));
+          break;
+      }
+    }
+  }
+
+  private static void applySetValueOperation(
+      OptionsParser parser,
+      Function<Object, String> sourceFunction,
+      FlagPolicy flagPolicy,
+      String flagName,
+      OptionValueDescription valueDescription,
+      OptionDescription optionDescription) throws OptionsParsingException {
+
+    SetValue setValue = flagPolicy.getSetValue();
+
+    // SetValue.flag_value must have at least 1 value.
+    if (setValue.getFlagValueCount() == 0) {
+      throw new OptionsParsingException(String.format(
+          "SetValue operation from invocation policy for flag '%s' does not have a value",
+          flagName));
+    }
+  
+    // Flag must allow multiple values if multiple values are specified by the policy.
+    if (setValue.getFlagValueCount() > 1 && !optionDescription.getAllowMultiple()) {
+      throw new OptionsParsingException(String.format(
+          "SetValue operation from invocation policy sets multiple values for flag '%s' which "
+          + "does not allow multiple values", flagName));
+    }
+  
+    if (setValue.getOverridable() && valueDescription != null) {
+      // The user set the value for the flag but the flag policy is overridable, so keep the user's
+      // value.
+      LOG.info(String.format("Keeping value '%s' from source '%s' for flag '%s' "
+          + "because the invocation policy specifying the value(s) '%s' is overridable",
+          valueDescription.getValue(), valueDescription.getSource(), flagName,
+          setValue.getFlagValueList()));
+    } else {
+  
+      // Clear the value in case the flag is a repeated flag (so that values don't accumulate), and
+      // in case the flag is an expansion flag or has implicit flags (so that the additional flags
+      // also get cleared).
+      parser.clearValue(flagName);
+
+      // Set all the flag values from the policy.
+      for (String flagValue : setValue.getFlagValueList()) {
+        if (valueDescription == null) {
+          LOG.info(String.format("Setting value for flag '%s' from invocation "
+              + "policy to '%s', overriding the default value '%s'", flagName, flagValue,
+              optionDescription.getDefaultValue()));
+        } else {
+          LOG.info(String.format("Setting value for flag '%s' from invocation "
+              + "policy to '%s', overriding value '%s' from '%s'", flagName, flagValue,
+              valueDescription.getValue(), valueDescription.getSource()));
+        }
+        setFlagValue(parser, flagName, flagValue, sourceFunction);
+      }
+    }
+  }
+
+  private static void applyUseDefaultOperation(OptionsParser parser, String flagName) {
+
+    Map<String, OptionValueDescription> clearedValues = parser.clearValue(flagName);
+    for (Entry<String, OptionValueDescription> clearedValue : clearedValues.entrySet()) {
+  
+      OptionValueDescription clearedValueDesc = clearedValue.getValue();
+      String clearedFlagName = clearedValue.getKey();
+      String originalValue = clearedValueDesc.getValue().toString();
+      String source = clearedValueDesc.getSource();
+  
+      OptionDescription clearedFlagDesc = parser.getOptionDescription(clearedFlagName);
+      Object clearedFlagdefaultValue = clearedFlagDesc.getDefaultValue();
+  
+      LOG.info(String.format("Using default value '%s' for flag '%s' as "
+          + "specified by invocation policy, overriding original value '%s' from '%s'",
+          clearedFlagdefaultValue, clearedFlagName, originalValue, source));
+    }
+  }
+
+  private static void applyAllowValuesOperation(
+      OptionsParser parser,
+      Function<Object, String> sourceFunction,
+      FlagPolicy flagPolicy,
+      String flagName,
+      OptionValueDescription valueDescription,
+      OptionDescription optionDescription) throws OptionsParsingException {
+
+    AllowValues allowValues = flagPolicy.getAllowValues();
+    applyAllowDisallowValueOperation(
+        parser,
+        sourceFunction,
+        /*allowValues=*/ true,
+        allowValues.getAllowedValuesList(),
+        allowValues.hasNewDefaultValue() ? allowValues.getNewDefaultValue() : null,
+        flagName,
+        valueDescription,
+        optionDescription);
+  }
+  
+  private static void applyDisallowValuesOperation(
+      OptionsParser parser,
+      Function<Object, String> sourceFunction,
+      FlagPolicy flagPolicy,
+      String flagName,
+      OptionValueDescription valueDescription,
+      OptionDescription optionDescription) throws OptionsParsingException {
+
+    DisallowValues disallowValues = flagPolicy.getDisallowValues();
+    applyAllowDisallowValueOperation(
+        parser,
+        sourceFunction,
+        /*allowValues=*/ false,
+        disallowValues.getDisallowedValuesList(),
+        disallowValues.hasNewDefaultValue() ? disallowValues.getNewDefaultValue() : null,
+        flagName,
+        valueDescription,
+        optionDescription);
+  }
+
+  /**
+   * Shared logic between AllowValues and DisallowValues operations.
+   *
+   * @param parser
+   * @param sourceFunction
+   * @param allowValues True if this is an AllowValues operation, false if DisallowValues
+   * @param policyValues The list of allowed or disallowed values
+   * @param newDefaultValue The new default to use if the default value for the flag is now allowed
+   *   (i.e. not in the list of allowed values or in the list of disallowed values).
+   * @param flagName
+   * @param valueDescription
+   * @param optionDescription
+   *
+   * @throws OptionsParsingException
+   */
+  private static void applyAllowDisallowValueOperation(
+      OptionsParser parser,
+      Function<Object, String> sourceFunction,
+      boolean allowValues,
+      List<String> policyValues,
+      String newDefaultValue,
+      String flagName,
+      OptionValueDescription valueDescription,
+      OptionDescription optionDescription) throws OptionsParsingException {
+
+    // For error reporting.
+    String policyType = allowValues ? "Allow" : "Disallow";
+    
+    // Convert all the allowed values from strings to real object using the option's
+    // converter so that they can be checked for equality using real .equals() instead
+    // of string comparison. For example, "--foo=0", "--foo=false", "--nofoo", and "-f-"
+    // (if the option has an abbreviation) are all equal for boolean flags. Plus converters
+    // can be arbitrarily complex.
+    Set<Object> convertedPolicyValues = Sets.newHashSet();
+    for (String value : policyValues) {
+      convertedPolicyValues.add(optionDescription.getConverter().convert(value));
+    }
+
+    if (valueDescription == null) {
+      // Nothing has set the value yet, so check that the default value from the flag's
+      // definition is allowed. The else case below (i.e. valueDescription is not null) checks for
+      // the flag allowing multiple values, however, flags that allow multiple values cannot have
+      // default values, and their value is always the empty list if they haven't been specified,
+      // which is why new_default_value is not a repeated field.
+      //
+      // This is xor'ed with allowValues because if the policy is to allow these values,
+      // then we want to apply the new default (or throw an error) if the default value of the flag
+      // is not in the set of allowed values. If the policy is to disallow these values
+      // (allowValues is false), then we want to apply the new default (or throw an error) if
+      // the default value of the flag is in the set of disallowed values. This works out to xor.
+      if (allowValues ^ convertedPolicyValues.contains(optionDescription.getDefaultValue())) {
+        if (newDefaultValue != null) {
+          // Use the default value from the policy.
+          LOG.info(String.format("Overriding default value '%s' for flag '%s' with "
+              + "new default value '%s' specified by invocation policy. %sed values are: %s",
+              optionDescription.getDefaultValue(), flagName, newDefaultValue,
+              policyType, Joiner.on(", ").join(policyValues)));
+          parser.clearValue(flagName);
+          setFlagValue(parser, flagName, newDefaultValue, sourceFunction);
+        } else {
+          // The operation disallows the default value, but doesn't supply its own default.
+          throw new OptionsParsingException(String.format(
+              "Default flag value '%s' for flag '%s' is not allowed by invocation policy, but "
+              + "the policy does not provide a new default value. "
+              + "%sed values are: %s", optionDescription.getDefaultValue(), flagName,
+              policyType, Joiner.on(", ").join(policyValues)));
+        }
+      }
+    } else {
+      // Check that the flag's value is allowed.
+      List<?> values;
+      if (optionDescription.getAllowMultiple()) {
+        // allowMultiple requires that the type of the option be List<T>.
+        values = (List<?>) valueDescription.getValue();
+      } else {
+        values = ImmutableList.of(valueDescription.getValue());
+      }
+
+      for (Object value : values) {
+        // See above about the xor.
+        if (allowValues ^ convertedPolicyValues.contains(value)) {
+          throw new OptionsParsingException(String.format(
+              "Flag value '%s' for flag '%s' is not allowed by invocation policy. "
+              + "%sed values are: %s", value, flagName, policyType,
+              Joiner.on(", ").join(policyValues)));
+        }
+      }
+    }
+  }
+
+  private static void setFlagValue(
+      OptionsParser parser,
+      String flagName,
+      String flagValue,
+      Function<? super String, String> sourceFunction) throws OptionsParsingException {
+ 
+    parser.parseWithSourceFunction(OptionPriority.INVOCATION_POLICY, sourceFunction,
+        Arrays.asList(String.format("--%s=%s", flagName, flagValue)));
+  }
+}
diff --git a/src/main/java/com/google/devtools/common/options/Option.java b/src/main/java/com/google/devtools/common/options/Option.java
index f41a051..ca3add9 100644
--- a/src/main/java/com/google/devtools/common/options/Option.java
+++ b/src/main/java/com/google/devtools/common/options/Option.java
@@ -62,6 +62,9 @@
    * be a compile-time constant.)  This special interpretation of the string
    * "null" is only applicable when computing the default value; if specified
    * on the command-line, this string will have its usual literal meaning.
+   *
+   * <p>The default value for flags that set allowMultiple to true is always
+   * the empty list and the value in the annotation is ignored. 
    */
   String defaultValue();
 
@@ -90,6 +93,9 @@
    * converter for this option must either match the parameter {@code T} or
    * {@code List<T>}. In the latter case the individual lists are concatenated
    * to form the full options value.
+   *
+   * <p>The {@link #defaultValue()} field of the annotation is ignored for repeatable
+   * flags and the default value will be the empty list.
    */
   boolean allowMultiple() default false;
 
diff --git a/src/main/java/com/google/devtools/common/options/OptionPriority.java b/src/main/java/com/google/devtools/common/options/OptionPriority.java
index b352e88..a28f012 100644
--- a/src/main/java/com/google/devtools/common/options/OptionPriority.java
+++ b/src/main/java/com/google/devtools/common/options/OptionPriority.java
@@ -50,9 +50,13 @@
   COMMAND_LINE,
 
   /**
+   * For options coming from invocation policy.
+   */
+  INVOCATION_POLICY,
+
+  /**
    * This priority can be used to unconditionally override any user-provided options.
    * This should be used rarely and with caution!
    */
   SOFTWARE_REQUIREMENT;
-
 }
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 d5512a1..61d798a 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsData.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsData.java
@@ -71,12 +71,19 @@
    */
   private final Map<Field, Converter<?>> converters;
 
+  /**
+   * Mapping from each Option-annotated field to a boolean for whether that field allows multiple
+   * values.
+   */
+  private final Map<Field, Boolean> allowMultiple;
+  
   private OptionsData(Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses,
                       Map<String, Field> nameToField,
                       Map<Character, Field> abbrevToField,
                       Map<Class<? extends OptionsBase>, List<Field>> allOptionsFields,
                       Map<Field, Object> optionDefaults,
-                      Map<Field, Converter<?>> converters) {
+                      Map<Field, Converter<?>> converters,
+                      Map<Field, Boolean> allowMultiple) {
     this.optionsClasses = ImmutableMap.copyOf(optionsClasses);
     this.allOptionsFields = ImmutableMap.copyOf(allOptionsFields);
     this.nameToField = ImmutableMap.copyOf(nameToField);
@@ -84,6 +91,7 @@
     // Can't use an ImmutableMap here because of null values.
     this.optionDefaults = Collections.unmodifiableMap(optionDefaults);
     this.converters = ImmutableMap.copyOf(converters);
+    this.allowMultiple = ImmutableMap.copyOf(allowMultiple);
   }
 
   public Collection<Class<? extends OptionsBase>> getOptionsClasses() {
@@ -119,6 +127,10 @@
     return converters.get(field);
   }
 
+  public boolean getAllowMultiple(Field field) {
+    return allowMultiple.get(field);
+  }
+  
   private static List<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) {
     List<Field> allFields = Lists.newArrayList();
     for (Field field : optionsClass.getFields()) {
@@ -157,6 +169,7 @@
     Map<Character, Field> abbrevToFieldBuilder = Maps.newHashMap();
     Map<Field, Object> optionDefaultsBuilder = Maps.newHashMap();
     Map<Field, Converter<?>> convertersBuilder = Maps.newHashMap();
+    Map<Field, Boolean> allowMultipleBuilder = Maps.newHashMap();
 
     // Read all Option annotations:
     for (Class<? extends OptionsBase> parsedOptionsClass : classes) {
@@ -256,9 +269,11 @@
         optionDefaultsBuilder.put(field, retrieveDefaultFromAnnotation(field));
 
         convertersBuilder.put(field, OptionsParserImpl.findConverter(field));
+        
+        allowMultipleBuilder.put(field, annotation.allowMultiple());
       }
     }
     return new OptionsData(constructorBuilder, nameToFieldBuilder, abbrevToFieldBuilder,
-        allOptionsFieldsBuilder, optionDefaultsBuilder, convertersBuilder);
+        allOptionsFieldsBuilder, optionDefaultsBuilder, convertersBuilder, allowMultipleBuilder);
   }
 }
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 80e56cb..400adee 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -187,6 +187,41 @@
   }
 
   /**
+   * The metadata about an option.
+   */
+  public static final class OptionDescription {
+
+    private final String name;
+    private final Object defaultValue;
+    private final Converter<?> converter;
+    private final boolean allowMultiple;
+
+    public OptionDescription(String name, Object defaultValue, Converter<?> converter,
+        boolean allowMultiple) {
+      this.name = name;
+      this.defaultValue = defaultValue;
+      this.converter = converter;
+      this.allowMultiple = allowMultiple;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Object getDefaultValue() {
+      return defaultValue;
+    }
+
+    public Converter<?> getConverter() {
+      return converter;
+    }
+
+    public boolean getAllowMultiple() {
+      return allowMultiple;
+    }
+  }
+  
+  /**
    * The name and value of an option with additional metadata describing its
    * priority, source, whether it was set via an implicit dependency, and if so,
    * by which other option.
@@ -217,10 +252,16 @@
       return value;
     }
 
+    /**
+     * @return the priority of the thing that set this value for this flag
+     */
     public OptionPriority getPriority() {
       return priority;
     }
 
+    /**
+     * @return the thing that set this value for this flag
+     */
     public String getSource() {
       return source;
     }
@@ -450,10 +491,24 @@
   }
 
   /**
+   * Returns a description of the option.
+   *
+   * @return The {@link OptionValueDescription} for the option, or null if there is no option by
+   *        the given name.
+   */
+  public OptionDescription getOptionDescription(String name) {
+    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.
+   *
+   * @return The {@link OptionValueDescription} for the option, or null if the value has not been
+   *        set.
+   * @throws IllegalArgumentException if there is no option by the given name.
    */
   public OptionValueDescription getOptionValueDescription(String name) {
     return impl.getOptionValueDescription(name);
@@ -520,6 +575,23 @@
     }
   }
 
+  /**
+   * Clears the given option. Also clears expansion arguments and implicit requirements for that
+   * option.
+   *
+   * <p>This will not affect options objects that have already been retrieved from this parser
+   * through {@link #getOptions(Class)}.
+   *
+   * @param optionName The full name of the option to clear.
+   * @return A map of an option name to the old value of the options that were cleared.
+   * @throws IllegalArgumentException If the flag does not exist.
+   */
+  public Map<String, OptionValueDescription> clearValue(String optionName) {
+    Map<String, OptionValueDescription> clearedValues = Maps.newHashMap();
+    impl.clearValue(optionName, clearedValues);
+    return clearedValues;
+  }
+
   @Override
   public List<String> getResidue() {
     return ImmutableList.copyOf(residue);
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 bae5293..59d4a0c 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -25,6 +25,7 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.devtools.common.options.OptionsParser.OptionDescription;
 import com.google.devtools.common.options.OptionsParser.OptionValueDescription;
 import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription;
 
@@ -433,6 +434,28 @@
     entry.addValue(priority, value);
   }
 
+  void clearValue(String optionName, Map<String, OptionValueDescription> clearedValues) {
+    Field field = optionsData.getFieldFromName(optionName);
+    if (field == null) {
+      throw new IllegalArgumentException("No such option '" + optionName + "'");
+    }
+
+    ParsedOptionEntry removed = parsedValues.remove(field);
+    if (removed != null) {
+      clearedValues.put(optionName, removed.asOptionValueDescription(optionName));
+    }
+
+    // Recurse to remove any implicit or expansion flags that this flag may have added when
+    // originally parsed.
+    Option option = field.getAnnotation(Option.class);
+    for (String implicitRequirement : option.implicitRequirements()) {
+      clearValue(implicitRequirement, clearedValues);
+    }
+    for (String expansion : option.expansion()) {
+      clearValue(expansion, clearedValues);
+    }
+  }
+
   private Object getValue(Field field) {
     ParsedOptionEntry entry = parsedValues.get(field);
     return entry == null ? null : entry.getValue();
@@ -450,6 +473,20 @@
     return entry.asOptionValueDescription(name);
   }
 
+  OptionDescription getOptionDescription(String name) {
+    Field field = optionsData.getFieldFromName(name);
+    if (field == null) {
+      return null;
+    }
+
+    Option optionAnnotation = field.getAnnotation(Option.class);
+    return new OptionDescription(
+        name,
+        optionsData.getDefaultValue(field),
+        optionsData.getConverter(field),
+        optionAnnotation.allowMultiple());
+  }
+
   boolean containsExplicitOption(String name) {
     Field field = optionsData.getFieldFromName(name);
     if (field == null) {
@@ -475,7 +512,7 @@
    * of options; in that case, the arg seen last takes precedence.
    *
    * <p>The method uses the invariant that if an option has neither an implicit
-   * dependant nor an expanded from value, then it must have been explicitly
+   * dependent nor an expanded from value, then it must have been explicitly
    * set.
    */
   private List<String> parse(OptionPriority priority,
@@ -720,5 +757,4 @@
       throw new AssertionError(e);
     }
   }
-
 }