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