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