• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.io.File
19 import java.io.IOException
20 
21 /** Generates API version history. */
22 class ApiGenerator {
23     /**
24      * Generates an API version history file based on the API surfaces of the versions provided.
25      *
26      * @param config Configuration provided from command line options.
27      */
28     fun generateApiHistory(config: GenerateApiHistoryConfig) {
29         val versionedApis = config.versionedApis
30         val api = createApiFromVersionedApis(config.useInternalNames, versionedApis)
31 
32         // If necessary, update the sdks properties.
33         config.sdkExtensionsArguments?.let { sdkExtensionsArguments ->
34             updateSdksAttributes(
35                 api,
36                 sdkExtensionsArguments.notFinalizedSdkVersion,
37                 sdkExtensionsArguments.sdkExtensionInfo,
38             )
39         }
40 
41         // If android.os.ext.SdkExtensions exists in the Api then patch up its history.
42         api.patchSdkExtensionsHistory()
43 
44         api.clean()
45 
46         // Apply the appropriate action for missing classes.
47         config.missingClassAction.apply(api)
48 
49         val outputFile = config.outputFile
50         val printer =
51             when (val extension = outputFile.extension) {
52                 "xml" -> {
53                     val availableSdkExtensions =
54                         config.sdkExtensionsArguments?.sdkExtensionInfo?.availableSdkExtensions
55                     ApiXmlPrinter(availableSdkExtensions, versionedApis)
56                 }
57                 "json" -> ApiJsonPrinter()
58                 else ->
59                     error(
60                         "unexpected extension for $outputFile, expected 'xml', or 'json' got '$extension'"
61                     )
62             }
63 
64         createApiLevelsFile(outputFile, printer, api)
65     }
66 
67     /**
68      * Traverses [api] updating the [ApiElement.sdks] properties to list the appropriate extensions.
69      *
70      * Some APIs only exist in extension SDKs and not in the Android SDK, but for backwards
71      * compatibility with tools that expect the Android SDK to be the only SDK, metalava needs to
72      * assign such APIs some Android SDK API version. This uses [versionNotInAndroidSdk].
73      *
74      * @param api the api to modify
75      * @param versionNotInAndroidSdk fallback API level for APIs not in the Android SDK
76      * @param sdkExtensionInfo the [SdkExtensionInfo] read from sdk-extension-info.xml file.
77      */
78     private fun updateSdksAttributes(
79         api: Api,
80         versionNotInAndroidSdk: ApiVersion,
81         sdkExtensionInfo: SdkExtensionInfo,
82     ) {
83         for (clazz in api.classes) {
84             val module = clazz.mainlineModule ?: continue
85 
86             // Get the extensions information for the mainline module. If no information exists for
87             // a particular module then the returned information is empty but can still be used to
88             // calculate sdks attribute.
89             val extensionsMap = sdkExtensionInfo.extensionsMapForJarOrEmpty(module)
90 
91             /** Update the sdks on each [ApiElement] in [elements]. */
92             fun updateSdks(elements: Collection<ApiElement>) {
93                 for (element in elements) {
94                     val sdks =
95                         extensionsMap.calculateSdksAttr(
96                             element.since,
97                             versionNotInAndroidSdk,
98                             extensionsMap.getExtensions(clazz, element),
99                             element.sinceExtension
100                         )
101                     element.updateSdks(sdks)
102                 }
103             }
104 
105             val sdks =
106                 extensionsMap.calculateSdksAttr(
107                     clazz.since,
108                     versionNotInAndroidSdk,
109                     extensionsMap.getExtensions(clazz),
110                     clazz.sinceExtension
111                 )
112             clazz.updateSdks(sdks)
113 
114             updateSdks(clazz.superClasses)
115             updateSdks(clazz.interfaces)
116             updateSdks(clazz.fields)
117             updateSdks(clazz.methods)
118         }
119     }
120 
121     /**
122      * Creates a file containing the [api].
123      *
124      * @param outFile the output file
125      * @param printer the [ApiPrinter] to use to write the file.
126      * @param api the api to write
127      */
128     private fun createApiLevelsFile(
129         outFile: File,
130         printer: ApiPrinter,
131         api: Api,
132     ) {
133         val parentFile = outFile.parentFile
134         if (!parentFile.exists()) {
135             val ok = parentFile.mkdirs()
136             if (!ok) {
137                 throw IOException("Could not create directory $parentFile")
138             }
139         }
140 
141         outFile.printWriter().use { writer ->
142             printer.print(api, writer)
143             if (writer.checkError()) throw IOException("Error writing $outFile")
144         }
145     }
146 
147     data class SdkExtensionsArguments(
148         /**
149          * The `sdk-extension-info.xml` file containing information about the available sdk
150          * extensions and the APIs each module contributes to them.
151          */
152         private val sdkExtInfoFile: File,
153 
154         /**
155          * A version that has not yet been finalized. Used when an API was added in an SDK extension
156          * but not yet part of an SDK release.
157          */
158         val notFinalizedSdkVersion: ApiVersion,
159     ) {
160         /** [SdkExtensionInfo] loaded on demand from [sdkExtInfoFile]. */
161         val sdkExtensionInfo by
162             lazy(LazyThreadSafetyMode.NONE) { SdkExtensionInfo.fromXml(sdkExtInfoFile.readText()) }
163     }
164 }
165 
166 /**
167  * Creates an [Api] from a list of [VersionedApi]s.
168  *
169  * @param useInternalNames `true` if JVM internal names should be used, `false` otherwise.
170  * @param versionedApis A list of [VersionedApi]s, one for each version of the API, in order from
171  *   oldest to newest API version.
172  */
createApiFromVersionedApisnull173 internal fun createApiFromVersionedApis(
174     useInternalNames: Boolean,
175     versionedApis: List<VersionedApi>
176 ): Api {
177     val api = Api(useInternalNames)
178     for (versionedApi in versionedApis) {
179         versionedApi.updateApi(api)
180     }
181     return api
182 }
183