Parse option -flattenpackagehierarchy [package_name].

Bug: 37764746
Change-Id: I03934d0341b1ff8981945c2a2a16124f0fbb903d
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 241746b..86844a3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -379,6 +380,7 @@
 
     // TODO(zerny): Consider which other proguard options should be given flags.
     assert internal.packagePrefix.length() == 0;
+    internal.packageObfuscationMode = proguardConfiguration.getPackageObfuscationMode();
     internal.packagePrefix = proguardConfiguration.getPackagePrefix();
     assert internal.allowAccessModification;
     internal.allowAccessModification = proguardConfiguration.getAllowAccessModification();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 0338533..72ad9f3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.DictionaryReader;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
@@ -19,6 +20,7 @@
 
     private final List<Path> injars = new ArrayList<>();
     private final List<Path> libraryjars = new ArrayList<>();
+    private PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE;
     private String packagePrefix = null;
     private boolean allowAccessModification = false;
     private boolean ignoreWarnings = false;
@@ -53,7 +55,17 @@
       this.libraryjars.addAll(libraryJars);
     }
 
+    public PackageObfuscationMode getPackageObfuscationMode() {
+      return packageObfuscationMode;
+    }
+
     public void setPackagePrefix(String packagePrefix) {
+      packageObfuscationMode = PackageObfuscationMode.REPACKAGE;
+      this.packagePrefix = packagePrefix;
+    }
+
+    public void setFlattenPackagePrefix(String packagePrefix) {
+      packageObfuscationMode = PackageObfuscationMode.FLATTEN;
       this.packagePrefix = packagePrefix;
     }
 
@@ -139,6 +151,7 @@
           dexItemFactory,
           injars,
           libraryjars,
+          packageObfuscationMode,
           packagePrefix,
           allowAccessModification,
           ignoreWarnings,
@@ -164,6 +177,7 @@
   private final DexItemFactory dexItemFactory;
   private final List<Path> injars;
   private final List<Path> libraryjars;
+  private final PackageObfuscationMode packageObfuscationMode;
   private final String packagePrefix;
   private final boolean allowAccessModification;
   private final boolean ignoreWarnings;
@@ -188,6 +202,7 @@
       DexItemFactory factory,
       List<Path> injars,
       List<Path> libraryjars,
+      PackageObfuscationMode packageObfuscationMode,
       String packagePrefix,
       boolean allowAccessModification,
       boolean ignoreWarnings,
@@ -210,6 +225,7 @@
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
     this.libraryjars = ImmutableList.copyOf(libraryjars);
+    this.packageObfuscationMode = packageObfuscationMode;
     this.packagePrefix = packagePrefix;
     this.allowAccessModification = allowAccessModification;
     this.ignoreWarnings = ignoreWarnings;
@@ -254,6 +270,10 @@
     return libraryjars;
   }
 
+  public PackageObfuscationMode getPackageObfuscationMode() {
+    return packageObfuscationMode;
+  }
+
   public String getPackagePrefix() {
     return packagePrefix;
   }
@@ -333,6 +353,7 @@
       super(factory,
           ImmutableList.of()    /* injars */,
           ImmutableList.of()    /* libraryjars */,
+          PackageObfuscationMode.REPACKAGE, /* TODO(b/36799686): should be NONE once implemented */
           ""                    /* package prefix */,
           false                 /* allowAccessModification */,
           false                 /* ignoreWarnings */,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 31fb77d..1ab82f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -131,7 +132,7 @@
              (option = Iterables.find(warnedSingleArgOptions,
                  this::skipOptionWithSingleArg, null)) != null
           || (option = Iterables.find(warnedFlagOptions, this::skipFlag, null)) != null) {
-        System.out.println("WARNING: Ignoring option: -" + option);
+        warnIgnoringOptions(option);
       } else if ((option = Iterables.find(unsupportedFlagOptions, this::skipFlag, null)) != null) {
         throw parseError("Unsupported option: -" + option);
       } else if (acceptString("keepattributes")) {
@@ -150,7 +151,7 @@
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
         configurationBuilder.setOptimize(false);
-        System.out.println("WARNING: Ignoring option: -dontoptimize");
+        warnIgnoringOptions("dontoptimize");
       } else if (acceptString("optimizationpasses")) {
         skipWhitespace();
         Integer expectedOptimizationPasses = acceptInteger();
@@ -158,7 +159,7 @@
           throw parseError("Missing n of \"-optimizationpasses n\"");
         }
         configurationBuilder.setOptimizationPasses(expectedOptimizationPasses);
