Home | History | Annotate | Download | only in utils
      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.utils;
      5 
      6 import static com.android.tools.r8.utils.FileUtils.isArchive;
      7 import static com.android.tools.r8.utils.FileUtils.isClassFile;
      8 import static com.android.tools.r8.utils.FileUtils.isDexFile;
      9 
     10 import com.android.tools.r8.ClassFileResourceProvider;
     11 import com.android.tools.r8.Resource;
     12 import com.android.tools.r8.errors.CompilationError;
     13 import com.android.tools.r8.errors.Unreachable;
     14 import com.android.tools.r8.graph.ClassKind;
     15 import com.google.common.collect.ImmutableList;
     16 import com.google.common.io.ByteStreams;
     17 import com.google.common.io.Closer;
     18 import java.io.ByteArrayOutputStream;
     19 import java.io.File;
     20 import java.io.FileInputStream;
     21 import java.io.FileNotFoundException;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.OutputStream;
     25 import java.nio.charset.StandardCharsets;
     26 import java.nio.file.CopyOption;
     27 import java.nio.file.Files;
     28 import java.nio.file.OpenOption;
     29 import java.nio.file.Path;
     30 import java.nio.file.Paths;
     31 import java.nio.file.StandardCopyOption;
     32 import java.nio.file.StandardOpenOption;
     33 import java.util.ArrayList;
     34 import java.util.Arrays;
     35 import java.util.Collection;
     36 import java.util.Collections;
     37 import java.util.List;
     38 import java.util.Set;
     39 import java.util.stream.Collectors;
     40 import java.util.zip.ZipEntry;
     41 import java.util.zip.ZipException;
     42 import java.util.zip.ZipInputStream;
     43 import java.util.zip.ZipOutputStream;
     44 
     45 /**
     46  * Collection of program files needed for processing.
     47  *
     48  * <p>This abstraction is the main input and output container for a given application.
     49  */
     50 public class AndroidApp {
     51 
     52   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
     53 
     54   private final ImmutableList<Resource> programResources;
     55   private final ImmutableList<Resource> classpathResources;
     56   private final ImmutableList<Resource> libraryResources;
     57   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
     58   private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
     59   private final Resource proguardMap;
     60   private final Resource proguardSeeds;
     61   private final Resource packageDistribution;
     62   private final Resource mainDexList;
     63 
     64   // See factory methods and AndroidApp.Builder below.
     65   private AndroidApp(
     66       ImmutableList<Resource> programResources,
     67       ImmutableList<Resource> classpathResources,
     68       ImmutableList<Resource> libraryResources,
     69       ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
     70       ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
     71       Resource proguardMap,
     72       Resource proguardSeeds,
     73       Resource packageDistribution,
     74       Resource mainDexList) {
     75     this.programResources = programResources;
     76     this.classpathResources = classpathResources;
     77     this.libraryResources = libraryResources;
     78     this.classpathResourceProviders = classpathResourceProviders;
     79     this.libraryResourceProviders = libraryResourceProviders;
     80     this.proguardMap = proguardMap;
     81     this.proguardSeeds = proguardSeeds;
     82     this.packageDistribution = packageDistribution;
     83     this.mainDexList = mainDexList;
     84   }
     85 
     86   /**
     87    * Create a new empty builder.
     88    */
     89   public static Builder builder() {
     90     return new Builder();
     91   }
     92 
     93   /**
     94    * Create a new builder initialized with the resources from @code{app}.
     95    */
     96   public static Builder builder(AndroidApp app) {
     97     return new Builder(app);
     98   }
     99 
    100   /**
    101    * Create an app from program files @code{files}. See also Builder::addProgramFiles.
    102    */
    103   public static AndroidApp fromProgramFiles(Path... files) throws IOException {
    104     return fromProgramFiles(Arrays.asList(files));
    105   }
    106 
    107   /**
    108    * Create an app from program files @code{files}. See also Builder::addProgramFiles.
    109    */
    110   public static AndroidApp fromProgramFiles(List<Path> files) throws IOException {
    111     return builder().addProgramFiles(files, false).build();
    112   }
    113 
    114   /**
    115    * Create an app from files found in @code{directory}. See also Builder::addProgramDirectory.
    116    */
    117   public static AndroidApp fromProgramDirectory(Path directory) throws IOException {
    118     return builder().addProgramDirectory(directory).build();
    119   }
    120 
    121   /**
    122    * Create an app from dex program data. See also Builder::addDexProgramData.
    123    */
    124   public static AndroidApp fromDexProgramData(byte[]... data) {
    125     return fromDexProgramData(Arrays.asList(data));
    126   }
    127 
    128   /**
    129    * Create an app from dex program data. See also Builder::addDexProgramData.
    130    */
    131   public static AndroidApp fromDexProgramData(List<byte[]> data) {
    132     return builder().addDexProgramData(data).build();
    133   }
    134 
    135   /**
    136    * Create an app from Java-bytecode program data. See also Builder::addClassProgramData.
    137    */
    138   public static AndroidApp fromClassProgramData(byte[]... data) {
    139     return fromClassProgramData(Arrays.asList(data));
    140   }
    141 
    142   /**
    143    * Create an app from Java-bytecode program data. See also Builder::addClassProgramData.
    144    */
    145   public static AndroidApp fromClassProgramData(List<byte[]> data) {
    146     return builder().addClassProgramData(data).build();
    147   }
    148 
    149   /** Get input streams for all dex program resources. */
    150   public List<Resource> getDexProgramResources() {
    151     return filter(programResources, Resource.Kind.DEX);
    152   }
    153 
    154   /** Get input streams for all Java-bytecode program resources. */
    155   public List<Resource> getClassProgramResources() {
    156     return filter(programResources, Resource.Kind.CLASSFILE);
    157   }
    158 
    159   /** Get input streams for all dex program classpath resources. */
    160   public List<Resource> getDexClasspathResources() {
    161     return filter(classpathResources, Resource.Kind.DEX);
    162   }
    163 
    164   /** Get input streams for all Java-bytecode classpath resources. */
    165   public List<Resource> getClassClasspathResources() {
    166     return filter(classpathResources, Resource.Kind.CLASSFILE);
    167   }
    168 
    169   /** Get input streams for all dex library resources. */
    170   public List<Resource> getDexLibraryResources() {
    171     return filter(libraryResources, Resource.Kind.DEX);
    172   }
    173 
    174   /** Get input streams for all Java-bytecode library resources. */
    175   public List<Resource> getClassLibraryResources() {
    176     return filter(libraryResources, Resource.Kind.CLASSFILE);
    177   }
    178 
    179   /** Get classpath resource providers. */
    180   public List<ClassFileResourceProvider> getClasspathResourceProviders() {
    181     return classpathResourceProviders;
    182   }
    183 
    184   /** Get library resource providers. */
    185   public List<ClassFileResourceProvider> getLibraryResourceProviders() {
    186     return libraryResourceProviders;
    187   }
    188 
    189   private List<Resource> filter(List<Resource> resources, Resource.Kind kind) {
    190     List<Resource> out = new ArrayList<>(resources.size());
    191     for (Resource resource : resources) {
    192       if (kind == resource.kind) {
    193         out.add(resource);
    194       }
    195     }
    196     return out;
    197   }
    198 
    199   /**
    200    * True if the proguard-map resource exists.
    201    */
    202   public boolean hasProguardMap() {
    203     return proguardMap != null;
    204   }
    205 
    206   /**
    207    * Get the input stream of the proguard-map resource if it exists.
    208    */
    209   public InputStream getProguardMap(Closer closer) throws IOException {
    210     return proguardMap == null ? null : proguardMap.getStream(closer);
    211   }
    212 
    213   /**
    214    * True if the proguard-seeds resource exists.
    215    */
    216   public boolean hasProguardSeeds() {
    217     return proguardSeeds != null;
    218   }
    219 
    220   /**
    221    * Get the input stream of the proguard-seeds resource if it exists.
    222    */
    223   public InputStream getProguardSeeds(Closer closer) throws IOException {
    224     return proguardSeeds == null ? null : proguardSeeds.getStream(closer);
    225   }
    226 
    227   /**
    228    * True if the package distribution resource exists.
    229    */
    230   public boolean hasPackageDistribution() {
    231     return packageDistribution != null;
    232   }
    233 
    234   /**
    235    * Get the input stream of the package distribution resource if it exists.
    236    */
    237   public InputStream getPackageDistribution(Closer closer) throws IOException {
    238     return packageDistribution == null ? null : packageDistribution.getStream(closer);
    239   }
    240 
    241   /**
    242    * True if the main dex list resource exists.
    243    */
    244   public boolean hasMainDexList() {
    245     return mainDexList != null;
    246   }
    247 
    248   /**
    249    * Get the input stream of the main dex list resource if it exists.
    250    */
    251   public InputStream getMainDexList(Closer closer) throws IOException {
    252     return mainDexList == null ? null : mainDexList.getStream(closer);
    253   }
    254 
    255   /**
    256    * Write the dex program resources and proguard resource to @code{output}.
    257    */
    258   public void write(Path output, OutputMode outputMode) throws IOException {
    259     if (isArchive(output)) {
    260       writeToZip(output, outputMode);
    261     } else {
    262       writeToDirectory(output, outputMode);
    263     }
    264   }
    265 
    266   /**
    267    * Write the dex program resources and proguard resource to @code{directory}.
    268    */
    269   public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
    270     if (outputMode == OutputMode.Indexed) {
    271       for (Path path : Files.list(directory).collect(Collectors.toList())) {
    272         if (isClassesDexFile(path)) {
    273           Files.delete(path);
    274         }
    275       }
    276     }
    277     CopyOption[] options = new CopyOption[] {StandardCopyOption.REPLACE_EXISTING};
    278     try (Closer closer = Closer.create()) {
    279       List<Resource> dexProgramSources = getDexProgramResources();
    280       for (int i = 0; i < dexProgramSources.size(); i++) {
    281         Path filePath = directory.resolve(outputMode.getOutputPath(dexProgramSources.get(i), i));
    282         if (!Files.exists(filePath.getParent())) {
    283           Files.createDirectories(filePath.getParent());
    284         }
    285         Files.copy(dexProgramSources.get(i).getStream(closer), filePath, options);
    286       }
    287     }
    288   }
    289 
    290   private static boolean isClassesDexFile(Path file) {
    291     String name = file.getFileName().toString().toLowerCase();
    292     if (!name.startsWith("classes") || !name.endsWith(".dex")) {
    293       return false;
    294     }
    295     String numeral = name.substring("classes".length(), name.length() - ".dex".length());
    296     if (numeral.isEmpty()) {
    297       return true;
    298     }
    299     char c0 = numeral.charAt(0);
    300     if (numeral.length() == 1) {
    301       return '2' <= c0 && c0 <= '9';
    302     }
    303     if (c0 < '1' || '9' < c0) {
    304       return false;
    305     }
    306     for (int i = 1; i < numeral.length(); i++) {
    307       char c = numeral.charAt(i);
    308       if (c < '0' || '9' < c) {
    309         return false;
    310       }
    311     }
    312     return true;
    313   }
    314 
    315   public List<byte[]> writeToMemory() throws IOException {
    316     List<byte[]> dex = new ArrayList<>();
    317     try (Closer closer = Closer.create()) {
    318       List<Resource> dexProgramSources = getDexProgramResources();
    319       for (int i = 0; i < dexProgramSources.size(); i++) {
    320         ByteArrayOutputStream out = new ByteArrayOutputStream();
    321         ByteStreams.copy(dexProgramSources.get(i).getStream(closer), out);
    322         dex.add(out.toByteArray());
    323       }
    324       // TODO(sgjesse): Add Proguard map and seeds.
    325     }
    326     return dex;
    327   }
    328 
    329   /**
    330    * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
    331    */
    332   public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
    333     OpenOption[] options =
    334         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
    335     try (Closer closer = Closer.create()) {
    336       try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
    337         List<Resource> dexProgramSources = getDexProgramResources();
    338         for (int i = 0; i < dexProgramSources.size(); i++) {
    339           ZipEntry zipEntry = new ZipEntry(outputMode.getOutputPath(dexProgramSources.get(i), i));
    340           byte[] bytes = ByteStreams.toByteArray(dexProgramSources.get(i).getStream(closer));
    341           zipEntry.setSize(bytes.length);
    342           out.putNextEntry(zipEntry);
    343           out.write(bytes);
    344           out.closeEntry();
    345         }
    346       }
    347     }
    348   }
    349 
    350   public void writeProguardMap(Closer closer, OutputStream out) throws IOException {
    351     InputStream input = getProguardMap(closer);
    352     assert input != null;
    353     out.write(ByteStreams.toByteArray(input));
    354   }
    355 
    356   public void writeProguardSeeds(Closer closer, OutputStream out) throws IOException {
    357     InputStream input = getProguardSeeds(closer);
    358     assert input != null;
    359     out.write(ByteStreams.toByteArray(input));
    360   }
    361 
    362   public void writeMainDexList(Closer closer, OutputStream out) throws IOException {
    363     InputStream input = getMainDexList(closer);
    364     assert input != null;
    365     out.write(ByteStreams.toByteArray(input));
    366   }
    367 
    368   /**
    369    * Builder interface for constructing an AndroidApp.
    370    */
    371   public static class Builder {
    372 
    373     private final List<Resource> programResources = new ArrayList<>();
    374     private final List<Resource> classpathResources = new ArrayList<>();
    375     private final List<Resource> libraryResources = new ArrayList<>();
    376     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
    377     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
    378     private Resource proguardMap;
    379     private Resource proguardSeeds;
    380     private Resource packageDistribution;
    381     private Resource mainDexList;
    382 
    383     // See AndroidApp::builder().
    384     private Builder() {
    385     }
    386 
    387     // See AndroidApp::builder(AndroidApp).
    388     private Builder(AndroidApp app) {
    389       programResources.addAll(app.programResources);
    390       classpathResources.addAll(app.classpathResources);
    391       libraryResources.addAll(app.libraryResources);
    392       classpathResourceProviders.addAll(app.classpathResourceProviders);
    393       libraryResourceProviders.addAll(app.libraryResourceProviders);
    394       proguardMap = app.proguardMap;
    395       proguardSeeds = app.proguardSeeds;
    396       packageDistribution = app.packageDistribution;
    397       mainDexList = app.mainDexList;
    398     }
    399 
    400     /**
    401      * Add dex program files and proguard-map file located in @code{directory}.
    402      *
    403      * <p>The program files included are the top-level files ending in .dex and the proguard-map
    404      * file should it exist (see @code{DEFAULT_PROGUARD_MAP_FILE} for its assumed name).
    405      *
    406      * <p>This method is mostly a utility for reading in the file content produces by some external
    407      * tool, eg, dx.
    408      *
    409      * @param directory Directory containing dex program files and optional proguard-map file.
    410      */
    411     public Builder addProgramDirectory(Path directory) throws IOException {
    412       File[] resources = directory.toFile().listFiles(file -> isDexFile(file.toPath()));
    413       for (File source : resources) {
    414         addFile(source.toPath(), ClassKind.PROGRAM, false);
    415       }
    416       File mapFile = new File(directory.toFile(), DEFAULT_PROGUARD_MAP_FILE);
    417       if (mapFile.exists()) {
    418         setProguardMapFile(mapFile.toPath());
    419       }
    420       return this;
    421     }
    422 
    423     /**
    424      * Add program file resources.
    425      */
    426     public Builder addProgramFiles(Path... files) throws IOException {
    427       return addProgramFiles(Arrays.asList(files), false);
    428     }
    429 
    430     /**
    431      * Add program file resources.
    432      */
    433     public Builder addProgramFiles(Collection<Path> files, boolean skipDex) throws IOException {
    434       for (Path file : files) {
    435         addFile(file, ClassKind.PROGRAM, skipDex);
    436       }
    437       return this;
    438     }
    439 
    440     /**
    441      * Add classpath file resources.
    442      */
    443     public Builder addClasspathFiles(Path... files) throws IOException {
    444       return addClasspathFiles(Arrays.asList(files));
    445     }
    446 
    447     /**
    448      * Add classpath file resources.
    449      */
    450     public Builder addClasspathFiles(Collection<Path> files) throws IOException {
    451       for (Path file : files) {
    452         addFile(file, ClassKind.CLASSPATH, false);
    453       }
    454       return this;
    455     }
    456 
    457     /**
    458      * Add classpath resource provider.
    459      */
    460     public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
    461       classpathResourceProviders.add(provider);
    462       return this;
    463     }
    464 
    465     /**
    466      * Add library file resources.
    467      */
    468     public Builder addLibraryFiles(Path... files) throws IOException {
    469       return addLibraryFiles(Arrays.asList(files));
    470     }
    471 
    472     /**
    473      * Add library file resources.
    474      */
    475     public Builder addLibraryFiles(Collection<Path> files) throws IOException {
    476       for (Path file : files) {
    477         addFile(file, ClassKind.LIBRARY, false);
    478       }
    479       return this;
    480     }
    481 
    482     /**
    483      * Add library resource provider.
    484      */
    485     public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
    486       libraryResourceProviders.add(provider);
    487       return this;
    488     }
    489 
    490     /**
    491      * Add dex program-data with class descriptor.
    492      */
    493     public Builder addDexProgramData(byte[] data, Set<String> classDescriptors) {
    494       resources(ClassKind.PROGRAM).add(
    495           Resource.fromBytes(Resource.Kind.DEX, data, classDescriptors));
    496       return this;
    497     }
    498 
    499     /**
    500      * Add dex program-data.
    501      */
    502     public Builder addDexProgramData(byte[]... data) {
    503       return addDexProgramData(Arrays.asList(data));
    504     }
    505 
    506     /**
    507      * Add dex program-data.
    508      */
    509     public Builder addDexProgramData(Collection<byte[]> data) {
    510       for (byte[] datum : data) {
    511         resources(ClassKind.PROGRAM).add(Resource.fromBytes(Resource.Kind.DEX, datum));
    512       }
    513       return this;
    514     }
    515 
    516     /**
    517      * Add Java-bytecode program data.
    518      */
    519     public Builder addClassProgramData(byte[]... data) {
    520       return addClassProgramData(Arrays.asList(data));
    521     }
    522 
    523     /**
    524      * Add Java-bytecode program data.
    525      */
    526     public Builder addClassProgramData(Collection<byte[]> data) {
    527       for (byte[] datum : data) {
    528         resources(ClassKind.PROGRAM).add(Resource.fromBytes(Resource.Kind.CLASSFILE, datum));
    529       }
    530       return this;
    531     }
    532 
    533     /**
    534      * Set proguard-map file.
    535      */
    536     public Builder setProguardMapFile(Path file) {
    537       proguardMap = file == null ? null : Resource.fromFile(null, file);
    538       return this;
    539     }
    540 
    541     /**
    542      * Set proguard-map data.
    543      */
    544     public Builder setProguardMapData(String content) {
    545       return setProguardMapData(content == null ? null : content.getBytes(StandardCharsets.UTF_8));
    546     }
    547 
    548     /**
    549      * Set proguard-map data.
    550      */
    551     public Builder setProguardMapData(byte[] content) {
    552       proguardMap = content == null ? null : Resource.fromBytes(null, content);
    553       return this;
    554     }
    555 
    556     /**
    557      * Set proguard-seeds data.
    558      */
    559     public Builder setProguardSeedsData(byte[] content) {
    560       proguardSeeds = content == null ? null : Resource.fromBytes(null, content);
    561       return this;
    562     }
    563 
    564     /**
    565      * Set the package-distribution file.
    566      */
    567     public Builder setPackageDistributionFile(Path file) {
    568       packageDistribution = file == null ? null : Resource.fromFile(null, file);
    569       return this;
    570     }
    571 
    572     /**
    573      * Set the main-dex list file.
    574      */
    575     public Builder setMainDexListFile(Path file) {
    576       mainDexList = file == null ? null : Resource.fromFile(null, file);
    577       return this;
    578     }
    579 
    580     /**
    581      * Set the main-dex list data.
    582      */
    583     public Builder setMainDexListData(byte[] content) {
    584       mainDexList = content == null ? null : Resource.fromBytes(null, content);
    585       return this;
    586     }
    587 
    588     /**
    589      * Build final AndroidApp.
    590      */
    591     public AndroidApp build() {
    592       return new AndroidApp(
    593           ImmutableList.copyOf(programResources),
    594           ImmutableList.copyOf(classpathResources),
    595           ImmutableList.copyOf(libraryResources),
    596           ImmutableList.copyOf(classpathResourceProviders),
    597           ImmutableList.copyOf(libraryResourceProviders),
    598           proguardMap,
    599           proguardSeeds,
    600           packageDistribution,
    601           mainDexList);
    602     }
    603 
    604     private List<Resource> resources(ClassKind classKind) {
    605       switch (classKind) {
    606         case PROGRAM:
    607           return programResources;
    608         case CLASSPATH:
    609           return classpathResources;
    610         case LIBRARY:
    611           return libraryResources;
    612       }
    613       throw new Unreachable();
    614     }
    615 
    616     private void addFile(Path file, ClassKind classKind, boolean skipDex) throws IOException {
    617       if (!Files.exists(file)) {
    618         throw new FileNotFoundException("Non-existent input file: " + file);
    619       }
    620       if (isDexFile(file) && !skipDex) {
    621         resources(classKind).add(Resource.fromFile(Resource.Kind.DEX, file));
    622       } else if (isClassFile(file)) {
    623         resources(classKind).add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
    624       } else if (isArchive(file)) {
    625         addArchive(file, classKind, skipDex);
    626       } else {
    627         throw new CompilationError("Unsupported source file type for file: " + file);
    628       }
    629     }
    630 
    631     private void addArchive(Path archive, ClassKind classKind, boolean skipDex) throws IOException {
    632       assert isArchive(archive);
    633       boolean containsDexData = false;
    634       boolean containsClassData = false;
    635       try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
    636         ZipEntry entry;
    637         while ((entry = stream.getNextEntry()) != null) {
    638           Path name = Paths.get(entry.getName());
    639           if (isDexFile(name) && !skipDex) {
    640             containsDexData = true;
    641             resources(classKind).add(Resource.fromBytes(
    642                 Resource.Kind.DEX, ByteStreams.toByteArray(stream)));
    643           } else if (isClassFile(name)) {
    644             containsClassData = true;
    645             String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
    646             resources(classKind).add(Resource.fromBytes(Resource.Kind.CLASSFILE,
    647                 ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
    648           }
    649         }
    650       } catch (ZipException e) {
    651         throw new CompilationError(
    652             "Zip error while reading '" + archive + "': " + e.getMessage(), e);
    653       }
    654       if (containsDexData && containsClassData) {
    655         throw new CompilationError(
    656             "Cannot create android app from an archive '" + archive
    657                 + "' containing both DEX and Java-bytecode content");
    658       }
    659     }
    660   }
    661 }
    662