1 /* 2 * 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.class2nonsdklist; 18 19 import com.android.annotationvisitor.AnnotationConsumer; 20 import com.android.annotationvisitor.AnnotationHandler; 21 import com.android.annotationvisitor.AnnotationVisitor; 22 import com.android.annotationvisitor.JarReader; 23 import com.android.annotationvisitor.RepeatedAnnotationHandler; 24 import com.android.annotationvisitor.Status; 25 26 import com.google.common.base.Splitter; 27 import com.google.common.collect.ImmutableMap; 28 import com.google.common.collect.ImmutableMap.Builder; 29 import com.google.common.io.Files; 30 31 import org.apache.commons.cli.CommandLine; 32 import org.apache.commons.cli.CommandLineParser; 33 import org.apache.commons.cli.GnuParser; 34 import org.apache.commons.cli.HelpFormatter; 35 import org.apache.commons.cli.OptionBuilder; 36 import org.apache.commons.cli.Options; 37 import org.apache.commons.cli.ParseException; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.nio.charset.StandardCharsets; 42 import java.util.Collections; 43 import java.util.HashMap; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.stream.Collectors; 47 48 /** 49 * Build time tool for extracting a list of members from jar files that have the 50 * @UnsupportedAppUsage annotation, for building the non SDK API lists. 51 */ 52 public class Class2NonSdkList { 53 54 private static final String UNSUPPORTED_APP_USAGE_ANNOTATION = 55 "android.compat.annotation.UnsupportedAppUsage"; 56 57 private static final String FLAG_UNSUPPORTED = "unsupported"; 58 private static final String FLAG_BLOCKED = "blocked"; 59 private static final String FLAG_MAX_TARGET_O = "max-target-o"; 60 private static final String FLAG_MAX_TARGET_P = "max-target-p"; 61 private static final String FLAG_MAX_TARGET_Q = "max-target-q"; 62 private static final String FLAG_MAX_TARGET_R = "max-target-r"; 63 private static final String FLAG_MAX_TARGET_S = "max-target-s"; 64 65 private static final String FLAG_PUBLIC_API = "public-api"; 66 67 private static final Map<Integer, String> TARGET_SDK_TO_LIST_MAP; 68 static { 69 Map<Integer, String> map = new HashMap<>(); map.put(null, FLAG_UNSUPPORTED)70 map.put(null, FLAG_UNSUPPORTED); 71 map.put(0, FLAG_BLOCKED); 72 map.put(26, FLAG_MAX_TARGET_O); 73 map.put(28, FLAG_MAX_TARGET_P); 74 map.put(29, FLAG_MAX_TARGET_Q); 75 map.put(30, FLAG_MAX_TARGET_R); 76 map.put(31, FLAG_MAX_TARGET_S); 77 map.put(32, FLAG_UNSUPPORTED); 78 map.put(33, FLAG_UNSUPPORTED); 79 map.put(34, FLAG_UNSUPPORTED); 80 map.put(35, FLAG_UNSUPPORTED); 81 map.put(10000, FLAG_UNSUPPORTED); // VMRuntime.SDK_VERSION_CUR_DEVELOPMENT 82 TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map); 83 } 84 85 private final Status mStatus; 86 private final String[] mJarFiles; 87 private final AnnotationConsumer mOutput; 88 private final Set<String> mPublicApis; 89 main(String[] args)90 public static void main(String[] args) { 91 Options options = new Options(); 92 options.addOption(OptionBuilder 93 .withLongOpt("stub-api-flags") 94 .hasArgs(1) 95 .withDescription("CSV file with API flags generated from public API stubs. " + 96 "Used to de-dupe bridge methods.") 97 .create("s")); 98 options.addOption(OptionBuilder 99 .withLongOpt("write-flags-csv") 100 .hasArgs(1) 101 .withDescription("Specify file to write hiddenapi flags to.") 102 .create('w')); 103 options.addOption(OptionBuilder 104 .withLongOpt("debug") 105 .hasArgs(0) 106 .withDescription("Enable debug") 107 .create("d")); 108 options.addOption(OptionBuilder 109 .withLongOpt("dump-all-members") 110 .withDescription("Dump all members from jar files to stdout. Ignore annotations. " + 111 "Do not use in conjunction with any other arguments.") 112 .hasArgs(0) 113 .create('m')); 114 options.addOption(OptionBuilder 115 .withLongOpt("write-metadata-csv") 116 .hasArgs(1) 117 .withDescription("Specify a file to write API metaadata to. This is a CSV file " + 118 "containing any annotation properties for all members. Do not use in " + 119 "conjunction with --write-flags-csv.") 120 .create('c')); 121 options.addOption(OptionBuilder 122 .withLongOpt("help") 123 .hasArgs(0) 124 .withDescription("Show this help") 125 .create('h')); 126 127 CommandLineParser parser = new GnuParser(); 128 CommandLine cmd; 129 130 try { 131 cmd = parser.parse(options, args); 132 } catch (ParseException e) { 133 System.err.println(e.getMessage()); 134 help(options); 135 return; 136 } 137 if (cmd.hasOption('h')) { 138 help(options); 139 } 140 141 142 String[] jarFiles = cmd.getArgs(); 143 if (jarFiles.length == 0) { 144 System.err.println("Error: no jar files specified."); 145 help(options); 146 } 147 148 Status status = new Status(cmd.hasOption('d')); 149 150 if (cmd.hasOption('m')) { 151 dumpAllMembers(status, jarFiles); 152 } else { 153 try { 154 Class2NonSdkList c2nsl = new Class2NonSdkList( 155 status, 156 cmd.getOptionValue('s', null), 157 cmd.getOptionValue('w', null), 158 cmd.getOptionValue('c', null), 159 jarFiles); 160 c2nsl.main(); 161 } catch (IOException e) { 162 status.error(e); 163 } 164 } 165 166 if (status.ok()) { 167 System.exit(0); 168 } else { 169 System.exit(1); 170 } 171 172 } 173 Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, String csvMetadataFile, String[] jarFiles)174 private Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, 175 String csvMetadataFile, String[] jarFiles) 176 throws IOException { 177 mStatus = status; 178 mJarFiles = jarFiles; 179 if (csvMetadataFile != null) { 180 mOutput = new AnnotationPropertyWriter(csvMetadataFile); 181 } else { 182 mOutput = new HiddenapiFlagsWriter(csvFlagsFile); 183 } 184 185 if (stubApiFlagsFile != null) { 186 mPublicApis = 187 Files.readLines(new File(stubApiFlagsFile), StandardCharsets.UTF_8).stream() 188 .map(s -> Splitter.on(",").splitToList(s)) 189 .filter(s -> s.contains(FLAG_PUBLIC_API)) 190 .map(s -> s.get(0)) 191 .collect(Collectors.toSet()); 192 } else { 193 mPublicApis = Collections.emptySet(); 194 } 195 } 196 createAnnotationHandlers()197 private Map<String, AnnotationHandler> createAnnotationHandlers() { 198 Builder<String, AnnotationHandler> builder = ImmutableMap.builder(); 199 UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler = 200 new UnsupportedAppUsageAnnotationHandler( 201 mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP); 202 203 addRepeatedAnnotationHandlers( 204 builder, 205 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION), 206 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION + "$Container"), 207 greylistAnnotationHandler); 208 209 CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler( 210 mOutput, mPublicApis, FLAG_PUBLIC_API); 211 212 return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME, 213 CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler) 214 .build(); 215 } 216 classNameToSignature(String a)217 private String classNameToSignature(String a) { 218 return "L" + a.replace('.', '/') + ";"; 219 } 220 221 /** 222 * Add a handler for an annotation as well as an handler for the container annotation that is 223 * used when the annotation is repeated. 224 * 225 * @param builder the builder for the map to which the handlers will be added. 226 * @param annotationName the name of the annotation. 227 * @param containerAnnotationName the name of the annotation container. 228 * @param handler the handler for the annotation. 229 */ addRepeatedAnnotationHandlers( Builder<String, AnnotationHandler> builder, String annotationName, String containerAnnotationName, AnnotationHandler handler)230 private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers( 231 Builder<String, AnnotationHandler> builder, 232 String annotationName, String containerAnnotationName, 233 AnnotationHandler handler) { 234 return builder 235 .put(annotationName, handler) 236 .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler)); 237 } 238 main()239 private void main() { 240 Map<String, AnnotationHandler> handlers = createAnnotationHandlers(); 241 for (String jarFile : mJarFiles) { 242 mStatus.debug("Processing jar file %s", jarFile); 243 try { 244 JarReader reader = new JarReader(mStatus, jarFile); 245 reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers) 246 .visit()); 247 reader.close(); 248 } catch (IOException e) { 249 mStatus.error(e); 250 } 251 } 252 mOutput.close(); 253 } 254 dumpAllMembers(Status status, String[] jarFiles)255 private static void dumpAllMembers(Status status, String[] jarFiles) { 256 for (String jarFile : jarFiles) { 257 status.debug("Processing jar file %s", jarFile); 258 try { 259 JarReader reader = new JarReader(status, jarFile); 260 reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status) 261 .visit()); 262 reader.close(); 263 } catch (IOException e) { 264 status.error(e); 265 } 266 } 267 } 268 help(Options options)269 private static void help(Options options) { 270 new HelpFormatter().printHelp( 271 "class2nonsdklist path/to/classes.jar [classes2.jar ...]", 272 "Extracts nonsdk entries from classes jar files given", 273 options, null, true); 274 System.exit(1); 275 } 276 } 277