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