AAPT2: Push more configuration code into the parser

When parsing is complete, we now have a list of output artifacts that
have their referential integrity validated. This means that once the
configuration file is parsed, the only errors that can occur are related
to APK processing, and not the configuration itself.

This reduces the number of errors that could cause a partial output of
APK artifacts. It simplifies the public API and reduces the complexity of
the code to generate multiple APKs.

Test: Ran unit tests
Test: manually ran the optimize command to ensure it still works

Change-Id: I3f2d885b207a84c958f5348a4baa6718598184a4
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index eaadfd8..d8bb999 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -44,8 +44,7 @@
 #include "util/Util.h"
 
 using ::aapt::configuration::Abi;
-using ::aapt::configuration::Artifact;
-using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::configuration::OutputArtifact;
 using ::android::ResTable_config;
 using ::android::StringPiece;
 using ::android::base::ReadFileToString;
@@ -74,7 +73,7 @@
 
   TableFlattenerOptions table_flattener_options;
 
-  Maybe<PostProcessingConfiguration> configuration;
+  Maybe<std::vector<OutputArtifact>> apk_artifacts;
 
   // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
   // are kept and will be written as output.
@@ -199,14 +198,11 @@
       ++split_constraints_iter;
     }
 
-    if (options_.configuration && options_.output_dir) {
+    if (options_.apk_artifacts && options_.output_dir) {
       MultiApkGenerator generator{apk.get(), context_};
       MultiApkGeneratorOptions generator_options = {
-          options_.output_dir.value(),
-          options_.configuration.value(),
-          options_.table_flattener_options,
-          options_.kept_artifacts,
-      };
+          options_.output_dir.value(), options_.apk_artifacts.value(),
+          options_.table_flattener_options, options_.kept_artifacts};
       if (!generator.FromBaseApk(generator_options)) {
         return 1;
       }
@@ -423,29 +419,27 @@
     std::string& path = config_path.value();
     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
     if (for_path) {
-      options.configuration = for_path.value().WithDiagnostics(diag).Parse();
+      options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
+      if (!options.apk_artifacts) {
+        diag->Error(DiagMessage() << "Failed to parse the output artifact list");
+        return 1;
+      }
+
     } else {
       diag->Error(DiagMessage() << "Could not parse config file " << path);
       return 1;
     }
 
     if (print_only) {
-      std::vector<std::string> names;
-      const PostProcessingConfiguration& config = options.configuration.value();
-      if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
-        diag->Error(DiagMessage() << "Failed to generate output artifact list");
-        return 1;
-      }
-
-      for (const auto& name : names) {
-        std::cout << name << std::endl;
+      for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
+        std::cout << artifact.name << std::endl;
       }
       return 0;
     }
 
     if (!kept_artifacts.empty()) {
-      for (const auto& artifact_str : kept_artifacts) {
-        for (const auto& artifact : util::Tokenize(artifact_str, ',')) {
+      for (const std::string& artifact_str : kept_artifacts) {
+        for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
           options.kept_artifacts.insert(artifact.to_string());
         }
       }
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 852ff17..ebc523f 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -28,6 +28,7 @@
 #include "ConfigDescription.h"
 #include "Diagnostics.h"
 #include "ResourceUtils.h"
+#include "configuration/ConfigurationParser.internal.h"
 #include "io/File.h"
 #include "io/FileSystem.h"
 #include "io/StringStream.h"
@@ -45,11 +46,22 @@
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidManifest;
 using ::aapt::configuration::AndroidSdk;
-using ::aapt::configuration::Artifact;
-using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::Entry;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Group;
 using ::aapt::configuration::Locale;
+using ::aapt::configuration::OutputArtifact;
+using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::configuration::handler::AbiGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::ArtifactFormatTagHandler;
+using ::aapt::configuration::handler::ArtifactTagHandler;
+using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
+using ::aapt::configuration::handler::GlTextureGroupTagHandler;
+using ::aapt::configuration::handler::LocaleGroupTagHandler;
+using ::aapt::configuration::handler::ScreenDensityGroupTagHandler;
 using ::aapt::io::IFile;
 using ::aapt::io::RegularFile;
 using ::aapt::io::StringInputStream;
@@ -59,19 +71,16 @@
 using ::aapt::xml::XmlActionExecutor;
 using ::aapt::xml::XmlActionExecutorPolicy;
 using ::aapt::xml::XmlNodeAction;
-using ::android::base::ReadFileToString;
 using ::android::StringPiece;
+using ::android::base::ReadFileToString;
 
-const std::unordered_map<std::string, Abi> kStringToAbiMap = {
+const std::unordered_map<StringPiece, Abi> kStringToAbiMap = {
     {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a},  {"arm64-v8a", Abi::kArm64V8a},
     {"x86", Abi::kX86},        {"x86_64", Abi::kX86_64},       {"mips", Abi::kMips},
     {"mips64", Abi::kMips64},  {"universal", Abi::kUniversal},
 };
-const std::map<Abi, std::string> kAbiToStringMap = {
-    {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"},  {Abi::kArm64V8a, "arm64-v8a"},
-    {Abi::kX86, "x86"},        {Abi::kX86_64, "x86_64"},       {Abi::kMips, "mips"},
-    {Abi::kMips64, "mips64"},  {Abi::kUniversal, "universal"},
-};
+const std::array<StringPiece, 8> kAbiToStringMap = {
+    {"armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64", "universal"}};
 
 constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
 
@@ -106,12 +115,25 @@
   }
 };
 
-}  // namespace
+/** Copies the values referenced in a configuration group to the target list. */
+template <typename T>
+bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups,
+                       std::vector<T>* target) {
+  // If there was no item configured, there is nothing to do and no error.
+  if (!name) {
+    return true;
+  }
 
-namespace configuration {
+  // If the group could not be found, then something is wrong.
+  auto group = groups.find(name.value());
+  if (group == groups.end()) {
+    return false;
+  }
 
-const std::string& AbiToString(Abi abi) {
-  return kAbiToStringMap.find(abi)->second;
+  for (const T& item : group->second) {
+    target->push_back(item);
+  }
+  return true;
 }
 
 /**
@@ -119,8 +141,8 @@
  * success, or false if the either the placeholder is not found in the name, or the value is not
  * present and the placeholder was.
  */
-static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
-                               std::string* name, IDiagnostics* diag) {
+bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
+                        std::string* name, IDiagnostics* diag) {
   size_t offset = name->find(placeholder.data());
   bool found = (offset != std::string::npos);
 
@@ -152,6 +174,160 @@
 }
 
 /**
+ * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the
+ * element was successfully processed, otherwise returns false.
+ */
+using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config,
+                                         xml::Element* element, IDiagnostics* diag)>;
+
+/** Binds an ActionHandler to the current configuration being populated. */
+xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfiguration* config,
+                                            const ActionHandler& handler) {
+  return [config, handler](xml::Element* root_element, SourcePathDiagnostics* diag) {
+    return handler(config, root_element, diag);
+  };
+}
+
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+                                                        IDiagnostics* diag) {
+  StringInputStream in(contents);
+  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
+  if (!doc) {
+    return {};
+  }
+
+  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+  Element* root = doc->root.get();
+  if (root == nullptr) {
+    diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+    return {};
+  }
+
+  std::string& xml_ns = root->namespace_uri;
+  if (!xml_ns.empty()) {
+    if (xml_ns != kAaptXmlNs) {
+      diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+      return {};
+    }
+
+    xml_ns.clear();
+    NamespaceVisitor visitor;
+    root->Accept(&visitor);
+  }
+
+  XmlActionExecutor executor;
+  XmlNodeAction& root_action = executor["post-process"];
+  XmlNodeAction& artifacts_action = root_action["artifacts"];
+  XmlNodeAction& groups_action = root_action["groups"];
+
+  PostProcessingConfiguration config;
+
+  // Parse the artifact elements.
+  artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+  artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+  // Parse the different configuration groups.
+  groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+  groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
+  groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+  groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
+  groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
+  groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
+
+  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+    diag->Error(DiagMessage() << "Could not process XML document");
+    return {};
+  }
+
+  return {config};
+}
+
+/** Converts a ConfiguredArtifact into an OutputArtifact. */
+Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
+                                       const std::string& apk_name,
+                                       const PostProcessingConfiguration& config,
+                                       IDiagnostics* diag) {
+  if (!artifact.name && !config.artifact_format) {
+    diag->Error(
+        DiagMessage() << "Artifact does not have a name and no global name template defined");
+    return {};
+  }
+
+  Maybe<std::string> artifact_name =
+      (artifact.name) ? artifact.Name(apk_name, diag)
+                      : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag);
+
+  if (!artifact_name) {
+    diag->Error(DiagMessage() << "Could not determine split APK artifact name");
+    return {};
+  }
+
+  OutputArtifact output_artifact;
+  output_artifact.name = artifact_name.value();
+
+  SourcePathDiagnostics src_diag{{output_artifact.name}, diag};
+  bool has_errors = false;
+
+  if (!CopyXmlReferences(artifact.abi_group, config.abi_groups, &output_artifact.abis)) {
+    src_diag.Error(DiagMessage() << "Could not lookup required ABIs: "
+                                 << artifact.abi_group.value());
+    has_errors = true;
+  }
+
+  if (!CopyXmlReferences(artifact.locale_group, config.locale_groups, &output_artifact.locales)) {
+    src_diag.Error(DiagMessage() << "Could not lookup required locales: "
+                                 << artifact.locale_group.value());
+    has_errors = true;
+  }
+
+  if (!CopyXmlReferences(artifact.screen_density_group, config.screen_density_groups,
+                         &output_artifact.screen_densities)) {
+    src_diag.Error(DiagMessage() << "Could not lookup required screen densities: "
+                                 << artifact.screen_density_group.value());
+    has_errors = true;
+  }
+
+  if (!CopyXmlReferences(artifact.device_feature_group, config.device_feature_groups,
+                         &output_artifact.features)) {
+    src_diag.Error(DiagMessage() << "Could not lookup required device features: "
+                                 << artifact.device_feature_group.value());
+    has_errors = true;
+  }
+
+  if (!CopyXmlReferences(artifact.gl_texture_group, config.gl_texture_groups,
+                         &output_artifact.textures)) {
+    src_diag.Error(DiagMessage() << "Could not lookup required OpenGL texture formats: "
+                                 << artifact.gl_texture_group.value());
+    has_errors = true;
+  }
+
+  if (artifact.android_sdk_group) {
+    auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
+    if (entry == config.android_sdk_groups.end()) {
+      src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
+                                   << artifact.android_sdk_group.value());
+      has_errors = true;
+    } else {
+      output_artifact.android_sdk = {entry->second};
+    }
+  }
+
+  if (has_errors) {
+    return {};
+  }
+  return {output_artifact};
+}
+
+}  // namespace
+
+namespace configuration {
+
+const StringPiece& AbiToString(Abi abi) {
+  return kAbiToStringMap.at(static_cast<size_t>(abi));
+}
+
+/**
  * Returns the common artifact base name from a template string.
  */
 Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) {
@@ -186,8 +362,9 @@
   return result;
 }
 
-Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, const StringPiece& apk_name,
-                                            IDiagnostics* diag) const {
+Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
+                                                      const StringPiece& apk_name,
+                                                      IDiagnostics* diag) const {
   Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
   if (!base) {
     return {};
@@ -221,7 +398,7 @@
   return result;
 }
 
-Maybe<std::string> Artifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const {
+Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const {
   if (!name) {
     return {};
   }
@@ -229,31 +406,6 @@
   return ToBaseName(name.value(), apk_name, diag);
 }
 
-bool PostProcessingConfiguration::AllArtifactNames(const StringPiece& apk_name,
-                                                   std::vector<std::string>* artifact_names,
-                                                   IDiagnostics* diag) const {
-  for (const auto& artifact : artifacts) {
-    Maybe<std::string> name;
-    if (artifact.name) {
-      name = artifact.Name(apk_name, diag);
-    } else {
-      if (!artifact_format) {
-        diag->Error(DiagMessage() << "No global artifact template and an artifact name is missing");
-        return false;
-      }
-      name = artifact.ToArtifactName(artifact_format.value(), apk_name, diag);
-    }
-
-    if (!name) {
-      return false;
-    }
-
-    artifact_names->push_back(std::move(name.value()));
-  }
-
-  return true;
-}
-
 }  // namespace configuration
 
 /** Returns a ConfigurationParser for the file located at the provided path. */
@@ -270,86 +422,58 @@
       diag_(&noop_) {
 }
 
-Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() {
-  StringInputStream in(contents_);
-  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag_, Source("config.xml"));
-  if (!doc) {
+Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
+    const android::StringPiece& apk_path) {
+  Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+  if (!maybe_config) {
     return {};
   }
-
-  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
-  Element* root = doc->root.get();
-  if (root == nullptr) {
-    diag_->Error(DiagMessage() << "Could not find the root element in the XML document");
-    return {};
-  }
-
-  std::string& xml_ns = root->namespace_uri;
-  if (!xml_ns.empty()) {
-    if (xml_ns != kAaptXmlNs) {
-      diag_->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
-      return {};
-    }
-
-    xml_ns.clear();
-    NamespaceVisitor visitor;
-    root->Accept(&visitor);
-  }
-
-  XmlActionExecutor executor;
-  XmlNodeAction& root_action = executor["post-process"];
-  XmlNodeAction& artifacts_action = root_action["artifacts"];
-  XmlNodeAction& groups_action = root_action["groups"];
-
-  PostProcessingConfiguration config;
-
-  // Helper to bind a static method to an action handler in the DOM executor.
-  auto bind_handler =
-      [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h)
-      -> XmlNodeAction::ActionFuncWithDiag {
-    return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
-  };
-
-  // Parse the artifact elements.
-  artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
-  artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
-
-  // Parse the different configuration groups.
-  groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
-  groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
-  groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
-  groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
-  groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
-  groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
-
-  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
-    diag_->Error(DiagMessage() << "Could not process XML document");
-    return {};
-  }
-
-  // TODO: Validate all references in the configuration are valid. It should be safe to assume from
-  // this point on that any references from one section to another will be present.
+  const PostProcessingConfiguration& config = maybe_config.value();
 
   // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
   // see: https://developer.android.com/google/play/publishing/multiple-apks.html
   //
   // For now, make sure the version codes are unique.
-  std::vector<Artifact>& artifacts = config.artifacts;
+  std::vector<ConfiguredArtifact> artifacts = config.artifacts;
   std::sort(artifacts.begin(), artifacts.end());
   if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
     diag_->Error(DiagMessage() << "Configuration has duplicate versions");
     return {};
   }
 
