Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cfbcdc1e authored by Michael Wright's avatar Michael Wright Committed by Gerrit Code Review
Browse files

Merge changes Ia860d7b0,Ie98db767 into main

* changes:
  check-flagged-apis: allow / chars in Symbol names
  check-flagged-apis: add support for methods (no parameters)
parents 013330b0 8d74fd0a
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -31,8 +31,9 @@ private val API_SIGNATURE =
      // Signature format: 2.0
      package android {
        @FlaggedApi("android.flag.foo") public final class Clazz {
          ctor public Clazz();
          ctor @FlaggedApi("android.flag.foo") public Clazz();
          field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
          method @FlaggedApi("android.flag.foo") public int getErrorCode();
        }
        @FlaggedApi("android.flag.bar") public static class Clazz.Builder {
        }
@@ -47,6 +48,7 @@ private val API_VERSIONS =
        <class name="android/Clazz" since="1">
          <method name="&lt;init>()V"/>
          <field name="FOO"/>
          <method name="getErrorCode()I"/>
        </class>
        <class name="android/Clazz${"$"}Builder" since="2">
        </class>
@@ -88,7 +90,9 @@ class CheckFlaggedApisTest {
    val expected =
        setOf(
            Pair(Symbol("android.Clazz"), Flag("android.flag.foo")),
            Pair(Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
            Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
            Pair(Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
            Pair(Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
        )
    val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
@@ -108,7 +112,9 @@ class CheckFlaggedApisTest {
    val expected: Set<Symbol> =
        setOf(
            Symbol("android.Clazz"),
            Symbol("android.Clazz.Clazz()"),
            Symbol("android.Clazz.FOO"),
            Symbol("android.Clazz.getErrorCode()"),
            Symbol("android.Clazz.Builder"),
        )
    val actual = parseApiVersions(API_VERSIONS.byteInputStream())
@@ -131,7 +137,11 @@ class CheckFlaggedApisTest {
    val expected =
        setOf<ApiError>(
            DisabledFlaggedApiIsPresentError(Symbol("android.Clazz"), Flag("android.flag.foo")),
            DisabledFlaggedApiIsPresentError(
                Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
            DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
            DisabledFlaggedApiIsPresentError(
                Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
            DisabledFlaggedApiIsPresentError(
                Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
        )
+47 −5
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ 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.Item
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ProgramResult
@@ -48,7 +49,7 @@ import org.w3c.dom.Node
@JvmInline
internal value class Symbol(val name: String) {
  companion object {
    private val FORBIDDEN_CHARS = listOf('/', '#', '$')
    private val FORBIDDEN_CHARS = listOf('#', '$')

    /** Create a new Symbol from a String that may include delimiters other than dot. */
    fun create(name: String): Symbol {
@@ -187,6 +188,25 @@ internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbo
          }
        }

        override fun visitMethod(method: MethodItem) {
          getFlagOrNull(method)?.let { flag ->
            val name = buildString {
              append(method.containingClass().qualifiedName())
              append(".")
              append(method.name())
              append("(")
              // TODO(334870672): replace this early return with proper parsing of the command line
              // arguments, followed by translation to Lname/of/class; + III format
              if (!method.parameters().isEmpty()) {
                return
              }
              append(")")
            }
            val symbol = Symbol.create(name)
            output.add(Pair(symbol, flag))
          }
        }

        private fun getFlagOrNull(item: Item): Flag? {
          return item.modifiers
              .findAnnotation("android.annotation.FlaggedApi")
@@ -223,7 +243,7 @@ internal fun parseApiVersions(input: InputStream): Set<Symbol> {
        requireNotNull(cls.getAttribute("name")) {
          "Bad XML: <class> element without name attribute"
        }
    output.add(Symbol.create(className))
    output.add(Symbol.create(className.replace("/", ".")))
  }

  val fields = document.getElementsByTagName("field")
@@ -235,9 +255,31 @@ internal fun parseApiVersions(input: InputStream): Set<Symbol> {
          "Bad XML: <field> element without name attribute"
        }
    val className =
        requireNotNull(field.getParentNode()) { "Bad XML: top level <field> element" }
            .getAttribute("name")
    output.add(Symbol.create("$className.$fieldName"))
        requireNotNull(field.getParentNode()?.getAttribute("name")) { "Bad XML: top level <field> element" }
    output.add(Symbol.create("${className.replace("/", ".")}.$fieldName"))
  }

  val methods = document.getElementsByTagName("method")
  // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
  for (i in 0.rangeUntil(methods.getLength())) {
    val method = methods.item(i)
    val methodSignature =
        requireNotNull(method.getAttribute("name")) {
          "Bad XML: <method> element without name attribute"
        }
    val methodSignatureParts = methodSignature.split(Regex("\\(|\\)"))
    if (methodSignatureParts.size != 3) {
      throw Exception("Bad XML: method signature '$methodSignature': debug $methodSignatureParts")
    }
    var (methodName, methodArgs, methodReturnValue) = methodSignatureParts
    val packageAndClassName =
        requireNotNull(method.getParentNode()?.getAttribute("name")) {
          "Bad XML: top level <method> element, or <class> element missing name attribute"
        }
    if (methodName == "<init>") {
      methodName = packageAndClassName.split("/").last()
    }
    output.add(Symbol.create("${packageAndClassName.replace("/", ".")}.$methodName($methodArgs)"))
  }

  return output