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(10000, FLAG_UNSUPPORTED); // VMRuntime.SDK_VERSION_CUR_DEVELOPMENT 81 TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map); 82 } 83 84 private final Status mStatus; 85 private final String[] mJarFiles; 86 private final AnnotationConsumer mOutput; 87 private final Set<String> mPublicApis; 88 main(String[] args)89 public static void main(String[] args) { 90 Options options = new Options(); 91 options.addOption(OptionBuilder 92 .withLongOpt("stub-api-flags") 93 .hasArgs(1) 94 .withDescription("CSV file with API flags generated from public API stubs. " + 95 "Used to de-dupe bridge methods.") 96 .create("s")); 97 options.addOption(OptionBuilder 98 .withLongOpt("write-flags-csv") 99 .hasArgs(1) 100 .withDescription("Specify file to write hiddenapi flags to.") 101 .create('w')); 102 options.addOption(OptionBuilder 103 .withLongOpt("debug") 104 .hasArgs(0) 105 .withDescription("Enable debug") 106 .create("d")); 107 options.addOption(OptionBuilder 108 .withLongOpt("dump-all-members") 109 .withDescription("Dump all members from jar files to stdout. Ignore annotations. " + 110 "Do not use in conjunction with any other arguments.") 111 .hasArgs(0) 112 .create('m')); 113 options.addOption(OptionBuilder 114 .withLongOpt("write-metadata-csv") 115 .hasArgs(1) 116 .withDescription("Specify a file to write API metaadata to. This is a CSV file " + 117 "containing any annotation properties for all members. Do not use in " + 118 "conjunction with --write-flags-csv.") 119 .create('c')); 120 options.addOption(OptionBuilder 121 .withLongOpt("help") 122 .hasArgs(0) 123 .withDescription("Show this help") 124 .create('h')); 125 126 CommandLineParser parser = new GnuParser(); 127 CommandLine cmd; 128 129 try { 130 cmd = parser.parse(options, args); 131 } catch (ParseException e) { 132 System.err.println(e.getMessage()); 133 help(options); 134 return; 135 } 136 if (cmd.hasOption('h')) { 137 help(options); 138 } 139 140 141 String[] jarFiles = cmd.getArgs(); 142 if (jarFiles.length == 0) { 143 System.err.println("Error: no jar files specified."); 144 help(options); 145 } 146 147 Status status = new Status(cmd.hasOption('d')); 148 149 if (cmd.hasOption('m')) { 150 dumpAllMembers(status, jarFiles); 151 } else { 152 try { 153 Class2NonSdkList c2nsl = new Class2NonSdkList( 154 status, 155 cmd.getOptionValue('s', null), 156 cmd.getOptionValue('w', null), 157 cmd.getOptionValue('c', null), 158 jarFiles); 159 c2nsl.main(); 160 } catch (IOException e) { 161 status.error(e); 162 } 163 } 164 165 if (status.ok()) { 166 System.exit(0); 167 } else { 168 System.exit(1); 169 } 170 171 } 172 Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, String csvMetadataFile, String[] jarFiles)173 private Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, 174 String csvMetadataFile, String[] jarFiles) 175 throws IOException { 176 mStatus = status; 177 mJarFiles = jarFiles; 178 if (csvMetadataFile != null) { 179 mOutput = new AnnotationPropertyWriter(csvMetadataFile); 180 } else { 181 mOutput = new HiddenapiFlagsWriter(csvFlagsFile); 182 } 183 184 if (stubApiFlagsFile != null) { 185 mPublicApis = 186 Files.readLines(new File(stubApiFlagsFile), StandardCharsets.UTF_8).stream() 187 .map(s -> Splitter.on(",").splitToList(s)) 188 .filter(s -> s.contains(FLAG_PUBLIC_API)) 189 .map(s -> s.get(0)) 190 .collect(Collectors.toSet()); 191 } else { 192 mPublicApis = Collections.emptySet(); 193 } 194 } 195 createAnnotationHandlers()196 private Map<String, AnnotationHandler> createAnnotationHandlers() { 197 Builder<String, AnnotationHandler> builder = ImmutableMap.builder(); 198 UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler = 199 new UnsupportedAppUsageAnnotationHandler( 200 mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP); 201 202 addRepeatedAnnotationHandlers( 203 builder, 204 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION), 205 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION + "$Container"), 206 greylistAnnotationHandler); 207 208 CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler( 209 mOutput, mPublicApis, FLAG_PUBLIC_API); 210 211 return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME, 212 CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler) 213 .build(); 214 } 215 classNameToSignature(String a)216 private String classNameToSignature(String a) { 217 return "L" + a.replace('.', '/') + ";"; 218 } 219 220 /** 221 * Add a handler for an annotation as well as an handler for the container annotation that is 222 * used when the annotation is repeated. 223 * 224 * @param builder the builder for the map to which the handlers will be added. 225 * @param annotationName the name of the annotation. 226 * @param containerAnnotationName the name of the annotation container. 227 * @param handler the handler for the annotation. 228 */ addRepeatedAnnotationHandlers( Builder<String, AnnotationHandler> builder, String annotationName, String containerAnnotationName, AnnotationHandler handler)229 private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers( 230 Builder<String, AnnotationHandler> builder, 231 String annotationName, String containerAnnotationName, 232 AnnotationHandler handler) { 233 return builder 234 .put(annotationName, handler) 235 .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler)); 236 } 237 main()238 private void main() { 239 Map<String, AnnotationHandler> handlers = createAnnotationHandlers(); 240 for (String jarFile : mJarFiles) { 241 mStatus.debug("Processing jar file %s", jarFile); 242 try { 243 JarReader reader = new JarReader(mStatus, jarFile); 244 reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers) 245 .visit()); 246 reader.close(); 247 } catch (IOException e) { 248 mStatus.error(e); 249 } 250 } 251 mOutput.close(); 252 } 253 dumpAllMembers(Status status, String[] jarFiles)254 private static void dumpAllMembers(Status status, String[] jarFiles) { 255 for (String jarFile : jarFiles) { 256 status.debug("Processing jar file %s", jarFile); 257 try { 258 JarReader reader = new JarReader(status, jarFile); 259 reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status) 260 .visit()); 261 reader.close(); 262 } catch (IOException e) { 263 status.error(e); 264 } 265 } 266 } 267 help(Options options)268 private static void help(Options options) { 269 new HelpFormatter().printHelp( 270 "class2nonsdklist path/to/classes.jar [classes2.jar ...]", 271 "Extracts nonsdk entries from classes jar files given", 272 options, null, true); 273 System.exit(1); 274 } 275 } 276