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