-  return {config};
+  const std::string& apk_name = file::GetFilename(apk_path).to_string();
+  const StringPiece ext = file::GetExtension(apk_name);
+  const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
+
+  // Convert from a parsed configuration to a list of artifacts for processing.
+  std::vector<OutputArtifact> output_artifacts;
+  bool has_errors = false;
+
+  for (const ConfiguredArtifact& artifact : artifacts) {
+    Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
+    if (!output_artifact) {
+      // Defer return an error condition so that all errors are reported.
+      has_errors = true;
+    } else {
+      output_artifacts.push_back(std::move(output_artifact.value()));
+    }
+  }
+
+  if (has_errors) {
+    return {};
+  }
+  return {output_artifacts};
 }
 
-ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+namespace configuration {
+namespace handler {
+
+bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                        IDiagnostics* diag) {
   // This will be incremented later so the first version will always be different to the base APK.
   int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
 
-  Artifact artifact{};
+  ConfiguredArtifact artifact{};
   Maybe<int> version;
   for (const auto& attr : root_element->attributes) {
     if (attr.name == "name") {
@@ -380,8 +504,8 @@
   return true;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                              IDiagnostics* /* diag */) {
   for (auto& node : root_element->children) {
     xml::Text* t;
     if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
@@ -392,8 +516,8 @@
   return true;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                        IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -420,8 +544,8 @@
   return valid;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                                  IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -461,8 +585,8 @@
   return valid;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                           IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -502,8 +626,8 @@
   return valid;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                               IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -560,8 +684,8 @@
   return valid;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                              IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -603,8 +727,8 @@
   return valid;
 };
 
-ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
-    [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                                  IDiagnostics* diag) {
   std::string label = GetLabel(root_element, diag);
   if (label.empty()) {
     return false;
@@ -632,4 +756,7 @@
   return valid;
 };
 
+}  // namespace handler
+}  // namespace configuration
+
 }  // namespace aapt
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index c5d3284..ca58910 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -30,54 +30,6 @@
 
 namespace configuration {
 
-/** A mapping of group labels to group of configuration items. */
-template<class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
-
-/** A mapping of group label to a single configuration item. */
-template <class T>
-using Entry = std::unordered_map<std::string, T>;
-
-/** Output artifact configuration options. */
-struct Artifact {
-  /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
-  Maybe<std::string> name;
-  /**
-   * Value to add to the base Android manifest versionCode. If it is not present in the
-   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
-   * a value, artifacts are a 1 based index.
-   */
-  int version;
-  /** If present, uses the ABI group with this name. */
-  Maybe<std::string> abi_group;
-  /** If present, uses the screen density group with this name. */
-  Maybe<std::string> screen_density_group;
-  /** If present, uses the locale group with this name. */
-  Maybe<std::string> locale_group;
-  /** If present, uses the Android SDK group with this name. */
-  Maybe<std::string> android_sdk_group;
-  /** If present, uses the device feature group with this name. */
-  Maybe<std::string> device_feature_group;
-  /** If present, uses the OpenGL texture group with this name. */
-  Maybe<std::string> gl_texture_group;
-
-  /** Convert an artifact name template into a name string based on configuration contents. */
-  Maybe<std::string> ToArtifactName(const android::StringPiece& format,
-                                    const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
-  /** Convert an artifact name template into a name string based on configuration contents. */
-  Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
-  bool operator<(const Artifact& rhs) const {
-    // TODO(safarmer): Order by play store multi-APK requirements.
-    return version < rhs.version;
-  }
-
-  bool operator==(const Artifact& rhs) const  {
-    return version == rhs.version;
-  }
-};
-
 /** Enumeration of currently supported ABIs. */
 enum class Abi {
   kArmeV6,
@@ -91,7 +43,7 @@
 };
 
 /** Helper method to convert an ABI to a string representing the path within the APK. */
-const std::string& AbiToString(Abi abi);
+const android::StringPiece& AbiToString(Abi abi);
 
 /**
  * Represents an individual locale. When a locale is included, it must be
@@ -151,22 +103,16 @@
   }
 };
 
-/** AAPT2 XML configuration file binary representation. */
-struct PostProcessingConfiguration {
-  // TODO: Support named artifacts?
-  std::vector<Artifact> artifacts;
-  Maybe<std::string> artifact_format;
-
-  Group<Abi> abi_groups;
-  Group<ConfigDescription> screen_density_groups;
-  Group<ConfigDescription> locale_groups;
-  Entry<AndroidSdk> android_sdk_groups;
-  Group<DeviceFeature> device_feature_groups;
-  Group<GlTexture> gl_texture_groups;
-
-  /** Helper method that generates a list of artifact names and returns true on success. */
-  bool AllArtifactNames(const android::StringPiece& apk_name,
-                        std::vector<std::string>* artifact_names, IDiagnostics* diag) const;
+/** An artifact with all the details pulled from the PostProcessingConfiguration. */
+struct OutputArtifact {
+  std::string name;
+  int version;
+  std::vector<Abi> abis;
+  std::vector<ConfigDescription> screen_densities;
+  std::vector<ConfigDescription> locales;
+  Maybe<AndroidSdk> android_sdk;
+  std::vector<DeviceFeature> features;
+  std::vector<GlTexture> textures;
 };
 
 }  // namespace configuration
@@ -202,7 +148,7 @@
    * Parses the configuration file and returns the results. If the configuration could not be parsed
    * the result is empty and any errors will be displayed with the provided diagnostics context.
    */
-  Maybe<configuration::PostProcessingConfiguration> Parse();
+  Maybe<std::vector<configuration::OutputArtifact>> Parse(const android::StringPiece& apk_path);
 
  protected:
   /**
@@ -217,30 +163,6 @@
     return diag_;
   }
 
-  /**
-   * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the
-   * element was successfully processed, otherwise returns false.
-   */
-  using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config,
-                                           xml::Element* element, IDiagnostics* diag)>;
-
-  /** Handler for <artifact> tags. */
-  static ActionHandler artifact_handler_;
-  /** Handler for <artifact-format> tags. */
-  static ActionHandler artifact_format_handler_;
-  /** Handler for <abi-group> tags. */
-  static ActionHandler abi_group_handler_;
-  /** Handler for <screen-density-group> tags. */
-  static ActionHandler screen_density_group_handler_;
-  /** Handler for <locale-group> tags. */
-  static ActionHandler locale_group_handler_;
-  /** Handler for <android-sdk-group> tags. */
-  static ActionHandler android_sdk_group_handler_;
-  /** Handler for <gl-texture-group> tags. */
-  static ActionHandler gl_texture_group_handler_;
-  /** Handler for <device-feature-group> tags. */
-  static ActionHandler device_feature_group_handler_;
-
  private:
   /** The contents of the configuration file to parse. */
   const std::string contents_;
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
new file mode 100644
index 0000000..7657ebd
--- /dev/null
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
+#define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
+
+namespace aapt {
+namespace configuration {
+
+/** A mapping of group labels to group of configuration items. */
+template <class T>
+using Group = std::unordered_map<std::string, std::vector<T>>;
+
+/** A mapping of group label to a single configuration item. */
+template <class T>
+using Entry = std::unordered_map<std::string, T>;
+
+/** Output artifact configuration options. */
+struct ConfiguredArtifact {
+  /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
+  Maybe<std::string> name;
+  /**
+   * Value to add to the base Android manifest versionCode. If it is not present in the
+   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
+   * a value, artifacts are a 1 based index.
+   */
+  int version;
+  /** If present, uses the ABI group with this name. */
+  Maybe<std::string> abi_group;
+  /** If present, uses the screen density group with this name. */
+  Maybe<std::string> screen_density_group;
+  /** If present, uses the locale group with this name. */
+  Maybe<std::string> locale_group;
+  /** If present, uses the Android SDK group with this name. */
+  Maybe<std::string> android_sdk_group;
+  /** If present, uses the device feature group with this name. */
+  Maybe<std::string> device_feature_group;
+  /** If present, uses the OpenGL texture group with this name. */
+  Maybe<std::string> gl_texture_group;
+
+  /** Convert an artifact name template into a name string based on configuration contents. */
+  Maybe<std::string> ToArtifactName(const android::StringPiece& format,
+                                    const android::StringPiece& apk_name, IDiagnostics* diag) const;
+
+  /** Convert an artifact name template into a name string based on configuration contents. */
+  Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
+
+  bool operator<(const ConfiguredArtifact& rhs) const {
+    // TODO(safarmer): Order by play store multi-APK requirements.
+    return version < rhs.version;
+  }
+
+  bool operator==(const ConfiguredArtifact& rhs) const {
+    return version == rhs.version;
+  }
+};
+
+/** AAPT2 XML configuration file binary representation. */
+struct PostProcessingConfiguration {
+  // TODO: Support named artifacts?
+  std::vector<ConfiguredArtifact> artifacts;
+  Maybe<std::string> artifact_format;
+
+  Group<Abi> abi_groups;
+  Group<ConfigDescription> screen_density_groups;
+  Group<ConfigDescription> locale_groups;
+  Entry<AndroidSdk> android_sdk_groups;
+  Group<DeviceFeature> device_feature_groups;
+  Group<GlTexture> gl_texture_groups;
+};
+
+namespace handler {
+
+/** Handler for <artifact> tags. */
+bool ArtifactTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+                        IDiagnostics* diag);
+
+/** Handler for <artifact-format> tags. */
+bool ArtifactFormatTagHandler(configuration::PostProcessingConfiguration* config,
+                              xml::Element* element, IDiagnostics* diag);
+
+/** Handler for <abi-group> tags. */
+bool AbiGroupTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+                        IDiagnostics* diag);
+
+/** Handler for <screen-density-group> tags. */
+bool ScreenDensityGroupTagHandler(configuration::PostProcessingConfiguration* config,
+                                  xml::Element* element, IDiagnostics* diag);
+
+/** Handler for <locale-group> tags. */
+bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
+                           xml::Element* element, IDiagnostics* diag);
+
+/** Handler for <android-sdk-group> tags. */
+bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
+                               xml::Element* element, IDiagnostics* diag);
+
+/** Handler for <gl-texture-group> tags. */
+bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
+                              xml::Element* element, IDiagnostics* diag);
+
+/** Handler for <device-feature-group> tags. */
+bool DeviceFeatureGroupTagHandler(configuration::PostProcessingConfiguration* config,
+                                  xml::Element* element, IDiagnostics* diag);
+
+}  // namespace handler
+}  // namespace configuration
+}  // namespace aapt
+#endif  // AAPT2_CONFIGURATIONPARSER_INTERNAL_H
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index f7153c8..3f356d7 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -22,6 +22,7 @@
 #include "androidfw/ResourceTypes.h"
 
 #include "SdkConstants.h"
