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