1 /* <lambda>null2 * 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 17 package com.android.tools.metalava.cli.compatibility 18 19 import com.android.tools.metalava.SignatureFileCache 20 import com.android.tools.metalava.cli.common.BaselineOptionsMixin 21 import com.android.tools.metalava.cli.common.CommonBaselineOptions 22 import com.android.tools.metalava.cli.common.ExecutionEnvironment 23 import com.android.tools.metalava.cli.common.PreviouslyReleasedApi 24 import com.android.tools.metalava.cli.common.allowStructuredOptionName 25 import com.android.tools.metalava.cli.common.existingFile 26 import com.android.tools.metalava.cli.common.map 27 import com.android.tools.metalava.model.Codebase 28 import com.android.tools.metalava.model.api.surface.ApiVariantType 29 import com.android.tools.metalava.model.visitors.ApiType 30 import com.github.ajalt.clikt.parameters.groups.OptionGroup 31 import com.github.ajalt.clikt.parameters.options.multiple 32 import com.github.ajalt.clikt.parameters.options.option 33 import com.github.ajalt.clikt.parameters.options.unique 34 import java.io.File 35 36 const val ARG_CHECK_COMPATIBILITY_API_RELEASED = "--check-compatibility:api:released" 37 const val ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED = "--check-compatibility:removed:released" 38 const val ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED = "--error-message:compatibility:released" 39 40 const val ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED = "--baseline:compatibility:released" 41 const val ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED = 42 "--update-baseline:compatibility:released" 43 const val ARG_API_COMPAT_ANNOTATION = "--api-compat-annotation" 44 45 /** The name of the group, can be used in help text to refer to the options in this group. */ 46 const val COMPATIBILITY_CHECK_GROUP = "Compatibility Checks" 47 48 class CompatibilityCheckOptions( 49 executionEnvironment: ExecutionEnvironment = ExecutionEnvironment(), 50 commonBaselineOptions: CommonBaselineOptions = CommonBaselineOptions(), 51 ) : 52 OptionGroup( 53 name = COMPATIBILITY_CHECK_GROUP, 54 help = 55 """ 56 Options controlling which, if any, compatibility checks are performed against a 57 previously released API. 58 """ 59 .trimIndent(), 60 ) { 61 62 private val checkReleasedApi: CheckRequest? by 63 option( 64 ARG_CHECK_COMPATIBILITY_API_RELEASED, 65 help = 66 """ 67 Check compatibility of the previously released API. 68 69 When multiple files are provided any files that are a delta on another file 70 must come after the other file, e.g. if `system` is a delta on `public` then 71 `public` must come first, then `system`. Or, in other words, they must be 72 provided in order from the narrowest API to the widest API. 73 """ 74 .trimIndent(), 75 ) 76 .existingFile() 77 .multiple() 78 .allowStructuredOptionName() 79 .map { CheckRequest.optionalCheckRequest(it, ApiType.PUBLIC_API) } 80 81 private val checkReleasedRemoved: CheckRequest? by 82 option( 83 ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, 84 help = 85 """ 86 Check compatibility of the previously released but since removed APIs. 87 88 When multiple files are provided any files that are a delta on another file 89 must come after the other file, e.g. if `system` is a delta on `public` then 90 `public` must come first, then `system`. Or, in other words, they must be 91 provided in order from the narrowest API to the widest API. 92 """ 93 .trimIndent(), 94 ) 95 .existingFile() 96 .multiple() 97 .allowStructuredOptionName() 98 .map { CheckRequest.optionalCheckRequest(it, ApiType.REMOVED) } 99 100 internal val apiCompatAnnotations: Set<String> by 101 option( 102 ARG_API_COMPAT_ANNOTATION, 103 help = 104 """ 105 Specify an annotation important for API compatibility. 106 107 Adding/removing this annotation will be considered an incompatible change. 108 The fully qualified name of the annotation should be passed. 109 """ 110 .trimIndent(), 111 metavar = "<annotation>", 112 ) 113 .multiple() 114 .unique() 115 116 /** 117 * If set, metalava will show this error message when "check-compatibility:*:released" fails. 118 * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED]) 119 */ 120 internal val errorMessage: String? by 121 option( 122 ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED, 123 help = 124 """ 125 If set, this is output when errors are detected in 126 $ARG_CHECK_COMPATIBILITY_API_RELEASED or 127 $ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED. 128 """ 129 .trimIndent(), 130 metavar = "<message>", 131 ) 132 .allowStructuredOptionName() 133 134 private val baselineOptionsMixin = 135 BaselineOptionsMixin( 136 containingGroup = this, 137 executionEnvironment, 138 baselineOptionName = ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED, 139 updateBaselineOptionName = ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED, 140 issueType = "compatibility", 141 description = "compatibility:released", 142 commonBaselineOptions = commonBaselineOptions, 143 ) 144 145 internal val baseline by baselineOptionsMixin::baseline 146 147 /** 148 * Encapsulates information needed to perform a compatibility check of the current API being 149 * generated against a previously released API. 150 */ 151 data class CheckRequest( 152 /** 153 * The previously released API with which the API being generated must be compatible. 154 * 155 * Each file is either a jar file (i.e. has an extension of `.jar`), or otherwise is a 156 * signature file. The latter's extension is not checked because while it usually has an 157 * extension of `.txt`, for legacy reasons Metalava will treat any file without a `,jar` 158 * extension as if it was a signature file. 159 */ 160 val previouslyReleasedApi: PreviouslyReleasedApi, 161 162 /** The part of the API to be checked. */ 163 val apiType: ApiType, 164 ) { 165 /** The last signature file, if any, defining the previously released API. */ 166 val lastSignatureFile by previouslyReleasedApi::lastSignatureFile 167 168 companion object { 169 /** Create a [CheckRequest] if [files] is not empty, otherwise return `null`. */ 170 internal fun optionalCheckRequest(files: List<File>, apiType: ApiType) = 171 PreviouslyReleasedApi.optionalPreviouslyReleasedApi( 172 checkCompatibilityOptionForApiType(apiType), 173 files, 174 apiVariantType = 175 when (apiType) { 176 ApiType.REMOVED -> ApiVariantType.REMOVED 177 else -> ApiVariantType.CORE 178 }, 179 ) 180 ?.let { previouslyReleasedApi -> CheckRequest(previouslyReleasedApi, apiType) } 181 182 private fun checkCompatibilityOptionForApiType(apiType: ApiType) = 183 "--check-compatibility:${apiType.flagName}:released" 184 } 185 186 override fun toString(): String { 187 // This is only used when reporting progress. 188 return "${checkCompatibilityOptionForApiType(apiType)} $previouslyReleasedApi" 189 } 190 } 191 192 /** 193 * The list of [CheckRequest] instances that need to be performed on the API being generated. 194 */ 195 val compatibilityChecks by 196 lazy(LazyThreadSafetyMode.NONE) { listOfNotNull(checkReleasedApi, checkReleasedRemoved) } 197 198 /** 199 * The optional Codebase corresponding to [compatibilityChecks]. 200 * 201 * This is used to provide the previously released API needed for `--revert-annotation`. 202 */ 203 fun previouslyReleasedCodebase(signatureFileCache: SignatureFileCache): Codebase? = 204 compatibilityChecks 205 .map { it.previouslyReleasedApi } 206 .reduceOrNull { p1, p2 -> p1.combine(p2) } 207 ?.load({ signatureFileCache.load(it) }) 208 } 209