+#include "configuration/ConfigurationParser.internal.h"
 #include "test/Test.h"
 #include "xml/XmlDom.h"
 
@@ -33,14 +34,15 @@
       << ", target=" << sdk.target_sdk_version.value_or_default(-1)
       << ", max=" << sdk.max_sdk_version.value_or_default(-1);
 }
-}  // namespace configuration
+
+namespace handler {
 
 namespace {
 
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidManifest;
 using ::aapt::configuration::AndroidSdk;
-using ::aapt::configuration::Artifact;
+using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::configuration::DeviceFeature;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Locale;
@@ -50,6 +52,7 @@
 using ::android::ResTable_config;
 using ::android::base::StringPrintf;
 using ::testing::ElementsAre;
+using ::testing::SizeIs;
 
 constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
 <post-process xmlns="http://schemas.android.com/tools/aapt">
@@ -144,44 +147,47 @@
 
 TEST_F(ConfigurationParserTest, ValidateFile) {
   auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
-  auto result = parser.Parse();
+  auto result = parser.Parse("test.apk");
   ASSERT_TRUE(result);
-  PostProcessingConfiguration& value = result.value();
-  EXPECT_EQ(2ul, value.artifacts.size());
-  ASSERT_TRUE(value.artifact_format);
-  EXPECT_EQ(
-      "${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release",
-      value.artifact_format.value()
-  );
+  const std::vector<OutputArtifact>& value = result.value();
+  EXPECT_THAT(value, SizeIs(2ul));
 
-  EXPECT_EQ(2ul, value.abi_groups.size());
-  EXPECT_EQ(2ul, value.abi_groups["arm"].size());
-  EXPECT_EQ(2ul, value.abi_groups["other"].size());
+  const OutputArtifact& a1 = value[0];
+  EXPECT_EQ(a1.name, "art1.apk");
+  EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
+  EXPECT_THAT(a1.screen_densities,
+              ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("xxhdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
+  EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
+                                      test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
+  EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+  EXPECT_THAT(a1.textures, SizeIs(1ul));
+  EXPECT_THAT(a1.features, SizeIs(1ul));
 
-  EXPECT_EQ(2ul, value.screen_density_groups.size());
-  EXPECT_EQ(3ul, value.screen_density_groups["large"].size());
-  EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size());
-
-  EXPECT_EQ(2ul, value.locale_groups.size());
-  EXPECT_EQ(4ul, value.locale_groups["europe"].size());
-  EXPECT_EQ(3ul, value.locale_groups["north-america"].size());
-
-  EXPECT_EQ(1ul, value.android_sdk_groups.size());
-  EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version);
-  EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value());
-
-  EXPECT_EQ(1ul, value.gl_texture_groups.size());
-  EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
-
-  EXPECT_EQ(1ul, value.device_feature_groups.size());
-  EXPECT_EQ(1ul, value.device_feature_groups["low-latency"].size());
+  const OutputArtifact& a2 = value[1];
+  EXPECT_EQ(a2.name, "art2.apk");
+  EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
+  EXPECT_THAT(a2.screen_densities,
+              ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("mdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("hdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("xxhdpi").CopyWithoutSdkVersion(),
+                          test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
+  EXPECT_THAT(a2.locales,
+              ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
+                          test::ParseConfigOrDie("fr-rCA")));
+  EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+  EXPECT_THAT(a2.textures, SizeIs(1ul));
+  EXPECT_THAT(a2.features, SizeIs(1ul));
 }
 
 TEST_F(ConfigurationParserTest, InvalidNamespace) {
   constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
   <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
 
-  auto result = ConfigurationParser::ForContents(invalid_ns).Parse();
+  auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
   ASSERT_FALSE(result);
 }
 
@@ -197,9 +203,9 @@
           gl-texture-group="dxt1"
           device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_));
