1/* 2 * Copyright (C) 2007 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 17package com.android.ide.eclipse.adt.internal.build.builders; 18 19import com.android.SdkConstants; 20import com.android.ide.eclipse.adt.AdtConstants; 21import com.android.ide.eclipse.adt.AdtPlugin; 22import com.android.ide.eclipse.adt.AndroidPrintStream; 23import com.android.ide.eclipse.adt.internal.build.AaptExecException; 24import com.android.ide.eclipse.adt.internal.build.AaptParser; 25import com.android.ide.eclipse.adt.internal.build.AaptResultException; 26import com.android.ide.eclipse.adt.internal.build.BuildHelper; 27import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker; 28import com.android.ide.eclipse.adt.internal.build.DexException; 29import com.android.ide.eclipse.adt.internal.build.Messages; 30import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException; 31import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor; 32import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 33import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 34import com.android.ide.eclipse.adt.internal.project.ApkInstallManager; 35import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 36import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer; 37import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 38import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 39import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 40import com.android.ide.eclipse.adt.internal.sdk.Sdk; 41import com.android.ide.eclipse.adt.io.IFileWrapper; 42import com.android.prefs.AndroidLocation.AndroidLocationException; 43import com.android.sdklib.build.ApkBuilder; 44import com.android.sdklib.build.ApkCreationException; 45import com.android.sdklib.build.DuplicateFileException; 46import com.android.sdklib.build.IArchiveBuilder; 47import com.android.sdklib.build.SealedApkException; 48import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; 49import com.android.xml.AndroidManifest; 50 51import org.eclipse.core.resources.IContainer; 52import org.eclipse.core.resources.IFile; 53import org.eclipse.core.resources.IFolder; 54import org.eclipse.core.resources.IMarker; 55import org.eclipse.core.resources.IProject; 56import org.eclipse.core.resources.IResource; 57import org.eclipse.core.resources.IResourceDelta; 58import org.eclipse.core.runtime.CoreException; 59import org.eclipse.core.runtime.IPath; 60import org.eclipse.core.runtime.IProgressMonitor; 61import org.eclipse.core.runtime.IStatus; 62import org.eclipse.jdt.core.IJavaModelMarker; 63import org.eclipse.jdt.core.IJavaProject; 64import org.eclipse.jdt.core.JavaCore; 65 66import java.io.File; 67import java.io.FileInputStream; 68import java.io.FileOutputStream; 69import java.io.IOException; 70import java.io.InputStream; 71import java.util.ArrayList; 72import java.util.Collection; 73import java.util.List; 74import java.util.Map; 75import java.util.jar.Attributes; 76import java.util.jar.JarEntry; 77import java.util.jar.JarOutputStream; 78import java.util.jar.Manifest; 79import java.util.regex.Pattern; 80 81public class PostCompilerBuilder extends BaseBuilder { 82 83 /** This ID is used in plugin.xml and in each project's .project file. 84 * It cannot be changed even if the class is renamed/moved */ 85 public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$ 86 87 private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$ 88 private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$ 89 private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$ 90 91 /** Flag to pass to PostCompiler builder that sets if it runs or not. 92 * Set this flag whenever calling build if PostCompiler is to run 93 */ 94 public final static String POST_C_REQUESTED = "RunPostCompiler"; //$NON-NLS-1$ 95 96 /** 97 * Dex conversion flag. This is set to true if one of the changed/added/removed 98 * file is a .class file. Upon visiting all the delta resource, if this 99 * flag is true, then we know we'll have to make the "classes.dex" file. 100 */ 101 private boolean mConvertToDex = false; 102 103 /** 104 * Package resources flag. This is set to true if one of the changed/added/removed 105 * file is a resource file. Upon visiting all the delta resource, if 106 * this flag is true, then we know we'll have to repackage the resources. 107 */ 108 private boolean mPackageResources = false; 109 110 /** 111 * Final package build flag. 112 */ 113 private boolean mBuildFinalPackage = false; 114 115 private AndroidPrintStream mOutStream = null; 116 private AndroidPrintStream mErrStream = null; 117 118 119 private ResourceMarker mResourceMarker = new ResourceMarker() { 120 @Override 121 public void setWarning(IResource resource, String message) { 122 BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING, 123 message, IMarker.SEVERITY_WARNING); 124 } 125 }; 126 127 128 public PostCompilerBuilder() { 129 super(); 130 } 131 132 @Override 133 protected void clean(IProgressMonitor monitor) throws CoreException { 134 super.clean(monitor); 135 136 // Get the project. 137 IProject project = getProject(); 138 139 if (DEBUG_LOG) { 140 AdtPlugin.log(IStatus.INFO, "%s CLEAN(POST)", project.getName()); 141 } 142 143 // Clear the project of the generic markers 144 removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); 145 removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING); 146 147 // also remove the files in the output folder (but not the Eclipse output folder). 148 IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project); 149 IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project); 150 151 if (javaOutput.equals(androidOutput) == false) { 152 // get the content 153 IResource[] members = androidOutput.members(); 154 for (IResource member : members) { 155 if (member.equals(javaOutput) == false) { 156 member.delete(true /*force*/, monitor); 157 } 158 } 159 } 160 } 161 162 // build() returns a list of project from which this project depends for future compilation. 163 @Override 164 protected IProject[] build( 165 int kind, 166 @SuppressWarnings("rawtypes") Map args, 167 IProgressMonitor monitor) 168 throws CoreException { 169 // get a project object 170 IProject project = getProject(); 171 172 if (DEBUG_LOG) { 173 AdtPlugin.log(IStatus.INFO, "%s BUILD(POST)", project.getName()); 174 } 175 176 // Benchmarking start 177 long startBuildTime = 0; 178 if (BuildHelper.BENCHMARK_FLAG) { 179 // End JavaC Timer 180 String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ 181 (System.nanoTime() - BuildHelper.sStartJavaCTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$ 182 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 183 msg = "BENCHMARK ADT: Starting PostCompilation"; //$NON-NLS-1$ 184 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 185 startBuildTime = System.nanoTime(); 186 } 187 188 // list of referenced projects. This is a mix of java projects and library projects 189 // and is computed below. 190 IProject[] allRefProjects = null; 191 192 try { 193 // get the project info 194 ProjectState projectState = Sdk.getProjectState(project); 195 196 // this can happen if the project has no project.properties. 197 if (projectState == null) { 198 return null; 199 } 200 201 boolean isLibrary = projectState.isLibrary(); 202 203 // get the libraries 204 List<IProject> libProjects = projectState.getFullLibraryProjects(); 205 206 IJavaProject javaProject = JavaCore.create(project); 207 208 // get the list of referenced projects. 209 List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project); 210 List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects( 211 javaProjects); 212 213 // mix the java project and the library projects 214 final int size = libProjects.size() + javaProjects.size(); 215 ArrayList<IProject> refList = new ArrayList<IProject>(size); 216 refList.addAll(libProjects); 217 refList.addAll(javaProjects); 218 allRefProjects = refList.toArray(new IProject[size]); 219 220 // get the android output folder 221 IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project); 222 IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES); 223 224 // First thing we do is go through the resource delta to not 225 // lose it if we have to abort the build for any reason. 226 if (args.containsKey(POST_C_REQUESTED) 227 && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { 228 // Skip over flag setting 229 } else if (kind == FULL_BUILD) { 230 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 231 Messages.Start_Full_Apk_Build); 232 233 if (DEBUG_LOG) { 234 AdtPlugin.log(IStatus.INFO, "%s full build!", project.getName()); 235 } 236 237 // Full build: we do all the steps. 238 mPackageResources = true; 239 mConvertToDex = true; 240 mBuildFinalPackage = true; 241 } else { 242 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 243 Messages.Start_Inc_Apk_Build); 244 245 // go through the resources and see if something changed. 246 IResourceDelta delta = getDelta(project); 247 if (delta == null) { 248 // no delta? Same as full build: we do all the steps. 249 mPackageResources = true; 250 mConvertToDex = true; 251 mBuildFinalPackage = true; 252 } else { 253 254 if (ResourceManager.isAutoBuilding() && AdtPrefs.getPrefs().isLintOnSave()) { 255 // Check for errors on save/build, if enabled 256 LintDeltaProcessor.create().process(delta); 257 } 258 259 PatternBasedDeltaVisitor dv = new PatternBasedDeltaVisitor( 260 project, project, 261 "POST:Main"); 262 263 ChangedFileSet manifestCfs = ChangedFileSetHelper.getMergedManifestCfs(project); 264 dv.addSet(manifestCfs); 265 266 ChangedFileSet resCfs = ChangedFileSetHelper.getResCfs(project); 267 dv.addSet(resCfs); 268 269 ChangedFileSet androidCodeCfs = ChangedFileSetHelper.getCodeCfs(project); 270 dv.addSet(androidCodeCfs); 271 272 ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(project); 273 dv.addSet(javaResCfs); 274 dv.addSet(ChangedFileSetHelper.NATIVE_LIBS); 275 276 delta.accept(dv); 277 278 // save the state 279 mPackageResources |= dv.checkSet(manifestCfs) || dv.checkSet(resCfs); 280 281 mConvertToDex |= dv.checkSet(androidCodeCfs); 282 283 mBuildFinalPackage |= dv.checkSet(javaResCfs) || 284 dv.checkSet(ChangedFileSetHelper.NATIVE_LIBS); 285 } 286 287 // check the libraries 288 if (libProjects.size() > 0) { 289 for (IProject libProject : libProjects) { 290 delta = getDelta(libProject); 291 if (delta != null) { 292 PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor( 293 project, libProject, 294 "POST:Lib"); 295 296 ChangedFileSet libResCfs = ChangedFileSetHelper.getFullResCfs( 297 libProject); 298 visitor.addSet(libResCfs); 299 visitor.addSet(ChangedFileSetHelper.NATIVE_LIBS); 300 // FIXME: add check on the library.jar? 301 302 delta.accept(visitor); 303 304 mPackageResources |= visitor.checkSet(libResCfs); 305 mBuildFinalPackage |= visitor.checkSet( 306 ChangedFileSetHelper.NATIVE_LIBS); 307 } 308 } 309 } 310 311 // also go through the delta for all the referenced projects 312 final int referencedCount = referencedJavaProjects.size(); 313 for (int i = 0 ; i < referencedCount; i++) { 314 IJavaProject referencedJavaProject = referencedJavaProjects.get(i); 315 delta = getDelta(referencedJavaProject.getProject()); 316 if (delta != null) { 317 PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor( 318 project, referencedJavaProject.getProject(), 319 "POST:RefedProject"); 320 321 ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(project); 322 visitor.addSet(javaResCfs); 323 324 ChangedFileSet bytecodeCfs = ChangedFileSetHelper.getByteCodeCfs(project); 325 visitor.addSet(bytecodeCfs); 326 327 delta.accept(visitor); 328 329 // save the state 330 mConvertToDex |= visitor.checkSet(bytecodeCfs); 331 mBuildFinalPackage |= visitor.checkSet(javaResCfs); 332 } 333 } 334 } 335 336 // store the build status in the persistent storage 337 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 338 saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); 339 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 340 341 // Top level check to make sure the build can move forward. Only do this after recording 342 // delta changes. 343 abortOnBadSetup(javaProject); 344 345 // remove older packaging markers. 346 removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING); 347 348 if (androidOutputFolder == null) { 349 // mark project and exit 350 markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output, 351 IMarker.SEVERITY_ERROR); 352 return allRefProjects; 353 } 354 355 // finished with the common init and tests. Special case of the library. 356 if (isLibrary) { 357 // check the jar output file is present, if not create it. 358 IFile jarIFile = androidOutputFolder.getFile( 359 project.getName().toLowerCase() + SdkConstants.DOT_JAR); 360 if (mConvertToDex == false && jarIFile.exists() == false) { 361 mConvertToDex = true; 362 } 363 364 // also update the crunch cache always since aapt does it smartly only 365 // on the files that need it. 366 if (DEBUG_LOG) { 367 AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName()); 368 } 369 BuildHelper helper = new BuildHelper(project, 370 mOutStream, mErrStream, 371 true /*debugMode*/, 372 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, 373 mResourceMarker); 374 updateCrunchCache(project, helper); 375 376 // refresh recursively bin/res folder 377 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 378 379 if (mConvertToDex) { // in this case this means some class files changed and 380 // we need to update the jar file. 381 if (DEBUG_LOG) { 382 AdtPlugin.log(IStatus.INFO, "%s updating jar!", project.getName()); 383 } 384 385 // resource to the AndroidManifest.xml file 386 IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); 387 String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile)); 388 389 IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project); 390 391 writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder); 392 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false); 393 394 // refresh the bin folder content with no recursion to update the library 395 // jar file. 396 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 397 398 // Also update the projects. The only way to force recompile them is to 399 // reset the library container. 400 List<ProjectState> parentProjects = projectState.getParentProjects(); 401 LibraryClasspathContainerInitializer.updateProject(parentProjects); 402 } 403 404 return allRefProjects; 405 } 406 407 // Check to see if we're going to launch or export. If not, we can skip 408 // the packaging and dexing process. 409 if (!args.containsKey(POST_C_REQUESTED) 410 && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { 411 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 412 Messages.Skip_Post_Compiler); 413 return allRefProjects; 414 } else { 415 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 416 Messages.Start_Full_Post_Compiler); 417 } 418 419 // first thing we do is check that the SDK directory has been setup. 420 String osSdkFolder = AdtPlugin.getOsSdkFolder(); 421 422 if (osSdkFolder.length() == 0) { 423 // this has already been checked in the precompiler. Therefore, 424 // while we do have to cancel the build, we don't have to return 425 // any error or throw anything. 426 return allRefProjects; 427 } 428 429 // do some extra check, in case the output files are not present. This 430 // will force to recreate them. 431 IResource tmp = null; 432 433 if (mPackageResources == false) { 434 // check the full resource package 435 tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_); 436 if (tmp == null || tmp.exists() == false) { 437 mPackageResources = true; 438 } 439 } 440 441 // check classes.dex is present. If not we force to recreate it. 442 if (mConvertToDex == false) { 443 tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX); 444 if (tmp == null || tmp.exists() == false) { 445 mConvertToDex = true; 446 } 447 } 448 449 // also check the final file(s)! 450 String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/); 451 if (mBuildFinalPackage == false) { 452 tmp = androidOutputFolder.findMember(finalPackageName); 453 if (tmp == null || (tmp instanceof IFile && 454 tmp.exists() == false)) { 455 String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName); 456 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 457 mBuildFinalPackage = true; 458 } 459 } 460 461 // at this point we know if we need to recreate the temporary apk 462 // or the dex file, but we don't know if we simply need to recreate them 463 // because they are missing 464 465 // refresh the output directory first 466 IContainer ic = androidOutputFolder.getParent(); 467 if (ic != null) { 468 ic.refreshLocal(IResource.DEPTH_ONE, monitor); 469 } 470 471 // Get the DX output stream. Since the builder is created for the life of the 472 // project, they can be kept around. 473 if (mOutStream == null) { 474 mOutStream = new AndroidPrintStream(project, null /*prefix*/, 475 AdtPlugin.getOutStream()); 476 mErrStream = new AndroidPrintStream(project, null /*prefix*/, 477 AdtPlugin.getOutStream()); 478 } 479 480 // we need to test all three, as we may need to make the final package 481 // but not the intermediary ones. 482 if (mPackageResources || mConvertToDex || mBuildFinalPackage) { 483 BuildHelper helper = new BuildHelper(project, 484 mOutStream, mErrStream, 485 true /*debugMode*/, 486 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, 487 mResourceMarker); 488 489 IPath androidBinLocation = androidOutputFolder.getLocation(); 490 if (androidBinLocation == null) { 491 markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing, 492 IMarker.SEVERITY_ERROR); 493 return allRefProjects; 494 } 495 String osAndroidBinPath = androidBinLocation.toOSString(); 496 497 // resource to the AndroidManifest.xml file 498 IFile manifestFile = androidOutputFolder.getFile( 499 SdkConstants.FN_ANDROID_MANIFEST_XML); 500 501 if (manifestFile == null || manifestFile.exists() == false) { 502 // mark project and exit 503 String msg = String.format(Messages.s_File_Missing, 504 SdkConstants.FN_ANDROID_MANIFEST_XML); 505 markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); 506 return allRefProjects; 507 } 508 509 // Remove the old .apk. 510 // This make sure that if the apk is corrupted, then dx (which would attempt 511 // to open it), will not fail. 512 String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName; 513 File finalPackage = new File(osFinalPackagePath); 514 515 // if delete failed, this is not really a problem, as the final package generation 516 // handle already present .apk, and if that one failed as well, the user will be 517 // notified. 518 finalPackage.delete(); 519 520 // Check if we need to package the resources. 521 if (mPackageResources) { 522 // also update the crunch cache always since aapt does it smartly only 523 // on the files that need it. 524 if (DEBUG_LOG) { 525 AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName()); 526 } 527 if (updateCrunchCache(project, helper) == false) { 528 return allRefProjects; 529 } 530 531 // refresh recursively bin/res folder 532 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 533 534 if (DEBUG_LOG) { 535 AdtPlugin.log(IStatus.INFO, "%s packaging resources!", project.getName()); 536 } 537 // remove some aapt_package only markers. 538 removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); 539 540 try { 541 helper.packageResources(manifestFile, libProjects, null /*resfilter*/, 542 0 /*versionCode */, osAndroidBinPath, 543 AdtConstants.FN_RESOURCES_AP_); 544 } catch (AaptExecException e) { 545 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 546 e.getMessage(), IMarker.SEVERITY_ERROR); 547 return allRefProjects; 548 } catch (AaptResultException e) { 549 // attempt to parse the error output 550 String[] aaptOutput = e.getOutput(); 551 boolean parsingError = AaptParser.parseOutput(aaptOutput, project); 552 553 // if we couldn't parse the output we display it in the console. 554 if (parsingError) { 555 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); 556 557 // if the exec failed, and we couldn't parse the error output (and 558 // therefore not all files that should have been marked, were marked), 559 // we put a generic marker on the project and abort. 560 BaseProjectHelper.markResource(project, 561 AdtConstants.MARKER_PACKAGING, 562 Messages.Unparsed_AAPT_Errors, 563 IMarker.SEVERITY_ERROR); 564 } 565 } 566 567 // build has been done. reset the state of the builder 568 mPackageResources = false; 569 570 // and store it 571 saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); 572 } 573 574 String classesDexPath = osAndroidBinPath + File.separator + 575 SdkConstants.FN_APK_CLASSES_DEX; 576 577 // then we check if we need to package the .class into classes.dex 578 if (mConvertToDex) { 579 if (DEBUG_LOG) { 580 AdtPlugin.log(IStatus.INFO, "%s running dex!", project.getName()); 581 } 582 try { 583 Collection<String> dxInputPaths = helper.getCompiledCodePaths(); 584 585 helper.executeDx(javaProject, dxInputPaths, classesDexPath); 586 } catch (DexException e) { 587 String message = e.getMessage(); 588 589 AdtPlugin.printErrorToConsole(project, message); 590 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 591 message, IMarker.SEVERITY_ERROR); 592 593 Throwable cause = e.getCause(); 594 595 if (cause instanceof NoClassDefFoundError 596 || cause instanceof NoSuchMethodError) { 597 AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning, 598 Messages.Requires_1_5_Error); 599 } 600 601 // dx failed, we return 602 return allRefProjects; 603 } 604 605 // build has been done. reset the state of the builder 606 mConvertToDex = false; 607 608 // and store it 609 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 610 } 611 612 // now we need to make the final package from the intermediary apk 613 // and classes.dex. 614 // This is the default package with all the resources. 615 616 try { 617 if (DEBUG_LOG) { 618 AdtPlugin.log(IStatus.INFO, "%s making final package!", project.getName()); 619 } 620 helper.finalDebugPackage( 621 osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_, 622 classesDexPath, osFinalPackagePath, libProjects, mResourceMarker); 623 } catch (KeytoolException e) { 624 String eMessage = e.getMessage(); 625 626 // mark the project with the standard message 627 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 628 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 629 IMarker.SEVERITY_ERROR); 630 631 // output more info in the console 632 AdtPlugin.printErrorToConsole(project, 633 msg, 634 String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), 635 Messages.ApkBuilder_Update_or_Execute_manually_s, 636 e.getCommandLine()); 637 638 return allRefProjects; 639 } catch (ApkCreationException e) { 640 String eMessage = e.getMessage(); 641 642 // mark the project with the standard message 643 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 644 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 645 IMarker.SEVERITY_ERROR); 646 } catch (AndroidLocationException e) { 647 String eMessage = e.getMessage(); 648 649 // mark the project with the standard message 650 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 651 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 652 IMarker.SEVERITY_ERROR); 653 } catch (NativeLibInJarException e) { 654 String msg = e.getMessage(); 655 656 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 657 msg, IMarker.SEVERITY_ERROR); 658 659 AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo()); 660 } catch (CoreException e) { 661 // mark project and return 662 String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); 663 AdtPlugin.printErrorToConsole(project, msg); 664 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 665 IMarker.SEVERITY_ERROR); 666 } catch (DuplicateFileException e) { 667 String msg1 = String.format( 668 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 669 e.getArchivePath(), e.getFile1(), e.getFile2()); 670 String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); 671 AdtPlugin.printErrorToConsole(project, msg2); 672 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2, 673 IMarker.SEVERITY_ERROR); 674 } 675 676 // we are done. 677 678 // refresh the bin folder content with no recursion. 679 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 680 681 // build has been done. reset the state of the builder 682 mBuildFinalPackage = false; 683 684 // and store it 685 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 686 687 // reset the installation manager to force new installs of this project 688 ApkInstallManager.getInstance().resetInstallationFor(project); 689 690 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), 691 "Build Success!"); 692 } 693 } catch (AbortBuildException e) { 694 return allRefProjects; 695 } catch (Exception exception) { 696 // try to catch other exception to actually display an error. This will be useful 697 // if we get an NPE or something so that we can at least notify the user that something 698 // went wrong. 699 700 // first check if this is a CoreException we threw to cancel the build. 701 if (exception instanceof CoreException) { 702 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) { 703 // Project is already marked with an error. Nothing to do 704 return allRefProjects; 705 } 706 } 707 708 String msg = exception.getMessage(); 709 if (msg == null) { 710 msg = exception.getClass().getCanonicalName(); 711 } 712 713 msg = String.format("Unknown error: %1$s", msg); 714 AdtPlugin.logAndPrintError(exception, project.getName(), msg); 715 markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); 716 } 717 718 // Benchmarking end 719 if (BuildHelper.BENCHMARK_FLAG) { 720 String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ 721 ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms"; //$NON-NLS-1$ 722 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 723 // End Overall Timer 724 msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ 725 (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$ 726 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 727 } 728 729 return allRefProjects; 730 } 731 732 private static class JarBuilder implements IArchiveBuilder { 733 734 private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$ 735 private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$ 736 737 private final byte[] buffer = new byte[1024]; 738 private final JarOutputStream mOutputStream; 739 private final String mAppPackage; 740 741 JarBuilder(JarOutputStream outputStream, String appPackage) { 742 mOutputStream = outputStream; 743 mAppPackage = appPackage.replace('.', '/'); 744 } 745 746 public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException { 747 // we only package class file from the output folder 748 if (SdkConstants.EXT_CLASS.equals(file.getFileExtension()) == false) { 749 return; 750 } 751 752 IPath packageApp = file.getParent().getFullPath().makeRelativeTo( 753 rootFolder.getFullPath()); 754 755 String name = file.getName(); 756 // Ignore the library's R/Manifest/BuildConfig classes. 757 if (mAppPackage.equals(packageApp.toString()) && 758 (BUILD_CONFIG_CLASS.equals(name) || 759 R_PATTERN.matcher(name).matches())) { 760 return; 761 } 762 763 IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath()); 764 try { 765 addFile(file.getContents(), file.getLocalTimeStamp(), path.toString()); 766 } catch (ApkCreationException e) { 767 throw e; 768 } catch (Exception e) { 769 throw new ApkCreationException(e, "Failed to add %s", file); 770 } 771 } 772 773 @Override 774 public void addFile(File file, String archivePath) throws ApkCreationException, 775 SealedApkException, DuplicateFileException { 776 try { 777 FileInputStream inputStream = new FileInputStream(file); 778 long lastModified = file.lastModified(); 779 addFile(inputStream, lastModified, archivePath); 780 } catch (ApkCreationException e) { 781 throw e; 782 } catch (Exception e) { 783 throw new ApkCreationException(e, "Failed to add %s", file); 784 } 785 } 786 787 private void addFile(InputStream content, long lastModified, String archivePath) 788 throws IOException, ApkCreationException { 789 // create the jar entry 790 JarEntry entry = new JarEntry(archivePath); 791 entry.setTime(lastModified); 792 793 try { 794 // add the entry to the jar archive 795 mOutputStream.putNextEntry(entry); 796 797 // read the content of the entry from the input stream, and write 798 // it into the archive. 799 int count; 800 while ((count = content.read(buffer)) != -1) { 801 mOutputStream.write(buffer, 0, count); 802 } 803 } finally { 804 try { 805 if (content != null) { 806 content.close(); 807 } 808 } catch (Exception e) { 809 throw new ApkCreationException(e, "Failed to close stream"); 810 } 811 } 812 } 813 } 814 815 /** 816 * Updates the crunch cache if needed and return true if the build must continue. 817 */ 818 private boolean updateCrunchCache(IProject project, BuildHelper helper) { 819 try { 820 helper.updateCrunchCache(); 821 } catch (AaptExecException e) { 822 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 823 e.getMessage(), IMarker.SEVERITY_ERROR); 824 return false; 825 } catch (AaptResultException e) { 826 // attempt to parse the error output 827 String[] aaptOutput = e.getOutput(); 828 boolean parsingError = AaptParser.parseOutput(aaptOutput, project); 829 // if we couldn't parse the output we display it in the console. 830 if (parsingError) { 831 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); 832 } 833 } 834 835 return true; 836 } 837 838 /** 839 * Writes the library jar file. 840 * @param jarIFile the destination file 841 * @param project the library project 842 * @param appPackage the library android package 843 * @param javaOutputFolder the JDT output folder. 844 */ 845 private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage, 846 IFolder javaOutputFolder) { 847 848 JarOutputStream jos = null; 849 try { 850 Manifest manifest = new Manifest(); 851 Attributes mainAttributes = manifest.getMainAttributes(); 852 mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$ 853 mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$ //$NON-NLS-2$ 854 jos = new JarOutputStream( 855 new FileOutputStream(jarIFile.getLocation().toFile()), manifest); 856 857 JarBuilder jarBuilder = new JarBuilder(jos, appPackage); 858 859 // write the class files 860 writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder); 861 862 // now write the standard Java resources from the output folder 863 ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile()); 864 865 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 866 } catch (Exception e) { 867 AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString()); 868 } finally { 869 if (jos != null) { 870 try { 871 jos.close(); 872 } catch (IOException e) { 873 // pass 874 } 875 } 876 } 877 } 878 879 private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder) 880 throws CoreException, IOException, ApkCreationException { 881 IResource[] members = folder.members(); 882 for (IResource member : members) { 883 if (member.getType() == IResource.FOLDER) { 884 writeClassFilesIntoJar(builder, (IFolder) member, rootFolder); 885 } else if (member.getType() == IResource.FILE) { 886 IFile file = (IFile) member; 887 builder.addFile(file, rootFolder); 888 } 889 } 890 } 891 892 @Override 893 protected void startupOnInitialize() { 894 super.startupOnInitialize(); 895 896 // load the build status. We pass true as the default value to 897 // force a recompile in case the property was not found 898 mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true); 899 mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true); 900 mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true); 901 } 902 903 @Override 904 protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException { 905 super.abortOnBadSetup(javaProject); 906 907 IProject iProject = getProject(); 908 909 // do a (hopefully quick) search for Precompiler type markers. Those are always only 910 // errors. 911 stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, 912 false /*checkSeverity*/); 913 stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, 914 false /*checkSeverity*/); 915 stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, 916 false /*checkSeverity*/); 917 stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, 918 false /*checkSeverity*/); 919 920 // do a search for JDT markers. Those can be errors or warnings 921 stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, 922 IResource.DEPTH_INFINITE, true /*checkSeverity*/); 923 stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, 924 IResource.DEPTH_INFINITE, true /*checkSeverity*/); 925 } 926} 927