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 {
setWarning(IResource resource, String message)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      */
BuildHelper(@onNull ProjectState projectState, @NonNull BuildToolInfo buildToolInfo, @NonNull AndroidPrintStream outStream, @NonNull AndroidPrintStream errStream, boolean forceJumbo, boolean disableDexMerger, boolean debugMode, boolean verbose, ResourceMarker resMarker)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 
updateCrunchCache()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      */
packageResources(IFile manifestFile, List<IProject> libProjects, String resFilter, int versionCode, String outputFolder, String outputFilename)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      */
addFolderToList(List<String> pathList, IFolder folder)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      */
finalDebugPackage(String intermediateApk, String dex, String output, List<IProject> libProjects, ResourceMarker resMarker)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      */
finalPackage(String intermediateApk, String dex, String output, List<IProject> libProjects, PrivateKey key, X509Certificate certificate, ResourceMarker resMarker)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 
setProguardOutput(String proguardFile)506     public void setProguardOutput(String proguardFile) {
507         mProguardFile = proguardFile;
508     }
509 
getCompiledCodePaths()510     public Collection<String> getCompiledCodePaths() {
511         return mCompiledCodePaths;
512     }
513 
runProguard(List<File> proguardConfigs, File inputJar, Collection<String> jarFiles, File obfuscatedJar, File logOutput)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      */
createWindowsProguardConfig(List<String> command)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      */
quotePath(String path)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      */
quoteWinArg(String path)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      */
executeDx(IJavaProject javaProject, Collection<String> inputPaths, String osOutFilePath)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 
getDexFileName(File inputFile)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      */
executeAapt(String aaptCommand, String osManifestPath, List<String> osResPaths, String osAssetsPath, String osOutFilePath, String configFilter, int versionCode)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      */
gatherPaths(ResourceMarker resMarker)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 
handleCPE(IClasspathEntry entry, IJavaProject javaProject, IWorkspaceRoot wsRoot, ResourceMarker resMarker)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 
handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot, ResourceMarker resMarker)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      */
checkFileForPackaging(IFile file)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      */
checkFolderForPackaging(IFolder folder)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      */
getJavaProjects(List<IProject> projects)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      */
grabProcessOutput( final IProject project, final Process process, final ArrayList<String> stderr)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