+    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
 
-    EXPECT_EQ(1ul, config.artifacts.size());
+    EXPECT_THAT(config.artifacts, SizeIs(1ul));
 
     auto& artifact = config.artifacts.back();
     EXPECT_FALSE(artifact.name);  // TODO: make this fail.
@@ -223,8 +229,8 @@
           gl-texture-group="dxt1"
           device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_EQ(2ul, config.artifacts.size());
+    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_THAT(config.artifacts, SizeIs(2ul));
     EXPECT_EQ(2, config.artifacts.back().version);
   }
 
@@ -240,8 +246,8 @@
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_EQ(3ul, config.artifacts.size());
+    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_THAT(config.artifacts, SizeIs(3ul));
     EXPECT_EQ(5, config.artifacts.back().version);
   }
 
@@ -256,8 +262,8 @@
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_EQ(4ul, config.artifacts.size());
+    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_THAT(config.artifacts, SizeIs(4ul));
     EXPECT_EQ(6, config.artifacts.back().version);
   }
 }
@@ -288,7 +294,7 @@
               device-feature-group="low-latency"/>
         </artifacts>
       </post-process>)xml";
-  auto result = ConfigurationParser::ForContents(configuration).Parse();
+  auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
   ASSERT_FALSE(result);
 }
 
@@ -299,7 +305,7 @@
     </artifact-format>)xml");
 
   PostProcessingConfiguration config;
-  bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = ArtifactFormatTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
   ASSERT_TRUE(config.artifact_format);
   EXPECT_EQ(
@@ -322,10 +328,10 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = abi_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  EXPECT_EQ(1ul, config.abi_groups.size());
+  EXPECT_THAT(config.abi_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.abi_groups.count("arm"));
 
   auto& out = config.abi_groups["arm"];
@@ -345,11 +351,10 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok =
-      screen_density_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  EXPECT_EQ(1ul, config.screen_density_groups.size());
+  EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.screen_density_groups.count("large"));
 
   ConfigDescription xhdpi;
@@ -375,7 +380,7 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = locale_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
   ASSERT_EQ(1ul, config.locale_groups.size());
@@ -407,7 +412,7 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
   ASSERT_EQ(1ul, config.android_sdk_groups.size());
@@ -434,7 +439,7 @@
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
     ASSERT_EQ(1ul, config.android_sdk_groups.size());
@@ -455,7 +460,7 @@
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
     ASSERT_EQ(1ul, config.android_sdk_groups.size());
@@ -476,7 +481,7 @@
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
     ASSERT_EQ(1ul, config.android_sdk_groups.size());
@@ -505,7 +510,7 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_FALSE(ok);
 }
 
@@ -526,7 +531,7 @@
   auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
 
   PostProcessingConfiguration config;
-  bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
   ASSERT_EQ(1ul, config.android_sdk_groups.size());
@@ -556,10 +561,10 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = gl_texture_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = GlTextureGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  EXPECT_EQ(1ul, config.gl_texture_groups.size());
+  EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
 
   auto& out = config.gl_texture_groups["dxt1"];
@@ -585,11 +590,10 @@
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok
-      = device_feature_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = DeviceFeatureGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  EXPECT_EQ(1ul, config.device_feature_groups.size());
+  EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
 
   auto& out = config.device_feature_groups["low-latency"];
@@ -603,14 +607,14 @@
 
 TEST(ArtifactTest, Simple) {
   StdErrDiagnostics diag;
-  Artifact x86;
+  ConfiguredArtifact x86;
   x86.abi_group = {"x86"};
 
   auto x86_result = x86.ToArtifactName("something.${abi}.apk", "", &diag);
   ASSERT_TRUE(x86_result);
   EXPECT_EQ(x86_result.value(), "something.x86.apk");
 
-  Artifact arm;
+  ConfiguredArtifact arm;
   arm.abi_group = {"armeabi-v7a"};
 
   {
@@ -640,7 +644,7 @@
 
 TEST(ArtifactTest, Complex) {
   StdErrDiagnostics diag;
-  Artifact artifact;
+  ConfiguredArtifact artifact;
   artifact.abi_group = {"mips64"};
   artifact.screen_density_group = {"ldpi"};
   artifact.device_feature_group = {"df1"};
@@ -686,7 +690,7 @@
 
 TEST(ArtifactTest, Missing) {
   StdErrDiagnostics diag;
-  Artifact x86;
+  ConfiguredArtifact x86;
   x86.abi_group = {"x86"};
 
   EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "", &diag));
@@ -697,7 +701,7 @@
 
 TEST(ArtifactTest, Empty) {
   StdErrDiagnostics diag;
-  Artifact artifact;
+  ConfiguredArtifact artifact;
 
   EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "", &diag));
   EXPECT_TRUE(artifact.ToArtifactName("something.apk", "", &diag));
@@ -707,7 +711,7 @@
 
 TEST(ArtifactTest, Repeated) {
   StdErrDiagnostics diag;
-  Artifact artifact;
+  ConfiguredArtifact artifact;
   artifact.screen_density_group = {"mdpi"};
 
   ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "", &diag));
@@ -717,7 +721,7 @@
 
 TEST(ArtifactTest, Nesting) {
   StdErrDiagnostics diag;
-  Artifact x86;
+  ConfiguredArtifact x86;
   x86.abi_group = {"x86"};
 
   EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", "", &diag));
@@ -729,7 +733,7 @@
 
 TEST(ArtifactTest, Recursive) {
   StdErrDiagnostics diag;
-  Artifact artifact;
+  ConfiguredArtifact artifact;
   artifact.device_feature_group = {"${gl}"};
   artifact.gl_texture_group = {"glx1"};
 
@@ -755,4 +759,6 @@
 }
 
 }  // namespace
+}  // namespace handler
+}  // namespace configuration
 }  // namespace aapt
diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp
index cb96235..9ace82a 100644
--- a/tools/aapt2/filter/AbiFilter.cpp
+++ b/tools/aapt2/filter/AbiFilter.cpp
@@ -25,7 +25,7 @@
 std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
   std::unordered_set<std::string> abi_set;
   for (auto& abi : abi_list) {
-    abi_set.insert(configuration::AbiToString(abi));
+    abi_set.insert(configuration::AbiToString(abi).to_string());
   }
   // Make unique by hand as the constructor is private.
   return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index e2d738a..16898d6 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -40,33 +40,11 @@
 namespace aapt {
 
 using ::aapt::configuration::AndroidSdk;
-using ::aapt::configuration::Artifact;
-using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::configuration::OutputArtifact;
 using ::aapt::xml::kSchemaAndroid;
 using ::aapt::xml::XmlResource;
 using ::android::StringPiece;
 
-namespace {
-
-Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config,
-                                IDiagnostics* diag) {
-  if (!artifact.android_sdk_group) {
-    return {};
-  }
-
-  const std::string& group_name = artifact.android_sdk_group.value();
-  auto group = config.android_sdk_groups.find(group_name);
-  // TODO: Remove validation when configuration parser ensures referential integrity.
-  if (group == config.android_sdk_groups.end()) {
-    diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'");
-    return {};
-  }
-
-  return group->second;
-}
-
-}  // namespace
-
 /**
  * Context wrapper that allows the min Android SDK value to be overridden.
  */
@@ -115,8 +93,9 @@
     min_sdk_ = min_sdk;
   }
 
