AAPT2: Refactor flags into commands

Refactors the flag based command invocation into classes that make using
subcommands easier.

Test: manual tests of printing
Change-Id: Ic8df6af0be30db552e32150afebecbfeec7e1075
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 1a2da7f..26770d1 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "Link.h"
+
 #include <sys/stat.h>
 #include <cinttypes>
 
@@ -29,7 +31,6 @@
 
 #include "AppInfo.h"
 #include "Debug.h"
-#include "Flags.h"
 #include "LoadedApk.h"
 #include "Locale.h"
 #include "NameMangler.h"
@@ -74,74 +75,6 @@
 
 namespace aapt {
 
-enum class OutputFormat {
-  kApk,
-  kProto,
-};
-
-struct LinkOptions {
-  std::string output_path;
-  std::string manifest_path;
-  std::vector<std::string> include_paths;
-  std::vector<std::string> overlay_files;
-  std::vector<std::string> assets_dirs;
-  bool output_to_directory = false;
-  bool auto_add_overlay = false;
-  OutputFormat output_format = OutputFormat::kApk;
-
-  // Java/Proguard options.
-  Maybe<std::string> generate_java_class_path;
-  Maybe<std::string> custom_java_package;
-  std::set<std::string> extra_java_packages;
-  Maybe<std::string> generate_text_symbols_path;
-  Maybe<std::string> generate_proguard_rules_path;
-  Maybe<std::string> generate_main_dex_proguard_rules_path;
-  bool generate_conditional_proguard_rules = false;
-  bool generate_non_final_ids = false;
-  std::vector<std::string> javadoc_annotations;
-  Maybe<std::string> private_symbols;
-
-  // Optimizations/features.
-  bool no_auto_version = false;
-  bool no_version_vectors = false;
-  bool no_version_transitions = false;
-  bool no_resource_deduping = false;
-  bool no_xml_namespaces = false;
-  bool do_not_compress_anything = false;
-  std::unordered_set<std::string> extensions_to_not_compress;
-
-  // Static lib options.
-  bool no_static_lib_packages = false;
-  bool auto_namespace_static_lib = false;
-
-  // AndroidManifest.xml massaging options.
-  ManifestFixerOptions manifest_fixer_options;
-
-  // Products to use/filter on.
-  std::unordered_set<std::string> products;
-
-  // Flattening options.
-  TableFlattenerOptions table_flattener_options;
-
-  // Split APK options.
-  TableSplitterOptions table_splitter_options;
-  std::vector<SplitConstraints> split_constraints;
-  std::vector<std::string> split_paths;
-
-  // Stable ID options.
-  std::unordered_map<ResourceName, ResourceId> stable_id_map;
-  Maybe<std::string> resource_id_map_path;
-
-  // When 'true', allow reserved package IDs to be used for applications. Pre-O, the platform
-  // treats negative resource IDs [those with a package ID of 0x80 or higher] as invalid.
-  // In order to work around this limitation, we allow the use of traditionally reserved
-  // resource IDs [those between 0x02 and 0x7E].
-  bool allow_reserved_package_id = false;
-
-  // Whether we should fail on definitions of a resource with conflicting visibility.
-  bool strict_visibility = false;
-};
-
 class LinkContext : public IAaptContext {
  public:
   LinkContext(IDiagnostics* diagnostics)
@@ -785,9 +718,9 @@
   return table.getTableCookie(idx);
 }
 
-class LinkCommand {
+class Linker {
  public:
-  LinkCommand(LinkContext* context, const LinkOptions& options)
+  Linker(LinkContext* context, const LinkOptions& options)
       : options_(options),
         context_(context),
         final_table_(),
@@ -2040,197 +1973,12 @@
   Maybe<std::string> included_feature_base_;
 };
 
-int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
-  LinkContext context(diagnostics);
-  LinkOptions options;
-  std::vector<std::string> overlay_arg_list;
-  std::vector<std::string> extra_java_packages;
-  Maybe<std::string> package_id;
-  std::vector<std::string> configs;
-  Maybe<std::string> preferred_density;
-  Maybe<std::string> product_list;
-  bool legacy_x_flag = false;
-  bool require_localization = false;
-  bool verbose = false;
-  bool shared_lib = false;
-  bool static_lib = false;
-  bool proto_format = false;
-  Maybe<std::string> stable_id_file_path;
-  std::vector<std::string> split_args;
-  Flags flags =
-      Flags()
-          .RequiredFlag("-o", "Output path.", &options.output_path)
-          .RequiredFlag("--manifest", "Path to the Android manifest to build.",
-                        &options.manifest_path)
-          .OptionalFlagList("-I", "Adds an Android APK to link against.", &options.include_paths)
-          .OptionalFlagList("-A",
-                            "An assets directory to include in the APK. These are unprocessed.",
-                            &options.assets_dirs)
-          .OptionalFlagList("-R",
-                            "Compilation unit to link, using `overlay` semantics.\n"
-                            "The last conflicting resource given takes precedence.",
-                            &overlay_arg_list)
-          .OptionalFlag("--package-id",
-                        "Specify the package ID to use for this app. Must be greater or equal to\n"
-                        "0x7f and can't be used with --static-lib or --shared-lib.",
-                        &package_id)
-          .OptionalFlag("--java", "Directory in which to generate R.java.",
-                        &options.generate_java_class_path)
-          .OptionalFlag("--proguard", "Output file for generated Proguard rules.",
-                        &options.generate_proguard_rules_path)
-          .OptionalFlag("--proguard-main-dex",
-                        "Output file for generated Proguard rules for the main dex.",
-                        &options.generate_main_dex_proguard_rules_path)
-          .OptionalSwitch("--proguard-conditional-keep-rules",
-                          "Generate conditional Proguard keep rules.",
-                          &options.generate_conditional_proguard_rules)
-          .OptionalSwitch("--no-auto-version",
-                          "Disables automatic style and layout SDK versioning.",
-                          &options.no_auto_version)
-          .OptionalSwitch("--no-version-vectors",
-                          "Disables automatic versioning of vector drawables. Use this only\n"
-                          "when building with vector drawable support library.",
-                          &options.no_version_vectors)
-          .OptionalSwitch("--no-version-transitions",
-                          "Disables automatic versioning of transition resources. Use this only\n"
-                          "when building with transition support library.",
-                          &options.no_version_transitions)
-          .OptionalSwitch("--no-resource-deduping",
-                          "Disables automatic deduping of resources with\n"
-                          "identical values across compatible configurations.",
-                          &options.no_resource_deduping)
-          .OptionalSwitch("--enable-sparse-encoding",
-                          "Enables encoding sparse entries using a binary search tree.\n"
-                          "This decreases APK size at the cost of resource retrieval performance.",
-                          &options.table_flattener_options.use_sparse_entries)
-          .OptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
-                          &legacy_x_flag)
-          .OptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
-                          &require_localization)
-          .OptionalFlagList("-c",
-                            "Comma separated list of configurations to include. The default\n"
-                            "is all configurations.",
-                            &configs)
-          .OptionalFlag("--preferred-density",
-                        "Selects the closest matching density and strips out all others.",
-                        &preferred_density)
-          .OptionalFlag("--product", "Comma separated list of product names to keep", &product_list)
-          .OptionalSwitch("--output-to-dir",
-                          "Outputs the APK contents to a directory specified by -o.",
-                          &options.output_to_directory)
-          .OptionalSwitch("--no-xml-namespaces",
-                          "Removes XML namespace prefix and URI information from\n"
-                          "AndroidManifest.xml and XML binaries in res/*.",
-                          &options.no_xml_namespaces)
-          .OptionalFlag("--min-sdk-version",
-                        "Default minimum SDK version to use for AndroidManifest.xml.",
-                        &options.manifest_fixer_options.min_sdk_version_default)
-          .OptionalFlag("--target-sdk-version",
-                        "Default target SDK version to use for AndroidManifest.xml.",
-                        &options.manifest_fixer_options.target_sdk_version_default)
-          .OptionalFlag("--version-code",
-                        "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
-                        "present.",
-                        &options.manifest_fixer_options.version_code_default)
-          .OptionalFlag("--version-name",
-                        "Version name to inject into the AndroidManifest.xml if none is present.",
-                        &options.manifest_fixer_options.version_name_default)
-          .OptionalSwitch("--replace-version",
-                         "If --version-code and/or --version-name are specified, these\n"
-                         "values will replace any value already in the manifest. By\n"
-                         "default, nothing is changed if the manifest already defines\n"
-                         "these attributes.",
-                         &options.manifest_fixer_options.replace_version)
-          .OptionalFlag("--compile-sdk-version-code",
-                        "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
-                        "present.",
-                        &options.manifest_fixer_options.compile_sdk_version)
-          .OptionalFlag("--compile-sdk-version-name",
-                        "Version name to inject into the AndroidManifest.xml if none is present.",
-                        &options.manifest_fixer_options.compile_sdk_version_codename)
-          .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
-                          &shared_lib)
-          .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib)
-          .OptionalSwitch("--proto-format",
-                          "Generates compiled resources in Protobuf format.\n"
-                          "Suitable as input to the bundle tool for generating an App Bundle.",
-                          &proto_format)
-          .OptionalSwitch("--no-static-lib-packages",
-                          "Merge all library resources under the app's package.",
-                          &options.no_static_lib_packages)
-          .OptionalSwitch("--auto-namespace-static-lib",
-                          "Automatically namespace resource references when building a static\n"
-                          "library.",
-                          &options.auto_namespace_static_lib)
-          .OptionalSwitch("--non-final-ids",
-                          "Generates R.java without the final modifier. This is implied when\n"
-                          "--static-lib is specified.",
-                          &options.generate_non_final_ids)
-          .OptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
-                        &stable_id_file_path)
-          .OptionalFlag("--emit-ids",
-                        "Emit a file at the given path with a list of name to ID mappings,\n"
-                        "suitable for use with --stable-ids.",
-                        &options.resource_id_map_path)
-          .OptionalFlag("--private-symbols",
-                        "Package name to use when generating R.java for private symbols.\n"
-                        "If not specified, public and private symbols will use the application's\n"
-                        "package name.",
-                        &options.private_symbols)
-          .OptionalFlag("--custom-package", "Custom Java package under which to generate R.java.",
-                        &options.custom_java_package)
-          .OptionalFlagList("--extra-packages",
-                            "Generate the same R.java but with different package names.",
-                            &extra_java_packages)
-          .OptionalFlagList("--add-javadoc-annotation",
-                            "Adds a JavaDoc annotation to all generated Java classes.",
-                            &options.javadoc_annotations)
-          .OptionalFlag("--output-text-symbols",
-                        "Generates a text file containing the resource symbols of the R class in\n"
-                        "the specified folder.",
-                        &options.generate_text_symbols_path)
-          .OptionalSwitch("--allow-reserved-package-id",
-                          "Allows the use of a reserved package ID. This should on be used for\n"
-                          "packages with a pre-O min-sdk\n",
-                          &options.allow_reserved_package_id)
-          .OptionalSwitch("--auto-add-overlay",
-                          "Allows the addition of new resources in overlays without\n"
-                          "<add-resource> tags.",
-                          &options.auto_add_overlay)
-          .OptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
-                        &options.manifest_fixer_options.rename_manifest_package)
-          .OptionalFlag("--rename-instrumentation-target-package",
-                        "Changes the name of the target package for instrumentation. Most useful\n"
-                        "when used in conjunction with --rename-manifest-package.",
-                        &options.manifest_fixer_options.rename_instrumentation_target_package)
-          .OptionalFlagList("-0", "File extensions not to compress.",
-                            &options.extensions_to_not_compress)
-          .OptionalSwitch("--no-compress", "Do not compress any resources.",
-                          &options.do_not_compress_anything)
-          .OptionalSwitch("--warn-manifest-validation",
-                          "Treat manifest validation errors as warnings.",
-                          &options.manifest_fixer_options.warn_validation)
-          .OptionalFlagList("--split",
-                            "Split resources matching a set of configs out to a Split APK.\n"
-                            "Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
-                            "On Windows, use a semicolon ';' separator instead.",
-                            &split_args)
-          .OptionalSwitch("-v", "Enables verbose logging.", &verbose)
-          .OptionalSwitch("--debug-mode",
-                          "Inserts android:debuggable=\"true\" in to the application node of the\n"
-                          "manifest, making the application debuggable even on production devices.",
-                          &options.manifest_fixer_options.debug_mode)
-          .OptionalSwitch("--strict-visibility",
-                          "Do not allow overlays with different visibility levels.",
-                          &options.strict_visibility);
-
-  if (!flags.Parse("aapt2 link", args, &std::cerr)) {
-    return 1;
-  }
+int LinkCommand::Action(const std::vector<std::string>& args) {
+  LinkContext context(diag_);
 
   // Expand all argument-files passed into the command line. These start with '@'.
   std::vector<std::string> arg_list;
-  for (const std::string& arg : flags.GetArgs()) {
+  for (const std::string& arg : args) {
     if (util::StartsWith(arg, "@")) {
       const std::string path = arg.substr(1, arg.size() - 1);
       std::string error;
@@ -2244,27 +1992,27 @@
   }
 
   // Expand all argument-files passed to -R.
-  for (const std::string& arg : overlay_arg_list) {
+  for (const std::string& arg : overlay_arg_list_) {
     if (util::StartsWith(arg, "@")) {
       const std::string path = arg.substr(1, arg.size() - 1);
       std::string error;
-      if (!file::AppendArgsFromFile(path, &options.overlay_files, &error)) {
+      if (!file::AppendArgsFromFile(path, &options_.overlay_files, &error)) {
         context.GetDiagnostics()->Error(DiagMessage(path) << error);
         return 1;
       }
     } else {
-      options.overlay_files.push_back(arg);
+      options_.overlay_files.push_back(arg);
     }
   }
 
-  if (verbose) {
-    context.SetVerbose(verbose);
+  if (verbose_) {
+    context.SetVerbose(verbose_);
   }
 
-  if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) {
+  if (int{shared_lib_} + int{static_lib_} + int{proto_format_} > 1) {
     context.GetDiagnostics()->Error(
         DiagMessage()
-        << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
+            << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
     return 1;
   }
 
@@ -2272,18 +2020,18 @@
   context.SetPackageType(PackageType::kApp);
   context.SetPackageId(kAppPackageId);
 
-  if (shared_lib) {
+  if (shared_lib_) {
     context.SetPackageType(PackageType::kSharedLib);
     context.SetPackageId(0x00);
-  } else if (static_lib) {
+  } else if (static_lib_) {
     context.SetPackageType(PackageType::kStaticLib);
-    options.output_format = OutputFormat::kProto;
-  } else if (proto_format) {
-    options.output_format = OutputFormat::kProto;
+    options_.output_format = OutputFormat::kProto;
+  } else if (proto_format_) {
+    options_.output_format = OutputFormat::kProto;
   }
 
-  if (options.auto_namespace_static_lib) {
-    if (!static_lib) {
+  if (options_.auto_namespace_static_lib) {
+    if (!static_lib_) {
       context.GetDiagnostics()->Error(
           DiagMessage() << "--auto-namespace-static-lib can only be used with --static-lib");
       return 1;
@@ -2291,16 +2039,16 @@
     context.SetAutoNamespace(true);
   }
 
-  if (package_id) {
+  if (package_id_) {
     if (context.GetPackageType() != PackageType::kApp) {
       context.GetDiagnostics()->Error(
           DiagMessage() << "can't specify --package-id when not building a regular app");
       return 1;
     }
 
-    const Maybe<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id.value());
+    const Maybe<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id_.value());
     if (!maybe_package_id_int) {
-      context.GetDiagnostics()->Error(DiagMessage() << "package ID '" << package_id.value()
+      context.GetDiagnostics()->Error(DiagMessage() << "package ID '" << package_id_.value()
                                                     << "' is not a valid integer");
       return 1;
     }
@@ -2308,7 +2056,7 @@
     const uint32_t package_id_int = maybe_package_id_int.value();
     if (package_id_int > std::numeric_limits<uint8_t>::max()
         || package_id_int == kFrameworkPackageId
-        || (!options.allow_reserved_package_id && package_id_int < kAppPackageId)) {
+        || (!options_.allow_reserved_package_id && package_id_int < kAppPackageId)) {
       context.GetDiagnostics()->Error(
           DiagMessage() << StringPrintf(
               "invalid package ID 0x%02x. Must be in the range 0x7f-0xff.", package_id_int));
@@ -2318,71 +2066,71 @@
   }
 
   // Populate the set of extra packages for which to generate R.java.
-  for (std::string& extra_package : extra_java_packages) {
+  for (std::string& extra_package : extra_java_packages_) {
     // A given package can actually be a colon separated list of packages.
     for (StringPiece package : util::Split(extra_package, ':')) {
-      options.extra_java_packages.insert(package.to_string());
+      options_.extra_java_packages.insert(package.to_string());
     }
   }
 
-  if (product_list) {
-    for (StringPiece product : util::Tokenize(product_list.value(), ',')) {
+  if (product_list_) {
+    for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
       if (product != "" && product != "default") {
-        options.products.insert(product.to_string());
+        options_.products.insert(product.to_string());
       }
     }
   }
 
   std::unique_ptr<IConfigFilter> filter;
-  if (!configs.empty()) {
-    filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
+  if (!configs_.empty()) {
+    filter = ParseConfigFilterParameters(configs_, context.GetDiagnostics());
     if (filter == nullptr) {
       return 1;
     }
-    options.table_splitter_options.config_filter = filter.get();
+    options_.table_splitter_options.config_filter = filter.get();
   }
 
-  if (preferred_density) {
+  if (preferred_density_) {
     Maybe<uint16_t> density =
-        ParseTargetDensityParameter(preferred_density.value(), context.GetDiagnostics());
+        ParseTargetDensityParameter(preferred_density_.value(), context.GetDiagnostics());
     if (!density) {
       return 1;
     }
-    options.table_splitter_options.preferred_densities.push_back(density.value());
+    options_.table_splitter_options.preferred_densities.push_back(density.value());
   }
 
   // Parse the split parameters.
-  for (const std::string& split_arg : split_args) {
-    options.split_paths.push_back({});
-    options.split_constraints.push_back({});
-    if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
-                             &options.split_constraints.back())) {
+  for (const std::string& split_arg : split_args_) {
+    options_.split_paths.push_back({});
+    options_.split_constraints.push_back({});
+    if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options_.split_paths.back(),
+        &options_.split_constraints.back())) {
       return 1;
     }
   }
 
-  if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path) {
-    if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(),
-                         &options.stable_id_map)) {
+  if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) {
+    if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(),
+        &options_.stable_id_map)) {
       return 1;
     }
   }
 
   // Populate some default no-compress extensions that are already compressed.
-  options.extensions_to_not_compress.insert(
+  options_.extensions_to_not_compress.insert(
       {".jpg",   ".jpeg", ".png",  ".gif", ".wav",  ".mp2",  ".mp3",  ".ogg",
-       ".aac",   ".mpg",  ".mpeg", ".mid", ".midi", ".smf",  ".jet",  ".rtttl",
-       ".imy",   ".xmf",  ".mp4",  ".m4a", ".m4v",  ".3gp",  ".3gpp", ".3g2",
-       ".3gpp2", ".amr",  ".awb",  ".wma", ".wmv",  ".webm", ".mkv"});
+          ".aac",   ".mpg",  ".mpeg", ".mid", ".midi", ".smf",  ".jet",  ".rtttl",
+          ".imy",   ".xmf",  ".mp4",  ".m4a", ".m4v",  ".3gp",  ".3gpp", ".3g2",
+          ".3gpp2", ".amr",  ".awb",  ".wma", ".wmv",  ".webm", ".mkv"});
 
   // Turn off auto versioning for static-libs.
   if (context.GetPackageType() == PackageType::kStaticLib) {
-    options.no_auto_version = true;
-    options.no_version_vectors = true;
-    options.no_version_transitions = true;
+    options_.no_auto_version = true;
+    options_.no_version_vectors = true;
+    options_.no_version_transitions = true;
   }
 
-  LinkCommand cmd(&context, options);
+  Linker cmd(&context, options_);
   return cmd.Run(arg_list);
 }