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