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