Home | History | Annotate | Download | only in dex
      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