• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.tools.metalava.apilevels;
18 
19 import com.android.tools.metalava.model.Codebase;
20 import com.android.tools.metalava.SdkIdentifier;
21 
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.PrintStream;
28 import java.nio.file.Files;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.function.Function;
37 
38 /**
39  * Main class for command line command to convert the existing API XML/TXT files into diff-based
40  * simple text files.
41  */
42 public class ApiGenerator {
generate(@otNull File[] apiLevels, int firstApiLevel, int currentApiLevel, boolean isDeveloperPreviewBuild, @NotNull File outputFile, @NotNull Codebase codebase, @Nullable File sdkJarRoot, @Nullable File sdkFilterFile, boolean removeMissingClasses)43     public static boolean generate(@NotNull File[] apiLevels,
44                                    int firstApiLevel,
45                                    int currentApiLevel,
46                                    boolean isDeveloperPreviewBuild,
47                                    @NotNull File outputFile,
48                                    @NotNull Codebase codebase,
49                                    @Nullable File sdkJarRoot,
50                                    @Nullable File sdkFilterFile,
51                                    boolean removeMissingClasses) throws IOException, IllegalArgumentException {
52         if ((sdkJarRoot == null) != (sdkFilterFile == null)) {
53             throw new IllegalArgumentException("sdkJarRoot and sdkFilterFile must both be null, or non-null");
54         }
55 
56         int notFinalizedApiLevel = currentApiLevel + 1;
57         Api api = readAndroidJars(apiLevels, firstApiLevel);
58         if (isDeveloperPreviewBuild || apiLevels.length - 1 < currentApiLevel) {
59             // Only include codebase if we don't have a prebuilt, finalized jar for it.
60             int apiLevel = isDeveloperPreviewBuild ? notFinalizedApiLevel : currentApiLevel;
61             AddApisFromCodebaseKt.addApisFromCodebase(api, apiLevel, codebase);
62         }
63         api.backfillHistoricalFixes();
64 
65         Set<SdkIdentifier> sdkIdentifiers = Collections.emptySet();
66         if (sdkJarRoot != null && sdkFilterFile != null) {
67             sdkIdentifiers = processExtensionSdkApis(api, notFinalizedApiLevel, sdkJarRoot, sdkFilterFile);
68         }
69         api.inlineFromHiddenSuperClasses();
70         api.removeImplicitInterfaces();
71         api.removeOverridingMethods();
72         api.prunePackagePrivateClasses();
73         if (removeMissingClasses) {
74             api.removeMissingClasses();
75         } else {
76             api.verifyNoMissingClasses();
77         }
78         return createApiFile(outputFile, api, sdkIdentifiers);
79     }
80 
readAndroidJars(File[] apiLevels, int firstApiLevel)81     private static Api readAndroidJars(File[] apiLevels, int firstApiLevel) {
82         Api api = new Api(firstApiLevel);
83         for (int apiLevel = firstApiLevel; apiLevel < apiLevels.length; apiLevel++) {
84             File jar = apiLevels[apiLevel];
85             JarReaderUtilsKt.readAndroidJar(api, apiLevel, jar);
86         }
87         return api;
88     }
89 
90     /**
91      * Modify the extension SDK API parts of an API as dictated by a filter.
92      *
93      *   - remove APIs not listed in the filter
94      *   - assign APIs listed in the filter their corresponding extensions
95      *
96      * Some APIs only exist in extension SDKs and not in the Android SDK, but for backwards
97      * compatibility with tools that expect the Android SDK to be the only SDK, metalava needs to
98      * assign such APIs some Android SDK API level. The recommended value is current-api-level + 1,
99      * which is what non-finalized APIs use.
100      *
101      * @param api the api to modify
102      * @param apiLevelNotInAndroidSdk fallback API level for APIs not in the Android SDK
103      * @param sdkJarRoot path to directory containing extension SDK jars (usually $ANDROID_ROOT/prebuilts/sdk/extensions)
104      * @param filterPath: path to the filter file. @see ApiToExtensionsMap
105      * @throws IOException if the filter file can not be read
106      * @throws IllegalArgumentException if an error is detected in the filter file, or if no jar files were found
107      */
processExtensionSdkApis( @otNull Api api, int apiLevelNotInAndroidSdk, @NotNull File sdkJarRoot, @NotNull File filterPath)108     private static Set<SdkIdentifier> processExtensionSdkApis(
109             @NotNull Api api,
110             int apiLevelNotInAndroidSdk,
111             @NotNull File sdkJarRoot,
112             @NotNull File filterPath) throws IOException, IllegalArgumentException {
113         String rules = new String(Files.readAllBytes(filterPath.toPath()));
114 
115         Map<String, List<VersionAndPath>> map = ExtensionSdkJarReader.Companion.findExtensionSdkJarFiles(sdkJarRoot);
116         if (map.isEmpty()) {
117             throw new IllegalArgumentException("no extension sdk jar files found in " + sdkJarRoot);
118         }
119         Map<String, ApiToExtensionsMap> moduleMaps = new HashMap<>();
120         for (Map.Entry<String, List<VersionAndPath>> entry : map.entrySet()) {
121             String mainlineModule = entry.getKey();
122             ApiToExtensionsMap moduleMap = ApiToExtensionsMap.Companion.fromXml(mainlineModule, rules);
123             if (moduleMap.isEmpty()) continue; // TODO(b/259115852): remove this (though it is an optimization too).
124 
125             moduleMaps.put(mainlineModule, moduleMap);
126             for (VersionAndPath f : entry.getValue()) {
127                 JarReaderUtilsKt.readExtensionJar(api, f.version, mainlineModule, f.path, apiLevelNotInAndroidSdk);
128             }
129         }
130         for (ApiClass clazz : api.getClasses()) {
131             String module = clazz.getMainlineModule();
132             if (module == null) continue;
133             ApiToExtensionsMap extensionsMap = moduleMaps.get(module);
134             String sdks = extensionsMap.calculateSdksAttr(clazz.getSince(), apiLevelNotInAndroidSdk,
135                 extensionsMap.getExtensions(clazz), clazz.getSinceExtension());
136             clazz.updateSdks(sdks);
137 
138             Iterator<ApiElement> iter = clazz.getFieldIterator();
139             while (iter.hasNext()) {
140                 ApiElement field = iter.next();
141                 sdks = extensionsMap.calculateSdksAttr(field.getSince(), apiLevelNotInAndroidSdk,
142                     extensionsMap.getExtensions(clazz, field), field.getSinceExtension());
143                 field.updateSdks(sdks);
144             }
145 
146             iter = clazz.getMethodIterator();
147             while (iter.hasNext()) {
148                 ApiElement method = iter.next();
149                 sdks = extensionsMap.calculateSdksAttr(method.getSince(), apiLevelNotInAndroidSdk,
150                     extensionsMap.getExtensions(clazz, method), method.getSinceExtension());
151                 method.updateSdks(sdks);
152             }
153         }
154         return ApiToExtensionsMap.Companion.fromXml("", rules).getSdkIdentifiers();
155     }
156 
157     /**
158      * Creates the simplified diff-based API level.
159      *
160      * @param outFile        the output file
161      * @param api            the api to write
162      * @param sdkIdentifiers SDKs referenced by the api
163      */
createApiFile(@otNull File outFile, @NotNull Api api, @NotNull Set<SdkIdentifier> sdkIdentifiers)164     private static boolean createApiFile(@NotNull File outFile, @NotNull Api api, @NotNull Set<SdkIdentifier> sdkIdentifiers) {
165         File parentFile = outFile.getParentFile();
166         if (!parentFile.exists()) {
167             boolean ok = parentFile.mkdirs();
168             if (!ok) {
169                 System.err.println("Could not create directory " + parentFile);
170                 return false;
171             }
172         }
173         try (PrintStream stream = new PrintStream(outFile, "UTF-8")) {
174             stream.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
175             api.print(stream, sdkIdentifiers);
176         } catch (Exception e) {
177             e.printStackTrace();
178             return false;
179         }
180 
181         return true;
182     }
183 }
184