blob: 5fede7b86c56cb3d6f7965c93a80ae3609a3956a [file] [log] [blame]
Mårten Kongstad90087242024-04-16 09:55:56 +02001/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16@file:JvmName("Main")
17
18package com.android.checkflaggedapis
19
Mårten Kongstad20de4052024-04-16 11:33:56 +020020import com.android.tools.metalava.model.BaseItemVisitor
21import com.android.tools.metalava.model.FieldItem
22import com.android.tools.metalava.model.text.ApiFile
Mårten Kongstadacfeb112024-04-16 10:30:26 +020023import com.github.ajalt.clikt.core.CliktCommand
24import com.github.ajalt.clikt.core.ProgramResult
Mårten Kongstad20de4052024-04-16 11:33:56 +020025import com.github.ajalt.clikt.parameters.options.help
26import com.github.ajalt.clikt.parameters.options.option
27import com.github.ajalt.clikt.parameters.options.required
28import com.github.ajalt.clikt.parameters.types.path
29import java.io.InputStream
Mårten Kongstadacfeb112024-04-16 10:30:26 +020030
Mårten Kongstade0179972024-04-16 11:16:44 +020031/**
32 * Class representing the fully qualified name of a class, method or field.
33 *
34 * This tool reads a multitude of input formats all of which represents the fully qualified path to
35 * a Java symbol slightly differently. To keep things consistent, all parsed APIs are converted to
36 * Symbols.
37 *
38 * All parts of the fully qualified name of the Symbol are separated by a dot, e.g.:
39 * <pre>
40 * package.class.inner-class.field
41 * </pre>
42 */
43@JvmInline
44internal value class Symbol(val name: String) {
45 companion object {
46 private val FORBIDDEN_CHARS = listOf('/', '#', '$')
47
48 /** Create a new Symbol from a String that may include delimiters other than dot. */
49 fun create(name: String): Symbol {
50 var sanitizedName = name
51 for (ch in FORBIDDEN_CHARS) {
52 sanitizedName = sanitizedName.replace(ch, '.')
53 }
54 return Symbol(sanitizedName)
55 }
56 }
57
58 init {
59 require(!name.isEmpty()) { "empty string" }
60 for (ch in FORBIDDEN_CHARS) {
61 require(!name.contains(ch)) { "$name: contains $ch" }
62 }
63 }
64
65 override fun toString(): String = name.toString()
66}
67
Mårten Kongstaddc3fc2e2024-04-16 11:23:22 +020068/**
69 * Class representing the fully qualified name of an aconfig flag.
70 *
71 * This includes both the flag's package and name, separated by a dot, e.g.:
72 * <pre>
73 * com.android.aconfig.test.disabled_ro
74 * <pre>
75 */
76@JvmInline
77internal value class Flag(val name: String) {
78 override fun toString(): String = name.toString()
79}
80
Mårten Kongstad20de4052024-04-16 11:33:56 +020081class CheckCommand :
82 CliktCommand(
83 help =
84 """
85Check that all flagged APIs are used in the correct way.
86
87This tool reads the API signature file and checks that all flagged APIs are used in the correct way.
88
89The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way.
90""") {
91 private val apiSignaturePath by
92 option("--api-signature")
93 .help(
94 """
95 Path to API signature file.
96 Usually named *current.txt.
97 Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs.
98 """)
99 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
100 .required()
101
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200102 override fun run() {
Mårten Kongstad20de4052024-04-16 11:33:56 +0200103 @Suppress("UNUSED_VARIABLE")
104 val flaggedSymbols =
105 apiSignaturePath.toFile().inputStream().use {
106 parseApiSignature(apiSignaturePath.toString(), it)
107 }
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200108 throw ProgramResult(0)
109 }
110}
111
Mårten Kongstad20de4052024-04-16 11:33:56 +0200112internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
113 // TODO(334870672): add support for classes and metods
114 val output = mutableSetOf<Pair<Symbol, Flag>>()
115 val visitor =
116 object : BaseItemVisitor() {
117 override fun visitField(field: FieldItem) {
118 val flag =
119 field.modifiers
120 .findAnnotation("android.annotation.FlaggedApi")
121 ?.findAttribute("value")
122 ?.value
123 ?.value() as? String
124 if (flag != null) {
125 val symbol = Symbol.create(field.baselineElementId())
126 output.add(Pair(symbol, Flag(flag)))
127 }
128 }
129 }
130 val codebase = ApiFile.parseApi(path, input)
131 codebase.accept(visitor)
132 return output
133}
134
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200135fun main(args: Array<String>) = CheckCommand().main(args)