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