Extract logic for dealing with CROSSTOOL values and build variables into separate class

RELNOTES: None.
PiperOrigin-RevId: 196517537
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/cpp/AppleCcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/apple/cpp/AppleCcToolchain.java
index 93f1555..5d07a2b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/cpp/AppleCcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/cpp/AppleCcToolchain.java
@@ -27,7 +27,7 @@
 import com.google.devtools.build.lib.rules.apple.XcodeConfig;
 import com.google.devtools.build.lib.rules.apple.XcodeConfigProvider;
 import com.google.devtools.build.lib.rules.cpp.CcToolchain;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -56,7 +56,7 @@
   public static final String APPLE_SDK_PLATFORM_VALUE_KEY = "apple_sdk_platform_value";
 
   @Override
-  protected void addBuildVariables(RuleContext ruleContext, Variables.Builder variables)
+  protected void addBuildVariables(RuleContext ruleContext, CcToolchainVariables.Builder variables)
       throws RuleErrorException {
     AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
index 4d6cb3a..ebf00f8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
@@ -46,7 +46,6 @@
 import com.google.devtools.build.lib.rules.cpp.CcCompilationHelper.SourceCategory;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.CollidingProvidesException;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
 import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoMode;
@@ -900,7 +899,7 @@
       return null;
     }
 
-    Variables buildVariables = toolchainProvider.getBuildVariables();
+    CcToolchainVariables buildVariables = toolchainProvider.getBuildVariables();
     String toolchainCcFlags =
         Joiner.on(" ")
             .join(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
index 0ce68ce..981152c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
@@ -48,8 +48,7 @@
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
@@ -1474,7 +1473,7 @@
     return flagsBuilder.build();
   }
 
-  private Variables setupCompileBuildVariables(
+  private CcToolchainVariables setupCompileBuildVariables(
       CppCompileActionBuilder builder,
       Label sourceLabel,
       String outputName,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
index dc65052..e794eb5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
@@ -43,7 +43,7 @@
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkerOrArchiver;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
index 7e92423..ab37119 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -53,7 +53,6 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.License;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
 import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoException;
 import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoMode;
@@ -844,16 +843,17 @@
   }
 
   /**
-   * Returns {@link Variables} instance with build variables that only depend on the toolchain.
+   * Returns {@link com.google.devtools.build.lib.rules.cpp.CcToolchainVariables} instance with
+   * build variables that only depend on the toolchain.
    *
    * @param ruleContext the rule context
    * @param defaultSysroot the default sysroot
    * @throws RuleErrorException if there are configuration errors making it impossible to resolve
    *     certain build variables of this toolchain
    */
-  private final Variables getBuildVariables(RuleContext ruleContext, PathFragment defaultSysroot)
-      throws RuleErrorException {
-    Variables.Builder variables = new Variables.Builder();
+  private final CcToolchainVariables getBuildVariables(
+      RuleContext ruleContext, PathFragment defaultSysroot) throws RuleErrorException {
+    CcToolchainVariables.Builder variables = new CcToolchainVariables.Builder();
 
     PathFragment sysroot = calculateSysroot(ruleContext, defaultSysroot);
     if (sysroot != null) {
@@ -866,12 +866,13 @@
   }
 
   /**
-   * Add local build variables from subclasses into {@link Variables} returned from {@link
+   * Add local build variables from subclasses into {@link
+   * com.google.devtools.build.lib.rules.cpp.CcToolchainVariables} returned from {@link
    * #getBuildVariables(RuleContext, PathFragment)}.
    *
    * <p>This method is meant to be overridden by subclasses of CcToolchain.
    */
-  protected void addBuildVariables(RuleContext ruleContext, Variables.Builder variables)
+  protected void addBuildVariables(RuleContext ruleContext, CcToolchainVariables.Builder variables)
       throws RuleErrorException {
     // To be overridden in subclasses.
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java
index 2e215f5..5091a36 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java
@@ -17,7 +17,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
@@ -27,15 +26,13 @@
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-import com.google.common.collect.Streams;
-import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
-import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
-import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.Expandable;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SingleVariables;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringChunk;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringValueParser;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
@@ -50,13 +47,11 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.Stack;
 import java.util.concurrent.ExecutionException;
 import javax.annotation.Nullable;
 
@@ -107,228 +102,6 @@
       "Symbol %s is provided by all of the following features: %s";
 
   /**
-   * A piece of a single string value.
-   *
-   * <p>A single value can contain a combination of text and variables (for example "-f
-   * %{var1}/%{var2}"). We split the string into chunks, where each chunk represents either a text
-   * snippet, or a variable that is to be replaced.
-   */
-  interface StringChunk {
-    /**
-     * Expands this chunk.
-     *
-     * @param variables binding of variable names to their values for a single flag expansion.
-     */
-    String expand(Variables variables);
-  }
-
-  /** A plain text chunk of a string (containing no variables). */
-  @Immutable
-  @AutoCodec
-  @VisibleForSerialization
-  static class StringLiteralChunk implements StringChunk, Serializable {
-    private final String text;
-
-    @VisibleForSerialization
-    StringLiteralChunk(String text) {
-      this.text = text;
-    }
-
-    @Override
-    public String expand(Variables variables) {
-      return text;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object object) {
-      if (this == object) {
-        return true;
-      }
-      if (object instanceof StringLiteralChunk) {
-        StringLiteralChunk that = (StringLiteralChunk) object;
-        return text.equals(that.text);
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(text);
-    }
-  }
-
-  /** A chunk of a string value into which a variable should be expanded. */
-  @Immutable
-  @AutoCodec
-  static class VariableChunk implements StringChunk, Serializable {
-    private final String variableName;
-
-    @VisibleForSerialization
-    VariableChunk(String variableName) {
-      this.variableName = variableName;
-    }
-
-    @Override
-    public String expand(Variables variables) {
-      // We check all variables in FlagGroup.expandCommandLine.
-      // If we arrive here with the variable not being available, the variable was provided, but
-      // the nesting level of the NestedSequence was deeper than the nesting level of the flag
-      // groups.
-      return variables.getStringVariable(variableName);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object object) {
-      if (this == object) {
-        return true;
-      }
-      if (object instanceof VariableChunk) {
-        VariableChunk that = (VariableChunk) object;
-        return variableName.equals(that.variableName);
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(variableName);
-    }
-  }
-  
-  /**
-   * Parser for toolchain string values.
-   *
-   * <p>A string value contains a snippet of text supporting variable expansion. For example, a
-   * string value "-f %{var1}/%{var2}" will expand the values of the variables "var1" and "var2" in
-   * the corresponding places in the string.
-   *
-   * <p>The {@code StringValueParser} takes a string and parses it into a list of {@link
-   * StringChunk} objects, where each chunk represents either a snippet of text or a variable to be
-   * expanded. In the above example, the resulting chunks would be ["-f ", var1, "/", var2].
-   *
-   * <p>In addition to the list of chunks, the {@link StringValueParser} also provides the set of
-   * variables necessary for the expansion of this flag via {@link #getUsedVariables}.
-   *
-   * <p>To get a literal percent character, "%%" can be used in the string.
-   */
-  static class StringValueParser {
-
-    private final String value;
-    
-    /**
-     * The current position in {@value} during parsing.
-     */
-    private int current = 0;
-    
-    private final ImmutableList.Builder<StringChunk> chunks = ImmutableList.builder();
-    private final ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
-    
-    StringValueParser(String value) throws InvalidConfigurationException {
-      this.value = value;
-      parse();
-    }
-    
-    /** @return the parsed chunks for this string. */
-    ImmutableList<StringChunk> getChunks() {
-      return chunks.build();
-    }
-    
-    /** @return all variable names needed to expand this string. */
-    ImmutableSet<String> getUsedVariables() {
-      return usedVariables.build();
-    }
-    
-    /**
-     * Parses the string.
-     * 
-     * @throws InvalidConfigurationException if there is a parsing error.
-     */
-    private void parse() throws InvalidConfigurationException {
-      while (current < value.length()) {
-        if (atVariableStart()) {
-          parseVariableChunk();
-        } else {
-          parseStringChunk();
-        }
-      }
-    }
-    
-    /**
-     * @return whether the current position is the start of a variable.
-     */
-    private boolean atVariableStart() {
-      // We parse a variable when value starts with '%', but not '%%'.
-      return value.charAt(current) == '%'
-          && (current + 1 >= value.length() || value.charAt(current + 1) != '%');
-    }
-    
-    /**
-     * Parses a chunk of text until the next '%', which indicates either an escaped literal '%'
-     * or a variable. 
-     */
-    private void parseStringChunk() {
-      int start = current;
-      // We only parse string chunks starting with '%' if they also start with '%%'.
-      // In that case, we want to have a single '%' in the string, so we start at the second
-      // character.
-      // Note that for strings like "abc%%def" this will lead to two string chunks, the first
-      // referencing the subtring "abc", and a second referencing the substring "%def".
-      if (value.charAt(current) == '%') {
-        current = current + 1;
-        start = current;
-      }
-      current = value.indexOf('%', current + 1);
-      if (current == -1) {
-        current = value.length();
-      }
-      final String text = value.substring(start, current);
-      chunks.add(new StringLiteralChunk(text));
-    }
-    
-    /**
-     * Parses a variable to be expanded.
-     * 
-     * @throws InvalidConfigurationException if there is a parsing error.
-     */
-    private void parseVariableChunk() throws InvalidConfigurationException {
-      current = current + 1;
-      if (current >= value.length() || value.charAt(current) != '{') {
-        abort("expected '{'");
-      }
-      current = current + 1;
-      if (current >= value.length() || value.charAt(current) == '}') {
-        abort("expected variable name");
-      }
-      int end = value.indexOf('}', current);
-      final String name = value.substring(current, end);
-      usedVariables.add(name);
-      chunks.add(new VariableChunk(name));
-      current = end + 1;
-    }
-    
-    /**
-     * @throws InvalidConfigurationException with the given error text, adding information about
-     * the current position in the string.
-     */
-    private void abort(String error) throws InvalidConfigurationException {
-      throw new InvalidConfigurationException("Invalid toolchain configuration: " + error
-          + " at position " + current + " while parsing a flag containing '" + value + "'");
-    }
-  }
-
-  /** A flag or flag group that can be expanded under a set of variables. */
-  interface Expandable {
-    /**
-     * Expands the current expandable under the given {@code view}, adding new flags to {@code
-     * commandLine}.
-     *
-     * <p>The {@code variables} controls which variables are visible during the expansion and allows
-     * to recursively expand nested flag groups.
-     */
-    void expand(Variables variables, @Nullable ArtifactExpander expander, List<String> commandLine);
-  }
-
-  /**
    * A single flag to be expanded under a set of variables.
    */
   @Immutable
@@ -345,7 +118,9 @@
     /** Expand this flag into a single new entry in {@code commandLine}. */
     @Override
     public void expand(
-        Variables variables, @Nullable ArtifactExpander expander, List<String> commandLine) {
+        CcToolchainVariables variables,
+        @Nullable ArtifactExpander expander,
+        List<String> commandLine) {
       StringBuilder flag = new StringBuilder();
       for (StringChunk chunk : chunks) {
         flag.append(chunk.expand(variables));
@@ -392,7 +167,7 @@
 
       @Override
       public void expand(
-          Variables variables,
+          CcToolchainVariables variables,
           @Nullable ArtifactExpander artifactExpander,
           List<String> commandLine) {
         commandLine.add(chunk.expand(variables));
@@ -439,10 +214,11 @@
     }
 
     /**
-     * Adds the key/value pair this object represents to the given map of environment variables.
-     * The value of the entry is expanded with the given {@code variables}.
+     * Adds the key/value pair this object represents to the given map of environment variables. The
+     * value of the entry is expanded with the given {@code variables}.
      */
-    public void addEnvEntry(Variables variables, ImmutableMap.Builder<String, String> envBuilder) {
+    public void addEnvEntry(
+        CcToolchainVariables variables, ImmutableMap.Builder<String, String> envBuilder) {
       StringBuilder value = new StringBuilder();
       for (StringChunk chunk : valueChunks) {
         value.append(chunk.expand(variables));
@@ -554,14 +330,16 @@
 
     @Override
     public void expand(
-        Variables variables, @Nullable ArtifactExpander expander, final List<String> commandLine) {
+        CcToolchainVariables variables,
+        @Nullable ArtifactExpander expander,
+        final List<String> commandLine) {
       if (!canBeExpanded(variables, expander)) {
         return;
       }
       if (iterateOverVariable != null) {
-        for (Variables.VariableValue variableValue :
+        for (CcToolchainVariables.VariableValue variableValue :
             variables.getSequenceVariable(iterateOverVariable, expander)) {
-          Variables nestedVariables =
+          CcToolchainVariables nestedVariables =
               new SingleVariables(variables, iterateOverVariable, variableValue);
           for (Expandable expandable : expandables) {
             expandable.expand(nestedVariables, expander, commandLine);
@@ -574,7 +352,8 @@
       }
     }
 
-    private boolean canBeExpanded(Variables variables, @Nullable ArtifactExpander expander) {
+    private boolean canBeExpanded(
+        CcToolchainVariables variables, @Nullable ArtifactExpander expander) {
       for (String variable : expandIfAllAvailable) {
         if (!variables.isAvailable(variable, expander)) {
           return false;
@@ -622,7 +401,9 @@
      * </ul>
      */
     private void expandCommandLine(
-        Variables variables, @Nullable ArtifactExpander expander, final List<String> commandLine) {
+        CcToolchainVariables variables,
+        @Nullable ArtifactExpander expander,
+        final List<String> commandLine) {
       expand(variables, expander, commandLine);
     }
 
@@ -726,7 +507,7 @@
     /** Adds the flags that apply to the given {@code action} to {@code commandLine}. */
     private void expandCommandLine(
         String action,
-        Variables variables,
+        CcToolchainVariables variables,
         Set<String> enabledFeatureNames,
         @Nullable ArtifactExpander expander,
         List<String> commandLine) {
@@ -846,12 +627,12 @@
     }
 
     /**
-     * Adds the environment key/value pairs that apply to the given {@code action} to
-     * {@code envBuilder}.
+     * Adds the environment key/value pairs that apply to the given {@code action} to {@code
+     * envBuilder}.
      */
     private void expandEnvironment(
         String action,
-        Variables variables,
+        CcToolchainVariables variables,
         Set<String> enabledFeatureNames,
         ImmutableMap.Builder<String, String> envBuilder) {
       if (!actions.contains(action)) {
@@ -939,7 +720,7 @@
     /** Adds environment variables for the given action to the provided builder. */
     private void expandEnvironment(
         String action,
-        Variables variables,
+        CcToolchainVariables variables,
         Set<String> enabledFeatureNames,
         ImmutableMap.Builder<String, String> envBuilder) {
       for (EnvSet envSet : envSets) {
@@ -950,7 +731,7 @@
     /** Adds the flags that apply to the given {@code action} to {@code commandLine}. */
     private void expandCommandLine(
         String action,
-        Variables variables,
+        CcToolchainVariables variables,
         Set<String> enabledFeatureNames,
         @Nullable ArtifactExpander expander,
         List<String> commandLine) {
@@ -1140,7 +921,7 @@
 
     /** Adds the flags that apply to this action to {@code commandLine}. */
     private void expandCommandLine(
-        Variables variables,
+        CcToolchainVariables variables,
         Set<String> enabledFeatureNames,
         @Nullable ArtifactExpander expander,
         List<String> commandLine) {
@@ -1189,8 +970,8 @@
      */
     public String getArtifactName(Map<String, String> variables) {
       StringBuilder resultBuilder = new StringBuilder();
-      Variables artifactNameVariables =
-          new Variables.Builder().addAllStringVariables(variables).build();
+      CcToolchainVariables artifactNameVariables =
+          new CcToolchainVariables.Builder().addAllStringVariables(variables).build();
       for (StringChunk chunk : chunks) {
         resultBuilder.append(chunk.expand(artifactNameVariables));
       }
@@ -1199,924 +980,6 @@
     }
   }
 
-  /**
-   * Configured build variables usable by the toolchain configuration.
-   *
-   * <p>TODO(b/32655571): Investigate cleanup once implicit iteration is not needed. Variables
-   * instance could serve as a top level View used to expand all flag_groups.
-   */
-  @SkylarkModule(
-    name = "variables",
-    documented = false,
-    category = SkylarkModuleCategory.BUILTIN,
-    doc = "Class encapsulating build variables."
-  )
-  public abstract static class Variables {
-    /** An empty variables instance. */
-    public static final Variables EMPTY = new Variables.Builder().build();
-
-    /**
-     * Retrieves a {@link StringSequence} variable named {@code variableName} from {@code variables}
-     * and converts it into a list of plain strings.
-     *
-     * <p>Throws {@link ExpansionException} when the variable is not a {@link StringSequence}.
-     */
-    public static final ImmutableList<String> toStringList(
-        CcToolchainFeatures.Variables variables, String variableName) {
-      return Streams
-          .stream(variables.getSequenceVariable(variableName))
-          .map(variable -> variable.getStringValue(variableName))
-          .collect(ImmutableList.toImmutableList());
-    }
-
-    /**
-     * Get a variable value named @param name. Supports accessing fields in structures (e.g.
-     * 'libraries_to_link.interface_libraries')
-     *
-     * @throws ExpansionException when no such variable or no such field are present, or when
-     *     accessing a field of non-structured variable
-     */
-    VariableValue getVariable(String name) {
-      return lookupVariable(name, true, null);
-    }
-
-    VariableValue getVariable(String name, @Nullable ArtifactExpander expander) {
-      return lookupVariable(name, true, expander);
-    }
-
-    /**
-     * Lookup a variable named @param name or return a reason why the variable was not found.
-     * Supports accessing fields in structures.
-     *
-     * @return Pair<VariableValue, String> returns either (variable value, null) or (null, string
-     *     reason why variable was not found)
-     */
-    private VariableValue lookupVariable(
-        String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) {
-      VariableValue nonStructuredVariable = getNonStructuredVariable(name);
-      if (nonStructuredVariable != null) {
-        return nonStructuredVariable;
-      }
-      VariableValue structuredVariable =
-          getStructureVariable(name, throwOnMissingVariable, expander);
-      if (structuredVariable != null) {
-        return structuredVariable;
-      } else if (throwOnMissingVariable) {
-        throw new ExpansionException(
-            String.format(
-                "Invalid toolchain configuration: Cannot find variable named '%s'.", name));
-      } else {
-        return null;
-      }
-    }
-
-    private VariableValue getStructureVariable(
-        String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) {
-      if (!name.contains(".")) {
-        return null;
-      }
-
-      Stack<String> fieldsToAccess = new Stack<>();
-      String structPath = name;
-      VariableValue variable;
-
-      do {
-        fieldsToAccess.push(structPath.substring(structPath.lastIndexOf('.') + 1));
-        structPath = structPath.substring(0, structPath.lastIndexOf('.'));
-        variable = getNonStructuredVariable(structPath);
-      } while (variable == null && structPath.contains("."));
-
-      if (variable == null) {
-        return null;
-      }
-
-      while (!fieldsToAccess.empty()) {
-        String field = fieldsToAccess.pop();
-        variable = variable.getFieldValue(structPath, field, expander);
-        if (variable == null) {
-          if (throwOnMissingVariable) {
-            throw new ExpansionException(
-                String.format(
-                    "Invalid toolchain configuration: Cannot expand variable '%s.%s': structure %s "
-                        + "doesn't have a field named '%s'",
-                    structPath, field, structPath, field));
-          } else {
-            return null;
-          }
-        }
-      }
-      return variable;
-    }
-
-    public String getStringVariable(String variableName) {
-      return getVariable(variableName, null).getStringValue(variableName);
-    }
-
-    public Iterable<? extends VariableValue> getSequenceVariable(String variableName) {
-      return getVariable(variableName, null).getSequenceValue(variableName);
-    }
-
-    public Iterable<? extends VariableValue> getSequenceVariable(
-        String variableName, @Nullable ArtifactExpander expander) {
-      return getVariable(variableName, expander).getSequenceValue(variableName);
-    }
-
-    /** Returns whether {@code variable} is set. */
-    boolean isAvailable(String variable) {
-      return isAvailable(variable, null);
-    }
-
-    boolean isAvailable(String variable, @Nullable ArtifactExpander expander) {
-      return lookupVariable(variable, false, expander) != null;
-    }
-
-    abstract Map<String, VariableValue> getVariablesMap();
-
-    abstract Map<String, String> getStringVariablesMap();
-
-    @Nullable
-    abstract VariableValue getNonStructuredVariable(String name);
-
-    /**
-     * Value of a build variable exposed to the CROSSTOOL used for flag expansion.
-     *
-     * <p>{@link VariableValue} represent either primitive values or an arbitrarily deeply nested
-     * recursive structures or sequences. Since there are builds with millions of values, some
-     * implementations might exist only to optimize memory usage.
-     *
-     * <p>Implementations must be immutable and without any side-effects. They will be expanded and
-     * queried multiple times.
-     */
-    interface VariableValue {
-      /**
-       * Returns string value of the variable, if the variable type can be converted to string (e.g.
-       * StringValue), or throw exception if it cannot (e.g. Sequence).
-       *
-       * @param variableName name of the variable value at hand, for better exception message.
-       */
-      String getStringValue(String variableName);
-
-      /**
-       * Returns Iterable value of the variable, if the variable type can be converted to a Iterable
-       * (e.g. Sequence), or throw exception if it cannot (e.g. StringValue).
-       *
-       * @param variableName name of the variable value at hand, for better exception message.
-       */
-      Iterable<? extends VariableValue> getSequenceValue(String variableName);
-
-      /**
-       * Returns value of the field, if the variable is of struct type or throw exception if it is
-       * not or no such field exists.
-       *
-       * @param variableName name of the variable value at hand, for better exception message.
-       */
-      VariableValue getFieldValue(String variableName, String field);
-
-      VariableValue getFieldValue(
-          String variableName, String field, @Nullable ArtifactExpander expander);
-
-      /** Returns true if the variable is truthy */
-      boolean isTruthy();
-    }
-
-    /**
-     * Adapter for {@link VariableValue} predefining error handling methods. Override {@link
-     * #getVariableTypeName()}, {@link #isTruthy()}, and one of {@link #getFieldValue(String,
-     * String)}, {@link #getSequenceValue(String)}, or {@link #getStringValue(String)}, and you'll
-     * get error handling for the other methods for free.
-     */
-    abstract static class VariableValueAdapter implements VariableValue {
-
-      /** Returns human-readable variable type name to be used in error messages. */
-      public abstract String getVariableTypeName();
-
-      @Override
-      public abstract boolean isTruthy();
-
-      @Override
-      public VariableValue getFieldValue(String variableName, String field) {
-        return getFieldValue(variableName, field, null);
-      }
-
-      @Override
-      public VariableValue getFieldValue(
-          String variableName, String field, @Nullable ArtifactExpander expander) {
-        throw new ExpansionException(
-            String.format(
-                "Invalid toolchain configuration: Cannot expand variable '%s.%s': variable '%s' is "
-                    + "%s, expected structure",
-                variableName, field, variableName, getVariableTypeName()));
-      }
-
-      @Override
-      public String getStringValue(String variableName) {
-        throw new ExpansionException(
-            String.format(
-                "Invalid toolchain configuration: Cannot expand variable '%s': expected string, "
-                    + "found %s",
-                variableName, getVariableTypeName()));
-      }
-
-      @Override
-      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
-        throw new ExpansionException(
-            String.format(
-                "Invalid toolchain configuration: Cannot expand variable '%s': expected sequence, "
-                    + "found %s",
-                variableName, getVariableTypeName()));
-      }
-    }
-
-    /** Interface for VariableValue builders */
-    public interface VariableValueBuilder {
-      VariableValue build();
-    }
-
-    /** Builder for StringSequence. */
-    public static class StringSequenceBuilder implements VariableValueBuilder {
-
-      private final ImmutableList.Builder<String> values = ImmutableList.builder();
-
-      /** Adds a value to the sequence. */
-      public StringSequenceBuilder addValue(String value) {
-        values.add(value);
-        return this;
-      }
-
-      /** Returns an immutable string sequence. */
-      @Override
-      public StringSequence build() {
-        return new StringSequence(values.build());
-      }
-    }
-
-    /** Builder for Sequence. */
-    public static class SequenceBuilder implements VariableValueBuilder {
-
-      private final ImmutableList.Builder<VariableValue> values = ImmutableList.builder();
-
-      /** Adds a value to the sequence. */
-      public SequenceBuilder addValue(VariableValue value) {
-        values.add(value);
-        return this;
-      }
-
-      /** Adds a value to the sequence. */
-      public SequenceBuilder addValue(VariableValueBuilder value) {
-        Preconditions.checkArgument(value != null, "Cannot use null builder for a sequence value");
-        values.add(value.build());
-        return this;
-      }
-
-      /** Returns an immutable sequence. */
-      @Override
-      public Sequence build() {
-        return new Sequence(values.build());
-      }
-    }
-
-    /** Builder for StructureValue. */
-    public static class StructureBuilder implements VariableValueBuilder {
-
-      private final ImmutableMap.Builder<String, VariableValue> fields = ImmutableMap.builder();
-
-      /** Adds a field to the structure. */
-      public StructureBuilder addField(String name, VariableValue value) {
-        fields.put(name, value);
-        return this;
-      }
-
-      /** Adds a field to the structure. */
-      public StructureBuilder addField(String name, VariableValueBuilder valueBuilder) {
-        Preconditions.checkArgument(
-            valueBuilder != null,
-            "Cannot use null builder to get a field value for field '%s'",
-            name);
-        fields.put(name, valueBuilder.build());
-        return this;
-      }
-
-      /** Adds a field to the structure. */
-      public StructureBuilder addField(String name, String value) {
-        fields.put(name, new StringValue(value));
-        return this;
-      }
-
-      /** Adds a field to the structure. */
-      public StructureBuilder addField(String name, ImmutableList<String> values) {
-        fields.put(name, new StringSequence(values));
-        return this;
-      }
-
-      /** Returns an immutable structure. */
-      @Override
-      public StructureValue build() {
-        return new StructureValue(fields.build());
-      }
-    }
-
-    /**
-     * Lazily computed string sequence. Exists as a memory optimization. Make sure the {@param
-     * supplier} doesn't capture anything that shouldn't outlive analysis phase (e.g. {@link
-     * RuleContext}).
-     */
-    @AutoCodec
-    @VisibleForSerialization
-    static final class LazyStringSequence extends VariableValueAdapter {
-      private final Supplier<ImmutableList<String>> supplier;
-
-      @VisibleForSerialization
-      LazyStringSequence(Supplier<ImmutableList<String>> supplier) {
-        this.supplier = Preconditions.checkNotNull(supplier);
-      }
-
-      @Override
-      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
-        return supplier
-            .get()
-            .stream()
-            .map(flag -> new StringValue(flag))
-            .collect(ImmutableList.toImmutableList());
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return !supplier.get().isEmpty();
-      }
-    }
-
-    /**
-     * A sequence of structure values. Exists as a memory optimization - a typical build can contain
-     * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects
-     * significantly reduces memory overhead.
-     */
-    @Immutable
-    @AutoCodec
-    public static class LibraryToLinkValue extends VariableValueAdapter {
-      public static final String OBJECT_FILES_FIELD_NAME = "object_files";
-      public static final String NAME_FIELD_NAME = "name";
-      public static final String TYPE_FIELD_NAME = "type";
-      public static final String IS_WHOLE_ARCHIVE_FIELD_NAME = "is_whole_archive";
-
-      private static final String LIBRARY_TO_LINK_VARIABLE_TYPE_NAME = "structure (LibraryToLink)";
-
-      @VisibleForSerialization
-      enum Type {
-        OBJECT_FILE("object_file"),
-        OBJECT_FILE_GROUP("object_file_group"),
-        INTERFACE_LIBRARY("interface_library"),
-        STATIC_LIBRARY("static_library"),
-        DYNAMIC_LIBRARY("dynamic_library"),
-        VERSIONED_DYNAMIC_LIBRARY("versioned_dynamic_library");
-
-        private final String name;
-
-        Type(String name) {
-          this.name = name;
-        }
-      }
-
-      private final String name;
-      private final ImmutableList<Artifact> objectFiles;
-      private final boolean isWholeArchive;
-      private final Type type;
-
-      public static LibraryToLinkValue forDynamicLibrary(String name) {
-        return new LibraryToLinkValue(
-            Preconditions.checkNotNull(name), null, false, Type.DYNAMIC_LIBRARY);
-      }
-
-      public static LibraryToLinkValue forVersionedDynamicLibrary(
-          String name) {
-        return new LibraryToLinkValue(
-            Preconditions.checkNotNull(name), null, false, Type.VERSIONED_DYNAMIC_LIBRARY);
-      }
-
-      public static LibraryToLinkValue forInterfaceLibrary(String name) {
-        return new LibraryToLinkValue(
-            Preconditions.checkNotNull(name), null, false, Type.INTERFACE_LIBRARY);
-      }
-
-      public static LibraryToLinkValue forStaticLibrary(String name, boolean isWholeArchive) {
-        return new LibraryToLinkValue(
-            Preconditions.checkNotNull(name), null, isWholeArchive, Type.STATIC_LIBRARY);
-      }
-
-      public static LibraryToLinkValue forObjectFile(String name, boolean isWholeArchive) {
-        return new LibraryToLinkValue(
-            Preconditions.checkNotNull(name), null, isWholeArchive, Type.OBJECT_FILE);
-      }
-
-      public static LibraryToLinkValue forObjectFileGroup(
-          ImmutableList<Artifact> objects, boolean isWholeArchive) {
-        Preconditions.checkNotNull(objects);
-        Preconditions.checkArgument(!objects.isEmpty());
-        return new LibraryToLinkValue(null, objects, isWholeArchive, Type.OBJECT_FILE_GROUP);
-      }
-
-      @VisibleForSerialization
-      LibraryToLinkValue(
-          String name, ImmutableList<Artifact> objectFiles, boolean isWholeArchive, Type type) {
-        this.name = name;
-        this.objectFiles = objectFiles;
-        this.isWholeArchive = isWholeArchive;
-        this.type = type;
-      }
-
-      @Override
-      public VariableValue getFieldValue(
-          String variableName, String field, @Nullable ArtifactExpander expander) {
-        Preconditions.checkNotNull(field);
-        if (NAME_FIELD_NAME.equals(field) && !type.equals(Type.OBJECT_FILE_GROUP)) {
-          return new StringValue(name);
-        } else if (OBJECT_FILES_FIELD_NAME.equals(field) && type.equals(Type.OBJECT_FILE_GROUP)) {
-          ImmutableList.Builder<String> expandedObjectFiles = ImmutableList.builder();
-          for (Artifact objectFile : objectFiles) {
-            if (objectFile.isTreeArtifact() && (expander != null)) {
-              List<Artifact> artifacts = new ArrayList<>();
-              expander.expand(objectFile, artifacts);
-              expandedObjectFiles.addAll(
-                  Iterables.transform(artifacts, artifact -> artifact.getExecPathString()));
-            } else {
-              expandedObjectFiles.add(objectFile.getExecPathString());
-            }
-          }
-          return new StringSequence(expandedObjectFiles.build());
-        } else if (TYPE_FIELD_NAME.equals(field)) {
-          return new StringValue(type.name);
-        } else if (IS_WHOLE_ARCHIVE_FIELD_NAME.equals(field)) {
-          return new IntegerValue(isWholeArchive ? 1 : 0);
-        } else {
-          return null;
-        }
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return LIBRARY_TO_LINK_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return true;
-      }
-    }
-
-    /** Sequence of arbitrary VariableValue objects. */
-    @Immutable
-    @AutoCodec
-    @VisibleForSerialization
-    static final class Sequence extends VariableValueAdapter {
-      private static final String SEQUENCE_VARIABLE_TYPE_NAME = "sequence";
-
-      private final ImmutableList<VariableValue> values;
-
-      public Sequence(ImmutableList<VariableValue> values) {
-        this.values = values;
-      }
-
-      @Override
-      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
-        return values;
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return SEQUENCE_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return values.isEmpty();
-      }
-    }
-
-    /**
-     * A sequence of structure values. Exists as a memory optimization - a typical build can contain
-     * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects
-     * significantly reduces memory overhead.
-     */
-    @Immutable
-    @AutoCodec
-    @VisibleForSerialization
-    static final class StructureSequence extends VariableValueAdapter {
-      private final ImmutableList<ImmutableMap<String, VariableValue>> values;
-
-      @VisibleForSerialization
-      StructureSequence(ImmutableList<ImmutableMap<String, VariableValue>> values) {
-        Preconditions.checkNotNull(values);
-        this.values = values;
-      }
-
-      @Override
-      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
-        final ImmutableList.Builder<VariableValue> sequences = ImmutableList.builder();
-        for (ImmutableMap<String, VariableValue> value : values) {
-          sequences.add(new StructureValue(value));
-        }
-        return sequences.build();
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return !values.isEmpty();
-      }
-    }
-
-    /**
-     * A sequence of simple string values. Exists as a memory optimization - a typical build can
-     * contain millions of feature values, so getting rid of the overhead of {@code StringValue}
-     * objects significantly reduces memory overhead.
-     */
-    @Immutable
-    @AutoCodec
-    static final class StringSequence extends VariableValueAdapter {
-      private final Iterable<String> values;
-
-      public StringSequence(Iterable<String> values) {
-        Preconditions.checkNotNull(values);
-        this.values = values;
-      }
-
-      @Override
-      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
-        final ImmutableList.Builder<VariableValue> sequences = ImmutableList.builder();
-        for (String value : values) {
-          sequences.add(new StringValue(value));
-        }
-        return sequences.build();
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return !Iterables.isEmpty(values);
-      }
-    }
-
-    /**
-     * Single structure value. Be careful not to create sequences of single structures, as the
-     * memory overhead is prohibitively big. Use optimized {@link StructureSequence} instead.
-     */
-    @Immutable
-    @AutoCodec
-    @VisibleForSerialization
-    static final class StructureValue extends VariableValueAdapter {
-      private static final String STRUCTURE_VARIABLE_TYPE_NAME = "structure";
-
-      private final ImmutableMap<String, VariableValue> value;
-
-      public StructureValue(ImmutableMap<String, VariableValue> value) {
-        this.value = value;
-      }
-
-      @Override
-      public VariableValue getFieldValue(
-          String variableName, String field, @Nullable ArtifactExpander expander) {
-        if (value.containsKey(field)) {
-          return value.get(field);
-        } else {
-          return null;
-        }
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return STRUCTURE_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return !value.isEmpty();
-      }
-    }
-
-    /**
-     * The leaves in the variable sequence node tree are simple string values. Note that this should
-     * never live outside of {@code expand}, as the object overhead is prohibitively expensive.
-     */
-    @Immutable
-    @AutoCodec
-    @VisibleForSerialization
-    static final class StringValue extends VariableValueAdapter {
-      private static final String STRING_VARIABLE_TYPE_NAME = "string";
-
-      private final String value;
-
-      public StringValue(String value) {
-        Preconditions.checkNotNull(value, "Cannot create StringValue from null");
-        this.value = value;
-      }
-
-      @Override
-      public String getStringValue(String variableName) {
-        return value;
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return STRING_VARIABLE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return !value.isEmpty();
-      }
-    }
-
-    /**
-     * The leaves in the variable sequence node tree are simple integer values. Note that this
-     * should never live outside of {@code expand}, as the object overhead is prohibitively
-     * expensive.
-     */
-    @Immutable
-    @AutoCodec
-    static final class IntegerValue extends VariableValueAdapter {
-      private static final String INTEGER_VALUE_TYPE_NAME = "integer";
-      private final int value;
-
-      public IntegerValue(int value) {
-        this.value = value;
-      }
-
-      @Override
-      public String getStringValue(String variableName) {
-        return Integer.toString(value);
-      }
-
-      @Override
-      public String getVariableTypeName() {
-        return INTEGER_VALUE_TYPE_NAME;
-      }
-
-      @Override
-      public boolean isTruthy() {
-        return value != 0;
-      }
-    }
-
-    /**
-     * Builder for {@code Variables}.
-     */
-    // TODO(b/65472725): Forbid sequences with empty string in them.
-    public static class Builder {
-      private final Map<String, VariableValue> variablesMap = new LinkedHashMap<>();
-      private final Map<String, String> stringVariablesMap = new LinkedHashMap<>();
-      private final Variables parent;
-
-      public Builder() {
-        parent = null;
-      }
-
-      public Builder(@Nullable Variables parent) {
-        this.parent = parent;
-      }
-
-      /** Add an integer variable that expands {@code name} to {@code value}. */
-      public Builder addIntegerVariable(String name, int value) {
-        variablesMap.put(name, new IntegerValue(value));
-        return this;
-      }
-
-      /** Add a string variable that expands {@code name} to {@code value}. */
-      public Builder addStringVariable(String name, String value) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(
-            value, "Cannot set null as a value for variable '%s'", name);
-        stringVariablesMap.put(name, value);
-        return this;
-      }
-
-      /** Overrides a variable to expands {@code name} to {@code value} instead. */
-      public Builder overrideStringVariable(String name, String value) {
-        Preconditions.checkNotNull(
-            value, "Cannot set null as a value for variable '%s'", name);
-        stringVariablesMap.put(name, value);
-        return this;
-      }
-
-      /** Overrides a variable to expands {@code name} to {@code value} instead. */
-      public Builder overrideLazyStringSequenceVariable(
-          String name, Supplier<ImmutableList<String>> supplier) {
-        Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name);
-        variablesMap.put(name, new LazyStringSequence(supplier));
-        return this;
-      }
-
-      /**
-       * Add a sequence variable that expands {@code name} to {@code values}.
-       *
-       * <p>Accepts values as ImmutableSet. As ImmutableList has smaller memory footprint, we copy
-       * the values into a new list.
-       */
-      public Builder addStringSequenceVariable(String name, ImmutableSet<String> values) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
-        ImmutableList.Builder<String> builder = ImmutableList.builder();
-        builder.addAll(values);
-        variablesMap.put(name, new StringSequence(builder.build()));
-        return this;
-      }
-
-      /**
-       * Add a sequence variable that expands {@code name} to {@code values}.
-       *
-       * <p>Accepts values as NestedSet. Nested set is stored directly, not cloned, not flattened.
-       */
-      public Builder addStringSequenceVariable(String name, NestedSet<String> values) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
-        variablesMap.put(name, new StringSequence(values));
-        return this;
-      }
-
-      /**
-       * Add a sequence variable that expands {@code name} to {@code values}.
-       *
-       * <p>Accepts values as Iterable. The iterable is stored directly, not cloned, not iterated.
-       * Be mindful of memory consumption of the particular Iterable. Prefer ImmutableList, or
-       * be sure that the iterable always returns the same elements in the same order, without any
-       * side effects.
-       */
-      public Builder addStringSequenceVariable(String name, Iterable<String> values) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
-        variablesMap.put(name, new StringSequence(values));
-        return this;
-      }
-
-      public Builder addLazyStringSequenceVariable(
-          String name, Supplier<ImmutableList<String>> supplier) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name);
-        variablesMap.put(name, new LazyStringSequence(supplier));
-        return this;
-      }
-
-      /**
-       * Add a variable built using {@code VariableValueBuilder} api that expands {@code name} to
-       * the value returned by the {@code builder}.
-       */
-      public Builder addCustomBuiltVariable(String name, Variables.VariableValueBuilder builder) {
-        checkVariableNotPresentAlready(name);
-        Preconditions.checkNotNull(
-            builder,
-            "Cannot use null builder to get variable value for variable '%s'",
-            name);
-        variablesMap.put(name, builder.build());
-        return this;
-      }
-
-      /** Add all string variables in a map. */
-      public Builder addAllStringVariables(Map<String, String> variables) {
-        for (String name : variables.keySet()) {
-          checkVariableNotPresentAlready(name);
-        }
-        stringVariablesMap.putAll(variables);
-        return this;
-      }
-
-      private void checkVariableNotPresentAlready(String name) {
-        Preconditions.checkNotNull(name);
-        Preconditions.checkArgument(
-            !variablesMap.containsKey(name), "Cannot overwrite variable '%s'", name);
-        Preconditions.checkArgument(
-            !stringVariablesMap.containsKey(name), "Cannot overwrite variable '%s'", name);
-      }
-
-      /**
-       * Adds all variables to this builder. Cannot override already added variables. Does not add
-       * variables defined in the {@code parent} variables.
-       */
-      public Builder addAllNonTransitive(Variables variables) {
-        SetView<String> intersection =
-            Sets.intersection(variables.getVariablesMap().keySet(), variablesMap.keySet());
-        SetView<String> stringIntersection =
-            Sets.intersection(
-                variables.getStringVariablesMap().keySet(), stringVariablesMap.keySet());
-        Preconditions.checkArgument(
-            intersection.isEmpty(), "Cannot overwrite existing variables: %s", intersection);
-        Preconditions.checkArgument(
-            stringIntersection.isEmpty(),
-            "Cannot overwrite existing variables: %s", stringIntersection);
-        this.variablesMap.putAll(variables.getVariablesMap());
-        this.stringVariablesMap.putAll(variables.getStringVariablesMap());
-        return this;
-      }
-
-      /** @return a new {@link Variables} object. */
-      public Variables build() {
-        if (stringVariablesMap.isEmpty() && variablesMap.size() == 1) {
-          return new SingleVariables(
-              parent,
-              variablesMap.keySet().iterator().next(),
-              variablesMap.values().iterator().next());
-        }
-        return new MapVariables(
-            parent, ImmutableMap.copyOf(variablesMap), ImmutableMap.copyOf(stringVariablesMap));
-      }
-    }
-    
-    /**
-     * A group of extra {@code Variable} instances, packaged as logic for adding to a
-     * {@code Builder}
-     */
-    public interface VariablesExtension {
-      void addVariables(Builder builder);
-    }
-  }
-
-  @Immutable
-  @AutoCodec.VisibleForSerialization
-  @AutoCodec
-  static class MapVariables extends Variables {
-    private final ImmutableMap<String, VariableValue> variablesMap;
-    private final ImmutableMap<String, String> stringVariablesMap;
-    private final Variables parent;
-
-    @AutoCodec.Instantiator
-    @VisibleForSerialization
-    MapVariables(
-        Variables parent,
-        ImmutableMap<String, VariableValue> variablesMap,
-        ImmutableMap<String, String> stringVariablesMap) {
-      this.variablesMap = variablesMap;
-      this.stringVariablesMap = stringVariablesMap;
-      this.parent = parent;
-    }
-
-    @Override
-    Map<String, VariableValue> getVariablesMap() {
-      return variablesMap;
-    }
-
-    @Override
-    Map<String, String> getStringVariablesMap() {
-      return stringVariablesMap;
-    }
-
-    @Override
-    VariableValue getNonStructuredVariable(String name) {
-      if (variablesMap.containsKey(name)) {
-        return variablesMap.get(name);
-      }
-      if (stringVariablesMap.containsKey(name)) {
-        return new StringValue(stringVariablesMap.get(name));
-      }
-
-      if (parent != null) {
-        return parent.getNonStructuredVariable(name);
-      }
-
-      return null;
-    }
-  }
-
-  @Immutable
-  private static class SingleVariables extends Variables {
-    private final Variables parent;
-    private final String name;
-    private final VariableValue variableValue;
-
-    SingleVariables(Variables parent, String name, VariableValue variableValue) {
-      this.parent = parent;
-      this.name = name;
-      this.variableValue = variableValue;
-    }
-
-    @Override
-    Map<String, VariableValue> getVariablesMap() {
-      return ImmutableMap.of(name, variableValue);
-    }
-
-    @Override
-    Map<String, String> getStringVariablesMap() {
-      return ImmutableMap.of();
-    }
-
-    @Override
-    VariableValue getNonStructuredVariable(String name) {
-      if (this.name.equals(name)) {
-        return variableValue;
-      }
-      return parent == null ? null : parent.getNonStructuredVariable(name);
-    }
-  }
-
   /** Captures the set of enabled features and action configs for a rule. */
   @Immutable
   @AutoCodec
@@ -2178,12 +1041,12 @@
     }
 
     /** @return the command line for the given {@code action}. */
-    public List<String> getCommandLine(String action, Variables variables) {
+    public List<String> getCommandLine(String action, CcToolchainVariables variables) {
       return getCommandLine(action, variables, null);
     }
 
     public List<String> getCommandLine(
-        String action, Variables variables, @Nullable ArtifactExpander expander) {
+        String action, CcToolchainVariables variables, @Nullable ArtifactExpander expander) {
       List<String> commandLine = new ArrayList<>();
       if (actionIsConfigured(action)) {
         actionConfigByActionName
@@ -2200,12 +1063,12 @@
 
     /** @return the flags expanded for the given {@code action} in per-feature buckets. */
     public ImmutableList<Pair<String, List<String>>> getPerFeatureExpansions(
-        String action, Variables variables) {
+        String action, CcToolchainVariables variables) {
       return getPerFeatureExpansions(action, variables, null);
     }
 
     public ImmutableList<Pair<String, List<String>>> getPerFeatureExpansions(
-        String action, Variables variables, @Nullable ArtifactExpander expander) {
+        String action, CcToolchainVariables variables, @Nullable ArtifactExpander expander) {
       ImmutableList.Builder<Pair<String, List<String>>> perFeatureExpansions =
           ImmutableList.builder();
       if (actionIsConfigured(action)) {
@@ -2226,7 +1089,7 @@
 
     /** @return the environment variables (key/value pairs) for the given {@code action}. */
     public ImmutableMap<String, String> getEnvironmentVariables(
-        String action, Variables variables) {
+        String action, CcToolchainVariables variables) {
       ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder();
       for (Feature feature : enabledFeatures) {
         feature.expandEnvironment(action, variables, enabledFeatureNames, envBuilder);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
index 66650d0..df328fb 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
@@ -29,7 +29,6 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
 import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoMode;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode;
@@ -78,7 +77,7 @@
           CcCompilationContext.EMPTY,
           /* supportsParamFiles= */ false,
           /* supportsHeaderParsing= */ false,
-          Variables.EMPTY,
+          CcToolchainVariables.EMPTY,
           /* builtinIncludeFiles= */ ImmutableList.<Artifact>of(),
           /* coverageEnvironment= */ NestedSetBuilder.emptySet(Order.COMPILE_ORDER),
           /* linkDynamicLibraryTool= */ null,
@@ -112,7 +111,7 @@
   private final CcCompilationContext ccCompilationContext;
   private final boolean supportsParamFiles;
   private final boolean supportsHeaderParsing;
-  private final Variables buildVariables;
+  private final CcToolchainVariables buildVariables;
   private final ImmutableList<Artifact> builtinIncludeFiles;
   private final NestedSet<Pair<String, String>> coverageEnvironment;
   @Nullable private final Artifact linkDynamicLibraryTool;
@@ -150,7 +149,7 @@
       CcCompilationContext ccCompilationContext,
       boolean supportsParamFiles,
       boolean supportsHeaderParsing,
-      Variables buildVariables,
+      CcToolchainVariables buildVariables,
       ImmutableList<Artifact> builtinIncludeFiles,
       NestedSet<Pair<String, String>> coverageEnvironment,
       Artifact linkDynamicLibraryTool,
@@ -548,7 +547,7 @@
   }
 
   /** Returns build variables to be templated into the crosstool. */
-  public Variables getBuildVariables() {
+  public CcToolchainVariables getBuildVariables() {
     return buildVariables;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java
new file mode 100644
index 0000000..093837c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java
@@ -0,0 +1,1204 @@
+// Copyright 2018 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.build.lib.rules.cpp;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import com.google.common.collect.Streams;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Stack;
+import javax.annotation.Nullable;
+
+/**
+ * Configured build variables usable by the toolchain configuration.
+ *
+ * <p>TODO(b/32655571): Investigate cleanup once implicit iteration is not needed. Variables
+ * instance could serve as a top level View used to expand all flag_groups.
+ */
+@SkylarkModule(
+    name = "variables",
+    documented = false,
+    category = SkylarkModuleCategory.BUILTIN,
+    doc = "Class encapsulating build variables.")
+public abstract class CcToolchainVariables {
+
+  /**
+   * A piece of a single string value.
+   *
+   * <p>A single value can contain a combination of text and variables (for example "-f
+   * %{var1}/%{var2}"). We split the string into chunks, where each chunk represents either a text
+   * snippet, or a variable that is to be replaced.
+   */
+  interface StringChunk {
+    /**
+     * Expands this chunk.
+     *
+     * @param variables binding of variable names to their values for a single flag expansion.
+     */
+    String expand(CcToolchainVariables variables);
+  }
+
+  /** A plain text chunk of a string (containing no variables). */
+  @Immutable
+  @AutoCodec
+  @VisibleForSerialization
+  static class StringLiteralChunk implements StringChunk, Serializable {
+    private final String text;
+
+    @VisibleForSerialization
+    StringLiteralChunk(String text) {
+      this.text = text;
+    }
+
+    @Override
+    public String expand(CcToolchainVariables variables) {
+      return text;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (object instanceof StringLiteralChunk) {
+        StringLiteralChunk that = (StringLiteralChunk) object;
+        return text.equals(that.text);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(text);
+    }
+  }
+
+  /** A chunk of a string value into which a variable should be expanded. */
+  @Immutable
+  @AutoCodec
+  static class VariableChunk implements StringChunk, Serializable {
+    private final String variableName;
+
+    @VisibleForSerialization
+    VariableChunk(String variableName) {
+      this.variableName = variableName;
+    }
+
+    @Override
+    public String expand(CcToolchainVariables variables) {
+      // We check all variables in FlagGroup.expandCommandLine.
+      // If we arrive here with the variable not being available, the variable was provided, but
+      // the nesting level of the NestedSequence was deeper than the nesting level of the flag
+      // groups.
+      return variables.getStringVariable(variableName);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (object instanceof VariableChunk) {
+        VariableChunk that = (VariableChunk) object;
+        return variableName.equals(that.variableName);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(variableName);
+    }
+  }
+
+  /**
+   * Parser for toolchain string values.
+   *
+   * <p>A string value contains a snippet of text supporting variable expansion. For example, a
+   * string value "-f %{var1}/%{var2}" will expand the values of the variables "var1" and "var2" in
+   * the corresponding places in the string.
+   *
+   * <p>The {@code StringValueParser} takes a string and parses it into a list of {@link
+   * StringChunk} objects, where each chunk represents either a snippet of text or a variable to be
+   * expanded. In the above example, the resulting chunks would be ["-f ", var1, "/", var2].
+   *
+   * <p>In addition to the list of chunks, the {@link StringValueParser} also provides the set of
+   * variables necessary for the expansion of this flag via {@link #getUsedVariables}.
+   *
+   * <p>To get a literal percent character, "%%" can be used in the string.
+   */
+  static class StringValueParser {
+
+    private final String value;
+
+    /**
+     * The current position in {@value} during parsing.
+     */
+    private int current = 0;
+
+    private final ImmutableList.Builder<StringChunk> chunks = ImmutableList.builder();
+    private final ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
+
+    StringValueParser(String value) throws InvalidConfigurationException {
+      this.value = value;
+      parse();
+    }
+
+    /** @return the parsed chunks for this string. */
+    ImmutableList<StringChunk> getChunks() {
+      return chunks.build();
+    }
+
+    /** @return all variable names needed to expand this string. */
+    ImmutableSet<String> getUsedVariables() {
+      return usedVariables.build();
+    }
+
+    /**
+     * Parses the string.
+     *
+     * @throws InvalidConfigurationException if there is a parsing error.
+     */
+    private void parse() throws InvalidConfigurationException {
+      while (current < value.length()) {
+        if (atVariableStart()) {
+          parseVariableChunk();
+        } else {
+          parseStringChunk();
+        }
+      }
+    }
+
+    /**
+     * @return whether the current position is the start of a variable.
+     */
+    private boolean atVariableStart() {
+      // We parse a variable when value starts with '%', but not '%%'.
+      return value.charAt(current) == '%'
+          && (current + 1 >= value.length() || value.charAt(current + 1) != '%');
+    }
+
+    /**
+     * Parses a chunk of text until the next '%', which indicates either an escaped literal '%' or a
+     * variable.
+     */
+    private void parseStringChunk() {
+      int start = current;
+      // We only parse string chunks starting with '%' if they also start with '%%'.
+      // In that case, we want to have a single '%' in the string, so we start at the second
+      // character.
+      // Note that for strings like "abc%%def" this will lead to two string chunks, the first
+      // referencing the subtring "abc", and a second referencing the substring "%def".
+      if (value.charAt(current) == '%') {
+        current = current + 1;
+        start = current;
+      }
+      current = value.indexOf('%', current + 1);
+      if (current == -1) {
+        current = value.length();
+      }
+      final String text = value.substring(start, current);
+      chunks.add(new StringLiteralChunk(text));
+    }
+
+    /**
+     * Parses a variable to be expanded.
+     *
+     * @throws InvalidConfigurationException if there is a parsing error.
+     */
+    private void parseVariableChunk() throws InvalidConfigurationException {
+      current = current + 1;
+      if (current >= value.length() || value.charAt(current) != '{') {
+        abort("expected '{'");
+      }
+      current = current + 1;
+      if (current >= value.length() || value.charAt(current) == '}') {
+        abort("expected variable name");
+      }
+      int end = value.indexOf('}', current);
+      final String name = value.substring(current, end);
+      usedVariables.add(name);
+      chunks.add(new VariableChunk(name));
+      current = end + 1;
+    }
+
+    /**
+     * @throws InvalidConfigurationException with the given error text, adding information about
+     * the current position in the string.
+     */
+    private void abort(String error) throws InvalidConfigurationException {
+      throw new InvalidConfigurationException("Invalid toolchain configuration: " + error
+          + " at position " + current + " while parsing a flag containing '" + value + "'");
+    }
+  }
+
+  /** A flag or flag group that can be expanded under a set of variables. */
+  interface Expandable {
+    /**
+     * Expands the current expandable under the given {@code view}, adding new flags to {@code
+     * commandLine}.
+     *
+     * <p>The {@code variables} controls which variables are visible during the expansion and allows
+     * to recursively expand nested flag groups.
+     */
+    void expand(
+        CcToolchainVariables variables,
+        @Nullable ArtifactExpander expander,
+        List<String> commandLine);
+  }
+
+  /** An empty variables instance. */
+  public static final CcToolchainVariables EMPTY = new CcToolchainVariables.Builder().build();
+
+  /**
+   * Retrieves a {@link StringSequence} variable named {@code variableName} from {@code variables}
+   * and converts it into a list of plain strings.
+   *
+   * <p>Throws {@link ExpansionException} when the variable is not a {@link StringSequence}.
+   */
+  public static final ImmutableList<String> toStringList(
+      CcToolchainVariables variables, String variableName) {
+      return Streams
+          .stream(variables.getSequenceVariable(variableName))
+          .map(variable -> variable.getStringValue(variableName))
+          .collect(ImmutableList.toImmutableList());
+    }
+
+    /**
+     * Get a variable value named @param name. Supports accessing fields in structures (e.g.
+     * 'libraries_to_link.interface_libraries')
+     *
+     * @throws ExpansionException when no such variable or no such field are present, or when
+     *     accessing a field of non-structured variable
+     */
+    VariableValue getVariable(String name) {
+    return lookupVariable(name, /* throwOnMissingVariable= */ true, /* expander= */ null);
+    }
+
+    VariableValue getVariable(String name, @Nullable ArtifactExpander expander) {
+    return lookupVariable(name, /* throwOnMissingVariable= */ true, expander);
+    }
+
+    /**
+     * Lookup a variable named @param name or return a reason why the variable was not found.
+     * Supports accessing fields in structures.
+     *
+     * @return Pair<VariableValue, String> returns either (variable value, null) or (null, string
+     *     reason why variable was not found)
+     */
+    private VariableValue lookupVariable(
+        String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) {
+      VariableValue nonStructuredVariable = getNonStructuredVariable(name);
+      if (nonStructuredVariable != null) {
+        return nonStructuredVariable;
+      }
+      VariableValue structuredVariable =
+          getStructureVariable(name, throwOnMissingVariable, expander);
+      if (structuredVariable != null) {
+        return structuredVariable;
+      } else if (throwOnMissingVariable) {
+        throw new ExpansionException(
+            String.format(
+                "Invalid toolchain configuration: Cannot find variable named '%s'.", name));
+      } else {
+        return null;
+      }
+    }
+
+    private VariableValue getStructureVariable(
+        String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) {
+      if (!name.contains(".")) {
+        return null;
+      }
+
+      Stack<String> fieldsToAccess = new Stack<>();
+      String structPath = name;
+      VariableValue variable;
+
+      do {
+        fieldsToAccess.push(structPath.substring(structPath.lastIndexOf('.') + 1));
+        structPath = structPath.substring(0, structPath.lastIndexOf('.'));
+        variable = getNonStructuredVariable(structPath);
+      } while (variable == null && structPath.contains("."));
+
+      if (variable == null) {
+        return null;
+      }
+
+      while (!fieldsToAccess.empty()) {
+        String field = fieldsToAccess.pop();
+        variable = variable.getFieldValue(structPath, field, expander);
+        if (variable == null) {
+          if (throwOnMissingVariable) {
+            throw new ExpansionException(
+                String.format(
+                    "Invalid toolchain configuration: Cannot expand variable '%s.%s': structure %s "
+                        + "doesn't have a field named '%s'",
+                    structPath, field, structPath, field));
+          } else {
+            return null;
+          }
+        }
+      }
+      return variable;
+    }
+
+    public String getStringVariable(String variableName) {
+    return getVariable(variableName, /* expander= */ null).getStringValue(variableName);
+    }
+
+    public Iterable<? extends VariableValue> getSequenceVariable(String variableName) {
+    return getVariable(variableName, /* expander= */ null).getSequenceValue(variableName);
+    }
+
+    public Iterable<? extends VariableValue> getSequenceVariable(
+        String variableName, @Nullable ArtifactExpander expander) {
+      return getVariable(variableName, expander).getSequenceValue(variableName);
+    }
+
+    /** Returns whether {@code variable} is set. */
+    boolean isAvailable(String variable) {
+    return isAvailable(variable, /* expander= */ null);
+    }
+
+    boolean isAvailable(String variable, @Nullable ArtifactExpander expander) {
+    return lookupVariable(variable, /* throwOnMissingVariable= */ false, expander) != null;
+    }
+
+    abstract Map<String, VariableValue> getVariablesMap();
+
+    abstract Map<String, String> getStringVariablesMap();
+
+    @Nullable
+    abstract VariableValue getNonStructuredVariable(String name);
+
+    /**
+     * Value of a build variable exposed to the CROSSTOOL used for flag expansion.
+     *
+     * <p>{@link VariableValue} represent either primitive values or an arbitrarily deeply nested
+     * recursive structures or sequences. Since there are builds with millions of values, some
+     * implementations might exist only to optimize memory usage.
+     *
+     * <p>Implementations must be immutable and without any side-effects. They will be expanded and
+     * queried multiple times.
+     */
+    interface VariableValue {
+      /**
+       * Returns string value of the variable, if the variable type can be converted to string (e.g.
+       * StringValue), or throw exception if it cannot (e.g. Sequence).
+       *
+       * @param variableName name of the variable value at hand, for better exception message.
+       */
+      String getStringValue(String variableName);
+
+      /**
+       * Returns Iterable value of the variable, if the variable type can be converted to a Iterable
+       * (e.g. Sequence), or throw exception if it cannot (e.g. StringValue).
+       *
+       * @param variableName name of the variable value at hand, for better exception message.
+       */
+      Iterable<? extends VariableValue> getSequenceValue(String variableName);
+
+      /**
+       * Returns value of the field, if the variable is of struct type or throw exception if it is
+       * not or no such field exists.
+       *
+       * @param variableName name of the variable value at hand, for better exception message.
+       */
+      VariableValue getFieldValue(String variableName, String field);
+
+      VariableValue getFieldValue(
+          String variableName, String field, @Nullable ArtifactExpander expander);
+
+      /** Returns true if the variable is truthy */
+      boolean isTruthy();
+    }
+
+    /**
+     * Adapter for {@link VariableValue} predefining error handling methods. Override {@link
+     * #getVariableTypeName()}, {@link #isTruthy()}, and one of {@link #getFieldValue(String,
+     * String)}, {@link #getSequenceValue(String)}, or {@link #getStringValue(String)}, and you'll
+     * get error handling for the other methods for free.
+     */
+    abstract static class VariableValueAdapter implements VariableValue {
+
+      /** Returns human-readable variable type name to be used in error messages. */
+      public abstract String getVariableTypeName();
+
+      @Override
+      public abstract boolean isTruthy();
+
+      @Override
+      public VariableValue getFieldValue(String variableName, String field) {
+      return getFieldValue(variableName, field, /* expander= */ null);
+      }
+
+      @Override
+      public VariableValue getFieldValue(
+          String variableName, String field, @Nullable ArtifactExpander expander) {
+        throw new ExpansionException(
+            String.format(
+                "Invalid toolchain configuration: Cannot expand variable '%s.%s': variable '%s' is "
+                    + "%s, expected structure",
+                variableName, field, variableName, getVariableTypeName()));
+      }
+
+      @Override
+      public String getStringValue(String variableName) {
+        throw new ExpansionException(
+            String.format(
+                "Invalid toolchain configuration: Cannot expand variable '%s': expected string, "
+                    + "found %s",
+                variableName, getVariableTypeName()));
+      }
+
+      @Override
+      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
+        throw new ExpansionException(
+            String.format(
+                "Invalid toolchain configuration: Cannot expand variable '%s': expected sequence, "
+                    + "found %s",
+                variableName, getVariableTypeName()));
+      }
+    }
+
+    /** Interface for VariableValue builders */
+    public interface VariableValueBuilder {
+      VariableValue build();
+    }
+
+    /** Builder for StringSequence. */
+    public static class StringSequenceBuilder implements VariableValueBuilder {
+
+      private final ImmutableList.Builder<String> values = ImmutableList.builder();
+
+      /** Adds a value to the sequence. */
+      public StringSequenceBuilder addValue(String value) {
+        values.add(value);
+        return this;
+      }
+
+      /** Returns an immutable string sequence. */
+      @Override
+      public StringSequence build() {
+        return new StringSequence(values.build());
+      }
+    }
+
+    /** Builder for Sequence. */
+    public static class SequenceBuilder implements VariableValueBuilder {
+
+      private final ImmutableList.Builder<VariableValue> values = ImmutableList.builder();
+
+      /** Adds a value to the sequence. */
+      public SequenceBuilder addValue(VariableValue value) {
+        values.add(value);
+        return this;
+      }
+
+      /** Adds a value to the sequence. */
+      public SequenceBuilder addValue(VariableValueBuilder value) {
+        Preconditions.checkArgument(value != null, "Cannot use null builder for a sequence value");
+        values.add(value.build());
+        return this;
+      }
+
+      /** Returns an immutable sequence. */
+      @Override
+      public Sequence build() {
+        return new Sequence(values.build());
+      }
+    }
+
+    /** Builder for StructureValue. */
+    public static class StructureBuilder implements VariableValueBuilder {
+
+      private final ImmutableMap.Builder<String, VariableValue> fields = ImmutableMap.builder();
+
+      /** Adds a field to the structure. */
+      public StructureBuilder addField(String name, VariableValue value) {
+        fields.put(name, value);
+        return this;
+      }
+
+      /** Adds a field to the structure. */
+      public StructureBuilder addField(String name, VariableValueBuilder valueBuilder) {
+        Preconditions.checkArgument(
+            valueBuilder != null,
+            "Cannot use null builder to get a field value for field '%s'",
+            name);
+        fields.put(name, valueBuilder.build());
+        return this;
+      }
+
+      /** Adds a field to the structure. */
+      public StructureBuilder addField(String name, String value) {
+        fields.put(name, new StringValue(value));
+        return this;
+      }
+
+      /** Adds a field to the structure. */
+      public StructureBuilder addField(String name, ImmutableList<String> values) {
+        fields.put(name, new StringSequence(values));
+        return this;
+      }
+
+      /** Returns an immutable structure. */
+      @Override
+      public StructureValue build() {
+        return new StructureValue(fields.build());
+      }
+    }
+
+    /**
+     * Lazily computed string sequence. Exists as a memory optimization. Make sure the {@param
+     * supplier} doesn't capture anything that shouldn't outlive analysis phase (e.g. {@link
+     * RuleContext}).
+     */
+    @AutoCodec
+    @VisibleForSerialization
+    static final class LazyStringSequence extends VariableValueAdapter {
+      private final Supplier<ImmutableList<String>> supplier;
+
+      @VisibleForSerialization
+      LazyStringSequence(Supplier<ImmutableList<String>> supplier) {
+        this.supplier = Preconditions.checkNotNull(supplier);
+      }
+
+      @Override
+      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
+        return supplier
+            .get()
+            .stream()
+            .map(flag -> new StringValue(flag))
+            .collect(ImmutableList.toImmutableList());
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return !supplier.get().isEmpty();
+      }
+    }
+
+    /**
+     * A sequence of structure values. Exists as a memory optimization - a typical build can contain
+     * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects
+     * significantly reduces memory overhead.
+     */
+    @Immutable
+    @AutoCodec
+    public static class LibraryToLinkValue extends VariableValueAdapter {
+      public static final String OBJECT_FILES_FIELD_NAME = "object_files";
+      public static final String NAME_FIELD_NAME = "name";
+      public static final String TYPE_FIELD_NAME = "type";
+      public static final String IS_WHOLE_ARCHIVE_FIELD_NAME = "is_whole_archive";
+
+      private static final String LIBRARY_TO_LINK_VARIABLE_TYPE_NAME = "structure (LibraryToLink)";
+
+      @VisibleForSerialization
+      enum Type {
+        OBJECT_FILE("object_file"),
+        OBJECT_FILE_GROUP("object_file_group"),
+        INTERFACE_LIBRARY("interface_library"),
+        STATIC_LIBRARY("static_library"),
+        DYNAMIC_LIBRARY("dynamic_library"),
+        VERSIONED_DYNAMIC_LIBRARY("versioned_dynamic_library");
+
+        private final String name;
+
+        Type(String name) {
+          this.name = name;
+        }
+      }
+
+      private final String name;
+      private final ImmutableList<Artifact> objectFiles;
+      private final boolean isWholeArchive;
+      private final Type type;
+
+      public static LibraryToLinkValue forDynamicLibrary(String name) {
+      return new LibraryToLinkValue(
+          Preconditions.checkNotNull(name),
+          /* objectFiles= */ null,
+          /* isWholeArchive= */ false,
+          Type.DYNAMIC_LIBRARY);
+      }
+
+      public static LibraryToLinkValue forVersionedDynamicLibrary(
+          String name) {
+      return new LibraryToLinkValue(
+          Preconditions.checkNotNull(name),
+          /* objectFiles= */ null,
+          /* isWholeArchive= */ false,
+          Type.VERSIONED_DYNAMIC_LIBRARY);
+      }
+
+      public static LibraryToLinkValue forInterfaceLibrary(String name) {
+      return new LibraryToLinkValue(
+          Preconditions.checkNotNull(name),
+          /* objectFiles= */ null,
+          /* isWholeArchive= */ false,
+          Type.INTERFACE_LIBRARY);
+      }
+
+      public static LibraryToLinkValue forStaticLibrary(String name, boolean isWholeArchive) {
+      return new LibraryToLinkValue(
+          Preconditions.checkNotNull(name),
+          /* objectFiles= */ null,
+          isWholeArchive,
+          Type.STATIC_LIBRARY);
+      }
+
+      public static LibraryToLinkValue forObjectFile(String name, boolean isWholeArchive) {
+      return new LibraryToLinkValue(
+          Preconditions.checkNotNull(name),
+          /* objectFiles= */ null,
+          isWholeArchive,
+          Type.OBJECT_FILE);
+      }
+
+      public static LibraryToLinkValue forObjectFileGroup(
+          ImmutableList<Artifact> objects, boolean isWholeArchive) {
+        Preconditions.checkNotNull(objects);
+        Preconditions.checkArgument(!objects.isEmpty());
+      return new LibraryToLinkValue(
+          /* name= */ null, objects, isWholeArchive, Type.OBJECT_FILE_GROUP);
+      }
+
+      @VisibleForSerialization
+      LibraryToLinkValue(
+          String name, ImmutableList<Artifact> objectFiles, boolean isWholeArchive, Type type) {
+        this.name = name;
+        this.objectFiles = objectFiles;
+        this.isWholeArchive = isWholeArchive;
+        this.type = type;
+      }
+
+      @Override
+      public VariableValue getFieldValue(
+          String variableName, String field, @Nullable ArtifactExpander expander) {
+        Preconditions.checkNotNull(field);
+        if (NAME_FIELD_NAME.equals(field) && !type.equals(Type.OBJECT_FILE_GROUP)) {
+          return new StringValue(name);
+        } else if (OBJECT_FILES_FIELD_NAME.equals(field) && type.equals(Type.OBJECT_FILE_GROUP)) {
+          ImmutableList.Builder<String> expandedObjectFiles = ImmutableList.builder();
+          for (Artifact objectFile : objectFiles) {
+            if (objectFile.isTreeArtifact() && (expander != null)) {
+              List<Artifact> artifacts = new ArrayList<>();
+              expander.expand(objectFile, artifacts);
+              expandedObjectFiles.addAll(
+                  Iterables.transform(artifacts, artifact -> artifact.getExecPathString()));
+            } else {
+              expandedObjectFiles.add(objectFile.getExecPathString());
+            }
+          }
+          return new StringSequence(expandedObjectFiles.build());
+        } else if (TYPE_FIELD_NAME.equals(field)) {
+          return new StringValue(type.name);
+        } else if (IS_WHOLE_ARCHIVE_FIELD_NAME.equals(field)) {
+          return new IntegerValue(isWholeArchive ? 1 : 0);
+        } else {
+          return null;
+        }
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return LIBRARY_TO_LINK_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return true;
+      }
+    }
+
+    /** Sequence of arbitrary VariableValue objects. */
+    @Immutable
+    @AutoCodec
+    @VisibleForSerialization
+    static final class Sequence extends VariableValueAdapter {
+      private static final String SEQUENCE_VARIABLE_TYPE_NAME = "sequence";
+
+      private final ImmutableList<VariableValue> values;
+
+      public Sequence(ImmutableList<VariableValue> values) {
+        this.values = values;
+      }
+
+      @Override
+      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
+        return values;
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return SEQUENCE_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return values.isEmpty();
+      }
+    }
+
+    /**
+     * A sequence of structure values. Exists as a memory optimization - a typical build can contain
+     * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects
+     * significantly reduces memory overhead.
+     */
+    @Immutable
+    @AutoCodec
+    @VisibleForSerialization
+    static final class StructureSequence extends VariableValueAdapter {
+      private final ImmutableList<ImmutableMap<String, VariableValue>> values;
+
+      @VisibleForSerialization
+      StructureSequence(ImmutableList<ImmutableMap<String, VariableValue>> values) {
+        Preconditions.checkNotNull(values);
+        this.values = values;
+      }
+
+      @Override
+      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
+        final ImmutableList.Builder<VariableValue> sequences = ImmutableList.builder();
+        for (ImmutableMap<String, VariableValue> value : values) {
+          sequences.add(new StructureValue(value));
+        }
+        return sequences.build();
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return !values.isEmpty();
+      }
+    }
+
+    /**
+     * A sequence of simple string values. Exists as a memory optimization - a typical build can
+     * contain millions of feature values, so getting rid of the overhead of {@code StringValue}
+     * objects significantly reduces memory overhead.
+     */
+    @Immutable
+    @AutoCodec
+    static final class StringSequence extends VariableValueAdapter {
+      private final Iterable<String> values;
+
+      public StringSequence(Iterable<String> values) {
+        Preconditions.checkNotNull(values);
+        this.values = values;
+      }
+
+      @Override
+      public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
+        final ImmutableList.Builder<VariableValue> sequences = ImmutableList.builder();
+        for (String value : values) {
+          sequences.add(new StringValue(value));
+        }
+        return sequences.build();
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return Sequence.SEQUENCE_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return !Iterables.isEmpty(values);
+      }
+    }
+
+    /**
+     * Single structure value. Be careful not to create sequences of single structures, as the
+     * memory overhead is prohibitively big. Use optimized {@link StructureSequence} instead.
+     */
+    @Immutable
+    @AutoCodec
+    @VisibleForSerialization
+    static final class StructureValue extends VariableValueAdapter {
+      private static final String STRUCTURE_VARIABLE_TYPE_NAME = "structure";
+
+      private final ImmutableMap<String, VariableValue> value;
+
+      public StructureValue(ImmutableMap<String, VariableValue> value) {
+        this.value = value;
+      }
+
+      @Override
+      public VariableValue getFieldValue(
+          String variableName, String field, @Nullable ArtifactExpander expander) {
+        if (value.containsKey(field)) {
+          return value.get(field);
+        } else {
+          return null;
+        }
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return STRUCTURE_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return !value.isEmpty();
+      }
+    }
+
+    /**
+     * The leaves in the variable sequence node tree are simple string values. Note that this should
+     * never live outside of {@code expand}, as the object overhead is prohibitively expensive.
+     */
+    @Immutable
+    @AutoCodec
+    @VisibleForSerialization
+    static final class StringValue extends VariableValueAdapter {
+      private static final String STRING_VARIABLE_TYPE_NAME = "string";
+
+      private final String value;
+
+      public StringValue(String value) {
+        Preconditions.checkNotNull(value, "Cannot create StringValue from null");
+        this.value = value;
+      }
+
+      @Override
+      public String getStringValue(String variableName) {
+        return value;
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return STRING_VARIABLE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return !value.isEmpty();
+      }
+    }
+
+    /**
+     * The leaves in the variable sequence node tree are simple integer values. Note that this
+     * should never live outside of {@code expand}, as the object overhead is prohibitively
+     * expensive.
+     */
+    @Immutable
+    @AutoCodec
+    static final class IntegerValue extends VariableValueAdapter {
+      private static final String INTEGER_VALUE_TYPE_NAME = "integer";
+      private final int value;
+
+      public IntegerValue(int value) {
+        this.value = value;
+      }
+
+      @Override
+      public String getStringValue(String variableName) {
+        return Integer.toString(value);
+      }
+
+      @Override
+      public String getVariableTypeName() {
+        return INTEGER_VALUE_TYPE_NAME;
+      }
+
+      @Override
+      public boolean isTruthy() {
+        return value != 0;
+      }
+    }
+
+    /**
+     * Builder for {@code Variables}.
+     */
+    // TODO(b/65472725): Forbid sequences with empty string in them.
+    public static class Builder {
+      private final Map<String, VariableValue> variablesMap = new LinkedHashMap<>();
+      private final Map<String, String> stringVariablesMap = new LinkedHashMap<>();
+    private final CcToolchainVariables parent;
+
+      public Builder() {
+        parent = null;
+      }
+
+    public Builder(@Nullable CcToolchainVariables parent) {
+        this.parent = parent;
+      }
+
+      /** Add an integer variable that expands {@code name} to {@code value}. */
+      public Builder addIntegerVariable(String name, int value) {
+        variablesMap.put(name, new IntegerValue(value));
+        return this;
+      }
+
+      /** Add a string variable that expands {@code name} to {@code value}. */
+      public Builder addStringVariable(String name, String value) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(
+            value, "Cannot set null as a value for variable '%s'", name);
+        stringVariablesMap.put(name, value);
+        return this;
+      }
+
+      /** Overrides a variable to expands {@code name} to {@code value} instead. */
+      public Builder overrideStringVariable(String name, String value) {
+        Preconditions.checkNotNull(
+            value, "Cannot set null as a value for variable '%s'", name);
+        stringVariablesMap.put(name, value);
+        return this;
+      }
+
+    /** Overrides a variable to expand {@code name} to {@code value} instead. */
+    public Builder overrideLazyStringSequenceVariable(
+        String name, Supplier<ImmutableList<String>> supplier) {
+        Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name);
+        variablesMap.put(name, new LazyStringSequence(supplier));
+        return this;
+      }
+
+      /**
+       * Add a sequence variable that expands {@code name} to {@code values}.
+       *
+       * <p>Accepts values as ImmutableSet. As ImmutableList has smaller memory footprint, we copy
+       * the values into a new list.
+       */
+      public Builder addStringSequenceVariable(String name, ImmutableSet<String> values) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
+        ImmutableList.Builder<String> builder = ImmutableList.builder();
+        builder.addAll(values);
+        variablesMap.put(name, new StringSequence(builder.build()));
+        return this;
+      }
+
+      /**
+       * Add a sequence variable that expands {@code name} to {@code values}.
+       *
+       * <p>Accepts values as NestedSet. Nested set is stored directly, not cloned, not flattened.
+       */
+      public Builder addStringSequenceVariable(String name, NestedSet<String> values) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
+        variablesMap.put(name, new StringSequence(values));
+        return this;
+      }
+
+      /**
+       * Add a sequence variable that expands {@code name} to {@code values}.
+       *
+       * <p>Accepts values as Iterable. The iterable is stored directly, not cloned, not iterated.
+       * Be mindful of memory consumption of the particular Iterable. Prefer ImmutableList, or
+       * be sure that the iterable always returns the same elements in the same order, without any
+       * side effects.
+       */
+      public Builder addStringSequenceVariable(String name, Iterable<String> values) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
+        variablesMap.put(name, new StringSequence(values));
+        return this;
+      }
+
+      public Builder addLazyStringSequenceVariable(
+          String name, Supplier<ImmutableList<String>> supplier) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name);
+        variablesMap.put(name, new LazyStringSequence(supplier));
+        return this;
+      }
+
+    /**
+     * Add a variable built using {@code VariableValueBuilder} api that expands {@code name} to the
+     * value returned by the {@code builder}.
+     */
+    public Builder addCustomBuiltVariable(
+        String name, CcToolchainVariables.VariableValueBuilder builder) {
+        checkVariableNotPresentAlready(name);
+        Preconditions.checkNotNull(
+            builder,
+            "Cannot use null builder to get variable value for variable '%s'",
+            name);
+        variablesMap.put(name, builder.build());
+        return this;
+      }
+
+      /** Add all string variables in a map. */
+      public Builder addAllStringVariables(Map<String, String> variables) {
+        for (String name : variables.keySet()) {
+          checkVariableNotPresentAlready(name);
+        }
+        stringVariablesMap.putAll(variables);
+        return this;
+      }
+
+      private void checkVariableNotPresentAlready(String name) {
+        Preconditions.checkNotNull(name);
+        Preconditions.checkArgument(
+            !variablesMap.containsKey(name), "Cannot overwrite variable '%s'", name);
+        Preconditions.checkArgument(
+            !stringVariablesMap.containsKey(name), "Cannot overwrite variable '%s'", name);
+      }
+
+    /**
+     * Adds all variables to this builder. Cannot override already added variables. Does not add
+     * variables defined in the {@code parent} variables.
+     */
+    public Builder addAllNonTransitive(CcToolchainVariables variables) {
+        SetView<String> intersection =
+            Sets.intersection(variables.getVariablesMap().keySet(), variablesMap.keySet());
+        SetView<String> stringIntersection =
+            Sets.intersection(
+                variables.getStringVariablesMap().keySet(), stringVariablesMap.keySet());
+        Preconditions.checkArgument(
+            intersection.isEmpty(), "Cannot overwrite existing variables: %s", intersection);
+        Preconditions.checkArgument(
+            stringIntersection.isEmpty(),
+            "Cannot overwrite existing variables: %s", stringIntersection);
+        this.variablesMap.putAll(variables.getVariablesMap());
+        this.stringVariablesMap.putAll(variables.getStringVariablesMap());
+        return this;
+      }
+
+    /** @return a new {@link CcToolchainVariables} object. */
+    public CcToolchainVariables build() {
+        if (stringVariablesMap.isEmpty() && variablesMap.size() == 1) {
+          return new SingleVariables(
+              parent,
+              variablesMap.keySet().iterator().next(),
+              variablesMap.values().iterator().next());
+        }
+        return new MapVariables(
+            parent, ImmutableMap.copyOf(variablesMap), ImmutableMap.copyOf(stringVariablesMap));
+      }
+    }
+    
+    /**
+     * A group of extra {@code Variable} instances, packaged as logic for adding to a
+     * {@code Builder}
+     */
+    public interface VariablesExtension {
+      void addVariables(Builder builder);
+    }
+
+  @Immutable
+  @AutoCodec.VisibleForSerialization
+  @AutoCodec
+  static class MapVariables extends CcToolchainVariables {
+    private final ImmutableMap<String, VariableValue> variablesMap;
+    private final ImmutableMap<String, String> stringVariablesMap;
+    private final CcToolchainVariables parent;
+
+    @AutoCodec.Instantiator
+    @VisibleForSerialization
+    MapVariables(
+        CcToolchainVariables parent,
+        ImmutableMap<String, VariableValue> variablesMap,
+        ImmutableMap<String, String> stringVariablesMap) {
+      this.variablesMap = variablesMap;
+      this.stringVariablesMap = stringVariablesMap;
+      this.parent = parent;
+    }
+
+    @Override
+    Map<String, VariableValue> getVariablesMap() {
+      return variablesMap;
+    }
+
+    @Override
+    Map<String, String> getStringVariablesMap() {
+      return stringVariablesMap;
+    }
+
+    @Override
+    VariableValue getNonStructuredVariable(String name) {
+      if (variablesMap.containsKey(name)) {
+        return variablesMap.get(name);
+      }
+      if (stringVariablesMap.containsKey(name)) {
+        return new StringValue(stringVariablesMap.get(name));
+      }
+
+      if (parent != null) {
+        return parent.getNonStructuredVariable(name);
+      }
+
+      return null;
+    }
+  }
+
+  @Immutable
+  static class SingleVariables extends CcToolchainVariables {
+    private final CcToolchainVariables parent;
+    private final String name;
+    private final VariableValue variableValue;
+
+    SingleVariables(CcToolchainVariables parent, String name, VariableValue variableValue) {
+      this.parent = parent;
+      this.name = name;
+      this.variableValue = variableValue;
+    }
+
+    @Override
+    Map<String, VariableValue> getVariablesMap() {
+      return ImmutableMap.of(name, variableValue);
+    }
+
+    @Override
+    Map<String, String> getStringVariablesMap() {
+      return ImmutableMap.of();
+    }
+
+    @Override
+    VariableValue getNonStructuredVariable(String name) {
+      if (this.name.equals(name)) {
+        return variableValue;
+      }
+      return parent == null ? null : parent.getNonStructuredVariable(name);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
index 166805d..3aa90dd 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
@@ -22,9 +22,8 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StringSequenceBuilder;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringSequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.util.FileType;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -104,7 +103,7 @@
     this.variableName = variableName;
   }
 
-  public static Variables setupVariablesOrReportRuleError(
+  public static CcToolchainVariables setupVariablesOrReportRuleError(
       RuleContext ruleContext,
       FeatureConfiguration featureConfiguration,
       CcToolchainProvider ccToolchainProvider,
@@ -156,11 +155,11 @@
               || CppFileTypes.CLIF_INPUT_PROTO.matches(sourceFile));
     } catch (EvalException e) {
       ruleContext.ruleError(e.getMessage());
-      return Variables.EMPTY;
+      return CcToolchainVariables.EMPTY;
     }
   }
 
-  public static Variables setupVariablesOrThrowEvalException(
+  public static CcToolchainVariables setupVariablesOrThrowEvalException(
       FeatureConfiguration featureConfiguration,
       CcToolchainProvider ccToolchainProvider,
       String sourceFile,
@@ -191,8 +190,8 @@
     Preconditions.checkNotNull(quoteIncludeDirs);
     Preconditions.checkNotNull(systemIncludeDirs);
     Preconditions.checkNotNull(defines);
-    Variables.Builder buildVariables =
-        new Variables.Builder(ccToolchainProvider.getBuildVariables());
+    CcToolchainVariables.Builder buildVariables =
+        new CcToolchainVariables.Builder(ccToolchainProvider.getBuildVariables());
 
     buildVariables.addStringSequenceVariable(
         USER_COMPILE_FLAGS.getVariableName(), userCompileFlags);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLine.java
index 2d28909..878391f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLine.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppCompileAction.DotdFile;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
@@ -35,7 +34,7 @@
   private final Artifact sourceFile;
   private final CoptsFilter coptsFilter;
   private final FeatureConfiguration featureConfiguration;
-  private final CcToolchainFeatures.Variables variables;
+  private final CcToolchainVariables variables;
   private final String actionName;
   private final DotdFile dotdFile;
 
@@ -45,7 +44,7 @@
       Artifact sourceFile,
       CoptsFilter coptsFilter,
       FeatureConfiguration featureConfiguration,
-      CcToolchainFeatures.Variables variables,
+      CcToolchainVariables variables,
       String actionName,
       DotdFile dotdFile) {
     this.sourceFile = Preconditions.checkNotNull(sourceFile);
@@ -81,8 +80,7 @@
    * @param overwrittenVariables: Variables that will overwrite original build variables. When null,
    *     unmodified original variables are used.
    */
-  protected List<String> getArguments(
-      @Nullable CcToolchainFeatures.Variables overwrittenVariables) {
+  protected List<String> getArguments(@Nullable CcToolchainVariables overwrittenVariables) {
     List<String> commandLine = new ArrayList<>();
 
     // first: The command name.
@@ -93,14 +91,12 @@
     return commandLine;
   }
 
-  public List<String> getCompilerOptions(
-      @Nullable CcToolchainFeatures.Variables overwrittenVariables) {
+  public List<String> getCompilerOptions(@Nullable CcToolchainVariables overwrittenVariables) {
     List<String> options = new ArrayList<>();
 
-    CcToolchainFeatures.Variables updatedVariables = variables;
+    CcToolchainVariables updatedVariables = variables;
     if (variables != null && overwrittenVariables != null) {
-      CcToolchainFeatures.Variables.Builder variablesBuilder =
-          new CcToolchainFeatures.Variables.Builder(variables);
+      CcToolchainVariables.Builder variablesBuilder = new CcToolchainVariables.Builder(variables);
       variablesBuilder.addAllNonTransitive(overwrittenVariables);
       updatedVariables = variablesBuilder.build();
     }
@@ -131,7 +127,7 @@
     return dotdFile;
   }
 
-  public Variables getVariables() {
+  public CcToolchainVariables getVariables() {
     return variables;
   }
 
@@ -144,7 +140,7 @@
    */
   public ImmutableList<String> getCopts() {
     if (variables.isAvailable(CompileBuildVariables.USER_COMPILE_FLAGS.getVariableName())) {
-      return Variables.toStringList(
+      return CcToolchainVariables.toStringList(
           variables, CompileBuildVariables.USER_COMPILE_FLAGS.getVariableName());
     } else {
       return ImmutableList.of();
@@ -164,7 +160,7 @@
     private final Artifact sourceFile;
     private CoptsFilter coptsFilter;
     private FeatureConfiguration featureConfiguration;
-    private CcToolchainFeatures.Variables variables = Variables.EMPTY;
+    private CcToolchainVariables variables = CcToolchainVariables.EMPTY;
     private final String actionName;
     @Nullable private final DotdFile dotdFile;
 
@@ -195,7 +191,7 @@
       return this;
     }
 
-    public Builder setVariables(Variables variables) {
+    public Builder setVariables(CcToolchainVariables variables) {
       this.variables = variables;
       return this;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
index 9ff24cf..d5e692f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -220,7 +220,7 @@
   /** Used modules that are not transitively used through other topLevelModules. */
   private Iterable<Artifact> topLevelModules = null;
 
-  private CcToolchainFeatures.Variables overwrittenVariables = null;
+  private CcToolchainVariables overwrittenVariables = null;
 
   /**
    * Creates a new action to compile C/C++ source files.
@@ -260,7 +260,7 @@
       ActionOwner owner,
       NestedSet<Artifact> allInputs,
       FeatureConfiguration featureConfiguration,
-      CcToolchainFeatures.Variables variables,
+      CcToolchainVariables variables,
       Artifact sourceFile,
       boolean shouldScanIncludes,
       boolean shouldPruneModules,
@@ -371,7 +371,7 @@
       Iterable<Artifact> additionalInputs,
       Collection<Artifact> usedModules,
       Iterable<Artifact> topLevelModules,
-      CcToolchainFeatures.Variables overwrittenVariables,
+      CcToolchainVariables overwrittenVariables,
       boolean needsDotdInputPruning,
       boolean needsIncludeValidation,
       IncludeProcessing includeProcessing,
@@ -1021,16 +1021,14 @@
    * Extracts all module (.pcm) files from potentialModules and returns a Variables object where
    * their exec paths are added to the value "module_files".
    */
-  private static CcToolchainFeatures.Variables getOverwrittenVariables(
-      Iterable<Artifact> potentialModules) {
+  private static CcToolchainVariables getOverwrittenVariables(Iterable<Artifact> potentialModules) {
     ImmutableList.Builder<String> usedModulePaths = ImmutableList.builder();
     for (Artifact input : potentialModules) {
       if (input.isFileType(CppFileTypes.CPP_MODULE)) {
         usedModulePaths.add(input.getExecPathString());
       }
     }
-    CcToolchainFeatures.Variables.Builder variableBuilder =
-        new CcToolchainFeatures.Variables.Builder();
+    CcToolchainVariables.Builder variableBuilder = new CcToolchainVariables.Builder();
     variableBuilder.addStringSequenceVariable("module_files", usedModulePaths.build());
     return variableBuilder.build();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
index da8bcdb..866d1a5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
@@ -32,7 +32,6 @@
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppCompileAction.DotdFile;
 import com.google.devtools.build.lib.util.FileType;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -54,7 +53,7 @@
   private final ActionOwner owner;
   private final BuildConfiguration configuration;
   private CcToolchainFeatures.FeatureConfiguration featureConfiguration;
-  private CcToolchainFeatures.Variables variables = Variables.EMPTY;
+  private CcToolchainVariables variables = CcToolchainVariables.EMPTY;
   private Artifact sourceFile;
   private final NestedSetBuilder<Artifact> mandatoryInputsBuilder;
   private Artifact optionalSourceFile;
@@ -529,16 +528,14 @@
     return this;
   }
 
-  /**
-   * Sets the feature build variables to be used for the action.
-   */
-  public CppCompileActionBuilder setVariables(CcToolchainFeatures.Variables variables) {
+  /** Sets the feature build variables to be used for the action. */
+  public CppCompileActionBuilder setVariables(CcToolchainVariables variables) {
     this.variables = variables;
     return this;
   }
 
   /** Returns the build variables to be used for the action. */
-  public CcToolchainFeatures.Variables getVariables() {
+  public CcToolchainVariables getVariables() {
     return variables;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
index 7cc7dc3..5592c4f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
@@ -104,8 +104,8 @@
     builder.setSourceFile(sourceTreeFileArtifact);
     builder.setOutputs(outputTreeFileArtifact, null);
 
-    CcToolchainFeatures.Variables.Builder buildVariables =
-        new CcToolchainFeatures.Variables.Builder(cppCompileActionBuilder.getVariables());
+    CcToolchainVariables.Builder buildVariables =
+        new CcToolchainVariables.Builder(cppCompileActionBuilder.getVariables());
     buildVariables.overrideStringVariable(
         "source_file", sourceTreeFileArtifact.getExecPathString());
     buildVariables.overrideStringVariable(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
index eed0942..07082ad 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
@@ -60,7 +60,6 @@
 import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Linkstamp;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Tool;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.shell.ShellUtils;
@@ -906,8 +905,8 @@
     Tool stripTool =
         Preconditions.checkNotNull(
             featureConfiguration.getToolForAction(CppCompileAction.STRIP_ACTION_NAME));
-    Variables variables =
-        new Variables.Builder(toolchain.getBuildVariables())
+    CcToolchainVariables variables =
+        new CcToolchainVariables.Builder(toolchain.getBuildVariables())
             .addStringVariable(
                 StripBuildVariables.OUTPUT_FILE.getVariableName(), output.getExecPathString())
             .addStringSequenceVariable(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
index 40af3b8..9ec07a3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
@@ -44,8 +44,7 @@
 import com.google.devtools.build.lib.packages.RuleErrorConsumer;
 import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Linkstamp;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
 import com.google.devtools.build.lib.rules.cpp.CppLinkAction.Context;
 import com.google.devtools.build.lib.rules.cpp.CppLinkAction.LinkArtifactFactory;
@@ -954,7 +953,8 @@
             : null;
 
     // Add build variables necessary to template link args into the crosstool.
-    Variables.Builder buildVariablesBuilder = new Variables.Builder(toolchain.getBuildVariables());
+    CcToolchainVariables.Builder buildVariablesBuilder =
+        new CcToolchainVariables.Builder(toolchain.getBuildVariables());
     Preconditions.checkState(!isLtoIndexing || allowLtoIndexing);
     Preconditions.checkState(allowLtoIndexing || thinltoParamFile == null);
     Preconditions.checkState(allowLtoIndexing || thinltoMergedObjectFile == null);
@@ -992,7 +992,7 @@
     ImmutableSet<Artifact> expandedLinkerArtifactsNoLinkstamps =
         Sets.difference(expandedLinkerArtifacts, linkstampObjectArtifacts).immutableCopy();
 
-    Variables variables =
+    CcToolchainVariables variables =
         LinkBuildVariables.setupVariables(
             getLinkType().linkerOrArchiver().equals(LinkerOrArchiver.LINKER),
             configuration,
@@ -1020,7 +1020,7 @@
       extraVariablesExtension.addVariables(buildVariablesBuilder);
     }
 
-    Variables buildVariables = buildVariablesBuilder.build();
+    CcToolchainVariables buildVariables = buildVariablesBuilder.build();
 
     Preconditions.checkArgument(
         linkType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY,
@@ -1086,8 +1086,8 @@
             : linkoptsForVariables;
     linkCommandLineBuilder.setLinkopts(linkoptsForVariables);
 
-    Variables patchedVariables =
-        new Variables.Builder(buildVariables)
+    CcToolchainVariables patchedVariables =
+        new CcToolchainVariables.Builder(buildVariables)
             .addStringSequenceVariable(
                 LinkBuildVariables.LEGACY_LINK_FLAGS.getVariableName(),
                 getToolchainFlags(linkoptsForVariables))
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
index 5d23af9..67d020f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
@@ -20,7 +20,6 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.regex.Pattern;
 
@@ -125,7 +124,7 @@
         .collect(ImmutableList.toImmutableList());
   }
 
-  private static Variables getVariables(
+  private static CcToolchainVariables getVariables(
       RuleContext ruleContext,
       Artifact sourceFile,
       Artifact outputFile,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
index e4f36ef..ab00b5c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
@@ -59,7 +59,7 @@
       ActionOwner owner,
       NestedSet<Artifact> allInputs,
       FeatureConfiguration featureConfiguration,
-      CcToolchainFeatures.Variables variables,
+      CcToolchainVariables variables,
       Artifact sourceFile,
       boolean shouldScanIncludes,
       boolean shouldPruneModules,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java
index 0a1223f..e10ca70 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java
@@ -787,26 +787,27 @@
   }
 
   /**
-   * Adds the FDO profile output path to the variable builder.
-   * If FDO is disabled, no build variable is added.
+   * Adds the FDO profile output path to the variable builder. If FDO is disabled, no build variable
+   * is added.
    */
   @ThreadSafe
-  public void getLinkOptions(FeatureConfiguration featureConfiguration,
-      CcToolchainFeatures.Variables.Builder buildVariables
-      ) {
+  public void getLinkOptions(
+      FeatureConfiguration featureConfiguration, CcToolchainVariables.Builder buildVariables) {
     if (featureConfiguration.isEnabled(CppRuleClasses.FDO_INSTRUMENT)) {
       buildVariables.addStringVariable("fdo_instrument_path", fdoInstrument);
     }
   }
 
   /**
-   * Adds the AutoFDO profile path to the variable builder and returns the profile artifact.
-   * If AutoFDO is disabled, no build variable is added and returns null.
+   * Adds the AutoFDO profile path to the variable builder and returns the profile artifact. If
+   * AutoFDO is disabled, no build variable is added and returns null.
    */
   @ThreadSafe
-  public Artifact buildProfileForLtoBackend(FdoSupportProvider fdoSupportProvider,
+  public Artifact buildProfileForLtoBackend(
+      FdoSupportProvider fdoSupportProvider,
       FeatureConfiguration featureConfiguration,
-      CcToolchainFeatures.Variables.Builder buildVariables, RuleContext ruleContext) {
+      CcToolchainVariables.Builder buildVariables,
+      RuleContext ruleContext) {
     if (!featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
       return null;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
index 56f361f..ad262b8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
@@ -20,8 +20,8 @@
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.LibraryToLinkValue;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.SequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode;
 import com.google.devtools.build.lib.util.Pair;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
index 826bf6a..34d39c3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
@@ -18,8 +18,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.SequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
 /** Enum covering all build variables we create for all various {@link CppLinkAction}. */
@@ -92,7 +91,7 @@
     return variableName;
   }
 
-  public static Variables setupVariables(
+  public static CcToolchainVariables setupVariables(
       boolean isUsingLinkerNotArchiver,
       BuildConfiguration configuration,
       Artifact outputArtifact,
@@ -113,7 +112,7 @@
       Iterable<String> runtimeLibrarySearchDirectories,
       SequenceBuilder librariesToLink,
       Iterable<String> librarySearchDirectories) {
-    Variables.Builder buildVariables = new Variables.Builder();
+    CcToolchainVariables.Builder buildVariables = new CcToolchainVariables.Builder();
 
     // symbol counting
     if (symbolCounts != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java
index 545dd9e..a1c037f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java
@@ -24,7 +24,6 @@
 import com.google.devtools.build.lib.collect.CollectionUtils;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkerOrArchiver;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
@@ -44,7 +43,7 @@
 public final class LinkCommandLine extends CommandLine {
   private final String actionName;
   private final String forcedToolPath;
-  private final CcToolchainFeatures.Variables variables;
+  private final CcToolchainVariables variables;
   // The feature config can be null for tests.
   @Nullable private final FeatureConfiguration featureConfiguration;
   private final ImmutableList<Artifact> buildInfoHeaderArtifacts;
@@ -71,7 +70,7 @@
       boolean nativeDeps,
       boolean useTestOnlyFlags,
       @Nullable Artifact paramFile,
-      CcToolchainFeatures.Variables variables,
+      CcToolchainVariables variables,
       @Nullable FeatureConfiguration featureConfiguration) {
 
     this.actionName = actionName;
@@ -159,7 +158,7 @@
 
   /** Returns the build variables used to template the crosstool for this linker invocation. */
   @VisibleForTesting
-  public Variables getBuildVariables() {
+  public CcToolchainVariables getBuildVariables() {
     return this.variables;
   }
 
@@ -213,7 +212,7 @@
     private final String forcedToolPath;
     private final FeatureConfiguration featureConfiguration;
     private final String actionName;
-    private final Variables variables;
+    private final CcToolchainVariables variables;
 
     public ParamFileCommandLine(
         Artifact paramsFile,
@@ -221,7 +220,7 @@
         String forcedToolPath,
         FeatureConfiguration featureConfiguration,
         String actionName,
-        Variables variables) {
+        CcToolchainVariables variables) {
       this.paramsFile = paramsFile;
       this.linkTargetType = linkTargetType;
       this.forcedToolPath = forcedToolPath;
@@ -366,7 +365,7 @@
       FeatureConfiguration featureConfiguration,
       String actionName,
       LinkTargetType linkTargetType,
-      Variables variables) {
+      CcToolchainVariables variables) {
     List<String> argv = new ArrayList<>();
     if (forcedToolPath != null) {
       argv.add(forcedToolPath);
@@ -419,7 +418,7 @@
     private boolean nativeDeps;
     private boolean useTestOnlyFlags;
     @Nullable private Artifact paramFile;
-    private Variables variables;
+    private CcToolchainVariables variables;
     private FeatureConfiguration featureConfiguration;
 
     public Builder(RuleContext ruleContext) {
@@ -440,7 +439,7 @@
       }
       
       if (variables == null) {
-        variables = Variables.EMPTY;
+        variables = CcToolchainVariables.EMPTY;
       }
 
       String actionName = linkTargetType.getActionName();
@@ -550,8 +549,8 @@
       this.paramFile = paramFile;
       return this;
     }
-    
-    public Builder setBuildVariables(Variables variables) {
+
+    public Builder setBuildVariables(CcToolchainVariables variables) {
       this.variables = variables;
       return this;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendArtifacts.java
index 5aaf1c1..df6dba7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendArtifacts.java
@@ -20,7 +20,6 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
@@ -217,8 +216,8 @@
     PathFragment compiler = ccToolchain.getToolPathFragment(Tool.GCC);
 
     builder.setExecutable(compiler);
-    Variables.Builder buildVariablesBuilder =
-        new Variables.Builder(ccToolchain.getBuildVariables());
+    CcToolchainVariables.Builder buildVariablesBuilder =
+        new CcToolchainVariables.Builder(ccToolchain.getBuildVariables());
     if (index != null) {
       buildVariablesBuilder.addStringVariable("thinlto_index", index.getExecPath().toString());
     } else {
@@ -250,7 +249,7 @@
 
     List<String> execArgs = new ArrayList<>();
     execArgs.addAll(commandLine);
-    Variables buildVariables = buildVariablesBuilder.build();
+    CcToolchainVariables buildVariables = buildVariablesBuilder.build();
     // Feature options should go after --copt for consistency with compile actions.
     execArgs.addAll(featureConfiguration.getCommandLine("lto-backend", buildVariables));
     // If this is a PIC compile (set based on the CppConfiguration), the PIC
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
index 3d93874..7ebb7aa 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
@@ -100,8 +100,8 @@
 import com.google.devtools.build.lib.rules.cpp.CcToolchain;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.CollidingProvidesException;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.rules.cpp.CppCompileAction;
 import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
 import com.google.devtools.build.lib.rules.cpp.CppHelper;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcVariablesExtension.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcVariablesExtension.java
index 6040007..f7df33f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcVariablesExtension.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcVariablesExtension.java
@@ -27,9 +27,9 @@
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
 import com.google.devtools.build.lib.rules.apple.ApplePlatform;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StringSequenceBuilder;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringSequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import java.util.Set;
 
@@ -128,7 +128,7 @@
   }
 
   @Override
-  public void addVariables(CcToolchainFeatures.Variables.Builder builder) {
+  public void addVariables(CcToolchainVariables.Builder builder) {
     addPchVariables(builder);
     addFrameworkVariables(builder);
     addModuleMapVariables(builder);
@@ -157,7 +157,7 @@
     }
   }
 
-  private void addPchVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addPchVariables(CcToolchainVariables.Builder builder) {
     if (ruleContext.attributes().has("pch", BuildType.LABEL)
         && ruleContext.getPrerequisiteArtifact("pch", Mode.TARGET) != null) {
       builder.addStringVariable(
@@ -166,7 +166,7 @@
     }
   }
 
-  private void addFrameworkVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addFrameworkVariables(CcToolchainVariables.Builder builder) {
     ApplePlatform applePlatform =
         buildConfiguration.getFragment(AppleConfiguration.class).getSingleArchPlatform();
     StringSequenceBuilder frameworkSequence = new StringSequenceBuilder();
@@ -177,7 +177,7 @@
     builder.addCustomBuiltVariable(FRAMEWORKS_VARIABLE_NAME, frameworkSequence);
   }
 
-  private void addModuleMapVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addModuleMapVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(
         MODULES_MAPS_DIR_NAME,
         intermediateArtifacts
@@ -191,7 +191,7 @@
         buildConfiguration.getGenfilesFragment() + "/" + OBJC_MODULE_CACHE_DIR_NAME);
   }
 
-  private void addArchiveVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addArchiveVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(
         OBJ_LIST_PATH_VARIABLE_NAME,
         intermediateArtifacts.archiveObjList().getExecPathString());
@@ -199,7 +199,7 @@
         ARCHIVE_PATH_VARIABLE_NAME, compilationArtifacts.getArchive().get().getExecPathString());
   }
 
-  private void addFullyLinkArchiveVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addFullyLinkArchiveVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(
         FULLY_LINKED_ARCHIVE_PATH_VARIABLE_NAME, fullyLinkArchive.getExecPathString());
     builder.addStringSequenceVariable(
@@ -213,7 +213,7 @@
         Artifact.toExecPaths(objcProvider.get(IMPORTED_LIBRARY)));
   }
 
-  private void addExecutableLinkVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addExecutableLinkVariables(CcToolchainVariables.Builder builder) {
     builder.addStringSequenceVariable(
         FRAMEWORK_NAMES_VARIABLE_NAME, frameworkNames);
     builder.addStringSequenceVariable(
@@ -235,7 +235,7 @@
     builder.addStringSequenceVariable(ATTR_LINKOPTS_VARIABLE_NAME, attributeLinkopts);
   }
 
-  private void addDsymVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addDsymVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(
         DSYM_BUNDLE_ZIP_VARIABLE_NAME, dsymBundleZip.getShellEscapedExecPathString());
     builder.addStringVariable(
@@ -243,11 +243,11 @@
         FileSystemUtils.removeExtension(dsymBundleZip.getExecPath()).getPathString());
   }
 
-  private void addLinkmapVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addLinkmapVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(LINKMAP_EXEC_PATH, linkmap.getExecPathString());
   }
 
-  private void addBitcodeVariables(CcToolchainFeatures.Variables.Builder builder) {
+  private void addBitcodeVariables(CcToolchainVariables.Builder builder) {
     builder.addStringVariable(
         BITCODE_SYMBOL_MAP_PATH_VARAIBLE_NAME, bitcodeSymbolMap.getExecPathString());
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
index 82f4e74..6c04b60 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
@@ -30,14 +30,13 @@
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ActionConfig;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.IntegerValue;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.LibraryToLinkValue;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.SequenceBuilder;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StringSequenceBuilder;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StructureBuilder;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValue;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValueBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.IntegerValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringSequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StructureBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValueBuilder;
 import com.google.devtools.build.lib.skyframe.serialization.AutoRegistry;
 import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecs;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
@@ -66,7 +65,7 @@
    * <p>If there are multiple entries with the same key, the variable will be treated as sequence
    * type.
    */
-  private Variables createVariables(String... entries) {
+  private CcToolchainVariables createVariables(String... entries) {
     if (entries.length % 2 != 0) {
       throw new IllegalArgumentException(
           "createVariables takes an even number of arguments (key/value pairs)");
@@ -75,7 +74,7 @@
     for (int i = 0; i < entries.length; i += 2) {
       entryMap.put(entries[i], entries[i + 1]);
     }
-    Variables.Builder variables = new Variables.Builder();
+    CcToolchainVariables.Builder variables = new CcToolchainVariables.Builder();
     for (String name : entryMap.keySet()) {
       Collection<String> value = entryMap.get(name);
       if (value.size() == 1) {
@@ -302,7 +301,11 @@
     return getExpansionOfFlag(value, createVariables());
   }
 
-  private List<String> getCommandLineForFlagGroups(String groups, Variables variables)
+  private String getExpansionOfFlag(String value, CcToolchainVariables variables) throws Exception {
+    return getCommandLineForFlag(value, variables).get(0);
+  }
+
+  private List<String> getCommandLineForFlagGroups(String groups, CcToolchainVariables variables)
       throws Exception {
     FeatureConfiguration configuration =
         buildFeatures(
@@ -317,14 +320,11 @@
     return configuration.getCommandLine(CppCompileAction.CPP_COMPILE, variables);
   }
 
-  private List<String> getCommandLineForFlag(String value, Variables variables) throws Exception {
+  private List<String> getCommandLineForFlag(String value, CcToolchainVariables variables)
+      throws Exception {
     return getCommandLineForFlagGroups("flag_group { flag: '" + value + "' }", variables);
   }
 
-  private String getExpansionOfFlag(String value, Variables variables) throws Exception {
-    return getCommandLineForFlag(value, variables).get(0);
-  }
-
   private String getFlagParsingError(String value) throws Exception {
     try {
       getExpansionOfFlag(value);
@@ -335,7 +335,8 @@
     }
   }
 
-  private String getFlagExpansionError(String value, Variables variables) throws Exception {
+  private String getFlagExpansionError(String value, CcToolchainVariables variables)
+      throws Exception {
     try {
       getExpansionOfFlag(value, variables);
       fail("Expected ExpansionException");
@@ -345,7 +346,7 @@
     }
   }
 
-  private String getFlagGroupsExpansionError(String flagGroups, Variables variables)
+  private String getFlagGroupsExpansionError(String flagGroups, CcToolchainVariables variables)
       throws Exception {
     try {
       getCommandLineForFlagGroups(flagGroups, variables).get(0);
@@ -371,7 +372,7 @@
     assertThat(
             getCommandLineForFlagGroups(
                 "flag_group{ iterate_over: 'v' flag: '%{v}' }",
-                new Variables.Builder()
+                new CcToolchainVariables.Builder()
                     .addStringSequenceVariable("v", ImmutableList.<String>of())
                     .build()))
         .isEmpty();
@@ -384,23 +385,24 @@
     assertThat(
             getCommandLineForFlagGroups(
                 "flag_group { iterate_over: 'lazy' flag: '-lazy-%{lazy}' }",
-                new Variables.Builder()
+                new CcToolchainVariables.Builder()
                     .addLazyStringSequenceVariable("lazy", () -> ImmutableList.of("a", "b", "c"))
                     .build()))
         .containsExactly("-lazy-a", "-lazy-b", "-lazy-c")
         .inOrder();
   }
 
-  private Variables createStructureSequenceVariables(String name, StructureBuilder... values) {
+  private CcToolchainVariables createStructureSequenceVariables(
+      String name, StructureBuilder... values) {
     SequenceBuilder builder = new SequenceBuilder();
     for (StructureBuilder value : values) {
       builder.addValue(value.build());
     }
-    return new Variables.Builder().addCustomBuiltVariable(name, builder).build();
+    return new CcToolchainVariables.Builder().addCustomBuiltVariable(name, builder).build();
   }
 
-  private Variables createStructureVariables(String name, StructureBuilder value) {
-    return new Variables.Builder().addCustomBuiltVariable(name, value).build();
+  private CcToolchainVariables createStructureVariables(String name, StructureBuilder value) {
+    return new CcToolchainVariables.Builder().addCustomBuiltVariable(name, value).build();
   }
 
   @Test
@@ -521,7 +523,7 @@
                     + "    }"
                     + "  }"
                     + "}",
-                new Variables.Builder()
+                new CcToolchainVariables.Builder()
                     .addCustomBuiltVariable(
                         "struct",
                         new StructureBuilder()
@@ -542,7 +544,7 @@
                     + "}",
                 createStructureVariables(
                     "struct",
-                    new Variables.StructureBuilder()
+                    new CcToolchainVariables.StructureBuilder()
                         .addField("foo", "fooValue")
                         .addField("bar", "barValue"))))
         .containsExactly("-AfooValue", "-BbarValue");
@@ -559,7 +561,7 @@
                     + "}",
                 createStructureVariables(
                     "struct",
-                    new Variables.StructureBuilder()
+                    new CcToolchainVariables.StructureBuilder()
                         .addField("foo", "fooValue")
                         .addField("bar", "barValue"))))
         .isEmpty();
@@ -602,7 +604,7 @@
                     + "}",
                 createStructureVariables(
                     "struct",
-                    new Variables.StructureBuilder()
+                    new CcToolchainVariables.StructureBuilder()
                         .addField("foo", "fooValue")
                         .addField("bar", "barValue"))))
         .containsExactly("-AfooValue", "-BbarValue");
@@ -618,7 +620,8 @@
                     + "  flag: '-B%{struct.bar}'"
                     + "}",
                 createStructureVariables(
-                    "struct", new Variables.StructureBuilder().addField("bar", "barValue"))))
+                    "struct",
+                    new CcToolchainVariables.StructureBuilder().addField("bar", "barValue"))))
         .isEmpty();
   }
 
@@ -636,7 +639,8 @@
                     + "  }"
                     + "}",
                 createStructureVariables(
-                    "struct", new Variables.StructureBuilder().addField("bar", "barValue"))))
+                    "struct",
+                    new CcToolchainVariables.StructureBuilder().addField("bar", "barValue"))))
         .containsExactly("-BbarValue");
   }
 
@@ -705,7 +709,7 @@
                     + "}",
                 createStructureVariables(
                     "struct",
-                    new Variables.StructureBuilder()
+                    new CcToolchainVariables.StructureBuilder()
                         .addField("bool", new IntegerValue(1))
                         .addField("foo", "fooValue")
                         .addField("bar", "barValue"))))
@@ -728,7 +732,7 @@
                     + "}",
                 createStructureVariables(
                     "struct",
-                    new Variables.StructureBuilder()
+                    new CcToolchainVariables.StructureBuilder()
                         .addField("bool", new IntegerValue(0))
                         .addField("foo", "fooValue")
                         .addField("bar", "barValue"))))
@@ -841,8 +845,8 @@
     }
   }
 
-  private Variables createNestedVariables(String name, int depth, int count) {
-    return new Variables.Builder()
+  private CcToolchainVariables createNestedVariables(String name, int depth, int count) {
+    return new CcToolchainVariables.Builder()
         .addCustomBuiltVariable(name, createNestedSequence(depth, count, ""))
         .build();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
index d039539..6835970 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
@@ -20,7 +20,6 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoMode;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import org.junit.Test;
@@ -60,7 +59,7 @@
             CcCompilationContext.EMPTY,
             /* supportsParamFiles= */ false,
             /* supportsHeaderParsing= */ false,
-            Variables.EMPTY,
+            CcToolchainVariables.EMPTY,
             /* builtinIncludeFiles= */ ImmutableList.<Artifact>of(),
             /* coverageEnvironment= */ NestedSetBuilder.emptySet(Order.COMPILE_ORDER),
             /* linkDynamicLibraryTool= */ null,
@@ -97,7 +96,7 @@
             CcCompilationContext.EMPTY,
             /* supportsParamFiles= */ false,
             /* supportsHeaderParsing= */ false,
-            Variables.EMPTY,
+            CcToolchainVariables.EMPTY,
             /* builtinIncludeFiles= */ ImmutableList.<Artifact>of(),
             /* coverageEnvironment= */ NestedSetBuilder.emptySet(Order.COMPILE_ORDER),
             /* linkDynamicLibraryTool= */ null,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariablesTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariablesTest.java
index 2714ef5..0e6caea 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariablesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariablesTest.java
@@ -22,7 +22,6 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -48,7 +47,8 @@
   }
 
   /** Returns active build variables for a compile action of given type for given target. */
-  protected Variables getCompileBuildVariables(String label, String name) throws Exception {
+  protected CcToolchainVariables getCompileBuildVariables(String label, String name)
+      throws Exception {
     return getCppCompileAction(label, name).getCompileCommandLine().getVariables();
   }
 
@@ -57,7 +57,7 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'])");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     assertThat(variables.getStringVariable(CompileBuildVariables.SOURCE_FILE.getVariableName()))
         .contains("x/bin.cc");
@@ -73,10 +73,10 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'])");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     ImmutableList<String> copts =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.LEGACY_COMPILE_FLAGS.getVariableName());
     assertThat(copts).contains("-foo");
   }
@@ -89,15 +89,15 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'], copts = ['-bar'],)");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     ImmutableList<String> userCopts =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.USER_COMPILE_FLAGS.getVariableName());
     assertThat(userCopts).containsAllIn(ImmutableList.<String>of("-foo", "-bar")).inOrder();
 
     ImmutableList<String> legacyCopts =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.LEGACY_COMPILE_FLAGS.getVariableName());
     assertThat(legacyCopts).doesNotContain("-foo");
   }
@@ -110,10 +110,10 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'], copts = ['-foo'])");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     ImmutableList<String> copts =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.USER_COMPILE_FLAGS.getVariableName());
     assertThat(copts).contains("-foo");
   }
@@ -128,10 +128,10 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'])");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     ImmutableList<String> unfilteredCompileFlags =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.UNFILTERED_COMPILE_FLAGS.getVariableName());
     assertThat(unfilteredCompileFlags).contains("--i_ll_live_forever");
   }
@@ -142,10 +142,10 @@
     scratch.file("x/bin.cc");
     useConfiguration("--per_file_copt=//x:bin@-foo", "--per_file_copt=//x:bar\\.cc@-bar");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     ImmutableList<String> copts =
-        Variables.toStringList(
+        CcToolchainVariables.toStringList(
             variables, CompileBuildVariables.USER_COMPILE_FLAGS.getVariableName());
     assertThat(copts).containsExactly("-foo").inOrder();
   }
@@ -160,7 +160,7 @@
     scratch.file("x/BUILD", "cc_binary(name = 'bin', srcs = ['bin.cc'])");
     scratch.file("x/bin.cc");
 
-    Variables variables = getCompileBuildVariables("//x:bin", "bin");
+    CcToolchainVariables variables = getCompileBuildVariables("//x:bin", "bin");
 
     assertThat(variables.getStringVariable(CcCommon.SYSROOT_VARIABLE_NAME))
         .isEqualTo("/usr/local/custom-sysroot");
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index 42673d9..8aaefcc 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -46,7 +46,7 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.packages.util.MockCcSupport;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValue;
 import com.google.devtools.build.lib.rules.cpp.CppActionConfigs.CppPlatform;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkerOrArchiver;
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTest.java
index 1eed5db..bc463b0 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTest.java
@@ -22,9 +22,8 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.packages.util.MockCcSupport;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.LibraryToLinkValue;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValue;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
 import com.google.devtools.build.lib.util.OsUtils;
 import java.util.List;
@@ -43,7 +42,7 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:bin");
-    Variables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
     String variableValue =
         getVariableValue(variables, LinkBuildVariables.FORCE_PIC.getVariableName());
     assertThat(variableValue).contains("");
@@ -58,7 +57,8 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
+    CcToolchainVariables variables =
+        getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
     VariableValue librariesToLinkSequence =
         variables.getVariable(LinkBuildVariables.LIBRARIES_TO_LINK.getVariableName());
     assertThat(librariesToLinkSequence).isNotNull();
@@ -87,7 +87,7 @@
     scratch.file("x/some-dir/bar.so");
 
     ConfiguredTarget target = getConfiguredTarget("//x:bin");
-    Variables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
     List<String> variableValue =
         getSequenceVariableValue(
             variables, LinkBuildVariables.LIBRARY_SEARCH_DIRECTORIES.getVariableName());
@@ -103,7 +103,7 @@
     scratch.file("x/some-dir/bar.so");
 
     ConfiguredTarget target = getConfiguredTarget("//x:bin");
-    Variables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
     String variableValue =
         getVariableValue(variables, LinkBuildVariables.LINKER_PARAM_FILE.getVariableName());
     assertThat(variableValue).matches(".*bin/x/bin" + OsUtils.executableExtension() + "-2.params$");
@@ -122,7 +122,8 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
+    CcToolchainVariables variables =
+        getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
 
     String interfaceLibraryBuilder =
         getVariableValue(variables, LinkBuildVariables.INTERFACE_LIBRARY_BUILDER.getVariableName());
@@ -170,7 +171,7 @@
         (CppLinkAction)
             getPredecessorByInputName(
                 backendAction, "x/libfoo.so.lto/x/_objs/foo/x/a.pic.o.thinlto.bc");
-    Variables variables = indexAction.getLinkCommandLine().getBuildVariables();
+    CcToolchainVariables variables = indexAction.getLinkCommandLine().getBuildVariables();
 
     String interfaceLibraryBuilder =
         getVariableValue(variables, LinkBuildVariables.INTERFACE_LIBRARY_BUILDER.getVariableName());
@@ -201,7 +202,7 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.STATIC_LIBRARY);
+    CcToolchainVariables variables = getLinkBuildVariables(target, LinkTargetType.STATIC_LIBRARY);
 
     String interfaceLibraryBuilder =
         getVariableValue(variables, LinkBuildVariables.INTERFACE_LIBRARY_BUILDER.getVariableName());
@@ -227,7 +228,8 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
+    CcToolchainVariables variables =
+        getLinkBuildVariables(target, LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
 
     assertThat(getVariableValue(variables, LinkBuildVariables.OUTPUT_EXECPATH.getVariableName()))
         .endsWith("x/libfoo.so");
@@ -262,7 +264,7 @@
         (CppLinkAction)
             getPredecessorByInputName(
                 backendAction, "x/libfoo.so.lto/x/_objs/foo/x/a.pic.o.thinlto.bc");
-    Variables variables = indexAction.getLinkCommandLine().getBuildVariables();
+    CcToolchainVariables variables = indexAction.getLinkCommandLine().getBuildVariables();
 
     assertThat(variables.isAvailable(LinkBuildVariables.OUTPUT_EXECPATH.getVariableName()))
         .isFalse();
@@ -276,14 +278,16 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget testTarget = getConfiguredTarget("//x:foo_test");
-    Variables testVariables = getLinkBuildVariables(testTarget, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables testVariables =
+        getLinkBuildVariables(testTarget, LinkTargetType.EXECUTABLE);
 
     assertThat(
             testVariables.getVariable(LinkBuildVariables.IS_CC_TEST.getVariableName()).isTruthy())
         .isTrue();
 
     ConfiguredTarget binaryTarget = getConfiguredTarget("//x:foo");
-    Variables binaryVariables = getLinkBuildVariables(binaryTarget, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables binaryVariables =
+        getLinkBuildVariables(binaryTarget, LinkTargetType.EXECUTABLE);
 
     assertThat(
             binaryVariables.getVariable(LinkBuildVariables.IS_CC_TEST.getVariableName()).isTruthy())
@@ -310,7 +314,7 @@
       String stripMode, String compilationMode, boolean isEnabled) throws Exception {
     useConfiguration("--strip=" + stripMode, "--compilation_mode=" + compilationMode);
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, LinkTargetType.EXECUTABLE);
     assertThat(variables.isAvailable(LinkBuildVariables.STRIP_DEBUG_SYMBOLS.getVariableName()))
         .isEqualTo(isEnabled);
   }
@@ -327,13 +331,14 @@
 
     useConfiguration("--fission=no");
     ConfiguredTarget target = getConfiguredTarget("//x:foo");
-    Variables variables = getLinkBuildVariables(target, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, LinkTargetType.EXECUTABLE);
     assertThat(variables.isAvailable(LinkBuildVariables.IS_USING_FISSION.getVariableName()))
         .isFalse();
 
     useConfiguration("--fission=yes");
     ConfiguredTarget fissionTarget = getConfiguredTarget("//x:foo");
-    Variables fissionVariables = getLinkBuildVariables(fissionTarget, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables fissionVariables =
+        getLinkBuildVariables(fissionTarget, LinkTargetType.EXECUTABLE);
     assertThat(fissionVariables.isAvailable(LinkBuildVariables.IS_USING_FISSION.getVariableName()))
         .isTrue();
   }
@@ -349,7 +354,8 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget testTarget = getConfiguredTarget("//x:foo");
-    Variables testVariables = getLinkBuildVariables(testTarget, LinkTargetType.EXECUTABLE);
+    CcToolchainVariables testVariables =
+        getLinkBuildVariables(testTarget, LinkTargetType.EXECUTABLE);
 
     assertThat(testVariables.isAvailable(CcCommon.SYSROOT_VARIABLE_NAME)).isTrue();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTestCase.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTestCase.java
index 3eab78d..f2ba846 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariablesTestCase.java
@@ -21,7 +21,6 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
 import com.google.protobuf.TextFormat;
@@ -61,7 +60,8 @@
   }
 
   /** Returns active build variables for a link action of given type for given target. */
-  protected Variables getLinkBuildVariables(ConfiguredTarget target, Link.LinkTargetType type) {
+  protected CcToolchainVariables getLinkBuildVariables(
+      ConfiguredTarget target, Link.LinkTargetType type) {
     return getCppLinkAction(target, type).getLinkCommandLine().getBuildVariables();
   }
 
@@ -76,7 +76,7 @@
   }
 
   /** Returns the value of a given sequence variable in context of the given Variables instance. */
-  protected List<String> getSequenceVariableValue(Variables variables, String variable)
+  protected List<String> getSequenceVariableValue(CcToolchainVariables variables, String variable)
       throws Exception {
     FeatureConfiguration mockFeatureConfiguration =
         buildFeatures(
@@ -95,7 +95,8 @@
   }
 
   /** Returns the value of a given string variable in context of the given Variables instance. */
-  protected String getVariableValue(Variables variables, String variable) throws Exception {
+  protected String getVariableValue(CcToolchainVariables variables, String variable)
+      throws Exception {
     FeatureConfiguration mockFeatureConfiguration =
         buildFeatures(
                 "feature {",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBuildVariablesTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBuildVariablesTest.java
index e49d647..5d2eed5 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBuildVariablesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBuildVariablesTest.java
@@ -26,7 +26,7 @@
 import com.google.devtools.build.lib.packages.util.MockObjcSupport;
 import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions;
 import com.google.devtools.build.lib.rules.apple.cpp.AppleCcToolchain;
-import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables;
 import com.google.devtools.build.lib.rules.cpp.CppLinkAction;
 import com.google.devtools.build.lib.rules.cpp.Link;
 import com.google.devtools.build.lib.rules.cpp.LinkBuildVariablesTestCase;
@@ -39,7 +39,7 @@
 /**
  * Tests that {@code CppLinkAction} is populated with the correct build variables for objective C
  * builds.
- **/
+ */
 @RunWith(JUnit4.class)
 public class ObjcBuildVariablesTest extends LinkBuildVariablesTestCase {
 
@@ -78,7 +78,7 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:bin");
-    Variables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
     assertThat(
             getVariableValue(variables, AppleCcToolchain.XCODE_VERISON_OVERRIDE_VALUE_KEY))
         .contains("5.8");
@@ -126,7 +126,7 @@
         ActionsTestUtil.getFirstArtifactEndingWith(appleBinLinkAction.getInputs(), "liba.a");
     CppLinkAction ccArchiveAction = (CppLinkAction) getGeneratingAction(archive);
 
-    Variables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
+    CcToolchainVariables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
     assertThat(
             getVariableValue(variables, AppleCcToolchain.XCODE_VERISON_OVERRIDE_VALUE_KEY))
         .contains("5.8");
@@ -174,7 +174,7 @@
         ActionsTestUtil.getFirstArtifactEndingWith(appleBinLinkAction.getInputs(), "liba.a");
     CppLinkAction ccArchiveAction = (CppLinkAction) getGeneratingAction(archive);
 
-    Variables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
+    CcToolchainVariables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
     assertThat(getVariableValue(variables, AppleCcToolchain.VERSION_MIN_KEY))
         .contains(dummyMinimumOsValue);
   }
@@ -193,7 +193,7 @@
     scratch.file("x/a.cc");
 
     ConfiguredTarget target = getConfiguredTarget("//x:bin");
-    Variables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
+    CcToolchainVariables variables = getLinkBuildVariables(target, Link.LinkTargetType.EXECUTABLE);
     assertThat(
             getVariableValue(variables, AppleCcToolchain.XCODE_VERISON_OVERRIDE_VALUE_KEY))
         .contains(MockObjcSupport.DEFAULT_XCODE_VERSION);
@@ -239,7 +239,7 @@
         ActionsTestUtil.getFirstArtifactEndingWith(appleBinLinkAction.getInputs(), "liba.a");
     CppLinkAction ccArchiveAction = (CppLinkAction) getGeneratingAction(archive);
 
-    Variables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
+    CcToolchainVariables variables = ccArchiveAction.getLinkCommandLine().getBuildVariables();
     assertThat(getVariableValue(variables, AppleCcToolchain.VERSION_MIN_KEY))
         .contains(dummyMinimumOsValue);
   }