Various improvements to dexdeps.
This includes:
* Adding an option to only produce lists of dependent classes (and not
also fields and methods).
* Adding the ability to process multiple files on a single invocation.
* Adding a label at the head of the section for each file.
* Neatening up Output a bit by factoring "System.out" into a static field.
Change-Id: Id9a691d23afd18f82ab3790cb760cfd12e14bc86
diff --git a/tools/dexdeps/README.txt b/tools/dexdeps/README.txt
index 060aecb..22380ce 100644
--- a/tools/dexdeps/README.txt
+++ b/tools/dexdeps/README.txt
@@ -9,7 +9,7 @@
Basic usage:
- dexdeps [options] <file.{dex,apk,jar}>
+ dexdeps [options] <file.{dex,apk,jar}> ...
For zip archives (including .jar and .apk), dexdeps will look for a
"classes.dex" entry.
@@ -25,3 +25,8 @@
"xml" produces a larger output file, readable with an XML browser. Types
are shown in a more human-readable form (e.g. "[I" becomes "int[]").
+
+ --just-classes
+
+ Indicates that output should only include a list of classes, as
+ opposed to also listing fields and methods.
diff --git a/tools/dexdeps/src/com/android/dexdeps/Main.java b/tools/dexdeps/src/com/android/dexdeps/Main.java
index d48889d..410694a 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Main.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Main.java
@@ -29,10 +29,16 @@
public class Main {
private static final String CLASSES_DEX = "classes.dex";
- private String mInputFileName;
+ 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) {
@@ -46,17 +52,31 @@
void run(String[] args) {
try {
parseArgs(args);
- RandomAccessFile raf = openInputFile();
- DexData dexData = new DexData(raf);
- dexData.load();
+ boolean first = true;
- Output.generate(dexData, mOutputFormat);
+ 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)
+ if (ioe.getMessage() != null) {
System.err.println("Failed: " + ioe);
+ }
System.exit(1);
} catch (DexDataException dde) {
/* a message was already reported, just bail quietly */
@@ -65,16 +85,18 @@
}
/**
- * Opens the input file, which could be a .dex or a .jar/.apk with a
+ * 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() throws IOException {
+ RandomAccessFile openInputFile(String fileName) throws IOException {
RandomAccessFile raf;
- raf = openInputFileAsZip();
+ raf = openInputFileAsZip(fileName);
if (raf == null) {
- File inputFile = new File(mInputFileName);
+ File inputFile = new File(fileName);
raf = new RandomAccessFile(inputFile, "r");
}
@@ -82,25 +104,26 @@
}
/**
- * Tries to open the input file as a Zip archive (jar/apk) with a
+ * 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() throws IOException {
+ RandomAccessFile openInputFileAsZip(String fileName) throws IOException {
ZipFile zipFile;
/*
* Try it as a zip file.
*/
try {
- zipFile = new ZipFile(mInputFileName);
+ zipFile = new ZipFile(fileName);
} catch (FileNotFoundException fnfe) {
/* not found, no point in retrying as non-zip */
- System.err.println("Unable to open '" + mInputFileName + "': " +
+ System.err.println("Unable to open '" + fileName + "': " +
fnfe.getMessage());
throw fnfe;
} catch (ZipException ze) {
@@ -116,7 +139,7 @@
ZipEntry entry = zipFile.getEntry(CLASSES_DEX);
if (entry == null) {
System.err.println("Unable to find '" + CLASSES_DEX +
- "' in '" + mInputFileName + "'");
+ "' in '" + fileName + "'");
zipFile.close();
throw new ZipException();
}
@@ -175,28 +198,34 @@
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();
}
}
- // expecting one argument left
- if (idx != args.length - 1) {
+ // We expect at least one more argument (file name).
+ int fileCount = args.length - idx;
+ if (fileCount == 0) {
throw new UsageException();
}
- mInputFileName = args[idx];
+ mInputFileNames = new String[fileCount];
+ System.arraycopy(args, idx, mInputFileNames, 0, fileCount);
}
/**
* Prints command-line usage info.
*/
void usage() {
- System.err.println("DEX dependency scanner v1.1");
- System.err.println("Copyright (C) 2009 The Android Open Source Project\n");
- System.err.println("Usage: dexdeps [options] <file.{dex,apk,jar}>");
- System.err.println("Options:");
- System.err.println(" --format={xml,brief}");
+ 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");
}
}
diff --git a/tools/dexdeps/src/com/android/dexdeps/Output.java b/tools/dexdeps/src/com/android/dexdeps/Output.java
index b786825..dbe3bc2 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Output.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Output.java
@@ -16,6 +16,8 @@
package com.android.dexdeps;
+import java.io.PrintStream;
+
/**
* Generate fancy output.
*/
@@ -26,11 +28,51 @@
private static final String IN3 = " ";
private static final String IN4 = " ";
- public static void generate(DexData dexData, String format) {
+ private static final PrintStream out = System.out;
+
+ private static void generateHeader0(String fileName, String format) {
if (format.equals("brief")) {
- printBrief(dexData);
+ if (fileName != null) {
+ out.println("File: " + fileName);
+ }
} else if (format.equals("xml")) {
- printXml(dexData);
+ if (fileName != null) {
+ out.println(IN0 + "<external file=\"" + fileName + "\">");
+ } else {
+ out.println(IN0 + "<external>");
+ }
+ } else {
+ /* should've been trapped in arg handler */
+ throw new RuntimeException("unknown output format");
+ }
+ }
+
+ public static void generateFirstHeader(String fileName, String format) {
+ generateHeader0(fileName, format);
+ }
+
+ public static void generateHeader(String fileName, String format) {
+ out.println();
+ generateHeader0(fileName, format);
+ }
+
+ public static void generateFooter(String format) {
+ if (format.equals("brief")) {
+ // Nothing to do.
+ } else if (format.equals("xml")) {
+ out.println("</external>");
+ } else {
+ /* should've been trapped in arg handler */
+ throw new RuntimeException("unknown output format");
+ }
+ }
+
+ public static void generate(DexData dexData, String format,
+ boolean justClasses) {
+ if (format.equals("brief")) {
+ printBrief(dexData, justClasses);
+ } else if (format.equals("xml")) {
+ printXml(dexData, justClasses);
} else {
/* should've been trapped in arg handler */
throw new RuntimeException("unknown output format");
@@ -40,23 +82,29 @@
/**
* Prints the data in a simple human-readable format.
*/
- static void printBrief(DexData dexData) {
+ static void printBrief(DexData dexData, boolean justClasses) {
ClassRef[] externClassRefs = dexData.getExternalReferences();
- printClassRefs(externClassRefs);
- printFieldRefs(externClassRefs);
- printMethodRefs(externClassRefs);
+ printClassRefs(externClassRefs, justClasses);
+
+ if (!justClasses) {
+ printFieldRefs(externClassRefs);
+ printMethodRefs(externClassRefs);
+ }
}
/**
* Prints the list of classes in a simple human-readable format.
*/
- static void printClassRefs(ClassRef[] classes) {
- System.out.println("Classes:");
+ static void printClassRefs(ClassRef[] classes, boolean justClasses) {
+ if (!justClasses) {
+ out.println("Classes:");
+ }
+
for (int i = 0; i < classes.length; i++) {
ClassRef ref = classes[i];
- System.out.println(descriptorToDot(ref.getName()));
+ out.println(descriptorToDot(ref.getName()));
}
}
@@ -64,14 +112,14 @@
* Prints the list of fields in a simple human-readable format.
*/
static void printFieldRefs(ClassRef[] classes) {
- System.out.println("\nFields:");
+ out.println("\nFields:");
for (int i = 0; i < classes.length; i++) {
FieldRef[] fields = classes[i].getFieldArray();
for (int j = 0; j < fields.length; j++) {
FieldRef ref = fields[j];
- System.out.println(descriptorToDot(ref.getDeclClassName()) +
+ out.println(descriptorToDot(ref.getDeclClassName()) +
"." + ref.getName() + " : " + ref.getTypeName());
}
}
@@ -81,30 +129,27 @@
* Prints the list of methods in a simple human-readable format.
*/
static void printMethodRefs(ClassRef[] classes) {
- System.out.println("\nMethods:");
+ out.println("\nMethods:");
for (int i = 0; i < classes.length; i++) {
MethodRef[] methods = classes[i].getMethodArray();
for (int j = 0; j < methods.length; j++) {
MethodRef ref = methods[j];
- System.out.println(descriptorToDot(ref.getDeclClassName()) +
+ out.println(descriptorToDot(ref.getDeclClassName()) +
"." + ref.getName() + " : " + ref.getDescriptor());
}
}
}
-
/**
* Prints the output in XML format.
*
* We shouldn't need to XML-escape the field/method info.
*/
- static void printXml(DexData dexData) {
+ static void printXml(DexData dexData, boolean justClasses) {
ClassRef[] externClassRefs = dexData.getExternalReferences();
- System.out.println(IN0 + "<external>");
-
/*
* Iterate through externClassRefs. For each class, dump all of
* the matching fields and methods.
@@ -121,24 +166,25 @@
*/
if (!packageName.equals(prevPackage)) {
if (prevPackage != null) {
- System.out.println(IN1 + "</package>");
+ out.println(IN1 + "</package>");
}
- System.out.println(IN1 +
+ out.println(IN1 +
"<package name=\"" + packageName + "\">");
prevPackage = packageName;
}
- System.out.println(IN2 + "<class name=\"" + className + "\">");
- printXmlFields(cref);
- printXmlMethods(cref);
- System.out.println(IN2 + "</class>");
+ out.println(IN2 + "<class name=\"" + className + "\">");
+ if (!justClasses) {
+ printXmlFields(cref);
+ printXmlMethods(cref);
+ }
+ out.println(IN2 + "</class>");
}
if (prevPackage != null)
- System.out.println(IN1 + "</package>");
- System.out.println(IN0 + "</external>");
+ out.println(IN1 + "</package>");
}
/**
@@ -149,7 +195,7 @@
for (int i = 0; i < fields.length; i++) {
FieldRef fref = fields[i];
- System.out.println(IN3 + "<field name=\"" + fref.getName() +
+ out.println(IN3 + "<field name=\"" + fref.getName() +
"\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>");
}
}
@@ -167,22 +213,22 @@
constructor = mref.getName().equals("<init>");
if (constructor) {
// use class name instead of method name
- System.out.println(IN3 + "<constructor name=\"" +
+ out.println(IN3 + "<constructor name=\"" +
classNameOnly(declClassName) + "\">");
} else {
- System.out.println(IN3 + "<method name=\"" + mref.getName() +
+ out.println(IN3 + "<method name=\"" + mref.getName() +
"\" return=\"" + descriptorToDot(mref.getReturnTypeName()) +
"\">");
}
String[] args = mref.getArgumentTypeNames();
for (int j = 0; j < args.length; j++) {
- System.out.println(IN4 + "<parameter type=\"" +
+ out.println(IN4 + "<parameter type=\"" +
descriptorToDot(args[j]) + "\"/>");
}
if (constructor) {
- System.out.println(IN3 + "</constructor>");
+ out.println(IN3 + "</constructor>");
} else {
- System.out.println(IN3 + "</method>");
+ out.println(IN3 + "</method>");
}
}
}