Allow passing a comma-separated list of strategies to the strategy flags.

Any strategy except the first one in a list is currently ignored. This
feature will be used in follow up changes to implement a new way of
specifying execution strategies and to improve the configurability of
execution fallback (e.g. remote to local, sandboxed to non-sandboxed).

RELNOTES: None.
PiperOrigin-RevId: 234849292
diff --git a/src/main/java/com/google/devtools/common/options/Converters.java b/src/main/java/com/google/devtools/common/options/Converters.java
index fb3bbfa..a691bd4 100644
--- a/src/main/java/com/google/devtools/common/options/Converters.java
+++ b/src/main/java/com/google/devtools/common/options/Converters.java
@@ -254,15 +254,24 @@
 
     private final String separatorDescription;
     private final Splitter splitter;
+    private final boolean allowEmptyValues;
 
-    protected SeparatedOptionListConverter(char separator, String separatorDescription) {
+    protected SeparatedOptionListConverter(
+        char separator, String separatorDescription, boolean allowEmptyValues) {
       this.separatorDescription = separatorDescription;
       this.splitter = Splitter.on(separator);
+      this.allowEmptyValues = allowEmptyValues;
     }
 
     @Override
-    public List<String> convert(String input) {
-      return input.isEmpty() ? ImmutableList.of() : ImmutableList.copyOf(splitter.split(input));
+    public List<String> convert(String input) throws OptionsParsingException {
+      List<String> result =
+          input.isEmpty() ? ImmutableList.of() : ImmutableList.copyOf(splitter.split(input));
+      if (!allowEmptyValues && result.contains("")) {
+        throw new OptionsParsingException(
+            "Empty values are not allowed as part of this " + getTypeDescription());
+      }
+      return result;
     }
 
     @Override
@@ -273,13 +282,20 @@
 
   public static class CommaSeparatedOptionListConverter extends SeparatedOptionListConverter {
     public CommaSeparatedOptionListConverter() {
-      super(',', "comma");
+      super(',', "comma", true);
+    }
+  }
+
+  public static class CommaSeparatedNonEmptyOptionListConverter
+      extends SeparatedOptionListConverter {
+    public CommaSeparatedNonEmptyOptionListConverter() {
+      super(',', "comma", false);
     }
   }
 
   public static class ColonSeparatedOptionListConverter extends SeparatedOptionListConverter {
     public ColonSeparatedOptionListConverter() {
-      super(':', "colon");
+      super(':', "colon", true);
     }
   }
 
@@ -444,6 +460,44 @@
 
   /**
    * A converter for variable assignments from the parameter list of a blaze command invocation.
+   * Assignments are expected to have the form {@code name=value1[,..,valueN]}, where names and
+   * values are defined to be as permissive as possible.
+   */
+  public static class AssignmentToListOfValuesConverter
+      implements Converter<Map.Entry<String, List<String>>> {
+
+    private final Splitter splitter = Splitter.on(',');
+
+    @Override
+    public Map.Entry<String, List<String>> convert(String input) throws OptionsParsingException {
+      int pos = input.indexOf("=");
+      if (pos <= 0) {
+        throw new OptionsParsingException(
+            "Variable definitions must be in the form of a 'name=value1[,..,valueN]' assignment");
+      }
+      String name = input.substring(0, pos);
+      List<String> value = splitter.splitToList(input.substring(pos + 1));
+      if (value.contains("")) {
+        // If the list contains exactly the empty string, it means an empty value was passed and we
+        // should instead return an empty list.
+        if (value.size() == 1) {
+          value = ImmutableList.of();
+        } else {
+          throw new OptionsParsingException(
+              "Variable definitions must not contain empty strings or leading / trailing commas");
+        }
+      }
+      return Maps.immutableEntry(name, value);
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a 'name=value1[,..,valueN]' assignment";
+    }
+  }
+
+  /**
+   * A converter for variable assignments from the parameter list of a blaze command invocation.
    * Assignments are expected to have the form "name[=value]", where names and values are defined to
    * be as permissive as possible and value part can be optional (in which case it is considered to
    * be null).