From 434d72821a6c7a62bbaf59555aa14dfc5478fa66 Mon Sep 17 00:00:00 2001
From: Makoto Onuki <omakoto@google.com>
Date: Thu, 25 Apr 2024 11:03:41 -0700
Subject: [PATCH] [Ravenwood] Dump supported APIs to CSV

Bug: 292141694
Test: ./ravenwood/scripts/ravenwood-stats-collector.sh
    and examine the generated files
Change-Id: I3ea52f20ca54644f3ab724f23ae3e8f0e08e269f
---
 Ravenwood.bp                                  | 28 ++++++++++
 ravenwood/Android.bp                          |  2 +
 .../scripts/ravenwood-stats-collector.sh      | 33 +++++++++---
 .../com/android/hoststubgen/HostStubGen.kt    |  6 ++-
 .../android/hoststubgen/HostStubGenOptions.kt |  4 ++
 .../android/hoststubgen/HostStubGenStats.kt   | 54 ++++++++++++++++---
 .../src/com/android/hoststubgen/Utils.kt      |  7 +++
 7 files changed, 118 insertions(+), 16 deletions(-)

diff --git a/Ravenwood.bp b/Ravenwood.bp
index 7c7c0e298f3f..74382a6e8d44 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -34,6 +34,7 @@ java_genrule {
 
         "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
         "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
+        "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
 
         "--out-impl-jar $(location ravenwood.jar) " +
 
@@ -58,6 +59,7 @@ java_genrule {
 
         "hoststubgen_framework-minus-apex.log",
         "hoststubgen_framework-minus-apex_stats.csv",
+        "hoststubgen_framework-minus-apex_apis.csv",
     ],
     visibility: ["//visibility:private"],
 }
@@ -90,6 +92,18 @@ genrule {
     ],
 }
 
+genrule {
+    name: "framework-minus-apex.ravenwood.apis",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_apis.csv}",
+    ],
+    out: [
+        "hoststubgen_framework-minus-apex_apis.csv",
+    ],
+}
+
 java_library {
     name: "services.core-for-hoststubgen",
     installable: false, // host only jar.
@@ -108,6 +122,7 @@ java_genrule {
 
         "--debug-log $(location hoststubgen_services.core.log) " +
         "--stats-file $(location hoststubgen_services.core_stats.csv) " +
+        "--supported-api-list-file $(location hoststubgen_services.core_apis.csv) " +
 
         "--out-impl-jar $(location ravenwood.jar) " +
 
@@ -132,6 +147,7 @@ java_genrule {
 
         "hoststubgen_services.core.log",
         "hoststubgen_services.core_stats.csv",
+        "hoststubgen_services.core_apis.csv",
     ],
     visibility: ["//visibility:private"],
 }
@@ -161,6 +177,18 @@ genrule {
     ],
 }
 
