Stop bundling Robolectric Processor's sdks.txt file as a Java resource

The resources approach would be difficult to get working in Gradle/Maven for
custom shadow packages outside of the Robolectric project itself. This is
because in Gradle/Maven, the the processor jar file currently contains a
sdks.txt resource that has hardcoded paths from the releaser's workstation.

Instead switch to a file-based scheme and emit the sdks.txt file to
Robolectric's root project's build directory. With a file based scheme is is
possible for external projects to specify an sdks.txt file path as well.

Also turn off the ImplementsValidator plugin off by default as it is
non-trivial for custom shadow packages in the Gradle/Maven world to generate
sdks.txt files. This validation is primarily intended for the Robolectric
project itself.

Fixes #4801

PiperOrigin-RevId: 344144378
diff --git a/processor/build.gradle b/processor/build.gradle
index 219aabc..e207327 100644
--- a/processor/build.gradle
+++ b/processor/build.gradle
@@ -22,10 +22,7 @@
 }
 
 task('generateSdksFile', type: GenerateSdksFileTask) {
-    dependsOn(tasks['processResources'])
-
-    File outDir = project.sourceSets['main'].output.resourcesDir
-    outFile = new File(outDir, 'sdks.txt')
+    outFile = new File(project.rootProject.buildDir, 'sdks.txt')
 }
 
 tasks['classes'].dependsOn(generateSdksFile)
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
index fed51c9..15e4139 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
@@ -134,7 +134,7 @@
       this.jsonDocsEnabled = "true".equalsIgnoreCase(options.get(JSON_DOCS_ENABLED));
       this.sdkCheckMode =
           SdkCheckMode.valueOf(options.getOrDefault(SDK_CHECK_MODE, "WARN").toUpperCase());
-      this.sdksFile = options.getOrDefault(SDKS_FILE, "/sdks.txt");
+      this.sdksFile = getSdksFile(options, SDKS_FILE);
       this.priority =
           Integer.parseInt(options.getOrDefault(PRIORITY, "0"));
 
@@ -144,6 +144,13 @@
     }
   }
 
+  /**
+   * Extendable to support Bazel environments, where the sdks file is generated as a build artifact.
+   */
+  protected String getSdksFile(Map<String, String> options, String sdksFileParam) {
+    return options.get(sdksFileParam);
+  }
+
   @Override
   public SourceVersion getSupportedSourceVersion() {
     return SourceVersion.latest();
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
index 34b53e8..4c6c592 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
@@ -5,14 +5,18 @@
 import static org.robolectric.annotation.processing.validator.ImplementsValidator.STATIC_INITIALIZER_METHOD_NAME;
 import static org.robolectric.annotation.processing.validator.ImplementsValidator.getClassFQName;
 
+import com.google.common.collect.ImmutableList;
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URI;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -37,6 +41,7 @@
 import org.objectweb.asm.tree.MethodNode;
 import org.robolectric.annotation.Implementation;
 
+/** Encapsulates a collection of Android framework jars. */
 public class SdkStore {
 
   private final Set<Sdk> sdks = new TreeSet<>();
@@ -83,10 +88,14 @@
     }
   }
 
-  private static List<Sdk> loadFromSdksFile(String resourceFileName) {
-    try (InputStream resIn = SdkStore.class.getResourceAsStream(resourceFileName)) {
+  private static ImmutableList<Sdk> loadFromSdksFile(String fileName) {
+    if (fileName == null || Files.notExists(Paths.get(fileName))) {
+      return ImmutableList.of();
+    }
+
+    try (InputStream resIn = new FileInputStream(fileName)) {
       if (resIn == null) {
-        throw new RuntimeException("no such resource " + resourceFileName);
+        throw new RuntimeException("no such file " + fileName);
       }
 
       BufferedReader in =
@@ -98,9 +107,9 @@
           sdks.add(new Sdk(line));
         }
       }
-      return sdks;
+      return ImmutableList.copyOf(sdks);
     } catch (IOException e) {
-      throw new RuntimeException("failed reading " + resourceFileName, e);
+      throw new RuntimeException("failed reading " + fileName, e);
     }
   }
 
@@ -182,8 +191,9 @@
       return null;
     }
 
-    private boolean suppressWarnings(ExecutableElement methodElement, String warningName) {
-      SuppressWarnings[] suppressWarnings = methodElement.getAnnotationsByType(SuppressWarnings.class);
+    private static boolean suppressWarnings(ExecutableElement methodElement, String warningName) {
+      SuppressWarnings[] suppressWarnings =
+          methodElement.getAnnotationsByType(SuppressWarnings.class);
       for (SuppressWarnings suppression : suppressWarnings) {
         for (String name : suppression.value()) {
           if (warningName.equals(name)) {
@@ -194,12 +204,13 @@
       return false;
     }
 
-    private boolean typeIsNumeric(MethodExtraInfo sdkMethod, MethodExtraInfo implMethod) {
+    private static boolean typeIsNumeric(MethodExtraInfo sdkMethod, MethodExtraInfo implMethod) {
       return implMethod.returnType.equals("java.lang.Number")
       && isNumericType(sdkMethod.returnType);
     }
 
-    private boolean typeIsOkForLooseSignatures(MethodExtraInfo implMethod, MethodExtraInfo sdkMethod) {
+    private static boolean typeIsOkForLooseSignatures(
+        MethodExtraInfo implMethod, MethodExtraInfo sdkMethod) {
       return
           // loose signatures allow a return type of Object...
           implMethod.returnType.equals("java.lang.Object")
@@ -208,7 +219,7 @@
                   && sdkMethod.returnType.endsWith("[]"));
     }
 
-    private boolean isNumericType(String type) {
+    private static boolean isNumericType(String type) {
       return type.equals("int") || type.equals("long");
     }
 
@@ -378,7 +389,7 @@
       }
     }
 
-    private String cleanMethodName(ExecutableElement methodElement) {
+    private static String cleanMethodName(ExecutableElement methodElement) {
       String name = methodElement.getSimpleName().toString();
       if (CONSTRUCTOR_METHOD_NAME.equals(name)) {
         return "<init>";
@@ -446,8 +457,7 @@
         return false;
       }
       MethodExtraInfo that = (MethodExtraInfo) o;
-      return isStatic == that.isStatic &&
-          Objects.equals(returnType, that.returnType);
+      return isStatic == that.isStatic && Objects.equals(returnType, that.returnType);
     }
 
     @Override