check-flagged-apis: parse classes
Teach check-flagged-apis to parse classes, including inner classes. Bug: 334870672 Test: atest --host check-flagged-apis-test Change-Id: I17f65d3af55a20a1920b47f4c47fd0e92f9fa852
This commit is contained in:
@@ -16,6 +16,8 @@
|
|||||||
package com.android.checkflaggedapis
|
package com.android.checkflaggedapis
|
||||||
|
|
||||||
import android.aconfig.Aconfig
|
import android.aconfig.Aconfig
|
||||||
|
import android.aconfig.Aconfig.flag_state.DISABLED
|
||||||
|
import android.aconfig.Aconfig.flag_state.ENABLED
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -28,10 +30,12 @@ private val API_SIGNATURE =
|
|||||||
"""
|
"""
|
||||||
// Signature format: 2.0
|
// Signature format: 2.0
|
||||||
package android {
|
package android {
|
||||||
public final class Clazz {
|
@FlaggedApi("android.flag.foo") public final class Clazz {
|
||||||
ctor public Clazz();
|
ctor public Clazz();
|
||||||
field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
|
field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
|
||||||
}
|
}
|
||||||
|
@FlaggedApi("android.flag.bar") public static class Clazz.Builder {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
.trim()
|
.trim()
|
||||||
@@ -44,12 +48,17 @@ private val API_VERSIONS =
|
|||||||
<method name="<init>()V"/>
|
<method name="<init>()V"/>
|
||||||
<field name="FOO"/>
|
<field name="FOO"/>
|
||||||
</class>
|
</class>
|
||||||
|
<class name="android/Clazz${"$"}Builder" since="2">
|
||||||
|
</class>
|
||||||
</api>
|
</api>
|
||||||
"""
|
"""
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
private fun generateFlagsProto(fooState: Aconfig.flag_state): InputStream {
|
private fun generateFlagsProto(
|
||||||
val parsed_flag =
|
fooState: Aconfig.flag_state,
|
||||||
|
barState: Aconfig.flag_state
|
||||||
|
): InputStream {
|
||||||
|
val fooFlag =
|
||||||
Aconfig.parsed_flag
|
Aconfig.parsed_flag
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.setPackage("android.flag")
|
.setPackage("android.flag")
|
||||||
@@ -57,9 +66,18 @@ private fun generateFlagsProto(fooState: Aconfig.flag_state): InputStream {
|
|||||||
.setState(fooState)
|
.setState(fooState)
|
||||||
.setPermission(Aconfig.flag_permission.READ_ONLY)
|
.setPermission(Aconfig.flag_permission.READ_ONLY)
|
||||||
.build()
|
.build()
|
||||||
val parsed_flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(parsed_flag).build()
|
val barFlag =
|
||||||
|
Aconfig.parsed_flag
|
||||||
|
.newBuilder()
|
||||||
|
.setPackage("android.flag")
|
||||||
|
.setName("bar")
|
||||||
|
.setState(barState)
|
||||||
|
.setPermission(Aconfig.flag_permission.READ_ONLY)
|
||||||
|
.build()
|
||||||
|
val flags =
|
||||||
|
Aconfig.parsed_flags.newBuilder().addParsedFlag(fooFlag).addParsedFlag(barFlag).build()
|
||||||
val binaryProto = ByteArrayOutputStream()
|
val binaryProto = ByteArrayOutputStream()
|
||||||
parsed_flags.writeTo(binaryProto)
|
flags.writeTo(binaryProto)
|
||||||
return ByteArrayInputStream(binaryProto.toByteArray())
|
return ByteArrayInputStream(binaryProto.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,21 +85,32 @@ private fun generateFlagsProto(fooState: Aconfig.flag_state): InputStream {
|
|||||||
class CheckFlaggedApisTest {
|
class CheckFlaggedApisTest {
|
||||||
@Test
|
@Test
|
||||||
fun testParseApiSignature() {
|
fun testParseApiSignature() {
|
||||||
val expected = setOf(Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")))
|
val expected =
|
||||||
|
setOf(
|
||||||
|
Pair(Symbol("android.Clazz"), Flag("android.flag.foo")),
|
||||||
|
Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
|
||||||
|
Pair(Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
|
||||||
|
)
|
||||||
val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
|
val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testParseFlagValues() {
|
fun testParseFlagValues() {
|
||||||
val expected: Map<Flag, Boolean> = mapOf(Flag("android.flag.foo") to true)
|
val expected: Map<Flag, Boolean> =
|
||||||
val actual = parseFlagValues(generateFlagsProto(Aconfig.flag_state.ENABLED))
|
mapOf(Flag("android.flag.foo") to true, Flag("android.flag.bar") to true)
|
||||||
|
val actual = parseFlagValues(generateFlagsProto(ENABLED, ENABLED))
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testParseApiVersions() {
|
fun testParseApiVersions() {
|
||||||
val expected: Set<Symbol> = setOf(Symbol("android.Clazz.FOO"))
|
val expected: Set<Symbol> =
|
||||||
|
setOf(
|
||||||
|
Symbol("android.Clazz"),
|
||||||
|
Symbol("android.Clazz.FOO"),
|
||||||
|
Symbol("android.Clazz.Builder"),
|
||||||
|
)
|
||||||
val actual = parseApiVersions(API_VERSIONS.byteInputStream())
|
val actual = parseApiVersions(API_VERSIONS.byteInputStream())
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
@@ -92,7 +121,7 @@ class CheckFlaggedApisTest {
|
|||||||
val actual =
|
val actual =
|
||||||
findErrors(
|
findErrors(
|
||||||
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
|
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
|
||||||
parseFlagValues(generateFlagsProto(Aconfig.flag_state.ENABLED)),
|
parseFlagValues(generateFlagsProto(ENABLED, ENABLED)),
|
||||||
parseApiVersions(API_VERSIONS.byteInputStream()))
|
parseApiVersions(API_VERSIONS.byteInputStream()))
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
@@ -101,11 +130,15 @@ class CheckFlaggedApisTest {
|
|||||||
fun testFindErrorsDisabledFlaggedApiIsPresent() {
|
fun testFindErrorsDisabledFlaggedApiIsPresent() {
|
||||||
val expected =
|
val expected =
|
||||||
setOf<ApiError>(
|
setOf<ApiError>(
|
||||||
DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")))
|
DisabledFlaggedApiIsPresentError(Symbol("android.Clazz"), Flag("android.flag.foo")),
|
||||||
|
DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
|
||||||
|
DisabledFlaggedApiIsPresentError(
|
||||||
|
Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
|
||||||
|
)
|
||||||
val actual =
|
val actual =
|
||||||
findErrors(
|
findErrors(
|
||||||
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
|
parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()),
|
||||||
parseFlagValues(generateFlagsProto(Aconfig.flag_state.DISABLED)),
|
parseFlagValues(generateFlagsProto(DISABLED, DISABLED)),
|
||||||
parseApiVersions(API_VERSIONS.byteInputStream()))
|
parseApiVersions(API_VERSIONS.byteInputStream()))
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,9 @@ package com.android.checkflaggedapis
|
|||||||
|
|
||||||
import android.aconfig.Aconfig
|
import android.aconfig.Aconfig
|
||||||
import com.android.tools.metalava.model.BaseItemVisitor
|
import com.android.tools.metalava.model.BaseItemVisitor
|
||||||
|
import com.android.tools.metalava.model.ClassItem
|
||||||
import com.android.tools.metalava.model.FieldItem
|
import com.android.tools.metalava.model.FieldItem
|
||||||
|
import com.android.tools.metalava.model.Item
|
||||||
import com.android.tools.metalava.model.text.ApiFile
|
import com.android.tools.metalava.model.text.ApiFile
|
||||||
import com.github.ajalt.clikt.core.CliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
import com.github.ajalt.clikt.core.ProgramResult
|
import com.github.ajalt.clikt.core.ProgramResult
|
||||||
@@ -167,21 +169,30 @@ The tool will exit with a non-zero exit code if any flagged APIs are found to be
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
|
internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
|
||||||
// TODO(334870672): add support for classes and metods
|
// TODO(334870672): add support for metods
|
||||||
val output = mutableSetOf<Pair<Symbol, Flag>>()
|
val output = mutableSetOf<Pair<Symbol, Flag>>()
|
||||||
val visitor =
|
val visitor =
|
||||||
object : BaseItemVisitor() {
|
object : BaseItemVisitor() {
|
||||||
|
override fun visitClass(cls: ClassItem) {
|
||||||
|
getFlagOrNull(cls)?.let { flag ->
|
||||||
|
val symbol = Symbol.create(cls.baselineElementId())
|
||||||
|
output.add(Pair(symbol, flag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitField(field: FieldItem) {
|
override fun visitField(field: FieldItem) {
|
||||||
val flag =
|
getFlagOrNull(field)?.let { flag ->
|
||||||
field.modifiers
|
val symbol = Symbol.create(field.baselineElementId())
|
||||||
|
output.add(Pair(symbol, flag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFlagOrNull(item: Item): Flag? {
|
||||||
|
return item.modifiers
|
||||||
.findAnnotation("android.annotation.FlaggedApi")
|
.findAnnotation("android.annotation.FlaggedApi")
|
||||||
?.findAttribute("value")
|
?.findAttribute("value")
|
||||||
?.value
|
?.value
|
||||||
?.value() as? String
|
?.let { Flag(it.value() as String) }
|
||||||
if (flag != null) {
|
|
||||||
val symbol = Symbol.create(field.baselineElementId())
|
|
||||||
output.add(Pair(symbol, Flag(flag)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val codebase = ApiFile.parseApi(path, input)
|
val codebase = ApiFile.parseApi(path, input)
|
||||||
@@ -203,6 +214,18 @@ internal fun parseApiVersions(input: InputStream): Set<Symbol> {
|
|||||||
val factory = DocumentBuilderFactory.newInstance()
|
val factory = DocumentBuilderFactory.newInstance()
|
||||||
val parser = factory.newDocumentBuilder()
|
val parser = factory.newDocumentBuilder()
|
||||||
val document = parser.parse(input)
|
val document = parser.parse(input)
|
||||||
|
|
||||||
|
val classes = document.getElementsByTagName("class")
|
||||||
|
// ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
|
||||||
|
for (i in 0.rangeUntil(classes.getLength())) {
|
||||||
|
val cls = classes.item(i)
|
||||||
|
val className =
|
||||||
|
requireNotNull(cls.getAttribute("name")) {
|
||||||
|
"Bad XML: <class> element without name attribute"
|
||||||
|
}
|
||||||
|
output.add(Symbol.create(className))
|
||||||
|
}
|
||||||
|
|
||||||
val fields = document.getElementsByTagName("field")
|
val fields = document.getElementsByTagName("field")
|
||||||
// ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
|
// ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
|
||||||
for (i in 0.rangeUntil(fields.getLength())) {
|
for (i in 0.rangeUntil(fields.getLength())) {
|
||||||
@@ -216,6 +239,7 @@ internal fun parseApiVersions(input: InputStream): Set<Symbol> {
|
|||||||
.getAttribute("name")
|
.getAttribute("name")
|
||||||
output.add(Symbol.create("$className.$fieldName"))
|
output.add(Symbol.create("$className.$fieldName"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user