blob: d323c200da82a8e4b5e26fd1e6ce5d7471c86d23 [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
Paul Duffin9acbefe2024-07-16 13:35:31 +010022import com.android.tools.metalava.model.CallableItem
Mårten Kongstad18ff19a2024-04-26 05:48:57 +020023import com.android.tools.metalava.model.ClassItem
Mårten Kongstad20de4052024-04-16 11:33:56 +020024import com.android.tools.metalava.model.FieldItem
Mårten Kongstad18ff19a2024-04-26 05:48:57 +020025import com.android.tools.metalava.model.Item
Mårten Kongstad20de4052024-04-16 11:33:56 +020026import com.android.tools.metalava.model.text.ApiFile
Mårten Kongstadacfeb112024-04-16 10:30:26 +020027import com.github.ajalt.clikt.core.CliktCommand
28import com.github.ajalt.clikt.core.ProgramResult
Mårten Kongstadd5ce20f2024-06-10 13:59:46 +020029import com.github.ajalt.clikt.core.subcommands
Mårten Kongstad20de4052024-04-16 11:33:56 +020030import com.github.ajalt.clikt.parameters.options.help
31import com.github.ajalt.clikt.parameters.options.option
32import com.github.ajalt.clikt.parameters.options.required
33import com.github.ajalt.clikt.parameters.types.path
34import java.io.InputStream
Mårten Kongstadb673d3b2024-04-16 18:34:20 +020035import javax.xml.parsers.DocumentBuilderFactory
36import org.w3c.dom.Node
Mårten Kongstadacfeb112024-04-16 10:30:26 +020037
Mårten Kongstade0179972024-04-16 11:16:44 +020038/**
39 * Class representing the fully qualified name of a class, method or field.
40 *
41 * This tool reads a multitude of input formats all of which represents the fully qualified path to
42 * a Java symbol slightly differently. To keep things consistent, all parsed APIs are converted to
43 * Symbols.
44 *
Mårten Kongstadece054c2024-05-02 09:45:11 +020045 * Symbols are encoded using the format similar to the one described in section 4.3.2 of the JVM
46 * spec [1], that is, "package.class.inner-class.method(int, int[], android.util.Clazz)" is
47 * represented as
Mårten Kongstade0179972024-04-16 11:16:44 +020048 * <pre>
Mårten Kongstadece054c2024-05-02 09:45:11 +020049 * package.class.inner-class.method(II[Landroid/util/Clazz;)
50 * <pre>
51 *
52 * Where possible, the format has been simplified (to make translation of the
53 * various input formats easier): for instance, only / is used as delimiter (#
54 * and $ are never used).
55 *
56 * 1. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2
Mårten Kongstade0179972024-04-16 11:16:44 +020057 */
Mårten Kongstada1fe3712024-05-06 13:46:21 +020058internal sealed class Symbol {
Mårten Kongstade0179972024-04-16 11:16:44 +020059 companion object {
Mårten Kongstadece054c2024-05-02 09:45:11 +020060 private val FORBIDDEN_CHARS = listOf('#', '$', '.')
Mårten Kongstade0179972024-04-16 11:16:44 +020061
Mårten Kongstadc3f05a62024-05-06 21:42:15 +020062 fun createClass(clazz: String, superclass: String?, interfaces: Set<String>): Symbol {
63 return ClassSymbol(
64 toInternalFormat(clazz),
65 superclass?.let { toInternalFormat(it) },
66 interfaces.map { toInternalFormat(it) }.toSet())
Mårten Kongstada1fe3712024-05-06 13:46:21 +020067 }
68
69 fun createField(clazz: String, field: String): Symbol {
70 require(!field.contains("(") && !field.contains(")"))
71 return MemberSymbol(toInternalFormat(clazz), toInternalFormat(field))
72 }
73
74 fun createMethod(clazz: String, method: String): Symbol {
75 return MemberSymbol(toInternalFormat(clazz), toInternalFormat(method))
76 }
77
78 protected fun toInternalFormat(name: String): String {
79 var internalName = name
Mårten Kongstade0179972024-04-16 11:16:44 +020080 for (ch in FORBIDDEN_CHARS) {
Mårten Kongstada1fe3712024-05-06 13:46:21 +020081 internalName = internalName.replace(ch, '/')
Mårten Kongstade0179972024-04-16 11:16:44 +020082 }
Mårten Kongstada1fe3712024-05-06 13:46:21 +020083 return internalName
Mårten Kongstade0179972024-04-16 11:16:44 +020084 }
85 }
86
Mårten Kongstada1fe3712024-05-06 13:46:21 +020087 abstract fun toPrettyString(): String
88}
Mårten Kongstade0179972024-04-16 11:16:44 +020089
Mårten Kongstadc3f05a62024-05-06 21:42:15 +020090internal data class ClassSymbol(
91 val clazz: String,
92 val superclass: String?,
93 val interfaces: Set<String>
94) : Symbol() {
Mårten Kongstada1fe3712024-05-06 13:46:21 +020095 override fun toPrettyString(): String = "$clazz"
96}
97
98internal data class MemberSymbol(val clazz: String, val member: String) : Symbol() {
99 override fun toPrettyString(): String = "$clazz/$member"
Mårten Kongstade0179972024-04-16 11:16:44 +0200100}
101
Mårten Kongstaddc3fc2e2024-04-16 11:23:22 +0200102/**
103 * Class representing the fully qualified name of an aconfig flag.
104 *
105 * This includes both the flag's package and name, separated by a dot, e.g.:
106 * <pre>
107 * com.android.aconfig.test.disabled_ro
108 * <pre>
109 */
110@JvmInline
111internal value class Flag(val name: String) {
112 override fun toString(): String = name.toString()
113}
114
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200115internal sealed class ApiError {
116 abstract val symbol: Symbol
117 abstract val flag: Flag
118}
119
120internal data class EnabledFlaggedApiNotPresentError(
121 override val symbol: Symbol,
122 override val flag: Flag
123) : ApiError() {
124 override fun toString(): String {
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200125 return "error: enabled @FlaggedApi not present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag"
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200126 }
127}
128
129internal data class DisabledFlaggedApiIsPresentError(
130 override val symbol: Symbol,
131 override val flag: Flag
132) : ApiError() {
133 override fun toString(): String {
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200134 return "error: disabled @FlaggedApi is present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag"
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200135 }
136}
137
138internal data class UnknownFlagError(override val symbol: Symbol, override val flag: Flag) :
139 ApiError() {
140 override fun toString(): String {
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200141 return "error: unknown flag: symbol=${symbol.toPrettyString()} flag=$flag"
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200142 }
143}
144
Mårten Kongstad576e8182024-06-10 15:33:12 +0200145val ARG_API_SIGNATURE = "--api-signature"
146val ARG_API_SIGNATURE_HELP =
147 """
148Path to API signature file.
149Usually named *current.txt.
150Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs.
151"""
152
153val ARG_FLAG_VALUES = "--flag-values"
154val ARG_FLAG_VALUES_HELP =
155 """
156Path to aconfig parsed_flags binary proto file.
157Tip: `m all_aconfig_declarations` will generate a file that includes all information about all flags.
158"""
159
160val ARG_API_VERSIONS = "--api-versions"
161val ARG_API_VERSIONS_HELP =
162 """
163Path to API versions XML file.
164Usually named xml-versions.xml.
165Tip: `m sdk dist` will generate a file that includes all platform and mainline APIs.
166"""
167
Mårten Kongstadd5ce20f2024-06-10 13:59:46 +0200168class MainCommand : CliktCommand() {
169 override fun run() {}
170}
171
Mårten Kongstad20de4052024-04-16 11:33:56 +0200172class CheckCommand :
173 CliktCommand(
174 help =
175 """
176Check that all flagged APIs are used in the correct way.
177
178This tool reads the API signature file and checks that all flagged APIs are used in the correct way.
179
180The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way.
181""") {
182 private val apiSignaturePath by
Mårten Kongstad576e8182024-06-10 15:33:12 +0200183 option(ARG_API_SIGNATURE)
184 .help(ARG_API_SIGNATURE_HELP)
Mårten Kongstad20de4052024-04-16 11:33:56 +0200185 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
186 .required()
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200187 private val flagValuesPath by
Mårten Kongstad576e8182024-06-10 15:33:12 +0200188 option(ARG_FLAG_VALUES)
189 .help(ARG_FLAG_VALUES_HELP)
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200190 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
191 .required()
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200192 private val apiVersionsPath by
Mårten Kongstad576e8182024-06-10 15:33:12 +0200193 option(ARG_API_VERSIONS)
194 .help(ARG_API_VERSIONS_HELP)
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200195 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
196 .required()
Mårten Kongstad20de4052024-04-16 11:33:56 +0200197
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200198 override fun run() {
Mårten Kongstad20de4052024-04-16 11:33:56 +0200199 val flaggedSymbols =
200 apiSignaturePath.toFile().inputStream().use {
201 parseApiSignature(apiSignaturePath.toString(), it)
202 }
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200203 val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) }
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200204 val exportedSymbols = apiVersionsPath.toFile().inputStream().use { parseApiVersions(it) }
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200205 val errors = findErrors(flaggedSymbols, flags, exportedSymbols)
206 for (e in errors) {
207 println(e)
208 }
209 throw ProgramResult(errors.size)
Mårten Kongstadacfeb112024-04-16 10:30:26 +0200210 }
211}
212
Mårten Kongstad1692a362024-06-10 16:04:34 +0200213class ListCommand :
214 CliktCommand(
215 help =
216 """
217List all flagged APIs and corresponding flags.
218
219The output format is "<fully-qualified-name-of-flag> <state-of-flag> <API>", one line per API.
220
221The output can be post-processed by e.g. piping it to grep to filter out only enabled APIs, or all APIs guarded by a given flag.
222""") {
223 private val apiSignaturePath by
224 option(ARG_API_SIGNATURE)
225 .help(ARG_API_SIGNATURE_HELP)
226 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
227 .required()
228 private val flagValuesPath by
229 option(ARG_FLAG_VALUES)
230 .help(ARG_FLAG_VALUES_HELP)
231 .path(mustExist = true, canBeDir = false, mustBeReadable = true)
232 .required()
233
234 override fun run() {
235 val flaggedSymbols =
236 apiSignaturePath.toFile().inputStream().use {
237 parseApiSignature(apiSignaturePath.toString(), it)
238 }
239 val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) }
240 val output = listFlaggedApis(flaggedSymbols, flags)
241 if (output.isNotEmpty()) {
242 println(output.joinToString("\n"))
243 }
244 }
245}
246
Mårten Kongstad20de4052024-04-16 11:33:56 +0200247internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
Mårten Kongstad20de4052024-04-16 11:33:56 +0200248 val output = mutableSetOf<Pair<Symbol, Flag>>()
249 val visitor =
250 object : BaseItemVisitor() {
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200251 override fun visitClass(cls: ClassItem) {
252 getFlagOrNull(cls)?.let { flag ->
Mårten Kongstad7c3571f2024-05-06 14:53:54 +0200253 val symbol =
254 Symbol.createClass(
255 cls.baselineElementId(),
Mårten Kongstadaa41dac2024-05-22 15:13:54 +0200256 if (cls.isInterface()) {
257 "java/lang/Object"
258 } else {
259 cls.superClass()?.baselineElementId()
260 },
Mårten Kongstad04d8b462024-05-06 16:26:40 +0200261 cls.allInterfaces()
262 .map { it.baselineElementId() }
263 .filter { it != cls.baselineElementId() }
264 .toSet())
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200265 output.add(Pair(symbol, flag))
Mårten Kongstad20de4052024-04-16 11:33:56 +0200266 }
267 }
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200268
269 override fun visitField(field: FieldItem) {
270 getFlagOrNull(field)?.let { flag ->
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200271 val symbol =
272 Symbol.createField(field.containingClass().baselineElementId(), field.name())
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200273 output.add(Pair(symbol, flag))
274 }
275 }
276
Paul Duffin9acbefe2024-07-16 13:35:31 +0100277 override fun visitCallable(callable: CallableItem) {
278 getFlagOrNull(callable)?.let { flag ->
279 val callableSignature = buildString {
280 append(callable.name())
Mårten Kongstad40da9702024-04-27 01:42:51 +0200281 append("(")
Paul Duffin9acbefe2024-07-16 13:35:31 +0100282 callable.parameters().joinTo(this, separator = "") { it.type().internalName() }
Mårten Kongstad40da9702024-04-27 01:42:51 +0200283 append(")")
284 }
Paul Duffin9acbefe2024-07-16 13:35:31 +0100285 val symbol = Symbol.createMethod(callable.containingClass().qualifiedName(), callableSignature)
Mårten Kongstad40da9702024-04-27 01:42:51 +0200286 output.add(Pair(symbol, flag))
287 }
288 }
289
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200290 private fun getFlagOrNull(item: Item): Flag? {
291 return item.modifiers
292 .findAnnotation("android.annotation.FlaggedApi")
293 ?.findAttribute("value")
294 ?.value
295 ?.let { Flag(it.value() as String) }
296 }
Mårten Kongstad20de4052024-04-16 11:33:56 +0200297 }
298 val codebase = ApiFile.parseApi(path, input)
299 codebase.accept(visitor)
300 return output
301}
302
Mårten Kongstad387ff6c2024-04-16 12:42:14 +0200303internal fun parseFlagValues(input: InputStream): Map<Flag, Boolean> {
304 val parsedFlags = Aconfig.parsed_flags.parseFrom(input).getParsedFlagList()
305 return parsedFlags.associateBy(
306 { Flag("${it.getPackage()}.${it.getName()}") },
307 { it.getState() == Aconfig.flag_state.ENABLED })
308}
309
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200310internal fun parseApiVersions(input: InputStream): Set<Symbol> {
311 fun Node.getAttribute(name: String): String? = getAttributes()?.getNamedItem(name)?.getNodeValue()
312
313 val output = mutableSetOf<Symbol>()
314 val factory = DocumentBuilderFactory.newInstance()
315 val parser = factory.newDocumentBuilder()
316 val document = parser.parse(input)
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200317
318 val classes = document.getElementsByTagName("class")
319 // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
320 for (i in 0.rangeUntil(classes.getLength())) {
321 val cls = classes.item(i)
322 val className =
323 requireNotNull(cls.getAttribute("name")) {
324 "Bad XML: <class> element without name attribute"
325 }
Mårten Kongstadc3f05a62024-05-06 21:42:15 +0200326 var superclass: String? = null
Mårten Kongstad7c3571f2024-05-06 14:53:54 +0200327 val interfaces = mutableSetOf<String>()
328 val children = cls.getChildNodes()
329 for (j in 0.rangeUntil(children.getLength())) {
330 val child = children.item(j)
Mårten Kongstadc3f05a62024-05-06 21:42:15 +0200331 when (child.getNodeName()) {
332 "extends" -> {
333 superclass =
334 requireNotNull(child.getAttribute("name")) {
335 "Bad XML: <extends> element without name attribute"
336 }
337 }
338 "implements" -> {
339 val interfaceName =
340 requireNotNull(child.getAttribute("name")) {
341 "Bad XML: <implements> element without name attribute"
342 }
343 interfaces.add(interfaceName)
344 }
Mårten Kongstad7c3571f2024-05-06 14:53:54 +0200345 }
346 }
Mårten Kongstadc3f05a62024-05-06 21:42:15 +0200347 output.add(Symbol.createClass(className, superclass, interfaces))
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200348 }
349
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200350 val fields = document.getElementsByTagName("field")
351 // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
352 for (i in 0.rangeUntil(fields.getLength())) {
353 val field = fields.item(i)
Mårten Kongstad04e45642024-04-26 05:39:03 +0200354 val fieldName =
355 requireNotNull(field.getAttribute("name")) {
356 "Bad XML: <field> element without name attribute"
357 }
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200358 val className =
Mårten Kongstadece054c2024-05-02 09:45:11 +0200359 requireNotNull(field.getParentNode()?.getAttribute("name")) {
360 "Bad XML: top level <field> element"
361 }
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200362 output.add(Symbol.createField(className, fieldName))
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200363 }
Mårten Kongstad18ff19a2024-04-26 05:48:57 +0200364
Mårten Kongstad40da9702024-04-27 01:42:51 +0200365 val methods = document.getElementsByTagName("method")
366 // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
367 for (i in 0.rangeUntil(methods.getLength())) {
368 val method = methods.item(i)
369 val methodSignature =
370 requireNotNull(method.getAttribute("name")) {
371 "Bad XML: <method> element without name attribute"
372 }
373 val methodSignatureParts = methodSignature.split(Regex("\\(|\\)"))
374 if (methodSignatureParts.size != 3) {
Mårten Kongstad9aef0d92024-04-29 10:25:34 +0200375 throw Exception("Bad XML: method signature '$methodSignature'")
Mårten Kongstad40da9702024-04-27 01:42:51 +0200376 }
Mårten Kongstadcd93aeb2024-05-02 10:19:18 +0200377 var (methodName, methodArgs, _) = methodSignatureParts
Mårten Kongstad40da9702024-04-27 01:42:51 +0200378 val packageAndClassName =
379 requireNotNull(method.getParentNode()?.getAttribute("name")) {
Mårten Kongstad02525a82024-05-06 10:28:02 +0200380 "Bad XML: top level <method> element, or <class> element missing name attribute"
381 }
382 .replace("$", "/")
Mårten Kongstad40da9702024-04-27 01:42:51 +0200383 if (methodName == "<init>") {
384 methodName = packageAndClassName.split("/").last()
385 }
Mårten Kongstada1fe3712024-05-06 13:46:21 +0200386 output.add(Symbol.createMethod(packageAndClassName, "$methodName($methodArgs)"))
Mårten Kongstad40da9702024-04-27 01:42:51 +0200387 }
388
Mårten Kongstadb673d3b2024-04-16 18:34:20 +0200389 return output
390}
391
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200392/**
393 * Find errors in the given data.
394 *
395 * @param flaggedSymbolsInSource the set of symbols that are flagged in the source code
396 * @param flags the set of flags and their values
397 * @param symbolsInOutput the set of symbols that are present in the output
398 * @return the set of errors found
399 */
400internal fun findErrors(
401 flaggedSymbolsInSource: Set<Pair<Symbol, Flag>>,
402 flags: Map<Flag, Boolean>,
403 symbolsInOutput: Set<Symbol>
404): Set<ApiError> {
Mårten Kongstadd2c70762024-05-06 14:58:18 +0200405 fun Set<Symbol>.containsSymbol(symbol: Symbol): Boolean {
406 // trivial case: the symbol is explicitly listed in api-versions.xml
407 if (contains(symbol)) {
408 return true
409 }
410
411 // non-trivial case: the symbol could be part of the surrounding class'
412 // super class or interfaces
413 val (className, memberName) =
414 when (symbol) {
415 is ClassSymbol -> return false
416 is MemberSymbol -> {
417 Pair(symbol.clazz, symbol.member)
418 }
419 }
420 val clazz = find { it is ClassSymbol && it.clazz == className } as? ClassSymbol?
421 if (clazz == null) {
422 return false
423 }
424
425 for (interfaceName in clazz.interfaces) {
426 // createMethod is the same as createField, except it allows parenthesis
427 val interfaceSymbol = Symbol.createMethod(interfaceName, memberName)
428 if (contains(interfaceSymbol)) {
429 return true
430 }
431 }
432
Mårten Kongstade8120392024-05-06 21:32:34 +0200433 if (clazz.superclass != null) {
434 val superclassSymbol = Symbol.createMethod(clazz.superclass, memberName)
435 return containsSymbol(superclassSymbol)
436 }
437
Mårten Kongstadd2c70762024-05-06 14:58:18 +0200438 return false
439 }
Mårten Kongstad0d44e722024-05-08 10:00:32 +0200440
441 /**
442 * Returns whether the given flag is enabled for the given symbol.
443 *
444 * A flagged member inside a flagged class is ignored (and the flag value considered disabled) if
445 * the class' flag is disabled.
446 *
447 * @param symbol the symbol to check
448 * @param flag the flag to check
449 * @return whether the flag is enabled for the given symbol
450 */
451 fun isFlagEnabledForSymbol(symbol: Symbol, flag: Flag): Boolean {
452 when (symbol) {
453 is ClassSymbol -> return flags.getValue(flag)
454 is MemberSymbol -> {
455 val memberFlagValue = flags.getValue(flag)
456 if (!memberFlagValue) {
457 return false
458 }
459 // Special case: if the MemberSymbol's flag is enabled, but the outer
460 // ClassSymbol's flag (if the class is flagged) is disabled, consider
461 // the MemberSymbol's flag as disabled:
462 //
463 // @FlaggedApi(this-flag-is-disabled) Clazz {
464 // @FlaggedApi(this-flag-is-enabled) method(); // The Clazz' flag "wins"
465 // }
466 //
467 // Note: the current implementation does not handle nested classes.
468 val classFlagValue =
469 flaggedSymbolsInSource
470 .find { it.first.toPrettyString() == symbol.clazz }
471 ?.let { flags.getValue(it.second) }
472 ?: true
473 return classFlagValue
474 }
475 }
476 }
477
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200478 val errors = mutableSetOf<ApiError>()
479 for ((symbol, flag) in flaggedSymbolsInSource) {
480 try {
Mårten Kongstad0d44e722024-05-08 10:00:32 +0200481 if (isFlagEnabledForSymbol(symbol, flag)) {
Mårten Kongstadd2c70762024-05-06 14:58:18 +0200482 if (!symbolsInOutput.containsSymbol(symbol)) {
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200483 errors.add(EnabledFlaggedApiNotPresentError(symbol, flag))
484 }
485 } else {
Mårten Kongstadd2c70762024-05-06 14:58:18 +0200486 if (symbolsInOutput.containsSymbol(symbol)) {
Mårten Kongstad9238a3a2024-04-16 13:19:50 +0200487 errors.add(DisabledFlaggedApiIsPresentError(symbol, flag))
488 }
489 }
490 } catch (e: NoSuchElementException) {
491 errors.add(UnknownFlagError(symbol, flag))
492 }
493 }
494 return errors
495}
496
Mårten Kongstad1692a362024-06-10 16:04:34 +0200497/**
498 * Collect all known info about all @FlaggedApi annotated APIs.
499 *
500 * Each API will be represented as a String, on the format
501 * <pre>
502 * &lt;fully-qualified-name-of-flag&lt; &lt;state-of-flag&lt; &lt;API&lt;
503 * </pre>
504 *
505 * @param flaggedSymbolsInSource the set of symbols that are flagged in the source code
506 * @param flags the set of flags and their values
507 * @return a list of Strings encoding API data using the format described above, sorted
508 * alphabetically
509 */
510internal fun listFlaggedApis(
511 flaggedSymbolsInSource: Set<Pair<Symbol, Flag>>,
512 flags: Map<Flag, Boolean>
513): List<String> {
514 val output = mutableListOf<String>()
515 for ((symbol, flag) in flaggedSymbolsInSource) {
516 val flagState =
517 when (flags.get(flag)) {
518 true -> "ENABLED"
519 false -> "DISABLED"
520 null -> "UNKNOWN"
521 }
522 output.add("$flag $flagState ${symbol.toPrettyString()}")
523 }
524 output.sort()
525 return output
526}
527
528fun main(args: Array<String>) = MainCommand().subcommands(CheckCommand(), ListCommand()).main(args)