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