/* * Copyright 2000-2012 JetBrains s.r.o. * * 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 org.jetbrains.android.compiler.tools; import com.intellij.openapi.util.io.FileUtilRt; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @author Eugene.Kudelevsky */ @SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace", "SSBasedInspection"}) public class AndroidDxRunner { @NonNls private final static String DEX_MAIN = "com.android.dx.command.dexer.Main"; @NonNls private final static String DEX_CONSOLE = "com.android.dx.command.DxConsole"; @NonNls private final static String DEX_ARGS = "com.android.dx.command.dexer.Main$Arguments"; @NonNls private final static String MAIN_RUN = "run"; private static Method myMethod; private static Constructor<?> myConstructor; private static Field myOutNameField; private static Field myVerboseField; private static Field myForceJumboField; private static Field myCoreLibraryField; private static Field myJarOutputField; private static Field myFileNamesField; private static Field myStrictNameCheckField; private static Field myOptimizeField; private static Field myConsoleOut; private static Field myConsoleErr; private AndroidDxRunner() { } private static void loadDex(String dxPath) { try { File f = new File(dxPath); if (!f.isFile()) { System.err.println("File not found: " + dxPath); return; } URL url = f.toURI().toURL(); URLClassLoader loader = new URLClassLoader(new URL[]{url}, AndroidDxRunner.class.getClassLoader()); Class<?> mainClass = loader.loadClass(DEX_MAIN); Class<?> consoleClass = loader.loadClass(DEX_CONSOLE); Class<?> argClass = loader.loadClass(DEX_ARGS); myMethod = mainClass.getMethod(MAIN_RUN, argClass); myConstructor = argClass.getConstructor(); myOutNameField = argClass.getField("outName"); myJarOutputField = argClass.getField("jarOutput"); myFileNamesField = argClass.getField("fileNames"); myVerboseField = argClass.getField("verbose"); myStrictNameCheckField = argClass.getField("strictNameCheck"); myForceJumboField = getFieldIfPossible(argClass, "forceJumbo"); myCoreLibraryField = getFieldIfPossible(argClass, "coreLibrary"); myOptimizeField = getFieldIfPossible(argClass, "optimize"); myConsoleOut = consoleClass.getField("out"); myConsoleErr = consoleClass.getField("err"); } catch (SecurityException e) { reportError("Unable to find API for dex.jar", e); } catch (NoSuchMethodException e) { reportError("Unable to find method for dex.jar", e); } catch (NoSuchFieldException e) { reportError("Unable to find field for dex.jar", e); } catch (MalformedURLException e) { reportError("Failed to load dx.jar", e); } catch (ClassNotFoundException e) { reportError("Failed to load dx.jar", e); } } @Nullable private static Field getFieldIfPossible(Class<?> argClass, String name) { try { return argClass.getField(name); } catch (NoSuchFieldException e) { return null; } } private static int runDex(String dxPath, String outFilePath, String[] fileNames, boolean optimize, boolean forceJumbo, boolean coreLibrary) { loadDex(dxPath); try { myConsoleErr.set(null, System.err); myConsoleOut.set(null, System.out); Object args = myConstructor.newInstance(); myOutNameField.set(args, outFilePath); myFileNamesField.set(args, fileNames); myJarOutputField.set(args, FileUtilRt.extensionEquals(new File(outFilePath).getName(), "jar")); myVerboseField.set(args, false); myStrictNameCheckField.set(args, false); if (myOptimizeField != null) { myOptimizeField.set(args, optimize); } else { reportWarning("Cannot find 'optimize' field. The option won't be passed to DEX"); } if (myForceJumboField != null) { myForceJumboField.set(args, forceJumbo); } else { reportWarning("Cannot find 'forceJumbo' field. The option won't be passed to DEX"); } if (myCoreLibraryField != null) { myCoreLibraryField.set(args, coreLibrary); } else { reportWarning("Cannot find 'coreLibrary' field. The option won't be passed to DEX"); } Object res = myMethod.invoke(null, args); if (res instanceof Integer) { return ((Integer)res).intValue(); } } catch (IllegalAccessException e) { reportError("Unable to execute DX", e); } catch (InstantiationException e) { reportError("Unable to execute DX", e); } catch (InvocationTargetException e) { Throwable targetException = e.getTargetException(); reportError("Unable to execute DX", targetException != null ? targetException : e); } return -1; } private static void reportError(String message, Throwable t) { System.err.println(message); t.printStackTrace(); } private static void reportWarning(String message) { System.err.println("warning: " + message); } private static void collectFiles(File root, Collection<String> result, Set<String> visited, Set<String> qNames) throws IOException { collectFiles(root.getParentFile(), root, result, visited, qNames); } private static void collectFiles(File root, File file, Collection<String> result, Set<String> visited, Set<String> qNames) throws IOException { String path = file.getCanonicalPath(); if (!visited.add(path)) { return; } if (file.isDirectory()) { final File[] children = file.listFiles(); if (children != null) { for (File child : children) { collectFiles(root, child, result, visited, qNames); } } } else { if (FileUtilRt.extensionEquals(file.getName(), "class")) { final String qName = getQualifiedName(root, file); if (qName != null && !qNames.add(qName)) { reportWarning(FileUtilRt.toSystemDependentName(file.getPath()) + " won't be added. Class " + qName + " already exists in classpath"); return; } } result.add(path); } } @Nullable private static String getQualifiedName(File root, File classFile) { String relativePath = FileUtilRt.getRelativePath(root, classFile); if (relativePath == null) { return null; } return FileUtilRt.getNameWithoutExtension(FileUtilRt.toSystemIndependentName(relativePath)).replace('/', '.'); } public static void main(String[] args) { if (args.length == 0) { System.err.println("Error: dx path must be passed as first argument"); } String dxPath = args[0]; if (args.length == 1) { System.err.println("Error: out file path must be passed as second argument"); } String outFilePath = args[1]; if (args.length == 2) { System.err.println("Error: no files"); } Set<String> files = new HashSet<String>(); HashSet<String> visited = new HashSet<String>(); HashSet<String> qNames = new HashSet<String>(); boolean optimize = true; boolean forceJumbo = false; boolean coreLibrary = false; int i = 2; while (i < args.length && args[i].startsWith("--")) { if ("--optimize".equals(args[i])) { i++; if (i < args.length) { optimize = Boolean.parseBoolean(args[i]); } } else if ("--forceJumbo".equals(args[i])) { i++; if (i < args.length) { forceJumbo = Boolean.parseBoolean(args[i]); } } else if ("--coreLibrary".equals(args[i])) { coreLibrary = true; } i++; } while (i < args.length) { String arg = args[i]; if ("--exclude".equals(arg)) { break; } File file = new File(arg); if (file.exists()) { try { collectFiles(file, files, visited, qNames); } catch (IOException e) { reportError("I/O error", e); } } i++; } String[] excludedFiles = new String[args.length - i - 1]; System.arraycopy(args, i + 1, excludedFiles, 0, excludedFiles.length); files.removeAll(Arrays.asList(excludedFiles)); String[] filesArray = files.toArray(new String[files.size()]); //System.out.println("file names: " + concat(filesArray)); runDex(dxPath, outFilePath, filesArray, optimize, forceJumbo, coreLibrary); } private static String concat(String[] ar) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < ar.length; i++) { builder.append('"').append(ar[i]).append('"'); if (i < ar.length - 1) { builder.append(", "); } } return builder.toString(); } }