-  void SetSource(const Source& source) {
-    source_diag_ = util::make_unique<SourcePathDiagnostics>(source, context_->GetDiagnostics());
+  void SetSource(const std::string& source) {
+    source_diag_ =
+        util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
   }
 
  private:
@@ -142,82 +121,60 @@
 
 bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
   // TODO(safarmer): Handle APK version codes for the generated APKs.
-  const PostProcessingConfiguration& config = options.config;
-
-  const std::string& apk_name = file::GetFilename(apk_->GetSource().path).to_string();
-  const StringPiece ext = file::GetExtension(apk_name);
-  const std::string base_name = apk_name.substr(0, apk_name.rfind(ext.to_string()));
 
   std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
   std::unordered_set<std::string> filtered_artifacts;
   std::unordered_set<std::string> kept_artifacts;
 
   // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
-  for (const Artifact& artifact : config.artifacts) {
-    SourcePathDiagnostics diag{{apk_name}, context_->GetDiagnostics()};
-
+  for (const OutputArtifact& artifact : options.apk_artifacts) {
     FilterChain filters;
 
-    if (!artifact.name && !config.artifact_format) {
-      diag.Error(
-          DiagMessage() << "Artifact does not have a name and no global name template defined");
-      return false;
-    }
-
-    Maybe<std::string> maybe_artifact_name =
-        (artifact.name) ? artifact.Name(apk_name, &diag)
-                        : artifact.ToArtifactName(config.artifact_format.value(), apk_name, &diag);
-
-    if (!maybe_artifact_name) {
-      diag.Error(DiagMessage() << "Could not determine split APK artifact name");
-      return false;
-    }
-
-    const std::string& artifact_name = maybe_artifact_name.value();
-
     ContextWrapper wrapped_context{context_};
-    wrapped_context.SetSource({artifact_name});
+    wrapped_context.SetSource(artifact.name);
 
     if (!options.kept_artifacts.empty()) {
-      const auto& it = artifacts_to_keep.find(artifact_name);
+      const auto& it = artifacts_to_keep.find(artifact.name);
       if (it == artifacts_to_keep.end()) {
-        filtered_artifacts.insert(artifact_name);
+        filtered_artifacts.insert(artifact.name);
         if (context_->IsVerbose()) {
-          context_->GetDiagnostics()->Note(DiagMessage(artifact_name) << "skipping artifact");
+          context_->GetDiagnostics()->Note(DiagMessage(artifact.name) << "skipping artifact");
         }
         continue;
       } else {
         artifacts_to_keep.erase(it);
-        kept_artifacts.insert(artifact_name);
+        kept_artifacts.insert(artifact.name);
       }
     }
 
     std::unique_ptr<ResourceTable> table =
-        FilterTable(artifact, config, *apk_->GetResourceTable(), &wrapped_context, &filters);
+        FilterTable(context_, artifact, *apk_->GetResourceTable(), &filters);
     if (!table) {
       return false;
     }
 
+    IDiagnostics* diag = wrapped_context.GetDiagnostics();
+
     std::unique_ptr<XmlResource> manifest;
-    if (!UpdateManifest(artifact, config, &manifest, &diag)) {
-      diag.Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact");
+    if (!UpdateManifest(artifact, &manifest, diag)) {
+      diag->Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact");
       return false;
     }
 
     std::string out = options.out_dir;
     if (!file::mkdirs(out)) {
-      diag.Warn(DiagMessage() << "could not create out dir: " << out);
+      diag->Warn(DiagMessage() << "could not create out dir: " << out);
     }
-    file::AppendPath(&out, artifact_name);
+    file::AppendPath(&out, artifact.name);
 
     if (context_->IsVerbose()) {
-      diag.Note(DiagMessage() << "Generating split: " << out);
+      diag->Note(DiagMessage() << "Generating split: " << out);
     }
 
-    std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(&diag, out);
+    std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(diag, out);
 
     if (context_->IsVerbose()) {
-      diag.Note(DiagMessage() << "Writing output: " << out);
+      diag->Note(DiagMessage() << "Writing output: " << out);
     }
 
     filters.AddFilter(util::make_unique<SignatureFilter>());
@@ -254,64 +211,34 @@
   return true;
 }
 
-std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(
-    const configuration::Artifact& artifact,
-    const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table,
-    IAaptContext* context, FilterChain* filters) {
+std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* context,
+                                                              const OutputArtifact& artifact,
+                                                              const ResourceTable& old_table,
+                                                              FilterChain* filters) {
   TableSplitterOptions splits;
   AxisConfigFilter axis_filter;
   ContextWrapper wrapped_context{context};
+  wrapped_context.SetSource(artifact.name);
 
-  if (artifact.abi_group) {
-    const std::string& group_name = artifact.abi_group.value();
-
-    auto group = config.abi_groups.find(group_name);
-    // TODO: Remove validation when configuration parser ensures referential integrity.
-    if (group == config.abi_groups.end()) {
-      context->GetDiagnostics()->Error(DiagMessage() << "could not find referenced ABI group '"
-                                                     << group_name << "'");
-      return {};
-    }
-    filters->AddFilter(AbiFilter::FromAbiList(group->second));
+  if (!artifact.abis.empty()) {
+    filters->AddFilter(AbiFilter::FromAbiList(artifact.abis));
   }
 
-  if (artifact.screen_density_group) {
-    const std::string& group_name = artifact.screen_density_group.value();
-
-    auto group = config.screen_density_groups.find(group_name);
-    // TODO: Remove validation when configuration parser ensures referential integrity.
-    if (group == config.screen_density_groups.end()) {
-      context->GetDiagnostics()->Error(DiagMessage()
-                                       << "could not find referenced group '" << group_name << "'");
-      return {};
-    }
-
-    const std::vector<ConfigDescription>& densities = group->second;
-    for (const auto& density_config : densities) {
+  if (!artifact.screen_densities.empty()) {
+    for (const auto& density_config : artifact.screen_densities) {
       splits.preferred_densities.push_back(density_config.density);
     }
   }
 
-  if (artifact.locale_group) {
-    const std::string& group_name = artifact.locale_group.value();
-    auto group = config.locale_groups.find(group_name);
-    // TODO: Remove validation when configuration parser ensures referential integrity.
-    if (group == config.locale_groups.end()) {
-      context->GetDiagnostics()->Error(DiagMessage()
-                                       << "could not find referenced group '" << group_name << "'");
-      return {};
-    }
-
-    const std::vector<ConfigDescription>& locales = group->second;
-    for (const auto& locale : locales) {
+  if (!artifact.locales.empty()) {
+    for (const auto& locale : artifact.locales) {
       axis_filter.AddConfig(locale);
     }
     splits.config_filter = &axis_filter;
   }
 
-  Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context->GetDiagnostics());
-  if (sdk && sdk.value().min_sdk_version) {
-    wrapped_context.SetMinSdkVersion(sdk.value().min_sdk_version.value());
+  if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
+    wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
   }
 
   std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -327,8 +254,7 @@
   return table;
 }
 
-bool MultiApkGenerator::UpdateManifest(const Artifact& artifact,
-                                       const PostProcessingConfiguration& config,
+bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
                                        std::unique_ptr<XmlResource>* updated_manifest,
                                        IDiagnostics* diag) {
   const xml::XmlResource* apk_manifest = apk_->GetManifest();
@@ -367,10 +293,9 @@
   versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
 
   // Check to see if the minSdkVersion needs to be updated.
-  Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag);
-  if (maybe_sdk) {
+  if (artifact.android_sdk) {
     // TODO(safarmer): Handle the rest of the Android SDK.
-    const AndroidSdk& android_sdk = maybe_sdk.value();
+    const AndroidSdk& android_sdk = artifact.android_sdk.value();
 
     if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
       if (xml::Attribute* min_sdk_attr =
@@ -392,10 +317,7 @@
     }
   }
 
-  if (artifact.screen_density_group) {
-    auto densities = config.screen_density_groups.find(artifact.screen_density_group.value());
-    CHECK(densities != config.screen_density_groups.end()) << "Missing density group";
-
+  if (!artifact.screen_densities.empty()) {
     xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
     if (!screens_el) {
       // create a new element.
@@ -408,7 +330,7 @@
       screens_el->GetChildElements().clear();
     }
 
-    for (const auto& density : densities->second) {
+    for (const auto& density : artifact.screen_densities) {
       std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>();
       screen_el->name = "screen";
       const char* density_str = density.toString().string();
diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h
index dedb610..19f64cc 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.h
+++ b/tools/aapt2/optimize/MultiApkGenerator.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <string>
 #include <unordered_set>
+#include <vector>
 
 #include "Diagnostics.h"
 #include "LoadedApk.h"
@@ -29,7 +30,7 @@
 
 struct MultiApkGeneratorOptions {
   std::string out_dir;
-  configuration::PostProcessingConfiguration config;
+  std::vector<configuration::OutputArtifact> apk_artifacts;
   TableFlattenerOptions table_flattener_options;
   std::unordered_set<std::string> kept_artifacts;
 };
@@ -49,18 +50,17 @@
   bool FromBaseApk(const MultiApkGeneratorOptions& options);
 
  protected:
-  virtual std::unique_ptr<ResourceTable> FilterTable(
-      const configuration::Artifact& artifact,
-      const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table,
-      IAaptContext* context, FilterChain* chain);
+  virtual std::unique_ptr<ResourceTable> FilterTable(IAaptContext* context,
+                                                     const configuration::OutputArtifact& artifact,
+                                                     const ResourceTable& old_table,
+                                                     FilterChain* chain);
 
  private:
   IDiagnostics* GetDiagnostics() {
     return context_->GetDiagnostics();
   }
 
-  bool UpdateManifest(const configuration::Artifact& artifact,
-                      const configuration::PostProcessingConfiguration& config,
+  bool UpdateManifest(const configuration::OutputArtifact& artifact,
                       std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag);
 
   LoadedApk* apk_;
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index 2d303e2..e1d951f 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -36,8 +36,7 @@
 
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidSdk;
-using ::aapt::configuration::Artifact;
-using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::configuration::OutputArtifact;
 using ::aapt::test::GetValue;
 using ::aapt::test::GetValueForConfig;
 using ::aapt::test::ParseConfigOrDie;
@@ -48,7 +47,6 @@
 using ::testing::PrintToString;
 using ::testing::Return;
 using ::testing::Test;
-using ::testing::_;
 
 /**
  * Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to
@@ -60,11 +58,11 @@
       : MultiApkGenerator(apk, context) {
   }
 
-  std::unique_ptr<ResourceTable> FilterTable(
-      const configuration::Artifact& artifact,
-      const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table,
-      IAaptContext* context, FilterChain* filter_chain) override {
-    return MultiApkGenerator::FilterTable(artifact, config, old_table, context, filter_chain);
+  std::unique_ptr<ResourceTable> FilterTable(IAaptContext* context,
+                                             const configuration::OutputArtifact& artifact,
+                                             const ResourceTable& old_table,
+                                             FilterChain* filter_chain) override {
+    return MultiApkGenerator::FilterTable(context, artifact, old_table, filter_chain);
   }
 };
 
@@ -108,27 +106,13 @@
 
   LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
-  PostProcessingConfiguration empty_config;
-  TableFlattenerOptions table_flattener_options;
   FilterChain chain;
 
-  Artifact x64 = test::ArtifactBuilder()
-                     .SetName("${basename}.${sdk}.apk")
-                     .SetDensityGroup("xhdpi")
-                     .SetAndroidSdk("v23")
-                     .Build();
-
-  auto config = test::PostProcessingConfigurationBuilder()
-                    .SetLocaleGroup("en", {"en"})
-                    .SetAbiGroup("x64", {Abi::kX86_64})
-                    .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23))
-                    .AddArtifact(x64)
-                    .Build();
+  OutputArtifact artifact = test::ArtifactBuilder().AddDensity(xhdpi_).SetAndroidSdk(23).Build();
 
   MultiApkGeneratorWrapper generator{&apk, ctx.get()};
   std::unique_ptr<ResourceTable> split =
-      generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain);
+      generator.FilterTable(ctx.get(), artifact, *apk.GetResourceTable(), &chain);
 
   ResourceTable* new_table = split.get();
   EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull());
@@ -149,27 +133,13 @@
 
   LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
-  PostProcessingConfiguration empty_config;
-  TableFlattenerOptions table_flattener_options;
   FilterChain chain;
 
-  Artifact x64 = test::ArtifactBuilder()
-                     .SetName("${basename}.${sdk}.apk")
-                     .SetDensityGroup("xhdpi")
-                     .SetAndroidSdk("v4")
-                     .Build();
-
-  auto config = test::PostProcessingConfigurationBuilder()
-                    .SetLocaleGroup("en", {"en"})
-                    .SetAbiGroup("x64", {Abi::kX86_64})
-                    .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4))
-                    .AddArtifact(x64)
-                    .Build();
+  OutputArtifact artifact = test::ArtifactBuilder().AddDensity(xhdpi_).SetAndroidSdk(4).Build();
 
   MultiApkGeneratorWrapper generator{&apk, ctx.get()};;
   std::unique_ptr<ResourceTable> split =
-      generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain);
+      generator.FilterTable(ctx.get(), artifact, *apk.GetResourceTable(), &chain);
 
   ResourceTable* new_table = split.get();
   EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull());
@@ -188,23 +158,13 @@
 
   LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
-  PostProcessingConfiguration empty_config;
-  TableFlattenerOptions table_flattener_options;
   FilterChain chain;
 
-  Artifact x64 =
-      test::ArtifactBuilder().SetName("${basename}.${sdk}.apk").SetDensityGroup("xhdpi").Build();
-
-  auto config = test::PostProcessingConfigurationBuilder()
-                    .SetLocaleGroup("en", {"en"})
-                    .SetAbiGroup("x64", {Abi::kX86_64})
-                    .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .AddArtifact(x64)
-                    .Build();
+  OutputArtifact artifact = test::ArtifactBuilder().AddDensity(xhdpi_).Build();
 
   MultiApkGeneratorWrapper generator{&apk, ctx.get()};
   std::unique_ptr<ResourceTable> split =
-      generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain);
+      generator.FilterTable(ctx.get(), artifact, *apk.GetResourceTable(), &chain);
 
   ResourceTable* new_table = split.get();
   EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull());
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index ecec63f..88897a8 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -25,7 +25,7 @@
 
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidSdk;
-using ::aapt::configuration::Artifact;
+using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::io::StringInputStream;
 using ::android::StringPiece;
 
@@ -221,72 +221,32 @@
   return doc;
 }
 
-PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAbiGroup(
-    const std::string& name, const std::vector<Abi>& abis) {
-  config_.abi_groups[name] = abis;
-  return *this;
-}
-
-PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetLocaleGroup(
-    const std::string& name, const std::vector<std::string>& locales) {
-  auto& group = config_.locale_groups[name];
-  for (const auto& locale : locales) {
-    group.push_back(ParseConfigOrDie(locale));
-  }
-  return *this;
-}
-
-PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetDensityGroup(
-    const std::string& name, const std::vector<std::string>& densities) {
-  auto& group = config_.screen_density_groups[name];
-  for (const auto& density : densities) {
-    group.push_back(ParseConfigOrDie(density));
-  }
-  return *this;
-}
-
-PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAndroidSdk(
-    const std::string& name, const AndroidSdk& sdk) {
-  config_.android_sdk_groups[name] = sdk;
-  return *this;
-}
-
-PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
-    const Artifact& artifact) {
-  config_.artifacts.push_back(artifact);
-  return *this;
-}
-
-configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
-  return config_;
-}
-
 ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) {
-  artifact_.name = {name};
+  artifact_.name = name;
   return *this;
 }
 
-ArtifactBuilder& ArtifactBuilder::SetAbiGroup(const std::string& name) {
-  artifact_.abi_group = {name};
+ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
+  artifact_.abis.push_back(abi);
   return *this;
 }
 
-ArtifactBuilder& ArtifactBuilder::SetDensityGroup(const std::string& name) {
-  artifact_.screen_density_group = {name};
+ArtifactBuilder& ArtifactBuilder::AddDensity(const ConfigDescription& density) {
+  artifact_.screen_densities.push_back(density);
   return *this;
 }
 
-ArtifactBuilder& ArtifactBuilder::SetLocaleGroup(const std::string& name) {
-  artifact_.locale_group = {name};
+ArtifactBuilder& ArtifactBuilder::AddLocale(const ConfigDescription& locale) {
+  artifact_.locales.push_back(locale);
   return *this;
 }
 
-ArtifactBuilder& ArtifactBuilder::SetAndroidSdk(const std::string& name) {
-  artifact_.android_sdk_group = {name};
+ArtifactBuilder& ArtifactBuilder::SetAndroidSdk(int min_sdk) {
+  artifact_.android_sdk = {AndroidSdk::ForMinSdk(min_sdk)};
   return *this;
 }
 
-configuration::Artifact ArtifactBuilder::Build() {
+configuration::OutputArtifact ArtifactBuilder::Build() {
   return artifact_;
 }
 
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 4cdfc33..2f83b78 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -25,6 +25,7 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "configuration/ConfigurationParser.h"
+#include "configuration/ConfigurationParser.internal.h"
 #include "process/IResourceTableConsumer.h"
 #include "test/Common.h"
 #include "util/Maybe.h"
@@ -154,38 +155,19 @@
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
                                                             const android::StringPiece& str);
 
-class PostProcessingConfigurationBuilder {
- public:
-  PostProcessingConfigurationBuilder() = default;
-
-  PostProcessingConfigurationBuilder& SetAbiGroup(const std::string& name,
-                                                  const std::vector<configuration::Abi>& abis);
-  PostProcessingConfigurationBuilder& SetLocaleGroup(const std::string& name,
-                                                     const std::vector<std::string>& locales);
-  PostProcessingConfigurationBuilder& SetDensityGroup(const std::string& name,
-                                                      const std::vector<std::string>& densities);
-  PostProcessingConfigurationBuilder& SetAndroidSdk(const std::string& name,
-                                                    const configuration::AndroidSdk& sdk);
-  PostProcessingConfigurationBuilder& AddArtifact(const configuration::Artifact& artifact);
-  configuration::PostProcessingConfiguration Build();
-
- private:
-  configuration::PostProcessingConfiguration config_;
-};
-
 class ArtifactBuilder {
  public:
   ArtifactBuilder() = default;
 
   ArtifactBuilder& SetName(const std::string& name);
-  ArtifactBuilder& SetAbiGroup(const std::string& name);
-  ArtifactBuilder& SetDensityGroup(const std::string& name);
-  ArtifactBuilder& SetLocaleGroup(const std::string& name);
-  ArtifactBuilder& SetAndroidSdk(const std::string& name);
-  configuration::Artifact Build();
+  ArtifactBuilder& AddAbi(configuration::Abi abi);
+  ArtifactBuilder& AddDensity(const ConfigDescription& density);
+  ArtifactBuilder& AddLocale(const ConfigDescription& locale);
+  ArtifactBuilder& SetAndroidSdk(int min_sdk);
+  configuration::OutputArtifact Build();
 
  private:
-  configuration::Artifact artifact_;
+  configuration::OutputArtifact artifact_;
 };
 
 }  // namespace test