Home | History | Annotate | Download | only in build
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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 
     17 package com.android.ide.eclipse.adt.internal.build;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.NonNull;
     21 import com.android.annotations.Nullable;
     22 import com.android.ide.eclipse.adt.AdtConstants;
     23 import com.android.ide.eclipse.adt.AdtPlugin;
     24 import com.android.ide.eclipse.adt.AndroidPrintStream;
     25 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
     27 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     29 import com.android.prefs.AndroidLocation.AndroidLocationException;
     30 import com.android.sdklib.BuildToolInfo;
     31 import com.android.sdklib.IAndroidTarget;
     32 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
     33 import com.android.sdklib.build.ApkBuilder;
     34 import com.android.sdklib.build.ApkBuilder.JarStatus;
     35 import com.android.sdklib.build.ApkBuilder.SigningInfo;
     36 import com.android.sdklib.build.ApkCreationException;
     37 import com.android.sdklib.build.DuplicateFileException;
     38 import com.android.sdklib.build.SealedApkException;
     39 import com.android.sdklib.internal.build.DebugKeyProvider;
     40 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
     41 import com.android.sdklib.util.GrabProcessOutput;
     42 import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
     43 import com.android.sdklib.util.GrabProcessOutput.Wait;
     44 import com.google.common.hash.HashCode;
     45 import com.google.common.hash.HashFunction;
     46 import com.google.common.hash.Hashing;
     47 
     48 import org.eclipse.core.resources.IFile;
     49 import org.eclipse.core.resources.IFolder;
     50 import org.eclipse.core.resources.IProject;
     51 import org.eclipse.core.resources.IResource;
     52 import org.eclipse.core.resources.IWorkspaceRoot;
     53 import org.eclipse.core.resources.ResourcesPlugin;
     54 import org.eclipse.core.runtime.CoreException;
     55 import org.eclipse.core.runtime.IPath;
     56 import org.eclipse.core.runtime.IStatus;
     57 import org.eclipse.core.runtime.Status;
     58 import org.eclipse.jdt.core.IClasspathContainer;
     59 import org.eclipse.jdt.core.IClasspathEntry;
     60 import org.eclipse.jdt.core.IJavaProject;
     61 import org.eclipse.jdt.core.JavaCore;
     62 import org.eclipse.jdt.core.JavaModelException;
     63 import org.eclipse.jface.preference.IPreferenceStore;
     64 
     65 import java.io.File;
     66 import java.io.FileWriter;
     67 import java.io.IOException;
     68 import java.io.PrintStream;
     69 import java.security.PrivateKey;
     70 import java.security.cert.X509Certificate;
     71 import java.util.ArrayList;
     72 import java.util.Collection;
     73 import java.util.Collections;
     74 import java.util.HashSet;
     75 import java.util.List;
     76 import java.util.Map;
     77 import java.util.Set;
     78 import java.util.TreeMap;
     79 
     80 /**
     81  * Helper with methods for the last 3 steps of the generation of an APK.
     82  *
     83  * {@link #packageResources(IFile, IProject[], String, int, String, String)} packages the
     84  * application resources using aapt into a zip file that is ready to be integrated into the apk.
     85  *
     86  * {@link #executeDx(IJavaProject, String, String, IJavaProject[])} will convert the Java byte
     87  * code into the Dalvik bytecode.
     88  *
     89  * {@link #finalPackage(String, String, String, boolean, IJavaProject, IProject[], IJavaProject[], String, boolean)}
     90  * will make the apk from all the previous components.
     91  *
     92  * This class only executes the 3 above actions. It does not handle the errors, and simply sends
     93  * them back as custom exceptions.
     94  *
     95  * Warnings are handled by the {@link ResourceMarker} interface.
     96  *
     97  * Console output (verbose and non verbose) is handled through the {@link AndroidPrintStream} passed
     98  * to the constructor.
     99  *
    100  */
    101 public class BuildHelper {
    102 
    103     private static final String CONSOLE_PREFIX_DX = "Dx";   //$NON-NLS-1$
    104     private final static String TEMP_PREFIX = "android_";   //$NON-NLS-1$
    105 
    106     private static final String COMMAND_CRUNCH = "crunch";  //$NON-NLS-1$
    107     private static final String COMMAND_PACKAGE = "package"; //$NON-NLS-1$
    108 
    109     @NonNull
    110     private final IProject mProject;
    111     @NonNull
    112     private final BuildToolInfo mBuildToolInfo;
    113     @NonNull
    114     private final AndroidPrintStream mOutStream;
    115     @NonNull
    116     private final AndroidPrintStream mErrStream;
    117     private final boolean mForceJumbo;
    118     private final boolean mDisableDexMerger;
    119     private final boolean mVerbose;
    120     private final boolean mDebugMode;
    121 
    122     private final Set<String> mCompiledCodePaths = new HashSet<String>();
    123 
    124     public static final boolean BENCHMARK_FLAG = false;
    125     public static long sStartOverallTime = 0;
    126     public static long sStartJavaCTime = 0;
    127 
    128     private final static int MILLION = 1000000;
    129     private String mProguardFile;
    130 
    131     /**
    132      * An object able to put a marker on a resource.
    133      */
    134     public interface ResourceMarker {
    135         void setWarning(IResource resource, String message);
    136     }
    137 
    138     /**
    139      * Creates a new post-compiler helper
    140      * @param project
    141      * @param outStream
    142      * @param errStream
    143      * @param debugMode whether this is a debug build
    144      * @param verbose
    145      * @throws CoreException
    146      */
    147     public BuildHelper(@NonNull IProject project,
    148             @NonNull BuildToolInfo buildToolInfo,
    149             @NonNull AndroidPrintStream outStream,
    150             @NonNull AndroidPrintStream errStream,
    151             boolean forceJumbo, boolean disableDexMerger, boolean debugMode,
    152             boolean verbose, ResourceMarker resMarker) throws CoreException {
    153         mProject = project;
    154         mBuildToolInfo = buildToolInfo;
    155         mOutStream = outStream;
    156         mErrStream = errStream;
    157         mDebugMode = debugMode;
    158         mVerbose = verbose;
    159         mForceJumbo = forceJumbo;
    160         mDisableDexMerger = disableDexMerger;
    161 
    162         gatherPaths(resMarker);
    163     }
    164 
    165     public void updateCrunchCache() throws AaptExecException, AaptResultException {
    166         // Benchmarking start
    167         long startCrunchTime = 0;
    168         if (BENCHMARK_FLAG) {
    169             String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)"; //$NON-NLS-1$
    170             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    171             startCrunchTime = System.nanoTime();
    172         }
    173 
    174         // Get the resources folder to crunch from
    175         IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
    176         List<String> resPaths = new ArrayList<String>();
    177         resPaths.add(resFolder.getLocation().toOSString());
    178 
    179         // Get the output folder where the cache is stored.
    180         IFolder cacheFolder = mProject.getFolder(AdtConstants.WS_CRUNCHCACHE);
    181         String cachePath = cacheFolder.getLocation().toOSString();
    182 
    183         /* For crunching, we don't need the osManifestPath, osAssetsPath, or the configFilter
    184          * parameters for executeAapt
    185          */
    186         executeAapt(COMMAND_CRUNCH, "", resPaths, "", cachePath, "", 0);
    187 
    188         // Benchmarking end
    189         if (BENCHMARK_FLAG) {
    190             String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
    191                             + ((System.nanoTime() - startCrunchTime)/MILLION) + "ms";     //$NON-NLS-1$
    192             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    193         }
    194     }
    195 
    196     /**
    197      * Packages the resources of the projet into a .ap_ file.
    198      * @param manifestFile the manifest of the project.
    199      * @param libProjects the list of library projects that this project depends on.
    200      * @param resFilter an optional resource filter to be used with the -c option of aapt. If null
    201      * no filters are used.
    202      * @param versionCode an optional versionCode to be inserted in the manifest during packaging.
    203      * If the value is <=0, no values are inserted.
    204      * @param outputFolder where to write the resource ap_ file.
    205      * @param outputFilename the name of the resource ap_ file.
    206      * @throws AaptExecException
    207      * @throws AaptResultException
    208      */
    209     public void packageResources(IFile manifestFile, List<IProject> libProjects, String resFilter,
    210             int versionCode, String outputFolder, String outputFilename)
    211             throws AaptExecException, AaptResultException {
    212 
    213         // Benchmarking start
    214         long startPackageTime = 0;
    215         if (BENCHMARK_FLAG) {
    216             String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)";    //$NON-NLS-1$
    217             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    218             startPackageTime = System.nanoTime();
    219         }
    220 
    221         // need to figure out some path before we can execute aapt;
    222 
    223         // get the cache folder
    224         IFolder cacheFolder = mProject.getFolder(AdtConstants.WS_CRUNCHCACHE);
    225 
    226         // get the resource folder
    227         IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
    228 
    229         // and the assets folder
    230         IFolder assetsFolder = mProject.getFolder(AdtConstants.WS_ASSETS);
    231 
    232         // we need to make sure this one exists.
    233         if (assetsFolder.exists() == false) {
    234             assetsFolder = null;
    235         }
    236 
    237         // list of res folder (main project + maybe libraries)
    238         ArrayList<String> osResPaths = new ArrayList<String>();
    239 
    240         IPath resLocation = resFolder.getLocation();
    241         IPath manifestLocation = manifestFile.getLocation();
    242 
    243         if (resLocation != null && manifestLocation != null) {
    244 
    245             // png cache folder first.
    246             addFolderToList(osResPaths, cacheFolder);
    247 
    248             // regular res folder next.
    249             osResPaths.add(resLocation.toOSString());
    250 
    251             // then libraries
    252             if (libProjects != null) {
    253                 for (IProject lib : libProjects) {
    254                     // png cache folder first
    255                     IFolder libCacheFolder = lib.getFolder(AdtConstants.WS_CRUNCHCACHE);
    256                     addFolderToList(osResPaths, libCacheFolder);
    257 
    258                     // regular res folder next.
    259                     IFolder libResFolder = lib.getFolder(AdtConstants.WS_RESOURCES);
    260                     addFolderToList(osResPaths, libResFolder);
    261                 }
    262             }
    263 
    264             String osManifestPath = manifestLocation.toOSString();
    265 
    266             String osAssetsPath = null;
    267             if (assetsFolder != null) {
    268                 osAssetsPath = assetsFolder.getLocation().toOSString();
    269             }
    270 
    271             // build the default resource package
    272             executeAapt(COMMAND_PACKAGE, osManifestPath, osResPaths, osAssetsPath,
    273                     outputFolder + File.separator + outputFilename, resFilter,
    274                     versionCode);
    275         }
    276 
    277         // Benchmarking end
    278         if (BENCHMARK_FLAG) {
    279             String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
    280                             + ((System.nanoTime() - startPackageTime)/MILLION) + "ms";    //$NON-NLS-1$
    281             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    282         }
    283     }
    284 
    285     /**
    286      * Adds os path of a folder to a list only if the folder actually exists.
    287      * @param pathList
    288      * @param folder
    289      */
    290     private void addFolderToList(List<String> pathList, IFolder folder) {
    291         // use a File instead of the IFolder API to ignore workspace refresh issue.
    292         File testFile = new File(folder.getLocation().toOSString());
    293         if (testFile.isDirectory()) {
    294             pathList.add(testFile.getAbsolutePath());
    295         }
    296     }
    297 
    298     /**
    299      * Makes a final package signed with the debug key.
    300      *
    301      * Packages the dex files, the temporary resource file into the final package file.
    302      *
    303      * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
    304      * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
    305      *
    306      * @param intermediateApk The path to the temporary resource file.
    307      * @param dex The path to the dex file.
    308      * @param output The path to the final package file to create.
    309      * @param libProjects an optional list of library projects (can be null)
    310      * @return true if success, false otherwise.
    311      * @throws ApkCreationException
    312      * @throws AndroidLocationException
    313      * @throws KeytoolException
    314      * @throws NativeLibInJarException
    315      * @throws CoreException
    316      * @throws DuplicateFileException
    317      */
    318     public void finalDebugPackage(String intermediateApk, String dex, String output,
    319             List<IProject> libProjects, ResourceMarker resMarker)
    320             throws ApkCreationException, KeytoolException, AndroidLocationException,
    321             NativeLibInJarException, DuplicateFileException, CoreException {
    322 
    323         AdtPlugin adt = AdtPlugin.getDefault();
    324         if (adt == null) {
    325             return;
    326         }
    327 
    328         // get the debug keystore to use.
    329         IPreferenceStore store = adt.getPreferenceStore();
    330         String keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
    331         if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) {
    332             keystoreOsPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
    333             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
    334                     Messages.ApkBuilder_Using_Default_Key);
    335         } else {
    336             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
    337                     String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath));
    338         }
    339 
    340         // from the keystore, get the signing info
    341         SigningInfo info = ApkBuilder.getDebugKey(keystoreOsPath, mVerbose ? mOutStream : null);
    342 
    343         finalPackage(intermediateApk, dex, output, libProjects,
    344                 info != null ? info.key : null, info != null ? info.certificate : null, resMarker);
    345     }
    346 
    347     /**
    348      * Makes the final package.
    349      *
    350      * Packages the dex files, the temporary resource file into the final package file.
    351      *
    352      * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
    353      * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
    354      *
    355      * @param intermediateApk The path to the temporary resource file.
    356      * @param dex The path to the dex file.
    357      * @param output The path to the final package file to create.
    358      * @param debugSign whether the apk must be signed with the debug key.
    359      * @param libProjects an optional list of library projects (can be null)
    360      * @param abiFilter an optional filter. If not null, then only the matching ABI is included in
    361      * the final archive
    362      * @return true if success, false otherwise.
    363      * @throws NativeLibInJarException
    364      * @throws ApkCreationException
    365      * @throws CoreException
    366      * @throws DuplicateFileException
    367      */
    368     public void finalPackage(String intermediateApk, String dex, String output,
    369             List<IProject> libProjects,
    370             PrivateKey key, X509Certificate certificate, ResourceMarker resMarker)
    371             throws NativeLibInJarException, ApkCreationException, DuplicateFileException,
    372             CoreException {
    373 
    374         try {
    375             ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex,
    376                     key, certificate,
    377                     mVerbose ? mOutStream: null);
    378             apkBuilder.setDebugMode(mDebugMode);
    379 
    380             // either use the full compiled code paths or just the proguard file
    381             // if present
    382             Collection<String> pathsCollection = mCompiledCodePaths;
    383             if (mProguardFile != null) {
    384                 pathsCollection = Collections.singletonList(mProguardFile);
    385                 mProguardFile = null;
    386             }
    387 
    388             // Now we write the standard resources from all the output paths.
    389             for (String path : pathsCollection) {
    390                 File file = new File(path);
    391                 if (file.isFile()) {
    392                     JarStatus jarStatus = apkBuilder.addResourcesFromJar(file);
    393 
    394                     // check if we found native libraries in the external library. This
    395                     // constitutes an error or warning depending on if they are in lib/
    396                     if (jarStatus.getNativeLibs().size() > 0) {
    397                         String libName = file.getName();
    398 
    399                         String msg = String.format(
    400                                 "Native libraries detected in '%1$s'. See console for more information.",
    401                                 libName);
    402 
    403                         ArrayList<String> consoleMsgs = new ArrayList<String>();
    404 
    405                         consoleMsgs.add(String.format(
    406                                 "The library '%1$s' contains native libraries that will not run on the device.",
    407                                 libName));
    408 
    409                         if (jarStatus.hasNativeLibsConflicts()) {
    410                             consoleMsgs.add("Additionally some of those libraries will interfer with the installation of the application because of their location in lib/");
    411                             consoleMsgs.add("lib/ is reserved for NDK libraries.");
    412                         }
    413 
    414                         consoleMsgs.add("The following libraries were found:");
    415 
    416                         for (String lib : jarStatus.getNativeLibs()) {
    417                             consoleMsgs.add(" - " + lib);
    418                         }
    419 
    420                         String[] consoleStrings = consoleMsgs.toArray(new String[consoleMsgs.size()]);
    421 
    422                         // if there's a conflict or if the prefs force error on any native code in jar
    423                         // files, throw an exception
    424                         if (jarStatus.hasNativeLibsConflicts() ||
    425                                 AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar()) {
    426                             throw new NativeLibInJarException(jarStatus, msg, libName, consoleStrings);
    427                         } else {
    428                             // otherwise, put a warning, and output to the console also.
    429                             if (resMarker != null) {
    430                                 resMarker.setWarning(mProject, msg);
    431                             }
    432 
    433                             for (String string : consoleStrings) {
    434                                 mOutStream.println(string);
    435                             }
    436                         }
    437                     }
    438                 } else if (file.isDirectory()) {
    439                     // this is technically not a source folder (class folder instead) but since we
    440                     // only care about Java resources (ie non class/java files) this will do the
    441                     // same
    442                     apkBuilder.addSourceFolder(file);
    443                 }
    444             }
    445 
    446             // now write the native libraries.
    447             // First look if the lib folder is there.
    448             IResource libFolder = mProject.findMember(SdkConstants.FD_NATIVE_LIBS);
    449             if (libFolder != null && libFolder.exists() &&
    450                     libFolder.getType() == IResource.FOLDER) {
    451                 // get a File for the folder.
    452                 apkBuilder.addNativeLibraries(libFolder.getLocation().toFile());
    453             }
    454 
    455             // write the native libraries for the library projects.
    456             if (libProjects != null) {
    457                 for (IProject lib : libProjects) {
    458                     libFolder = lib.findMember(SdkConstants.FD_NATIVE_LIBS);
    459                     if (libFolder != null && libFolder.exists() &&
    460                             libFolder.getType() == IResource.FOLDER) {
    461                         apkBuilder.addNativeLibraries(libFolder.getLocation().toFile());
    462                     }
    463                 }
    464             }
    465 
    466             // seal the APK.
    467             apkBuilder.sealApk();
    468         } catch (SealedApkException e) {
    469             // this won't happen as we control when the apk is sealed.
    470         }
    471     }
    472 
    473     public void setProguardOutput(String proguardFile) {
    474         mProguardFile = proguardFile;
    475     }
    476 
    477     public Collection<String> getCompiledCodePaths() {
    478         return mCompiledCodePaths;
    479     }
    480 
    481     public void runProguard(List<File> proguardConfigs, File inputJar, Collection<String> jarFiles,
    482                             File obfuscatedJar, File logOutput)
    483             throws ProguardResultException, ProguardExecException, IOException {
    484         IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
    485 
    486         // prepare the command line for proguard
    487         List<String> command = new ArrayList<String>();
    488         command.add(AdtPlugin.getOsAbsoluteProguard());
    489 
    490         for (File configFile : proguardConfigs) {
    491             command.add("-include"); //$NON-NLS-1$
    492             command.add(quotePath(configFile.getAbsolutePath()));
    493         }
    494 
    495         command.add("-injars"); //$NON-NLS-1$
    496         StringBuilder sb = new StringBuilder(quotePath(inputJar.getAbsolutePath()));
    497         for (String jarFile : jarFiles) {
    498             sb.append(File.pathSeparatorChar);
    499             sb.append(quotePath(jarFile));
    500         }
    501         command.add(quoteWinArg(sb.toString()));
    502 
    503         command.add("-outjars"); //$NON-NLS-1$
    504         command.add(quotePath(obfuscatedJar.getAbsolutePath()));
    505 
    506         command.add("-libraryjars"); //$NON-NLS-1$
    507         sb = new StringBuilder(quotePath(target.getPath(IAndroidTarget.ANDROID_JAR)));
    508         IOptionalLibrary[] libraries = target.getOptionalLibraries();
    509         if (libraries != null) {
    510             for (IOptionalLibrary lib : libraries) {
    511                 sb.append(File.pathSeparatorChar);
    512                 sb.append(quotePath(lib.getJarPath()));
    513             }
    514         }
    515         command.add(quoteWinArg(sb.toString()));
    516 
    517         if (logOutput != null) {
    518             if (logOutput.isDirectory() == false) {
    519                 logOutput.mkdirs();
    520             }
    521 
    522             command.add("-dump");                                              //$NON-NLS-1$
    523             command.add(new File(logOutput, "dump.txt").getAbsolutePath());    //$NON-NLS-1$
    524 
    525             command.add("-printseeds");                                        //$NON-NLS-1$
    526             command.add(new File(logOutput, "seeds.txt").getAbsolutePath());   //$NON-NLS-1$
    527 
    528             command.add("-printusage");                                        //$NON-NLS-1$
    529             command.add(new File(logOutput, "usage.txt").getAbsolutePath());   //$NON-NLS-1$
    530 
    531             command.add("-printmapping");                                      //$NON-NLS-1$
    532             command.add(new File(logOutput, "mapping.txt").getAbsolutePath()); //$NON-NLS-1$
    533         }
    534 
    535         String commandArray[] = null;
    536 
    537         if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
    538             commandArray = createWindowsProguardConfig(command);
    539         }
    540 
    541         if (commandArray == null) {
    542             // For Mac & Linux, use a regular command string array.
    543             commandArray = command.toArray(new String[command.size()]);
    544         }
    545 
    546         // Define PROGUARD_HOME to point to $SDK/tools/proguard if it's not yet defined.
    547         // The Mac/Linux proguard.sh can infer it correctly but not the proguard.bat one.
    548         String[] envp = null;
    549         Map<String, String> envMap = new TreeMap<String, String>(System.getenv());
    550         if (!envMap.containsKey("PROGUARD_HOME")) {                                    //$NON-NLS-1$
    551             envMap.put("PROGUARD_HOME",    Sdk.getCurrent().getSdkLocation() +         //$NON-NLS-1$
    552                                             SdkConstants.FD_TOOLS + File.separator +
    553                                             SdkConstants.FD_PROGUARD);
    554             envp = new String[envMap.size()];
    555             int i = 0;
    556             for (Map.Entry<String, String> entry : envMap.entrySet()) {
    557                 envp[i++] = String.format("%1$s=%2$s",                                 //$NON-NLS-1$
    558                                           entry.getKey(),
    559                                           entry.getValue());
    560             }
    561         }
    562 
    563         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
    564             sb = new StringBuilder();
    565             for (String c : commandArray) {
    566                 sb.append(c).append(' ');
    567             }
    568             AdtPlugin.printToConsole(mProject, sb.toString());
    569         }
    570 
    571         // launch
    572         int execError = 1;
    573         try {
    574             // launch the command line process
    575             Process process = Runtime.getRuntime().exec(commandArray, envp);
    576 
    577             // list to store each line of stderr
    578             ArrayList<String> results = new ArrayList<String>();
    579 
    580             // get the output and return code from the process
    581             execError = grabProcessOutput(mProject, process, results);
    582 
    583             if (mVerbose) {
    584                 for (String resultString : results) {
    585                     mOutStream.println(resultString);
    586                 }
    587             }
    588 
    589             if (execError != 0) {
    590                 throw new ProguardResultException(execError,
    591                         results.toArray(new String[results.size()]));
    592             }
    593 
    594         } catch (IOException e) {
    595             String msg = String.format(Messages.Proguard_Exec_Error, commandArray[0]);
    596             throw new ProguardExecException(msg, e);
    597         } catch (InterruptedException e) {
    598             String msg = String.format(Messages.Proguard_Exec_Error, commandArray[0]);
    599             throw new ProguardExecException(msg, e);
    600         }
    601     }
    602 
    603     /**
    604      * For tools R8 up to R11, the proguard.bat launcher on Windows only accepts
    605      * arguments %1..%9. Since we generally have about 15 arguments, we were working
    606      * around this by generating a temporary config file for proguard and then using
    607      * that.
    608      * Starting with tools R12, the proguard.bat launcher has been fixed to take
    609      * all arguments using %* so we no longer need this hack.
    610      *
    611      * @param command
    612      * @return
    613      * @throws IOException
    614      */
    615     private String[] createWindowsProguardConfig(List<String> command) throws IOException {
    616 
    617         // Arg 0 is the proguard.bat path and arg 1 is the user config file
    618         String launcher = AdtPlugin.readFile(new File(command.get(0)));
    619         if (launcher.contains("%*")) {                                      //$NON-NLS-1$
    620             // This is the launcher from Tools R12. Don't work around it.
    621             return null;
    622         }
    623 
    624         // On Windows, proguard.bat can only pass %1...%9 to the java -jar proguard.jar
    625         // call, but we have at least 15 arguments here so some get dropped silently
    626         // and quoting is a big issue. So instead we'll work around that by writing
    627         // all the arguments to a temporary config file.
    628 
    629         String[] commandArray = new String[3];
    630 
    631         commandArray[0] = command.get(0);
    632         commandArray[1] = command.get(1);
    633 
    634         // Write all the other arguments to a config file
    635         File argsFile = File.createTempFile(TEMP_PREFIX, ".pro");           //$NON-NLS-1$
    636         // TODO FIXME this may leave a lot of temp files around on a long session.
    637         // Should have a better way to clean up e.g. before each build.
    638         argsFile.deleteOnExit();
    639 
    640         FileWriter fw = new FileWriter(argsFile);
    641 
    642         for (int i = 2; i < command.size(); i++) {
    643             String s = command.get(i);
    644             fw.write(s);
    645             fw.write(s.startsWith("-") ? ' ' : '\n');                       //$NON-NLS-1$
    646         }
    647 
    648         fw.close();
    649 
    650         commandArray[2] = "@" + argsFile.getAbsolutePath();                 //$NON-NLS-1$
    651         return commandArray;
    652     }
    653 
    654     /**
    655      * Quotes a single path for proguard to deal with spaces.
    656      *
    657      * @param path The path to quote.
    658      * @return The original path if it doesn't contain a space.
    659      *   Or the original path surrounded by single quotes if it contains spaces.
    660      */
    661     private String quotePath(String path) {
    662         if (path.indexOf(' ') != -1) {
    663             path = '\'' + path + '\'';
    664         }
    665         return path;
    666     }
    667 
    668     /**
    669      * Quotes a compound proguard argument to deal with spaces.
    670      * <p/>
    671      * Proguard takes multi-path arguments such as "path1;path2" for some options.
    672      * When the {@link #quotePath} methods adds quotes for such a path if it contains spaces,
    673      * the proguard shell wrapper will absorb the quotes, so we need to quote around the
    674      * quotes.
    675      *
    676      * @param path The path to quote.
    677      * @return The original path if it doesn't contain a single quote.
    678      *   Or on Windows the original path surrounded by double quotes if it contains a quote.
    679      */
    680     private String quoteWinArg(String path) {
    681         if (path.indexOf('\'') != -1 &&
    682                 SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
    683             path = '"' + path + '"';
    684         }
    685         return path;
    686     }
    687 
    688 
    689     /**
    690      * Execute the Dx tool for dalvik code conversion.
    691      * @param javaProject The java project
    692      * @param inputPaths the input paths for DX
    693      * @param osOutFilePath the path of the dex file to create.
    694      *
    695      * @throws CoreException
    696      * @throws DexException
    697      */
    698     public void executeDx(IJavaProject javaProject, Collection<String> inputPaths,
    699             String osOutFilePath)
    700             throws CoreException, DexException {
    701 
    702         // get the dex wrapper
    703         Sdk sdk = Sdk.getCurrent();
    704         DexWrapper wrapper = sdk.getDexWrapper(mBuildToolInfo);
    705 
    706         if (wrapper == null) {
    707             throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    708                     Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
    709         }
    710 
    711         try {
    712             // set a temporary prefix on the print streams.
    713             mOutStream.setPrefix(CONSOLE_PREFIX_DX);
    714             mErrStream.setPrefix(CONSOLE_PREFIX_DX);
    715 
    716             IFolder binFolder = BaseProjectHelper.getAndroidOutputFolder(javaProject.getProject());
    717             File binFile = binFolder.getLocation().toFile();
    718             File dexedLibs = new File(binFile, "dexedLibs");
    719             if (dexedLibs.exists() == false) {
    720                 dexedLibs.mkdir();
    721             }
    722 
    723             // replace the libs by their dexed versions (dexing them if needed.)
    724             List<String> finalInputPaths = new ArrayList<String>(inputPaths.size());
    725             if (mDisableDexMerger || inputPaths.size() == 1) {
    726                 // only one input, no need to put a pre-dexed version, even if this path is
    727                 // just a jar file (case for proguard'ed builds)
    728                 finalInputPaths.addAll(inputPaths);
    729             } else {
    730 
    731                 for (String input : inputPaths) {
    732                     File inputFile = new File(input);
    733                     if (inputFile.isDirectory()) {
    734                         finalInputPaths.add(input);
    735                     } else if (inputFile.isFile()) {
    736                         String fileName = getDexFileName(inputFile);
    737 
    738                         File dexedLib = new File(dexedLibs, fileName);
    739                         String dexedLibPath = dexedLib.getAbsolutePath();
    740 
    741                         if (dexedLib.isFile() == false ||
    742                                 dexedLib.lastModified() < inputFile.lastModified()) {
    743 
    744                             if (mVerbose) {
    745                                 mOutStream.println(
    746                                         String.format("Pre-Dexing %1$s -> %2$s", input, fileName));
    747                             }
    748 
    749                             if (dexedLib.isFile()) {
    750                                 dexedLib.delete();
    751                             }
    752 
    753                             int res = wrapper.run(dexedLibPath, Collections.singleton(input),
    754                                     mForceJumbo, mVerbose, mOutStream, mErrStream);
    755 
    756                             if (res != 0) {
    757                                 // output error message and mark the project.
    758                                 String message = String.format(Messages.Dalvik_Error_d, res);
    759                                 throw new DexException(message);
    760                             }
    761                         } else {
    762                             if (mVerbose) {
    763                                 mOutStream.println(
    764                                         String.format("Using Pre-Dexed %1$s <- %2$s",
    765                                                 fileName, input));
    766                             }
    767                         }
    768 
    769                         finalInputPaths.add(dexedLibPath);
    770                     }
    771                 }
    772             }
    773 
    774             if (mVerbose) {
    775                 for (String input : finalInputPaths) {
    776                     mOutStream.println("Input: " + input);
    777                 }
    778             }
    779 
    780             int res = wrapper.run(osOutFilePath,
    781                     finalInputPaths,
    782                     mForceJumbo,
    783                     mVerbose,
    784                     mOutStream, mErrStream);
    785 
    786             mOutStream.setPrefix(null);
    787             mErrStream.setPrefix(null);
    788 
    789             if (res != 0) {
    790                 // output error message and marker the project.
    791                 String message = String.format(Messages.Dalvik_Error_d, res);
    792                 throw new DexException(message);
    793             }
    794         } catch (DexException e) {
    795             throw e;
    796         } catch (Throwable t) {
    797             String message = t.getMessage();
    798             if (message == null) {
    799                 message = t.getClass().getCanonicalName();
    800             }
    801             message = String.format(Messages.Dalvik_Error_s, message);
    802 
    803             throw new DexException(message, t);
    804         }
    805     }
    806 
    807     private String getDexFileName(File inputFile) {
    808         // get the filename
    809         String name = inputFile.getName();
    810         // remove the extension
    811         int pos = name.lastIndexOf('.');
    812         if (pos != -1) {
    813             name = name.substring(0, pos);
    814         }
    815 
    816         // add a hash of the original file path
    817         HashFunction hashFunction = Hashing.md5();
    818         HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath());
    819 
    820         return name + "-" + hashCode.toString() + ".jar";
    821     }
    822 
    823     /**
    824      * Executes aapt. If any error happen, files or the project will be marked.
    825      * @param command The command for aapt to execute. Currently supported: package and crunch
    826      * @param osManifestPath The path to the manifest file
    827      * @param osResPath The path to the res folder
    828      * @param osAssetsPath The path to the assets folder. This can be null.
    829      * @param osOutFilePath The path to the temporary resource file to create,
    830      *   or in the case of crunching the path to the cache to create/update.
    831      * @param configFilter The configuration filter for the resources to include
    832      * (used with -c option, for example "port,en,fr" to include portrait, English and French
    833      * resources.)
    834      * @param versionCode optional version code to insert in the manifest during packaging. If <=0
    835      * then no value is inserted
    836      * @throws AaptExecException
    837      * @throws AaptResultException
    838      */
    839     private void executeAapt(String aaptCommand, String osManifestPath,
    840             List<String> osResPaths, String osAssetsPath, String osOutFilePath,
    841             String configFilter, int versionCode) throws AaptExecException, AaptResultException {
    842         IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
    843 
    844         String aapt = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
    845 
    846         // Create the command line.
    847         ArrayList<String> commandArray = new ArrayList<String>();
    848         commandArray.add(aapt);
    849         commandArray.add(aaptCommand);
    850         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
    851             commandArray.add("-v"); //$NON-NLS-1$
    852         }
    853 
    854         // Common to all commands
    855         for (String path : osResPaths) {
    856             commandArray.add("-S"); //$NON-NLS-1$
    857             commandArray.add(path);
    858         }
    859 
    860         if (aaptCommand.equals(COMMAND_PACKAGE)) {
    861             commandArray.add("-f");          //$NON-NLS-1$
    862             commandArray.add("--no-crunch"); //$NON-NLS-1$
    863 
    864             // if more than one res, this means there's a library (or more) and we need
    865             // to activate the auto-add-overlay
    866             if (osResPaths.size() > 1) {
    867                 commandArray.add("--auto-add-overlay"); //$NON-NLS-1$
    868             }
    869 
    870             if (mDebugMode) {
    871                 commandArray.add("--debug-mode"); //$NON-NLS-1$
    872             }
    873 
    874             if (versionCode > 0) {
    875                 commandArray.add("--version-code"); //$NON-NLS-1$
    876                 commandArray.add(Integer.toString(versionCode));
    877             }
    878 
    879             if (configFilter != null) {
    880                 commandArray.add("-c"); //$NON-NLS-1$
    881                 commandArray.add(configFilter);
    882             }
    883 
    884             commandArray.add("-M"); //$NON-NLS-1$
    885             commandArray.add(osManifestPath);
    886 
    887             if (osAssetsPath != null) {
    888                 commandArray.add("-A"); //$NON-NLS-1$
    889                 commandArray.add(osAssetsPath);
    890             }
    891 
    892             commandArray.add("-I"); //$NON-NLS-1$
    893             commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
    894 
    895             commandArray.add("-F"); //$NON-NLS-1$
    896             commandArray.add(osOutFilePath);
    897         } else if (aaptCommand.equals(COMMAND_CRUNCH)) {
    898             commandArray.add("-C"); //$NON-NLS-1$
    899             commandArray.add(osOutFilePath);
    900         }
    901 
    902         String command[] = commandArray.toArray(
    903                 new String[commandArray.size()]);
    904 
    905         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
    906             StringBuilder sb = new StringBuilder();
    907             for (String c : command) {
    908                 sb.append(c);
    909                 sb.append(' ');
    910             }
    911             AdtPlugin.printToConsole(mProject, sb.toString());
    912         }
    913 
    914         // Benchmarking start
    915         long startAaptTime = 0;
    916         if (BENCHMARK_FLAG) {
    917             String msg = "BENCHMARK ADT: Starting " + aaptCommand  //$NON-NLS-1$
    918                          + " call to Aapt";                        //$NON-NLS-1$
    919             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    920             startAaptTime = System.nanoTime();
    921         }
    922 
    923         // launch
    924         try {
    925             // launch the command line process
    926             Process process = Runtime.getRuntime().exec(command);
    927 
    928             // list to store each line of stderr
    929             ArrayList<String> stdErr = new ArrayList<String>();
    930 
    931             // get the output and return code from the process
    932             int returnCode = grabProcessOutput(mProject, process, stdErr);
    933 
    934             if (mVerbose) {
    935                 for (String stdErrString : stdErr) {
    936                     mOutStream.println(stdErrString);
    937                 }
    938             }
    939             if (returnCode != 0) {
    940                 throw new AaptResultException(returnCode,
    941                         stdErr.toArray(new String[stdErr.size()]));
    942             }
    943         } catch (IOException e) {
    944             String msg = String.format(Messages.AAPT_Exec_Error_s, command[0]);
    945             throw new AaptExecException(msg, e);
    946         } catch (InterruptedException e) {
    947             String msg = String.format(Messages.AAPT_Exec_Error_s, command[0]);
    948             throw new AaptExecException(msg, e);
    949         }
    950 
    951         // Benchmarking end
    952         if (BENCHMARK_FLAG) {
    953             String msg = "BENCHMARK ADT: Ending " + aaptCommand                  //$NON-NLS-1$
    954                          + " call to Aapt.\nBENCHMARK ADT: Time Elapsed: "       //$NON-NLS-1$
    955                          + ((System.nanoTime() - startAaptTime)/MILLION) + "ms"; //$NON-NLS-1$
    956             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
    957         }
    958     }
    959 
    960     /**
    961      * Computes all the project output and dependencies that must go into building the apk.
    962      *
    963      * @param resMarker
    964      * @throws CoreException
    965      */
    966     private void gatherPaths(ResourceMarker resMarker)
    967             throws CoreException {
    968         IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
    969 
    970         // get a java project for the project.
    971         IJavaProject javaProject = JavaCore.create(mProject);
    972 
    973 
    974         // get the output of the main project
    975         IPath path = javaProject.getOutputLocation();
    976         IResource outputResource = wsRoot.findMember(path);
    977         if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
    978             mCompiledCodePaths.add(outputResource.getLocation().toOSString());
    979         }
    980 
    981         // we could use IJavaProject.getResolvedClasspath directly, but we actually
    982         // want to see the containers themselves.
    983         IClasspathEntry[] classpaths = javaProject.readRawClasspath();
    984         if (classpaths != null) {
    985             for (IClasspathEntry e : classpaths) {
    986                 // ignore non exported entries, unless they're in the DEPEDENCIES container,
    987                 // in which case we always want it (there may be some older projects that
    988                 // have it as non exported).
    989                 if (e.isExported() ||
    990                         (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
    991                          e.getPath().toString().equals(AdtConstants.CONTAINER_DEPENDENCIES))) {
    992                     handleCPE(e, javaProject, wsRoot, resMarker);
    993                 }
    994             }
    995         }
    996     }
    997 
    998     private void handleCPE(IClasspathEntry entry, IJavaProject javaProject,
    999             IWorkspaceRoot wsRoot, ResourceMarker resMarker) {
   1000 
   1001         // if this is a classpath variable reference, we resolve it.
   1002         if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
   1003             entry = JavaCore.getResolvedClasspathEntry(entry);
   1004         }
   1005 
   1006         if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
   1007             IProject refProject = wsRoot.getProject(entry.getPath().lastSegment());
   1008             try {
   1009                 // ignore if it's an Android project, or if it's not a Java Project
   1010                 if (refProject.hasNature(JavaCore.NATURE_ID) &&
   1011                         refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
   1012                     IJavaProject refJavaProject = JavaCore.create(refProject);
   1013 
   1014                     // get the output folder
   1015                     IPath path = refJavaProject.getOutputLocation();
   1016                     IResource outputResource = wsRoot.findMember(path);
   1017                     if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
   1018                         mCompiledCodePaths.add(outputResource.getLocation().toOSString());
   1019                     }
   1020                 }
   1021             } catch (CoreException exception) {
   1022                 // can't query the project nature? ignore
   1023             }
   1024 
   1025         } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
   1026             handleClasspathLibrary(entry, wsRoot, resMarker);
   1027         } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
   1028             // get the container
   1029             try {
   1030                 IClasspathContainer container = JavaCore.getClasspathContainer(
   1031                         entry.getPath(), javaProject);
   1032                 // ignore the system and default_system types as they represent
   1033                 // libraries that are part of the runtime.
   1034                 if (container != null && container.getKind() == IClasspathContainer.K_APPLICATION) {
   1035                     IClasspathEntry[] entries = container.getClasspathEntries();
   1036                     for (IClasspathEntry cpe : entries) {
   1037                         handleCPE(cpe, javaProject, wsRoot, resMarker);
   1038                     }
   1039                 }
   1040             } catch (JavaModelException jme) {
   1041                 // can't resolve the container? ignore it.
   1042                 AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath());
   1043             }
   1044         }
   1045     }
   1046 
   1047     private void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot,
   1048             ResourceMarker resMarker) {
   1049         // get the IPath
   1050         IPath path = e.getPath();
   1051 
   1052         IResource resource = wsRoot.findMember(path);
   1053 
   1054         if (resource != null && resource.getType() == IResource.PROJECT) {
   1055             // if it's a project we should just ignore it because it's going to be added
   1056             // later when we add all the referenced projects.
   1057 
   1058         } else if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
   1059             // case of a jar file (which could be relative to the workspace or a full path)
   1060             if (resource != null && resource.exists() &&
   1061                     resource.getType() == IResource.FILE) {
   1062                 mCompiledCodePaths.add(resource.getLocation().toOSString());
   1063             } else {
   1064                 // if the jar path doesn't match a workspace resource,
   1065                 // then we get an OSString and check if this links to a valid file.
   1066                 String osFullPath = path.toOSString();
   1067 
   1068                 File f = new File(osFullPath);
   1069                 if (f.isFile()) {
   1070                     mCompiledCodePaths.add(osFullPath);
   1071                 } else {
   1072                     String message = String.format( Messages.Couldnt_Locate_s_Error,
   1073                             path);
   1074                     // always output to the console
   1075                     mOutStream.println(message);
   1076 
   1077                     // put a marker
   1078                     if (resMarker != null) {
   1079                         resMarker.setWarning(mProject, message);
   1080                     }
   1081                 }
   1082             }
   1083         } else {
   1084             // this can be the case for a class folder.
   1085             if (resource != null && resource.exists() &&
   1086                     resource.getType() == IResource.FOLDER) {
   1087                 mCompiledCodePaths.add(resource.getLocation().toOSString());
   1088             } else {
   1089                 // if the path doesn't match a workspace resource,
   1090                 // then we get an OSString and check if this links to a valid folder.
   1091                 String osFullPath = path.toOSString();
   1092 
   1093                 File f = new File(osFullPath);
   1094                 if (f.isDirectory()) {
   1095                     mCompiledCodePaths.add(osFullPath);
   1096                 }
   1097             }
   1098         }
   1099     }
   1100 
   1101     /**
   1102      * Checks a {@link IFile} to make sure it should be packaged as standard resources.
   1103      * @param file the IFile representing the file.
   1104      * @return true if the file should be packaged as standard java resources.
   1105      */
   1106     public static boolean checkFileForPackaging(IFile file) {
   1107         String name = file.getName();
   1108 
   1109         String ext = file.getFileExtension();
   1110         return ApkBuilder.checkFileForPackaging(name, ext);
   1111     }
   1112 
   1113     /**
   1114      * Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as
   1115      * standard Java resource.
   1116      * @param folder the {@link IFolder} to check.
   1117      */
   1118     public static boolean checkFolderForPackaging(IFolder folder) {
   1119         String name = folder.getName();
   1120         return ApkBuilder.checkFolderForPackaging(name);
   1121     }
   1122 
   1123     /**
   1124      * Returns a list of {@link IJavaProject} matching the provided {@link IProject} objects.
   1125      * @param projects the IProject objects.
   1126      * @return a new list object containing the IJavaProject object for the given IProject objects.
   1127      * @throws CoreException
   1128      */
   1129     public static List<IJavaProject> getJavaProjects(List<IProject> projects) throws CoreException {
   1130         ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
   1131 
   1132         for (IProject p : projects) {
   1133             if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
   1134 
   1135                 list.add(JavaCore.create(p));
   1136             }
   1137         }
   1138 
   1139         return list;
   1140     }
   1141 
   1142     /**
   1143      * Get the stderr output of a process and return when the process is done.
   1144      * @param process The process to get the output from
   1145      * @param stderr The array to store the stderr output
   1146      * @return the process return code.
   1147      * @throws InterruptedException
   1148      */
   1149     public final static int grabProcessOutput(
   1150             final IProject project,
   1151             final Process process,
   1152             final ArrayList<String> stderr)
   1153             throws InterruptedException {
   1154 
   1155         return GrabProcessOutput.grabProcessOutput(
   1156                 process,
   1157                 Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output!
   1158                 new IProcessOutput() {
   1159 
   1160                     @SuppressWarnings("unused")
   1161                     @Override
   1162                     public void out(@Nullable String line) {
   1163                         if (line != null) {
   1164                             // If benchmarking always print the lines that
   1165                             // correspond to benchmarking info returned by ADT
   1166                             if (BENCHMARK_FLAG && line.startsWith("BENCHMARK:")) {    //$NON-NLS-1$
   1167                                 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS,
   1168                                         project, line);
   1169                             } else {
   1170                                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
   1171                                         project, line);
   1172                             }
   1173                         }
   1174                     }
   1175 
   1176                     @Override
   1177                     public void err(@Nullable String line) {
   1178                         if (line != null) {
   1179                             stderr.add(line);
   1180                             if (BuildVerbosity.VERBOSE == AdtPrefs.getPrefs().getBuildVerbosity()) {
   1181                                 AdtPlugin.printErrorToConsole(project, line);
   1182                             }
   1183                         }
   1184                     }
   1185                 });
   1186     }
   1187 }
   1188