1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.dex; 5 6 import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor; 7 import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor; 8 import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor; 9 import com.android.tools.r8.errors.CompilationError; 10 import com.android.tools.r8.graph.AppInfo; 11 import com.android.tools.r8.graph.DexAnnotation; 12 import com.android.tools.r8.graph.DexAnnotationDirectory; 13 import com.android.tools.r8.graph.DexAnnotationSet; 14 import com.android.tools.r8.graph.DexAnnotationSetRefList; 15 import com.android.tools.r8.graph.DexApplication; 16 import com.android.tools.r8.graph.DexCode; 17 import com.android.tools.r8.graph.DexDebugInfo; 18 import com.android.tools.r8.graph.DexEncodedArray; 19 import com.android.tools.r8.graph.DexProgramClass; 20 import com.android.tools.r8.graph.DexType; 21 import com.android.tools.r8.graph.DexTypeList; 22 import com.android.tools.r8.graph.DexValue; 23 import com.android.tools.r8.naming.MinifiedNameMapPrinter; 24 import com.android.tools.r8.naming.NamingLens; 25 import com.android.tools.r8.utils.AndroidApp; 26 import com.android.tools.r8.utils.DescriptorUtils; 27 import com.android.tools.r8.utils.InternalOptions; 28 import com.android.tools.r8.utils.OutputMode; 29 import com.android.tools.r8.utils.PackageDistribution; 30 import java.io.ByteArrayOutputStream; 31 import java.io.IOException; 32 import java.io.PrintStream; 33 import java.io.PrintWriter; 34 import java.io.Writer; 35 import java.util.LinkedHashMap; 36 import java.util.Map; 37 import java.util.concurrent.ExecutionException; 38 import java.util.concurrent.ExecutorService; 39 import java.util.concurrent.Future; 40 41 public class ApplicationWriter { 42 43 public final DexApplication application; 44 public final AppInfo appInfo; 45 public final NamingLens namingLens; 46 public final byte[] proguardSeedsData; 47 public final InternalOptions options; 48 49 private static class SortAnnotations extends MixedSectionCollection { 50 51 @Override 52 public boolean add(DexAnnotationSet dexAnnotationSet) { 53 // Annotation sets are sorted by annotation types. 54 dexAnnotationSet.sort(); 55 return true; 56 } 57 58 @Override 59 public boolean add(DexAnnotation annotation) { 60 // The elements of encoded annotation must be sorted by name. 61 annotation.annotation.sort(); 62 return true; 63 } 64 65 @Override 66 public boolean add(DexEncodedArray dexEncodedArray) { 67 // Dex values must potentially be sorted, eg, for DexValueAnnotation. 68 for (DexValue value : dexEncodedArray.values) { 69 value.sort(); 70 } 71 return true; 72 } 73 74 @Override 75 public boolean add(DexProgramClass dexClassData) { 76 return true; 77 } 78 79 @Override 80 public boolean add(DexCode dexCode) { 81 return true; 82 } 83 84 @Override 85 public boolean add(DexDebugInfo dexDebugInfo) { 86 return true; 87 } 88 89 @Override 90 public boolean add(DexTypeList dexTypeList) { 91 return true; 92 } 93 94 @Override 95 public boolean add(DexAnnotationSetRefList annotationSetRefList) { 96 return true; 97 } 98 99 @Override 100 public boolean setAnnotationsDirectoryForClass(DexProgramClass clazz, 101 DexAnnotationDirectory annotationDirectory) { 102 return true; 103 } 104 } 105 106 public ApplicationWriter( 107 DexApplication application, 108 AppInfo appInfo, 109 InternalOptions options, 110 NamingLens namingLens, 111 byte[] proguardSeedsData) { 112 assert application != null; 113 this.application = application; 114 this.appInfo = appInfo; 115 assert options != null; 116 this.options = options; 117 this.namingLens = namingLens; 118 this.proguardSeedsData = proguardSeedsData; 119 } 120 121 public AndroidApp write(PackageDistribution packageDistribution, ExecutorService executorService) 122 throws IOException, ExecutionException { 123 application.timing.begin("DexApplication.write"); 124 try { 125 application.dexItemFactory.sort(namingLens); 126 SortAnnotations sortAnnotations = new SortAnnotations(); 127 application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations)); 128 129 // Distribute classes into dex files. 130 VirtualFile.Distributor distributor = null; 131 if (options.outputMode == OutputMode.FilePerClass) { 132 assert packageDistribution == null : 133 "Cannot combine package distribution definition with file-per-class option."; 134 distributor = new FilePerClassDistributor(this); 135 } else if (options.minApiLevel < Constants.ANDROID_L_API 136 && options.mainDexKeepRules.isEmpty() 137 && application.mainDexList.isEmpty()) { 138 if (packageDistribution != null) { 139 throw new CompilationError("Cannot apply package distribution. Multidex is not" 140 + " supported with API level " + options.minApiLevel +"." 141 + " For API level < " + Constants.ANDROID_L_API + ", main dex classes list or" 142 + " rules must be specified."); 143 } 144 distributor = new VirtualFile.MonoDexDistributor(this); 145 } else if (packageDistribution != null) { 146 assert !options.minimalMainDex : 147 "Cannot combine package distribution definition with minimal-main-dex option."; 148 distributor = new PackageMapDistributor(this, packageDistribution, executorService); 149 } else { 150 distributor = new FillFilesDistributor(this, options.minimalMainDex); 151 } 152 Map<Integer, VirtualFile> newFiles = distributor.run(); 153 154 // Write the dex files and the Proguard mapping file in parallel. Use a linked hash map 155 // as the order matters when addDexProgramData is called below. 156 LinkedHashMap<VirtualFile, Future<byte[]>> dexDataFutures = new LinkedHashMap<>(); 157 for (int i = 0; i < newFiles.size(); i++) { 158 VirtualFile newFile = newFiles.get(i); 159 assert newFile.getId() == i; 160 if (!newFile.isEmpty()) { 161 dexDataFutures.put(newFile, executorService.submit(() -> writeDexFile(newFile))); 162 } 163 } 164 165 // Wait for all the spawned futures to terminate. 166 AndroidApp.Builder builder = AndroidApp.builder(); 167 try { 168 for (Map.Entry<VirtualFile, Future<byte[]>> entry : dexDataFutures.entrySet()) { 169 builder.addDexProgramData(entry.getValue().get(), entry.getKey().getClassDescriptors()); 170 } 171 } catch (InterruptedException e) { 172 throw new RuntimeException("Interrupted while waiting for future.", e); 173 } 174 // Write the proguard map file after writing the dex files, as the map writer traverses 175 // the DexProgramClass structures, which are destructively updated during dex file writing. 176 byte[] proguardMapResult = writeProguardMapFile(); 177 if (proguardMapResult != null) { 178 builder.setProguardMapData(proguardMapResult); 179 } 180 if (proguardSeedsData != null) { 181 builder.setProguardSeedsData(proguardSeedsData); 182 } 183 byte[] mainDexList = writeMainDexList(); 184 if (mainDexList != null) { 185 builder.setMainDexListData(mainDexList); 186 } 187 return builder.build(); 188 } finally { 189 application.timing.end(); 190 } 191 } 192 193 private byte[] writeDexFile(VirtualFile vfile) { 194 FileWriter fileWriter = 195 new FileWriter( 196 vfile.computeMapping(application), application, appInfo, options, namingLens); 197 // The file writer now knows the indexes of the fixed sections including strings. 198 fileWriter.rewriteCodeWithJumboStrings(vfile.classes()); 199 // Collect the non-fixed sections. 200 fileWriter.collect(); 201 // Generate and write the bytes. 202 return fileWriter.generate(); 203 } 204 205 private byte[] writeProguardMapFile() throws IOException { 206 // TODO(herhut): Should writing of the proguard-map file be split like this? 207 if (!namingLens.isIdentityLens()) { 208 MinifiedNameMapPrinter printer = new MinifiedNameMapPrinter(application, namingLens); 209 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 210 PrintStream stream = new PrintStream(bytes); 211 printer.write(stream); 212 stream.flush(); 213 return bytes.toByteArray(); 214 } else if (application.getProguardMap() != null) { 215 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 216 Writer writer = new PrintWriter(bytes); 217 application.getProguardMap().write(writer, !options.skipDebugLineNumberOpt); 218 writer.flush(); 219 return bytes.toByteArray(); 220 } 221 return null; 222 } 223 224 private String mapMainDexListName(DexType type) { 225 return DescriptorUtils.descriptorToJavaType(namingLens.lookupDescriptor(type).toString()) 226 .replace('.', '/') + ".class"; 227 } 228 229 private byte[] writeMainDexList() throws IOException { 230 if (application.mainDexList.isEmpty()) { 231 return null; 232 } 233 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 234 PrintWriter writer = new PrintWriter(bytes); 235 application.mainDexList.forEach( 236 type -> writer.println(mapMainDexListName(type)) 237 ); 238 writer.flush(); 239 return bytes.toByteArray(); 240 } 241 } 242