diff options
author | Raphael Moll <raphael@google.com> | 2012-06-14 14:14:45 -0700 |
---|---|---|
committer | Raphael Moll <raphael@google.com> | 2012-06-14 14:14:45 -0700 |
commit | bce1011dd2a371c587d2e39ad8c3d446b63049d0 (patch) | |
tree | 3e99f4996919ff44364c895e95999f3f9c03d37b /tools/layoutlib | |
parent | eb02d40c6d068ffcbb822910c5449b7081d1ae51 (diff) | |
parent | 8cf489c1f509ad2414a887035ad3d6996d1931a3 (diff) | |
download | frameworks_base-bce1011dd2a371c587d2e39ad8c3d446b63049d0.zip frameworks_base-bce1011dd2a371c587d2e39ad8c3d446b63049d0.tar.gz frameworks_base-bce1011dd2a371c587d2e39ad8c3d446b63049d0.tar.bz2 |
resolved conflicts for merge of 8cf489c1 to jb-dev-plus-aosp
Change-Id: I06608e3c122b8f321429d457c51aa2213dd238b4
Diffstat (limited to 'tools/layoutlib')
3 files changed, 846 insertions, 11 deletions
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java new file mode 100755 index 0000000..c988c70 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2012 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.tools.layoutlib.create; + +import com.android.tools.layoutlib.annotations.VisibleForTesting; +import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Analyzes the input JAR using the ASM java bytecode manipulation library + * to list the classes and their dependencies. A "dependency" is a class + * used by another class. + */ +public class DependencyFinder { + + // Note: a bunch of stuff has package-level access for unit tests. Consider it private. + + /** Output logger. */ + private final Log mLog; + + /** + * Creates a new analyzer. + * + * @param log The log output. + */ + public DependencyFinder(Log log) { + mLog = log; + } + + /** + * Starts the analysis using parameters from the constructor. + * + * @param osJarPath The input source JARs to parse. + * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }. + * [1]: map { missing class FQCN => set of FQCN class that uses it. } + */ + public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException { + + Map<String, ClassReader> zipClasses = parseZip(osJarPath); + mLog.info("Found %d classes in input JAR%s.", + zipClasses.size(), + osJarPath.size() > 1 ? "s" : ""); + + Map<String, Set<String>> deps = findClassesDeps(zipClasses); + + Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet()); + + List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2); + result.add(deps); + result.add(missing); + return result; + } + + /** + * Prints dependencies to the current logger, found stuff and missing stuff. + */ + public void printAllDeps(List<Map<String, Set<String>>> result) { + assert result.size() == 2; + Map<String, Set<String>> deps = result.get(0); + Map<String, Set<String>> missing = result.get(1); + + // Print all dependences found in the format: + // +Found: <FQCN from zip> + // uses: FQCN + + mLog.info("++++++ %d Entries found in source JARs", deps.size()); + mLog.info(""); + + for (Entry<String, Set<String>> entry : deps.entrySet()) { + mLog.info( "+Found : %s", entry.getKey()); + for (String dep : entry.getValue()) { + mLog.info(" uses: %s", dep); + } + + mLog.info(""); + } + + + // Now print all missing dependences in the format: + // -Missing <FQCN>: + // used by: <FQCN> + + mLog.info(""); + mLog.info("------ %d Entries missing from source JARs", missing.size()); + mLog.info(""); + + for (Entry<String, Set<String>> entry : missing.entrySet()) { + mLog.info( "-Missing : %s", entry.getKey()); + for (String dep : entry.getValue()) { + mLog.info(" used by: %s", dep); + } + + mLog.info(""); + } + } + + /** + * Prints only a summary of the missing dependencies to the current logger. + */ + public void printMissingDeps(List<Map<String, Set<String>>> result) { + assert result.size() == 2; + @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0); + Map<String, Set<String>> missing = result.get(1); + + for (String fqcn : missing.keySet()) { + mLog.info("%s", fqcn); + } + } + + // ---------------- + + /** + * Parses a JAR file and returns a list of all classes founds using a map + * class name => ASM ClassReader. Class names are in the form "android.view.View". + */ + Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException { + TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>(); + + for (String jarPath : jarPathList) { + ZipFile zip = new ZipFile(jarPath); + Enumeration<? extends ZipEntry> entries = zip.entries(); + ZipEntry entry; + while (entries.hasMoreElements()) { + entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + ClassReader cr = new ClassReader(zip.getInputStream(entry)); + String className = classReaderToClassName(cr); + classes.put(className, cr); + } + } + } + + return classes; + } + + /** + * Utility that returns the fully qualified binary class name for a ClassReader. + * E.g. it returns something like android.view.View. + */ + static String classReaderToClassName(ClassReader classReader) { + if (classReader == null) { + return null; + } else { + return classReader.getClassName().replace('/', '.'); + } + } + + /** + * Utility that returns the fully qualified binary class name from a path-like FQCN. + * E.g. it returns android.view.View from android/view/View. + */ + static String internalToBinaryClassName(String className) { + if (className == null) { + return null; + } else { + return className.replace('/', '.'); + } + } + + /** + * Finds all dependencies for all classes in keepClasses which are also + * listed in zipClasses. Returns a map of all the dependencies found. + */ + Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) { + + // The dependencies that we'll collect. + // It's a map Class name => uses class names. + Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>(); + + DependencyVisitor visitor = getVisitor(); + + int count = 0; + try { + for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { + String name = entry.getKey(); + + TreeSet<String> set = new TreeSet<String>(); + dependencyMap.put(name, set); + visitor.setDependencySet(set); + + ClassReader cr = entry.getValue(); + cr.accept(visitor, 0 /* flags */); + + visitor.setDependencySet(null); + + mLog.debugNoln("Visited %d classes\r", ++count); + } + } finally { + mLog.debugNoln("\n"); + } + + return dependencyMap; + } + + /** + * Computes which classes FQCN were found as dependencies that are NOT listed + * in the original JAR classes. + * + * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}. + * @param zipClasses The set of all classes FQCN found in the JAR files. + * @return A map { FQCN not found in the zipClasses => classes using it } + */ + private Map<String, Set<String>> findMissingClasses( + Map<String, Set<String>> deps, + Set<String> zipClasses) { + Map<String, Set<String>> missing = new TreeMap<String, Set<String>>(); + + for (Entry<String, Set<String>> entry : deps.entrySet()) { + String name = entry.getKey(); + + for (String dep : entry.getValue()) { + if (!zipClasses.contains(dep)) { + // This dependency doesn't exist in the zip classes. + Set<String> set = missing.get(dep); + if (set == null) { + set = new TreeSet<String>(); + missing.put(dep, set); + } + set.add(name); + } + } + + } + + return missing; + } + + + // ---------------------------------- + + /** + * Instantiates a new DependencyVisitor. Useful for unit tests. + */ + @VisibleForTesting(visibility=Visibility.PRIVATE) + DependencyVisitor getVisitor() { + return new DependencyVisitor(); + } + + /** + * Visitor to collect all the type dependencies from a class. + */ + public class DependencyVisitor extends ClassVisitor { + + private Set<String> mCurrentDepSet; + + /** + * Creates a new visitor that will find all the dependencies for the visited class. + */ + public DependencyVisitor() { + super(Opcodes.ASM4); + } + + /** + * Sets the {@link Set} where to record direct dependencies for this class. + * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call. + */ + public void setDependencySet(Set<String> set) { + mCurrentDepSet = set; + } + + /** + * Considers the given class name as a dependency. + */ + public void considerName(String className) { + if (className == null) { + return; + } + + className = internalToBinaryClassName(className); + + try { + // exclude classes that are part of the default JRE (the one executing this program) + if (getClass().getClassLoader().loadClass(className) != null) { + return; + } + } catch (ClassNotFoundException e) { + // ignore + } + + // Add it to the dependency set for the currently visited class, as needed. + assert mCurrentDepSet != null; + if (mCurrentDepSet != null) { + mCurrentDepSet.add(className); + } + } + + /** + * Considers this array of names using considerName(). + */ + public void considerNames(String[] classNames) { + if (classNames != null) { + for (String className : classNames) { + considerName(className); + } + } + } + + /** + * Considers this signature or type signature by invoking the {@link SignatureVisitor} + * on it. + */ + public void considerSignature(String signature) { + if (signature != null) { + SignatureReader sr = new SignatureReader(signature); + // SignatureReader.accept will call accessType so we don't really have + // to differentiate where the signature comes from. + sr.accept(new MySignatureVisitor()); + } + } + + /** + * Considers this {@link Type}. For arrays, the element type is considered. + * If the type is an object, it's internal name is considered. + */ + public void considerType(Type t) { + if (t != null) { + if (t.getSort() == Type.ARRAY) { + t = t.getElementType(); + } + if (t.getSort() == Type.OBJECT) { + considerName(t.getInternalName()); + } + } + } + + /** + * Considers a descriptor string. The descriptor is converted to a {@link Type} + * and then considerType() is invoked. + */ + public boolean considerDesc(String desc) { + if (desc != null) { + try { + if (desc.length() > 0 && desc.charAt(0) == '(') { + // This is a method descriptor with arguments and a return type. + Type t = Type.getReturnType(desc); + considerType(t); + + for (Type arg : Type.getArgumentTypes(desc)) { + considerType(arg); + } + + } else { + Type t = Type.getType(desc); + considerType(t); + } + return true; + } catch (ArrayIndexOutOfBoundsException e) { + // ignore, not a valid type. + } + } + return false; + } + + + // --------------------------------------------------- + // --- ClassVisitor, FieldVisitor + // --------------------------------------------------- + + // Visits a class header + @Override + public void visit(int version, int access, String name, + String signature, String superName, String[] interfaces) { + // signature is the signature of this class. May be null if the class is not a generic + // one, and does not extend or implement generic classes or interfaces. + + if (signature != null) { + considerSignature(signature); + } + + // superName is the internal of name of the super class (see getInternalName). + // For interfaces, the super class is Object. May be null but only for the Object class. + considerName(superName); + + // interfaces is the internal names of the class's interfaces (see getInternalName). + // May be null. + considerNames(interfaces); + } + + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // desc is the class descriptor of the annotation class. + considerDesc(desc); + return new MyAnnotationVisitor(); + } + + @Override + public void visitAttribute(Attribute attr) { + // pass + } + + // Visits the end of a class + @Override + public void visitEnd() { + // pass + } + + private class MyFieldVisitor extends FieldVisitor { + + public MyFieldVisitor() { + super(Opcodes.ASM4); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // desc is the class descriptor of the annotation class. + considerDesc(desc); + return new MyAnnotationVisitor(); + } + + @Override + public void visitAttribute(Attribute attr) { + // pass + } + + // Visits the end of a class + @Override + public void visitEnd() { + // pass + } + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + // desc is the field's descriptor (see Type). + considerDesc(desc); + + // signature is the field's signature. May be null if the field's type does not use + // generic types. + considerSignature(signature); + + return new MyFieldVisitor(); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // name is the internal name of an inner class (see getInternalName). + // Note: outerName/innerName seems to be null when we're reading the + // _Original_ClassName classes generated by layoutlib_create. + if (outerName != null) { + considerName(name); + } + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + // desc is the method's descriptor (see Type). + considerDesc(desc); + // signature is the method's signature. May be null if the method parameters, return + // type and exceptions do not use generic types. + considerSignature(signature); + + return new MyMethodVisitor(); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + // pass + } + + @Override + public void visitSource(String source, String debug) { + // pass + } + + + // --------------------------------------------------- + // --- MethodVisitor + // --------------------------------------------------- + + private class MyMethodVisitor extends MethodVisitor { + + public MyMethodVisitor() { + super(Opcodes.ASM4); + } + + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return new MyAnnotationVisitor(); + } + + @Override + public void visitCode() { + // pass + } + + // field instruction + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + // name is the field's name. + // desc is the field's descriptor (see Type). + considerDesc(desc); + } + + @Override + public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { + // pass + } + + @Override + public void visitIincInsn(int var, int increment) { + // pass -- an IINC instruction + } + + @Override + public void visitInsn(int opcode) { + // pass -- a zero operand instruction + } + + @Override + public void visitIntInsn(int opcode, int operand) { + // pass -- a single int operand instruction + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + // pass -- a jump instruction + } + + @Override + public void visitLabel(Label label) { + // pass -- a label target + } + + // instruction to load a constant from the stack + @Override + public void visitLdcInsn(Object cst) { + if (cst instanceof Type) { + considerType((Type) cst); + } + } + + @Override + public void visitLineNumber(int line, Label start) { + // pass + } + + @Override + public void visitLocalVariable(String name, String desc, + String signature, Label start, Label end, int index) { + // desc is the type descriptor of this local variable. + considerDesc(desc); + // signature is the type signature of this local variable. May be null if the local + // variable type does not use generic types. + considerSignature(signature); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + // pass -- a lookup switch instruction + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + // pass + } + + // instruction that invokes a method + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + + // owner is the internal name of the method's owner class + if (!considerDesc(owner) && owner.indexOf('/') != -1) { + considerName(owner); + } + // desc is the method's descriptor (see Type). + considerDesc(desc); + } + + // instruction multianewarray, whatever that is + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + + // desc an array type descriptor. + considerDesc(desc); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, + boolean visible) { + // desc is the class descriptor of the annotation class. + considerDesc(desc); + return new MyAnnotationVisitor(); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + // pass -- table switch instruction + + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + // type is the internal name of the type of exceptions handled by the handler, + // or null to catch any exceptions (for "finally" blocks). + considerName(type); + } + + // type instruction + @Override + public void visitTypeInsn(int opcode, String type) { + // type is the operand of the instruction to be visited. This operand must be the + // internal name of an object or array class. + considerName(type); + } + + @Override + public void visitVarInsn(int opcode, int var) { + // pass -- local variable instruction + } + } + + private class MySignatureVisitor extends SignatureVisitor { + + public MySignatureVisitor() { + super(Opcodes.ASM4); + } + + // --------------------------------------------------- + // --- SignatureVisitor + // --------------------------------------------------- + + private String mCurrentSignatureClass = null; + + // Starts the visit of a signature corresponding to a class or interface type + @Override + public void visitClassType(String name) { + mCurrentSignatureClass = name; + considerName(name); + } + + // Visits an inner class + @Override + public void visitInnerClassType(String name) { + if (mCurrentSignatureClass != null) { + mCurrentSignatureClass += "$" + name; + considerName(mCurrentSignatureClass); + } + } + + @Override + public SignatureVisitor visitArrayType() { + return new MySignatureVisitor(); + } + + @Override + public void visitBaseType(char descriptor) { + // pass -- a primitive type, ignored + } + + @Override + public SignatureVisitor visitClassBound() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitExceptionType() { + return new MySignatureVisitor(); + } + + @Override + public void visitFormalTypeParameter(String name) { + // pass + } + + @Override + public SignatureVisitor visitInterface() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitInterfaceBound() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitParameterType() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitReturnType() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitSuperclass() { + return new MySignatureVisitor(); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return new MySignatureVisitor(); + } + + @Override + public void visitTypeVariable(String name) { + // pass + } + + @Override + public void visitTypeArgument() { + // pass + } + } + + + // --------------------------------------------------- + // --- AnnotationVisitor + // --------------------------------------------------- + + private class MyAnnotationVisitor extends AnnotationVisitor { + + public MyAnnotationVisitor() { + super(Opcodes.ASM4); + } + + // Visits a primitive value of an annotation + @Override + public void visit(String name, Object value) { + // value is the actual value, whose type must be Byte, Boolean, Character, Short, + // Integer, Long, Float, Double, String or Type + if (value instanceof Type) { + considerType((Type) value); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + // desc is the class descriptor of the nested annotation class. + considerDesc(desc); + return new MyAnnotationVisitor(); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new MyAnnotationVisitor(); + } + + @Override + public void visitEnum(String name, String desc, String value) { + // desc is the class descriptor of the enumeration class. + considerDesc(desc); + } + } + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java index 8efd871..c3ba591 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java @@ -33,11 +33,19 @@ public class Log { } } + /** Similar to debug() but doesn't do a \n automatically. */ + public void debugNoln(String format, Object... args) { + if (mVerbose) { + String s = String.format(format, args); + System.out.print(s); + } + } + public void info(String format, Object... args) { String s = String.format(format, args); outPrintln(s); } - + public void error(String format, Object... args) { String s = String.format(format, args); errPrintln(s); @@ -50,15 +58,15 @@ public class Log { pw.flush(); error(format + "\n" + sw.toString(), args); } - + /** for unit testing */ protected void errPrintln(String msg) { System.err.println(msg); } - + /** for unit testing */ protected void outPrintln(String msg) { System.out.println(msg); } - + } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index 4b7a348..9cd74db 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -18,6 +18,8 @@ package com.android.tools.layoutlib.create; import java.io.IOException; import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; @@ -47,6 +49,8 @@ public class Main { public static class Options { public boolean generatePublicAccess = true; + public boolean listAllDeps = false; + public boolean listOnlyMissingDeps = false; } public static final Options sOptions = new Options(); @@ -60,16 +64,29 @@ public class Main { if (!processArgs(log, args, osJarPath, osDestJar)) { log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ..."); + log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ..."); System.exit(1); } - log.info("Output: %1$s", osDestJar[0]); + if (sOptions.listAllDeps || sOptions.listOnlyMissingDeps) { + System.exit(listDeps(osJarPath, log)); + + } else { + System.exit(createLayoutLib(osDestJar[0], osJarPath, log)); + } + + + System.exit(1); + } + + private static int createLayoutLib(String osDestJar, ArrayList<String> osJarPath, Log log) { + log.info("Output: %1$s", osDestJar); for (String path : osJarPath) { log.info("Input : %1$s", path); } try { - AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo()); + AsmGenerator agen = new AsmGenerator(log, osDestJar, new CreateInfo()); AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen, new String[] { // derived from @@ -116,17 +133,33 @@ public class Main { for (String path : osJarPath) { log.info("- Input JAR : %1$s", path); } - System.exit(1); + return 1; } - System.exit(0); + return 0; } catch (IOException e) { log.exception(e, "Failed to load jar"); } catch (LogAbortException e) { e.error(log); } - System.exit(1); + return 1; + } + + private static int listDeps(ArrayList<String> osJarPath, Log log) { + DependencyFinder df = new DependencyFinder(log); + try { + List<Map<String, Set<String>>> result = df.findDeps(osJarPath); + if (sOptions.listAllDeps) { + df.printAllDeps(result); + } else if (sOptions.listOnlyMissingDeps) { + df.printMissingDeps(result); + } + } catch (IOException e) { + log.exception(e, "Failed to load jar"); + } + + return 0; } /** @@ -138,14 +171,21 @@ public class Main { */ private static boolean processArgs(Log log, String[] args, ArrayList<String> osJarPath, String[] osDestJar) { + boolean needs_dest = true; for (int i = 0; i < args.length; i++) { String s = args[i]; if (s.equals("-v")) { log.setVerbose(true); } else if (s.equals("-p")) { sOptions.generatePublicAccess = false; + } else if (s.equals("--list-deps")) { + sOptions.listAllDeps = true; + needs_dest = false; + } else if (s.equals("--missing-deps")) { + sOptions.listOnlyMissingDeps = true; + needs_dest = false; } else if (!s.startsWith("-")) { - if (osDestJar[0] == null) { + if (needs_dest && osDestJar[0] == null) { osDestJar[0] = s; } else { osJarPath.add(s); @@ -160,7 +200,7 @@ public class Main { log.error("Missing parameter: path to input jar"); return false; } - if (osDestJar[0] == null) { + if (needs_dest && osDestJar[0] == null) { log.error("Missing parameter: path to output jar"); return false; } |