+genrule {
+    name: "services.core.ravenwood.apis",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}",
+    ],
+    out: [
+        "hoststubgen_services.core_apis.csv",
+    ],
+}
+
 java_library {
     name: "services.core.ravenwood-jarjar",
     installable: false,
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 8905ad3273d8..33374195e674 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -163,6 +163,8 @@ sh_test_host {
     test_suites: ["general-tests"],
     data: [
         ":framework-minus-apex.ravenwood.stats",
+        ":framework-minus-apex.ravenwood.apis",
         ":services.core.ravenwood.stats",
+        ":services.core.ravenwood.apis",
     ],
 }
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index 4dcaa2be5af3..b5843d0fa26b 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -17,8 +17,9 @@
 
 set -e
 
-# Output file
-out=/tmp/ravenwood-stats-all.csv
+# Output files
+stats=/tmp/ravenwood-stats-all.csv
+apis=/tmp/ravenwood-apis-all.csv
 
 # Where the input files are.
 path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
@@ -41,12 +42,28 @@ dump() {
     sed -e '1d' -e "s/^/$jar,/"  $file
 }
 
-collect() {
-    echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
-    dump "framework-minus-apex"  hoststubgen_framework-minus-apex_stats.csv
-    dump "service.core"  hoststubgen_services.core_stats.csv
+collect_stats() {
+    local out="$1"
+    {
+        echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
+        dump "framework-minus-apex"  hoststubgen_framework-minus-apex_stats.csv
+        dump "service.core"  hoststubgen_services.core_stats.csv
+    } > "$out"
+
+    echo "Stats CVS created at $out"
+}
+
+collect_apis() {
+    local out="$1"
+    {
+        echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+        dump "framework-minus-apex"  hoststubgen_framework-minus-apex_apis.csv
+        dump "service.core"  hoststubgen_services.core_apis.csv
+    } > "$out"
+
+    echo "API CVS created at $out"
 }
 
-collect >$out
 
-echo "Full dump CVS created at $out"
+collect_stats $stats
+collect_apis $apis
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 803dc283b8c7..2f432cc7ac96 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -85,9 +85,13 @@ class HostStubGen(val options: HostStubGenOptions) {
 
         // Dump statistics, if specified.
         options.statsFile.ifSet {
-            PrintWriter(it).use { pw -> stats.dump(pw) }
+            PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
             log.i("Dump file created at $it")
         }
+        options.apiListFile.ifSet {
+            PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+            log.i("API list file created at $it")
+        }
     }
 
     /**
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9ff798a4b5cb..e192516d334d 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -110,6 +110,8 @@ class HostStubGenOptions(
         var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false),
 
         var statsFile: SetOnce<String?> = SetOnce(null),
+
+        var apiListFile: SetOnce<String?> = SetOnce(null),
 ) {
     companion object {
 
@@ -255,6 +257,7 @@ class HostStubGenOptions(
                         "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
 
                         "--stats-file" -> ret.statsFile.setNextStringArg()
+                        "--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
 
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
@@ -392,6 +395,7 @@ class HostStubGenOptions(
               enablePostTrace=$enablePostTrace,
               enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
               statsFile=$statsFile,
+              apiListFile=$apiListFile,
             }
             """.trimIndent()
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index 50518e1ccd9c..da6146911a21 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -29,8 +29,25 @@ open class HostStubGenStats {
 
     private val stats = mutableMapOf<String, Stats>()
 
-    fun onVisitPolicyForMethod(fullClassName: String, methodName: String, descriptor: String,
-                               policy: FilterPolicyWithReason, access: Int) {
+    data class Api(
+        val fullClassName: String,
+        val methodName: String,
+        val methodDesc: String,
+    )
+
+    private val apis = mutableListOf<Api>()
+
+    fun onVisitPolicyForMethod(
+        fullClassName: String,
+        methodName: String,
+        descriptor: String,
+        policy: FilterPolicyWithReason,
+        access: Int
+    ) {
+        if (policy.policy.isSupported) {
+            apis.add(Api(fullClassName, methodName, descriptor))
+        }
+
         // Ignore methods that aren't public
         if ((access and Opcodes.ACC_PUBLIC) == 0) return
         // Ignore methods that are abstract
@@ -39,7 +56,7 @@ open class HostStubGenStats {
         if (policy.isIgnoredForStats) return
 
         val packageName = resolvePackageName(fullClassName)
-        val className = resolveClassName(fullClassName)
+        val className = resolveOuterClassName(fullClassName)
 
         // Ignore methods for certain generated code
         if (className.endsWith("Proto")
@@ -60,11 +77,11 @@ open class HostStubGenStats {
         classStats.total += 1
     }
 
-    fun dump(pw: PrintWriter) {
+    fun dumpOverview(pw: PrintWriter) {
         pw.printf("PackageName,ClassName,SupportedMethods,TotalMethods\n")
-        stats.forEach { (packageName, packageStats) ->
+        stats.toSortedMap().forEach { (packageName, packageStats) ->
             if (packageStats.supported > 0) {
-                packageStats.children.forEach { (className, classStats) ->
+                packageStats.children.toSortedMap().forEach { (className, classStats) ->
                     pw.printf("%s,%s,%d,%d\n", packageName, className,
                             classStats.supported, classStats.total)
                 }
@@ -72,12 +89,26 @@ open class HostStubGenStats {
         }
     }
 
+    fun dumpApis(pw: PrintWriter) {
+        pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
+        apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
+            .forEach { api ->
+            pw.printf(
+                "%s,%s,%s,%s\n",
+                csvEscape(resolvePackageName(api.fullClassName)),
+                csvEscape(resolveClassName(api.fullClassName)),
+                csvEscape(api.methodName),
+                csvEscape(api.methodDesc),
+                )
+        }
+    }
+
     private fun resolvePackageName(fullClassName: String): String {
         val start = fullClassName.lastIndexOf('/')
         return fullClassName.substring(0, start).toHumanReadableClassName()
     }
 
-    private fun resolveClassName(fullClassName: String): String {
+    private fun resolveOuterClassName(fullClassName: String): String {
         val start = fullClassName.lastIndexOf('/')
         val end = fullClassName.indexOf('$')
         if (end == -1) {
@@ -86,4 +117,13 @@ open class HostStubGenStats {
             return fullClassName.substring(start + 1, end)
         }
     }
+
+    private fun resolveClassName(fullClassName: String): String {
+        val pos = fullClassName.lastIndexOf('/')
+        if (pos == -1) {
+            return fullClassName
+        } else {
+            return fullClassName.substring(pos + 1)
+        }
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index aa63d8d9f870..10179eefcb95 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -89,3 +89,10 @@ class ParseException : Exception, UserErrorException {
         }
     }
 }
+
+/**
+ * Escape a string for a CSV field.
+ */
+fun csvEscape(value: String): String {
+    return "\"" + value.replace("\"", "\"\"") + "\""
+}
-- 
GitLab