blob: 005f6c0fb888385ad8f71d3a124416f504465d30 [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 Kongstad387ff6c2024-04-16 12:42:14 +020020import android.aconfig.Aconfig
Mårten Kongstad20de4052024-04-16 11:33:56 +020021import com.android.tools.metalava.model.BaseItemVisitor
22import com.android.tools.metalava.model.FieldItem
23import com.android.tools.metalava.model.text.ApiFile
Mårten Kongstadacfeb112024-04-16 10:30:26 +020024import com.github.ajalt.clikt.core.CliktCommand
25import com.github.ajalt.clikt.core.ProgramResult
Mårten Kongstad20de4052024-04-16 11:33:56 +020026import com.github.ajalt.clikt.parameters.options.help
27import com.github.ajalt.clikt.parameters.options.option
28import com.github.ajalt.clikt.parameters.options.required
29import com.github.ajalt.clikt.parameters.types.path
30import java.io.InputStream
Mårten Kongstadacfeb112024-04-16 10:30:26 +020031
Mårten Kongstade0179972024-04-16 11:16:44 +020032/**
33 * Class representing the fully qualified name of a class, method or field.
34 *
35 * This tool reads a multitude of input formats all of which represents the fully qualified path to
36 * a Java symbol slightly differently. To keep things consistent, all parsed APIs are converted to
37 * Symbols.
38 *
39 * All parts of the fully qualified name of the Symbol are separated by a dot, e.g.:
40 * <pre>
41 * package.class.inner-class.field
42 * </pre>
43 */
44@JvmInline
45internal value class Symbol(val name: String) {
46 companion object {
47 private val FORBIDDEN_CHARS = listOf('/', '#', '$')
48
49 /** Create a new Symbol from a String that may include delimiters other than dot. */
50 fun create(name: String): Symbol {
51 var sanitizedName = name
52 for (ch in FORBIDDEN_CHARS) {
53 sanitizedName = sanitizedName.replace(ch, '.')
54 }
55 return Symbol(sanitizedName)
56 }
57 }
58
59 init {
60 require(!name.isEmpty()) { "empty string" }
61 for (ch in FORBIDDEN_CHARS) {
62 require(!name.contains(ch)) { "$name: contains $ch" }
63 }
64 }
65
66 override fun toString(): String = name.toString()
67}
68
Mårten Kongstaddc3fc2e2024-04-16 11:23:22 +020069/**
70 * Class representing the fully qualified name of an aconfig flag.
71 *
72 * This includes both the flag's package and name, separated by a dot, e.g.:
73 * <pre>
74 * com.android.aconfig.test.disabled_ro
75 * <pre>
76 */
77@JvmInline
78internal value class Flag(val name: String) {
79 override fun toString(): String = name.toString()
80}
81
Mårten Kongstad20de4052024-04-16 11:33:56 +020082class CheckCommand :
83 CliktCommand(
84 help =
85 """
86Check that all flagged APIs are used in the correct way.
87
88This tool reads the API signature file and checks that all flagged APIs are used in the correct way.
89
90The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way.
91""") {
92 private val apiSignaturePath by
93 option("--api-signature")
94 .help(
95 """
96 Path to API signature file.
97 Usually named *current.txt.
98 Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs.
99 """)
100 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
101 .required()
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200102 private val flagValuesPath by
103 option("--flag-values")
104 .help(
105 """
106 Path to aconfig parsed_flags binary proto file.
107 Tip: `m all_aconfig_declarations` will generate a file that includes all information about all flags.
108 """)
109 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
110 .required()
Mårten Kongstad20de4052024-04-16 11:33:56 +0200111
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200112 override fun run() {
Mårten Kongstad20de4052024-04-16 11:33:56 +0200113 @Suppress("UNUSED_VARIABLE")
114 val flaggedSymbols =
115 apiSignaturePath.toFile().inputStream().use {
116 parseApiSignature(apiSignaturePath.toString(), it)
117 }
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200118 @Suppress("UNUSED_VARIABLE")
119 val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) }
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200120 throw ProgramResult(0)
121 }
122}
123
Mårten Kongstad20de4052024-04-16 11:33:56 +0200124internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
125 // TODO(334870672): add support for classes and metods
126 val output = mutableSetOf<Pair<Symbol, Flag>>()
127 val visitor =
128 object : BaseItemVisitor() {
129 override fun visitField(field: FieldItem) {
130 val flag =
131 field.modifiers
132 .findAnnotation("android.annotation.FlaggedApi")
133 ?.findAttribute("value")
134 ?.value
135 ?.value() as? String
136 if (flag != null) {
137 val symbol = Symbol.create(field.baselineElementId())
138 output.add(Pair(symbol, Flag(flag)))
139 }
140 }
141 }
142 val codebase = ApiFile.parseApi(path, input)
143 codebase.accept(visitor)
144 return output
145}
146
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200147internal fun parseFlagValues(input: InputStream): Map<Flag, Boolean> {
148 val parsedFlags = Aconfig.parsed_flags.parseFrom(input).getParsedFlagList()
149 return parsedFlags.associateBy(
150 { Flag("${it.getPackage()}.${it.getName()}") },
151 { it.getState() == Aconfig.flag_state.ENABLED })
152}
153
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200154fun main(args: Array<String>) = CheckCommand().main(args)