1 /* <lambda>null2 * Copyright (C) 2017 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 package com.android.tools.metalava.apilevels 17 18 import com.android.tools.metalava.SdkIdentifier 19 import com.android.tools.metalava.SignatureFileCache 20 import com.android.tools.metalava.apilevels.ApiToExtensionsMap.Companion.fromXml 21 import com.android.tools.metalava.apilevels.ExtensionSdkJarReader.Companion.findExtensionSdkJarFiles 22 import com.android.tools.metalava.model.Codebase 23 import com.android.tools.metalava.model.Item 24 import com.android.tools.metalava.model.text.SignatureFile 25 import java.io.File 26 import java.io.IOException 27 import java.io.PrintStream 28 import java.nio.charset.StandardCharsets 29 import java.util.function.Predicate 30 31 /** 32 * Main class for command line command to convert the existing API XML/TXT files into diff-based 33 * simple text files. 34 */ 35 class ApiGenerator(private val signatureFileCache: SignatureFileCache) { 36 @Throws(IOException::class, IllegalArgumentException::class) 37 fun generateXml( 38 apiLevels: Array<File>, 39 firstApiLevel: Int, 40 currentApiLevel: Int, 41 isDeveloperPreviewBuild: Boolean, 42 outputFile: File, 43 codebase: Codebase, 44 sdkExtensionsArguments: SdkExtensionsArguments?, 45 removeMissingClasses: Boolean 46 ): Boolean { 47 val notFinalizedApiLevel = currentApiLevel + 1 48 val api = createApiFromAndroidJars(apiLevels, firstApiLevel) 49 if (isDeveloperPreviewBuild || apiLevels.size - 1 < currentApiLevel) { 50 // Only include codebase if we don't have a prebuilt, finalized jar for it. 51 val apiLevel = if (isDeveloperPreviewBuild) notFinalizedApiLevel else currentApiLevel 52 addApisFromCodebase(api, apiLevel, codebase, true) 53 } 54 api.backfillHistoricalFixes() 55 var sdkIdentifiers = emptySet<SdkIdentifier>() 56 if (sdkExtensionsArguments != null) { 57 sdkIdentifiers = 58 processExtensionSdkApis( 59 api, 60 notFinalizedApiLevel, 61 sdkExtensionsArguments.sdkExtJarRoot, 62 sdkExtensionsArguments.sdkExtInfoFile, 63 sdkExtensionsArguments.skipVersionsGreaterThan 64 ) 65 } 66 api.inlineFromHiddenSuperClasses() 67 api.removeImplicitInterfaces() 68 api.removeOverridingMethods() 69 api.prunePackagePrivateClasses() 70 if (removeMissingClasses) { 71 api.removeMissingClasses() 72 } else { 73 api.verifyNoMissingClasses() 74 } 75 return createApiLevelsXml(outputFile, api, sdkIdentifiers) 76 } 77 78 /** 79 * Creates an [Api] from a list of past API signature files. In the generated [Api], the oldest 80 * API version will be represented as level 1, the next as level 2, etc. 81 * 82 * @param previousApiFiles A list of API signature files, one for each version of the API, in 83 * order from oldest to newest API version. 84 */ 85 private fun createApiFromSignatureFiles(previousApiFiles: List<File>): Api { 86 // Starts at level 1 because 0 is not a valid API level. 87 var apiLevel = 1 88 val api = Api(apiLevel) 89 for (apiFile in previousApiFiles) { 90 val codebase: Codebase = signatureFileCache.load(SignatureFile.fromFile(apiFile)) 91 addApisFromCodebase(api, apiLevel, codebase, false) 92 apiLevel += 1 93 } 94 api.clean() 95 return api 96 } 97 98 /** 99 * Generates an API version history file based on the API surfaces of the versions provided. 100 * 101 * @param pastApiVersions A list of API signature files, ordered from the oldest API version to 102 * newest. 103 * @param currentApiVersion A codebase representing the current API surface. 104 * @param outputFile Path of the JSON file to write output to. 105 * @param apiVersionNames The names of the API versions, ordered starting from version 1. This 106 * should include the names of all the [pastApiVersions], then the name of the 107 * [currentApiVersion]. 108 * @param filterEmit The filter to use to determine if an [Item] should be included in the API. 109 * @param filterReference The filter to use to determine if a reference to an [Item] should be 110 * included in the API. 111 */ 112 fun generateJson( 113 pastApiVersions: List<File>, 114 currentApiVersion: Codebase, 115 outputFile: File, 116 apiVersionNames: List<String>, 117 filterEmit: Predicate<Item>, 118 filterReference: Predicate<Item> 119 ) { 120 val api = createApiFromSignatureFiles(pastApiVersions) 121 addApisFromCodebase( 122 api, 123 apiVersionNames.size, 124 currentApiVersion, 125 false, 126 filterEmit, 127 filterReference 128 ) 129 val printer = ApiJsonPrinter(apiVersionNames) 130 printer.print(api, outputFile) 131 } 132 133 private fun createApiFromAndroidJars(apiLevels: Array<File>, firstApiLevel: Int): Api { 134 val api = Api(firstApiLevel) 135 for (apiLevel in firstApiLevel until apiLevels.size) { 136 val jar = apiLevels[apiLevel] 137 api.readAndroidJar(apiLevel, jar) 138 } 139 return api 140 } 141 142 /** 143 * Modify the extension SDK API parts of an API as dictated by a filter. 144 * - remove APIs not listed in the filter 145 * - assign APIs listed in the filter their corresponding extensions 146 * 147 * Some APIs only exist in extension SDKs and not in the Android SDK, but for backwards 148 * compatibility with tools that expect the Android SDK to be the only SDK, metalava needs to 149 * assign such APIs some Android SDK API level. The recommended value is current-api-level + 1, 150 * which is what non-finalized APIs use. 151 * 152 * @param api the api to modify 153 * @param apiLevelNotInAndroidSdk fallback API level for APIs not in the Android SDK 154 * @param sdkJarRoot path to directory containing extension SDK jars (usually 155 * $ANDROID_ROOT/prebuilts/sdk/extensions) 156 * @param filterPath: path to the filter file. @see ApiToExtensionsMap 157 * @throws IOException if the filter file can not be read 158 * @throws IllegalArgumentException if an error is detected in the filter file, or if no jar 159 * files were found 160 */ 161 @Throws(IOException::class, IllegalArgumentException::class) 162 private fun processExtensionSdkApis( 163 api: Api, 164 apiLevelNotInAndroidSdk: Int, 165 sdkJarRoot: File, 166 filterPath: File, 167 skipVersionsGreaterThan: Int? 168 ): Set<SdkIdentifier> { 169 val rules = filterPath.readText() 170 val map = findExtensionSdkJarFiles(sdkJarRoot, skipVersionsGreaterThan) 171 require(map.isNotEmpty()) { "no extension sdk jar files found in $sdkJarRoot" } 172 val moduleMaps: MutableMap<String, ApiToExtensionsMap> = HashMap() 173 for ((mainlineModule, value) in map) { 174 val moduleMap = fromXml(mainlineModule, rules) 175 if (moduleMap.isEmpty()) 176 continue // TODO(b/259115852): remove this (though it is an optimization too). 177 moduleMaps[mainlineModule] = moduleMap 178 for ((version, path) in value) { 179 api.readExtensionJar(version, mainlineModule, path, apiLevelNotInAndroidSdk) 180 } 181 } 182 for (clazz in api.classes) { 183 val module = clazz.mainlineModule ?: continue 184 val extensionsMap = moduleMaps[module] 185 var sdks = 186 extensionsMap!!.calculateSdksAttr( 187 clazz.since, 188 apiLevelNotInAndroidSdk, 189 extensionsMap.getExtensions(clazz), 190 clazz.sinceExtension 191 ) 192 clazz.updateSdks(sdks) 193 var iter = clazz.fieldIterator 194 while (iter.hasNext()) { 195 val field = iter.next() 196 sdks = 197 extensionsMap.calculateSdksAttr( 198 field.since, 199 apiLevelNotInAndroidSdk, 200 extensionsMap.getExtensions(clazz, field), 201 field.sinceExtension 202 ) 203 field.updateSdks(sdks) 204 } 205 iter = clazz.methodIterator 206 while (iter.hasNext()) { 207 val method = iter.next() 208 sdks = 209 extensionsMap.calculateSdksAttr( 210 method.since, 211 apiLevelNotInAndroidSdk, 212 extensionsMap.getExtensions(clazz, method), 213 method.sinceExtension 214 ) 215 method.updateSdks(sdks) 216 } 217 } 218 return fromXml("", rules).getSdkIdentifiers() 219 } 220 221 /** 222 * Creates the simplified diff-based API level. 223 * 224 * @param outFile the output file 225 * @param api the api to write 226 * @param sdkIdentifiers SDKs referenced by the api 227 */ 228 private fun createApiLevelsXml( 229 outFile: File, 230 api: Api, 231 sdkIdentifiers: Set<SdkIdentifier> 232 ): Boolean { 233 val parentFile = outFile.parentFile 234 if (!parentFile.exists()) { 235 val ok = parentFile.mkdirs() 236 if (!ok) { 237 System.err.println("Could not create directory $parentFile") 238 return false 239 } 240 } 241 try { 242 PrintStream(outFile, StandardCharsets.UTF_8).use { stream -> 243 stream.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>") 244 api.print(stream, sdkIdentifiers) 245 } 246 } catch (e: Exception) { 247 e.printStackTrace() 248 return false 249 } 250 return true 251 } 252 253 data class SdkExtensionsArguments( 254 var sdkExtJarRoot: File, 255 var sdkExtInfoFile: File, 256 var skipVersionsGreaterThan: Int? 257 ) 258 } 259