Loading ravenwood/scripts/ravenwood-stats-collector.sh +2 −2 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ collect_apis() { collect_stats $stats " (import it as 'ravenwood_stats')" collect_apis $apis " (import it as 'ravenwood_supported_apis')" collect_apis $apis " (import it as 'ravenwood_supported_apis2')" cp *keep_all.txt $keep_all_dir echo "Keep all files created at:" Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt +3 −7 Original line number Diff line number Diff line Loading @@ -24,13 +24,9 @@ import org.objectweb.asm.Opcodes import java.io.PrintWriter /** * TODO This is for the legacy API coverage stats CSV that shows how many APIs are "supported" * in each class with some heuristics. We created [ApiDumper] later, which dumpps all methods * with the "supported" status. We should update the coverage dashboard to use the [ApiDumper] * output and remove this class, once we port all the heuristics to [ApiDumper] as well. * (For example, this class ignores non-public and/or abstract methods, but [ApiDumper] shows * all of them in the same way. We should probably mark them as "Boring" or maybe "Ignore" * for [ApiDumper]) * This class is no longer used. It was used for the old ravenwood dashboard. (b/402797626) * * TODO: Delete the class. */ open class HostStubGenStats(val classes: ClassNodes) { data class Stats( Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt +4 −0 Original line number Diff line number Diff line Loading @@ -377,6 +377,10 @@ fun MethodNode.isPublic(): Boolean { return (this.access and Opcodes.ACC_PUBLIC) != 0 } fun MethodNode.isAbstract(): Boolean { return (this.access and Opcodes.ACC_ABSTRACT) != 0 } fun MethodNode.isNative(): Boolean { return (this.access and Opcodes.ACC_NATIVE) != 0 } Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt +56 −73 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import com.android.hoststubgen.asm.CTOR_NAME import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.getClassNameFromFullClassName import com.android.hoststubgen.asm.getPackageNameFromFullClassName import com.android.hoststubgen.asm.isAbstract import com.android.hoststubgen.asm.isPublic import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.csvEscape import com.android.hoststubgen.filters.FilterPolicy Loading @@ -27,8 +29,8 @@ import com.android.hoststubgen.filters.FilterPolicyWithReason import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.StatsLabel import com.android.hoststubgen.log import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode import java.io.PrintWriter /** Loading @@ -45,19 +47,14 @@ class ApiDumper( val descriptor: String, ) private val javaStandardApiPolicy = FilterPolicy.Keep.withReason( "Java standard API", StatsLabel.Supported, ) private val shownMethods = mutableSetOf<MethodKey>() /** * Do the dump. */ fun dump() { pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" + ",Supported,Policy,Reason,SupportedLabel\n") pw.printf("PackageName,ClassName,Inherited,DeclareClass,MethodName,MethodDesc" + ",Supported,Policy,Reason,Boring\n") classes.forEach { classNode -> shownMethods.clear() Loading @@ -72,32 +69,21 @@ class ApiDumper( methodClassName: String, methodName: String, methodDesc: String, classPolicy: FilterPolicyWithReason, computedMethodLabel: StatsLabel, methodPolicy: FilterPolicyWithReason, ) { if (methodPolicy.statsLabel == StatsLabel.Ignored) { return } // Label hack -- if the method is supported, but the class is boring, then the // method is boring too. var methodLabel = methodPolicy.statsLabel if (methodLabel == StatsLabel.SupportedButBoring && classPolicy.statsLabel == StatsLabel.SupportedButBoring) { methodLabel = classPolicy.statsLabel } pw.printf( "%s,%s,%d,%s,%s,%s,%d,%s,%s,%s\n", "%s,%s,%d,%s,%s,%s,%d,%s,%s,%d\n", csvEscape(classPackage), csvEscape(className), if (isSuperClass) { 1 } else { 0 }, csvEscape(methodClassName), csvEscape(methodName), csvEscape(methodDesc), methodLabel.statValue, csvEscape(methodName + methodDesc), if (computedMethodLabel.isSupported) { 1 } else { 0 }, methodPolicy.policy, csvEscape(methodPolicy.reason), methodLabel, if (computedMethodLabel == StatsLabel.SupportedButBoring) { 1 } else { 0 }, ) } Loading @@ -111,6 +97,42 @@ class ApiDumper( return false } private fun getClassLabel(cn: ClassNode, classPolicy: FilterPolicyWithReason): StatsLabel { if (!classPolicy.statsLabel.isSupported) { return classPolicy.statsLabel } if (cn.name.endsWith("Proto") || cn.name.endsWith("ProtoEnums") || cn.name.endsWith("LogTags") || cn.name.endsWith("StatsLog")) { return StatsLabel.SupportedButBoring } return classPolicy.statsLabel } private fun resolveMethodLabel( mn: MethodNode, methodPolicy: FilterPolicyWithReason, classLabel: StatsLabel, ): StatsLabel { // Class label will override the method label if (!classLabel.isSupported) { return classLabel } // If method isn't supported, just use it as-is. if (!methodPolicy.statsLabel.isSupported) { return methodPolicy.statsLabel } // Use heuristics to override the label. if (!mn.isPublic() || mn.isAbstract()) { return StatsLabel.SupportedButBoring } return methodPolicy.statsLabel } private fun dump( dumpClass: ClassNode, methodClass: ClassNode, Loading @@ -120,9 +142,11 @@ class ApiDumper( return } log.d("Class ${dumpClass.name} -- policy $classPolicy") val classLabel = getClassLabel(dumpClass, classPolicy) val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val humanReadableClassName = dumpClass.name.toHumanReadableClassName() val pkg = getPackageNameFromFullClassName(humanReadableClassName) val cls = getClassNameFromFullClassName(humanReadableClassName) val isSuperClass = dumpClass != methodClass Loading Loading @@ -150,8 +174,12 @@ class ApiDumper( val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc) val methodLabel = resolveMethodLabel(method, methodPolicy, classLabel) if (methodLabel != StatsLabel.Ignored) { dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(), renameTo ?: method.name, method.desc, classPolicy, methodPolicy) renameTo ?: method.name, method.desc, methodLabel, methodPolicy) } } // Dump super class methods. Loading @@ -178,51 +206,6 @@ class ApiDumper( dump(dumpClass, methodClass) return } // Dump overriding methods from Java standard classes, except for the Object methods, // which are obvious. if (methodClassName.startsWith("java/") || methodClassName.startsWith("javax/")) { if (methodClassName != "java/lang/Object") { dumpStandardClass(dumpClass, methodClassName) } return } log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.") } /** * Dump methods from Java standard classes. */ private fun dumpStandardClass( dumpClass: ClassNode, methodClassName: String, ) { val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val methodClassName = methodClassName.toHumanReadableClassName() try { val clazz = Class.forName(methodClassName) // Method.getMethods() returns only public methods, but with inherited ones. // Method.getDeclaredMethods() returns private methods too, but no inherited methods. // // Since we're only interested in public ones, just use getMethods(). clazz.methods.forEach { method -> val methodName = method.name val methodDesc = Type.getMethodDescriptor(method) // If we already printed the method from a subclass, don't print it. if (shownAlready(methodName, methodDesc)) { return@forEach } dumpMethod(pkg, cls, true, methodClassName, methodName, methodDesc, javaStandardApiPolicy, javaStandardApiPolicy) } } catch (e: ClassNotFoundException) { log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.") } } } ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +9 −1 Original line number Diff line number Diff line Loading @@ -32,7 +32,15 @@ enum class StatsLabel(val statValue: Int, val label: String) { SupportedButBoring(1, "Boring"), /** Entry should be shown as "supported" */ Supported(2, "Supported"), Supported(2, "Supported"); val isSupported: Boolean get() { return when (this) { SupportedButBoring, Supported -> true else -> false } } } /** Loading Loading
ravenwood/scripts/ravenwood-stats-collector.sh +2 −2 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ collect_apis() { collect_stats $stats " (import it as 'ravenwood_stats')" collect_apis $apis " (import it as 'ravenwood_supported_apis')" collect_apis $apis " (import it as 'ravenwood_supported_apis2')" cp *keep_all.txt $keep_all_dir echo "Keep all files created at:" Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt +3 −7 Original line number Diff line number Diff line Loading @@ -24,13 +24,9 @@ import org.objectweb.asm.Opcodes import java.io.PrintWriter /** * TODO This is for the legacy API coverage stats CSV that shows how many APIs are "supported" * in each class with some heuristics. We created [ApiDumper] later, which dumpps all methods * with the "supported" status. We should update the coverage dashboard to use the [ApiDumper] * output and remove this class, once we port all the heuristics to [ApiDumper] as well. * (For example, this class ignores non-public and/or abstract methods, but [ApiDumper] shows * all of them in the same way. We should probably mark them as "Boring" or maybe "Ignore" * for [ApiDumper]) * This class is no longer used. It was used for the old ravenwood dashboard. (b/402797626) * * TODO: Delete the class. */ open class HostStubGenStats(val classes: ClassNodes) { data class Stats( Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt +4 −0 Original line number Diff line number Diff line Loading @@ -377,6 +377,10 @@ fun MethodNode.isPublic(): Boolean { return (this.access and Opcodes.ACC_PUBLIC) != 0 } fun MethodNode.isAbstract(): Boolean { return (this.access and Opcodes.ACC_ABSTRACT) != 0 } fun MethodNode.isNative(): Boolean { return (this.access and Opcodes.ACC_NATIVE) != 0 } Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt +56 −73 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import com.android.hoststubgen.asm.CTOR_NAME import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.getClassNameFromFullClassName import com.android.hoststubgen.asm.getPackageNameFromFullClassName import com.android.hoststubgen.asm.isAbstract import com.android.hoststubgen.asm.isPublic import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.csvEscape import com.android.hoststubgen.filters.FilterPolicy Loading @@ -27,8 +29,8 @@ import com.android.hoststubgen.filters.FilterPolicyWithReason import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.StatsLabel import com.android.hoststubgen.log import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode import java.io.PrintWriter /** Loading @@ -45,19 +47,14 @@ class ApiDumper( val descriptor: String, ) private val javaStandardApiPolicy = FilterPolicy.Keep.withReason( "Java standard API", StatsLabel.Supported, ) private val shownMethods = mutableSetOf<MethodKey>() /** * Do the dump. */ fun dump() { pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" + ",Supported,Policy,Reason,SupportedLabel\n") pw.printf("PackageName,ClassName,Inherited,DeclareClass,MethodName,MethodDesc" + ",Supported,Policy,Reason,Boring\n") classes.forEach { classNode -> shownMethods.clear() Loading @@ -72,32 +69,21 @@ class ApiDumper( methodClassName: String, methodName: String, methodDesc: String, classPolicy: FilterPolicyWithReason, computedMethodLabel: StatsLabel, methodPolicy: FilterPolicyWithReason, ) { if (methodPolicy.statsLabel == StatsLabel.Ignored) { return } // Label hack -- if the method is supported, but the class is boring, then the // method is boring too. var methodLabel = methodPolicy.statsLabel if (methodLabel == StatsLabel.SupportedButBoring && classPolicy.statsLabel == StatsLabel.SupportedButBoring) { methodLabel = classPolicy.statsLabel } pw.printf( "%s,%s,%d,%s,%s,%s,%d,%s,%s,%s\n", "%s,%s,%d,%s,%s,%s,%d,%s,%s,%d\n", csvEscape(classPackage), csvEscape(className), if (isSuperClass) { 1 } else { 0 }, csvEscape(methodClassName), csvEscape(methodName), csvEscape(methodDesc), methodLabel.statValue, csvEscape(methodName + methodDesc), if (computedMethodLabel.isSupported) { 1 } else { 0 }, methodPolicy.policy, csvEscape(methodPolicy.reason), methodLabel, if (computedMethodLabel == StatsLabel.SupportedButBoring) { 1 } else { 0 }, ) } Loading @@ -111,6 +97,42 @@ class ApiDumper( return false } private fun getClassLabel(cn: ClassNode, classPolicy: FilterPolicyWithReason): StatsLabel { if (!classPolicy.statsLabel.isSupported) { return classPolicy.statsLabel } if (cn.name.endsWith("Proto") || cn.name.endsWith("ProtoEnums") || cn.name.endsWith("LogTags") || cn.name.endsWith("StatsLog")) { return StatsLabel.SupportedButBoring } return classPolicy.statsLabel } private fun resolveMethodLabel( mn: MethodNode, methodPolicy: FilterPolicyWithReason, classLabel: StatsLabel, ): StatsLabel { // Class label will override the method label if (!classLabel.isSupported) { return classLabel } // If method isn't supported, just use it as-is. if (!methodPolicy.statsLabel.isSupported) { return methodPolicy.statsLabel } // Use heuristics to override the label. if (!mn.isPublic() || mn.isAbstract()) { return StatsLabel.SupportedButBoring } return methodPolicy.statsLabel } private fun dump( dumpClass: ClassNode, methodClass: ClassNode, Loading @@ -120,9 +142,11 @@ class ApiDumper( return } log.d("Class ${dumpClass.name} -- policy $classPolicy") val classLabel = getClassLabel(dumpClass, classPolicy) val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val humanReadableClassName = dumpClass.name.toHumanReadableClassName() val pkg = getPackageNameFromFullClassName(humanReadableClassName) val cls = getClassNameFromFullClassName(humanReadableClassName) val isSuperClass = dumpClass != methodClass Loading Loading @@ -150,8 +174,12 @@ class ApiDumper( val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc) val methodLabel = resolveMethodLabel(method, methodPolicy, classLabel) if (methodLabel != StatsLabel.Ignored) { dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(), renameTo ?: method.name, method.desc, classPolicy, methodPolicy) renameTo ?: method.name, method.desc, methodLabel, methodPolicy) } } // Dump super class methods. Loading @@ -178,51 +206,6 @@ class ApiDumper( dump(dumpClass, methodClass) return } // Dump overriding methods from Java standard classes, except for the Object methods, // which are obvious. if (methodClassName.startsWith("java/") || methodClassName.startsWith("javax/")) { if (methodClassName != "java/lang/Object") { dumpStandardClass(dumpClass, methodClassName) } return } log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.") } /** * Dump methods from Java standard classes. */ private fun dumpStandardClass( dumpClass: ClassNode, methodClassName: String, ) { val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() val methodClassName = methodClassName.toHumanReadableClassName() try { val clazz = Class.forName(methodClassName) // Method.getMethods() returns only public methods, but with inherited ones. // Method.getDeclaredMethods() returns private methods too, but no inherited methods. // // Since we're only interested in public ones, just use getMethods(). clazz.methods.forEach { method -> val methodName = method.name val methodDesc = Type.getMethodDescriptor(method) // If we already printed the method from a subclass, don't print it. if (shownAlready(methodName, methodDesc)) { return@forEach } dumpMethod(pkg, cls, true, methodClassName, methodName, methodDesc, javaStandardApiPolicy, javaStandardApiPolicy) } } catch (e: ClassNotFoundException) { log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.") } } }
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +9 −1 Original line number Diff line number Diff line Loading @@ -32,7 +32,15 @@ enum class StatsLabel(val statValue: Int, val label: String) { SupportedButBoring(1, "Boring"), /** Entry should be shown as "supported" */ Supported(2, "Supported"), Supported(2, "Supported"); val isSupported: Boolean get() { return when (this) { SupportedButBoring, Supported -> true else -> false } } } /** Loading