Read META-INF rules and configuration injars in a fixed point.

Change-Id: I420913e5b5211193ed1f157abcc51da60a7ac1f5
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e579889..0bf09ac 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.profile.art.ArtProfileForRewriting;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationParserOptions;
@@ -30,6 +31,7 @@
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -40,18 +42,21 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Deque;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -556,47 +561,6 @@
       ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
       configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
 
-      if (proguardConfigurationConsumerForTesting != null) {
-        proguardConfigurationConsumerForTesting.accept(configurationBuilder);
-      }
-
-      // Process Proguard configurations supplied through data resources in the input.
-      DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
-          new DataResourceProvider.Visitor() {
-            @Override
-            public void visit(DataDirectoryResource directory) {
-              // Don't do anything.
-            }
-
-            @Override
-            public void visit(DataEntryResource resource) {
-              if (resource.getName().startsWith("META-INF/proguard/")) {
-                try (InputStream in = resource.getByteStream()) {
-                  ProguardConfigurationSource source =
-                      new ProguardConfigurationSourceBytes(in, resource.getOrigin());
-                  parser.parse(source);
-                } catch (ResourceException e) {
-                  reporter.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
-                      resource.getOrigin()));
-                } catch (Exception e) {
-                  reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
-                }
-              }
-            }
-          };
-
-      getAppBuilder().getProgramResourceProviders().stream()
-          .map(ProgramResourceProvider::getDataResourceProvider)
-          .filter(Objects::nonNull)
-          .forEach(
-              dataResourceProvider -> {
-                try {
-                  dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
-                } catch (ResourceException e) {
-                  reporter.error(new ExceptionDiagnostic(e));
-                }
-              });
-
       if (getMode() == CompilationMode.DEBUG) {
         disableMinification = true;
         configurationBuilder.disableOptimization();
@@ -610,10 +574,12 @@
         configurationBuilder.disableObfuscation();
       }
 
+      if (proguardConfigurationConsumerForTesting != null) {
+        proguardConfigurationConsumerForTesting.accept(configurationBuilder);
+      }
+      amendWithRulesAndProvidersForInjarsAndMetaInf(reporter, parser);
       ProguardConfiguration configuration = configurationBuilder.build();
-      getAppBuilder()
-          .addFilteredProgramArchives(configuration.getInjars())
-          .addFilteredLibraryArchives(configuration.getLibraryjars());
+      getAppBuilder().addFilteredLibraryArchives(configuration.getLibraryjars());
 
       assert getProgramConsumer() != null;
 
@@ -673,6 +639,64 @@
       return command;
     }
 
+    private void amendWithRulesAndProvidersForInjarsAndMetaInf(
+        Reporter reporter, ProguardConfigurationParser parser) {
+
+      // Process Proguard configurations supplied through data resources in the input.
+      DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
+          new DataResourceProvider.Visitor() {
+            @Override
+            public void visit(DataDirectoryResource directory) {
+              // Don't do anything.
+            }
+
+            @Override
+            public void visit(DataEntryResource resource) {
+              if (resource.getName().startsWith("META-INF/proguard/")) {
+                try (InputStream in = resource.getByteStream()) {
+                  ProguardConfigurationSource source =
+                      new ProguardConfigurationSourceBytes(in, resource.getOrigin());
+                  parser.parse(source);
+                } catch (ResourceException e) {
+                  reporter.error(
+                      new StringDiagnostic(
+                          "Failed to open input: " + e.getMessage(), resource.getOrigin()));
+                } catch (Exception e) {
+                  reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+                }
+              }
+            }
+          };
+
+      // Since -injars can itself reference archives with rules and that in turn have -injars the
+      // completion of amending rules and providers must run in a fixed point. The fixed point is
+      // reached once the injars set is stable.
+      Set<FilteredClassPath> seenInjars = SetUtils.newIdentityHashSet();
+      Deque<ProgramResourceProvider> providers =
+          new ArrayDeque<>(getAppBuilder().getProgramResourceProviders());
+      while (true) {
+        for (FilteredClassPath injar : parser.getConfigurationBuilder().getInjars()) {
+          if (seenInjars.add(injar)) {
+            ArchiveResourceProvider provider = getAppBuilder().createAndAddProvider(injar);
+            providers.add(provider);
+          }
+        }
+        if (providers.isEmpty()) {
+          return;
+        }
+        while (!providers.isEmpty()) {
+          DataResourceProvider dataResourceProvider = providers.pop().getDataResourceProvider();
+          if (dataResourceProvider != null) {
+            try {
+              dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+            } catch (ResourceException e) {
+              reporter.error(new ExceptionDiagnostic(e));
+            }
+          }
+        }
+      }
+    }
+
     // Internal for-testing method to add post-processors of the proguard configuration.
     void addProguardConfigurationConsumerForTesting(Consumer<ProguardConfiguration.Builder> c) {
       Consumer<ProguardConfiguration.Builder> oldConsumer = proguardConfigurationConsumerForTesting;
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 99edc77..955069f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -76,6 +76,10 @@
       this.reporter = reporter;
     }
 
+    public List<FilteredClassPath> getInjars() {
+      return injars;
+    }
+
     public void addParsedConfiguration(String source) {
       parsedConfiguration.add(source);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index a72a237..9f8c41b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -992,20 +992,25 @@
       return this;
     }
 
+    public ArchiveResourceProvider createAndAddProvider(FilteredClassPath archive) {
+      if (isArchive(archive.getPath())) {
+        ArchiveResourceProvider archiveResourceProvider =
+            new ArchiveResourceProvider(archive, ignoreDexInArchive);
+        addProgramResourceProvider(archiveResourceProvider);
+        return archiveResourceProvider;
+      }
+      reporter.error(
+          new StringDiagnostic(
+              "Unexpected input type. Only archive types are supported, e.g., .jar, .zip, etc.",
+              archive.getOrigin(),
+              archive.getPosition()));
+      return null;
+    }
+
     /** Add filtered archives of program resources. */
     public Builder addFilteredProgramArchives(Collection<FilteredClassPath> filteredArchives) {
       for (FilteredClassPath archive : filteredArchives) {
-        if (isArchive(archive.getPath())) {
-          ArchiveResourceProvider archiveResourceProvider =
-              new ArchiveResourceProvider(archive, ignoreDexInArchive);
-          addProgramResourceProvider(archiveResourceProvider);
-        } else {
-          reporter.error(
-              new StringDiagnostic(
-                  "Unexpected input type. Only archive types are supported, e.g., .jar, .zip, etc.",
-                  archive.getOrigin(),
-                  archive.getPosition()));
-        }
+        createAndAddProvider(archive);
       }
       return this;
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index a0029a0..2741404 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
@@ -84,16 +85,26 @@
     }
   }
 
+  enum ProviderType {
+    API,
+    INJARS
+  }
+
   @Parameter(0)
   public TestParameters parameters;
 
   @Parameter(1)
   public LibraryType libraryType;
 
-  @Parameters(name = "{0} AAR: {1}")
+  @Parameter(2)
+  public ProviderType providerType;
+
+  @Parameters(name = "{0}, AAR: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), LibraryType.values());
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(),
+        LibraryType.values(),
+        ProviderType.values());
   }
 
   private Path buildLibrary(List<String> rules) throws Exception {
@@ -121,8 +132,10 @@
   }
 
   private CodeInspector runTest(List<String> rules) throws Exception {
+    Path library = buildLibrary(rules);
     return testForR8(parameters.getBackend())
-        .addProgramFiles(buildLibrary(rules))
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspector();