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