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 17package com.android.dexdeps; 18 19import java.io.File; 20import java.io.FileNotFoundException; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.RandomAccessFile; 24import java.util.zip.ZipEntry; 25import java.util.zip.ZipException; 26import java.util.zip.ZipFile; 27import java.util.zip.ZipInputStream; 28 29public class Main { 30 private static final String CLASSES_DEX = "classes.dex"; 31 32 private String mInputFileName; 33 private String mOutputFormat = "xml"; 34 35 /** 36 * Entry point. 37 */ 38 public static void main(String[] args) { 39 Main main = new Main(); 40 main.run(args); 41 } 42 43 /** 44 * Start things up. 45 */ 46 void run(String[] args) { 47 try { 48 parseArgs(args); 49 RandomAccessFile raf = openInputFile(); 50 DexData dexData = new DexData(raf); 51 dexData.load(); 52 53 Output.generate(dexData, mOutputFormat); 54 } catch (UsageException ue) { 55 usage(); 56 System.exit(2); 57 } catch (IOException ioe) { 58 if (ioe.getMessage() != null) 59 System.err.println("Failed: " + ioe); 60 System.exit(1); 61 } catch (DexDataException dde) { 62 /* a message was already reported, just bail quietly */ 63 System.exit(1); 64 } 65 } 66 67 /** 68 * Opens the input file, which could be a .dex or a .jar/.apk with a 69 * classes.dex inside. If the latter, we extract the contents to a 70 * temporary file. 71 */ 72 RandomAccessFile openInputFile() throws IOException { 73 RandomAccessFile raf; 74 75 raf = openInputFileAsZip(); 76 if (raf == null) { 77 File inputFile = new File(mInputFileName); 78 raf = new RandomAccessFile(inputFile, "r"); 79 } 80 81 return raf; 82 } 83 84 /** 85 * Tries to open the input file as a Zip archive (jar/apk) with a 86 * "classes.dex" inside. 87 * 88 * @return a RandomAccessFile for classes.dex, or null if the input file 89 * is not a zip archive 90 * @throws IOException if the file isn't found, or it's a zip and 91 * classes.dex isn't found inside 92 */ 93 RandomAccessFile openInputFileAsZip() throws IOException { 94 ZipFile zipFile; 95 96 /* 97 * Try it as a zip file. 98 */ 99 try { 100 zipFile = new ZipFile(mInputFileName); 101 } catch (FileNotFoundException fnfe) { 102 /* not found, no point in retrying as non-zip */ 103 System.err.println("Unable to open '" + mInputFileName + "': " + 104 fnfe.getMessage()); 105 throw fnfe; 106 } catch (ZipException ze) { 107 /* not a zip */ 108 return null; 109 } 110 111 /* 112 * We know it's a zip; see if there's anything useful inside. A 113 * failure here results in some type of IOException (of which 114 * ZipException is a subclass). 115 */ 116 ZipEntry entry = zipFile.getEntry(CLASSES_DEX); 117 if (entry == null) { 118 System.err.println("Unable to find '" + CLASSES_DEX + 119 "' in '" + mInputFileName + "'"); 120 zipFile.close(); 121 throw new ZipException(); 122 } 123 124 InputStream zis = zipFile.getInputStream(entry); 125 126 /* 127 * Create a temp file to hold the DEX data, open it, and delete it 128 * to ensure it doesn't hang around if we fail. 129 */ 130 File tempFile = File.createTempFile("dexdeps", ".dex"); 131 //System.out.println("+++ using temp " + tempFile); 132 RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); 133 tempFile.delete(); 134 135 /* 136 * Copy all data from input stream to output file. 137 */ 138 byte copyBuf[] = new byte[32768]; 139 int actual; 140 141 while (true) { 142 actual = zis.read(copyBuf); 143 if (actual == -1) 144 break; 145 146 raf.write(copyBuf, 0, actual); 147 } 148 149 zis.close(); 150 raf.seek(0); 151 152 return raf; 153 } 154 155 156 /** 157 * Parses command-line arguments. 158 * 159 * @throws UsageException if arguments are missing or poorly formed 160 */ 161 void parseArgs(String[] args) { 162 int idx; 163 164 for (idx = 0; idx < args.length; idx++) { 165 String arg = args[idx]; 166 167 if (arg.equals("--") || !arg.startsWith("--")) { 168 break; 169 } else if (arg.startsWith("--format=")) { 170 mOutputFormat = arg.substring(arg.indexOf('=') + 1); 171 if (!mOutputFormat.equals("brief") && 172 !mOutputFormat.equals("xml")) 173 { 174 System.err.println("Unknown format '" + mOutputFormat +"'"); 175 throw new UsageException(); 176 } 177 //System.out.println("+++ using format " + mOutputFormat); 178 } else { 179 System.err.println("Unknown option '" + arg + "'"); 180 throw new UsageException(); 181 } 182 } 183 184 // expecting one argument left 185 if (idx != args.length - 1) { 186 throw new UsageException(); 187 } 188 189 mInputFileName = args[idx]; 190 } 191 192 /** 193 * Prints command-line usage info. 194 */ 195 void usage() { 196 System.err.println("DEX dependency scanner v1.1"); 197 System.err.println("Copyright (C) 2009 The Android Open Source Project\n"); 198 System.err.println("Usage: dexdeps [options] <file.{dex,apk,jar}>"); 199 System.err.println("Options:"); 200 System.err.println(" --format={xml,brief}"); 201 } 202} 203