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