• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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