• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.tools.r8wrappers;
17 
18 import com.android.tools.r8.AndroidResourceInput;
19 import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer;
20 import com.android.tools.r8.ArchiveProtoAndroidResourceProvider;
21 import com.android.tools.r8.BaseCompilerCommand;
22 import com.android.tools.r8.CompilationFailedException;
23 import com.android.tools.r8.DiagnosticsLevel;
24 import com.android.tools.r8.ParseFlagInfo;
25 import com.android.tools.r8.ParseFlagPrinter;
26 import com.android.tools.r8.R8;
27 import com.android.tools.r8.R8Command;
28 import com.android.tools.r8.ResourceException;
29 import com.android.tools.r8.ResourcePath;
30 import com.android.tools.r8.Version;
31 import com.android.tools.r8.origin.Origin;
32 import com.android.tools.r8.origin.PathOrigin;
33 import com.android.tools.r8wrappers.utils.DepsFileWriter;
34 import com.android.tools.r8wrappers.utils.WrapperDiagnosticsHandler;
35 import com.android.tools.r8wrappers.utils.WrapperFlag;
36 import java.io.ByteArrayInputStream;
37 import java.io.InputStream;
38 import java.io.IOException;
39 import java.nio.charset.StandardCharsets;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.List;
47 
48 public class R8Wrapper {
49 
50   private static final String WRAPPER_STRING = "r8-aosp-wrapper";
51 
52   private static final Origin CLI_ORIGIN =
53       new Origin(Origin.root()) {
54         @Override
55         public String part() {
56           return WRAPPER_STRING;
57         }
58       };
59 
getAdditionalFlagsInfo()60   private static List<ParseFlagInfo> getAdditionalFlagsInfo() {
61     return Arrays.asList(
62         new WrapperFlag("--deps-file <file>", "Write input dependencies to <file>."),
63         new WrapperFlag("--info", "Print the info-level log messages from the compiler."),
64         new WrapperFlag("--resource-input", "Resource input for the resource shrinker."),
65         new WrapperFlag("--resource-output", "Resource shrinker output."),
66         new WrapperFlag("--optimized-resource-shrinking", "Use R8 optimizing resource pipeline."),
67         new WrapperFlag("--protect-api-surface", "API surface protection for libcore."),
68         new WrapperFlag(
69             "--store-store-fence-constructor-inlining",
70             "Use aggressive R8 constructor inlining."),
71         new WrapperFlag(
72             "--no-implicit-default-init",
73             "Disable compat-mode behavior of keeping default constructors in full mode."),
74         new WrapperFlag(
75             "--exclude <file>",
76             "Path to file containing name of classes that should not be compiled using R8."));
77   }
78 
getUsageMessage()79   private static String getUsageMessage() {
80     StringBuilder builder =
81         appendLines(
82             new StringBuilder(),
83             "Usage: r8 [options] [@<argfile>] <input-files>",
84             " where <input-files> are any combination of class, zip, or jar files",
85             " and each <argfile> is a file containing additional arguments (one per line)",
86             " and options are:");
87     new ParseFlagPrinter()
88         .addFlags(R8Command.getParseFlagsInformation())
89         .addFlags(getAdditionalFlagsInfo())
90         .setIndent(2)
91         .appendLinesToBuilder(builder);
92     return builder.toString();
93   }
94 
appendLines(StringBuilder builder, String... lines)95   private static StringBuilder appendLines(StringBuilder builder, String... lines) {
96     for (String line : lines) {
97       builder.append(line).append(System.lineSeparator());
98     }
99     return builder;
100   }
101 
main(String[] args)102   public static void main(String[] args) throws CompilationFailedException, IOException {
103     // Disable this optimization as it can impact weak reference semantics. See b/233432839.
104     System.setProperty("com.android.tools.r8.disableEnqueuerDeferredTracing", "1");
105     // Disable class merging across different files to improve attribution. See b/242881914.
106     System.setProperty("com.android.tools.r8.enableSameFilePolicy", "1");
107     // Enable experimental -whyareyounotinlining config to aid debugging. See b/277389461.
108     System.setProperty("com.android.tools.r8.experimental.enablewhyareyounotinlining", "1");
109     // Allow use of -convertchecknotnull optimization. See b/280633711.
110     System.setProperty("com.android.tools.r8.experimental.enableconvertchecknotnull", "1");
111     // Allow conditional keep rule application against library references. See b/386409781.
112     System.setProperty("com.android.tools.r8.applyIfRulesToLibrary", "1");
113     // Do not keep runtime invisible annotations with @KeepForApi. See b/399021897.
114     System.setProperty("com.android.tools.r8.keepanno.unkeepInvisibleAnnotationsInKeepForApi", "1");
115 
116     R8Wrapper wrapper = new R8Wrapper();
117     String[] remainingArgs = wrapper.parseWrapperArguments(args);
118     if (!wrapper.useCompatPg && !wrapper.noImplicitDefaultInit) {
119       // Retain incorrect behavior in full mode that will implicitly keep default constructors.
120       // See b/132318799.
121       System.setProperty(
122           "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion",
123           "1");
124     }
125     R8Command.Builder builder = R8Command.parse(
126         remainingArgs, CLI_ORIGIN, wrapper.diagnosticsHandler);
127     if (builder.isPrintHelp()) {
128       System.out.println(getUsageMessage());
129       return;
130     }
131     if (builder.isPrintVersion()) {
132       System.out.println("R8(" + WRAPPER_STRING + ") " + Version.getVersionString());
133       return;
134     }
135     wrapper.applyWrapperArguments(builder);
136     applyCommonCompilerArguments(builder);
137     builder.setEnableExperimentalKeepAnnotations(true);
138     R8.run(builder.build());
139   }
140 
141   private WrapperDiagnosticsHandler diagnosticsHandler = new WrapperDiagnosticsHandler();
142   private boolean ignoreLibraryExtendsProgram = false;
143   private boolean useCompatPg = false;
144   private Path depsOutput = null;
145   private Path resourceInput = null;
146   private Path resourceOutput = null;
147   private final List<String> pgRules = new ArrayList<>();
148   private boolean printInfoDiagnostics = false;
149   private boolean dontOptimize = false;
150   private boolean keepRuntimeInvisibleAnnotations = false;
151   private boolean optimizingResourceShrinking = false;
152   private boolean forceOptimizingResourceShrinking = false;
153   private boolean noImplicitDefaultInit = false;
154   private boolean protectApiSurface = false;
155   private boolean storeStoreFenceConstructorInlining = false;
156   private final List<String> excludeClasses = new ArrayList<>();
157 
parseWrapperArguments(String[] args)158   private String[] parseWrapperArguments(String[] args) throws IOException {
159     List<String> remainingArgs = new ArrayList<>();
160     for (int i = 0; i < args.length; i++) {
161       String arg = args[i];
162       switch (arg) {
163         case "--exclude":
164           {
165             String nextArg = args[++i];
166             Path excludeFile = Paths.get(nextArg);
167             for (String line : Files.readAllLines(excludeFile)) {
168               line = line.trim();
169               if (line.isEmpty() || line.startsWith("#")) {
170                 continue;
171               }
172               excludeClasses.add(line);
173             }
174             break;
175           }
176         case "--ignore-library-extends-program":
177           {
178             ignoreLibraryExtendsProgram = true;
179             break;
180           }
181         case "--info":
182           {
183             printInfoDiagnostics = true;
184             break;
185           }
186         case "--keep-runtime-invisible-annotations":
187           {
188             keepRuntimeInvisibleAnnotations = true;
189             break;
190           }
191         case "--resource-input":
192           {
193             if (resourceInput != null) {
194               throw new RuntimeException("Only one --resource-input flag accepted");
195             }
196             String nextArg = args[++i];
197             resourceInput = Paths.get(nextArg);
198             break;
199           }
200         case "--resource-output":
201           {
202             if (resourceOutput != null) {
203               throw new RuntimeException("Only one --resource-output flag accepted");
204             }
205             String nextArg = args[++i];
206             resourceOutput = Paths.get(nextArg);
207             break;
208           }
209         case "--optimized-resource-shrinking":
210           {
211             optimizingResourceShrinking = true;
212             break;
213           }
214         case "--force-optimized-resource-shrinking":
215           {
216             forceOptimizingResourceShrinking = true;
217             break;
218           }
219         case "--no-implicit-default-init":
220           {
221             noImplicitDefaultInit = true;
222             break;
223           }
224         case "--deps-file":
225           {
226             String nextArg = args[++i];
227             depsOutput = Paths.get(nextArg);
228             break;
229           }
230           // Remove uses of this same as for D8 (b/69377755).
231         case "--multi-dex":
232           {
233             break;
234           }
235           // TODO(zerny): replace uses with --pg-compat
236         case "--force-proguard-compatibility":
237           {
238             useCompatPg = true;
239             break;
240           }
241           // Zero argument PG rules.
242         case "-dontshrink":
243         case "-dontobfuscate":
244         case "-ignorewarnings":
245           {
246             pgRules.add(arg);
247             break;
248           }
249         case "-dontoptimize":
250           {
251             dontOptimize = true;
252             pgRules.add(arg);
253             break;
254           }
255           // One argument PG rules.
256         case "-injars":
257         case "-libraryjars":
258         case "-include":
259         case "-printmapping":
260         case "-printconfiguration":
261         case "-printusage":
262         case "-printseeds":
263           {
264             pgRules.add(arg + " " + args[++i]);
265             break;
266           }
267         case "--protect-api-surface":
268           {
269             protectApiSurface = true;
270             break;
271           }
272         case "--store-store-fence-constructor-inlining":
273           {
274             storeStoreFenceConstructorInlining = true;
275             break;
276           }
277         default:
278           {
279             remainingArgs.add(arg);
280             break;
281           }
282       }
283     }
284     return remainingArgs.toArray(new String[0]);
285   }
286 
applyWrapperArguments(R8Command.Builder builder)287   private void applyWrapperArguments(R8Command.Builder builder) {
288     diagnosticsHandler.setPrintInfoDiagnostics(printInfoDiagnostics);
289     // Surface duplicate type warnings for optimized targets where duplicates are more dangerous.
290     // TODO(b/222468116): Bump the level to ERROR for all optimized targets after resolving current
291     // duplicates, and the default level to WARNING.
292     if (!dontOptimize) {
293       diagnosticsHandler.setDuplicateTypesDiagnosticsLevel(DiagnosticsLevel.WARNING);
294     }
295     if (depsOutput != null) {
296       Path codeOutput = builder.getOutputPath();
297       Path target = Files.isDirectory(codeOutput) ? codeOutput.resolve("classes.dex") : codeOutput;
298       builder.setInputDependencyGraphConsumer(new DepsFileWriter(target, depsOutput.toString()));
299     }
300     if (resourceInput != null && resourceOutput != null) {
301       builder.setAndroidResourceProvider(new AOSPResourceProvider(resourceInput,
302           new PathOrigin(resourceInput)));
303       builder.setAndroidResourceConsumer(
304           new ArchiveProtoAndroidResourceConsumer(resourceOutput, resourceInput));
305       if (optimizingResourceShrinking) {
306         builder.setResourceShrinkerConfiguration(b -> b.enableOptimizedShrinkingWithR8().build());
307         if (!forceOptimizingResourceShrinking) {
308           // TODO(b/372264901): There is a range of test targets that rely on using ids for looking
309           // up ui elements. For now, keep all of these.
310           builder.addProguardConfiguration(List.of("-keep class **.R$id {<fields>;}"),
311               CLI_ORIGIN);
312         }
313       }
314     } else if (resourceOutput != null || resourceInput != null) {
315       throw new RuntimeException("Both --resource-input and --resource-output must be specified");
316     }
317     if (ignoreLibraryExtendsProgram) {
318       System.setProperty("com.android.tools.r8.allowLibraryExtendsProgramInFullMode", "1");
319     }
320     if (keepRuntimeInvisibleAnnotations) {
321       builder.addProguardConfiguration(
322           List.of(
323               "-keepattributes RuntimeInvisibleAnnotations",
324               "-keepattributes RuntimeInvisibleParameterAnnotations",
325               "-keepattributes RuntimeInvisibleTypeAnnotations"),
326           CLI_ORIGIN);
327     }
328     if (!excludeClasses.isEmpty()) {
329       String includePatterns = "**";
330       String excludePatterns = String.join(",", excludeClasses);
331       builder.enableExperimentalPartialShrinking(includePatterns, excludePatterns);
332     }
333     if (!pgRules.isEmpty()) {
334       builder.addProguardConfiguration(pgRules, CLI_ORIGIN);
335     }
336     if (protectApiSurface) {
337       builder.setProtectApiSurface(true);
338     }
339     if (useCompatPg) {
340       builder.setProguardCompatibility(useCompatPg);
341     }
342     if (storeStoreFenceConstructorInlining) {
343       System.setProperty("com.android.tools.r8.enableConstructorInliningWithFinalFields", "1");
344     }
345   }
346 
347   /** Utility method to apply platform specific settings to both D8 and R8. */
applyCommonCompilerArguments(BaseCompilerCommand.Builder<?, ?> builder)348   public static void applyCommonCompilerArguments(BaseCompilerCommand.Builder<?, ?> builder) {
349     // TODO(b/232073181): Remove this once platform flag is the default.
350     if (!builder.getAndroidPlatformBuild()) {
351       System.setProperty("com.android.tools.r8.disableApiModeling", "1");
352     }
353   }
354 
355   private static class AOSPResourceProvider extends ArchiveProtoAndroidResourceProvider {
356     final String defaultXmlRules = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
357         + "<resources xmlns:tools=\"http://schemas.android.com/tools\"\n"
358         + "    tools:shrinkMode=\"strict\"\n"
359         + "    tools:keep=\"@id/*\"\n"
360         + "/>\n";
361 
362     final AndroidResourceInput defaultRules = new AndroidResourceInput() {
363       @Override
364       public ResourcePath getPath() {
365         return new ResourcePath() {
366           @Override
367           public String location() {
368             return "res/raw/asop_default.xml";
369           }
370         };
371       }
372 
373       @Override
374       public Kind getKind() {
375         return Kind.KEEP_RULE_FILE;
376       }
377 
378       @Override
379       public InputStream getByteStream() throws ResourceException {
380         return new ByteArrayInputStream(defaultXmlRules.getBytes(StandardCharsets.UTF_8));
381       }
382 
383       @Override
384       public Origin getOrigin() {
385         return new PathOrigin(Paths.get("R8Wrapper.java"));
386       }
387     };
388 
AOSPResourceProvider(Path archive, Origin origin)389     public AOSPResourceProvider(Path archive, Origin origin) {
390       super(archive, origin);
391     }
392 
393     @Override
getAndroidResources()394     public Collection<AndroidResourceInput> getAndroidResources() throws ResourceException {
395       ArrayList<AndroidResourceInput> androidResourceInputs = new ArrayList<>(
396           super.getAndroidResources());
397       androidResourceInputs.add(defaultRules);
398       return androidResourceInputs;
399     }
400   }
401 }
402