Merge changes I397b32ae,Ic244b896,I5ccf2a64 into main
* changes:
check-flagged-apis: parse flag names and values
check-flagged-apis: parse API signature files
check-flagged-apis: add unit test infrastructure
diff --git a/tools/check-flagged-apis/Android.bp b/tools/check-flagged-apis/Android.bp
index 209c89b..ebd79c1 100644
--- a/tools/check-flagged-apis/Android.bp
+++ b/tools/check-flagged-apis/Android.bp
@@ -13,16 +13,39 @@
// limitations under the License.
package {
+ default_team: "trendy_team_updatable_sdk_apis",
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_defaults {
+ name: "check-flagged-apis-defaults",
+ srcs: [
+ "src/com/android/checkflaggedapis/Main.kt",
+ ],
+ static_libs: [
+ "libaconfig_java_proto_lite",
+ "metalava-signature-reader",
+ "metalava-tools-common-m2-deps",
+ ],
+}
+
java_binary_host {
name: "check-flagged-apis",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "metalava-tools-common-m2-deps",
+ defaults: [
+ "check-flagged-apis-defaults",
],
main_class: "com.android.checkflaggedapis.Main",
}
+
+java_test_host {
+ name: "check-flagged-apis-test",
+ defaults: [
+ "check-flagged-apis-defaults",
+ ],
+ srcs: [
+ "src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt",
+ ],
+ static_libs: [
+ "tradefed",
+ ],
+}
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
new file mode 100644
index 0000000..5fb67be
--- /dev/null
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.checkflaggedapis
+
+import android.aconfig.Aconfig
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val API_SIGNATURE =
+ """
+ // Signature format: 2.0
+ package android {
+ public final class Clazz {
+ ctor public Clazz();
+ field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
+ }
+ }
+"""
+ .trim()
+
+private val PARSED_FLAGS =
+ {
+ val parsed_flag =
+ Aconfig.parsed_flag
+ .newBuilder()
+ .setPackage("android.flag")
+ .setName("foo")
+ .setState(Aconfig.flag_state.ENABLED)
+ .setPermission(Aconfig.flag_permission.READ_ONLY)
+ .build()
+ val parsed_flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(parsed_flag).build()
+ val binaryProto = ByteArrayOutputStream()
+ parsed_flags.writeTo(binaryProto)
+ ByteArrayInputStream(binaryProto.toByteArray())
+ }()
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class CheckFlaggedApisTest : BaseHostJUnit4Test() {
+ @Test
+ fun testParseApiSignature() {
+ val expected = setOf(Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")))
+ val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun testParseFlagValues() {
+ val expected: Map<Flag, Boolean> = mapOf(Flag("android.flag.foo") to true)
+ val actual = parseFlagValues(PARSED_FLAGS)
+ assertEquals(expected, actual)
+ }
+}
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
index af8be11..005f6c0 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -17,8 +17,17 @@
package com.android.checkflaggedapis
+import android.aconfig.Aconfig
+import com.android.tools.metalava.model.BaseItemVisitor
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.text.ApiFile
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ProgramResult
+import com.github.ajalt.clikt.parameters.options.help
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import com.github.ajalt.clikt.parameters.types.path
+import java.io.InputStream
/**
* Class representing the fully qualified name of a class, method or field.
@@ -70,11 +79,76 @@
override fun toString(): String = name.toString()
}
-class CheckCommand : CliktCommand() {
+class CheckCommand :
+ CliktCommand(
+ help =
+ """
+Check that all flagged APIs are used in the correct way.
+
+This tool reads the API signature file and checks that all flagged APIs are used in the correct way.
+
+The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way.
+""") {
+ private val apiSignaturePath by
+ option("--api-signature")
+ .help(
+ """
+ Path to API signature file.
+ Usually named *current.txt.
+ Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs.
+ """)
+ .path(mustExist = true, canBeDir = false, mustBeReadable = true)
+ .required()
+ private val flagValuesPath by
+ option("--flag-values")
+ .help(
+ """
+ Path to aconfig parsed_flags binary proto file.
+ Tip: `m all_aconfig_declarations` will generate a file that includes all information about all flags.
+ """)
+ .path(mustExist = true, canBeDir = false, mustBeReadable = true)
+ .required()
+
override fun run() {
- println("hello world")
+ @Suppress("UNUSED_VARIABLE")
+ val flaggedSymbols =
+ apiSignaturePath.toFile().inputStream().use {
+ parseApiSignature(apiSignaturePath.toString(), it)
+ }
+ @Suppress("UNUSED_VARIABLE")
+ val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) }
throw ProgramResult(0)
}
}
+internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
+ // TODO(334870672): add support for classes and metods
+ val output = mutableSetOf<Pair<Symbol, Flag>>()
+ val visitor =
+ object : BaseItemVisitor() {
+ override fun visitField(field: FieldItem) {
+ val flag =
+ field.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value() as? String
+ if (flag != null) {
+ val symbol = Symbol.create(field.baselineElementId())
+ output.add(Pair(symbol, Flag(flag)))
+ }
+ }
+ }
+ val codebase = ApiFile.parseApi(path, input)
+ codebase.accept(visitor)
+ return output
+}
+
+internal fun parseFlagValues(input: InputStream): Map<Flag, Boolean> {
+ val parsedFlags = Aconfig.parsed_flags.parseFrom(input).getParsedFlagList()
+ return parsedFlags.associateBy(
+ { Flag("${it.getPackage()}.${it.getName()}") },
+ { it.getState() == Aconfig.flag_state.ENABLED })
+}
+
fun main(args: Array<String>) = CheckCommand().main(args)