-        System.out.println("WARNING: Ignoring option: -optimizationpasses");
+        warnIgnoringOptions("optimizationpasses");
       } else if (acceptString("dontobfuscate")) {
         configurationBuilder.setObfuscating(false);
       } else if (acceptString("dontshrink")) {
@@ -180,6 +181,9 @@
           configurationBuilder.addDontWarnPattern(pattern);
         } while (acceptChar(','));
       } else if (acceptString("repackageclasses")) {
+        if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
+          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy");
+        }
         skipWhitespace();
         if (acceptChar('\'')) {
           configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
@@ -187,6 +191,24 @@
         } else {
           configurationBuilder.setPackagePrefix("");
         }
+      } else if (acceptString("flattenpackagehierarchy")) {
+        if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
+          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy");
+          skipWhitespace();
+          if (isOptionalArgumentGiven()) {
+            skipSingleArgument();
+          }
+        } else {
+          skipWhitespace();
+          if (acceptChar('\'')) {
+            configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
+            expectChar('\'');
+          } else {
+            configurationBuilder.setFlattenPackagePrefix("");
+          }
+        }
+        // TODO(b/37764746): warn until package flattening is implemented and in effect.
+        warnIgnoringOptions("flattenpackagehierarchy");
       } else if (acceptString("allowaccessmodification")) {
         configurationBuilder.setAllowAccessModification(true);
       } else if (acceptString("printmapping")) {
@@ -229,6 +251,14 @@
       return true;
     }
 
