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