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