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