/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.class2nonsdklist; import com.android.annotationvisitor.AnnotationConsumer; import com.android.annotationvisitor.AnnotationHandler; import com.android.annotationvisitor.AnnotationVisitor; import com.android.annotationvisitor.JarReader; import com.android.annotationvisitor.RepeatedAnnotationHandler; import com.android.annotationvisitor.Status; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.io.Files; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Build time tool for extracting a list of members from jar files that have the * @UnsupportedAppUsage annotation, for building the non SDK API lists. */ public class Class2NonSdkList { private static final String UNSUPPORTED_APP_USAGE_ANNOTATION = "android.compat.annotation.UnsupportedAppUsage"; private static final String FLAG_UNSUPPORTED = "unsupported"; private static final String FLAG_BLOCKED = "blocked"; private static final String FLAG_MAX_TARGET_O = "max-target-o"; private static final String FLAG_MAX_TARGET_P = "max-target-p"; private static final String FLAG_MAX_TARGET_Q = "max-target-q"; private static final String FLAG_MAX_TARGET_R = "max-target-r"; private static final String FLAG_MAX_TARGET_S = "max-target-s"; private static final String FLAG_PUBLIC_API = "public-api"; private static final Map TARGET_SDK_TO_LIST_MAP; static { Map map = new HashMap<>(); map.put(null, FLAG_UNSUPPORTED); map.put(0, FLAG_BLOCKED); map.put(26, FLAG_MAX_TARGET_O); map.put(28, FLAG_MAX_TARGET_P); map.put(29, FLAG_MAX_TARGET_Q); map.put(30, FLAG_MAX_TARGET_R); map.put(31, FLAG_MAX_TARGET_S); map.put(32, FLAG_UNSUPPORTED); map.put(33, FLAG_UNSUPPORTED); map.put(34, FLAG_UNSUPPORTED); map.put(35, FLAG_UNSUPPORTED); map.put(10000, FLAG_UNSUPPORTED); // VMRuntime.SDK_VERSION_CUR_DEVELOPMENT TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map); } private final Status mStatus; private final String[] mJarFiles; private final AnnotationConsumer mOutput; private final Set mPublicApis; public static void main(String[] args) { Options options = new Options(); options.addOption(OptionBuilder .withLongOpt("stub-api-flags") .hasArgs(1) .withDescription("CSV file with API flags generated from public API stubs. " + "Used to de-dupe bridge methods.") .create("s")); options.addOption(OptionBuilder .withLongOpt("write-flags-csv") .hasArgs(1) .withDescription("Specify file to write hiddenapi flags to.") .create('w')); options.addOption(OptionBuilder .withLongOpt("debug") .hasArgs(0) .withDescription("Enable debug") .create("d")); options.addOption(OptionBuilder .withLongOpt("dump-all-members") .withDescription("Dump all members from jar files to stdout. Ignore annotations. " + "Do not use in conjunction with any other arguments.") .hasArgs(0) .create('m')); options.addOption(OptionBuilder .withLongOpt("write-metadata-csv") .hasArgs(1) .withDescription("Specify a file to write API metaadata to. This is a CSV file " + "containing any annotation properties for all members. Do not use in " + "conjunction with --write-flags-csv.") .create('c')); options.addOption(OptionBuilder .withLongOpt("help") .hasArgs(0) .withDescription("Show this help") .create('h')); CommandLineParser parser = new GnuParser(); CommandLine cmd; try { cmd = parser.parse(options, args); } catch (ParseException e) { System.err.println(e.getMessage()); help(options); return; } if (cmd.hasOption('h')) { help(options); } String[] jarFiles = cmd.getArgs(); if (jarFiles.length == 0) { System.err.println("Error: no jar files specified."); help(options); } Status status = new Status(cmd.hasOption('d')); if (cmd.hasOption('m')) { dumpAllMembers(status, jarFiles); } else { try { Class2NonSdkList c2nsl = new Class2NonSdkList( status, cmd.getOptionValue('s', null), cmd.getOptionValue('w', null), cmd.getOptionValue('c', null), jarFiles); c2nsl.main(); } catch (IOException e) { status.error(e); } } if (status.ok()) { System.exit(0); } else { System.exit(1); } } private Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, String csvMetadataFile, String[] jarFiles) throws IOException { mStatus = status; mJarFiles = jarFiles; if (csvMetadataFile != null) { mOutput = new AnnotationPropertyWriter(csvMetadataFile); } else { mOutput = new HiddenapiFlagsWriter(csvFlagsFile); } if (stubApiFlagsFile != null) { mPublicApis = Files.readLines(new File(stubApiFlagsFile), StandardCharsets.UTF_8).stream() .map(s -> Splitter.on(",").splitToList(s)) .filter(s -> s.contains(FLAG_PUBLIC_API)) .map(s -> s.get(0)) .collect(Collectors.toSet()); } else { mPublicApis = Collections.emptySet(); } } private Map createAnnotationHandlers() { Builder builder = ImmutableMap.builder(); UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler = new UnsupportedAppUsageAnnotationHandler( mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP); addRepeatedAnnotationHandlers( builder, classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION), classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION + "$Container"), greylistAnnotationHandler); CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler( mOutput, mPublicApis, FLAG_PUBLIC_API); return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME, CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler) .build(); } private String classNameToSignature(String a) { return "L" + a.replace('.', '/') + ";"; } /** * Add a handler for an annotation as well as an handler for the container annotation that is * used when the annotation is repeated. * * @param builder the builder for the map to which the handlers will be added. * @param annotationName the name of the annotation. * @param containerAnnotationName the name of the annotation container. * @param handler the handler for the annotation. */ private static Builder addRepeatedAnnotationHandlers( Builder builder, String annotationName, String containerAnnotationName, AnnotationHandler handler) { return builder .put(annotationName, handler) .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler)); } private void main() { Map handlers = createAnnotationHandlers(); for (String jarFile : mJarFiles) { mStatus.debug("Processing jar file %s", jarFile); try { JarReader reader = new JarReader(mStatus, jarFile); reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers) .visit()); reader.close(); } catch (IOException e) { mStatus.error(e); } } mOutput.close(); } private static void dumpAllMembers(Status status, String[] jarFiles) { for (String jarFile : jarFiles) { status.debug("Processing jar file %s", jarFile); try { JarReader reader = new JarReader(status, jarFile); reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status) .visit()); reader.close(); } catch (IOException e) { status.error(e); } } } private static void help(Options options) { new HelpFormatter().printHelp( "class2nonsdklist path/to/classes.jar [classes2.jar ...]", "Extracts nonsdk entries from classes jar files given", options, null, true); System.exit(1); } }