Refactor embedded Proguard rules extractor

Bug: b/377144587
Change-Id: Ic7ac37612f9087af5563003bb89578303e17be53
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index f2197c4..4b1738b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParserOptions;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
-import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.startup.StartupProfileProvider;
@@ -43,6 +42,7 @@
 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.EmbeddedRulesExtractor;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -54,13 +54,12 @@
 import com.android.tools.r8.utils.R8PartialCompilationConfiguration;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.SemanticVersionUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
-import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -858,30 +857,13 @@
         Reporter reporter, ProguardConfigurationParser parser) {
 
       Supplier<SemanticVersion> semanticVersionSupplier =
-          Suppliers.memoize(
-              () -> {
-                SemanticVersion compilerVersion =
-                    fakeCompilerVersion == null
-                        ? SemanticVersion.create(
-                            Version.getMajorVersion(),
-                            Version.getMinorVersion(),
-                            Version.getPatchVersion())
-                        : fakeCompilerVersion;
-                if (compilerVersion.getMajor() < 0) {
-                  compilerVersion =
-                      SemanticVersion.create(
-                          Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
-                  reporter.warning(
-                      "Running R8 version "
-                          + Version.getVersionString()
-                          + ", which cannot be represented as a semantic version. Using"
-                          + " an artificial version newer than any known version for selecting"
-                          + " Proguard configurations embedded under META-INF/. This means that"
-                          + " all rules with a '-upto-' qualifier will be excluded and all rules"
-                          + " with a -from- qualifier will be included.");
-                }
-                return compilerVersion;
-              });
+          SemanticVersionUtils.compilerVersionSemanticVersionSupplier(
+              fakeCompilerVersion,
+              "Using an artificial version newer than any known version for selecting"
+                  + " Proguard configurations embedded under META-INF/. This means that"
+                  + " all rules with a '-upto-' qualifier will be excluded and all rules"
+                  + " with a -from- qualifier will be included.",
+              reporter);
       Set<FilteredClassPath> seen = SetUtils.newIdentityHashSet();
       // Find resources in program providers. Both from API and added through legacy -injars in
       // configuration files.
@@ -934,8 +916,8 @@
         DataResourceProvider dataResourceProvider) {
       if (dataResourceProvider != null) {
         try {
-          ExtractEmbeddedRules embeddedProguardConfigurationVisitor =
-              new ExtractEmbeddedRules(reporter, semanticVersionSupplier);
+          EmbeddedRulesExtractor embeddedProguardConfigurationVisitor =
+              new EmbeddedRulesExtractor(reporter, semanticVersionSupplier);
           dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
           embeddedProguardConfigurationVisitor.parseRelevantRules(parser);
         } catch (ResourceException e) {
@@ -1534,126 +1516,4 @@
         .build();
   }
 
-  private static class ExtractEmbeddedRules implements DataResourceProvider.Visitor {
-
-    private final Supplier<SemanticVersion> compilerVersionSupplier;
-    private final Reporter reporter;
-    private final List<ProguardConfigurationSource> proguardSources = new ArrayList<>();
-    private final List<ProguardConfigurationSource> r8Sources = new ArrayList<>();
-    private SemanticVersion compilerVersion;
-
-    public ExtractEmbeddedRules(
-        Reporter reporter, Supplier<SemanticVersion> compilerVersionSupplier) {
-      this.compilerVersionSupplier = compilerVersionSupplier;
-      this.reporter = reporter;
-    }
-
-    @Override
-    public void visit(DataDirectoryResource directory) {
-      // Don't do anything.
-    }
-
-    @Override
-    public void visit(DataEntryResource resource) {
-      if (relevantProguardResource(resource)) {
-        assert !relevantR8Resource(resource);
-        readProguardConfigurationSource(resource, proguardSources::add);
-      } else if (relevantR8Resource(resource)) {
-        assert !relevantProguardResource(resource);
-        readProguardConfigurationSource(resource, r8Sources::add);
-      }
-    }
-
-    private void readProguardConfigurationSource(
-        DataEntryResource resource, Consumer<ProguardConfigurationSource> consumer) {
-      try (InputStream in = resource.getByteStream()) {
-        consumer.accept(new ProguardConfigurationSourceBytes(in, resource.getOrigin()));
-      } 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()));
-      }
-    }
-
-    private boolean relevantProguardResource(DataEntryResource resource) {
-      // Configurations in META-INF/com.android.tools/proguard/ are ignored.
-      final String proguardPrefix = "META-INF/proguard";
-      if (!resource.getName().startsWith(proguardPrefix)) {
-        return false;
-      }
-      String withoutPrefix = resource.getName().substring(proguardPrefix.length());
-      return withoutPrefix.startsWith("/");
-    }
-
-    private boolean relevantR8Resource(DataEntryResource resource) {
-      final String r8Prefix = "META-INF/com.android.tools/r8";
-      if (!resource.getName().startsWith(r8Prefix)) {
-        return false;
-      }
-      String withoutPrefix = resource.getName().substring(r8Prefix.length());
-      if (withoutPrefix.startsWith("/")) {
-        // Everything under META-INF/com.android.tools/r8/ is included (not version specific).
-        return true;
-      }
-      // Expect one of the following patterns:
-      //   com.android.tools/r8-from-1.5.0/
-      //   com.android.tools/r8-upto-1.6.0/
-      //   com.android.tools/r8-from-1.5.0-upto-1.6.0/
-      final String fromPrefix = "-from-";
-      final String uptoPrefix = "-upto-";
-      if (!withoutPrefix.startsWith(fromPrefix) && !withoutPrefix.startsWith(uptoPrefix)) {
-        return false;
-      }
-
-      SemanticVersion from = SemanticVersion.min();
-      SemanticVersion upto = null;
-
-      if (withoutPrefix.startsWith(fromPrefix)) {
-        withoutPrefix = withoutPrefix.substring(fromPrefix.length());
-        int versionEnd = StringUtils.indexOf(withoutPrefix, '-', '/');
-        if (versionEnd == -1) {
-          return false;
-        }
-        try {
-          from = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
-        } catch (IllegalArgumentException e) {
-          return false;
-        }
-        withoutPrefix = withoutPrefix.substring(versionEnd);
-      }
-      if (withoutPrefix.startsWith(uptoPrefix)) {
-        withoutPrefix = withoutPrefix.substring(uptoPrefix.length());
-        int versionEnd = withoutPrefix.indexOf('/');
-        if (versionEnd == -1) {
-          return false;
-        }
-        try {
-          upto = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
-        } catch (IllegalArgumentException e) {
-          return false;
-        }
-      }
-      if (compilerVersion == null) {
-        compilerVersion = compilerVersionSupplier.get();
-      }
-      return compilerVersion.isNewerOrEqual(from)
-          && (upto == null || upto.isNewer(compilerVersion));
-    }
-
-    private void parse(
-        List<ProguardConfigurationSource> sources, ProguardConfigurationParser parser) {
-      for (ProguardConfigurationSource source : sources) {
-        try {
-          parser.parse(source);
-        } catch (Exception e) {
-          reporter.error(new ExceptionDiagnostic(e, source.getOrigin()));
-        }
-      }
-    }
-
-    void parseRelevantRules(ProguardConfigurationParser parser) {
-      parse(!r8Sources.isEmpty() ? r8Sources : proguardSources, parser);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java b/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java
new file mode 100644
index 0000000..7f68aa3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2024, 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.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class EmbeddedRulesExtractor implements DataResourceProvider.Visitor {
+
+  private final Supplier<SemanticVersion> compilerVersionSupplier;
+  private final Reporter reporter;
+  private final List<ProguardConfigurationSource> proguardSources = new ArrayList<>();
+  private final List<ProguardConfigurationSource> r8Sources = new ArrayList<>();
+  private SemanticVersion compilerVersion;
+
+  public EmbeddedRulesExtractor(
+      Reporter reporter, Supplier<SemanticVersion> compilerVersionSupplier) {
+    this.compilerVersionSupplier = compilerVersionSupplier;
+    this.reporter = reporter;
+  }
+
+  @Override
+  public void visit(DataDirectoryResource directory) {
+    // Don't do anything.
+  }
+
+  @Override
+  public void visit(DataEntryResource resource) {
+    if (isRelevantProguardResource(resource)) {
+      assert !isRelevantR8Resource(resource);
+      readProguardConfigurationSource(resource, proguardSources::add);
+    } else if (isRelevantR8Resource(resource)) {
+      assert !isRelevantProguardResource(resource);
+      readProguardConfigurationSource(resource, r8Sources::add);
+    }
+  }
+
+  private void readProguardConfigurationSource(
+      DataEntryResource resource, Consumer<ProguardConfigurationSource> consumer) {
+    try (InputStream in = resource.getByteStream()) {
+      consumer.accept(new ProguardConfigurationSourceBytes(in, resource.getOrigin()));
+    } 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()));
+    }
+  }
+
+  private boolean isRelevantProguardResource(DataEntryResource resource) {
+    // Configurations in META-INF/com.android.tools/proguard/ are ignored.
+    final String proguardPrefix = "META-INF/proguard";
+    if (!resource.getName().startsWith(proguardPrefix)) {
+      return false;
+    }
+    String withoutPrefix = resource.getName().substring(proguardPrefix.length());
+    return withoutPrefix.startsWith("/");
+  }
+
+  private boolean isRelevantR8Resource(DataEntryResource resource) {
+    final String r8Prefix = "META-INF/com.android.tools/r8";
+    if (!resource.getName().startsWith(r8Prefix)) {
+      return false;
+    }
+    String withoutPrefix = resource.getName().substring(r8Prefix.length());
+    if (withoutPrefix.startsWith("/")) {
+      // Everything under META-INF/com.android.tools/r8/ is included (not version specific).
+      return true;
+    }
+    // Expect one of the following patterns:
+    //   com.android.tools/r8-from-1.5.0/
+    //   com.android.tools/r8-upto-1.6.0/
+    //   com.android.tools/r8-from-1.5.0-upto-1.6.0/
+    final String fromPrefix = "-from-";
+    final String uptoPrefix = "-upto-";
+    if (!withoutPrefix.startsWith(fromPrefix) && !withoutPrefix.startsWith(uptoPrefix)) {
+      return false;
+    }
+
+    SemanticVersion from = SemanticVersion.min();
+    SemanticVersion upto = null;
+
+    if (withoutPrefix.startsWith(fromPrefix)) {
+      withoutPrefix = withoutPrefix.substring(fromPrefix.length());
+      int versionEnd = StringUtils.indexOf(withoutPrefix, '-', '/');
+      if (versionEnd == -1) {
+        return false;
+      }
+      try {
+        from = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
+      } catch (IllegalArgumentException e) {
+        return false;
+      }
+      withoutPrefix = withoutPrefix.substring(versionEnd);
+    }
+    if (withoutPrefix.startsWith(uptoPrefix)) {
+      withoutPrefix = withoutPrefix.substring(uptoPrefix.length());
+      int versionEnd = withoutPrefix.indexOf('/');
+      if (versionEnd == -1) {
+        return false;
+      }
+      try {
+        upto = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
+      } catch (IllegalArgumentException e) {
+        return false;
+      }
+    }
+    if (compilerVersion == null) {
+      compilerVersion = compilerVersionSupplier.get();
+    }
+    return compilerVersion.isNewerOrEqual(from) && (upto == null || upto.isNewer(compilerVersion));
+  }
+
+  private void parse(
+      List<ProguardConfigurationSource> sources, ProguardConfigurationParser parser) {
+    for (ProguardConfigurationSource source : sources) {
+      try {
+        parser.parse(source);
+      } catch (Exception e) {
+        reporter.error(new ExceptionDiagnostic(e, source.getOrigin()));
+      }
+    }
+  }
+
+  public void parseRelevantRules(ProguardConfigurationParser parser) {
+    parse(!r8Sources.isEmpty() ? r8Sources : proguardSources, parser);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SemanticVersionUtils.java b/src/main/java/com/android/tools/r8/utils/SemanticVersionUtils.java
new file mode 100644
index 0000000..238e620
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SemanticVersionUtils.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2024, 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.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.Version;
+import com.google.common.base.Suppliers;
+import java.util.function.Supplier;
+
+public class SemanticVersionUtils {
+
+  public static Supplier<SemanticVersion> compilerVersionSemanticVersionSupplier(
+      SemanticVersion forceCompilerVersion,
+      String artificialMaxVersionWarningInfo,
+      Reporter reporter) {
+    return Suppliers.memoize(
+        () -> {
+          SemanticVersion compilerVersion =
+              forceCompilerVersion == null
+                  ? SemanticVersion.create(
+                      Version.getMajorVersion(),
+                      Version.getMinorVersion(),
+                      Version.getPatchVersion())
+                  : forceCompilerVersion;
+          if (compilerVersion.getMajor() < 0) {
+            compilerVersion =
+                SemanticVersion.create(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+            reporter.warning(
+                "Running R8 version "
+                    + Version.getVersionString()
+                    + ", which cannot be represented as a semantic version."
+                    + (artificialMaxVersionWarningInfo == null
+                        ? ""
+                        : (" " + artificialMaxVersionWarningInfo)));
+          }
+          return compilerVersion;
+        });
+  }
+}