blob: 410694aee826084256c5b1d2a6dd66f6be6f4ea9 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dexdeps;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class Main {
private static final String CLASSES_DEX = "classes.dex";
private String[] mInputFileNames;
private String mOutputFormat = "xml";
/**
* whether to only emit info about classes used; when {@code false},
* info about fields and methods is also emitted
*/
private boolean mJustClasses = false;
/**
* Entry point.
*/
public static void main(String[] args) {
Main main = new Main();
main.run(args);
}
/**
* Start things up.
*/
void run(String[] args) {
try {
parseArgs(args);
boolean first = true;
for (String fileName : mInputFileNames) {
RandomAccessFile raf = openInputFile(fileName);
DexData dexData = new DexData(raf);
dexData.load();
if (first) {
first = false;
Output.generateFirstHeader(fileName, mOutputFormat);
} else {
Output.generateHeader(fileName, mOutputFormat);
}
Output.generate(dexData, mOutputFormat, mJustClasses);
Output.generateFooter(mOutputFormat);
raf.close();
}
} catch (UsageException ue) {
usage();
System.exit(2);
} catch (IOException ioe) {
if (ioe.getMessage() != null) {
System.err.println("Failed: " + ioe);
}
System.exit(1);
} catch (DexDataException dde) {
/* a message was already reported, just bail quietly */
System.exit(1);
}
}
/**
* Opens an input file, which could be a .dex or a .jar/.apk with a
* classes.dex inside. If the latter, we extract the contents to a
* temporary file.
*
* @param fileName the name of the file to open
*/
RandomAccessFile openInputFile(String fileName) throws IOException {
RandomAccessFile raf;
raf = openInputFileAsZip(fileName);
if (raf == null) {
File inputFile = new File(fileName);
raf = new RandomAccessFile(inputFile, "r");
}
return raf;
}
/**
* Tries to open an input file as a Zip archive (jar/apk) with a
* "classes.dex" inside.
*
* @param fileName the name of the file to open
* @return a RandomAccessFile for classes.dex, or null if the input file
* is not a zip archive
* @throws IOException if the file isn't found, or it's a zip and
* classes.dex isn't found inside
*/
RandomAccessFile openInputFileAsZip(String fileName) throws IOException {
ZipFile zipFile;
/*
* Try it as a zip file.
*/
try {
zipFile = new ZipFile(fileName);
} catch (FileNotFoundException fnfe) {
/* not found, no point in retrying as non-zip */
System.err.println("Unable to open '" + fileName + "': " +
fnfe.getMessage());
throw fnfe;
} catch (ZipException ze) {
/* not a zip */
return null;
}
/*
* We know it's a zip; see if there's anything useful inside. A
* failure here results in some type of IOException (of which
* ZipException is a subclass).
*/
ZipEntry entry = zipFile.getEntry(CLASSES_DEX);
if (entry == null) {
System.err.println("Unable to find '" + CLASSES_DEX +
"' in '" + fileName + "'");
zipFile.close();
throw new ZipException();
}
InputStream zis = zipFile.getInputStream(entry);
/*
* Create a temp file to hold the DEX data, open it, and delete it
* to ensure it doesn't hang around if we fail.
*/
File tempFile = File.createTempFile("dexdeps", ".dex");
//System.out.println("+++ using temp " + tempFile);
RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
tempFile.delete();
/*
* Copy all data from input stream to output file.
*/
byte copyBuf[] = new byte[32768];
int actual;
while (true) {
actual = zis.read(copyBuf);
if (actual == -1)
break;
raf.write(copyBuf, 0, actual);
}
zis.close();
raf.seek(0);
return raf;
}
/**
* Parses command-line arguments.
*
* @throws UsageException if arguments are missing or poorly formed
*/
void parseArgs(String[] args) {
int idx;
for (idx = 0; idx < args.length; idx++) {
String arg = args[idx];
if (arg.equals("--") || !arg.startsWith("--")) {
break;
} else if (arg.startsWith("--format=")) {
mOutputFormat = arg.substring(arg.indexOf('=') + 1);
if (!mOutputFormat.equals("brief") &&
!mOutputFormat.equals("xml"))
{
System.err.println("Unknown format '" + mOutputFormat +"'");
throw new UsageException();
}
//System.out.println("+++ using format " + mOutputFormat);
} else if (arg.equals("--just-classes")) {
mJustClasses = true;
} else {
System.err.println("Unknown option '" + arg + "'");
throw new UsageException();
}
}
// We expect at least one more argument (file name).
int fileCount = args.length - idx;
if (fileCount == 0) {
throw new UsageException();
}
mInputFileNames = new String[fileCount];
System.arraycopy(args, idx, mInputFileNames, 0, fileCount);
}
/**
* Prints command-line usage info.
*/
void usage() {
System.err.print(
"DEX dependency scanner v1.2\n" +
"Copyright (C) 2009 The Android Open Source Project\n\n" +
"Usage: dexdeps [options] <file.{dex,apk,jar}> ...\n" +
"Options:\n" +
" --format={xml,brief}\n" +
" --just-classes\n");
}
}