Home | History | Annotate | Download | only in dexdeps
      1 /*
      2  * Copyright (C) 2009 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 
     17 package com.android.dexdeps;
     18 
     19 import java.io.File;
     20 import java.io.FileNotFoundException;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.RandomAccessFile;
     24 import java.util.zip.ZipEntry;
     25 import java.util.zip.ZipException;
     26 import java.util.zip.ZipFile;
     27 import java.util.zip.ZipInputStream;
     28 import java.util.ArrayList;
     29 import java.util.Collections;
     30 import java.util.List;
     31 
     32 public class Main {
     33     private String[] mInputFileNames;
     34     private String mOutputFormat = "xml";
     35 
     36     /**
     37      * whether to only emit info about classes used; when {@code false},
     38      * info about fields and methods is also emitted
     39      */
     40     private boolean mJustClasses = false;
     41 
     42     /**
     43      * Entry point.
     44      */
     45     public static void main(String[] args) {
     46         Main main = new Main();
     47         main.run(args);
     48     }
     49 
     50     /**
     51      * Start things up.
     52      */
     53     void run(String[] args) {
     54         try {
     55             parseArgs(args);
     56             boolean first = true;
     57 
     58             for (String fileName : mInputFileNames) {
     59                 if (first) {
     60                     first = false;
     61                     Output.generateFirstHeader(fileName, mOutputFormat);
     62                 } else {
     63                     Output.generateHeader(fileName, mOutputFormat);
     64                 }
     65                 List<RandomAccessFile> rafs = openInputFiles(fileName);
     66                 for (RandomAccessFile raf : rafs) {
     67                     DexData dexData = new DexData(raf);
     68                     dexData.load();
     69                     Output.generate(dexData, mOutputFormat, mJustClasses);
     70                     raf.close();
     71                 }
     72                 Output.generateFooter(mOutputFormat);
     73             }
     74         } catch (UsageException ue) {
     75             usage();
     76             System.exit(2);
     77         } catch (IOException ioe) {
     78             if (ioe.getMessage() != null) {
     79                 System.err.println("Failed: " + ioe);
     80             }
     81             System.exit(1);
     82         } catch (DexDataException dde) {
     83             /* a message was already reported, just bail quietly */
     84             System.exit(1);
     85         }
     86     }
     87 
     88     /**
     89      * Opens an input file, which could be a .dex or a .jar/.apk with a
     90      * classes.dex inside.  If the latter, we extract the contents to a
     91      * temporary file.
     92      *
     93      * @param fileName the name of the file to open
     94      */
     95     List<RandomAccessFile> openInputFiles(String fileName) throws IOException {
     96         List<RandomAccessFile> rafs = openInputFileAsZip(fileName);
     97 
     98         if (rafs == null) {
     99             File inputFile = new File(fileName);
    100             RandomAccessFile raf = new RandomAccessFile(inputFile, "r");
    101             rafs = Collections.singletonList(raf);
    102         }
    103 
    104         return rafs;
    105     }
    106 
    107     /**
    108      * Tries to open an input file as a Zip archive (jar/apk) with dex files inside.
    109      *
    110      * @param fileName the name of the file to open
    111      * @return a list of RandomAccessFile for classes.dex,
    112      *         classes2.dex, etc., or null if the input file is not a
    113      *         zip archive
    114      * @throws IOException if the file isn't found, or it's a zip and
    115      *         no classes.dex isn't found inside
    116      */
    117     List<RandomAccessFile> openInputFileAsZip(String fileName) throws IOException {
    118         /*
    119          * Try it as a zip file.
    120          */
    121         ZipFile zipFile;
    122         try {
    123             zipFile = new ZipFile(fileName);
    124         } catch (FileNotFoundException fnfe) {
    125             /* not found, no point in retrying as non-zip */
    126             System.err.println("Unable to open '" + fileName + "': " +
    127                 fnfe.getMessage());
    128             throw fnfe;
    129         } catch (ZipException ze) {
    130             /* not a zip */
    131             return null;
    132         }
    133 
    134         List<RandomAccessFile> result = new ArrayList<RandomAccessFile>();
    135         try {
    136             int classesDexNumber = 1;
    137             while (true) {
    138                 result.add(openClassesDexZipFileEntry(zipFile, classesDexNumber));
    139                 classesDexNumber++;
    140             }
    141         } catch (IOException ioe) {
    142             // We didn't find any of the expected dex files in the zip.
    143             if (result.isEmpty()) {
    144                 throw ioe;
    145             }
    146             return result;
    147         }
    148     }
    149 
    150     RandomAccessFile openClassesDexZipFileEntry(ZipFile zipFile, int classesDexNumber)
    151             throws IOException {
    152         /*
    153          * We know it's a zip; see if there's anything useful inside.  A
    154          * failure here results in some type of IOException (of which
    155          * ZipException is a subclass).
    156          */
    157         String zipEntryName = ("classes" +
    158                                (classesDexNumber == 1 ? "" : classesDexNumber) +
    159                                ".dex");
    160         ZipEntry entry = zipFile.getEntry(zipEntryName);
    161         if (entry == null) {
    162             zipFile.close();
    163             throw new ZipException("Unable to find '" + zipEntryName +
    164                 "' in '" + zipFile.getName() + "'");
    165         }
    166 
    167         InputStream zis = zipFile.getInputStream(entry);
    168 
    169         /*
    170          * Create a temp file to hold the DEX data, open it, and delete it
    171          * to ensure it doesn't hang around if we fail.
    172          */
    173         File tempFile = File.createTempFile("dexdeps", ".dex");
    174         //System.out.println("+++ using temp " + tempFile);
    175         RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
    176         tempFile.delete();
    177 
    178         /*
    179          * Copy all data from input stream to output file.
    180          */
    181         byte copyBuf[] = new byte[32768];
    182         int actual;
    183 
    184         while (true) {
    185             actual = zis.read(copyBuf);
    186             if (actual == -1)
    187                 break;
    188 
    189             raf.write(copyBuf, 0, actual);
    190         }
    191 
    192         zis.close();
    193         raf.seek(0);
    194 
    195         return raf;
    196     }
    197 
    198 
    199     /**
    200      * Parses command-line arguments.
    201      *
    202      * @throws UsageException if arguments are missing or poorly formed
    203      */
    204     void parseArgs(String[] args) {
    205         int idx;
    206 
    207         for (idx = 0; idx < args.length; idx++) {
    208             String arg = args[idx];
    209 
    210             if (arg.equals("--") || !arg.startsWith("--")) {
    211                 break;
    212             } else if (arg.startsWith("--format=")) {
    213                 mOutputFormat = arg.substring(arg.indexOf('=') + 1);
    214                 if (!mOutputFormat.equals("brief") &&
    215                     !mOutputFormat.equals("xml"))
    216                 {
    217                     System.err.println("Unknown format '" + mOutputFormat +"'");
    218                     throw new UsageException();
    219                 }
    220                 //System.out.println("+++ using format " + mOutputFormat);
    221             } else if (arg.equals("--just-classes")) {
    222                 mJustClasses = true;
    223             } else {
    224                 System.err.println("Unknown option '" + arg + "'");
    225                 throw new UsageException();
    226             }
    227         }
    228 
    229         // We expect at least one more argument (file name).
    230         int fileCount = args.length - idx;
    231         if (fileCount == 0) {
    232             throw new UsageException();
    233         }
    234 
    235         mInputFileNames = new String[fileCount];
    236         System.arraycopy(args, idx, mInputFileNames, 0, fileCount);
    237     }
    238 
    239     /**
    240      * Prints command-line usage info.
    241      */
    242     void usage() {
    243         System.err.print(
    244                 "DEX dependency scanner v1.2\n" +
    245                 "Copyright (C) 2009 The Android Open Source Project\n\n" +
    246                 "Usage: dexdeps [options] <file.{dex,apk,jar}> ...\n" +
    247                 "Options:\n" +
    248                 "  --format={xml,brief}\n" +
    249                 "  --just-classes\n");
    250     }
    251 }
    252