1 /* <lambda>null2 * Copyright (C) 2018 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 18 19 import com.android.SdkConstants 20 import com.android.tools.metalava.model.Codebase 21 import com.android.tools.metalava.model.FieldItem 22 import com.android.tools.metalava.model.Item 23 import com.android.tools.metalava.model.MethodItem 24 import com.android.tools.metalava.model.PackageItem 25 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS 26 import com.android.tools.metalava.model.visitors.ApiVisitor 27 import com.google.common.io.ByteStreams 28 import org.objectweb.asm.ClassReader 29 import org.objectweb.asm.Opcodes 30 import org.objectweb.asm.tree.ClassNode 31 import org.objectweb.asm.tree.FieldNode 32 import org.objectweb.asm.tree.MethodNode 33 import java.io.File 34 import java.io.IOException 35 import java.util.function.Predicate 36 import java.util.zip.ZipFile 37 38 /** 39 * In an Android source tree, rewrite the signature files in prebuilts/sdk 40 * by reading what's actually there in the android.jar files. 41 */ 42 class ConvertJarsToSignatureFiles { 43 fun convertJars(root: File) { 44 var api = 1 45 while (true) { 46 val apiJar = File( 47 root, 48 if (api <= 3) 49 "prebuilts/tools/common/api-versions/android-$api/android.jar" 50 else 51 "prebuilts/sdk/$api/public/android.jar" 52 ) 53 if (!apiJar.isFile) { 54 break 55 } 56 val signatureFile = "prebuilts/sdk/$api/public/api/android.txt" 57 val oldApiFile = File(root, "prebuilts/sdk/$api/public/api/android.txt") 58 val newApiFile = 59 // Place new-style signature files in separate files? 60 // File(root, "prebuilts/sdk/$api/public/api/android.${if (options.compatOutput) "txt" else "v2.txt"}") 61 File(root, "prebuilts/sdk/$api/public/api/android.txt") 62 63 progress("Writing signature files $signatureFile for $apiJar") 64 65 // Treat android.jar file as not filtered since they contain misc stuff that shouldn't be 66 // there: package private super classes etc. 67 val jarCodebase = loadFromJarFile(apiJar, null, preFiltered = false) 68 val apiEmit = ApiType.PUBLIC_API.getEmitFilter() 69 val apiReference = ApiType.PUBLIC_API.getReferenceFilter() 70 71 if (api >= 28) { 72 // As of API 28 we'll put nullness annotations into the jar but some of them 73 // may be @RecentlyNullable/@RecentlyNonNull. Translate these back into 74 // normal @Nullable/@NonNull 75 jarCodebase.accept(object : ApiVisitor() { 76 override fun visitItem(item: Item) { 77 unmarkRecent(item) 78 super.visitItem(item) 79 } 80 81 private fun unmarkRecent(new: Item) { 82 val annotation = NullnessMigration.findNullnessAnnotation(new) ?: return 83 // Nullness information change: Add migration annotation 84 val annotationClass = if (annotation.isNullable()) ANDROIDX_NULLABLE else ANDROIDX_NONNULL 85 86 val modifiers = new.mutableModifiers() 87 modifiers.removeAnnotation(annotation) 88 89 // Don't map annotation names - this would turn newly non null back into non null 90 modifiers.addAnnotation(new.codebase.createAnnotation("@$annotationClass", new, mapName = false)) 91 } 92 }) 93 assert(!SUPPORT_TYPE_USE_ANNOTATIONS) { "We'll need to rewrite type annotations here too" } 94 } 95 96 // Sadly the old signature files have some APIs recorded as deprecated which 97 // are not in fact deprecated in the jar files. Try to pull this back in. 98 99 val oldRemovedFile = File(root, "prebuilts/sdk/$api/public/api/removed.txt") 100 if (oldRemovedFile.isFile) { 101 val oldCodebase = SignatureFileLoader.load(oldRemovedFile) 102 val visitor = object : ComparisonVisitor() { 103 override fun compare(old: MethodItem, new: MethodItem) { 104 new.removed = true 105 progress("Removed $old") 106 } 107 108 override fun compare(old: FieldItem, new: FieldItem) { 109 new.removed = true 110 progress("Removed $old") 111 } 112 } 113 CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null) 114 } 115 116 // Read deprecated attributes. Seem to be missing from code model; 117 // try to read via ASM instead since it must clearly be there. 118 markDeprecated(jarCodebase, apiJar, apiJar.path) 119 120 // ASM doesn't seem to pick up everything that's actually there according to 121 // javap. So as another fallback, read from the existing signature files: 122 if (oldApiFile.isFile) { 123 val oldCodebase = SignatureFileLoader.load(oldApiFile) 124 val visitor = object : ComparisonVisitor() { 125 override fun compare(old: Item, new: Item) { 126 if (old.deprecated && !new.deprecated && old !is PackageItem) { 127 new.deprecated = true 128 progress("Recorded deprecation from previous signature file for $old") 129 } 130 } 131 } 132 CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null) 133 } 134 135 createReportFile(jarCodebase, newApiFile, "API") { printWriter -> 136 SignatureWriter(printWriter, apiEmit, apiReference, jarCodebase.preFiltered) 137 } 138 139 // Delete older redundant .xml files 140 val xmlFile = File(newApiFile.parentFile, "android.xml") 141 if (xmlFile.isFile) { 142 xmlFile.delete() 143 } 144 145 api++ 146 } 147 } 148 149 private fun markDeprecated(codebase: Codebase, file: File, path: String) { 150 when { 151 file.name.endsWith(SdkConstants.DOT_JAR) -> try { 152 ZipFile(file).use { jar -> 153 val enumeration = jar.entries() 154 while (enumeration.hasMoreElements()) { 155 val entry = enumeration.nextElement() 156 if (entry.name.endsWith(SdkConstants.DOT_CLASS)) { 157 try { 158 jar.getInputStream(entry).use { `is` -> 159 val bytes = ByteStreams.toByteArray(`is`) 160 if (bytes != null) { 161 markDeprecated(codebase, bytes, path + ":" + entry.name) 162 } 163 } 164 } catch (e: Exception) { 165 options.stdout.println("Could not read jar file entry ${entry.name} from $file: $e") 166 } 167 } 168 } 169 } 170 } catch (e: IOException) { 171 options.stdout.println("Could not read jar file contents from $file: $e") 172 } 173 file.isDirectory -> { 174 val listFiles = file.listFiles() 175 listFiles?.forEach { 176 markDeprecated(codebase, it, it.path) 177 } 178 } 179 file.path.endsWith(SdkConstants.DOT_CLASS) -> { 180 val bytes = file.readBytes() 181 markDeprecated(codebase, bytes, file.path) 182 } 183 else -> options.stdout.println("Ignoring entry $file") 184 } 185 } 186 187 private fun markDeprecated(codebase: Codebase, bytes: ByteArray, path: String) { 188 val reader: ClassReader 189 val classNode: ClassNode 190 try { 191 // TODO: We don't actually need to build a DOM. 192 reader = ClassReader(bytes) 193 classNode = ClassNode() 194 reader.accept(classNode, 0) 195 } catch (t: Throwable) { 196 options.stderr.println("Error processing $path: broken class file?") 197 return 198 } 199 200 if ((classNode.access and Opcodes.ACC_DEPRECATED) != 0) { 201 val item = codebase.findClass(classNode, MATCH_ALL) 202 if (item != null && !item.deprecated) { 203 item.deprecated = true 204 progress("Turned deprecation on for $item") 205 } 206 } 207 208 val methodList = classNode.methods 209 for (f in methodList) { 210 val methodNode = f as MethodNode 211 if ((methodNode.access and Opcodes.ACC_DEPRECATED) == 0) { 212 continue 213 } 214 val item = codebase.findMethod(classNode, methodNode, MATCH_ALL) 215 if (item != null && !item.deprecated) { 216 item.deprecated = true 217 progress("Turned deprecation on for $item") 218 } 219 } 220 221 val fieldList = classNode.fields 222 for (f in fieldList) { 223 val fieldNode = f as FieldNode 224 if ((fieldNode.access and Opcodes.ACC_DEPRECATED) == 0) { 225 continue 226 } 227 val item = codebase.findField(classNode, fieldNode, MATCH_ALL) 228 if (item != null && !item.deprecated) { 229 item.deprecated = true 230 progress("Turned deprecation on for $item") 231 } 232 } 233 } 234 235 companion object { 236 val MATCH_ALL: Predicate<Item> = Predicate { true } 237 } 238 }