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