+    private void warnIgnoringOptions(String optionName) {
+      System.out.println("WARNING: Ignoring option: -" + optionName);
+    }
+
+    private void warnOverridingOptions(String optionName, String victim) {
+      System.out.println("WARNING: option -" + optionName + " overrides -" + victim);
+    }
+
     private void parseInclude() throws ProguardRuleParserException {
       Path included = parseFileName();
       try {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9fea848..7d7ea46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -76,6 +76,7 @@
   public Path printMainDexListFile;
   public boolean ignoreMissingClasses = false;
   public boolean skipMinification = false;
+  public PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE;
   public String packagePrefix = "";
   public boolean allowAccessModification = true;
   public boolean inlineAccessors = true;
@@ -134,6 +135,15 @@
     return logArgumentsFilter.indexOf(qualifiedName) >= 0;
   }
 
+  public enum PackageObfuscationMode {
+    // General package obfuscation.
+    NONE,
+    // Repackaging all classes into the single user-given (or top-level) package.
+    REPACKAGE,
+    // Repackaging all packages into the single user-given (or top-level) package.
+    FLATTEN
+  }
+
   public static class OutlineOptions {
 
     public boolean enabled = true;
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b0fe113..7a81186 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -80,6 +80,7 @@
 
   private void copyProguardConfigurationToInternalOptions(
       ProguardConfiguration config, InternalOptions options) {
+    options.packageObfuscationMode = config.getPackageObfuscationMode();
     options.packagePrefix = config.getPackagePrefix();
     options.allowAccessModification = config.getAllowAccessModification();
     options.classObfuscationDictionary = config.getClassObfuscationDictionary();
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ad1a392..6f2a347 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -56,6 +57,18 @@
       VALID_PROGUARD_DIR + "keepdirectories.flags";
   private static final String DONT_OBFUSCATE =
       VALID_PROGUARD_DIR + "dontobfuscate.flags";
+  private static final String PACKAGE_OBFUSCATION_1 =
+      VALID_PROGUARD_DIR + "package-obfuscation-1.flags";
+  private static final String PACKAGE_OBFUSCATION_2 =
+      VALID_PROGUARD_DIR + "package-obfuscation-2.flags";
+  private static final String PACKAGE_OBFUSCATION_3 =
+      VALID_PROGUARD_DIR + "package-obfuscation-3.flags";
+  private static final String PACKAGE_OBFUSCATION_4 =
+      VALID_PROGUARD_DIR + "package-obfuscation-4.flags";
+  private static final String PACKAGE_OBFUSCATION_5 =
+      VALID_PROGUARD_DIR + "package-obfuscation-5.flags";
+  private static final String PACKAGE_OBFUSCATION_6 =
+      VALID_PROGUARD_DIR + "package-obfuscation-6.flags";
   private static final String DONT_SHRINK =
       VALID_PROGUARD_DIR + "dontshrink.flags";
   private static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES =
@@ -257,7 +270,72 @@
 
   @Test
   public void parseDontobfuscate() throws IOException, ProguardRuleParserException {
-    new ProguardConfigurationParser(new DexItemFactory()).parse(Paths.get(DONT_OBFUSCATE));
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(DONT_OBFUSCATE));
+    ProguardConfiguration config = parser.getConfig();
+    assertFalse(config.isObfuscating());
+  }
+
+  @Test
+  public void parseRepackageClassesEmpty() throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("", config.getPackagePrefix());
+  }
+
+  @Test
+  public void parseRepackageClassesNonEmpty() throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("p.q.r", config.getPackagePrefix());
+  }
+
+  @Test
+  public void parseFlattenPackageHierarchyEmpty() throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("", config.getPackagePrefix());
+  }
+
+  @Test
+  public void parseFlattenPackageHierarchyNonEmpty() throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("p.q.r", config.getPackagePrefix());
+  }
+
+  @Test
+  public void flattenPackageHierarchyCannotOverrideRepackageClasses()
+      throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_5));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("top", config.getPackagePrefix());
+  }
+
+  @Test
+  public void repackageClassesOverridesFlattenPackageHierarchy()
+      throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(PACKAGE_OBFUSCATION_6));
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
+    assertNotNull(config.getPackagePrefix());
+    assertEquals("top", config.getPackagePrefix());
   }
 
   @Test
diff --git a/src/test/proguard/valid/package-obfuscation-1.flags b/src/test/proguard/valid/package-obfuscation-1.flags
new file mode 100644
index 0000000..a852b2e
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-1.flags
@@ -0,0 +1,5 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-repackageclasses
diff --git a/src/test/proguard/valid/package-obfuscation-2.flags b/src/test/proguard/valid/package-obfuscation-2.flags
new file mode 100644
index 0000000..5d847d3
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-2.flags
@@ -0,0 +1,5 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-repackageclasses 'p.q.r'
diff --git a/src/test/proguard/valid/package-obfuscation-3.flags b/src/test/proguard/valid/package-obfuscation-3.flags
new file mode 100644
index 0000000..84d6aa3
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-3.flags
@@ -0,0 +1,5 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-flattenpackagehierarchy
diff --git a/src/test/proguard/valid/package-obfuscation-4.flags b/src/test/proguard/valid/package-obfuscation-4.flags
new file mode 100644
index 0000000..e872bfc
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-4.flags
@@ -0,0 +1,5 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-flattenpackagehierarchy 'p.q.r'
diff --git a/src/test/proguard/valid/package-obfuscation-5.flags b/src/test/proguard/valid/package-obfuscation-5.flags
new file mode 100644
index 0000000..0455d30
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-5.flags
@@ -0,0 +1,6 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-repackageclasses 'top'
+-flattenpackagehierarchy 'cannot.override.the.above'
diff --git a/src/test/proguard/valid/package-obfuscation-6.flags b/src/test/proguard/valid/package-obfuscation-6.flags
new file mode 100644
index 0000000..f3c1f59
--- /dev/null
+++ b/src/test/proguard/valid/package-obfuscation-6.flags
@@ -0,0 +1,6 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-flattenpackagehierarchy 'will.be.ignored'
+-repackageclasses 'top'