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( 375 projectState, 376 mBuildToolInfo, 377 mOutStream, mErrStream, 378 false /*jumbo mode doesn't matter here*/, 379 false /*dex merger doesn't matter here*/, 380 true /*debugMode*/, 381 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, 382 mResourceMarker); 383 updateCrunchCache(project, helper); 384 385 // refresh recursively bin/res folder 386 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 387 388 if (mConvertToDex) { // in this case this means some class files changed and 389 // we need to update the jar file. 390 if (DEBUG_LOG) { 391 AdtPlugin.log(IStatus.INFO, "%s updating jar!", project.getName()); 392 } 393 394 // resource to the AndroidManifest.xml file 395 IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); 396 String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile)); 397 398 IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project); 399 400 writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder); 401 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false); 402 403 // refresh the bin folder content with no recursion to update the library 404 // jar file. 405 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 406 407 // Also update the projects. The only way to force recompile them is to 408 // reset the library container. 409 List<ProjectState> parentProjects = projectState.getParentProjects(); 410 LibraryClasspathContainerInitializer.updateProject(parentProjects); 411 } 412 413 return allRefProjects; 414 } 415 416 // Check to see if we're going to launch or export. If not, we can skip 417 // the packaging and dexing process. 418 if (!args.containsKey(POST_C_REQUESTED) 419 && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { 420 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 421 Messages.Skip_Post_Compiler); 422 return allRefProjects; 423 } else { 424 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 425 Messages.Start_Full_Post_Compiler); 426 } 427 428 // first thing we do is check that the SDK directory has been setup. 429 String osSdkFolder = AdtPlugin.getOsSdkFolder(); 430 431 if (osSdkFolder.length() == 0) { 432 // this has already been checked in the precompiler. Therefore, 433 // while we do have to cancel the build, we don't have to return 434 // any error or throw anything. 435 return allRefProjects; 436 } 437 438 // do some extra check, in case the output files are not present. This 439 // will force to recreate them. 440 IResource tmp = null; 441 442 if (mPackageResources == false) { 443 // check the full resource package 444 tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_); 445 if (tmp == null || tmp.exists() == false) { 446 mPackageResources = true; 447 } 448 } 449 450 // check classes.dex is present. If not we force to recreate it. 451 if (mConvertToDex == false) { 452 tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX); 453 if (tmp == null || tmp.exists() == false) { 454 mConvertToDex = true; 455 } 456 } 457 458 // also check the final file(s)! 459 String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/); 460 if (mBuildFinalPackage == false) { 461 tmp = androidOutputFolder.findMember(finalPackageName); 462 if (tmp == null || (tmp instanceof IFile && 463 tmp.exists() == false)) { 464 String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName); 465 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 466 mBuildFinalPackage = true; 467 } 468 } 469 470 // at this point we know if we need to recreate the temporary apk 471 // or the dex file, but we don't know if we simply need to recreate them 472 // because they are missing 473 474 // refresh the output directory first 475 IContainer ic = androidOutputFolder.getParent(); 476 if (ic != null) { 477 ic.refreshLocal(IResource.DEPTH_ONE, monitor); 478 } 479 480 // we need to test all three, as we may need to make the final package 481 // but not the intermediary ones. 482 if (mPackageResources || mConvertToDex || mBuildFinalPackage) { 483 String forceJumboStr = projectState.getProperty( 484 AdtConstants.DEX_OPTIONS_FORCEJUMBO); 485 Boolean jumbo = Boolean.valueOf(forceJumboStr); 486 487 String dexMergerStr = projectState.getProperty( 488 AdtConstants.DEX_OPTIONS_DISABLE_MERGER); 489 Boolean dexMerger = Boolean.valueOf(dexMergerStr); 490 491 BuildHelper helper = new BuildHelper( 492 projectState, 493 mBuildToolInfo, 494 mOutStream, mErrStream, 495 jumbo.booleanValue(), 496 dexMerger.booleanValue(), 497 true /*debugMode*/, 498 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, 499 mResourceMarker); 500 501 IPath androidBinLocation = androidOutputFolder.getLocation(); 502 if (androidBinLocation == null) { 503 markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing, 504 IMarker.SEVERITY_ERROR); 505 return allRefProjects; 506 } 507 String osAndroidBinPath = androidBinLocation.toOSString(); 508 509 // resource to the AndroidManifest.xml file 510 IFile manifestFile = androidOutputFolder.getFile( 511 SdkConstants.FN_ANDROID_MANIFEST_XML); 512 513 if (manifestFile == null || manifestFile.exists() == false) { 514 // mark project and exit 515 String msg = String.format(Messages.s_File_Missing, 516 SdkConstants.FN_ANDROID_MANIFEST_XML); 517 markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); 518 return allRefProjects; 519 } 520 521 // Remove the old .apk. 522 // This make sure that if the apk is corrupted, then dx (which would attempt 523 // to open it), will not fail. 524 String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName; 525 File finalPackage = new File(osFinalPackagePath); 526 527 // if delete failed, this is not really a problem, as the final package generation 528 // handle already present .apk, and if that one failed as well, the user will be 529 // notified. 530 finalPackage.delete(); 531 532 // Check if we need to package the resources. 533 if (mPackageResources) { 534 // also update the crunch cache always since aapt does it smartly only 535 // on the files that need it. 536 if (DEBUG_LOG) { 537 AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName()); 538 } 539 if (updateCrunchCache(project, helper) == false) { 540 return allRefProjects; 541 } 542 543 // refresh recursively bin/res folder 544 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 545 546 if (DEBUG_LOG) { 547 AdtPlugin.log(IStatus.INFO, "%s packaging resources!", project.getName()); 548 } 549 // remove some aapt_package only markers. 550 removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); 551 552 try { 553 helper.packageResources(manifestFile, libProjects, null /*resfilter*/, 554 0 /*versionCode */, osAndroidBinPath, 555 AdtConstants.FN_RESOURCES_AP_); 556 } catch (AaptExecException e) { 557 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 558 e.getMessage(), IMarker.SEVERITY_ERROR); 559 return allRefProjects; 560 } catch (AaptResultException e) { 561 // attempt to parse the error output 562 String[] aaptOutput = e.getOutput(); 563 boolean parsingError = AaptParser.parseOutput(aaptOutput, project); 564 565 // if we couldn't parse the output we display it in the console. 566 if (parsingError) { 567 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); 568 569 // if the exec failed, and we couldn't parse the error output (and 570 // therefore not all files that should have been marked, were marked), 571 // we put a generic marker on the project and abort. 572 BaseProjectHelper.markResource(project, 573 AdtConstants.MARKER_PACKAGING, 574 Messages.Unparsed_AAPT_Errors, 575 IMarker.SEVERITY_ERROR); 576 } 577 } 578 579 // build has been done. reset the state of the builder 580 mPackageResources = false; 581 582 // and store it 583 saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); 584 } 585 586 String classesDexPath = osAndroidBinPath + File.separator + 587 SdkConstants.FN_APK_CLASSES_DEX; 588 589 // then we check if we need to package the .class into classes.dex 590 if (mConvertToDex) { 591 if (DEBUG_LOG) { 592 AdtPlugin.log(IStatus.INFO, "%s running dex!", project.getName()); 593 } 594 try { 595 Collection<String> dxInputPaths = helper.getCompiledCodePaths(); 596 597 helper.executeDx(javaProject, dxInputPaths, classesDexPath); 598 } catch (DexException e) { 599 String message = e.getMessage(); 600 601 AdtPlugin.printErrorToConsole(project, message); 602 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 603 message, IMarker.SEVERITY_ERROR); 604 605 Throwable cause = e.getCause(); 606 607 if (cause instanceof NoClassDefFoundError 608 || cause instanceof NoSuchMethodError) { 609 AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning, 610 Messages.Requires_1_5_Error); 611 } 612 613 // dx failed, we return 614 return allRefProjects; 615 } 616 617 // build has been done. reset the state of the builder 618 mConvertToDex = false; 619 620 // and store it 621 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 622 } 623 624 // now we need to make the final package from the intermediary apk 625 // and classes.dex. 626 // This is the default package with all the resources. 627 628 try { 629 if (DEBUG_LOG) { 630 AdtPlugin.log(IStatus.INFO, "%s making final package!", project.getName()); 631 } 632 helper.finalDebugPackage( 633 osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_, 634 classesDexPath, osFinalPackagePath, libProjects, mResourceMarker); 635 } catch (KeytoolException e) { 636 String eMessage = e.getMessage(); 637 638 // mark the project with the standard message 639 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 640 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 641 IMarker.SEVERITY_ERROR); 642 643 // output more info in the console 644 AdtPlugin.printErrorToConsole(project, 645 msg, 646 String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), 647 Messages.ApkBuilder_Update_or_Execute_manually_s, 648 e.getCommandLine()); 649 650 AdtPlugin.log(e, msg); 651 652 return allRefProjects; 653 } catch (ApkCreationException e) { 654 String eMessage = e.getMessage(); 655 656 // mark the project with the standard message 657 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 658 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 659 IMarker.SEVERITY_ERROR); 660 661 AdtPlugin.log(e, msg); 662 } catch (AndroidLocationException e) { 663 String eMessage = e.getMessage(); 664 665 // mark the project with the standard message 666 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 667 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 668 IMarker.SEVERITY_ERROR); 669 AdtPlugin.log(e, msg); 670 } catch (NativeLibInJarException e) { 671 String msg = e.getMessage(); 672 673 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 674 msg, IMarker.SEVERITY_ERROR); 675 676 AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo()); 677 } catch (CoreException e) { 678 // mark project and return 679 String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); 680 AdtPlugin.printErrorToConsole(project, msg); 681 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, 682 IMarker.SEVERITY_ERROR); 683 AdtPlugin.log(e, msg); 684 } catch (DuplicateFileException e) { 685 String msg1 = String.format( 686 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 687 e.getArchivePath(), e.getFile1(), e.getFile2()); 688 String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); 689 AdtPlugin.printErrorToConsole(project, msg2); 690 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2, 691 IMarker.SEVERITY_ERROR); 692 } 693 694 // we are done. 695 696 // refresh the bin folder content with no recursion. 697 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 698 699 // build has been done. reset the state of the builder 700 mBuildFinalPackage = false; 701 702 // and store it 703 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 704 705 // reset the installation manager to force new installs of this project 706 ApkInstallManager.getInstance().resetInstallationFor(project); 707 708 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), 709 "Build Success!"); 710 } 711 } catch (AbortBuildException e) { 712 return allRefProjects; 713 } catch (Exception exception) { 714 // try to catch other exception to actually display an error. This will be useful 715 // if we get an NPE or something so that we can at least notify the user that something 716 // went wrong. 717 718 // first check if this is a CoreException we threw to cancel the build. 719 if (exception instanceof CoreException) { 720 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) { 721 // Project is already marked with an error. Nothing to do 722 return allRefProjects; 723 } 724 } 725 726 String msg = exception.getMessage(); 727 if (msg == null) { 728 msg = exception.getClass().getCanonicalName(); 729 } 730 731 msg = String.format("Unknown error: %1$s", msg); 732 AdtPlugin.logAndPrintError(exception, project.getName(), msg); 733 markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); 734 } 735 736 // Benchmarking end 737 if (BuildHelper.BENCHMARK_FLAG) { 738 String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ 739 ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms"; //$NON-NLS-1$ 740 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 741 // End Overall Timer 742 msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ 743 (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$ 744 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); 745 } 746 747 return allRefProjects; 748 } 749 750 private static class JarBuilder implements IArchiveBuilder { 751 752 private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$ 753 private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$ 754 755 private final byte[] buffer = new byte[1024]; 756 private final JarOutputStream mOutputStream; 757 private final String mAppPackage; 758 759 JarBuilder(JarOutputStream outputStream, String appPackage) { 760 mOutputStream = outputStream; 761 mAppPackage = appPackage.replace('.', '/'); 762 } 763 764 public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException { 765 // we only package class file from the output folder 766 if (SdkConstants.EXT_CLASS.equals(file.getFileExtension()) == false) { 767 return; 768 } 769 770 IPath packageApp = file.getParent().getFullPath().makeRelativeTo( 771 rootFolder.getFullPath()); 772 773 String name = file.getName(); 774 // Ignore the library's R/Manifest/BuildConfig classes. 775 if (mAppPackage.equals(packageApp.toString()) && 776 (BUILD_CONFIG_CLASS.equals(name) || 777 R_PATTERN.matcher(name).matches())) { 778 return; 779 } 780 781 IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath()); 782 try { 783 addFile(file.getContents(), file.getLocalTimeStamp(), path.toString()); 784 } catch (ApkCreationException e) { 785 throw e; 786 } catch (Exception e) { 787 throw new ApkCreationException(e, "Failed to add %s", file); 788 } 789 } 790 791 @Override 792 public void addFile(File file, String archivePath) throws ApkCreationException, 793 SealedApkException, DuplicateFileException { 794 try { 795 FileInputStream inputStream = new FileInputStream(file); 796 long lastModified = file.lastModified(); 797 addFile(inputStream, lastModified, archivePath); 798 } catch (ApkCreationException e) { 799 throw e; 800 } catch (Exception e) { 801 throw new ApkCreationException(e, "Failed to add %s", file); 802 } 803 } 804 805 private void addFile(InputStream content, long lastModified, String archivePath) 806 throws IOException, ApkCreationException { 807 // create the jar entry 808 JarEntry entry = new JarEntry(archivePath); 809 entry.setTime(lastModified); 810 811 try { 812 // add the entry to the jar archive 813 mOutputStream.putNextEntry(entry); 814 815 // read the content of the entry from the input stream, and write 816 // it into the archive. 817 int count; 818 while ((count = content.read(buffer)) != -1) { 819 mOutputStream.write(buffer, 0, count); 820 } 821 } finally { 822 try { 823 if (content != null) { 824 content.close(); 825 } 826 } catch (Exception e) { 827 throw new ApkCreationException(e, "Failed to close stream"); 828 } 829 } 830 } 831 } 832 833 /** 834 * Updates the crunch cache if needed and return true if the build must continue. 835 */ 836 private boolean updateCrunchCache(IProject project, BuildHelper helper) { 837 try { 838 helper.updateCrunchCache(); 839 } catch (AaptExecException e) { 840 BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, 841 e.getMessage(), IMarker.SEVERITY_ERROR); 842 return false; 843 } catch (AaptResultException e) { 844 // attempt to parse the error output 845 String[] aaptOutput = e.getOutput(); 846 boolean parsingError = AaptParser.parseOutput(aaptOutput, project); 847 // if we couldn't parse the output we display it in the console. 848 if (parsingError) { 849 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); 850 } 851 } 852 853 return true; 854 } 855 856 /** 857 * Writes the library jar file. 858 * @param jarIFile the destination file 859 * @param project the library project 860 * @param appPackage the library android package 861 * @param javaOutputFolder the JDT output folder. 862 */ 863 private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage, 864 IFolder javaOutputFolder) { 865 866 JarOutputStream jos = null; 867 try { 868 Manifest manifest = new Manifest(); 869 Attributes mainAttributes = manifest.getMainAttributes(); 870 mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$ 871 mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$ //$NON-NLS-2$ 872 jos = new JarOutputStream( 873 new FileOutputStream(jarIFile.getLocation().toFile()), manifest); 874 875 JarBuilder jarBuilder = new JarBuilder(jos, appPackage); 876 877 // write the class files 878 writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder); 879 880 // now write the standard Java resources from the output folder 881 ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile()); 882 883 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 884 } catch (Exception e) { 885 AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString()); 886 } finally { 887 if (jos != null) { 888 try { 889 jos.close(); 890 } catch (IOException e) { 891 // pass 892 } 893 } 894 } 895 } 896 897 private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder) 898 throws CoreException, IOException, ApkCreationException { 899 IResource[] members = folder.members(); 900 for (IResource member : members) { 901 if (member.getType() == IResource.FOLDER) { 902 writeClassFilesIntoJar(builder, (IFolder) member, rootFolder); 903 } else if (member.getType() == IResource.FILE) { 904 IFile file = (IFile) member; 905 builder.addFile(file, rootFolder); 906 } 907 } 908 } 909 910 @Override 911 protected void startupOnInitialize() { 912 super.startupOnInitialize(); 913 914 // load the build status. We pass true as the default value to 915 // force a recompile in case the property was not found 916 mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true); 917 mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true); 918 mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true); 919 } 920 921 @Override 922 protected void abortOnBadSetup( 923 @NonNull IJavaProject javaProject, 924 @Nullable ProjectState projectState) throws AbortBuildException, CoreException { 925 super.abortOnBadSetup(javaProject, projectState); 926 927 IProject iProject = getProject(); 928 929 // do a (hopefully quick) search for Precompiler type markers. Those are always only 930 // errors. 931 stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, 932 false /*checkSeverity*/); 933 stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, 934 false /*checkSeverity*/); 935 stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, 936 false /*checkSeverity*/); 937 stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, 938 false /*checkSeverity*/); 939 940 // do a search for JDT markers. Those can be errors or warnings 941 stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, 942 IResource.DEPTH_INFINITE, true /*checkSeverity*/); 943 stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, 944 IResource.DEPTH_INFINITE, true /*checkSeverity*/); 945 } 946 } 947